SQLAlchemy 0.7 中的新功能?
关于本文档
本文描述了 SQLAlchemy 版本 0.6(最后发布于 2012 年 5 月 5 日)和 SQLAlchemy 版本 0.7(截至 2012 年 10 月正在进行维护发布)之间的更改。
文档日期:2011 年 7 月 27 日
介绍
本指南介绍了 SQLAlchemy 版本 0.7 中的新功能,还记录了影响用户将其应用程序从 SQLAlchemy 0.6 系列迁移到 0.7 的更改。
尽可能地,更改是以不破坏为 0.6 构建的应用程序的兼容性的方式进行的。必然不向后兼容的更改非常少,除了可变属性默认值的更改之外,应该只影响极小部分应用程序 - 许多更改涉及非公共 API 和一些用户可能一直在尝试使用的未记录的黑客。
还有第二个更小的非向后兼容更改类别也有文档记录。这类更改涉及那些至少自 0.5 版本以来已被弃用并自弃用以来一直引发警告的功能和行为。这些更改只会影响仍在使用 0.4 或早期 0.5 风格 API 的应用程序。随着项目的成熟,我们在 0.x 级别发布中有越来越少这类更改,这是由于我们的 API 具有越来越少的功能,这些功能不太适合它们原本要解决的用例。
一系列现有功能在 SQLAlchemy 0.7 中已被取代。术语“取代”和“弃用”之间没有太大区别,只是前者更弱地暗示了旧功能可能会被移除。在 0.7 中,像synonym
和comparable_property
这样的功能,以及所有的Extension
和其他事件类都已被取代。但这些“被取代”的功能已被重新实现,使得它们的实现大部分存在于核心 ORM 代码之外,因此它们的持续“挂在那里”并不影响 SQLAlchemy 进一步简化和完善其内部结构的能力,我们预计它们将在可预见的未来保留在 API 中。
新功能
新事件系统
SQLAlchemy 早期使用了MapperExtension
类,该类提供了对映射器持久性周期的钩子。随着 SQLAlchemy 迅速变得更加组件化,将映射器推入更专注的配置角色,许多更多的“扩展”,“监听器”和“代理”类出现,以以一种临时方式解决各种活动拦截用例。部分原因是由活动的分歧驱动的;ConnectionProxy
对象希望提供一个重写语句和参数的系统;AttributeExtension
提供了一个替换传入值的系统,而DDL
对象具有可以根据方言敏感的可调用函数进行切换的事件。
0.7 版本重新实现了几乎所有这些插件点,采用了一种新的、统一的方法,保留了不同系统的所有功能,提供了更多的灵活性和更少的样板代码,性能更好,并且消除了需要为每个事件子系统学习根本不同的 API 的必要性。之前存在的类 MapperExtension
、SessionExtension
、AttributeExtension
、ConnectionProxy
、PoolListener
以及 DDLElement.execute_at
方法已被弃用,现在根据新系统实现 - 这些 API 仍然完全可用,并预计将在可预见的未来保持不变。
新方法使用命名事件和用户定义的可调用对象将活动与事件关联起来。API 的外观和感觉受到了 JQuery、Blinker 和 Hibernate 等多样化来源的驱动,并且在与数十位用户进行的 Twitter 会议期间进行了多次修改,这似乎比邮件列表对此类问题的回应率要高得多。
它还具有一个开放式的目标规范系统,允许将事件与 API 类关联,例如所有 Session
或 Engine
对象,以及与 API 类的特定实例关联,例如特定的 Pool
或 Mapper
,以及与用户定义的类(映射的类)或特定子类的实例的特定属性等相关对象。各个监听器子系统可以对传入的用户定义监听器函数应用包装器,修改它们的调用方式 - 映射器事件可以接收被操作对象的实例,或者其底层的 InstanceState
对象。属性事件可以选择是否有责任返回一个新值。
几个系统现在基于新的事件 API 进行构建,包括新的“可变属性” API 以及复合属性。对事件的更大强调还导致了一些新事件的引入,包括属性过期和刷新操作,pickle 加载/转储操作,完成的映射器构建操作。
另请参阅
事件
Hybrid Attributes,实现/取代了 synonym()、comparable_property()
“派生属性”示例现在已成为官方扩展。synonym()
的典型用例是为映射列提供描述符访问;comparable_property()
的用例是能够从任何描述符返回 PropComparator
。实际上,“派生”的方法更易于使用,更具可扩展性,用几十行纯 Python 实现,几乎不需要导入,甚至不需要 ORM 核心知道它。该功能现在被称为“Hybrid Attributes”扩展。
synonym()
和comparable_property()
仍然是 ORM 的一部分,尽管它们的实现已经移出,建立在类似于混合扩展的方法上,因此核心 ORM 映射器/查询/属性模块在其他方面并不真正意识到它们。
另请参见
混合属性
速度增强
与所有主要 SQLA 版本一样,通过内部进行广泛遍历以减少开销和调用次数,进一步减少了常见情况下所需的工作量。此版本的亮点包括:
- 刷新过程现在将 INSERT 语句捆绑成批次提供给
cursor.executemany()
,对于主键已经存在的行。特别是这通常适用于连接表继承配置中的“子”表,这意味着对于大量连接表对象的大量插入,可以将对cursor.execute
的调用次数减半,从而允许本地 DBAPI 优化为那些传递给cursor.executemany()
的语句(如重用准备好的语句)。 - 当访问已加载的相关对象的多对一引用时调用的代码路径已经大大简化。直接检查标识映射,无需首先生成新的
Query
对象,这在上下文中访问成千上万个内存中的多对一时是昂贵的。对于大多数延迟属性加载,也不再使用每次调用构造的“加载器”对象。 - 重新编写组合使得在映射器内部访问刷新时映射属性的代码路径更短。
- 新的内联属性访问函数取代了以前在“保存-更新”和其他级联操作需要在属性关联的所有数据成员范围内级联时使用“历史”时的用法。这减少了为这个速度关键操作生成新的
History
对象的开销。 ExecutionContext
的内部,即对语句执行的对象,已经被内联和简化。- 为每个语句执行生成的类型的
bind_processor()
和result_processor()
可调用现在被缓存(小心处理,以避免临时类型和方言的内存泄漏)为该类型的生命周期,进一步减少每个语句的调用开销。 - 特定语句的
Compiled
实例的“绑定处理器”集合也被缓存在Compiled
对象上,进一步利用刷新过程使用的“编译缓存”来重用相同的编译形式的 INSERT、UPDATE、DELETE 语句。
包括一个示例基准脚本在内的减少调用次数的演示可在techspot.zzzeek.org/2010/12/12/a-tale-of-three
- profiles/中找到。
重写组合
“复合”特性已被重写,与synonym()
和comparable_property()
一样,使用了基于描述符和事件的轻量级实现,而不是构建到 ORM 内部。这使得从映射器/工作单元内部删除了一些延迟,并简化了复合的工作原理。复合属性现在不再隐藏其建立在其上的基础列,这些列现在保持为常规属性。复合对象还可以充当relationship()
以及Column()
属性的代理。
复合的主要向后不兼容变更是,它们不再使用mutable=True
系统来检测原地突变。请使用Mutation Tracking扩展来建立对现有复合用法的原位更改事件。
另请参阅
复合列类型
突变追踪
更简洁的查询.join(target, onclause)形式
向具有显式 onclause 的目标发出query.join()
的默认方法现在是:
query.join(SomeClass, SomeClass.id == ParentClass.some_id)
在 0.6 版本中,此用法被认为是错误的,因为join()
接受多个参数,对应于多个 JOIN 子句 - 两个参数形式需要在元组中以消除单参数和双参数连接目标之间的歧义。在 0.6 的中间,我们添加了对此特定调用样式的检测和错误消息,因为它是如此常见。在 0.7 中,由于我们无论如何都在检测确切的模式,并且由于为了没有理由而必须键入元组而极端烦人,因此非元组方法现在成为“正常”方法。这种“多个 JOIN”用例与单个 JOIN 用例相比极为罕见,而且这些天多次连接更清楚地表示为多次调用join()
。
元组形式将保留以确保向后兼容性。
请注意,所有其他形式的query.join()
保持不变:
query.join(MyClass.somerelation) query.join("somerelation") query.join(MyTarget) # ... etc
突变事件扩展,取代了“mutable=True”
一个新的扩展,突变追踪,提供了一种机制,通过该机制,用户定义的数据类型可以向拥有的父对象提供更改事件。该扩展包括了一种用于标量数据库值的方法,例如由PickleType
、postgresql.ARRAY
或其他自定义MutableType
类管理的值,以及一种用于 ORM“复合”对象的方法,这些对象使用composite()
进行配置。
另请参阅
突变追踪
NULLS FIRST / NULLS LAST 运算符
这些作为 asc()
和 desc()
操作符的扩展实现,称为 nullsfirst()
和 nullslast()
。
另见
nullsfirst()
nullslast()
select.distinct()、query.distinct() 接受 *args 用于 PostgreSQL 的 DISTINCT ON
通过向 select()
的 distinct
关键字参数传递表达式列表,现在当使用 PostgreSQL 后端时,select()
和 Query
的 distinct()
方法接受位置参数,这些参数将被渲染为 DISTINCT ON。
Index()
可以嵌入到 Table
、__table_args__
内。
Index() 构造可以与 Table 定义一起内联创建,使用字符串作为列名,作为在 Table 外部创建索引的替代方法。即:
Table( "mytable", metadata, Column("id", Integer, primary_key=True), Column("name", String(50), nullable=False), Index("idx_name", "name"), )
这里的主要理由是为了声明性 __table_args__
的利益,特别是与混入类一起使用时:
class HasNameMixin(object): name = Column("name", String(50), nullable=False) @declared_attr def __table_args__(cls): return (Index("name"), {}) class User(HasNameMixin, Base): __tablename__ = "user" id = Column("id", Integer, primary_key=True)
窗口函数 SQL 结构
“窗口函数”在语句生成结果集时向其提供信息。这允许针对诸如“行号”、“排名”等各种条件进行判断。已知至少由 PostgreSQL、SQL Server 和 Oracle 支持。可能还有其他数据库也支持。
最好的窗口函数介绍在 PostgreSQL 网站上,自从版本 8.4 开始就支持窗口函数:
www.postgresql.org/docs/current/static/tutorial-window.html
SQLAlchemy 提供了一个简单的构造,通常通过现有函数子句调用,使用 over()
方法,该方法接受 order_by
和 partition_by
关键字参数。下面我们复制了 PG 教程中的第一个示例:
from sqlalchemy.sql import table, column, select, func empsalary = table("empsalary", column("depname"), column("empno"), column("salary")) s = select( [ empsalary, func.avg(empsalary.c.salary) .over(partition_by=empsalary.c.depname) .label("avg"), ] ) print(s)
SQL:
SELECT empsalary.depname, empsalary.empno, empsalary.salary, avg(empsalary.salary) OVER (PARTITION BY empsalary.depname) AS avg FROM empsalary
sqlalchemy.sql.expression.over
Connection 上的 execution_options() 接受 “isolation_level” 参数
这为单个 Connection
设置了事务隔离级别,直到该 Connection
关闭并且其底层的 DBAPI 资源返回到连接池,此时隔离级别将重置回默认值。默认的隔离级别是通过 create_engine()
的 isolation_level
参数设置的。
目前只有 PostgreSQL 和 SQLite 后端支持事务隔离。
TypeDecorator
与整数主键列一起工作
可以使用扩展Integer
行为的TypeDecorator
与主键列一起使用。Column
的“autoincrement”功能现在将识别底层数据库列仍然是整数,以便继续使 lastrowid 机制正常工作。TypeDecorator
本身的结果值处理器将应用于新生成的主键,包括通过 DBAPI cursor.lastrowid
访问器接收的主键。
TypeDecorator
存在于“sqlalchemy”导入空间中
不再需要从sqlalchemy.types
导入,现在在sqlalchemy
中有镜像。
新方言
已添加方言:
- 用于 Drizzle 数据库的 MySQLdb 驱动程序:
Drizzle - 支持 pymysql DBAPI:
pymsql Notes - psycopg2 现在支持 Python 3
行为变化(向后兼容)
默认情况下构建 C 扩展
这是从 0.7b4 开始的。如果检测到 cPython 2.xx,则会构建扩展。如果构建失败,例如在 Windows 安装中,会捕获该条件并继续非 C 安装。如果使用 Python 3 或 PyPy,则不会构建 C 扩展。
简化了 Query.count(),几乎总是有效
Query.count()
中非常古老的猜测现在已经现代化,使用.from_self()
。也就是说,query.count()
现在等效于:
query.from_self(func.count(literal_column("1"))).scalar()
以前,内部逻辑尝试重写查询本身的列子句,并在检测到“子查询”条件时,例如可能在其中包含聚合的基于列的查询,或者具有 DISTINCT 的查询时,会经历一个复杂的过程来重写列子句。这种逻辑在复杂条件下失败,特别是涉及联接表继承的条件,并且长期以来已经被更全面的.from_self()
调用所取代。
query.count()
现在始终生成以下形式的 SQL:
SELECT count(1) AS count_1 FROM ( SELECT user.id AS user_id, user.name AS user_name from user ) AS anon_1
也就是说,原始查询完全保留在子查询中,不再猜测如何应用 count。
发出非子查询形式的 count()
MySQL 用户已经报告说 MyISAM 引擎不出所料地完全崩溃了。请注意,对于优化不能处理简单子查询的数据库的简单count()
,应该使用func.count()
:
from sqlalchemy import func session.query(func.count(MyClass.id)).scalar()
或者对于count(*)
:
from sqlalchemy import func, literal_column session.query(func.count(literal_column("*"))).select_from(MyClass).scalar()
LIMIT/OFFSET 子句现在使用绑定参数
LIMIT 和 OFFSET 子句,或其后端等效项(即 TOP、ROW NUMBER OVER 等),对于所有支持的后端(除了 Sybase),使用绑定参数进行实际值,这允许更好的查询优化器性能,因为具有不同 LIMIT/OFFSET 的多个语句的文本字符串现在是相同的。
日志增强
Vinay Sajip 提供了一个补丁,使我们的日志系统中不再需要嵌入在引擎和池日志语句中的“十六进制字符串”以使 echo
标志能够正常工作。使用过滤日志对象的新系统使我们能够保持当前行为,即 echo
仅适用于各个引擎,而无需为这些引擎添加额外的标识字符串。
简化的 polymorphic_on 赋值
当在继承场景中使用时,polymorphic_on
列映射属性的填充现在发生在对象构造时,即其 __init__
方法被调用时,使用 init 事件。然后,该属性的行为与任何其他列映射属性相同。以前,特殊逻辑会在刷新期间触发以填充此列,这会阻止任何用户代码修改其行为。新方法在三个方面改进了这一点:1. 多态标识现在在对象构造时立即存在;2. 多态标识可以被用户代码更改,而不会与任何其他列映射属性的行为有任何区别;3. 刷新期间映射器的内部简化,不再需要对此列进行特殊检查。
在多个路径(即“all()”)上进行 contains_eager() 链
contains_eager()
修改器现在会把自己链接到一个更长的路径上,而不需要释放独立的contains_eager()
调用。而不是:
session.query(A).options(contains_eager(A.b), contains_eager(A.b, B.c))
你可以说:
session.query(A).options(contains_eager(A.b, B.c))
允许刷新没有父级的孤立对象
我们一直有一个长期存在的行为,即在刷新期间检查所谓的“孤立对象”,即与指定“delete-orphan”级联的 relationship()
关联的对象,已经被新增到会话中进行插入,并且没有建立父关系。多年前添加了此检查以适应一些测试用例,这些测试用例测试了孤立行为的一致性。在现代 SQLA 中,此检查在 Python 端不再需要。通过使外键引用对象的父行 NOT NULL,数据库会以与 SQLA 允许大多数其他操作相同的方式建立数据一致性。如果对象的父外键可为空,则可以插入行。当对象与特定父对象一起持久化,然后与该父对象解除关联时,会触发“孤立”行为,导致为其发出 DELETE 语句。
在刷新时生成警告,当集合成员、标量引用不在刷新中时
当父对象上标记为 “脏” 的加载的 relationship()
引用的相关对象不在当前 Session
中时,现在会发出警告。
当对象添加到 Session
中或首次与父对象关联时,save-update
级联生效,因此对象及其相关内容通常都存在于同一个 Session
中。然而,如果对于特定的 relationship()
禁用了 save-update
级联,则不会发生这种行为,刷新过程也不会尝试纠正它,而是保持与配置的级联行为一致。以前,在刷新时检测到这样的对象时,它们会被静默跳过。新的行为是发出警告,目的是提醒可能是意外行为来源的情况。
安装程序不再安装 Nose 插件
自从我们转向 nose 以来,我们使用了一个通过 setuptools 安装的插件,这样 nosetests
脚本会自动运行 SQLA 的插件代码,这对于我们的测试来说是必要的,以便具有完整的环境。在 0.6 中间,我们意识到这里的导入模式意味着 Nose 的 “coverage” 插件会中断,因为 “coverage” 要求在导入要覆盖的任何模块之前启动它;因此,在 0.6 中间,我们通过添加一个单独的 sqlalchemy-nose
包来解决这个问题,使情况变得更糟。
在 0.7 中,我们放弃了尝试让 nosetests
自动工作,因为 SQLAlchemy 模块会为所有使用 nosetests
的用法产生大量的 nose 配置选项,不仅仅是 SQLAlchemy 单元测试本身,而且额外的 sqlalchemy-nose
安装是一个更糟糕的想法,在 Python 环境中产生了一个额外的包。在 0.7 中,sqla_nose.py
脚本现在是使用 nose 运行测试的唯一方法。
非 Table
派生的构造可以被映射
一个根本不针对任何 Table
的构造,比如一个函数,可以被映射。
from sqlalchemy import select, func from sqlalchemy.orm import mapper class Subset(object): pass selectable = select(["x", "y", "z"]).select_from(func.some_db_function()).alias() mapper(Subset, selectable, primary_key=[selectable.c.x])
aliased() 接受 FromClause
元素
这是一个方便的辅助程序,当传递一个普通的 FromClause
,比如一个 select
、Table
或 join
到 orm.aliased()
构造时,它会通过到达该 from 构造的 .alias()
方法,而不是构造一个 ORM 级别的 AliasedClass
。
Session.connection(),Session.execute() 接受 ‘bind’
这是为了允许 execute/connection 操作明确参与引擎的开放事务。它还允许自定义的 Session
子类实现自己的 get_bind()
方法和参数,以便在 execute()
和 connection()
方法中同样使用这些自定义参数。
Session.connection Session.execute
独立的绑定参数在列子句中自动标记。
存在于 select 的“columns clause”中的绑定参数现在像其他“匿名”子句一样自动标记,这样在获取行时它们的“类型”就有意义,就像结果行处理器一样。
SQLite - 相对文件路径通过 os.path.abspath() 进行标准化
这样,更改当前目录的脚本将继续定位到后续建立的 SQLite 连接的相同位置。
MS-SQL - String
/Unicode
/VARCHAR
/NVARCHAR
/VARBINARY
在未指定长度时发出“max”
在 MS-SQL 后端,String/Unicode 类型及其对应的 VARCHAR/NVARCHAR,以及 VARBINARY (#1833) 在未指定长度时发出“max”作为长度。这使其更兼容于 PostgreSQL 的 VARCHAR 类型,当未指定长度时同样是无界限的。SQL Server 在未指定长度时默认这些类型的长度为‘1’。
行为变更(不兼容后向)
再次注意,除了默认的可变性更改外,大多数这些更改都是极其微小的,不会影响大多数用户。
PickleType
和 ARRAY 的可变性默认关闭
此更改涉及 ORM 在映射具有 PickleType
或 postgresql.ARRAY
数据类型的列时的默认行为。mutable
标志现在默认设置为 False
。如果现有应用程序使用这些类型并依赖于就地变异的检测,则必须使用 mutable=True
构造类型对象以恢复 0.6 版本的行为:
Table( "mytable", metadata, # .... Column("pickled_data", PickleType(mutable=True)), )
mutable=True
标志正在逐步淘汰,取而代之的是新的Mutation Tracking 扩展。该扩展提供了一种机制,通过该机制,用户定义的数据类型可以向拥有的父级或父级提供更改事件。
以前使用mutable=True
的方法不提供更改事件 - 相反,ORM 必须在每次调用flush()
时扫描会话中存在的所有可变值,并将它们与它们的原始值进行比较,这是一个非常耗时的事件。这是 SQLAlchemy 非常早期的遗留问题,当时flush()
不是自动的,历史跟踪系统也不像现在这样复杂。
现有应用程序使用PickleType
,postgresql.ARRAY
或其他MutableType
子类,并需要原地变异检测的应用程序应该迁移到新的变异跟踪系统,因为mutable=True
可能会在未来被弃用。
composite()
的可变性检测需要变异跟踪扩展
所谓的“复合”映射属性,使用在复合列类型中描述的技术配置的那些,已经重新实现,以使 ORM 内部不再意识到它们(导致关键部分中的代码路径更短更高效)。虽然复合类型通常应被视为不可变值对象,但从未强制执行。对于使用具有可变性的复合的应用程序,变异跟踪扩展提供了一个基类,该基类建立了一个机制,使用户定义的复合类型能够向每个对象的拥有父对象或父对象发送更改事件消息。
使用复合类型并依赖于这些对象的原地变异检测的应用程序应该迁移到“变异跟踪”扩展,或者更改复合类型的使用,以便不再需要原地更改(即将它们视为不可变值对象)。
SQLite - SQLite 方言现在对基于文件的数据库使用NullPool
这个改变是99.999%向后兼容,除非您在连接池连接之间使用临时表。
基于文件的 SQLite 连接速度非常快,使用NullPool
意味着每次调用Engine.connect
都会创建一个新的 pysqlite 连接。
以前,使用SingletonThreadPool
,这意味着在一个线程中对某个引擎的所有连接将是相同的连接。新方法更直观,特别是在使用多个连接时。
当使用:memory:
数据库时,SingletonThreadPool
仍然是默认引擎。
请注意,这个改变破坏了跨会话提交使用的临时表,这是由于 SQLite 处理临时表的方式。如果需要超出一个连接池连接范围的临时表,请参阅www.sqlalchemy.org/docs/dialects/sqlite.html#using
- temporary-tables-with-sqlite 中的说明。
Session.merge()
为具有版本控制的映射器检查版本 id
Session.merge()
将会检查传入状态的版本 id 与数据库中的版本 id 是否匹配,假设映射使用了版本 id,并且传入状态已经分配了一个版本 id,如果它们不匹配,则会引发StaleDataError
。这是正确的行为,因为如果传入状态包含一个过期的版本 id,则应该假设该状态已过期。
如果将数据合并到一个有版本控制的状态中,则版本 id 属性可以不定义,并且不会进行版本检查。
通过检查 Hibernate 的做法已确认了这一点 - merge()
和版本控制功能最初都是从 Hibernate 适配而来的。
查询中改进的元组标签名称
这种改进可能对依赖于旧行为的应用程序稍微具有向后不兼容性。
给定两个映射类Foo
和Bar
,每个类都有一个名为spam
的列:
qa = session.query(Foo.spam) qb = session.query(Bar.spam) qu = qa.union(qb)
由qu
产生的单个列的名称将是spam
。之前由于union
组合的方式,它可能是foo_spam
之类的东西,这与非联合查询的情况下的spam
名称不一致。
映射的列属性首先引用最具体的列
这是一个行为变更,涉及到当一个映射的列属性引用多个列时,特别是在处理一个具有与超类相同名称的属性的联接表子类的属性时。
使用声明式的情况是这样的:
class Parent(Base): __tablename__ = "parent" id = Column(Integer, primary_key=True) class Child(Parent): __tablename__ = "child" id = Column(Integer, ForeignKey("parent.id"), primary_key=True)
在上面的例子中,属性Child.id
同时引用了child.id
列和parent.id
列 - 这是由于属性的名称。如果在类上以不同的方式命名它,比如Child.child_id
,那么它将明确地映射到child.id
,而Child.id
将是与Parent.id
相同的属性。
当id
属性被设置为引用parent.id
和child.id
时,它们会被存储在一个有序列表中。例如Child.id
这样的表达式在渲染时只会引用其中一个列。直到 0.6 版本,这个列会是parent.id
。在 0.7 版本中,它是不那么令人惊讶的child.id
。
这种行为的传统与 ORM 的行为和限制相关,这些限制实际上已经不适用了;一切所需的只是颠倒顺序。
这种方法的一个主要优势是现在更容易构造引用本地列的primaryjoin
表达式:
class Child(Parent): __tablename__ = "child" id = Column(Integer, ForeignKey("parent.id"), primary_key=True) some_related = relationship( "SomeRelated", primaryjoin="Child.id==SomeRelated.child_id" ) class SomeRelated(Base): __tablename__ = "some_related" id = Column(Integer, primary_key=True) child_id = Column(Integer, ForeignKey("child.id"))
在 0.7 版本之前,Child.id
表达式会引用Parent.id
,并且需要将child.id
映射到一个不同的属性上。
这也意味着像这样的查询的行为发生了变化:
session.query(Parent).filter(Child.id > 7)
在 0.6 版本中,这将呈现为:
SELECT parent.id AS parent_id FROM parent WHERE parent.id > :id_1
在 0.7 版本中,您会得到:
SELECT parent.id AS parent_id FROM parent, child WHERE child.id > :id_1
你会注意到这是一个笛卡尔积 - 这种行为现在等同于Child
中的任何其他局部属性。with_polymorphic()
方法或类似的显式连接基础 Table
对象的策略,用于对所有带有Child
条件的 Parent
对象进行查询,方式与 0.5 和 0.6 相同:
print(s.query(Parent).with_polymorphic([Child]).filter(Child.id > 7))
在 0.6 和 0.7 版本都是这样呈现的:
SELECT parent.id AS parent_id, child.id AS child_id FROM parent LEFT OUTER JOIN child ON parent.id = child.id WHERE child.id > :id_1
这种更改的另一个效果是,跨两个表的连接继承加载将从子表的值填充,而不是从父表的值填充。一个不寻常的情况是,使用with_polymorphic="*"
对“Parent”进行查询会对“parent”发出查询,并且左外连接到“child”。行位于“Parent”中,看到多态标识对应于“Child”,但是假设“child”中的实际行已被删除。由于这种损坏,行会带有所有对应于“child”的列设置为 NULL 的值 - 这是现在被填充的值,而不是父表中的值。
将两个或更多同名列映射到连接时需要明确声明
这与之前的变更#1892有些相关。在映射到连接时,同名列必须显式地链接到映射属性,即如将类映射到多个表中描述的那样。
给定两个表 foo
和 bar
,每个表都有一个主键列 id
,现在会产生一个错误:
foobar = foo.join(bar, foo.c.id == bar.c.foo_id) mapper(FooBar, foobar)
这是因为 mapper()
拒绝猜测 FooBar.id
的主要表示列是 foo.c.id
还是 bar.c.id
?属性必须是明确的:
foobar = foo.join(bar, foo.c.id == bar.c.foo_id) mapper(FooBar, foobar, properties={"id": [foo.c.id, bar.c.id]})
SqlAlchemy 2.0 中文文档(七十九)(2)https://developer.aliyun.com/article/1561103