SqlAlchemy 2.0 中文文档(七十三)(3)https://developer.aliyun.com/article/1562219
关键行为变化 - ORM
Query.join() 更明确地处理决定“左”侧的歧义
从历史上看,给定以下查询:
u_alias = aliased(User) session.query(User, u_alias).join(Address)
鉴于标准教程映射,查询将生成一个 FROM 子句,如下所示:
SELECT ... FROM users AS users_1, users JOIN addresses ON users.id = addresses.user_id
也就是说,JOIN 会隐式地针对第一个匹配的实体。新的行为是要求解决这种模糊性的异常:
sqlalchemy.exc.InvalidRequestError: Can't determine which FROM clause to join from, there are multiple FROMS which can join to this entity. Try adding an explicit ON clause to help resolve the ambiguity.
解决方案是提供一个 ON 子句,可以是一个表达式:
# join to User session.query(User, u_alias).join(Address, Address.user_id == User.id) # join to u_alias session.query(User, u_alias).join(Address, Address.user_id == u_alias.id)
或者使用关系属性,如果可用的话:
# join to User session.query(User, u_alias).join(Address, User.addresses) # join to u_alias session.query(User, u_alias).join(Address, u_alias.addresses)
这个变化包括,如果连接是非模糊的,那么现在连接可以正确地链接到不是列表中第一个元素的 FROM 子句:
session.query(func.current_timestamp(), User).join(Address)
在这项增强之前,上述查询将引发:
sqlalchemy.exc.InvalidRequestError: Don't know how to join from CURRENT_TIMESTAMP; please use select_from() to establish the left entity/selectable of this join
现在查询可以正常工作了:
SELECT CURRENT_TIMESTAMP AS current_timestamp_1, users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.password AS users_password FROM users JOIN addresses ON users.id = addresses.user_id
总的来说,这种变化直接符合 Python 的“显式优于隐式”的哲学。
#4365 ### FOR UPDATE 子句在联合贪婪加载子查询中以及外部呈现
此更改特别适用于使用joinedload()
加载策略与行限制查询相结合,例如使用Query.first()
或Query.limit()
,以及使用Query.with_for_update()
方法。
给定一个查询:
session.query(A).options(joinedload(A.b)).limit(5)
当联合贪婪加载与 LIMIT 结合时,Query
对象呈现如下形式的 SELECT:
SELECT subq.a_id, subq.a_data, b_alias.id, b_alias.data FROM ( SELECT a.id AS a_id, a.data AS a_data FROM a LIMIT 5 ) AS subq LEFT OUTER JOIN b ON subq.a_id=b.a_id
这样,对主要实体的行限制就会发生,而不会影响到关联项的贪婪加载。当上述查询与“SELECT…FOR UPDATE”结合时,行为是这样的:
SELECT subq.a_id, subq.a_data, b_alias.id, b_alias.data FROM ( SELECT a.id AS a_id, a.data AS a_data FROM a LIMIT 5 ) AS subq LEFT OUTER JOIN b ON subq.a_id=b.a_id FOR UPDATE
然而,由于bugs.mysql.com/bug.php?id=90693
,MySQL 不会锁定子查询中的行,不像 PostgreSQL 和其他数据库。因此,上述查询现在呈现为:
SELECT subq.a_id, subq.a_data, b_alias.id, b_alias.data FROM ( SELECT a.id AS a_id, a.data AS a_data FROM a LIMIT 5 FOR UPDATE ) AS subq LEFT OUTER JOIN b ON subq.a_id=b.a_id FOR UPDATE
在 Oracle 方言中,内部的“FOR UPDATE”不会呈现,因为 Oracle 不支持这种语法,方言会跳过针对子查询的任何“FOR UPDATE”;在任何情况下都不是必要的,因为 Oracle 像 PostgreSQL 一样正确锁定返回行的所有元素。
当在Query.with_for_update.of
修饰符上使用时,通常在 PostgreSQL 上,外部的“FOR UPDATE”被省略,OF 现在在内部呈现;以前,OF 目标不会被转换以正确适应子查询。因此,考虑到:
session.query(A).options(joinedload(A.b)).with_for_update(of=A).limit(5)
现在查询将呈现为:
SELECT subq.a_id, subq.a_data, b_alias.id, b_alias.data FROM ( SELECT a.id AS a_id, a.data AS a_data FROM a LIMIT 5 FOR UPDATE OF a ) AS subq LEFT OUTER JOIN b ON subq.a_id=b.a_id
上述形式应该对 PostgreSQL 有所帮助,因为 PostgreSQL 不允许在 LEFT OUTER JOIN 目标之后呈现 FOR UPDATE 子句。
总的来说,对于正在使用的目标数据库,FOR UPDATE 仍然非常具体,不容易推广到更复杂的查询。
#4246 ### passive_deletes=’all’将使 FK 对于从集合中移除的对象保持不变
relationship.passive_deletes
选项接受值"all"
,表示在刷新对象时不应修改任何外键属性,即使关系的集合/引用已被移除。以前,在以下情况下,这种情况并不适用于一对多或一对一关系:
class User(Base): __tablename__ = "users" id = Column(Integer, primary_key=True) addresses = relationship("Address", passive_deletes="all") class Address(Base): __tablename__ = "addresses" id = Column(Integer, primary_key=True) email = Column(String) user_id = Column(Integer, ForeignKey("users.id")) user = relationship("User") u1 = session.query(User).first() address = u1.addresses[0] u1.addresses.remove(address) session.commit() # would fail and be set to None assert address.user_id == u1.id
修复现在包括address.user_id
根据passive_deletes="all"
保持不变。这种情况对于构建自定义“版本表”方案等非常有用,其中行被归档而不是被删除。
#3844 ### Query.join()更明确地处理决定“左”侧的模棱两可性
历史上,给定如下查询:
u_alias = aliased(User) session.query(User, u_alias).join(Address)
鉴于标准教程映射,查询将生成一个 FROM 子句:
SELECT ... FROM users AS users_1, users JOIN addresses ON users.id = addresses.user_id
这意味着 JOIN 将隐式地针对第一个匹配的实体。新行为是,异常请求解决这种模棱两可的情况:
sqlalchemy.exc.InvalidRequestError: Can't determine which FROM clause to join from, there are multiple FROMS which can join to this entity. Try adding an explicit ON clause to help resolve the ambiguity.
解决方案是提供一个 ON 子句,可以是一个表达式:
# join to User session.query(User, u_alias).join(Address, Address.user_id == User.id) # join to u_alias session.query(User, u_alias).join(Address, Address.user_id == u_alias.id)
或者使用关系属性,如果可用的话:
# join to User session.query(User, u_alias).join(Address, User.addresses) # join to u_alias session.query(User, u_alias).join(Address, u_alias.addresses)
更改包括现在 JOIN 可以正确地链接到不是列表中第一个元素的 FROM 子句,如果 JOIN 是非模棱两可的话:
session.query(func.current_timestamp(), User).join(Address)
在此增强之前,上述查询将引发:
sqlalchemy.exc.InvalidRequestError: Don't know how to join from CURRENT_TIMESTAMP; please use select_from() to establish the left entity/selectable of this join
现在查询正常工作:
SELECT CURRENT_TIMESTAMP AS current_timestamp_1, users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.password AS users_password FROM users JOIN addresses ON users.id = addresses.user_id
总的来说,这种变化直接符合 Python 的“显式优于隐式”的哲学。
FOR UPDATE 子句在连接贪婪加载子查询内部以及外部都被渲染
此更改特别适用于使用joinedload()
加载策略与行限制查询结合使用时,例如使用Query.first()
或Query.limit()
,以及使用Query.with_for_update()
方法。
给定一个查询如下:
session.query(A).options(joinedload(A.b)).limit(5)
当连接贪婪加载与 LIMIT 结合时,Query
对象会渲染以下形式的 SELECT:
SELECT subq.a_id, subq.a_data, b_alias.id, b_alias.data FROM ( SELECT a.id AS a_id, a.data AS a_data FROM a LIMIT 5 ) AS subq LEFT OUTER JOIN b ON subq.a_id=b.a_id
这样做是为了使主实体的行限制生效,而不影响相关项目的连接贪婪加载。当上述查询与“SELECT…FOR UPDATE”结合时,行为如下:
SELECT subq.a_id, subq.a_data, b_alias.id, b_alias.data FROM ( SELECT a.id AS a_id, a.data AS a_data FROM a LIMIT 5 ) AS subq LEFT OUTER JOIN b ON subq.a_id=b.a_id FOR UPDATE
然而,由于bugs.mysql.com/bug.php?id=90693
,MySQL 不会锁定子查询内的行,不像 PostgreSQL 和其他数据库。因此,上述查询现在会渲染为:
SELECT subq.a_id, subq.a_data, b_alias.id, b_alias.data FROM ( SELECT a.id AS a_id, a.data AS a_data FROM a LIMIT 5 FOR UPDATE ) AS subq LEFT OUTER JOIN b ON subq.a_id=b.a_id FOR UPDATE
在 Oracle 方言中,内部的“FOR UPDATE”不会被渲染,因为 Oracle 不支持这种语法,方言会跳过任何针对子查询的“FOR UPDATE”;在任何情况下都不是必要的,因为 Oracle 像 PostgreSQL 一样正确地锁定了返回行的所有元素。
当在Query.with_for_update.of
修饰符上使用时,通常在 PostgreSQL 上,外部的“FOR UPDATE”会被省略,OF 现在会在内部被渲染;以前,OF 目标不会被转换以适应子查询。所以给定:
session.query(A).options(joinedload(A.b)).with_for_update(of=A).limit(5)
查询现在会渲染为:
SELECT subq.a_id, subq.a_data, b_alias.id, b_alias.data FROM ( SELECT a.id AS a_id, a.data AS a_data FROM a LIMIT 5 FOR UPDATE OF a ) AS subq LEFT OUTER JOIN b ON subq.a_id=b.a_id
以上形式在 PostgreSQL 上也应该有所帮助,因为 PostgreSQL 不允许在 LEFT OUTER JOIN 目标之后渲染 FOR UPDATE 子句。
总的来说,FOR UPDATE 对于正在使用的目标数据库非常具体,不能轻易地推广到更复杂的查询。
passive_deletes='all’将使从集合中移除的对象的 FK 保持不变
relationship.passive_deletes
选项接受值"all"
,表示在刷新对象时不应修改任何外键属性,即使关系的集合/引用已被移除。以前,在以下情况下,这不会发生在一对多或一对一关系中:
class User(Base): __tablename__ = "users" id = Column(Integer, primary_key=True) addresses = relationship("Address", passive_deletes="all") class Address(Base): __tablename__ = "addresses" id = Column(Integer, primary_key=True) email = Column(String) user_id = Column(Integer, ForeignKey("users.id")) user = relationship("User") u1 = session.query(User).first() address = u1.addresses[0] u1.addresses.remove(address) session.commit() # would fail and be set to None assert address.user_id == u1.id
修复现在包括address.user_id
按照passive_deletes="all"
不变。这种情况对于构建自定义“版本表”方案等非常有用,其中行被归档而不是删除。
新功能和改进 - 核心
新的多列命名约定标记,长名称截断
为了适应MetaData
命名约定需要区分多列约束并希望在生成的约束名称中使用所有列的情况,添加了一系列新的命名约定标记,包括column_0N_name
、column_0_N_name
、column_0N_key
、column_0_N_key
、referred_column_0N_name
、referred_column_0_N_name
等,它们将所有列的列名(或键或标签)连接在一起,没有分隔符或使用下划线分隔符。下面我们定义一个约定,将UniqueConstraint
约束命名为连接所有列名称的名称:
metadata_obj = MetaData( naming_convention={"uq": "uq_%(table_name)s_%(column_0_N_name)s"} ) table = Table( "info", metadata_obj, Column("a", Integer), Column("b", Integer), Column("c", Integer), UniqueConstraint("a", "b", "c"), )
上述表的 CREATE TABLE 将呈现为:
CREATE TABLE info ( a INTEGER, b INTEGER, c INTEGER, CONSTRAINT uq_info_a_b_c UNIQUE (a, b, c) )
此外,现在将长名称截断逻辑应用于命名约定生成的名称,特别是为了适应可能产生非常长名称的多列标签。这种逻辑与用于截断 SELECT 语句中的长标签名称的逻辑相同,用一个确定性生成的 4 字符哈希替换超过目标数据库标识符长度限制的多余字符。例如,在 PostgreSQL 上,标识符不能超过 63 个字符,长约束名称通常从下面的表定义中生成:
long_names = Table( "long_names", metadata_obj, Column("information_channel_code", Integer, key="a"), Column("billing_convention_name", Integer, key="b"), Column("product_identifier", Integer, key="c"), UniqueConstraint("a", "b", "c"), )
截断逻辑将确保不会为 UNIQUE 约束生成过长的名称:
CREATE TABLE long_names ( information_channel_code INTEGER, billing_convention_name INTEGER, product_identifier INTEGER, CONSTRAINT uq_long_names_information_channel_code_billing_conventi_a79e UNIQUE (information_channel_code, billing_convention_name, product_identifier) )
上述后缀a79e
基于长名称的 md5 哈希,并且每次生成相同的值,以产生给定模式的一致名称。
注意,当约束名在给定方言中显式过大时,截断逻辑也会引发IdentifierError
。这已经是Index
对象的行为很长时间了,但现在也适用于其他类型的约束:
from sqlalchemy import Column from sqlalchemy import Integer from sqlalchemy import MetaData from sqlalchemy import Table from sqlalchemy import UniqueConstraint from sqlalchemy.dialects import postgresql from sqlalchemy.schema import AddConstraint m = MetaData() t = Table("t", m, Column("x", Integer)) uq = UniqueConstraint( t.c.x, name="this_is_too_long_of_a_name_for_any_database_backend_even_postgresql", ) print(AddConstraint(uq).compile(dialect=postgresql.dialect()))
将输出:
sqlalchemy.exc.IdentifierError: Identifier 'this_is_too_long_of_a_name_for_any_database_backend_even_postgresql' exceeds maximum length of 63 characters
异常引发阻止了由数据库后端截断的非确定性约束名称的生成,这些名称后来与数据库迁移不兼容。
要将 SQLAlchemy 端的截断规则应用于上述标识符,请使用conv()
构造:
uq = UniqueConstraint( t.c.x, name=conv("this_is_too_long_of_a_name_for_any_database_backend_even_postgresql"), )
这将再次输出确定性截断的 SQL,如下所示:
ALTER TABLE t ADD CONSTRAINT this_is_too_long_of_a_name_for_any_database_backend_eve_ac05 UNIQUE (x)
目前还没有选项可以使名称传递以允许数据库端截断。这对于Index
名称已经有一段时间了,而且并没有提出问题。
此更改还修复了另外两个问题。一个是column_0_key
标记虽然被记录,但却无法使用,另一个是如果这两个值不同,referred_column_0_name
标记会错误地呈现.key
而不是.name
列。
另请参阅
配置约束命名约定
MetaData.naming_convention
#3989 ### 用于 SQL 函数的二进制比较解释
这个增强功能是在核心层实现的,但主要适用于 ORM。
现在可以将比较两个元素的 SQL 函数用作“比较”对象,适用于 ORM relationship()
的用法,首先像往常一样使用func
工厂创建函数,然后在函数完成时调用FunctionElement.as_comparison()
修饰符来生成一个具有“左”和“右”两侧的BinaryExpression
:
class Venue(Base): __tablename__ = "venue" id = Column(Integer, primary_key=True) name = Column(String) descendants = relationship( "Venue", primaryjoin=func.instr(remote(foreign(name)), name + "/").as_comparison(1, 2) == 1, viewonly=True, order_by=name, )
上面,“后代”关系的relationship.primaryjoin
将基于传递给instr()
的第一个和第二个参数产生一个“左”和一个“右”表达式。这使得 ORM 懒加载等功能能够产生 SQL,例如:
SELECT venue.id AS venue_id, venue.name AS venue_name FROM venue WHERE instr(venue.name, (? || ?)) = ? ORDER BY venue.name ('parent1', '/', 1)
以及一个 joinedload,例如:
v1 = ( s.query(Venue) .filter_by(name="parent1") .options(joinedload(Venue.descendants)) .one() )
工作原理如下:
SELECT venue.id AS venue_id, venue.name AS venue_name, venue_1.id AS venue_1_id, venue_1.name AS venue_1_name FROM venue LEFT OUTER JOIN venue AS venue_1 ON instr(venue_1.name, (venue.name || ?)) = ? WHERE venue.name = ? ORDER BY venue_1.name ('/', 1, 'parent1')
该功能预期将有助于处理诸如在关系连接条件中使用几何函数或任何通过 SQL 函数来表达 SQL 连接的 ON 子句等情况。
#3831 ### 扩展 IN 特性现在支持空列表
版本 1.2 中引入的“扩展 IN”功能现在支持传递给ColumnOperators.in_()
运算符的空列表。对于空列表的实现将产生一个特定于目标后端的“空集”表达式,例如对于 PostgreSQL,“SELECT CAST(NULL AS INTEGER) WHERE 1!=1”,对于 MySQL,“SELECT 1 FROM (SELECT 1) as _empty_set WHERE 1!=1”:
>>> from sqlalchemy import create_engine >>> from sqlalchemy import select, literal_column, bindparam >>> e = create_engine("postgresql://scott:tiger@localhost/test", echo=True) >>> with e.connect() as conn: ... conn.execute( ... select([literal_column("1")]).where( ... literal_column("1").in_(bindparam("q", expanding=True)) ... ), ... q=[], ... ) {exexsql}SELECT 1 WHERE 1 IN (SELECT CAST(NULL AS INTEGER) WHERE 1!=1)
该功能还适用于基于元组的 IN 语句,其中“空 IN”表达式将被扩展以支持元组中给定的元素,例如在 PostgreSQL 上:
>>> from sqlalchemy import create_engine >>> from sqlalchemy import select, literal_column, tuple_, bindparam >>> e = create_engine("postgresql://scott:tiger@localhost/test", echo=True) >>> with e.connect() as conn: ... conn.execute( ... select([literal_column("1")]).where( ... tuple_(50, "somestring").in_(bindparam("q", expanding=True)) ... ), ... q=[], ... ) {exexsql}SELECT 1 WHERE (%(param_1)s, %(param_2)s) IN (SELECT CAST(NULL AS INTEGER), CAST(NULL AS VARCHAR) WHERE 1!=1)
#4271 ### TypeEngine 方法 bind_expression、column_expression 与 Variant、特定类型一起工作
当TypeEngine.bind_expression()
和TypeEngine.column_expression()
方法存在于特定数据类型的“impl”上时,这些方法现在可以被方言使用,也可以用于TypeDecorator
和Variant
的用例。
以下示例说明了一个TypeDecorator
,它将 SQL 时间转换函数应用于LargeBinary
。为了使此类型在Variant
的上下文中工作,编译器需要深入“impl”变体表达式以定位这些方法:
from sqlalchemy import TypeDecorator, LargeBinary, func class CompressedLargeBinary(TypeDecorator): impl = LargeBinary def bind_expression(self, bindvalue): return func.compress(bindvalue, type_=self) def column_expression(self, col): return func.uncompress(col, type_=self) MyLargeBinary = LargeBinary().with_variant(CompressedLargeBinary(), "sqlite")
上述表达式仅在 SQLite 上使用时会呈现为 SQL 中的函数:
from sqlalchemy import select, column from sqlalchemy.dialects import sqlite print(select([column("x", CompressedLargeBinary)]).compile(dialect=sqlite.dialect()))
将呈现:
SELECT uncompress(x) AS x
此更改还包括方言可以在方言级别的实现类型上实现TypeEngine.bind_expression()
和TypeEngine.column_expression()
,它们现在将被使用;特别是这将用于 MySQL 的新“二进制前缀”要求以及用于将 MySQL 的十进制绑定值转换为浮点数。
#3981 ### QueuePool 的新后进先出策略
通常由 create_engine()
使用的连接池称为 QueuePool
。此连接池使用类似于 Python 内置的 Queue
类的对象来存储等待使用的数据库连接。Queue
具有先进先出的行为,旨在提供对持续在池中的数据库连接的循环使用。然而,这样做的一个潜在缺点是,当池的利用率低时,池中的每个连接的串行重复使用意味着试图减少未使用连接的服务器端超时策略被阻止关闭这些连接。为了适应这种情况,添加了一个新的标志 create_engine.pool_use_lifo
,它将 Queue
的 .get()
方法反转,以从队列的开头而不是末尾获取连接,从而实质上将“队列”变为“栈”(考虑到这样做会增加一个称为 StackPool
的全新连接池,但这太啰嗦了)。
另见
使用 FIFO vs. LIFO ### 新的多列命名约定标记,长名称截断
为了适应需要通过 MetaData
命名约定消除多列约束的歧义,并希望在生成的约束名称中使用所有列的情况,添加了一系列新的命名约定标记,包括 column_0N_name
、column_0_N_name
、column_0N_key
、column_0_N_key
、referred_column_0N_name
、referred_column_0_N_name
等,这些标记将约束中的所有列的列名(或键或标签)连接在一起,可以是没有分隔符或带有下划线分隔符。下面我们定义一个约定,将会以将所有列的名称连接在一起的方式命名 UniqueConstraint
约束:
metadata_obj = MetaData( naming_convention={"uq": "uq_%(table_name)s_%(column_0_N_name)s"} ) table = Table( "info", metadata_obj, Column("a", Integer), Column("b", Integer), Column("c", Integer), UniqueConstraint("a", "b", "c"), )
上述表的 CREATE TABLE 将呈现为:
CREATE TABLE info ( a INTEGER, b INTEGER, c INTEGER, CONSTRAINT uq_info_a_b_c UNIQUE (a, b, c) )
此外,现在对通过命名约定生成的名称应用长名称截断逻辑,特别是为了适应可能产生非常长名称的多列标签。这个逻辑与在 SELECT 语句中截断长标签名称所使用的逻辑相同,它用一个确定性生成的 4 字符哈希替换了超过目标数据库标识符长度限制的多余字符。例如,在 PostgreSQL 中,标识符不能超过 63 个字符,长约束名通常是从下面的表定义生成的:
long_names = Table( "long_names", metadata_obj, Column("information_channel_code", Integer, key="a"), Column("billing_convention_name", Integer, key="b"), Column("product_identifier", Integer, key="c"), UniqueConstraint("a", "b", "c"), )
截断逻辑将确保不会为 UNIQUE 约束生成过长的名称:
CREATE TABLE long_names ( information_channel_code INTEGER, billing_convention_name INTEGER, product_identifier INTEGER, CONSTRAINT uq_long_names_information_channel_code_billing_conventi_a79e UNIQUE (information_channel_code, billing_convention_name, product_identifier) )
上述后缀a79e
基于长名称的 md5 哈希,并且每次生成相同的值,以为给定的模式生成一致的名称。
请注意,当约束名称显式过大时,截断逻辑还会引发IdentifierError
。这已经是Index
对象的行为很长时间了,但现在也适用于其他类型的约束:
from sqlalchemy import Column from sqlalchemy import Integer from sqlalchemy import MetaData from sqlalchemy import Table from sqlalchemy import UniqueConstraint from sqlalchemy.dialects import postgresql from sqlalchemy.schema import AddConstraint m = MetaData() t = Table("t", m, Column("x", Integer)) uq = UniqueConstraint( t.c.x, name="this_is_too_long_of_a_name_for_any_database_backend_even_postgresql", ) print(AddConstraint(uq).compile(dialect=postgresql.dialect()))
输出将是:
sqlalchemy.exc.IdentifierError: Identifier 'this_is_too_long_of_a_name_for_any_database_backend_even_postgresql' exceeds maximum length of 63 characters
异常抛出可防止由数据库后端截断的不确定性约束名称的生成,这些名称随后与数据库迁移不兼容。
要将 SQLAlchemy 端的截断规则应用于上述标识符,请使用conv()
构造:
uq = UniqueConstraint( t.c.x, name=conv("this_is_too_long_of_a_name_for_any_database_backend_even_postgresql"), )
这将再次输出确定性截断的 SQL,如下所示:
ALTER TABLE t ADD CONSTRAINT this_is_too_long_of_a_name_for_any_database_backend_eve_ac05 UNIQUE (x)
目前尚无选项可使名称通过以允许数据库端截断。这在一段时间内已经适用于Index
名称,并且并未引起问题。
这一变更还修复了其他两个问题。其中一个是column_0_key
令牌虽然已记录,但却不可用,另一个是如果这两个值不同,referred_column_0_name
令牌会意外地呈现.key
而不是.name
。
另请参阅
配置约束命名约定
MetaData.naming_convention
SQL 函数的二进制比较解释
此增强功能是在核心级别实现的,但主要适用于 ORM。
现在可以将比较两个元素的 SQL 函数用作适用于 ORM relationship()
中的“比较”对象,首先使用func
工厂通常创建该函数,然后当函数完成时调用FunctionElement.as_comparison()
修饰符,以生成具有“左”和“右”两侧的BinaryExpression
:
class Venue(Base): __tablename__ = "venue" id = Column(Integer, primary_key=True) name = Column(String) descendants = relationship( "Venue", primaryjoin=func.instr(remote(foreign(name)), name + "/").as_comparison(1, 2) == 1, viewonly=True, order_by=name, )
上面,“descendants”关系的relationship.primaryjoin
将基于传递给instr()
的第一个和第二个参数产生一个“left”和一个“right”表达式。这允许 ORM lazyload 等功能生成类似以下的 SQL:
SELECT venue.id AS venue_id, venue.name AS venue_name FROM venue WHERE instr(venue.name, (? || ?)) = ? ORDER BY venue.name ('parent1', '/', 1)
以及一个 joinedload,例如:
v1 = ( s.query(Venue) .filter_by(name="parent1") .options(joinedload(Venue.descendants)) .one() )
作为:
SELECT venue.id AS venue_id, venue.name AS venue_name, venue_1.id AS venue_1_id, venue_1.name AS venue_1_name FROM venue LEFT OUTER JOIN venue AS venue_1 ON instr(venue_1.name, (venue.name || ?)) = ? WHERE venue.name = ? ORDER BY venue_1.name ('/', 1, 'parent1')
这个功能预计将有助于处理诸如在关系连接条件中使用几何函数或任何 ON 子句以 SQL 函数形式表达的情况。
扩展 IN 功能现在支持空列表
在版本 1.2 中引入的“扩展 IN”功能现在支持传递给ColumnOperators.in_()
运算符的空列表。对于空列表的实现将产生一个特定于目标后端的“空集合”表达式,例如对于 PostgreSQL,“SELECT CAST(NULL AS INTEGER) WHERE 1!=1”,对于 MySQL,“SELECT 1 FROM (SELECT 1) as _empty_set WHERE 1!=1”:
>>> from sqlalchemy import create_engine >>> from sqlalchemy import select, literal_column, bindparam >>> e = create_engine("postgresql://scott:tiger@localhost/test", echo=True) >>> with e.connect() as conn: ... conn.execute( ... select([literal_column("1")]).where( ... literal_column("1").in_(bindparam("q", expanding=True)) ... ), ... q=[], ... ) {exexsql}SELECT 1 WHERE 1 IN (SELECT CAST(NULL AS INTEGER) WHERE 1!=1)
该功能还适用于基于元组的 IN 语句,其中“空 IN”表达式将被扩展以支持元组中给定的元素,例如在 PostgreSQL 上:
>>> from sqlalchemy import create_engine >>> from sqlalchemy import select, literal_column, tuple_, bindparam >>> e = create_engine("postgresql://scott:tiger@localhost/test", echo=True) >>> with e.connect() as conn: ... conn.execute( ... select([literal_column("1")]).where( ... tuple_(50, "somestring").in_(bindparam("q", expanding=True)) ... ), ... q=[], ... ) {exexsql}SELECT 1 WHERE (%(param_1)s, %(param_2)s) IN (SELECT CAST(NULL AS INTEGER), CAST(NULL AS VARCHAR) WHERE 1!=1)
TypeEngine 方法 bind_expression、column_expression 与 Variant、特定类型一起工作
TypeEngine.bind_expression()
和TypeEngine.column_expression()
方法现在在特定数据类型的“impl”上存在时也能工作,允许这些方法被方言以及TypeDecorator
和Variant
用例使用。
以下示例说明了一个TypeDecorator
,它将 SQL 时间转换函数应用于LargeBinary
。为了使这种类型在Variant
的上下文中工作,编译器需要深入“impl”变体表达式以定位这些方法:
from sqlalchemy import TypeDecorator, LargeBinary, func class CompressedLargeBinary(TypeDecorator): impl = LargeBinary def bind_expression(self, bindvalue): return func.compress(bindvalue, type_=self) def column_expression(self, col): return func.uncompress(col, type_=self) MyLargeBinary = LargeBinary().with_variant(CompressedLargeBinary(), "sqlite")
上述表达式仅在 SQLite 上使用时会在 SQL 中呈现一个函数:
from sqlalchemy import select, column from sqlalchemy.dialects import sqlite print(select([column("x", CompressedLargeBinary)]).compile(dialect=sqlite.dialect()))
将呈现:
SELECT uncompress(x) AS x
此更改还包括方言可以在方言级别的实现类型上实现TypeEngine.bind_expression()
和TypeEngine.column_expression()
,在那里它们现在将被使用;特别是这将用于 MySQL 的新“二进制前缀”要求以及用于将 MySQL 的十进制绑定值转换的情况。
QueuePool 的新后进先出策略
通常由create_engine()
使用的连接池被称为QueuePool
。此池使用一个类似于 Python 内置的Queue
类的对象来存储等待使用的数据库连接。Queue
具有先进先出的行为,旨在提供对持久在池中的数据库连接的循环使用。然而,这种方法的一个潜在缺点是,当池的利用率较低时,池中每个连接的串行重复使用意味着试图减少未使用连接的服务器端超时策略被阻止关闭这些连接。为了适应这种用例,添加了一个新标志create_engine.pool_use_lifo
,它将Queue
的.get()
方法反转,从队列的开头而不是末尾获取连接,从本质上将“队列”变成“栈”(考虑到添加一个名为StackPool
的全新池,但这太啰嗦了)。
另请参阅
使用 FIFO vs. LIFO
SqlAlchemy 2.0 中文文档(七十三)(5)https://developer.aliyun.com/article/1562231