SQLAlchemy 1.0有什么新功能?
关于此文档
本文档描述了截至2014年5月正在进行维护发布的SQLAlchemy版本0.9和2015年4月发布的SQLAlchemy版本1.0之间的更改。
文件上次更新时间:2015年6月9日
介绍
本指南介绍了SQLAlchemy 1.0版的新增功能,并记录了影响用户将其应用程序从0.9系列的SQLAlchemy迁移到1.0的更改。
请仔细查看有关行为变化的章节,了解行为中潜在的向后不兼容的变化。
新功能和改进-ORM
新会话批量插入/更新API
一系列新的 Session
已经创建了直接在工作单元的设施中提供挂钩以发出插入和更新语句的方法。如果使用正确,这个面向专家的系统可以允许使用ORM映射来生成批量插入和更新批处理到ExecuteMany组中的语句,从而允许语句以与直接使用核心相媲美的速度继续运行。
参见
散装作业 -介绍和完整文档
新性能示例套件
灵感来源于为 散装作业 功能以及 如何分析一个由SQLAlchemy支持的应用程序? 在FAQ部分,添加了一个新的示例部分,其中包含一些旨在说明各种核心和ORM技术的相对性能概要的脚本。这些脚本被组织成用例,并打包在一个控制台界面下,这样就可以运行任何演示组合、转储计时、python概要文件结果和/或runsnake概要文件显示。
参见
“烘烤”查询
“baked”查询特性是一种不寻常的新方法,它允许直接构造调用 Query
使用缓存的对象,在连续调用时,它的特点是大大减少了Python函数调用开销(超过75%)。通过指定 Query
对象作为一系列仅调用一次的lambda,作为预编译单元的查询开始可行:
from sqlalchemy.ext import baked from sqlalchemy import bindparam bakery = baked.bakery() def search_for_user(session, username, email=None): baked_query = bakery(lambda session: session.query(User)) baked_query += lambda q: q.filter(User.name == bindparam('username')) baked_query += lambda q: q.order_by(User.id) if email: baked_query += lambda q: q.filter(User.email == bindparam('email')) result = baked_query(session).params(username=username, email=email).all() return result
参见
声明性混合的改进, @declared_attr
以及相关功能
与 declared_attr
已经过大修以支持新的能力。
装饰有 declared_attr
现在只调用 之后 生成任何基于mixin的列副本。这意味着函数可以调用mixin已建立的列,并将接收对正确的 Column
对象:
class HasFooBar(object): foobar = Column(Integer) @declared_attr def foobar_prop(cls): return column_property('foobar: ' + cls.foobar) class SomeClass(HasFooBar, Base): __tablename__ = 'some_table' id = Column(Integer, primary_key=True)
上面, SomeClass.foobar_prop
将针对 SomeClass
和 SomeClass.foobar
将是决赛 Column
要映射到的对象 SomeClass
,而不是直接出现在 HasFooBar
,即使尚未映射列。
这个 declared_attr
现在函数 回忆录 按类返回的值,以便重复调用同一属性将返回相同的值。我们可以改变例子来说明这一点:
class HasFooBar(object): @declared_attr def foobar(cls): return Column(Integer) @declared_attr def foobar_prop(cls): return column_property('foobar: ' + cls.foobar) class SomeClass(HasFooBar, Base): __tablename__ = 'some_table' id = Column(Integer, primary_key=True)
以前, SomeClass
将映射为 foobar
列,但 foobar_prop
通过呼吁 foobar
第二次将产生不同的列。价值 SomeClass.foobar
现在是在声明性设置期间被记忆化的,这样即使在映射器映射属性之前,临时列值也将保持一致,无论 declared_attr
被召唤。
上面的两个行为对于从其他属性派生的许多类型的映射器属性的声明性定义应该有很大帮助,其中 declared_attr
函数是从其他函数调用的 declared_attr
在实际映射类之前本地存在的函数。
对于一个希望构建声明性mixin(为每个子类建立不同的列)的非常小的边缘情况,一个新的修饰符 declared_attr.cascading
添加。使用此修饰符,将为映射的继承层次结构中的每个类单独调用修饰函数。虽然这已经是特殊属性的行为,例如 __table_args__
和 __mapper_args__
对于列和其他属性,默认情况下的行为假定属性仅附加到基类,并且仅从子类继承。用 declared_attr.cascading
,可以应用个人行为:
class HasIdMixin(object): @declared_attr.cascading def id(cls): if has_inherited_table(cls): return Column(ForeignKey('myclass.id'), primary_key=True) else: return Column(Integer, primary_key=True) class MyClass(HasIdMixin, Base): __tablename__ = 'myclass' # ... class MySubClass(MyClass): "" # ...
参见
最后, AbstractConcreteBase
类已被重写,以便可以在抽象基上以内联方式设置关系或其他映射器属性::
from sqlalchemy import Column, Integer, ForeignKey from sqlalchemy.orm import relationship from sqlalchemy.ext.declarative import (declarative_base, declared_attr, AbstractConcreteBase) Base = declarative_base() class Something(Base): __tablename__ = u'something' id = Column(Integer, primary_key=True) class Abstract(AbstractConcreteBase, Base): id = Column(Integer, primary_key=True) @declared_attr def something_id(cls): return Column(ForeignKey(Something.id)) @declared_attr def something(cls): return relationship(Something) class Concrete(Abstract): __tablename__ = u'cca' __mapper_args__ = {'polymorphic_identity': 'cca', 'concrete': True}
上面的映射将建立一个表 cca
两者兼有 id
和A something_id
列,以及 Concrete
也会有关系 something
. 新功能是 Abstract
也将具有独立配置的关系 something
它建立在基础的多态联合之上。
ORM完全对象获取速度快25%
机械的 loading.py
模块以及标识映射已经经历了几次内联、重构和修剪过程,因此原始的行负载现在可以以大约25%的速度填充基于ORM的对象。假设有一个1米的行表,下面这样的脚本说明了最改进的加载类型:
import time from sqlalchemy import Integer, Column, create_engine, Table from sqlalchemy.orm import Session from sqlalchemy.ext.declarative import declarative_base Base = declarative_base() class Foo(Base): __table__ = Table( 'foo', Base.metadata, Column('id', Integer, primary_key=True), Column('a', Integer(), nullable=False), Column('b', Integer(), nullable=False), Column('c', Integer(), nullable=False), ) engine = create_engine( 'mysql+mysqldb://scott:tiger@localhost/test', echo=True) sess = Session(engine) now = time.time() # avoid using all() so that we don't have the overhead of building # a large list of full objects in memory for obj in sess.query(Foo).yield_per(100).limit(1000000): pass print("Total time: %d" % (time.time() - now))
本地MacBookPro结果工作台从19秒0.9下降到14秒1.0。这个 Query.yield_per()
当成批处理大量行时,调用总是一个好主意,因为它可以防止Python解释器必须一次为所有对象及其指令插入分配大量内存。没有 Query.yield_per()
在MacBookPro上,上面的脚本在0.9上是31秒,在1.0上是26秒,这是设置非常大的内存缓冲区所花费的额外时间。
新的keyedtuple实现速度显著加快
我们看了一下 KeyedTuple
实现,希望改进这样的查询:
rows = sess.query(Foo.a, Foo.b, Foo.c).all()
这个 KeyedTuple
类被使用而不是python的 collections.namedtuple()
因为后者有一个非常复杂的类型创建例程,其基准测试速度比 KeyedTuple
. 然而,当获取数十万行时, collections.namedtuple()
快速超车 KeyedTuple
随着实例调用的增加,速度会明显变慢。怎么办?一种在两种方法之间进行对冲的新类型。对于“大小”(返回的行数)和“num”(不同查询数)这三种类型,新的“轻量级键控元组”要么优于这两种类型,要么稍微落后于速度更快的对象,具体取决于哪种情况。在“最佳位置”中,我们都在创建大量的新类型,并获取大量的行,轻量级对象完全吸走了name-duple和keyedtuple::
----------------- size=10 num=10000 # few rows, lots of queries namedtuple: 3.60302400589 # namedtuple falls over keyedtuple: 0.255059957504 # KeyedTuple very fast lw keyed tuple: 0.582715034485 # lw keyed trails right on KeyedTuple ----------------- size=100 num=1000 # <--- sweet spot namedtuple: 0.365247011185 keyedtuple: 0.24896979332 lw keyed tuple: 0.0889317989349 # lw keyed blows both away! ----------------- size=10000 num=100 namedtuple: 0.572599887848 keyedtuple: 2.54251694679 lw keyed tuple: 0.613876104355 ----------------- size=1000000 num=10 # few queries, lots of rows namedtuple: 5.79669594765 # namedtuple very fast keyedtuple: 28.856498003 # KeyedTuple falls over lw keyed tuple: 6.74346804619 # lw keyed trails right on namedtuple
结构记忆使用的显著改善
结构内存的使用已经通过更显著地使用 __slots__
对于许多内部对象。这种优化特别适用于具有大量表和列的大型应用程序的基本内存大小,并减少了各种大容量对象的内存大小,包括事件侦听内部构件、比较器对象以及ORM属性和加载程序策略系统的一部分。
使用大量测量nova启动规模的工作台说明,在“nova.db.sqlacalchemy.models”的基本导入中,sqlacalchemy的对象、关联字典以及weakrefs所占的内存量减少了大约3.7兆兆,即46%:
# reported by heapy, summation of SQLAlchemy objects + # associated dicts + weakref-related objects with core of Nova imported: Before: total count 26477 total bytes 7975712 After: total count 18181 total bytes 4236456 # reported for the Python module space overall with the # core of Nova imported: Before: Partition of a set of 355558 objects. Total size = 61661760 bytes. After: Partition of a set of 346034 objects. Total size = 57808016 bytes.
更新语句现在与executeMany()一起以刷新方式批处理
现在可以在ORM flush中将update语句批处理到更高性能的executemany()调用中,类似于如何对insert语句进行批处理;这将在flush中根据以下条件调用:
顺序中的两个或多个UPDATE语句涉及要修改的相同列集。
语句在set子句中没有嵌入的SQL表达式。
映射不使用
mapper.version_id_col
或者后端方言支持executemany()操作的“健全”行数;大多数dbapis现在都正确地支持这一点。
session.get_bind()处理各种各样的继承方案
这个 Session.get_bind()
每当查询或工作单元刷新进程查找与特定类对应的数据库引擎时,都会调用方法。该方法已得到改进,可以处理各种面向继承的场景,包括:
绑定到mixin或抽象类:
class MyClass(SomeMixin, Base): __tablename__ = 'my_table' # ... session = Session(binds={SomeMixin: some_engine})
基于表单独绑定到继承的具体子类:
class BaseClass(Base): __tablename__ = 'base' # ... class ConcreteSubClass(BaseClass): __tablename__ = 'concrete' # ... __mapper_args__ = {'concrete': True} session = Session(binds={ base_table: some_engine, concrete_table: some_other_engine })
session.get_bind()将在所有相关查询案例中接收映射器
修复了一系列问题, Session.get_bind()
无法接收主要邮件 Mapper
的 Query
,即使此映射器随时可用(主映射器是单个映射器,或者是与 Query
对象)。
这个 Mapper
对象,当传递给 Session.get_bind()
通常由利用 Session.binds
参数将映射器与一系列引擎相关联(尽管在此用例中,由于绑定将通过映射表对象定位,因此在大多数情况下都经常“工作”),或者更具体地说,实现用户定义的 Session.get_bind()
提供基于映射器选择引擎的某种模式的方法,例如水平分片或将查询路由到不同后端的所谓“路由”会话。
这些场景包括:
session.query(User).count()
Query.update()
和Query.delete()
对于更新/删除语句以及“fetch”策略使用的select:session.query(User).filter(User.id == 15).update( {"name": "foob"}, synchronize_session='fetch') session.query(User).filter(User.id == 15).delete( synchronize_session='fetch')
对单个列的查询::
session.query(User.id, User.name).all()
针对间接映射的SQL函数和其他表达式,例如
column_property
::class User(Base): # ... score = column_property(func.coalesce(self.tables.users.c.name, None))) session.query(func.max(User.score)).scalar()
.信息字典改进
这个 InspectionAttr.info
集合现在可用于从 Mapper.all_orm_descriptors
收集。这包括 hybrid_property
和 association_proxy()
. 但是,由于这些对象是类绑定的描述符,因此必须访问它们 分别地 从它们附加到的类中获取属性。下面使用 Mapper.all_orm_descriptors
命名空间::
class SomeObject(Base): # ... @hybrid_property def some_prop(self): return self.value + 5 inspect(SomeObject).all_orm_descriptors.some_prop.info['foo'] = 'bar'
它也可以作为所有 SchemaItem
对象(例如) ForeignKey
, UniqueConstraint
以及其他ORM结构,如 synonym()
.
columnProperty构造在别名、排序方式等方面工作得更好
关于 column_property()
已经确定,尤其是关于 aliased()
构造以及0.9中引入的“按标签排序”逻辑(请参见 标签构造现在可以按顺序单独呈现为其名称 )
给出如下映射:
class A(Base): __tablename__ = 'a' id = Column(Integer, primary_key=True) class B(Base): __tablename__ = 'b' id = Column(Integer, primary_key=True) a_id = Column(ForeignKey('a.id')) A.b = column_property( select([func.max(B.id)]).where(B.a_id == A.id).correlate(A) )
包含“a.b”两次的简单场景将无法正确呈现:
print(sess.query(A, a1).order_by(a1.b))
这将按错误的列排序:
SELECT a.id AS a_id, (SELECT max(b.id) AS max_1 FROM b WHERE b.a_id = a.id) AS anon_1, a_1.id AS a_1_id, (SELECT max(b.id) AS max_2 FROM b WHERE b.a_id = a_1.id) AS anon_2 FROM a, a AS a_1 ORDER BY anon_1
新输出:
SELECT a.id AS a_id, (SELECT max(b.id) AS max_1 FROM b WHERE b.a_id = a.id) AS anon_1, a_1.id AS a_1_id, (SELECT max(b.id) AS max_2 FROM b WHERE b.a_id = a_1.id) AS anon_2 FROM a, a AS a_1 ORDER BY anon_2
还有许多场景,“order by”逻辑无法按标签排序,例如,如果映射是“多态的”::
class A(Base): __tablename__ = 'a' id = Column(Integer, primary_key=True) type = Column(String) __mapper_args__ = {'polymorphic_on': type, 'with_polymorphic': '*'}
订单将无法使用标签,因为它将由于多态加载而匿名:
SELECT a.id AS a_id, a.type AS a_type, (SELECT max(b.id) AS max_1 FROM b WHERE b.a_id = a.id) AS anon_1 FROM a ORDER BY (SELECT max(b.id) AS max_2 FROM b WHERE b.a_id = a.id)
现在,order by label跟踪匿名标签,现在可以工作了:
SELECT a.id AS a_id, a.type AS a_type, (SELECT max(b.id) AS max_1 FROM b WHERE b.a_id = a.id) AS anon_1 FROM a ORDER BY anon_1
这些修复中包含了各种可能破坏 aliased()
构造为使标记逻辑再次失败;这些也已被修复。
新功能和改进-核心
可以将select/query limit/offset指定为任意SQL表达式
这个 Select.limit()
和 Select.offset()
方法现在接受除整数值之外的任何SQL表达式作为参数。奥姆 Query
对象还通过任何表达式传递给基础 Select
对象。通常,这用于允许传递绑定参数,稍后可以用值替换该参数::
sel = select([table]).limit(bindparam('mylimit')).offset(bindparam('myoffset'))
不支持非整数限制或偏移表达式的方言可能继续不支持此行为;第三方方言也可能需要修改才能利用新行为。当前使用的方言 ._limit
或 ._offset
对于那些将限制/偏移量指定为简单整数值的情况,属性将继续工作。但是,当指定SQL表达式时,这两个属性将改为引发 CompileError
访问。希望支持新功能的第三方方言现在应调用 ._limit_clause
和 ._offset_clause
属性来接收完整的SQL表达式,而不是整数值。
这个 use_alter
旗上 ForeignKeyConstraint
不再需要
这个 MetaData.create_all()
和 MetaData.drop_all()
方法现在将使用一个系统,该系统自动为表之间相互依赖的循环中涉及的外键约束呈现alter语句,而无需指定 ForeignKeyConstraint.use_alter
. 此外,为了通过alter创建,外键约束不再需要有名称;只有drop操作需要名称。在drop的情况下,该特性将确保只有具有显式名称的约束实际包含为alter语句。在液滴内出现不可溶解循环的情况下,如果液滴无法继续进行,系统会立即发出一条简洁清晰的错误消息。
这个 ForeignKeyConstraint.use_alter
和 ForeignKey.use_alter
标记保持在适当的位置,并继续具有建立那些在创建/删除场景期间需要更改的约束的相同效果。
从1.0.1版开始,特殊逻辑在不支持alter的sqlite的情况下接管,如果在删除过程中,给定的表有一个不可解析的循环;在这种情况下,会发出警告,并使用 no 排序,通常在sqlite上是可以的,除非启用了约束。要解决警告并继续至少对sqlite数据库进行部分排序,特别是在启用了约束的情况下,请对这些数据库重新应用“use-alter”标志。 ForeignKey
和 ForeignKeyConstraint
应在排序中显式省略的对象。
参见
通过alter创建/删除外键约束 -新行为的完整描述。
结果代理“自动关闭”现在是“软”关闭
对于许多版本, ResultProxy
对象总是在提取所有结果行时自动关闭。这是为了允许在不需要调用的情况下使用对象 ResultProxy.close()
显式地;当所有DBAPI资源都被释放时,对象可以安全地丢弃。但是,对象保持严格的“关闭”行为,这意味着任何后续调用 ResultProxy.fetchone()
, ResultProxy.fetchmany()
或 ResultProxy.fetchall()
现在会提高 ResourceClosedError
::
>>> result = connection.execute(stmt) >>> result.fetchone() (1, 'x') >>> result.fetchone() None # indicates no more rows >>> result.fetchone() exception: ResourceClosedError
这种行为与PEP-249所说的不一致,即即使在结果用尽之后,也可以重复调用fetch方法。它还会干扰一些结果代理实现的行为,例如 BufferedColumnResultProxy
由cx-oracle方言用于某些数据类型。
为了解决这个问题,在 ResultProxy
已分为两种状态:一种“软关闭”,它执行“关闭”的大部分操作,即释放DBAPI光标,在“结果关闭”对象的情况下,也将释放连接,“关闭”状态是“软关闭”包括的所有内容,并将获取方法设置为“关闭”。这个 ResultProxy.close()
方法现在从不隐式调用,只调用 ResultProxy._soft_close()
非公共方法:
>>> result = connection.execute(stmt) >>> result.fetchone() (1, 'x') >>> result.fetchone() None # indicates no more rows >>> result.fetchone() None # still None >>> result.fetchall() [] >>> result.close() >>> result.fetchone() exception: ResourceClosedError # *now* it raises
检查约束现在支持 %(column_0_name)s
命名约定中的标记
这个 %(column_0_name)s
将从表达式中找到的第一列派生 CheckConstraint
::
metadata = MetaData( naming_convention={"ck": "ck_%(table_name)s_%(column_0_name)s"} ) foo = Table('foo', metadata, Column('value', Integer), ) CheckConstraint(foo.c.value > 5)
将呈现:
CREATE TABLE foo ( value INTEGER, CONSTRAINT ck_foo_value CHECK (value > 5) )
命名约定与由 SchemaType
如 Boolean
或 Enum
现在还将使用所有检查约束约定。
参见
引用未连接列的约束在附加其引用列时可以自动附加到表
因为至少版本0.8,a Constraint
有能力“自动连接”到 Table
基于传递的表附加列:
from sqlalchemy import Table, Column, MetaData, Integer, UniqueConstraint m = MetaData() t = Table('t', m, Column('a', Integer), Column('b', Integer) ) uq = UniqueConstraint(t.c.a, t.c.b) # will auto-attach to Table assert uq in t.constraints
为了帮助处理一些倾向于提出声明性的情况,即使在 Column
对象尚未与 Table
;确定其他事件,以便 Column
对象是关联的, Constraint
还增加了:
from sqlalchemy import Table, Column, MetaData, Integer, UniqueConstraint m = MetaData() a = Column('a', Integer) b = Column('b', Integer) uq = UniqueConstraint(a, b) t = Table('t', m, a, b) assert uq in t.constraints # constraint auto-attached
以上功能是从1.0.0b3版开始的最新添加。从1.0.4版开始的修复 #3411 确保在 Constraint
指的是 Column
对象和字符串列名;因为我们还没有跟踪将名称添加到 Table
::
from sqlalchemy import Table, Column, MetaData, Integer, UniqueConstraint m = MetaData() a = Column('a', Integer) b = Column('b', Integer) uq = UniqueConstraint(a, 'b') t = Table('t', m, a, b) # constraint *not* auto-attached, as we do not have tracking # to locate when a name 'b' becomes available on the table assert uq not in t.constraints
上面,“A”列到“T”列的附加事件将在“B”列附加之前触发(如“A”所述 Table
构造函数位于“b”)之前,如果约束试图连接,它将无法定位“b”。为了一致性,如果约束引用任何字符串名称,则跳过列附加逻辑上的自动附加。
当然,如果 Table
已经包含所有目标 Column
对象的时间 Constraint
构造::
from sqlalchemy import Table, Column, MetaData, Integer, UniqueConstraint m = MetaData() a = Column('a', Integer) b = Column('b', Integer) t = Table('t', m, a, b) uq = UniqueConstraint(a, 'b') # constraint auto-attached normally as in older versions assert uq in t.constraints
从select插入现在包括python和sql表达式默认值
Insert.from_select()
如果未指定,则现在包括python和sql表达式默认值;现在取消了从select插入时不包括非服务器列默认值的限制,并将这些表达式作为常量呈现到select语句中::
from sqlalchemy import Table, Column, MetaData, Integer, select, func m = MetaData() t = Table( 't', m, Column('x', Integer), Column('y', Integer, default=func.somefunction())) stmt = select([t.c.x]) print(t.insert().from_select(['x'], stmt))
将呈现:
INSERT INTO t (x, y) SELECT t.x, somefunction() AS somefunction_1 FROM t
可以使用禁用该功能 Insert.from_select.include_defaults
.
列服务器默认值现在呈现文本值
当 DefaultClause
,由 Column.server_default
作为要编译的SQL表达式存在。这允许嵌入在SQL中的文本正确呈现,例如:
from sqlalchemy import Table, Column, MetaData, Text from sqlalchemy.schema import CreateTable from sqlalchemy.dialects.postgresql import ARRAY, array from sqlalchemy.dialects import postgresql metadata = MetaData() tbl = Table("derp", metadata, Column("arr", ARRAY(Text), server_default=array(["foo", "bar", "baz"])), ) print(CreateTable(tbl).compile(dialect=postgresql.dialect()))
现在呈现:
CREATE TABLE derp ( arr TEXT[] DEFAULT ARRAY['foo', 'bar', 'baz'] )
以前,文字值 "foo", "bar", "baz"
将呈现为绑定参数,这在DDL中是无用的。
uniqueconstraint现在是表反射过程的一部分
A Table
对象填充使用 autoload=True
现在包括 UniqueConstraint
构造以及 Index
构造。这个逻辑对PostgreSQL和MySQL有一些警告:
《PostgreSQL》
PostgreSQL具有这样的行为:当创建唯一约束时,它也隐式地创建与该约束对应的唯一索引。这个 Inspector.get_indexes()
以及 Inspector.get_unique_constraints()
方法将继续 both 清楚地返回这些条目,其中 Inspector.get_indexes()
现在有一个代币 duplicates_constraint
在索引项中,指示检测到的相应约束。但是,当使用 Table(..., autoload=True)
, the Index
检测到构造链接到 UniqueConstraint
是 not 存在于 Table.indexes
收藏;只有 UniqueConstraint
将出现在 Table.constraints
收集。此重复数据消除逻辑通过加入 pg_constraint
查询时的表 pg_index
查看这两个结构是否链接。
MySQL
对于唯一索引和唯一约束,MySQL没有单独的概念。虽然它在创建表和索引时同时支持两种语法,但它不会以任何不同的方式存储它们。这个 Inspector.get_indexes()
以及 Inspector.get_unique_constraints()
方法将继续 both 返回MySQL中唯一索引的条目,其中 Inspector.get_unique_constraints()
提供新的令牌 duplicates_index
在约束项中,指示这是对应于该索引的重复项。但是,当使用 Table(..., autoload=True)
, the UniqueConstraint
构造是 not 完全反射的部分 Table
在任何情况下构造;此构造始终由 Index
与 unique=True
设置存在于 Table.indexes
收集。
参见
安全地发出参数化警告的新系统
很长一段时间以来,有一个限制,警告消息不能引用数据元素,这样一个特定的函数可能会发出无限数量的唯一警告。发生这种情况的关键位置是 Unicode type received non-unicode bind param value
警告。在这个消息中放置数据值意味着python __warningregistry__
对于该模块,或者在某些情况下,对于python全局 warnings.onceregistry
,将无限增长,在大多数警告场景中,这两个集合中的一个将填充每个不同的警告消息。
这里的变化是通过使用特殊的 string
类型有目的地更改字符串的散列方式,我们可以控制大量参数化消息只对一小组可能的散列值进行散列,例如 Unicode type received non-unicode bind param value
可以调整为只发出特定次数;除此之外,python warnings注册表将开始将它们记录为重复项。
为了说明这一点,下面的测试脚本将只显示10个参数集(总共1000个)发出的警告:
from sqlalchemy import create_engine, Unicode, select, cast import random import warnings e = create_engine("sqlite://") # Use the "once" filter (which is also the default for Python # warnings). Exactly ten of these warnings will # be emitted; beyond that, the Python warnings registry will accumulate # new values as dupes of one of the ten existing. warnings.filterwarnings("once") for i in range(1000): e.execute(select([cast( ('foo_%d' % random.randint(0, 1000000)).encode('ascii'), Unicode)]))
此处警告的格式为:
/path/lib/sqlalchemy/sql/sqltypes.py:186: SAWarning: Unicode type received non-unicode bind param value 'foo_4852'. (this warning may be suppressed after 10 occurrences)
关键行为变化-ORM
query.update()现在将字符串名称解析为映射的属性名称
文件 Query.update()
说明 values
字典是“一个以属性名为键的字典”,这意味着它们是映射的属性名。不幸的是,该函数的设计更多的是为了接收属性和SQL表达式,而不是像传递字符串那样多;当传递字符串时,这些字符串将直接传递到核心更新语句,而不需要任何解析,只要这些名称在映射类上的表示方式,这意味着名称必须与表列的T,而不是该名称的属性如何映射到类。
现在,字符串名称认真地解析为属性名称::
class User(Base): __tablename__ = 'user' id = Column(Integer, primary_key=True) name = Column('user_name', String(50))
上面,柱子 user_name
被映射为 name
. 以前,打电话给 Query.update()
传递的字符串必须按如下方式调用:
session.query(User).update({'user_name': 'moonbeam'})
现在根据实体解析给定字符串:
session.query(User).update({'name': 'moonbeam'})
通常,最好直接使用属性,以避免任何模糊性:
session.query(User).update({User.name: 'moonbeam'})
更改还指示同义词和混合属性也可以由字符串名称引用::
class User(Base): __tablename__ = 'user' id = Column(Integer, primary_key=True) name = Column('user_name', String(50)) @hybrid_property def fullname(self): return self.name session.query(User).update({'fullname': 'moonbeam'})
将没有值的对象与关系进行比较时发出警告
此更改自1.0.1起是新的。一些用户正在执行本质上属于此表单的查询:
session.query(Address).filter(Address.user == User(id=None))
sqlacalchemy当前不支持此模式。对于所有版本,它发出类似以下内容的SQL:
SELECT address.id AS address_id, address.user_id AS address_user_id, address.email_address AS address_email_address FROM address WHERE ? = address.user_id (None,)
注意,上面有一个比较 WHERE ? = address.user_id
其中,绑定值 ?
正在接收 None
或 NULL
在SQL中。 这在SQL中总是返回false . 理论上,这里的比较将生成以下SQL:
SELECT address.id AS address_id, address.user_id AS address_user_id, address.email_address AS address_email_address FROM address WHERE address.user_id IS NULL
但现在, 它不 . 依赖“空=空”这一事实的应用程序在所有情况下都会产生错误的风险,有一天,SQLAlchemy可能会修复此问题以生成“空”,然后查询将产生不同的结果。因此,通过这种操作,您将看到一个警告:
SAWarning: Got None for value of column user.id; this is unsupported for a relationship comparison and will not currently produce an IS comparison (but may in a future release)
注意,在大多数情况下,这个模式在1.0.0版中都被破坏了,包括所有的beta;一个类似 SYMBOL('NEVER_SET')
将生成。这个问题已经得到了解决,但是由于识别了这个模式,现在警告就出现了,这样我们就可以更安全地修复这个被破坏的行为(现在在 #3373 )在未来的版本中。
“negated contains or equals”关系比较将使用属性的当前值,而不是数据库值
从1.0.1开始,这个变化是新的;虽然我们希望它是1.0.0,但它只是由于 #3371 .
给定映射:
class A(Base): __tablename__ = 'a' id = Column(Integer, primary_key=True) class B(Base): __tablename__ = 'b' id = Column(Integer, primary_key=True) a_id = Column(ForeignKey('a.id')) a = relationship("A")
鉴于 A
,主键为7,但在不刷新的情况下改为10:
s = Session(autoflush=False) a1 = A(id=7) s.add(a1) s.commit() a1.id = 10
针对将此对象作为目标的多对一关系的查询将使用绑定参数中的值10::
s.query(B).filter(B.a == a1)
生产::
SELECT b.id AS b_id, b.a_id AS b_a_id FROM b WHERE ? = b.a_id (10,)
然而,在这一变化之前,否定这一标准将 not 使用10,它将使用7,除非先刷新对象:
s.query(B).filter(B.a != a1)
生成(在0.9和1.0.1之前的所有版本中)::
SELECT b.id AS b_id, b.a_id AS b_a_id FROM b WHERE b.a_id != ? OR b.a_id IS NULL (7,)
对于临时对象,它将生成一个中断的查询::
SELECT b.id, b.a_id FROM b WHERE b.a_id != :a_id_1 OR b.a_id IS NULL {u'a_id_1': symbol('NEVER_SET')}
在本例中,已经修复了这种不一致性,并且在所有查询中都使用了当前属性值 10
,将立即使用。
对属性事件的更改以及与没有预先存在值的属性相关的其他操作
在此更改中,默认返回值为 None
访问对象时,现在在每次访问时都动态返回,而不是在首次访问时使用特殊的“set”操作隐式设置属性的状态。这种变化的明显结果是 obj.__dict__
不是在get上隐式修改的,并且还有一些 get_history()
以及相关功能。
给定一个没有状态的对象:
>>> obj = Foo()
它始终是SQLAlchemy的行为,因此如果我们访问一个从未设置过的标量或多对一属性,它将返回为 None
::
>>> obj.someattr None
这个值 None
实际上现在是 obj
,并且不像我们已经显式地设置了属性,例如 obj.someattr = None
. 但是,这里的“set on get”在历史和事件方面的表现会有所不同。它不会发出任何属性事件,另外,如果我们查看历史记录,我们会看到:
>>> inspect(obj).attrs.someattr.history History(added=(), unchanged=[None], deleted=()) # 0.9 and below
也就是说,就好像属性总是 None
从未改变过。这与我们先设置属性时明显不同:
>>> obj = Foo() >>> obj.someattr = None >>> inspect(obj).attrs.someattr.history History(added=[None], unchanged=(), deleted=()) # all versions
上面的意思是,我们的“set”操作的行为可能会被之前通过“get”访问该值的事实所破坏。在1.0中,这种不一致性已经得到了解决,当使用默认的“getter”时不再实际设置任何内容。
>>> obj = Foo() >>> obj.someattr None >>> inspect(obj).attrs.someattr.history History(added=(), unchanged=(), deleted=()) # 1.0 >>> obj.someattr = None >>> inspect(obj).attrs.someattr.history History(added=[None], unchanged=(), deleted=())
上述行为没有太大影响的原因是关系数据库中的insert语句在大多数情况下认为缺少的值与空值相同。对于设置为“无”或“不”的特定属性,SQLAlchemy是否接收到历史事件通常无关紧要;因为发送“无”或“不”之间的差异不会产生影响。然而,作为 #3060 (此处介绍 关系绑定属性与fk绑定属性的属性更改优先级可能会发生更改。 )举例说明,我们确实希望在一些很少边缘的情况下 None
集合。此外,在这里允许属性事件意味着现在可以为ORM映射的属性创建“默认值”函数。
作为此更改的一部分,隐式“none”的生成现在在以前发生这种情况的其他情况下被禁用;这包括当接收到多对一的属性集操作时;以前,如果不进行其他设置,“old”值将为“none”;现在它将发送该值 NEVER_SET
,该值现在可以发送到属性侦听器。调用mapper实用程序函数(如 Mapper.primary_key_from_instance()
;如果主键属性完全没有设置,则值为 None
以前,现在是 NEVER_SET
符号,不会更改对象的状态。
关系绑定属性与fk绑定属性的属性更改优先级可能会发生更改。
作为…的副作用 #3060 ,将关系绑定属性设置为 None
现在是一个跟踪的历史事件,它指的是持续的意图 None
到那个属性。由于一直以来,设置一个关系绑定属性将胜过直接分配给外键属性,因此在这里,当不分配任何属性时,可以看到行为的变化。给定映射:
class A(Base): __tablename__ = 'table_a' id = Column(Integer, primary_key=True) class B(Base): __tablename__ = 'table_b' id = Column(Integer, primary_key=True) a_id = Column(ForeignKey('table_a.id')) a = relationship(A)
在1.0中,关系绑定属性在任何情况下都优先于FK绑定属性,无论我们分配的值是否引用 A
对象还是 None
. 在0.9中,行为是不一致的,只有在分配了值时才会生效;不考虑无:
a1 = A(id=1) a2 = A(id=2) session.add_all([a1, a2]) session.flush() b1 = B() b1.a = a1 # we expect a_id to be '1'; takes precedence in 0.9 and 1.0 b2 = B() b2.a = None # we expect a_id to be None; takes precedence only in 1.0 b1.a_id = 2 b2.a_id = 2 session.add_all([b1, b2]) session.commit() assert b1.a is a1 # passes in both 0.9 and 1.0 assert b2.a is None # passes in 1.0, in 0.9 it's a2
session.expunge()将完全分离已删除的对象
行为 Session.expunge()
有一个bug,导致有关已删除对象的行为不一致。这个 object_session()
功能以及 InstanceState.session
属性仍会将对象报告为属于 Session
删除之后:
u1 = sess.query(User).first() sess.delete(u1) sess.flush() assert u1 not in sess assert inspect(u1).session is sess # this is normal before commit sess.expunge(u1) assert u1 not in sess assert inspect(u1).session is None # would fail
注意这是正常的 u1 not in sess
当 inspect(u1).session
仍然是指会话,而事务在删除操作之后仍在进行,并且 Session.expunge()
尚未调用;完全分离通常在提交事务后完成。这个问题也会影响到依赖 Session.expunge()
如 make_transient()
.
显式不允许使用yield-per进行joined/subquery预加载
为了使 Query.yield_per()
方法更易于使用,如果使用yield-per时,任何子查询抢先加载程序或将使用集合的联接抢先加载程序生效,则会引发异常,因为这些程序当前与yield-per不兼容(但是,子查询加载在理论上可能是这样)。当出现此错误时, lazyload()
选项可以用星号发送:
q = sess.query(Object).options(lazyload('*')).yield_per(100)
或使用 Query.enable_eagerloads()
::
q = sess.query(Object).enable_eagerloads(False).yield_per(100)
这个 lazyload()
选项的优点是,还可以使用附加的多对一联接加载器选项:
q = sess.query(Object).options( lazyload('*'), joinedload("some_manytoone")).yield_per(100)
处理重复联接目标时的更改和修复
此处的更改包括错误,在某些情况下,当两次联接到一个实体或针对同一个表的多个单表实体时,如果不使用基于子句的关系,以及当多次联接到同一目标关系时,会发生意外和不一致的行为。
从以下映射开始:
from sqlalchemy import Integer, Column, String, ForeignKey from sqlalchemy.orm import Session, relationship from sqlalchemy.ext.declarative import declarative_base Base = declarative_base() class A(Base): __tablename__ = 'a' id = Column(Integer, primary_key=True) bs = relationship("B") class B(Base): __tablename__ = 'b' id = Column(Integer, primary_key=True) a_id = Column(ForeignKey('a.id'))
联接到的查询 A.bs
两次::
print(s.query(A).join(A.bs).join(A.bs))
将呈现:
SELECT a.id AS a_id FROM a JOIN b ON a.id = b.a_id
查询将消除冗余的 A.bs
因为它正试图支持如下案例:
s.query(A).join(A.bs).\ filter(B.foo == 'bar').\ reset_joinpoint().join(A.bs, B.cs).filter(C.bar == 'bat')
也就是说, A.bs
是“路径”的一部分。作为一部分 #3367 ,如果两次到达同一个端点而不作为较大路径的一部分,则现在将发出警告::
SAWarning: Pathed join target A.bs has already been joined to; skipping
更大的变化涉及到在不使用关系绑定路径的情况下加入实体。如果我们加入 B
两次::
print(s.query(A).join(B, B.a_id == A.id).join(B, B.a_id == A.id))
在0.9中,这将呈现如下:
SELECT a.id AS a_id FROM a JOIN b ON b.a_id = a.id JOIN b AS b_1 ON b_1.a_id = a.id
这是有问题的,因为别名是隐式的,在不同的on子句的情况下,可能会导致不可预知的结果。
在1.0中,没有应用自动别名,我们得到:
SELECT a.id AS a_id FROM a JOIN b ON b.a_id = a.id JOIN b ON b.a_id = a.id
这将从数据库中引发错误。如果“重复连接目标”的行为是相同的,那么当我们从冗余关系和基于冗余非关系的目标中连接时,可能会很好,但目前我们只在更严重的情况下更改行为,在这种情况下,隐式别名会发生在以前,并且只在关系情况下发出警告。最终,在所有情况下,如果两次连接到同一个对象而没有任何消除歧义的别名,则会导致错误。
更改也会影响单表继承目标。使用如下映射:
from sqlalchemy import Integer, Column, String, ForeignKey from sqlalchemy.orm import Session, relationship from sqlalchemy.ext.declarative import declarative_base Base = declarative_base() class A(Base): __tablename__ = "a" id = Column(Integer, primary_key=True) type = Column(String) __mapper_args__ = {'polymorphic_on': type, 'polymorphic_identity': 'a'} class ASub1(A): __mapper_args__ = {'polymorphic_identity': 'asub1'} class ASub2(A): __mapper_args__ = {'polymorphic_identity': 'asub2'} class B(Base): __tablename__ = 'b' id = Column(Integer, primary_key=True) a_id = Column(Integer, ForeignKey("a.id")) a = relationship("A", primaryjoin="B.a_id == A.id", backref='b') s = Session() print(s.query(ASub1).join(B, ASub1.b).join(ASub2, B.a)) print(s.query(ASub1).join(B, ASub1.b).join(ASub2, ASub2.id == B.a_id))
底部的两个查询是等效的,都应呈现相同的SQL::
SELECT a.id AS a_id, a.type AS a_type FROM a JOIN b ON b.a_id = a.id JOIN a ON b.a_id = a.id AND a.type IN (:type_1) WHERE a.type IN (:type_2)
上面的SQL是无效的,因为它两次呈现“a”在“from”列表中。但是,隐式别名错误只会出现在第二个查询中,并将其呈现为:
SELECT a.id AS a_id, a.type AS a_type FROM a JOIN b ON b.a_id = a.id JOIN a AS a_1 ON a_1.id = b.a_id AND a_1.type IN (:type_1) WHERE a_1.type IN (:type_2)
在上面的位置,第二个到“a”的连接是别名。虽然这看起来很方便,但单继承查询的工作方式并不是一般的,它具有误导性和不一致性。
最终的结果是依赖这个bug的应用程序现在会有一个由数据库引发的错误。解决方案是使用预期的形式。在查询中引用单个继承实体的多个子类时,必须手动使用别名来消除表的歧义,因为所有子类通常都引用同一个表::
asub2_alias = aliased(ASub2) print(s.query(ASub1).join(B, ASub1.b).join(asub2_alias, B.a.of_type(asub2_alias)))
延迟列不再隐式取消传递
标记为deferred而没有显式取消引用的映射属性现在将保持“deferred”,即使它们的列以某种方式存在于结果集中。这是一种性能增强,因为ORM负载在获得结果集时不再花时间搜索每个延迟的列。但是,对于一直依赖于此的应用程序,显式 undefer()
或者现在应该使用类似的选项,以防止在访问属性时发出选择。
已删除不推荐使用的ORM事件挂钩
已删除以下ORM事件挂钩,其中一些自0.5以来已被弃用: translate_row
, populate_instance
, append_result
, create_instance
. 这些钩子的用例起源于早期的0.1/0.2系列的sqlAlchemy,并且很久以来都是不必要的。特别是,钩子在很大程度上是不可用的,因为这些事件中的行为契约与周围的内部结构紧密相连,例如需要如何创建和初始化实例,以及列如何位于ORM生成的行中。这些挂钩的拆卸大大简化了ORM物体加载的力学过程。
使用自定义行加载器时,新捆绑功能的API更改
新的 Bundle
当 create_row_processor()
方法在自定义类上被重写。以前,示例代码如下所示:
from sqlalchemy.orm import Bundle class DictBundle(Bundle): def create_row_processor(self, query, procs, labels): """Override create_row_processor to return values as dictionaries""" def proc(row, result): return dict( zip(labels, (proc(row, result) for proc in procs)) ) return proc
未使用的 result
成员现在被删除::
from sqlalchemy.orm import Bundle class DictBundle(Bundle): def create_row_processor(self, query, procs, labels): """Override create_row_processor to return values as dictionaries""" def proc(row): return dict( zip(labels, (proc(row) for proc in procs)) ) return proc
参见
右内部联接嵌套现在是joinedLoad的默认值,inner join=true
行为 joinedload.innerjoin
以及 relationship.innerjoin
现在要使用“嵌套的”内部联接(即右嵌套)作为内部联接联接联接的预加载链接到外部联接的预加载时的默认行为。为了获得在存在外部联接时将所有联接的热切负载作为外部联接链接的旧行为,请使用 innerjoin="unnested"
.
如中所述 右嵌套的内部联接在联接的热切加载中可用 从0.9版开始, innerjoin="nested"
是指链接到外部连接的内部连接热切负载将使用正确的嵌套连接。 "nested"
现在在使用时暗示 innerjoin=True
::
query(User).options( joinedload("orders", innerjoin=False).joinedload("items", innerjoin=True))
使用新的默认值,这将以如下形式呈现FROM子句:
FROM users LEFT OUTER JOIN (orders JOIN items ON <onclause>) ON <onclause>
也就是说,对内部联接使用正确的嵌套联接,以便 users
可以退货。使用内部联接比使用外部联接更有效,并且允许 joinedload.innerjoin
在所有情况下生效的优化参数。
要获得旧行为,请使用 innerjoin="unnested"
::
query(User).options( joinedload("orders", innerjoin=False).joinedload("items", innerjoin="unnested"))
这将避免正确的嵌套联接,并使用所有外部联接将联接链接在一起,尽管innerjoin指令为:
FROM users LEFT OUTER JOIN orders ON <onclause> LEFT OUTER JOIN items ON <onclause>
如0.9注释中所述,唯一难以正确嵌套联接的数据库后端是sqlite;从0.9开始的sqlachemy将正确嵌套联接转换为子查询,作为sqlite上的联接目标。
参见
右嵌套的内部联接在联接的热切加载中可用 -0.9.4中介绍的功能说明。
子查询不再应用于useList=false joined-earch-loads
给定一个像下面这样的连接的渴望负载:
class A(Base): __tablename__ = 'a' id = Column(Integer, primary_key=True) b = relationship("B", uselist=False) class B(Base): __tablename__ = 'b' id = Column(Integer, primary_key=True) a_id = Column(ForeignKey('a.id')) s = Session() print(s.query(A).options(joinedload(A.b)).limit(5))
sqlAlchemy考虑了这种关系 A.b
“一对多,加载为单个值”,这本质上是一种“一对一”关系。但是,Joined Earge Loading始终将上述情况视为主查询需要位于子查询中的情况,就像主查询有限制的B对象集合通常需要的情况一样:
SELECT anon_1.a_id AS anon_1_a_id, b_1.id AS b_1_id, b_1.a_id AS b_1_a_id FROM (SELECT a.id AS a_id FROM a LIMIT :param_1) AS anon_1 LEFT OUTER JOIN b AS b_1 ON anon_1.a_id = b_1.a_id
但是,由于内部查询与外部查询的关系是,在 uselist=False
(与多对一的方式相同),在这种情况下,与limit+joined earchy loading一起使用的“子查询”现在被删除:
SELECT a.id AS a_id, b_1.id AS b_1_id, b_1.a_id AS b_1_a_id FROM a LEFT OUTER JOIN b AS b_1 ON a.id = b_1.a_id LIMIT :param_1
在左外部联接返回多行的情况下,ORM始终在此处发出警告并忽略 uselist=False
,因此导致该错误情况不应改变。
query.update()/query.delete()如果与join()一起使用,则会引发,请从u self()中选择u from()。
当 Query.update()
或 Query.delete()
方法是针对已调用的查询调用的 Query.join()
, Query.outerjoin()
, Query.select_from()
或 Query.from_self()
. 这些都是不受支持的用例,在0.9.10之前的0.9系列中,这些用例会无声地失败,并在其中发出警告。在1.0中,这些情况会引发异常。
query.update()使用 synchronize_session='evaluate'
在多表更新时引发
的“评估者” Query.update()
无法使用多表更新,需要设置为 synchronize_session=False
或 synchronize_session='fetch'
当存在多个表时。新的行为是现在引发一个显式异常,并显示一条消息来更改同步设置。这是从0.9.7发出的警告升级而来的。
复活事件已被删除
“复活”ORM事件已被完全删除。自0.8版从工作单元中删除旧的“可变”系统以来,此事件不再具有任何功能。
使用from_self(),count()时更改为单表继承条件
给定单个表继承映射,例如:
class Widget(Base): __table__ = 'widget_table' class FooWidget(Widget): pass
使用 Query.from_self()
或 Query.count()
对于子类,将生成子查询,但随后将子类的“Where”条件添加到外部:
sess.query(FooWidget).from_self().all()
致使::
SELECT anon_1.widgets_id AS anon_1_widgets_id, anon_1.widgets_type AS anon_1_widgets_type FROM (SELECT widgets.id AS widgets_id, widgets.type AS widgets_type, FROM widgets) AS anon_1 WHERE anon_1.widgets_type IN (?)
问题在于,如果内部查询没有指定所有列,那么我们就不能在外部添加WHERE子句(它实际上会尝试,并产生一个错误的查询)。这一决定显然可以追溯到0.6.5,并注明“可能需要对此作出更多调整”。好吧,这些调整已经到了!所以现在上面的查询将呈现:
SELECT anon_1.widgets_id AS anon_1_widgets_id, anon_1.widgets_type AS anon_1_widgets_type FROM (SELECT widgets.id AS widgets_id, widgets.type AS widgets_type, FROM widgets WHERE widgets.type IN (?)) AS anon_1
因此,不包含“type”的查询仍然有效!::
sess.query(FooWidget.id).count()
渲染:
SELECT count(*) AS count_1 FROM (SELECT widgets.id AS widgets_id FROM widgets WHERE widgets.type IN (?)) AS anon_1
无条件添加到所有ON子句的单表继承条件
当连接到单表继承子类目标时,ORM总是在连接关系时添加“单表条件”。给定映射为:
class Widget(Base): __tablename__ = 'widget' id = Column(Integer, primary_key=True) type = Column(String) related_id = Column(ForeignKey('related.id')) related = relationship("Related", backref="widget") __mapper_args__ = {'polymorphic_on': type} class FooWidget(Widget): __mapper_args__ = {'polymorphic_identity': 'foo'} class Related(Base): __tablename__ = 'related' id = Column(Integer, primary_key=True)
在相当长的一段时间内,关系上的联接将为类型呈现“single inheritance”子句,这是一种行为:
s.query(Related).join(FooWidget, Related.widget).all()
SQL输出:
SELECT related.id AS related_id FROM related JOIN widget ON related.id = widget.related_id AND widget.type IN (:type_1)
因为我们加入了一个子类 FooWidget
, Query.join()
知道添加 AND widget.type IN ('foo')
ON条款的标准。
这里的零钱是 AND widget.type IN()
条件现在附加到 any on子句,而不仅仅是由关系生成的,包括明确声明的:
# ON clause will now render as # related.id = widget.related_id AND widget.type IN (:type_1) s.query(Related).join(FooWidget, FooWidget.related_id == Related.id).all()
以及“隐式”联接,当没有任何类型的ON子句时:
# ON clause will now render as # related.id = widget.related_id AND widget.type IN (:type_1) s.query(Related).join(FooWidget).all()
以前,这些的ON子句不包括单个继承条件。已经在添加此条件以解决此问题的应用程序将希望删除其显式使用,但如果同时呈现两次此条件,它将继续正常工作。
参见
关键行为变化-核心
将完整的SQL片段强制转换为文本()时发出警告
自从sqlacalchemy诞生以来,一直强调不要妨碍纯文本的使用。核心和ORM表达式系统旨在允许用户使用纯文本SQL表达式的任意数量的点,而不仅仅是在您可以向其发送完整的SQL字符串的意义上。 Connection.execute()
,但可以将带有SQL表达式的字符串发送到许多函数中,例如 Select.where()
, Query.filter()
和 Select.order_by()
.
注意,“SQL表达式”是指 SQL字符串的完整片段 ,例如:
# the argument sent to where() is a full SQL expression stmt = select([sometable]).where("somecolumn = 'value'")
我们是 不谈论字符串参数 也就是说,传递变为参数化的字符串值的正常行为:
# This is a normal Core expression with a string argument - # we aren't talking about this!! stmt = select([sometable]).where(sometable.c.somecolumn == 'value')
核心教程中有一个使用此技术的示例,使用 select()
构造它的几乎所有组件都被指定为直字符串。然而,尽管这种长期存在的行为和例子,用户显然对这种行为的存在感到惊讶,当我在社区中询问时,我无法找到任何实际上是 not 很惊讶您可以将一个完整的字符串发送到类似 Query.filter()
.
因此,这里的更改是鼓励用户在组成部分或完全由文本片段组成的SQL时限定文本字符串。按如下方式组合选择时:
stmt = select(["a", "b"]).where("a = b").select_from("sometable")
语句是正常构建的,具有与以前相同的强制。但是,您将看到发出以下警告:
SAWarning: Textual column expression 'a' should be explicitly declared with text('a'), or use column('a') for more specificity (this warning may be suppressed after 10 occurrences) SAWarning: Textual column expression 'b' should be explicitly declared with text('b'), or use column('b') for more specificity (this warning may be suppressed after 10 occurrences) SAWarning: Textual SQL expression 'a = b' should be explicitly declared as text('a = b') (this warning may be suppressed after 10 occurrences) SAWarning: Textual SQL FROM expression 'sometable' should be explicitly declared as text('sometable'), or use table('sometable') for more specificity (this warning may be suppressed after 10 occurrences)
这些警告试图通过显示参数以及接收字符串的位置来准确显示问题所在。警告使用 session.get_bind()处理各种各样的继承方案 这样,参数化的警告就可以安全地发出,而不会耗尽内存,并且一如既往,如果希望警告是异常的,那么 Python Warnings Filter 应使用:
import warnings warnings.simplefilter("error") # all warnings raise an exception
鉴于上述警告,我们的语句工作得很好,但为了消除这些警告,我们将重写语句如下:
from sqlalchemy import select, text stmt = select([ text("a"), text("b") ]).where(text("a = b")).select_from(text("sometable"))
如警告所示,如果我们使用 column()
和 table()
::
from sqlalchemy import select, text, column, table stmt = select([column("a"), column("b")]).\ where(text("a = b")).select_from(table("sometable"))
其中还要注意 table()
和 column()
现在可以从“sqlacalchemy”导入,而不需要“sql”部分。
这里的行为适用于 select()
以及关键方法 Query
包括 Query.filter()
, Query.from_statement()
和 Query.having()
.
order by和group by是特殊情况
有一种情况下,字符串的使用具有特殊意义,作为这一更改的一部分,我们增强了它的功能。当我们拥有 select()
或 Query
指的是某个列名或命名标签,我们可能希望按已知列或标签分组和/或排序:
stmt = select([ user.c.name, func.count(user.c.id).label("id_count") ]).group_by("name").order_by("id_count")
在上面的语句中,我们希望看到“order by id_count”,而不是函数的重新语句。在编译期间,给定的字符串参数与columns子句中的一个条目主动匹配,因此上面的语句将按预期生成,而不会出现警告(不过请注意 "name"
表达式已解析为 users.name
!)::
SELECT users.name, count(users.id) AS id_count FROM users GROUP BY users.name ORDER BY id_count
但是,如果我们引用了一个无法定位的名称,那么我们会再次收到警告,如下所示:
stmt = select([ user.c.name, func.count(user.c.id).label("id_count") ]).order_by("some_label")
输出按我们所说的做,但它再次警告我们:
SAWarning: Can't resolve label reference 'some_label'; converting to text() (this warning may be suppressed after 10 occurrences) SELECT users.name, count(users.id) AS id_count FROM users ORDER BY some_label
上述行为适用于我们可能希望引用所谓“标签引用”的所有位置;ORDER BY和GROUP BY,但也适用于over子句以及引用列(例如PostgreSQL语法)的DISTINCT ON子句。
我们仍然可以使用 text()
::
stmt = select([users]).order_by(text("some special expression"))
整个更改的结果是,sqlAlchemy现在希望我们在发送字符串时告诉它,此字符串显式是 text()
构造或列、表等,如果我们使用它作为ORDER BY、GROUP BY或其他表达式中的标签名称,则sqlAlchemy希望该字符串解析为已知的内容,否则它应再次使用限定符 text()
或者类似的。
当使用多值插入时,为每行单独调用python端默认值
在使用多值版本的 Insert.values()
在使用的方言使用绑定参数的非位置(如命名)样式时,以及在不需要为每行调用python端可调用时,它们基本上没有实现,只能在特定情况下“偶然”工作。
已对该功能进行了全面检查,使其工作起来更类似于“ExecuteMany”类型的调用:
import itertools counter = itertools.count(1) t = Table( 'my_table', metadata, Column('id', Integer, default=lambda: next(counter)), Column('data', String) ) conn.execute(t.insert().values([ {"data": "d1"}, {"data": "d2"}, {"data": "d3"}, ]))
上面的示例将调用 next(counter)
对于每一行,如预期的那样:
INSERT INTO my_table (id, data) VALUES (?, ?), (?, ?), (?, ?) (1, 'd1', 2, 'd2', 3, 'd3')
以前,位置方言将失败,因为不会为其他位置生成绑定::
Incorrect number of bindings supplied. The current statement uses 6, and there are 4 supplied. [SQL: u'INSERT INTO my_table (id, data) VALUES (?, ?), (?, ?), (?, ?)'] [parameters: (1, 'd1', 'd2', 'd3')]
对于“命名”方言,“id”的相同值将在每行中重复使用(因此,此更改与依赖于此的系统向后不兼容)::
INSERT INTO my_table (id, data) VALUES (:id, :data_0), (:id, :data_1), (:id, :data_2) {u'data_2': 'd3', u'data_1': 'd2', u'data_0': 'd1', 'id': 1}
系统还将拒绝调用“服务器端”默认值作为内联呈现的SQL,因为不能保证服务器端默认值与此兼容。如果values子句呈现特定列,则需要python-side值;如果省略的值只引用服务器端默认值,则引发异常:
t = Table( 'my_table', metadata, Column('id', Integer, primary_key=True), Column('data', String, server_default='some default') ) conn.execute(t.insert().values([ {"data": "d1"}, {"data": "d2"}, {}, ]))
将提高:
sqlalchemy.exc.CompileError: INSERT value for column my_table.data is explicitly rendered as a boundparameter in the VALUES clause; a Python-side value or SQL expression is required
以前,值“d1”将被复制到第三行的值中(但同样,只使用命名格式!)::
INSERT INTO my_table (data) VALUES (:data_0), (:data_1), (:data_0) {u'data_1': 'd2', u'data_0': 'd1'}
无法在该事件的运行程序中添加或删除事件侦听器
从同一事件本身内部删除事件侦听器将在迭代过程中修改列表的元素,这将导致仍然附加的事件侦听器静默地无法激发。为了在保持性能的同时防止出现这种情况,列表已替换为 collections.deque()
,它不允许在迭代期间进行任何添加或删除,而是引发 RuntimeError
.
insert…from select结构现在意味着 inline=True
使用 Insert.from_select()
现在暗示 inline=True
在 insert()
. 这有助于修复这样一个错误:在支持后端,insert…from select构造无意中被编译为“隐式返回”,在插入零行(隐式返回需要行)的情况下,这将导致中断;在插入多行(例如,插入)的情况下,这将导致任意返回数据。只有第一排)。类似的更改也应用于具有多个参数集的insert..values;隐式返回也不再为此语句发出。由于这两个结构都处理可变行数,因此 ResultProxy.inserted_primary_key
访问器不适用。以前,有一个文档说明,人们可能更喜欢 inline=True
由于某些数据库不支持返回,因此不能执行“隐式”返回,因此使用insert..from select,但在任何情况下,insert…from select都不需要隐式返回。正则显式 Insert.returning()
如果需要插入数据,则应用于返回结果行的可变数目。
autoload_with
now implies autoload=True
A Table
可以设置为通过反射 Table.autoload_with
单独::
my_table = Table('my_table', metadata, autoload_with=some_engine)
DBAPI异常包装和处理_Error()事件改进
当 Connection
对象无效,然后尝试重新连接并遇到错误;已解决此问题。
此外,最近增加了 ConnectionEvents.handle_error()
现在,对于在初始连接、重新连接以及何时发生的错误调用事件。 create_engine()
通过提供自定义连接函数 create_engine.creator
.
这个 ExceptionContext
对象具有新的数据成员 ExceptionContext.engine
这总是指 Engine
在使用中,在这些情况下, Connection
对象不可用(例如,在初始连接时)。
ForeignKeyConstraint.Columns现在是ColumnCollection
ForeignKeyConstraint.columns
以前是包含字符串或 Column
对象,取决于 ForeignKeyConstraint
是否与表关联。该系列现在是 ColumnCollection
,并且仅在 ForeignKeyConstraint
与 Table
. 一种新的存取器 ForeignKeyConstraint.column_keys
添加为无条件返回本地列集的字符串键,而不管对象是如何构造的或其当前状态如何。
metadata.sorted_tables访问器是“确定性的”
对由 MetaData.sorted_tables
访问器是“确定性的”;在任何情况下,顺序都应该相同,而不考虑Python散列。这是通过在将表传递给拓扑算法之前先按名称对表进行排序来完成的,拓扑算法在迭代时保持这种排序。
注意,这个变化确实 not 但适用于发出时应用的顺序 MetaData.create_all()
或 MetaData.drop_all()
.
空()、假()和真()常量不再是单例
这三个常量被更改为在0.9中返回“singleton”值;不幸的是,这将导致如下查询无法按预期呈现:
select([null(), null()])
仅渲染 SELECT NULL AS anon_1
因为这两个 null()
构造将以相同的形式出现 NULL
对象,而SQLAlchemy的核心模型是基于对象标识来确定词汇意义的。0.9中的更改除了节省对象开销之外没有其他重要意义;一般来说,未命名的构造需要保持在词汇上的唯一性,以便得到唯一的标记。
对于临时表/视图名称报告,sqlite/oracle有不同的方法
这个 Inspector.get_table_names()
和 Inspector.get_view_names()
对于sqlite/oracle,方法还将返回临时表和视图的名称,这些名称不是由任何其他方言提供的(对于mysql,至少是不可能的)。这种逻辑已被转移到两种新方法中。 Inspector.get_temp_table_names()
和 Inspector.get_temp_view_names()
.
请注意,特定命名临时表或临时视图的反射 Table('name', autoload=True)
或者通过类似的方法 Inspector.get_columns()
即使不是所有方言,也会继续发挥作用。特别是对于sqlite,对于临时表中的唯一约束反射也有一个bug修复,即 #3203 .
方言改进与变化-PostgreSQL
修改枚举类型创建/删除规则
《PostgreSQL规则》 ENUM
在创建和删除类型方面做得更为严格。
安 ENUM
这是创造出来的 没有 与 MetaData
将创建对象 and 删除对应于 Table.create()
和 Table.drop()
::
table = Table('sometable', metadata, Column('some_enum', ENUM('a', 'b', 'c', name='myenum')) ) table.create(engine) # will emit CREATE TYPE and CREATE TABLE table.drop(engine) # will emit DROP TABLE and DROP TYPE - new for 1.0
这意味着,如果第二个表也有一个名为“myenum”的枚举,则上述删除操作现在将失败。为了适应公共共享枚举类型的用例,增强了与元数据关联的枚举的行为。
安 ENUM
这是创造出来的 with 与 MetaData
对象意志 not 被创造 or 删除对应于 Table.create()
和 Table.drop()
,除了 Table.create()
调用与 checkfirst=True
旗帜:
my_enum = ENUM('a', 'b', 'c', name='myenum', metadata=metadata) table = Table('sometable', metadata, Column('some_enum', my_enum) ) # will fail: ENUM 'my_enum' does not exist table.create(engine) # will check for enum and emit CREATE TYPE table.create(engine, checkfirst=True) table.drop(engine) # will emit DROP TABLE, *not* DROP TYPE metadata.drop_all(engine) # will emit DROP TYPE metadata.create_all(engine) # will emit CREATE TYPE
新PostgreSQL表选项
添加了对pg table options表空间、on commit、with(out)oid和inherits的支持,当通过 Table
构造。
参见
使用PostgreSQL方言新建get enums()方法
这个 inspect()
方法返回 PGInspector
PostgreSQL中的对象,其中包括一个新的 PGInspector.get_enums()
返回所有可用信息的方法 ENUM
类型:
from sqlalchemy import inspect, create_engine engine = create_engine("postgresql+psycopg2://host/dbname") insp = inspect(engine) print(insp.get_enums())
参见
PostgreSQL方言反映物化视图、外来表
变更如下:
这个
Table
构建与autoload=True
现在将匹配数据库中作为物化视图或外部表存在的名称。Inspector.get_view_names()
将返回纯视图名称和物化视图名称。Inspector.get_table_names()
做 not 更改postgresql后,它继续只返回普通表的名称。一种新方法
PGInspector.get_foreign_table_names()
将返回PostgreSQL模式表中特别标记为“foreign”的表的名称。
对反射的更改包括添加 'm'
和 'f'
查询时使用的限定符列表 pg_class.relkind
但是这个变化在1.0.0中是新的,以避免在生产中运行0.9的人遇到任何向后不兼容的意外。
《PostgreSQL》 has_table()
现在为临时桌工作
这是一个简单的修复程序,这样临时表的“has table”就可以工作了,这样就可以继续执行以下代码:
from sqlalchemy import * metadata = MetaData() user_tmp = Table( "user_tmp", metadata, Column("id", INT, primary_key=True), Column('name', VARCHAR(50)), prefixes=['TEMPORARY'] ) e = create_engine("postgresql://scott:tiger@localhost/test", echo='debug') with e.begin() as conn: user_tmp.create(conn, checkfirst=True) # checkfirst will succeed user_tmp.create(conn, checkfirst=True)
这种行为不太可能导致非失败应用程序的行为不同,这是因为PostgreSQL允许非临时表以静默方式覆盖临时表。因此,下面这样的代码现在将完全不同,不再在临时表之后创建实际表:
from sqlalchemy import * metadata = MetaData() user_tmp = Table( "user_tmp", metadata, Column("id", INT, primary_key=True), Column('name', VARCHAR(50)), prefixes=['TEMPORARY'] ) e = create_engine("postgresql://scott:tiger@localhost/test", echo='debug') with e.begin() as conn: user_tmp.create(conn, checkfirst=True) m2 = MetaData() user = Table( "user_tmp", m2, Column("id", INT, primary_key=True), Column('name', VARCHAR(50)), ) # in 0.9, *will create* the new table, overwriting the old one. # in 1.0, *will not create* the new table user.create(conn, checkfirst=True)
PostgreSQL筛选关键字
从9.4开始,PostgreSQL现在支持聚合函数的SQL标准筛选关键字。SQLAlchemy允许使用 FunctionElement.filter()
::
func.count(1).filter(True)
参见
PG8000方言支持客户端编码
这个 create_engine.encoding
参数现在被pg8000方言接受,它使用在发出 SET CLIENT_ENCODING
匹配所选编码。
PG8000本地JSONB支持
已经添加了对大于1.10.1的PG8000版本的支持,其中本机支持JSONB。
支持pypy上的psycopg2cffi方言
增加了对pypy psycopg2cffi方言的支持。
参见
sqlalchemy.dialects.postgresql.psycopg2cffi
方言改进和变化-MySQL
MySQL时间戳类型现在在所有情况下都呈现为空/非空
MySQL方言总是绕过与时间戳列关联的MySQL隐式NOT NULL缺省值,方法是为此类类型发出NULL(如果列是用 nullable=True
。但是,MySQL 5.6.6及更高版本提供了一个新标志 explicit_defaults_for_timestamp 它修复了MySQL的非标准行为,使其行为与任何其他类型一样;为了适应这一点,SQLAlChemy现在为所有时间戳列无条件地发出NULL/NOT NULL。
参见
MySQL集合类型大修以支持空集合、Unicode、空值处理
这个 SET
历史上,类型不包括单独处理空集和空值的系统;由于不同的驱动程序在处理空字符串和空字符串集表示时具有不同的行为,因此集合类型仅尝试在这些行为之间进行对冲,选择将空集视为 set([''])
正如MySQL连接器python dbapi的当前行为一样。这里的部分理由是,在MySQL集合中实际存储一个空字符串是不可能的,因为驱动程序没有办法区分 set([''])
和 set()
. 由用户决定 set([''])
其实意思是“空套”或不是。
新的行为为空白字符串移动用例,这是一个不寻常的情况,甚至在MySQL文档中也没有记录到一个特殊的情况下,默认行为是 SET
现在是:
处理空字符串
''
由mysql python返回到空集合中set()
;转换单个空白值集
set([''])
由mysql connector python返回到空集合中set()
;若要处理实际希望的集合类型的实例,则包含空白值
''
在其可能值列表中,实现了一个新特性(在本用例中是必需的),通过该特性,设置值被持久化并作为一个位整数值加载;标志SET.retrieve_as_bitwise
是为了启用此功能而添加的。
使用 SET.retrieve_as_bitwise
标志允许持久化和检索集,而不存在值的模糊性。理论上,只要类型的给定值列表与数据库中声明的顺序完全匹配,就可以在所有情况下启用此标志;它只会使SQL Echo输出更加不寻常。
的默认行为 SET
否则将使用字符串保持相同的往返值。基于字符串的行为现在支持unicode,包括mysql python和use_unicode=0。
MySQL内部“无此类表”异常未传递给事件处理程序
MySQL方言现在将禁用 ConnectionEvents.handle_error()
从内部触发用于检测表是否存在的那些语句的事件。这是通过使用执行选项实现的 skip_user_error_events
为该执行的范围禁用句柄错误事件。通过这种方式,重写异常的用户代码不需要担心MySQL方言或其他偶尔需要捕获特定于sqlAlchemy的异常的方言。
更改了默认值 raise_on_warnings
对于MySQL连接器
将mysql connector的默认值“raise-on-u warnings”更改为false。出于某种原因,这被设为真。不幸的是,“buffered”标志必须保持为true,因为mysqlconnector不允许关闭光标,除非完全获取所有结果。
MySQL布尔符号“真”、“假”再次起作用
0.9对中的is/is运算符和布尔类型的大修 #2682 禁止MySQL方言在“is”/“is not”上下文中使用“true”和“false”符号。显然,尽管mysql没有“boolean”类型,但它支持使用特殊的“true”和“false”符号时的is/is,即使这些符号在其他方面与“1”和“0”同义(is/is不适用于数字)。
所以这里的变化是MySQL方言仍然是“非本地布尔”,但是 true()
和 false()
符号再次产生关键字“真”和“假”,使表达式像 column.is_(true())
同样适用于MySQL。
match()操作符现在返回与mysql的浮点返回值兼容的不可知匹配类型。
返回类型 ColumnOperators.match()
表达式现在是名为 MatchType
. 这是 Boolean
,可以被方言截取,以便在SQL执行时生成不同的结果类型。
下面这样的代码现在可以正常工作,并在MySQL上返回浮点值:
>>> connection.execute( ... select([ ... matchtable.c.title.match('Agile Ruby Programming').label('ruby'), ... matchtable.c.title.match('Dive Python').label('python'), ... matchtable.c.title ... ]).order_by(matchtable.c.id) ... ) [ (2.0, 0.0, 'Agile Web Development with Ruby On Rails'), (0.0, 2.0, 'Dive Into Python'), (2.0, 0.0, "Programming Matz's Ruby"), (0.0, 0.0, 'The Definitive Guide to Django'), (0.0, 1.0, 'Python in a Nutshell') ]
细雨方言现在是一种外来方言。
的方言 Drizzle 现在是一种外部方言,可从https://bitbucket.org/zzzeek/sqlalchemy-drizzle.获得此方言是在SQLAlChemy能够很好地适应第三方方言之前添加到SQLAlChemy中的;展望未来,所有不属于“无处不在的使用”类别的数据库都是第三方方言。该方言的实现没有改变,仍然基于SQLAlChemy中的MySQL+MySQLdb方言。到目前为止,这种方言还没有发布,处于“阁楼”状态;然而,如果有人想要改进它,它通过了大多数测试,总体上还处于良好的工作状态。
方言改进和变化-sqlite
SQLite命名和未命名的唯一和外键约束将检查和反映
唯一键和外键约束现在完全反映在有名称和没有名称的sqlite上。以前,忽略了外键名,跳过了未命名的唯一约束。尤其是这将有助于Alembic新的sqlite迁移功能。
为了实现这一点,对于外键和唯一约束,将pragma foreign_keys、index_list和index_info的结果与create table语句的正则表达式解析相结合,形成约束名称的完整图片,并区分创建为唯一约束和未命名约束的唯一约束。索引。
方言改进和更改-SQL Server
基于主机名的SQL Server连接需要pyodbc驱动程序名
使用无DSN连接(例如使用显式主机名)通过pyodbc连接到SQL Server,现在需要一个驱动程序名-sqlacchemy将不再尝试猜测默认值::
engine = create_engine("mssql+pyodbc://scott:tiger@myhost:port/databasename?driver=SQL+Server+Native+Client+10.0")
SQLAlchemy以前的硬编码默认值“SQL Server”在Windows上已过时,不能根据操作系统/驱动程序检测来猜测最佳驱动程序。使用ODBC时,最好使用DSN,以完全避免此问题。
SQL Server 2012大型文本/二进制类型呈现为varchar、nvarchar、varbinary
呈现 TextClause
, UnicodeText
和 LargeBinary
SQL Server 2012及更高版本的类型已更改,根据Microsoft的弃用准则,可以选择完全控制行为。见 大文本/二进制类型取消预测 有关详细信息。
方言改进和变化-Oracle
改进了对Oracle中CTE的支持
对Oracle的CTE支持已经修复,还有一个新功能 CTE.with_suffixes()
可以协助Oracle的特殊指令:
included_parts = select([ part.c.sub_part, part.c.part, part.c.quantity ]).where(part.c.part == "p1").\ cte(name="included_parts", recursive=True).\ suffix_with( "search depth first by part set ord1", "cycle part set y_cycle to 1 default 0", dialect='oracle')
DDL的新Oracle关键字
关键字,如compress、on commit、bitmap: