SqlAlchemy 2.0 中文文档(七十七)(5)

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群版 2核4GB 100GB
推荐场景:
搭建个人博客
云原生数据库 PolarDB MySQL 版,Serverless 5000PCU 100GB
简介: SqlAlchemy 2.0 中文文档(七十七)

SqlAlchemy 2.0 中文文档(七十七)(4)https://developer.aliyun.com/article/1561181


行为改进

改进应该不会产生兼容性问题,除非在极为罕见和不寻常的假设情况下,但如果有意外问题,了解这些改进是很好的。

许多 JOIN 和 LEFT OUTER JOIN 表达式将不再包含在 (SELECT * FROM …) AS ANON_1 中

多年来,SQLAlchemy ORM 一直无法在现有 JOIN 的右侧嵌套 JOIN(通常是 LEFT OUTER JOIN),因为内部 JOIN 通常可以被展平:

SELECT  a.*,  b.*,  c.*  FROM  a  LEFT  OUTER  JOIN  (b  JOIN  c  ON  b.id  =  c.id)  ON  a.id

这是因为 SQLite 直到版本3.7.16都无法解析上述格式的语句:

SQLite version 3.7.15.2 2013-01-09 11:53:05
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> create table a(id integer);
sqlite> create table b(id integer);
sqlite> create table c(id integer);
sqlite> select a.id, b.id, c.id from a left outer join (b join c on b.id=c.id) on b.id=a.id;
Error: no such column: b.id

右外连接当然是解决右侧括号化的另一种方法;这将显着复杂化并且视觉上不美观,但幸运的是 SQLite 也不支持 RIGHT OUTER JOIN 😃:

sqlite>  select  a.id,  b.id,  c.id  from  b  join  c  on  b.id=c.id
  ...>  right  outer  join  a  on  b.id=a.id;
Error:  RIGHT  and  FULL  OUTER  JOINs  are  not  currently  supported

早在 2005 年,其他数据库是否有问题尚不清楚,但今天看来,除 SQLite 外的每个测试数据库都支持它(Oracle 8  是一个非常老的数据库,根本不支持 JOIN 关键字,但 SQLAlchemy 一直为 Oracle  的语法制定了一个简单的重写方案)。更糟糕的是,SQLAlchemy 常规的解决方法在诸如 PostgreSQL 和 MySQL 等平台上应用  SELECT 通常会降低性能:

SELECT  a.*,  anon_1.*  FROM  a  LEFT  OUTER  JOIN  (
  SELECT  b.id  AS  b_id,  c.id  AS  c_id
  FROM  b  JOIN  c  ON  b.id  =  c.id
  )  AS  anon_1  ON  a.id=anon_1.b_id

当使用联接表继承结构时,像上面的 JOIN 形式是司空见惯的;每当使用Query.join()从某个父类连接到联接表子类,或者类似地使用joinedload(),SQLAlchemy 的 ORM 总是确保不会渲染嵌套 JOIN,以免查询无法在 SQLite 上运行。即使 Core 一直支持更紧凑形式的 JOIN,ORM 也必须避免它。

当在跨多对多关系上生成连接时,如果 ON 子句中存在特殊条件,将会出现另一个问题。考虑下面这样的 eager load 连接:

session.query(Order).outerjoin(Order.items)

假设从OrderItem的多对多关系实际上指的是一个子类Subitem,上述情况的 SQL 如下所示:

SELECT  order.id,  order.name
FROM  order  LEFT  OUTER  JOIN  order_item  ON  order.id  =  order_item.order_id
LEFT  OUTER  JOIN  item  ON  order_item.item_id  =  item.id  AND  item.type  =  'subitem'

上面的查询有什么问题?基本上,它会加载许多order / order_item行,其中item.type == 'subitem'的条件不成立。

从 SQLAlchemy 0.9 开始,采取了全新的方法。ORM 不再担心将 JOIN 嵌套在连接 JOIN 的右侧,现在会尽可能地渲染这些 JOIN,同时仍然返回正确的结果。当 SQL 语句被传递进行编译时,方言编译器将会重写 JOIN以适应目标后端,如果该后端已知不支持右嵌套 JOIN(目前只有 SQLite - 如果其他后端也有此问题,请告诉我们!)。

因此,现在通常会生成一个更简单的表达式:

SELECT  parent.id  AS  parent_id
FROM  parent  JOIN  (
  base_table  JOIN  subclass_table
  ON  base_table.id  =  subclass_table.id)  ON  parent.id  =  base_table.parent_id

使用像query(Parent).options(joinedload(Parent.subclasses))这样的 eager loads 会给每个表起别名,而不是包装在ANON_1中:

SELECT  parent.*,  base_table_1.*,  subclass_table_1.*  FROM  parent
  LEFT  OUTER  JOIN  (
  base_table  AS  base_table_1  JOIN  subclass_table  AS  subclass_table_1
  ON  base_table_1.id  =  subclass_table_1.id)
  ON  parent.id  =  base_table_1.parent_id

多对多连接和 eagerloads 将会将“secondary”和“right”表右嵌套:

SELECT  order.id,  order.name
FROM  order  LEFT  OUTER  JOIN
(order_item  JOIN  item  ON  order_item.item_id  =  item.id  AND  item.type  =  'subitem')
ON  order_item.order_id  =  order.id

所有这些连接,当与明确指定use_labels=TrueSelect语句一起渲染时,这对于 ORM 发出的所有查询都是真实的,都是“连接重写”的候选对象,这是将所有这些右嵌套连接重写为嵌套的 SELECT 语句的过程,同时保持Select使用的相同标签。因此,SQLite,即在 2013 年仍不支持这种非常常见的 SQL 语法的数据库,自身承担了额外的复杂性,上述查询被重写为:

-- sqlite only!
SELECT  parent.id  AS  parent_id
  FROM  parent  JOIN  (
  SELECT  base_table.id  AS  base_table_id,
  base_table.parent_id  AS  base_table_parent_id,
  subclass_table.id  AS  subclass_table_id
  FROM  base_table  JOIN  subclass_table  ON  base_table.id  =  subclass_table.id
  )  AS  anon_1  ON  parent.id  =  anon_1.base_table_parent_id
-- sqlite only!
SELECT  parent.id  AS  parent_id,  anon_1.subclass_table_1_id  AS  subclass_table_1_id,
  anon_1.base_table_1_id  AS  base_table_1_id,
  anon_1.base_table_1_parent_id  AS  base_table_1_parent_id
FROM  parent  LEFT  OUTER  JOIN  (
  SELECT  base_table_1.id  AS  base_table_1_id,
  base_table_1.parent_id  AS  base_table_1_parent_id,
  subclass_table_1.id  AS  subclass_table_1_id
  FROM  base_table  AS  base_table_1
  JOIN  subclass_table  AS  subclass_table_1  ON  base_table_1.id  =  subclass_table_1.id
)  AS  anon_1  ON  parent.id  =  anon_1.base_table_1_parent_id
-- sqlite only!
SELECT  "order".id  AS  order_id
FROM  "order"  LEFT  OUTER  JOIN  (
  SELECT  order_item_1.order_id  AS  order_item_1_order_id,
  order_item_1.item_id  AS  order_item_1_item_id,
  item.id  AS  item_id,  item.type  AS  item_type
FROM  order_item  AS  order_item_1
  JOIN  item  ON  item.id  =  order_item_1.item_id  AND  item.type  IN  (?)
)  AS  anon_1  ON  "order".id  =  anon_1.order_item_1_order_id

注意

从 SQLAlchemy 1.1 开始,此功能中存在的针对 SQLite 的解决方法将在检测到 SQLite 版本3.7.16或更高版本时自动禁用,因为 SQLite 已修复了对右嵌套连接的支持。

Join.alias()aliased()with_polymorphic()函数现在支持一个新参数,flat=True,用于构建别名的连接表实体而不嵌入到 SELECT 中。这个标志默认情况下是关闭的,以帮助向后兼容 - 但现在一个“多态”可选择可以作为目标连接而不生成任何子查询:

employee_alias = with_polymorphic(Person, [Engineer, Manager], flat=True)
session.query(Company).join(Company.employees.of_type(employee_alias)).filter(
    or_(Engineer.primary_language == "python", Manager.manager_name == "dilbert")
)

生成(除了 SQLite 之外的所有地方):

SELECT  companies.company_id  AS  companies_company_id,  companies.name  AS  companies_name
FROM  companies  JOIN  (
  people  AS  people_1
  LEFT  OUTER  JOIN  engineers  AS  engineers_1  ON  people_1.person_id  =  engineers_1.person_id
  LEFT  OUTER  JOIN  managers  AS  managers_1  ON  people_1.person_id  =  managers_1.person_id
)  ON  companies.company_id  =  people_1.company_id
WHERE  engineers.primary_language  =  %(primary_language_1)s
  OR  managers.manager_name  =  %(manager_name_1)s

#2369 #2587 ### 右嵌套内连接在连接的急切加载中可用

从版本 0.9.4 开始,上述提到的右嵌套连接可以在连接的急切加载中启用,在这种情况下,一个“外部”连接链接到右侧的“内部”连接。

通常,像下面这样的连接急切加载链:

query(User).options(
    joinedload("orders", innerjoin=False).joinedload("items", innerjoin=True)
)

不会产生内连接;因为从用户->订单的 LEFT OUTER JOIN,连接的急切加载不能使用从订单->项目的 INNER join,而不更改返回的用户行,并且会忽略“链接”innerjoin=True指令。0.9.0 应该交付的是,而不是:

FROM  users  LEFT  OUTER  JOIN  orders  ON  <onclause>  LEFT  OUTER  JOIN  items  ON  <onclause>

新的“右嵌套连接是可以的”逻辑会启动,我们会得到:

FROM  users  LEFT  OUTER  JOIN  (orders  JOIN  items  ON  <onclause>)  ON  <onclause>

由于我们错过了这一点,为了避免进一步的退化,我们通过将字符串"nested"指定给joinedload.innerjoin来添加上述功能:

query(User).options(
    joinedload("orders", innerjoin=False).joinedload("items", innerjoin="nested")
)

这个功能是在 0.9.4 中新增的。

#2976

ORM 可以高效地使用 RETURNING 获取刚生成的 INSERT/UPDATE 默认值

Mapper长期以来支持一个名为eager_defaults=True的未记录标志。这个标志的效果是,当进行  INSERT 或 UPDATE 时,如果知道行具有服务器生成的默认值,那么会立即跟随一个 SELECT  以“急切地”加载这些新值。通常,服务器生成的列会在对象上标记为“过期”,因此除非应用程序在刷新后立即访问这些列,否则不会产生任何开销。因此,eager_defaults标志并没有太大用处,因为它只会降低性能,并且只存在于支持需要默认值在刷新过程中立即可用的奇特事件方案中。

在 0.9 版本中,由于版本 id 增强,eager_defaults现在可以为这些值发出一个 RETURNING 子句,因此在具有强大 RETURNING 支持的后端,特别是 PostgreSQL 中,ORM 可以在 INSERT 或 UPDATE 中内联获取新生成的默认值和 SQL 表达式值。当启用eager_defaults时,当目标后端和Table支持“隐式返回”时,会自动使用 RETURNING。

子查询急加载将对某些查询的最内层 SELECT 应用 DISTINCT

为了减少在涉及到多对一关系时子查询急加载可能生成的重复行数,当连接的目标是不包含主键的列时,将在最内层的 SELECT 中应用 DISTINCT 关键字,就像在加载多对一关系时一样。

也就是说,在从 A->B 进行子查询加载时:

SELECT  b.id  AS  b_id,  b.name  AS  b_name,  anon_1.b_id  AS  a_b_id
FROM  (SELECT  DISTINCT  a_b_id  FROM  a)  AS  anon_1
JOIN  b  ON  b.id  =  anon_1.a_b_id

由于a.b_id是一个非唯一的外键,所以应用了 DISTINCT 以消除冗余的a.b_id。可以针对特定的relationship()使用distinct_target_key标志来无条件地打开或关闭此行为,将值设置为True表示无条件打开,False表示无条件关闭,None表示当目标 SELECT 针对不包含完整主键的列时才生效。在 0.9 版本中,None是默认值。

该选项也被回溯到了 0.8 版本,其中distinct_target_key选项的默认值为False

尽管此功能旨在通过消除重复行来提高性能,但 SQL 中的DISTINCT关键字本身可能会对性能产生负面影响。如果 SELECT 中的列没有索引,DISTINCT可能会对行集执行ORDER BY,这可能是昂贵的。通过将该功能限制在希望在任何情况下都具有索引的外键上,预计新的默认值是合理的。

该功能也不能消除每种可能的重复行情况;如果在连接链中的其他地方存在多对一关系,则可能仍然存在重复行。

#2836 ### Backref 处理程序现在可以传播超过一层

属性事件沿着它们的“发起者”传递的机制已经发生了变化;不再传递AttributeImpl,而是传递一个新的对象Event;这个对象同时指向AttributeImpl和一个“操作令牌”,表示操作是追加、删除还是替换操作。

属性事件系统不再查看这个“initiator”对象以阻止一系列递归属性事件。相反,防止由于相互依赖的返回处理程序而导致无限递归的系统已经移动到了  ORM 返回事件处理程序中,这些处理程序现在接管了确保一系列相互依赖事件(例如向集合 A.bs 添加,响应中设置多对一属性  B.a)不会进入无限递归流的角色。这里的理念是,给予返回系统更多的细节和对事件传播的控制,最终可以允许操作深于一个级别的发生;典型情况是集合追加导致多对一替换操作,然后应该导致从以前的集合中移除该项:

class Parent(Base):
    __tablename__ = "parent"
    id = Column(Integer, primary_key=True)
    children = relationship("Child", backref="parent")
class Child(Base):
    __tablename__ = "child"
    id = Column(Integer, primary_key=True)
    parent_id = Column(ForeignKey("parent.id"))
p1 = Parent()
p2 = Parent()
c1 = Child()
p1.children.append(c1)
assert c1.parent is p1  # backref event establishes c1.parent as p1
p2.children.append(c1)
assert c1.parent is p2  # backref event establishes c1.parent as p2
assert c1 not in p1.children  # second backref event removes c1 from p1.children

在此之前,在此更改之前,c1对象仍然存在于p1.children中,即使它同时也存在于p2.children中;返回处理程序将停止替换c1.parentp2而不是p1。在 0.9 版本中,使用更详细的Event对象以及让返回处理程序对这些对象做出更详细的决策,传播可以继续到从p1.children中移除c1,同时保持对传播进入无限递归循环的检查。

使用 AttributeEvents.set()AttributeEvents.append()AttributeEvents.remove() 事件的最终用户代码,并且作为这些事件的结果启动进一步的属性修改操作的可能需要进行修改,以防止递归循环,因为在没有返回事件处理程序的情况下,属性系统不再阻止一系列事件无休止地传播。此外,依赖于initiator值的代码将需要调整到新的 API,并且必须准备好在一系列由返回引发的事件中,initiator的值从其原始值更改为其他值,因为返回处理程序现在可能会为某些操作替换新的initiator值。

#2789 ### 类型系统现在处理呈现“文字绑定”值的任务

一个新的方法被添加到TypeEngine TypeEngine.literal_processor()以及TypeDecorator.process_literal_param(),用于处理所谓的“内联文字参数” - 通常呈现为“绑定”值的参数,但由于编译器配置的原因而被内联渲染到 SQL 语句中。此功能在生成构造如CheckConstraint的 DDL 时使用,以及当使用像 op.inline_literal() 这样的构造时,由 Alembic 使用。之前,一个简单的“isinstance”检查仅检查了几种基本类型,并且“绑定处理器”无条件地被使用,导致诸如字符串过早编码为 utf-8 等问题。

使用TypeDecorator编写的自定义类型应继续在“内联文字”场景中工作,因为TypeDecorator.process_literal_param()默认情况下回退到TypeDecorator.process_bind_param(),因为这些方法通常处理数据操作,而不是数据如何呈现给数据库。 TypeDecorator.process_literal_param()可以被指定为特别生成表示值应该如何呈现为内联 DDL 语句的字符串。

#2838 ### 架构标识符现在携带其自身的引号信息

此更改简化了核心对所谓的“引号”标志的使用,例如传递给TableColumnquote标志。该标志现在内部化在字符串名称本身中,现在表示为quoted_name的实例,一个字符串子类。IdentifierPreparer现在仅依赖于由quoted_name对象报告的引号偏好,而不再在大多数情况下检查任何显式的quote标志。此处解决的问题包括各种区分大小写的方法,如Engine.has_table()以及方言内的类似方法现在可以使用显式带引号的名称正常工作,而无需复杂化或引入与引号标志的细节相关的不兼容更改到这些  API(其中许多是第三方)- 特别是,更广泛范围的标识符现在可以与所谓的“大写”后端(如 Oracle、Firebird 和 DB2  等后端,这些后端使用全大写存储和报告表和列名称以用于不区分大小写的名称)正确地运行。

quoted_name对象根据需要在内部使用;但是,如果其他关键字需要固定引号偏好,则该类可公开使用。

#2812 ### 改进的布尔常量、NULL 常量、连接的渲染

新功能已添加到true()false()常量中,特别是与and_()or_()函数以及与这些类型、布尔类型总体以及null()常量一起使用的 WHERE/HAVING 子句的行为。

从这样的表格开始:

from sqlalchemy import Table, Boolean, Integer, Column, MetaData
t1 = Table("t", MetaData(), Column("x", Boolean()), Column("y", Integer))

在不支持true/false常量行为的后端上,选择构造现在将布尔列呈现为二进制表达式:

>>> from sqlalchemy import select, and_, false, true
>>> from sqlalchemy.dialects import mysql, postgresql
>>> print(select([t1]).where(t1.c.x).compile(dialect=mysql.dialect()))
SELECT  t.x,  t.y  FROM  t  WHERE  t.x  =  1 

and_()or_()构造现在将表现出准“短路”行为,即当存在true()false()常量时,截断呈现的表达式:

>>> print(
...     select([t1]).where(and_(t1.c.y > 5, false())).compile(dialect=postgresql.dialect())
... )
SELECT  t.x,  t.y  FROM  t  WHERE  false 

true()可以用作构建表达式的基础:

>>> expr = true()
>>> expr = expr & (t1.c.y > 5)
>>> print(select([t1]).where(expr))
SELECT  t.x,  t.y  FROM  t  WHERE  t.y  >  :y_1 

布尔常量true()false()本身在没有布尔常量的后端上呈现为0 = 11 = 1

>>> print(select([t1]).where(and_(t1.c.y > 5, false())).compile(dialect=mysql.dialect()))
SELECT  t.x,  t.y  FROM  t  WHERE  0  =  1 

None的解释,虽然不是特别有效的 SQL,但至少现在是一致的:

>>> print(select([t1.c.x]).where(None))
SELECT  t.x  FROM  t  WHERE  NULL
>>> print(select([t1.c.x]).where(None).where(None))
SELECT  t.x  FROM  t  WHERE  NULL  AND  NULL
>>> print(select([t1.c.x]).where(and_(None, None)))
SELECT  t.x  FROM  t  WHERE  NULL  AND  NULL 

#2804 ### 标签构造现在可以仅在 ORDER BY 中呈现为它们的名称

对于在 SELECT 的列子句和 ORDER BY 子句中都使用Label的情况,假设底层方言报告支持此功能,则标签将仅在 ORDER BY 子句中呈现为其名称。

例如一个示例如:

from sqlalchemy.sql import table, column, select, func
t = table("t", column("c1"), column("c2"))
expr = (func.foo(t.c.c1) + t.c.c2).label("expr")
stmt = select([expr]).order_by(expr)
print(stmt)

在 0.9 之前将呈现为:

SELECT  foo(t.c1)  +  t.c2  AS  expr
FROM  t  ORDER  BY  foo(t.c1)  +  t.c2

现在呈现为:

SELECT  foo(t.c1)  +  t.c2  AS  expr
FROM  t  ORDER  BY  expr

仅当标签未进一步嵌入到 ORDER BY 中的表达式中时,ORDER BY 才会呈现标签,除了简单的ASCDESC

上述格式在所有经过测试的数据库上都有效,但可能与旧数据库版本(MySQL 4?Oracle 8?等)存在兼容性问题。根据用户报告,我们可以添加规则,根据数据库版本检测禁用该功能。

#1068 ### RowProxy现在具有元组排序行为

RowProxy对象的行为很像元组,但直到现在,如果使用sorted()对它们的列表进行排序,它们不会像元组一样排序。现在__eq__()方法将两侧都作为元组进行比较,还添加了一个__lt__()方法:

users.insert().execute(
    dict(user_id=1, user_name="foo"),
    dict(user_id=2, user_name="bar"),
    dict(user_id=3, user_name="def"),
)
rows = users.select().order_by(users.c.user_name).execute().fetchall()
eq_(rows, [(2, "bar"), (3, "def"), (1, "foo")])
eq_(sorted(rows), [(1, "foo"), (2, "bar"), (3, "def")])

#2848 ### 当类型可用时,没有类型的 bindparam()构造会通过复制进行升级

将“升级”bindparam()构造以采用封闭表达式类型的逻辑已经以两种方式得到改进。首先,在分配新类型之前,会复制bindparam()对象,以便给定的bindparam()不会在原地改变。其次,在编译InsertUpdate构造时,会对通过ValuesBase.values()方法在语句中设置的“values”进行相同的操作。

如果给定一个未指定类型的bindparam()

bp = bindparam("some_col")

如果我们像下面这样使用这个参数:

expr = mytable.c.col == bp

对于bp的类型仍然是NullType,但是如果mytable.c.col的类型是String,那么expr.right,即二进制表达式的右侧,将采用String类型。以前,bp本身会被直接更改为String类型。

类似地,这个操作发生在InsertUpdate中:

stmt = mytable.update().values(col=bp)

在上面的例子中,bp保持不变,但当语句执行时将使用String类型,我们可以通过检查binds字典来看到这一点:

>>> compiled = stmt.compile()
>>> compiled.binds["some_col"].type
String

该功能允许自定义类型在 INSERT/UPDATE 语句中发挥其预期效果,而无需在每个bindparam()表达式中显式指定这些类型。

可能的向后兼容更改涉及两种不太可能的情况。由于绑定参数是克隆的,用户不应该依赖于对创建后的bindparam()构造进行原地更改。此外,使用InsertUpdate语句中的bindparam()的代码,如果依赖于bindparam()不根据分配给的列进行类型化,则不再以这种方式运行。

#2850 ### Columns can reliably get their type from a column referred to via ForeignKey

有一个长期存在的行为,它规定可以声明一个Column而不需要类型,只要这个Column被一个ForeignKeyConstraint所引用,并且被引用的列的类型会被复制到这个列中。问题在于,这个特性从来没有很好地工作过,并且没有得到维护。核心问题是ForeignKey对象在被询问之前不知道它引用的目标Column是哪一个,通常是第一次使用外键来构造一个Join时。因此,在那之前,父Column将没有类型,或者更具体地说,它将具有一个NullType的默认类型。

虽然花费了很长时间,重新组织ForeignKey对象的初始化工作已经完成,以便使这一功能最终能够令人满意地运行。这一变化的核心是,ForeignKey.column属性不再延迟初始化目标Column的位置;这一系统的问题在于,拥有的Column会一直被固定为NullType类型,直到ForeignKey被使用。

在新版本中,ForeignKey通过内部附加事件与最终将引用的Column协调,因此一旦引用的ColumnMetaData关联,所有引用它的ForeignKey对象都将收到一条消息,告诉它们需要初始化其父列。这一系统更加复杂,但更加稳固;作为奖励,现在已经为各种Column / ForeignKey配置场景设置了测试,并且错误消息已经改进,以便非常具体地指出不少于七种不同的错误条件。

现在可以正确工作的场景包括:

  1. 一旦目标Column与相同的MetaData关联,Column上的类型立即存在;无论哪一侧首先配置,这都能正常工作:
>>> from sqlalchemy import Table, MetaData, Column, Integer, ForeignKey
>>> metadata = MetaData()
>>> t2 = Table("t2", metadata, Column("t1id", ForeignKey("t1.id")))
>>> t2.c.t1id.type
NullType()
>>> t1 = Table("t1", metadata, Column("id", Integer, primary_key=True))
>>> t2.c.t1id.type
Integer()
  1. 系统现在也与ForeignKeyConstraint一起工作:
>>> from sqlalchemy import Table, MetaData, Column, Integer, ForeignKeyConstraint
>>> metadata = MetaData()
>>> t2 = Table(
...     "t2",
...     metadata,
...     Column("t1a"),
...     Column("t1b"),
...     ForeignKeyConstraint(["t1a", "t1b"], ["t1.a", "t1.b"]),
... )
>>> t2.c.t1a.type
NullType()
>>> t2.c.t1b.type
NullType()
>>> t1 = Table(
...     "t1",
...     metadata,
...     Column("a", Integer, primary_key=True),
...     Column("b", Integer, primary_key=True),
... )
>>> t2.c.t1a.type
Integer()
>>> t2.c.t1b.type
Integer()
  1. 它甚至适用于“多跳” - 也就是,一个ForeignKey指向一个Column再指向另一个Column
>>> from sqlalchemy import Table, MetaData, Column, Integer, ForeignKey
>>> metadata = MetaData()
>>> t2 = Table("t2", metadata, Column("t1id", ForeignKey("t1.id")))
>>> t3 = Table("t3", metadata, Column("t2t1id", ForeignKey("t2.t1id")))
>>> t2.c.t1id.type
NullType()
>>> t3.c.t2t1id.type
NullType()
>>> t1 = Table("t1", metadata, Column("id", Integer, primary_key=True))
>>> t2.c.t1id.type
Integer()
>>> t3.c.t2t1id.type
Integer()

#1765 ### 许多 JOIN 和 LEFT OUTER JOIN 表达式将不再被包装在(SELECT * FROM …) AS ANON_1 中

多年来,SQLAlchemy ORM 一直无法将 JOIN 嵌套在现有 JOIN 的右侧(通常是 LEFT OUTER JOIN,因为 INNER JOIN 始终可以被展平):

SELECT  a.*,  b.*,  c.*  FROM  a  LEFT  OUTER  JOIN  (b  JOIN  c  ON  b.id  =  c.id)  ON  a.id

这是因为 SQLite 直到版本3.7.16之前无法解析上述格式的语句:

SQLite version 3.7.15.2 2013-01-09 11:53:05
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> create table a(id integer);
sqlite> create table b(id integer);
sqlite> create table c(id integer);
sqlite> select a.id, b.id, c.id from a left outer join (b join c on b.id=c.id) on b.id=a.id;
Error: no such column: b.id

右外连接当然是另一种解决右侧括号化的方法;这将会显著复杂化并且视觉上不愉快去实现,但幸运的是 SQLite 也不支持 RIGHT OUTER JOIN 😃:

sqlite>  select  a.id,  b.id,  c.id  from  b  join  c  on  b.id=c.id
  ...>  right  outer  join  a  on  b.id=a.id;
Error:  RIGHT  and  FULL  OUTER  JOINs  are  not  currently  supported

回到 2005 年,其他数据库是否有问题这种形式并不清楚,但今天看来,除了 SQLite  之外的每个测试过的数据库现在都支持它(Oracle 8,一个非常古老的数据库,根本不支持 JOIN 关键字,但 SQLAlchemy 一直对  Oracle 的语法有一个简单的重写方案)。更糟糕的是,SQLAlchemy 通常的解决方法,即应用 SELECT,通常会降低像  PostgreSQL 和 MySQL 这样的平台的性能:

SELECT  a.*,  anon_1.*  FROM  a  LEFT  OUTER  JOIN  (
  SELECT  b.id  AS  b_id,  c.id  AS  c_id
  FROM  b  JOIN  c  ON  b.id  =  c.id
  )  AS  anon_1  ON  a.id=anon_1.b_id

像上述形式的 JOIN 在处理联接表继承结构时很常见;每当使用Query.join()从某个父类连接到联接表子类,或者类似地使用joinedload()时,SQLAlchemy 的 ORM 总是确保不会呈现嵌套的 JOIN,以免查询无法在 SQLite 上运行。即使 Core 始终支持更紧凑形式的 JOIN,ORM 也必须避免使用它。

当在 ON 子句中存在特殊条件时,跨多对多关系生成连接时会出现另一个问题。考虑像下面这样的急切加载连接:

session.query(Order).outerjoin(Order.items)

假设从OrderItem的多对多实际上指的是像Subitem这样的子类,上述情况的 SQL 将如下所示:

SELECT  order.id,  order.name
FROM  order  LEFT  OUTER  JOIN  order_item  ON  order.id  =  order_item.order_id
LEFT  OUTER  JOIN  item  ON  order_item.item_id  =  item.id  AND  item.type  =  'subitem'

上述查询有什么问题?基本上,它将加载许多order / order_item行,其中item.type == 'subitem'的条件不成立。

从 SQLAlchemy 0.9 开始,采取了一种全新的方法。ORM 不再担心在封闭连接的右侧嵌套 JOIN,并且现在将尽可能经常地呈现这些,同时仍然返回正确的结果。当 SQL 语句被传递进行编译时,方言编译器将会重写连接以适应目标后端,如果该后端已知不支持右嵌套 JOIN(目前只有 SQLite - 如果其他后端有此问题,请告诉我们!)。

因此,一个常规的 query(Parent).join(Subclass) 现在通常会产生一个更简单的表达式:

SELECT  parent.id  AS  parent_id
FROM  parent  JOIN  (
  base_table  JOIN  subclass_table
  ON  base_table.id  =  subclass_table.id)  ON  parent.id  =  base_table.parent_id

query(Parent).options(joinedload(Parent.subclasses)) 这样的连接急切加载将为各个表创建别名,而不是包装在 ANON_1 中:

SELECT  parent.*,  base_table_1.*,  subclass_table_1.*  FROM  parent
  LEFT  OUTER  JOIN  (
  base_table  AS  base_table_1  JOIN  subclass_table  AS  subclass_table_1
  ON  base_table_1.id  =  subclass_table_1.id)
  ON  parent.id  =  base_table_1.parent_id

多对多连接和急切加载将右嵌套“次要”和“右”表:

SELECT  order.id,  order.name
FROM  order  LEFT  OUTER  JOIN
(order_item  JOIN  item  ON  order_item.item_id  =  item.id  AND  item.type  =  'subitem')
ON  order_item.order_id  =  order.id

所有这些连接,当与明确指定 use_labels=TrueSelect 语句一起呈现时,这对于 ORM 发出的所有查询都是真实的,都是“连接重写”的候选对象,这是将所有这些右嵌套连接重写为嵌套的 SELECT 语句的过程,同时保持与 Select 使用的相同标签。因此,SQLite,即使在 2013 年,仍然不支持这种非常常见的 SQL 语法,也要承担额外的复杂性,以上查询被重写为:

-- sqlite only!
SELECT  parent.id  AS  parent_id
  FROM  parent  JOIN  (
  SELECT  base_table.id  AS  base_table_id,
  base_table.parent_id  AS  base_table_parent_id,
  subclass_table.id  AS  subclass_table_id
  FROM  base_table  JOIN  subclass_table  ON  base_table.id  =  subclass_table.id
  )  AS  anon_1  ON  parent.id  =  anon_1.base_table_parent_id
-- sqlite only!
SELECT  parent.id  AS  parent_id,  anon_1.subclass_table_1_id  AS  subclass_table_1_id,
  anon_1.base_table_1_id  AS  base_table_1_id,
  anon_1.base_table_1_parent_id  AS  base_table_1_parent_id
FROM  parent  LEFT  OUTER  JOIN  (
  SELECT  base_table_1.id  AS  base_table_1_id,
  base_table_1.parent_id  AS  base_table_1_parent_id,
  subclass_table_1.id  AS  subclass_table_1_id
  FROM  base_table  AS  base_table_1
  JOIN  subclass_table  AS  subclass_table_1  ON  base_table_1.id  =  subclass_table_1.id
)  AS  anon_1  ON  parent.id  =  anon_1.base_table_1_parent_id
-- sqlite only!
SELECT  "order".id  AS  order_id
FROM  "order"  LEFT  OUTER  JOIN  (
  SELECT  order_item_1.order_id  AS  order_item_1_order_id,
  order_item_1.item_id  AS  order_item_1_item_id,
  item.id  AS  item_id,  item.type  AS  item_type
FROM  order_item  AS  order_item_1
  JOIN  item  ON  item.id  =  order_item_1.item_id  AND  item.type  IN  (?)
)  AS  anon_1  ON  "order".id  =  anon_1.order_item_1_order_id

注意

从 SQLAlchemy 1.1 开始,此功能中存在的针对 SQLite 的解决方法将在检测到 SQLite 版本 3.7.16 或更高版本时自动禁用自身,因为 SQLite 已修复了对右嵌套连接的支持。

Join.alias()aliased()with_polymorphic() 函数现在支持一个新参数 flat=True,用于构建连接表实体的别名而不嵌入到 SELECT 中。这个标志默认情况下是关闭的,以帮助向后兼容性 - 但现在一个“多态”可选择可以作为目标连接而不生成任何子查询:

employee_alias = with_polymorphic(Person, [Engineer, Manager], flat=True)
session.query(Company).join(Company.employees.of_type(employee_alias)).filter(
    or_(Engineer.primary_language == "python", Manager.manager_name == "dilbert")
)

生成(除了 SQLite 之外的所有地方):

SELECT  companies.company_id  AS  companies_company_id,  companies.name  AS  companies_name
FROM  companies  JOIN  (
  people  AS  people_1
  LEFT  OUTER  JOIN  engineers  AS  engineers_1  ON  people_1.person_id  =  engineers_1.person_id
  LEFT  OUTER  JOIN  managers  AS  managers_1  ON  people_1.person_id  =  managers_1.person_id
)  ON  companies.company_id  =  people_1.company_id
WHERE  engineers.primary_language  =  %(primary_language_1)s
  OR  managers.manager_name  =  %(manager_name_1)s

#2369 #2587

右嵌套内连接在连接的急切加载中可用

从版本 0.9.4 开始,在连接的急切加载情况下,可以启用上述提到的右嵌套连接,其中“外部”连接链接到右侧的“内部”连接。

通常,像下面这样的连接急切加载链:

query(User).options(
    joinedload("orders", innerjoin=False).joinedload("items", innerjoin=True)
)

不会产生内连接;由于从 user->order 的 LEFT OUTER JOIN,连接的急切加载无法使用从 order->items 的 INNER join 而不更改返回的用户行,并且会忽略“链接”的innerjoin=True指令。0.9.0 应该交付的是,而不是:

FROM  users  LEFT  OUTER  JOIN  orders  ON  <onclause>  LEFT  OUTER  JOIN  items  ON  <onclause>

新的“右嵌套连接是可以的”逻辑将启动,我们将得到:

FROM  users  LEFT  OUTER  JOIN  (orders  JOIN  items  ON  <onclause>)  ON  <onclause>

由于我们错过了这一点,为了避免进一步的退化,我们通过将字符串"nested"指定给joinedload.innerjoin来添加了上述功能:

query(User).options(
    joinedload("orders", innerjoin=False).joinedload("items", innerjoin="nested")
)

此功能是在 0.9.4 中新增的。

#2976

ORM 可以使用 RETURNING 高效地获取刚生成的 INSERT/UPDATE 默认值

Mapper长期以来支持一个名为eager_defaults=True的未记录标志。此标志的效果是,当进行  INSERT 或 UPDATE 时,并且已知该行具有服务器生成的默认值时,将立即跟随 SELECT  以“急切地”加载这些新值。通常,服务器生成的列会在对象上标记为“过期”,因此除非应用程序在刷新后立即访问这些列,否则不会产生任何开销。因此,eager_defaults标志实际上没有太大用处,因为它只会降低性能,并且仅用于支持需要默认值在刷新过程中立即可用的奇特事件方案。

0.9 版本由于版本 ID 增强,eager_defaults现在可以为这些值发出一个 RETURNING 子句,因此在具有强大 RETURNING 支持的后端,特别是 PostgreSQL 上,ORM 可以在 INSERT 或 UPDATE 中内联获取新生成的默认值和 SQL 表达式值。eager_defaults在启用时,当目标后端和Table支持“隐式返回”时,会自动使用 RETURNING。

子查询急切加载将对某些查询的最内部 SELECT 应用 DISTINCT

为了减少涉及多对一关系时子查询急切加载可能生成的重复行数,当连接针对不包括主键的列时,将在最内部 SELECT 中应用 DISTINCT 关键字,例如在沿着多对一加载时。

也就是说,当在 A->B 的多对一上进行子查询加载时:

SELECT  b.id  AS  b_id,  b.name  AS  b_name,  anon_1.b_id  AS  a_b_id
FROM  (SELECT  DISTINCT  a_b_id  FROM  a)  AS  anon_1
JOIN  b  ON  b.id  =  anon_1.a_b_id

由于a.b_id是一个非唯一的外键,因此应用了 DISTINCT,以消除冗余的a.b_id。可以通过在特定的relationship()上设置distinct_target_key标志来无条件地打开或关闭此行为,将值设置为True表示无条件打开,False表示无条件关闭,None表示当目标 SELECT 针对不包含完整主键的列时,该特性生效。在 0.9 版本中,None是默认值。

该选项也被回溯到了 0.8 版本,其中distinct_target_key选项的默认值为False

虽然这个特性旨在通过消除重复行来提高性能,但 SQL 中的DISTINCT关键字本身可能会对性能产生负面影响。如果 SELECT 中的列没有索引,DISTINCT可能会对行集执行ORDER BY,这可能是昂贵的。通过将该特性限制在希望在任何情况下都有索引的外键上,预计新的默认值是合理的。

该特性也不会消除每种可能的重复行情况;如果在连接链中的其他地方存在多对一关系,则可能仍然存在重复行。

#2836

反向引用处理程序现在可以传播超过一级深度

属性事件传递其“发起者”的机制已经发生了变化;不再传递AttributeImpl,而是传递一个新对象Event;这个对象同时指向AttributeImpl和一个“操作令牌”,表示操作是追加、移除还是替换操作。

属性事件系统不再查看这个“发起者”对象以阻止属性事件的递归系列。相反,为了防止由于相互依赖的反向引用处理程序而导致的无限递归,现在将这一系统移动到了  ORM 反向引用事件处理程序中,这些处理程序现在负责确保一系列相互依赖的事件(例如向集合 A.bs 追加,在响应中设置多对一属性  B.a)不会进入无限递归流。这里的理念是,反向引用系统,通过更详细和控制事件传播,最终可以允许超过一级深度的操作发生;典型情况是,当集合追加导致多对一替换操作时,这反过来应该导致项目从先前的集合中移除:

class Parent(Base):
    __tablename__ = "parent"
    id = Column(Integer, primary_key=True)
    children = relationship("Child", backref="parent")
class Child(Base):
    __tablename__ = "child"
    id = Column(Integer, primary_key=True)
    parent_id = Column(ForeignKey("parent.id"))
p1 = Parent()
p2 = Parent()
c1 = Child()
p1.children.append(c1)
assert c1.parent is p1  # backref event establishes c1.parent as p1
p2.children.append(c1)
assert c1.parent is p2  # backref event establishes c1.parent as p2
assert c1 not in p1.children  # second backref event removes c1 from p1.children

在此更改之前,c1 对象仍然会存在于p1.children中,即使它同时也存在于p2.children中;回引处理程序会在替换c1.parentp2而不是p1时停止。在 0.9 版本中,使用更详细的Event对象,让回引处理程序对这些对象做出更详细的决策,传播可以继续删除p1.children中的c1,同时保持检查以防止传播进入无限递归循环。

终端用户代码,a. 使用AttributeEvents.set()AttributeEvents.append()AttributeEvents.remove()事件,并且 b. 由于这些事件导致进一步的属性修改操作,可能需要修改以防止递归循环,因为在缺少回引事件处理程序的情况下,属性系统不再阻止事件链无限传播。此外,依赖于initiator值的代码将需要调整到新的 API,并且必须准备好initiator值在一系列由回引引发的事件中从其原始值更改,因为回引处理程序现在可能会为某些操作交换新的initiator值。

#2789

类型系统现在处理呈现“文字绑定”值的任务

TypeEngine添加了一个新方法TypeEngine.literal_processor()以及TypeDecorator.process_literal_param()用于TypeDecorator,它们负责呈现所谓的“内联文字参数” - 通常呈现为“绑定”值的参数,但由于编译器配置的原因而被内联呈现到 SQL 语句中。此功能用于生成诸如CheckConstraint等结构的 DDL,以及在使用诸如op.inline_literal()之类的结构时,被 Alembic 使用。以前,一个简单的“isinstance”检查检查了一些基本类型,并且“绑定处理程序”无条件地被使用,导致诸如字符串过早编码为 utf-8 等问题。

使用 TypeDecorator 编写的自定义类型应继续在“内联文字”场景中工作,因为 TypeDecorator.process_literal_param() 默认情况下会退回到 TypeDecorator.process_bind_param(),因为这些方法通常处理数据操作,而不是数据如何呈现给数据库。TypeDecorator.process_literal_param() 可以指定特定地生成一个表示值应如何呈现为内联 DDL 语句的字符串。

#2838

现在模式标识符携带自己的引号信息

此更改简化了 Core 对所谓的“引号”标志的使用,例如传递给 TableColumnquote 标志。该标志现在内部化在字符串名称本身中,现在表示为 quoted_name 的实例,一个字符串子类。IdentifierPreparer 现在仅依赖于由 quoted_name 对象报告的引号偏好,而不是在大多数情况下检查任何显式的 quote 标志。此处解决的问题包括,各种区分大小写的方法,如 Engine.has_table() 以及方言内的类似方法现在可以使用显式引号名称正常工作,而无需复杂化或引入对这些  API(其中许多是第三方的)的引号标志的变更。特别是,更广泛范围的标识符现在可以与所谓的“大写”后端(如 Oracle、Firebird 和  DB2)正确地工作,这些后端使用全大写存储和报告不区分大小写的名称的表和列名称。

quoted_name 对象在需要时在内部使用;然而,如果其他关键字需要固定引号偏好,该类是公开可用的。

#2812

改进的布尔常量、NULL 常量、连接的���现

新功能已添加到true()false()常量中,特别是与and_()or_()函数以及与这些类型、布尔类型总体以及null()常量一起使用时的 WHERE/HAVING 子句的行为。

从这样的表开始:

from sqlalchemy import Table, Boolean, Integer, Column, MetaData
t1 = Table("t", MetaData(), Column("x", Boolean()), Column("y", Integer))

在不具有true/false常量行为的后端上,select 构造现在将将布尔列呈现为二进制表达式:

>>> from sqlalchemy import select, and_, false, true
>>> from sqlalchemy.dialects import mysql, postgresql
>>> print(select([t1]).where(t1.c.x).compile(dialect=mysql.dialect()))
SELECT  t.x,  t.y  FROM  t  WHERE  t.x  =  1 

and_()or_()构造现在将表现出准“短路”行为,即当存在true()false()常量时,将截断呈现的表达式:

>>> print(
...     select([t1]).where(and_(t1.c.y > 5, false())).compile(dialect=postgresql.dialect())
... )
SELECT  t.x,  t.y  FROM  t  WHERE  false 

true()可以用作构建表达式的基础:

>>> expr = true()
>>> expr = expr & (t1.c.y > 5)
>>> print(select([t1]).where(expr))
SELECT  t.x,  t.y  FROM  t  WHERE  t.y  >  :y_1 

布尔常量true()false()本身在没有布尔常量的后端上呈现为0 = 11 = 1

>>> print(select([t1]).where(and_(t1.c.y > 5, false())).compile(dialect=mysql.dialect()))
SELECT  t.x,  t.y  FROM  t  WHERE  0  =  1 

None的解释,虽然不是特别有效的 SQL,但至少现在是一致的:

>>> print(select([t1.c.x]).where(None))
SELECT  t.x  FROM  t  WHERE  NULL
>>> print(select([t1.c.x]).where(None).where(None))
SELECT  t.x  FROM  t  WHERE  NULL  AND  NULL
>>> print(select([t1.c.x]).where(and_(None, None)))
SELECT  t.x  FROM  t  WHERE  NULL  AND  NULL 

#2804

Label 构造现在可以在 ORDER BY 中仅呈现为其名称

对于在 SELECT 的列子句和 ORDER BY 子句中都使用Label的情况,假设底层方言报告支持此功能,则标签将仅在 ORDER BY 子句中呈现为其名称。

例如,一个示例:

from sqlalchemy.sql import table, column, select, func
t = table("t", column("c1"), column("c2"))
expr = (func.foo(t.c.c1) + t.c.c2).label("expr")
stmt = select([expr]).order_by(expr)
print(stmt)

在 0.9 之前会呈现为:

SELECT  foo(t.c1)  +  t.c2  AS  expr
FROM  t  ORDER  BY  foo(t.c1)  +  t.c2

现在呈现为:

SELECT  foo(t.c1)  +  t.c2  AS  expr
FROM  t  ORDER  BY  expr

仅当标签未进一步嵌入到 ORDER BY 中的表达式中时,ORDER BY 才会呈现标签,除了简单的ASCDESC

上述格式在所有经过测试的数据库上都有效,但可能与旧版本数据库(MySQL 4?Oracle 8?等)存在兼容性问题。根据用户报告,我们可以添加规则,根据数据库版本检测来禁用该功能。

#1068

RowProxy现在具有元组排序行为

RowProxy对象的行为很像元组,但直到现在,如果使用sorted()对它们的列表进行排序,它们将不会作为元组进行排序。现在,__eq__()方法会将两边都作为元组进行比较,同时还添加了一个__lt__()方法:

users.insert().execute(
    dict(user_id=1, user_name="foo"),
    dict(user_id=2, user_name="bar"),
    dict(user_id=3, user_name="def"),
)
rows = users.select().order_by(users.c.user_name).execute().fetchall()
eq_(rows, [(2, "bar"), (3, "def"), (1, "foo")])
eq_(sorted(rows), [(1, "foo"), (2, "bar"), (3, "def")])

#2848

当类型可用时,bindparam()构造不带类型的通过复制进行升级

对于将bindparam()构造升级为采用封闭表达式类型的逻辑已经有了两方面的改进。首先,在分配新类型之前,bindparam()对象会被复制,以便给定的bindparam()不会在原地改变。其次,当编译InsertUpdate构造时,通过ValuesBase.values()方法在语句中设置的“values”也会发生相同的操作。

如果给定一个未类型化的bindparam()

bp = bindparam("some_col")

如果我们使用这个参数如下:

expr = mytable.c.col == bp

对于bp的类型仍然是NullType,但是如果mytable.c.colString类型,则expr.right,即二进制表达式的右侧,将采用String类型。以前,bp本身会被直接更改为具有String类型。

类似地,这个操作发生在InsertUpdate中:

stmt = mytable.update().values(col=bp)

在上面的例子中,bp保持不变,但当语句执行时将使用String类型,我们可以通过检查binds字典来看到这一点:

>>> compiled = stmt.compile()
>>> compiled.binds["some_col"].type
String

该功能允许自定义类型在 INSERT/UPDATE 语句中产生预期效果,而无需在每个bindparam()表达式中显式指定这些类型。

潜在的向后兼容更改涉及两种不太可能的情况。由于绑定参数是克隆的,用户不应该依赖于对一旦创建的bindparam()构造进行就地更改。此外,使用bindparam()的代码在InsertUpdate语句中,依赖于bindparam()未根据分配给的列进行类型化的事实,将不再以这种方式运行。

#2850

列可以可靠地从通过 ForeignKey 引用的列获取其类型

存在一个长期存在的行为,即可以声明不带类型的Column,只要该ColumnForeignKeyConstraint引用,并且引用列的类型将被复制到此列中。问题在于,这个功能从未很好地工作过,并且没有得到维护。核心问题在于,ForeignKey对象在被要求之前不知道它引用的目标Column,通常是第一次外键用于构造Join时。因此,在那之前,父Column将没有类型,或者更具体地说,它将具有默认类型NullType

虽然花了很长时间,但重新组织 ForeignKey 对象初始化的工作已经完成,以便这个功能最终可以令人满意地工作。 这个变化的核心是 ForeignKey.column 属性不再延迟初始化目标 Column 的位置;这个系统的问题是拥有的 Column 会被困在 NullType 作为其类型,直到 ForeignKey 被使用。

在新版本中,ForeignKey 与最终将引用的 Column 协调使用内部附加事件,因此一旦引用的 ColumnMetaData 关联,所有引用它的 ForeignKey 对象都会收到一条消息,告诉它们需要初始化其父列。 这个系统更复杂,但更可靠;作为奖励,现在已经为各种 Column / ForeignKey 配置方案设置了测试,并且错误消息已经改进为非常具体,涵盖了不少于七种不同的错误条件。

现在正确工作的场景包括:

  1. Column 上的类型会在目标 Column 与相同的 MetaData 关联后立即出现;无论哪一边先配置都可以:
>>> from sqlalchemy import Table, MetaData, Column, Integer, ForeignKey
>>> metadata = MetaData()
>>> t2 = Table("t2", metadata, Column("t1id", ForeignKey("t1.id")))
>>> t2.c.t1id.type
NullType()
>>> t1 = Table("t1", metadata, Column("id", Integer, primary_key=True))
>>> t2.c.t1id.type
Integer()
  1. 系统现在也可以与 ForeignKeyConstraint 一起工作:
>>> from sqlalchemy import Table, MetaData, Column, Integer, ForeignKeyConstraint
>>> metadata = MetaData()
>>> t2 = Table(
...     "t2",
...     metadata,
...     Column("t1a"),
...     Column("t1b"),
...     ForeignKeyConstraint(["t1a", "t1b"], ["t1.a", "t1.b"]),
... )
>>> t2.c.t1a.type
NullType()
>>> t2.c.t1b.type
NullType()
>>> t1 = Table(
...     "t1",
...     metadata,
...     Column("a", Integer, primary_key=True),
...     Column("b", Integer, primary_key=True),
... )
>>> t2.c.t1a.type
Integer()
>>> t2.c.t1b.type
Integer()
  1. 它甚至适用于“多次跳跃” - 即,一个ForeignKey指向一个Column,该列指向另一个Column:
>>> from sqlalchemy import Table, MetaData, Column, Integer, ForeignKey
>>> metadata = MetaData()
>>> t2 = Table("t2", metadata, Column("t1id", ForeignKey("t1.id")))
>>> t3 = Table("t3", metadata, Column("t2t1id", ForeignKey("t2.t1id")))
>>> t2.c.t1id.type
NullType()
>>> t3.c.t2t1id.type
NullType()
>>> t1 = Table("t1", metadata, Column("id", Integer, primary_key=True))
>>> t2.c.t1id.type
Integer()
>>> t3.c.t2t1id.type
Integer()

#1765

方言变更

Firebird fdb 现在是默认的 Firebird 方言。

如果创建引擎时没有指定方言,即 firebird://,则现在使用 fdb 方言。fdb 是一个兼容 kinterbasdb 的 DBAPI,根据 Firebird 项目,现在是他们官方的 Python 驱动程序。

#2504

Firebird fdbkinterbasdb 默认设置 retaining=False

fdbkinterbasdb DBAPI 都支持一个标志 retaining=True,可以传递给其连接的 commit()rollback() 方法。文档中对此标志的解释是,DBAPI 可以重新使用内部事务状态进行后续事务,以提高性能。但是,较新的文档提到了 Firebird 的“垃圾收集”的分析,表明此标志可能对数据库的处理清理任务的能力产生负面影响,并且因此报告了性能的降低

鉴于此信息,目前不清楚此标志实际上如何可用,并且由于它似乎仅是一种性能增强功能,因此现在默认设置为 False。可以通过向create_engine()调用传递标志 retaining=True 来控制值。这是从 0.8.2 开始添加的新标志,因此在 0.8.2 上的应用程序可以根据需要将其设置为 TrueFalse

另见

sqlalchemy.dialects.firebird.fdb

sqlalchemy.dialects.firebird.kinterbasdb

pythonhosted.org/fdb/usage-guide.html#retaining-transactions - 有关“保留”标志的信息。

#2763

Firebird fdb 现在是默认的 Firebird 方言。

如果创建引擎时没有指定方言,即 firebird://,则现在使用 fdb 方言。fdb 是一个兼容 kinterbasdb 的 DBAPI,根据 Firebird 项目,现在是他们官方的 Python 驱动程序。

#2504

Firebird fdbkinterbasdb 默认设置 retaining=False

fdbkinterbasdb两个 DBAPI 都支持一个名为retaining=True的标志,可以传递给其连接的commit()rollback()方法。文档中对这个标志的理由是,DBAPI 可以重用内部事务状态以用于后续事务,以提高性能。然而,更新的文档提到了对 Firebird 的“垃圾回收”的分析,表明这个标志可能会对数据库处理清理任务的能力产生负面影响,并因此被报告为降低性能。

鉴于这些信息,目前尚不清楚如何实际使用这个标志,而且由于它似乎只是一个性能增强功能,现在默认值为False。可以通过在create_engine()调用中传递标志retaining=True来控制该值。这是一个新标志,从 0.8.2 版本开始添加,因此在 0.8.2 上的应用程序可以根据需要将其设置为TrueFalse

另请参阅

sqlalchemy.dialects.firebird.fdb

sqlalchemy.dialects.firebird.kinterbasdb

pythonhosted.org/fdb/usage-guide.html#retaining-transactions - 有关“retaining”标志的信息。

#2763

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
2天前
|
SQL 关系型数据库 MySQL
SqlAlchemy 2.0 中文文档(七十六)(2)
SqlAlchemy 2.0 中文文档(七十六)
17 2
|
2天前
|
SQL 关系型数据库 MySQL
SqlAlchemy 2.0 中文文档(七十六)(1)
SqlAlchemy 2.0 中文文档(七十六)
11 2
|
2天前
|
SQL 关系型数据库 MySQL
SqlAlchemy 2.0 中文文档(七十九)(5)
SqlAlchemy 2.0 中文文档(七十九)
9 2
|
2天前
|
SQL 关系型数据库 数据库
SqlAlchemy 2.0 中文文档(七十七)(2)
SqlAlchemy 2.0 中文文档(七十七)
9 0
|
2天前
|
SQL 关系型数据库 数据库
SqlAlchemy 2.0 中文文档(七十七)(1)
SqlAlchemy 2.0 中文文档(七十七)
9 0
|
2天前
|
SQL 关系型数据库 MySQL
SqlAlchemy 2.0 中文文档(七十七)(3)
SqlAlchemy 2.0 中文文档(七十七)
12 0
|
2天前
|
SQL JSON 关系型数据库
SqlAlchemy 2.0 中文文档(七十七)(4)
SqlAlchemy 2.0 中文文档(七十七)
10 0
|
2天前
|
SQL 测试技术 API
SqlAlchemy 2.0 中文文档(七十六)(4)
SqlAlchemy 2.0 中文文档(七十六)
11 0
|
2天前
|
SQL Oracle 关系型数据库
SqlAlchemy 2.0 中文文档(七十六)(5)
SqlAlchemy 2.0 中文文档(七十六)
12 0
|
2天前
|
SQL 关系型数据库 MySQL
SqlAlchemy 2.0 中文文档(七十六)(3)
SqlAlchemy 2.0 中文文档(七十六)
12 0