版本:1.1.0b2 |发布日期:2016年7月1日

SQLAlchemy 1.1文档

更改和迁移

项目版本

SQLAlchemy 1.1中有什么新东西?

关于本文档

本文档介绍SQLAlchemy版本1.0和SQLAlchemy版本1.1之间的更改。

引言¶ T0>

本指南介绍了SQLAlchemy 1.1版中的新增功能,并介绍了影响用户将其应用程序从1.0系列SQLAlchemy迁移到1.1的更改。

请仔细阅读有关行为变化的章节,以了解行为可能出现的后向不相容变化。

平台/安装程序更改

Setuptools现在需要安装

SQLAlchemy的setup.py文件在Setuptools安装和不安装的情况下都支持多年的操作。支持使用直线Distutils的“后备”模式。As a Setuptools-less Python environment is now unheard of, and in order to support the featureset of Setuptools more fully, in particular to support py.test’s integration with it as well as things like “extras”, setup.py now depends on Setuptools fully.

也可以看看

Installation Guide

#3489 T0>

启用/禁用C扩展构建仅通过环境变量

只要有可能,C扩展默认在安装过程中生成。要禁用C扩展构建,从SQLAlchemy 0.8.6 / 0.9.4开始可以使用DISABLE_SQLALCHEMY_CEXT环境变量。之前使用--without-cextensions参数的方法已被删除,因为它依赖于setuptools的弃用功能。

#3500 T0>

新功能和改进 - ORM

新的会话生命周期事件

The Session has long supported events that allow some degree of tracking of state changes to objects, including SessionEvents.before_attach(), SessionEvents.after_attach(), and SessionEvents.before_flush(). Session文档还在Quickie Intro to Object States中记录主要对象状态。但是,在通过这些转换时,从来没有专门跟踪对象的系统。另外,由于对象在“持久”和“分离”状态之间的某个地方起作用,“被删除”对象的状态在历史上一直很模糊。

为了清理这个区域并且允许会话状态转换的领域是完全透明的,已经添加了一系列新的事件,这些事件旨在涵盖对象可能在状态之间转换的各种可能的方式,另外,“已删除”状态具有在会话对象状态领域被赋予了自己的官方名称。

新的状态转换事件

现在可以根据旨在覆盖特定转换的会话级别事件来拦截诸如persistentpending等对象的所有状态之间的转换。Transitions as objects move into a Session, move out of a Session, and even all the transitions which occur when the transaction is rolled back using Session.rollback() are explicitly present in the interface of SessionEvents.

In total, there are ten new events. 这些事件的摘要在新近撰写的文档部分Object Lifecycle Events中。

新对象状态“已删除”被添加,已删除对象不再是“持久”

始终将Session中对象的persistent状态记录为具有有效数据库标识的对象;但是对于在flush中被删除的对象,它们总是处于一个灰色区域,在那里它们并不真正从Session中“分离”,因为它们仍然可以在回滚,但并不是真正的“持久”,因为它们的数据库标识已经被删除,并且它们不存在于标识映射中。

为了解决给定新事件的灰色区域,引入了新的对象状态deleted这个状态存在于“持久”和“分离”状态之间。通过Session.delete()标记为删除的对象保持“持久”状态,直到刷新进行;在这一点上,它将从身份映射中移除,移至“已删除”状态,并调用SessionEvents.persistent_to_deleted()钩子。If the Session object’s transaction is rolled back, the object is restored as persistent; the SessionEvents.deleted_to_persistent() transition is called. 否则,如果Session对象的事务被提交,则调用SessionEvents.deleted_to_detached()转换。

此外,对于处于新“已删除”状态的对象,InstanceState.persistent访问器不再返回True;相反,InstanceState.deleted访问器已被增强,以可靠地报告这个新的状态。当对象被分离时,InstanceState.deleted将返回False,而InstanceState.detached存取器则为True。要确定在当前事务中还是在之前的事务中是否删除了对象,请使用InstanceState.was_deleted访问器。

强身份地图被弃用

对于新一系列过渡事件,其中一个启发就是在物体进出身份图时能够进行防漏跟踪,以便可以保持“强参照”,以反映物体移入和移出的情况地图。有了这个新的功能,就不再需要Session.weak_identity_map参数和相应的StrongIdentityMap对象。由于“强引用”行为曾经是唯一可用的行为,并且许多应用程序都是为了承担这种行为而编写的,所以此选项在SQLAlchemy中保留了很多年。长久以来,建议对象的强参考跟踪不是Session的固有工作,而应该是应用程序需要构建的应用级构造;新的事件模型甚至可以复制强身份映射的确切行为。有关说明如何替换强身份图的新配方,请参见Session Referencing Behavior

#2677 T0>

新的init_scalar()事件在ORM级处截取默认值

对于非持久化对象,首次访问尚未设置的属性时,ORM会生成None值:

>>> obj = MyObj()
>>> obj.some_value
None

有一个用例,即使在对象持久化之前,这个in-Python值也对应于Core生成的默认值。为了适应这个用例,添加了一个新的事件AttributeEvents.init_scalar()Attribute Instrumentation中的新示例active_column_defaults.py演示了一个示例使用,所以效果可以改为:

>>> obj = MyObj()
>>> obj.some_value
"my default"

#1311 T0>

有关“不可干扰”类型的更改

The Query object has a well-known behavior of “deduping” returned rows that contain at least one ORM-mapped entity (e.g., a full mapped object, as opposed to individual column values). 这样做的主要目的是为了使实体的处理能够顺利地与身份映射一起工作,包括适应通常在加入的加载加载中表示的重复实体,以及何时使用连接来过滤额外的列。

这种重复数据删除依赖于行内元素的可操作性。通过Postgresql的特殊类型(如postgresql.ARRAYpostgresql.HSTOREpostgresql.JSON)的引入,行内类型的体验是不可及的,遇到问题比以前更普遍。

实际上,从0.8版本开始,SQLAlchemy在数据类型中包含一个标记,标记为“不可干扰”,但是这个标志在内置类型中并没有被一致地使用。ARRAY and JSON types now correctly specify “unhashable”,现在为Postgresql的所有“结构”类型设置此标志。

由于NullType用于引用未知类型的任何表达式,因此“不可用”标志也设置在NullType类型上。

此外,所谓的“不可干扰”类型的处理与之前的版本略有不同,在内部,我们使用id()函数从这些结构中获取“散列值”,就像我们任何普通的映射对象一样。这取代了以前的方法,对对象应用了一个计数器。

#3499 T0>

添加了特定的检查以传递映射类,实例为SQL文本

现在,键入系统对SQLAlchemy“可检查”对象在上下文中的传递进行了特定的检查,否则这些对象将作为字面值进行处理。任何合法的作为SQL值传递的SQLAlchemy内置对象都包含一个为该对象提供有效SQL表达式的方法__clause_element__()对于不提供此功能的SQLAlchemy对象(如映射类,映射器和映射实例),会发出更多的信息性错误消息,而不是让DBAPI接收对象并稍后失败。一个例子如下所示,其中基于字符串的属性User.nameUser()的完整实例进行比较,而不是针对字符串值:

>>> some_user = User()
>>> q = s.query(User).filter(User.name == some_user)
...
sqlalchemy.exc.ArgumentError: Object <__main__.User object at 0x103167e90> is not legal as a SQL literal value

User.name == some_user之间进行比较时,立即发生异常。以前,像上面这样的比较会产生一个SQL表达式,只有在解析成DBAPI执行调用后才会失败;映射的User对象将最终成为将被DBAPI拒绝的绑定参数。

请注意,在上面的例子中,表达式失败,因为User.name是一个基于字符串(例如列的)属性。The change does not impact the usual case of comparing a many-to-one relationship attribute to an object, which is handled distinctly:

>>> # Address.user refers to the User mapper, so
>>> # this is of course still OK!
>>> q = s.query(Address).filter(Address.user == some_user)

#3321 T0>

新的可索引ORM扩展

Indexable扩展是对混合属性特征的扩展,它允许构建引用“可索引”数据类型的特定元素(如数组或JSON字段)的属性:

class Person(Base):
    __tablename__ = 'person'

    id = Column(Integer, primary_key=True)
    data = Column(JSON)

    name = index_property('data', 'name')

以上,在初始化为空字典之后,name属性将从JSON列data中读取/写入字段"name"

>>> person = Person(name='foobar')
>>> person.name
foobar

当属性被修改时,扩展也会触发一个改变事件,所以不需要使用MutableDict来跟踪这个改变。

也可以看看

Indexable

新的选项允许在默认的上显式地持久化NULL

Related to the new JSON-NULL support added to Postgresql as part of JSON “null” is inserted as expected with ORM operations, regardless of column default present, the base TypeEngine class now supports a method TypeEngine.evaluates_none() which allows a positive set of the None value on an attribute to be persisted as NULL, rather than omitting the column from the INSERT statement, which has the effect of using the column-level default. 这允许对属性赋予sql.null()的现有对象级别技术的映射器级配置。

也可以看看

Forcing NULL on a column with a default强制NULL列

#3250 T0>

进一步修复单表继承查询

Continuing from 1.0’s Change to single-table-inheritance criteria when using from_self(), count(), the Query should no longer inappropriately add the “single inheritance” criteria when the query is against a subquery expression such as an exists:

class Widget(Base):
    __tablename__ = 'widget'
    id = Column(Integer, primary_key=True)
    type = Column(String)
    data = Column(String)
    __mapper_args__ = {'polymorphic_on': type}


class FooWidget(Widget):
    __mapper_args__ = {'polymorphic_identity': 'foo'}

q = session.query(FooWidget).filter(FooWidget.data == 'bar').exists()

session.query(q).all()

生产:

SELECT EXISTS (SELECT 1
FROM widget
WHERE widget.data = :data_1 AND widget.type IN (:type_1)) AS anon_1

内部的IN子句是合适的,为了限制到FooWidget对象,但是以前IN子句也会在子查询的外面再次生成。

#3582 T0>

当数据库取消SAVEPOINT时,改进的会话状态

MySQL的一个常见情况是当事务内发生死锁时,SAVEPOINT被取消。Session已经被修改,稍微更优雅地处理这个失败模式,这样外部的非保存点事务仍然可用:

s = Session()
s.begin_nested()

s.add(SomeObject())

try:
    # assume the flush fails, flush goes to rollback to the
    # savepoint and that also fails
    s.flush()
except Exception as err:
    print("Something broke, and our SAVEPOINT vanished too")

# this is the SAVEPOINT transaction, marked as
# DEACTIVE so the rollback() call succeeds
s.rollback()

# this is the outermost transaction, remains ACTIVE
# so rollback() or commit() can succeed
s.rollback()

这个问题是#2696的延续,我们发出警告,以便在Python 2上运行时可以看到原始错误,即使SAVEPOINT异常优先。在Python 3中,异常是链接的,因此两个失败都会单独报告。

#3680 T0>

错误的“新实例X与持久性实例Y冲突”flush错误修正

Session.rollback()方法负责删除插入到数据库中的对象,例如从挂起转移到持久,在现在回滚的交易。使这种状态变化的对象在弱引用集合中被跟踪,并且如果一个对象从该集合中被垃圾收集,Session不再担心它(否则它不会为插入操作交易中的许多新对象)。但是,如果应用程序在回滚之前重新加载同一个垃圾收集行,则会出现问题;如果这个对象的强引用保留在下一个事务中,那么这个对象没有被插入并且应该被移除的事实将会丢失,并且flush会错误地引发一个错误:

from sqlalchemy import Column, create_engine
from sqlalchemy.orm import Session
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class A(Base):
    __tablename__ = 'a'
    id = Column(Integer, primary_key=True)

e = create_engine("sqlite://", echo=True)
Base.metadata.create_all(e)

s = Session(e)

# persist an object
s.add(A(id=1))
s.flush()

# rollback buffer loses reference to A

# load it again, rollback buffer knows nothing
# about it
a1 = s.query(A).first()

# roll back the transaction; all state is expired but the
# "a1" reference remains
s.rollback()

# previous "a1" conflicts with the new one because we aren't
# checking that it never got committed
s.add(A(id=1))
s.commit()

上述计划将会提高:

FlushError: New instance <User at 0x7f0287eca4d0> with identity key
(<class 'test.orm.test_transaction.User'>, ('u1',)) conflicts
with persistent instance <User at 0x7f02889c70d0>

错误在于,当上面的异常被提出时,工作单元正在对原始对象进行操作,假设它是一个活动行,而事实上对象已经过期,并且经过测试显示它已经消失。修复现在测试这个条件,所以在SQL日志中我们看到:

BEGIN (implicit)

INSERT INTO a (id) VALUES (?)
(1,)

SELECT a.id AS a_id FROM a LIMIT ? OFFSET ?
(1, 0)

ROLLBACK

BEGIN (implicit)

SELECT a.id AS a_id FROM a WHERE a.id = ?
(1,)

INSERT INTO a (id) VALUES (?)
(1,)

COMMIT

上面,工作单元现在为我们将要报告的行做一个SELECT作为冲突,看到它不存在,并正常进行。这个SELECT的开销只有在我们在任何情况下都会错误地提出异常的情况下才会发生。

#3677 T0>

被加入继承映射的passive_deletes功能

一个连接表继承映射现在可以允许一个DELETE继续作为Session.delete()的结果,它只为基表发出DELETE,而不是子类表,允许配置ON DELETE CASCADE为配置的外键进行。这是使用orm.mapper.passive_deletes选项配置的:

from sqlalchemy import Column, Integer, String, ForeignKey, create_engine
from sqlalchemy.orm import Session
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()


class A(Base):
    __tablename__ = "a"
    id = Column('id', Integer, primary_key=True)
    type = Column(String)

    __mapper_args__ = {
        'polymorphic_on': type,
        'polymorphic_identity': 'a',
        'passive_deletes': True
    }


class B(A):
    __tablename__ = 'b'
    b_table_id = Column('b_table_id', Integer, primary_key=True)
    bid = Column('bid', Integer, ForeignKey('a.id', ondelete="CASCADE"))
    data = Column('data', String)

    __mapper_args__ = {
        'polymorphic_identity': 'b'
    }

通过上面的映射,在基本映射器上配置orm.mapper.passive_deletes选项;它对于所有非映射器的映射器的后裔与选项设置生效。对于B类型的对象,DELETE不再需要检索b_table_id的主键值,也不需要为表本身发出DELETE语句:

session.delete(some_b)
session.commit()

将发射SQL如下:

DELETE FROM a WHERE a.id = %(id)s
{'id': 1}
COMMIT

与往常一样,目标数据库必须具有启用了ON DELETE CASCADE的外键支持。

#2349 T0>

相同名称的backrefs在应用于具体继承子类时不会产生错误

下面的映射总是可以没有问题的:

class A(Base):
    __tablename__ = 'a'
    id = Column(Integer, primary_key=True)
    b = relationship("B", foreign_keys="B.a_id", backref="a")

class A1(A):
    __tablename__ = 'a1'
    id = Column(Integer, primary_key=True)
    b = relationship("B", foreign_keys="B.a1_id", backref="a1")
    __mapper_args__ = {'concrete': True}

class B(Base):
    __tablename__ = 'b'
    id = Column(Integer, primary_key=True)

    a_id = Column(ForeignKey('a.id'))
    a1_id = Column(ForeignKey('a1.id'))

在上面,即使class A和class A1有一个名为b的关系,因为class A1

但是,如果以其他方式配置关系,则会发生错误:

class A(Base):
    __tablename__ = 'a'
    id = Column(Integer, primary_key=True)


class A1(A):
    __tablename__ = 'a1'
    id = Column(Integer, primary_key=True)
    __mapper_args__ = {'concrete': True}


class B(Base):
    __tablename__ = 'b'
    id = Column(Integer, primary_key=True)

    a_id = Column(ForeignKey('a.id'))
    a1_id = Column(ForeignKey('a1.id'))

    a = relationship("A", backref="b")
    a1 = relationship("A1", backref="b")

该修补程序增强了backref功能,以便不会发出错误,还会在映射程序逻辑中进行额外的检查以绕过要替换的属性的警告。

#3630 T0>

混合属性和方法现在传播docstring以及.info

混合方法或属性现在将反映原始文档字符串中存在的__doc__值:

class A(Base):
    __tablename__ = 'a'
    id = Column(Integer, primary_key=True)

    name = Column(String)

    @hybrid_property
    def some_name(self):
        """The name field"""
        return self.name

以上的A.some_name.__doc__值现在可以得到:

>>> A.some_name.__doc__
The name field

但是,要实现这一点,混合属性的机制必然变得更加复杂。以前,混合类的级别访问器是一个简单的pass-thru,也就是说,这个测试会成功:

>>> assert A.name is A.some_name

随着更改,由A.some_name返回的表达式被封装在它自己的QueryableAttribute包装器中:

>>> A.some_name
<sqlalchemy.orm.attributes.hybrid_propertyProxy object at 0x7fde03888230>

很多测试都是为了确保这个包装器正常工作,包括类似于Custom Value Object配方的详细方案,但是我们将会看到,没有其他的用户回归。

作为此更改的一部分,现在还从混合描述符本身传播hybrid_property.info集合,而不是从基础表达式传播。也就是说,访问A.some_name.info现在返回从inspect(A).all_orm_descriptors['some_name'].info

>>> A.some_name.info['foo'] = 'bar'
>>> from sqlalchemy import inspect
>>> inspect(A).all_orm_descriptors['some_name'].info
{'foo': 'bar'}

请注意,这个.info字典与混合描述符可以直接代理的映射属性的分开这是从1.0的行为变化。包装器仍将代理镜像属性的其他有用属性,如QueryableAttribute.propertyQueryableAttribute.class_

#3653 T0>

Session.merge解决与持久相同的未决冲突

现在,Session.merge()方法将跟踪图中给定的对象的身份,以便在发出INSERT之前保持主键的唯一性。When duplicate objects of the same identity are encountered, non-primary-key attributes are overwritten as the objects are encountered, which is essentially non-deterministic. 此行为与持久对象(即通过主键已经位于数据库中的对象)已被处理的方式相匹配,因此此行为在内部更一致。

鉴于:

u1 = User(id=7, name='x')
u1.orders = [
    Order(description='o1', address=Address(id=1, email_address='a')),
    Order(description='o2', address=Address(id=1, email_address='b')),
    Order(description='o3', address=Address(id=1, email_address='c'))
]

sess = Session()
sess.merge(u1)

上面,我们将一个User对象与三个新的Order对象合并,每个对象指向一个不同的Address对象,但是每个对象都被赋予相同的主键。The current behavior of Session.merge() is to look in the identity map for this Address object, and use that as the target. If the object is present, meaning that the database already has a row for Address with primary key “1”, we can see that the email_address field of the Address will be overwritten three times, in this case with the values a, b and finally c.

但是,如果主键“1”的Address行不存在,Session.merge()会改为创建三个单独的Address然后在INSERT时我们会得到一个主键冲突。新的行为是为这些Address对象建议的主键在单独的字典中被跟踪,以便我们将三个建议的Address对象的状态合并到一个Address要插入的对象。

如果原始案例发出某种警告,即在单一合并树中存在冲突数据,可能会更可取,但价值的非确定性合并已成为持续多年的行为。它现在匹配的悬案。警告冲突值的功能对于这两种情况仍然可行,但会增加相当大的性能开销,因为在合并期间必须对每个列值进行比较。

#3601 T0>

修复涉及多对一的对象移动用户启动foriegn键操作

一个错误已经被修复,涉及用另一个对象替换对象的多对一引用的机制。在属性操作期间,之前提到的对象的位置现在使用数据库提交的外键值,而不是当前的外键值。修复的主要效果是,即使外键属性被预先手动移动到新值,在进行多对一更改时,对集合的backref事件也会更准确地触发。Assume a mapping of the classes Parent and SomeClass, where SomeClass.parent refers to Parent and Parent.items refers to the collection of SomeClass objects:

some_object = SomeClass()
session.add(some_object)
some_object.parent_id = some_parent.id
some_object.parent = some_parent

Above, we’ve made a pending object some_object, manipulated its foreign key towards Parent to refer to it, then we actually set up the relationship. 在错误修复之前,backref将不会被触发:

# before the fix
assert some_object not in some_parent.items

现在修复的是,当我们试图找到some_object.parent的前一个值时,忽略手动设置的父ID,我们查找数据库提交的值。In this case, it’s None because the object is pending, so the event system logs some_object.parent as a net change:

# after the fix, backref fired off for some_object.parent = some_parent
assert some_object in some_parent.items

虽然不鼓励操纵由关系管理的外键属性,但对此用例的支持有限。为了允许加载而操纵外键的应用程序通常会利用Session.enable_relationship_loading()RelationshipProperty.load_on_pending特性,这些特性会导致关系发出延迟加载基于内存中不存在的外键值。无论这些功能是否正在使用,这种行为改善现在将是显而易见的。

#3708 T0>

使用polymoprhic实体改进Query.correlate方法

在最近的SQLAlchemy版本中,由多种形式的“多态”查询生成的SQL具有比以前更为“扁平化”的形式,其中几个表的JOIN不再无条件地绑定到子查询中。为了适应这种情况,现在,Query.correlate()方法从这种多态可选项中提取单个表,并确保所有都是子查询的“关联”的一部分。假设映射文档中的Person/Manager/Engineer->Company设置,使用with_polymorphic:

sess.query(Person.name)
            .filter(
                sess.query(Company.name).
                filter(Company.company_id == Person.company_id).
                correlate(Person).as_scalar() == "Elbonia, Inc.")

上面的查询现在生成:

SELECT people.name AS people_name
FROM people
LEFT OUTER JOIN engineers ON people.person_id = engineers.person_id
LEFT OUTER JOIN managers ON people.person_id = managers.person_id
WHERE (SELECT companies.name
FROM companies
WHERE companies.company_id = people.company_id) = ?

Before the fix, the call to correlate(Person) would inadvertently attempt to correlate to the join of Person, Engineer and Manager as a single unit, so Person wouldn’t be correlated:

-- old, incorrect query
SELECT people.name AS people_name
FROM people
LEFT OUTER JOIN engineers ON people.person_id = engineers.person_id
LEFT OUTER JOIN managers ON people.person_id = managers.person_id
WHERE (SELECT companies.name
FROM companies, people
WHERE companies.company_id = people.company_id) = ?

使用相关的子查询对多态映射仍然有一些未完善的边缘。例如,如果Person是多态链接到所谓的“具体多态联合”查询,则上述子查询可能不会正确引用此子查询。在所有情况下,完全引用“多态”实体的方法是先从它创建一个aliased()对象:

# works with all SQLAlchemy versions and all types of polymorphic
# aliasing.

paliased = aliased(Person)
sess.query(paliased.name)
            .filter(
                sess.query(Company.name).
                filter(Company.company_id == paliased.company_id).
                correlate(paliased).as_scalar() == "Elbonia, Inc.")

aliased()结构保证“多态可选”包装在子查询中。通过在相关的子查询中明确地引用它,正确地使用多态形式。

#3662 T0>

查询的字符串化将会向会话查询正确的方言

Query对象上调用str()会查询Session中是否使用了正确的“绑定”,以便呈现被传递到数据库。特别是,假设Query与适当的Session相关联,这允许引用特定于方言的SQL结构的Query是可呈现的。以前,如果与映射关联的MetaData本身绑定到目标Engine,此行为才会生效。

If neither the underlying MetaData nor the Session are associated with any bound Engine, then the fallback to the “default” dialect is used to generate the SQL string.

#3081 T0>

加入了同一个实体在一行中多次出现的急切加载

已经做了一个修复,即通过连接的预加载来加载属性,即使实体已经从不包含该属性的不同“路径”上加载。这是一个很难重现的深层用例,但总体思路如下:

class A(Base):
    __tablename__ = 'a'
    id = Column(Integer, primary_key=True)
    b_id = Column(ForeignKey('b.id'))
    c_id = Column(ForeignKey('c.id'))

    b = relationship("B")
    c = relationship("C")


class B(Base):
    __tablename__ = 'b'
    id = Column(Integer, primary_key=True)
    c_id = Column(ForeignKey('c.id'))

    c = relationship("C")


class C(Base):
    __tablename__ = 'c'
    id = Column(Integer, primary_key=True)
    d_id = Column(ForeignKey('d.id'))
    d = relationship("D")


class D(Base):
    __tablename__ = 'd'
    id = Column(Integer, primary_key=True)


c_alias_1 = aliased(C)
c_alias_2 = aliased(C)

q = s.query(A)
q = q.join(A.b).join(c_alias_1, B.c).join(c_alias_1.d)
q = q.options(contains_eager(A.b).contains_eager(B.c, alias=c_alias_1).contains_eager(C.d))
q = q.join(c_alias_2, A.c)
q = q.options(contains_eager(A.c, alias=c_alias_2))

上面的查询发出这样的SQL:

SELECT
    d.id AS d_id,
    c_1.id AS c_1_id, c_1.d_id AS c_1_d_id,
    b.id AS b_id, b.c_id AS b_c_id,
    c_2.id AS c_2_id, c_2.d_id AS c_2_d_id,
    a.id AS a_id, a.b_id AS a_b_id, a.c_id AS a_c_id
FROM
    a
    JOIN b ON b.id = a.b_id
    JOIN c AS c_1 ON c_1.id = b.c_id
    JOIN d ON d.id = c_1.d_id
    JOIN c AS c_2 ON c_2.id = a.c_id

我们可以看到c表是从两次中选择的;一次在abc - &gt; c_alias_1的上下文中,另一个在Ac - &gt; c_alias_2Also, we can see that it is quite possible that the C identity for a single row is the same for both c_alias_1 and c_alias_2, meaning two sets of columns in one row result in only one new object being added to the identity map.

上面的查询选项只能调用属性C.dc_alias_1的上下文中加载,而不是c_alias_2因此,无论我们在标识映射中获得的最后一个C对象是否具有加载的C.d属性,都取决于遍历映射的方式,而不是完全随机的, -deterministic。The fix is that even if the loader for c_alias_1 is processed after that of c_alias_2 for a single row where they both refer to the same identity, the C.d element will still be loaded. 以前,加载器并不试图修改已经通过不同路径加载的实体的加载。首先到达实体的加载器一直是非确定性的,所以这种修复可能在某些情况下可以被检测为行为改变,而不是在其他情况下。

修复包括两种“多路径到一个实体”情况下的测试,修复应该涵盖这种性质的所有其他情况。

#3431 T0>

DISTINCT + ORDER BY 不再重复添加列

像下面这样的查询现在只会增加SELECT列表中缺少的那些列,而没有重复:

q = session.query(User.id, User.name.label('name')).\
    distinct().\
    order_by(User.id, User.name, User.fullname)

生产:

SELECT DISTINCT user.id AS a_id, user.name AS name,
 user.fullname AS a_fullname
FROM a ORDER BY user.id, user.name, user.fullname

以前,它会产生:

SELECT DISTINCT user.id AS a_id, user.name AS name, user.name AS a_name,
  user.fullname AS a_fullname
FROM a ORDER BY user.id, user.name, user.fullname

如上所述,user.name列被不必要地添加。结果不会受到影响,因为在任何情况下附加列都不包含在结果中,但列是不必要的。

另外,当传递表达式到Query.distinct()使用Postgresql DISTINCT ON格式时,上面的“列添加”逻辑被完全禁用。

当查询绑定到子查询中用于加入的加载时,“增加列表”规则必然更具侵略性,因此ORDER BY仍然可以被满足,所以这种情况保持不变。

#3641 T0>

将新的MutableList和MutableSet助手添加到突变跟踪扩展

新的帮助类MutableListMutableSet已被添加到Mutation Tracking扩展中,以补充现有的MutableDict助手。

#3297 T0>

新的“raise”加载器策略

To assist with the use case of preventing unwanted lazy loads from occurring after a series of objects are loaded, the new “lazy=’raise’” strategy and corresponding loader option orm.raiseload() may be applied to a relationship attribute which will cause it to raise InvalidRequestError when a non-eagerly-loaded attribute is accessed for read:

>>> from sqlalchemy.orm import raiseload
>>> a1 = s.query(A).options(raiseload(A.bs)).first()
>>> a1.bs
Traceback (most recent call last):
...
sqlalchemy.exc.InvalidRequestError: 'A.bs' is not available due to lazy='raise'

#3512 T0>

Mapper.order_by被弃用

SQLAlchemy最初版本中的这个旧参数是ORM原始设计的一部分,它把Mapper对象作为一个面向公众的查询结构。This role has long since been replaced by the Query object, where we use Query.order_by() to indicate the ordering of results in a way that works consistently for any combination of SELECT statements, entities and SQL expressions. There are many areas in which Mapper.order_by doesn’t work as expected (or what would be expected is not clear), such as when queries are combined into unions; these cases are not supported.

#3394 T0>

新功能和改进 - 核心

CTE支持INSERT,UPDATE,DELETE

其中一个最广泛要求的功能是支持与INSERT,UPDATE,DELETE一起使用的公共表表达式(CTE)。INSERT / UPDATE / DELETE既可以从SQL顶部的WITH子句中绘制,也可以在更大的语句的上下文中用作CTE本身。

作为此更改的一部分,包含CTE的SELECT INSERT将现在将CTE呈现在整个语句的顶部,而不是像1.0中那样嵌套在SELECT语句中。

下面是一个在一个语句中呈现UPDATE,INSERT和SELECT全部的例子:

>>> from sqlalchemy import table, column, select, literal, exists
>>> orders = table(
...     'orders',
...     column('region'),
...     column('amount'),
...     column('product'),
...     column('quantity')
... )
>>>
>>> upsert = (
...     orders.update()
...     .where(orders.c.region == 'Region1')
...     .values(amount=1.0, product='Product1', quantity=1)
...     .returning(*(orders.c._all_columns)).cte('upsert'))
>>>
>>> insert = orders.insert().from_select(
...     orders.c.keys(),
...     select([
...         literal('Region1'), literal(1.0),
...         literal('Product1'), literal(1)
...     ]).where(~exists(upsert.select()))
... )
>>>
>>> print(insert)  # note formatting added for clarity
WITH upsert AS
(UPDATE orders SET amount=:amount, product=:product, quantity=:quantity
 WHERE orders.region = :region_1
 RETURNING orders.region, orders.amount, orders.product, orders.quantity
)
INSERT INTO orders (region, amount, product, quantity)
SELECT
    :param_1 AS anon_1, :param_2 AS anon_2,
    :param_3 AS anon_3, :param_4 AS anon_4
WHERE NOT (
    EXISTS (
        SELECT upsert.region, upsert.amount,
               upsert.product, upsert.quantity
        FROM upsert))

#2551 T0>

支持窗口函数中的RANGE和ROWS规范

新的expression.over.range_expression.over.rows参数允许窗口函数的RANGE和ROWS表达式:

>>> from sqlalchemy import func

>>> print func.row_number().over(order_by='x', range_=(-5, 10))
row_number() OVER (ORDER BY x RANGE BETWEEN :param_1 PRECEDING AND :param_2 FOLLOWING)

>>> print func.row_number().over(order_by='x', rows=(None, 0))
row_number() OVER (ORDER BY x ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)

>>> print func.row_number().over(order_by='x', range_=(-2, None))
row_number() OVER (ORDER BY x RANGE BETWEEN :param_1 PRECEDING AND UNBOUNDED FOLLOWING)

expression.over.range_ and expression.over.rows are specified as 2-tuples and indicate negative and positive values for specific ranges, 0 for “CURRENT ROW”, and None for UNBOUNDED.

也可以看看

Window Functions

#3049 T0>

支持SQL LATERAL关键字

目前已知LATERAL关键字只被Postgresql 9.3和更高版本支持,但因为它是SQL关键字添加到Core的标准支持的一部分。Select.lateral()的实现采用了特殊的逻辑,而不仅仅是渲染LATERAL关键字,以允许从相同的FROM子句派生的表的相关性为可选择的,例如,横向相关:

>>> from sqlalchemy import table, column, select, true
>>> people = table('people', column('people_id'), column('age'), column('name'))
>>> books = table('books', column('book_id'), column('owner_id'))
>>> subq = select([books.c.book_id]).\
...      where(books.c.owner_id == people.c.people_id).lateral("book_subq")
>>> print(select([people]).select_from(people.join(subq, true())))
SELECT people.people_id, people.age, people.name
FROM people JOIN LATERAL (SELECT books.book_id AS book_id
FROM books WHERE books.owner_id = people.people_id)
AS book_subq ON true

#2857 T0>

支持TABLESAMPLE

SQL标准TABLESAMPLE可以使用FromClause.tablesample()方法呈现,该方法返回类似于别名的TableSample结构:

from sqlalchemy import func

selectable = people.tablesample(
            func.bernoulli(1),
            name='alias',
            seed=func.random())
stmt = select([selectable.c.people_id])

假设people具有列people_id,则上述语句将呈现为:

SELECT alias.people_id FROM
people AS alias TABLESAMPLE bernoulli(:bernoulli_1)
REPEATABLE (random())

#3718 T0>

.autoincrement指令不再为组合主键列启用

SQLAlchemy总是具有为后端数据库的“自动增量”功能启用单列整数主键的便利功能;通过“autoincrement”,我们的意思是数据库列将包括数据库提供的任何DDL指令,以指示自动递增的整数标识符,例如PostgreSQL上的SERIAL关键字或MySQL上的AUTO_INCREMENT,另外方言将接收这些生成的使用适合该后端的技术执行Table.insert()结构的值。

改变的是这个特性不再为复合主键自动打开;以前,一个表定义如:

Table(
    'some_table', metadata,
    Column('x', Integer, primary_key=True),
    Column('y', Integer, primary_key=True)
)

将“自动增量”语义应用于'x'列,只是因为它在主键列的列表中第一个。为了禁用它,必须在所有列上关闭autoincrement

# old way
Table(
    'some_table', metadata,
    Column('x', Integer, primary_key=True, autoincrement=False),
    Column('y', Integer, primary_key=True, autoincrement=False)
)

使用新的行为,复合主键将不会有自动增量语义,除非列使用autoincrement=True显式标记:

# column 'y' will be SERIAL/AUTO_INCREMENT/ auto-generating
Table(
    'some_table', metadata,
    Column('x', Integer, primary_key=True),
    Column('y', Integer, primary_key=True, autoincrement=True)
)

In order to anticipate some potential backwards-incompatible scenarios, the Table.insert() construct will perform more thorough checks for missing primary key values on composite primary key columns that don’t have autoincrement set up; given a table such as:

Table(
    'b', metadata,
    Column('x', Integer, primary_key=True),
    Column('y', Integer, primary_key=True)
)

一个INSERT发出没有值的表将产生异常:

CompileError: Column 'b.x' is marked as a member of the primary
key for table 'b', but has no Python-side or server-side default
generator indicated, nor does it indicate 'autoincrement=True',
and no explicit value is passed.  Primary key columns may not
store NULL. Note that as of SQLAlchemy 1.1, 'autoincrement=True'
must be indicated explicitly for composite (e.g. multicolumn)
primary keys if AUTO_INCREMENT/SERIAL/IDENTITY behavior is
expected for one of the columns in the primary key. CREATE TABLE
statements are impacted by this change as well on most backends.

对于从服务器端默认或不常见的主键值(如触发器)接收主键值的列,可以使用FetchedValue来指示值生成器的存在:

Table(
    'b', metadata,
    Column('x', Integer, primary_key=True, server_default=FetchedValue()),
    Column('y', Integer, primary_key=True, server_default=FetchedValue())
)

对于非常不可能的情况,组合主键实际上是要在其一个或多个列中存储NULL(仅在SQLite和MySQL上受支持),请使用nullable=True指定列:

Table(
    'b', metadata,
    Column('x', Integer, primary_key=True),
    Column('y', Integer, primary_key=True, nullable=True)
)

在相关更改中,autoincrement标志可能在具有客户端或服务器端缺省的列上设置为True。这通常在INSERT期间对列的行为没有太大的影响。

#3216 T0>

支持IS DISTINCT FROM和IS NOT DISTINCT FROM

New operators ColumnOperators.is_distinct_from() and ColumnOperators.isnot_distinct_from() allow the IS DISTINCT FROM and IS NOT DISTINCT FROM sql operation:

>>> print column('x').is_distinct_from(None)
x IS DISTINCT FROM NULL

处理提供了NULL,True和False:

>>> print column('x').isnot_distinct_from(False)
x IS NOT DISTINCT FROM false

对于没有这个操作符的SQLite,会显示“IS”/“IS NOT”,在SQLite上,它与其他后端不同:

>>> from sqlalchemy.dialects import sqlite
>>> print column('x').is_distinct_from(None).compile(dialect=sqlite.dialect())
x IS NOT NULL

核心和ORM支持FULL OUTER JOIN

The new flag FromClause.outerjoin.full, available at the Core and ORM level, instructs the compiler to render FULL OUTER JOIN where it would normally render LEFT OUTER JOIN:

stmt = select([t1]).select_from(t1.outerjoin(t2, full=True))

该标志也适用于ORM级别:

q = session.query(MyClass).outerjoin(MyOtherClass, full=True)

#1957 T0>

ResultSet列匹配增强;文本SQL的位置列设置

对1.0系列中的ResultProxy系统进行了一系列改进,作为#918的一部分,重新​​组织内部以将游标绑定结果列与表/ ORM元数据编译的SQL结构包含有关要返回的结果行的完整信息,而不是通过匹配名称。这可以显着节省Python的开销,并将ORM和Core SQL表达式链接到结果行的准确性更高。在1.1中,这种重组已经在内部得到了进一步的实现,并且通过使用最近添加的TextClause.columns()方法也可以用于纯文本SQL结构。

TextAsFrom.columns()现在在位置上工作

0.9中添加的TextClause.columns()方法在位置上接受基于列的参数;在1.1中,当所有列在位置上通过时,这些列与最终结果集的相关性也在位置上进行。这里的关键优势在于,现在可以将文本SQL链接到ORM级别的结果集,而不需要处理不明确或重复的列名称,或者必须将标签方案与ORM级别标签方案相匹配。现在所需要的只是文本SQL和传递给TextClause.columns()的列参数的相同排序:

from sqlalchemy import text
stmt = text("SELECT users.id, addresses.id, users.id, "
     "users.name, addresses.email_address AS email "
     "FROM users JOIN addresses ON users.id=addresses.user_id "
     "WHERE users.id = 1").columns(
        User.id,
        Address.id,
        Address.user_id,
        User.name,
        Address.email_address
     )

query = session.query(User).from_statement(text).\
    options(contains_eager(User.addresses))
result = query.all()

在上面,文本SQL包含三次“id”列,这通常是不明确的。Using the new feature, we can apply the mapped columns from the User and Address class directly, even linking the Address.user_id column to the users.id column in textual SQL for fun, and the Query object will receive rows that are correctly targetable as needed, including for an eager load.

这种改变是向后不兼容的与代码传递列的方法与文本语句中存在不同的顺序。希望这个影响会很低,因为这个方法总是被记录下来,说明按照与文本SQL语句相同的顺序传递的列,尽管内部没有检查为了这。在任何情况下,该方法本身只添加0.9,可能还没有广泛使用。关于如何处理使用它的应用程序的行为改变的注意事项在TextClause.columns() will match columns positionally, not by name, when passed positionally

对于core_ORM SQL结构的名称匹配,位置匹配是值得信赖的

这个改变的另一个方面是匹配列的规则也被修改,以便更加充分地依赖于“位置”匹配来编译SQL结构。给出如下的声明:

ua = users.alias('ua')
stmt = select([users.c.user_id, ua.c.user_id])

上述声明将编译成:

SELECT users.user_id, ua.user_id FROM users, users AS ua

在1.0中,上面的语句在执行时会与使用位置匹配的原始编译结构相匹配,但是由于语句包含重复的'user_id'标签,“模糊列”规则仍然会涉及防止从一行中获取列。从1.1开始,“不明确的列”规则不会影响从列结构到SQL列的完全匹配,这是ORM用于读取列的内容:

result = conn.execute(stmt)
row = result.first()

# these both match positionally, so no error
user_id = row[users.c.user_id]
ua_id = row[ua.c.user_id]

# this still raises, however
user_id = row['user_id']

不太可能得到“不明确的列”错误消息

As part of this change, the wording of the error message Ambiguous column name '<name>' in result set! try 'use_labels' option on select statement. has been dialed back; as this message should now be extremely rare when using the ORM or Core compiled SQL constructs, it merely states Ambiguous column name '<name>' in result set column descriptions, and only when a result column is retrieved using the string name that is actually ambiguous, e.g. row['user_id'] in the above example. 它现在还引用了呈现的SQL语句本身的实际不明确名称,而不是指示用于提取的构造本地的键或名称。

#3501 T0>

支持Python的本地enum类型和兼容形式

现在可以使用任何符合PEP-435枚举类型来构造Enum类型。使用这种模式时,输入值和返回值是实际的枚举对象,而不是字符串值:

import enum
from sqlalchemy import Table, MetaData, Column, Enum, create_engine


class MyEnum(enum.Enum):
    one = "one"
    two = "two"
    three = "three"


t = Table(
    'data', MetaData(),
    Column('value', Enum(MyEnum))
)

e = create_engine("sqlite://")
t.create(e)

e.execute(t.insert(), {"value": MyEnum.two})
assert e.scalar(t.select()) is MyEnum.two

#3292 T0>

Core结果行容纳的负整数索引

The RowProxy object now accomodates single negative integer indexes like a regular Python sequence, both in the pure Python and C-extension version. 以前,负值只能在切片中工作:

>>> from sqlalchemy import create_engine
>>> e = create_engine("sqlite://")
>>> row = e.execute("select 1, 2, 3").first()
>>> row[-1], row[-2], row[1], row[-2:2]
3 2 2 (2,)

现在,Enum类型对值进行了Python验证

为了适应Python本地枚举对象,以及对于在ARRAY中使用非本地ENUM类型和CHECK约束不可行的边缘情况,Enum数据类型现在添加当使用Enum.validate_strings标志时,输入值的Python验证(1.1.0b2):

>>> from sqlalchemy import Table, MetaData, Column, Enum, create_engine
>>> t = Table(
...     'data', MetaData(),
...     Column('value', Enum("one", "two", "three", validate_strings=True))
... )
>>> e = create_engine("sqlite://")
>>> t.create(e)
>>> e.execute(t.insert(), {"value": "four"})
Traceback (most recent call last):
  ...
sqlalchemy.exc.StatementError: (exceptions.LookupError)
"four" is not among the defined enum values
[SQL: u'INSERT INTO data (value) VALUES (?)']
[parameters: [{'value': 'four'}]]

这个验证在默认情况下是关闭的,因为已经有用户不需要这种验证的用例(比如字符串比较)。对于非字符串类型,它必然发生在所有情况下。检查也无条件地发生在结果处理端,当来自数据库的值被返回时。

除了使用非本机枚举类型时创建CHECK约束的现有行为之外,此验证也是如此。现在可以使用新的Enum.create_constraint标志来禁止创建CHECK约束。

#3095 T0>

非本地布尔整型值被强制为零/ one /无所有情况

The Boolean datatype coerces Python booleans to integer values for backends that don’t have a native boolean type, such as SQLite and MySQL. 在这些后端上,通常会建立CHECK约束,确保数据库中的值实际上是这两个值中的一个。但是,MySQL会忽略CHECK约束,约束是可选的,而现有的数据库可能没有这个约束。Boolean数据类型已经被修复,使得已经是整数值的传入Python端值被强制为0或1,而不仅仅是按原样传递;此外,结果的int-to-boolean处理器的C扩展版本现在使用相同的Python布尔值解释,而不是断言一个确切的一个或零值。现在,这与纯Python的int-to-boolean处理器是一致的,并且更加宽容已经存在于数据库中的数据。None / NULL的值与以前一样保留为None / NULL。

#3730 T0>

在记录和异常显示中,大参数和行值现在被截断

现在,在记录,异常报告以及repr()中的显示期间,将显示一个作为SQL语句的绑定参数以及结果行中存在的大值的大值。行本身:

>>> from sqlalchemy import create_engine
>>> import random
>>> e = create_engine("sqlite://", echo='debug')
>>> some_value = ''.join(chr(random.randint(52, 85)) for i in range(5000))
>>> row = e.execute("select ?", [some_value]).first()
... (lines are wrapped for clarity) ...
2016-02-17 13:23:03,027 INFO sqlalchemy.engine.base.Engine select ?
2016-02-17 13:23:03,027 INFO sqlalchemy.engine.base.Engine
('E6@?>9HPOJB<<BHR:@=TS:5ILU=;JLM<4?B9<S48PTNG9>:=TSTLA;9K;9FPM4M8M@;NM6GU
LUAEBT9QGHNHTHR5EP75@OER4?SKC;D:TFUMD:M>;C6U:JLM6R67GEK<A6@S@C@J7>4=4:P
GJ7HQ6 ... (4702 characters truncated) ... J6IK546AJMB4N6S9L;;9AKI;=RJP
HDSSOTNBUEEC9@Q:RCL:I@5?FO<9K>KJAGAO@E6@A7JI8O:J7B69T6<8;F:S;4BEIJS9HM
K:;5OLPM@JR;R:J6<SOTTT=>Q>7T@I::OTDC:CC<=NGP6C>BC8N',)
2016-02-17 13:23:03,027 DEBUG sqlalchemy.engine.base.Engine Col ('?',)
2016-02-17 13:23:03,027 DEBUG sqlalchemy.engine.base.Engine
Row (u'E6@?>9HPOJB<<BHR:@=TS:5ILU=;JLM<4?B9<S48PTNG9>:=TSTLA;9K;9FPM4M8M@;
NM6GULUAEBT9QGHNHTHR5EP75@OER4?SKC;D:TFUMD:M>;C6U:JLM6R67GEK<A6@S@C@J7
>4=4:PGJ7HQ ... (4703 characters truncated) ... J6IK546AJMB4N6S9L;;9AKI;=
RJPHDSSOTNBUEEC9@Q:RCL:I@5?FO<9K>KJAGAO@E6@A7JI8O:J7B69T6<8;F:S;4BEIJS9HM
K:;5OLPM@JR;R:J6<SOTTT=>Q>7T@I::OTDC:CC<=NGP6C>BC8N',)
>>> print(row)
(u'E6@?>9HPOJB<<BHR:@=TS:5ILU=;JLM<4?B9<S48PTNG9>:=TSTLA;9K;9FPM4M8M@;NM6
GULUAEBT9QGHNHTHR5EP75@OER4?SKC;D:TFUMD:M>;C6U:JLM6R67GEK<A6@S@C@J7>4
=4:PGJ7HQ ... (4703 characters truncated) ... J6IK546AJMB4N6S9L;;9AKI;
=RJPHDSSOTNBUEEC9@Q:RCL:I@5?FO<9K>KJAGAO@E6@A7JI8O:J7B69T6<8;F:S;4BEIJS9H
MK:;5OLPM@JR;R:J6<SOTTT=>Q>7T@I::OTDC:CC<=NGP6C>BC8N',)

#2837 T0>

带有LIMIT / OFFSET / ORDER BY的UNION或类似的SELECT现在将嵌入的选择符括起来

与其他人一样,由于SQLite缺乏能力而长期处于困境的问题现在已经得到增强,可以在所有支持的后端上运行。我们引用一个查询,它是一个SELECT语句的UNION,它们本身包含行限制或排序功能,包括LIMIT,OFFSET和/或ORDER BY:

(SELECT x FROM table1 ORDER BY y LIMIT 1) UNION
(SELECT x FROM table2 ORDER BY y LIMIT 2)

上面的查询需要在每个子选择内进行括号,以正确地对子结果进行分组。在SQLAlchemy Core中生成上面的语句如下所示:

stmt1 = select([table1.c.x]).order_by(table1.c.y).limit(1)
stmt2 = select([table1.c.x]).order_by(table2.c.y).limit(2)

stmt = union(stmt1, stmt2)

以前,上面的构造不会产生内部SELECT语句的括号,产生一个在所有后端都失败的查询。

The above formats will continue to fail on SQLite; additionally, the format that includes ORDER BY but no LIMIT/SELECT will continue to fail on Oracle. 这不是一个向后不兼容的更改,因为查询失败,没有括号;与修复,查询至少在所有其他数据库上工作。

在所有情况下,为了生成也适用于SQLite并且在所有情况下都在Oracle上的有限SELECT语句的UNION,子查询必须是ALIAS的SELECT:

stmt1 = select([table1.c.x]).order_by(table1.c.y).limit(1).alias().select()
stmt2 = select([table2.c.x]).order_by(table2.c.y).limit(2).alias().select()

stmt = union(stmt1, stmt2)

此解决方法适用于所有SQLAlchemy版本。在ORM中,它看起来像:

stmt1 = session.query(Model1).order_by(Model1.y).limit(1).subquery().select()
stmt2 = session.query(Model2).order_by(Model2.y).limit(1).subquery().select()

stmt = session.query(Model1).from_statement(stmt1.union(stmt2))

这里的行为与在Many JOIN and LEFT OUTER JOIN expressions will no longer be wrapped in (SELECT * FROM ..) AS ANON_1中。但是在这种情况下,我们选择不添加新的重写行为来适应SQLite的这种情况。现有的重写行为已经非常复杂了,而使用括号化SELECT语句的UNION的情况比那个特性的“right-nested-join”用例要少得多。

#2528 T0>

对Core 添加了JSON支持

由于除了Postgresql JSON数据类型之外,MySQL现在还有一个JSON数据类型,所以现在内核获得了一个sqlalchemy.types.JSON数据类型,它们是这两个数据类型的基础。使用这种类型允许以对Postgresql和MySQL不可知的方式访问“getitem”运算符以及“getpath”运算符。

新的数据类型还对NULL值的处理以及表达式处理进行了一系列的改进。

#3619 T0>

JSON“null”按预期插入ORM操作,而不管列默认存在

The types.JSON type and its descendant types postgresql.JSON and mysql.JSON have a flag types.JSON.none_as_null which when set to True indicates that the Python value None should translate into a SQL NULL rather than a JSON NULL value. 此标志默认为False,这意味着除非使用了null()常量,否则列应该从不插入SQL NULL或回退到默认值。但是,在两种情况下,这在ORM中将会失败;一个是该列还包含default或server_default值,映射属性上的正值None仍然会导致列级别的默认值被触发,替换None

obj = MyObject(json_value=None)
session.add(obj)
session.commit()   # would fire off default / server_default, not encode "'none'"

另一个是在使用Session.bulk_insert_mappings()方法时,无论在任何情况下都将忽略None

session.bulk_insert_mappings(
    MyObject,
    [{"json_value": None}])  # would insert SQL NULL and/or trigger defaults

types.JSON类型现在实现了TypeEngine.should_evaluate_none标志,表示None在这里不应该被忽略。它是根据types.JSON.none_as_null的值自动配置的。感谢#3061,我们可以区分当None值是由用户主动设置还是从未设置。

如果没有设置该属性,那么列级别的默认值触发,并且/或者SQL NULL将按照预期插入,就像以前的行为一样。下面说明两个变体:

obj = MyObject(json_value=None)
session.add(obj)
session.commit()   # *will not* fire off column defaults, will insert JSON 'null'

obj = MyObject()
session.add(obj)
session.commit()   # *will* fire off column defaults, and/or insert SQL NULL

该功能也适用于新的基础types.JSON类型及其后代类型。

#3514 T0>

新的JSON.NULL常量已添加

To ensure that an application can always have full control at the value level of whether a types.JSON, postgresql.JSON, mysql.JSON, or postgresql.JSONB column should receive a SQL NULL or JSON "null" value, the constant types.JSON.NULL has been added, which in conjunction with null() can be used to determine fully between SQL NULL and JSON "null", regardless of what types.JSON.none_as_null is set to:

from sqlalchemy import null
from sqlalchemy.dialects.postgresql import JSON

obj1 = MyObject(json_value=null())  # will *always* insert SQL NULL
obj2 = MyObject(json_value=JSON.NULL)  # will *always* insert JSON string "null"

session.add_all([obj1, obj2])
session.commit()

该功能也适用于新的基础types.JSON类型及其后代类型。

#3514 T0>

阵列支持添加到核心;新的ANY和ALL运算符

Along with the enhancements made to the Postgresql postgresql.ARRAY type described in Correct SQL Types are Established from Indexed Access of ARRAY, JSON, HSTORE, the base class of postgresql.ARRAY itself has been moved to Core in a new class types.ARRAY.

数组是SQL标准的一部分,还有几个面向数组的函数,如array_agg()unnest()为了支持这些结构不仅适用于PostgreSQL,而且也适用于将来其他具有阵列能力的后端,例如DB2,SQL表达式的大部分数组逻辑现在都在Core中。The types.ARRAY type still only works on Postgresql, however it can be used directly, supporting special array use cases such as indexed access, as well as support for the ANY and ALL:

mytable = Table("mytable", metadata,
        Column("data", ARRAY(Integer, dimensions=2))
    )

expr = mytable.c.data[5][6]

expr = mytable.c.data[5].any(12)

In support of ANY and ALL, the types.ARRAY type retains the same types.ARRAY.Comparator.any() and types.ARRAY.Comparator.all() methods from the PostgreSQL type, but also exports these operations to new standalone operator functions sql.expression.any_() and sql.expression.all_(). 这两个函数以更多的传统SQL方式工作,允许右侧的表达形式如:

from sqlalchemy import any_, all_

select([mytable]).where(12 == any_(mytable.c.data[5]))

对于特定于PostgreSQL的运算符“contains”,“contained_by”和“overlaps”,应该继续直接使用postgresql.ARRAY类型,它提供了types.ARRAY类型。

The sql.expression.any_() and sql.expression.all_() operators are open-ended at the Core level, however their interpretation by backend databases is limited. 在Postgresql后端,两个运算符只接受数组值而在MySQL后端,它们只接受子查询值在MySQL上,可以使用如下表达式:

from sqlalchemy import any_, all_

subq = select([mytable.c.value])
select([mytable]).where(12 > any_(subq))

#3516 T0>

新功能特性“WITHIN GROUP”,array_agg和集合函数

使用新的types.ARRAY类型,我们还可以为返回数组的array_agg() SQL函数实现一个预先定义的函数,该函数现在可以使用array_agg

from sqlalchemy import func
stmt = select([func.array_agg(table.c.value)])

用于聚合ORDER BY的Postgresql元素也通过postgresql.aggregate_order_by添加:

from sqlalchemy.dialects.postgresql import aggregate_order_by
expr = func.array_agg(aggregate_order_by(table.c.a, table.c.b.desc()))
stmt = select([expr])

生产:

SELECT array_agg(table1.a ORDER BY table1.b DESC) AS array_agg_1 FROM table1

PG方言本身也提供一个postgresql.array_agg()包装来确保postgresql.ARRAY类型:

from sqlalchemy.dialects.postgresql import array_agg
stmt = select([array_agg(table.c.value).contains('foo')])

Additionally, functions like percentile_cont(), percentile_disc(), rank(), dense_rank() and others that require an ordering via WITHIN GROUP (ORDER BY <expr>) are now available via the FunctionElement.within_group() modifier:

from sqlalchemy import func
stmt = select([
    department.c.id,
    func.percentile_cont(0.5).within_group(
        department.c.salary.desc()
    )
])

上面的语句会产生类似于以下的SQL:

SELECT department.id, percentile_cont(0.5)
WITHIN GROUP (ORDER BY department.salary DESC)

Placeholders with correct return types are now provided for these functions, and include percentile_cont, percentile_disc, rank, dense_rank, mode, percent_rank, and cume_dist.

#3132 #1370

TypeDecorator现在自动使用Enum,Boolean,“schema”类型。

The SchemaType types include types such as Enum and Boolean which, in addition to corresponding to a database type, also generate either a CHECK constraint or in the case of Postgresql ENUM a new CREATE TYPE statement, will now work automatically with TypeDecorator recipes. 以前,postgresql.ENUMTypeDecorator必须如下所示:

# old way
class MyEnum(TypeDecorator, SchemaType):
    impl = postgresql.ENUM('one', 'two', 'three', name='myenum')

    def _set_table(self, table):
        self.impl._set_table(table)

现在,TypeDecorator传播这些附加事件,因此可以像其他类型那样完成:

# new way
class MyEnum(TypeDecorator):
    impl = postgresql.ENUM('one', 'two', 'three', name='myenum')

#2919 T0>

表对象的多租户模式转换

为了支持在许多模式中使用同一组Table对象的应用程序的用例,例如schema-per-user,新的执行选项Connection.execution_options.schema_translate_map使用这个映射,一组Table对象可以以每个连接为基础来引用任何一组模式,而不是指向它们的Table.schema该翻译适用于DDL和SQL生成以及ORM。

例如,如果User类被分配了模式“per_user”:

class User(Base):
    __tablename__ = 'user'
    id = Column(Integer, primary_key=True)

    __table_args__ = {'schema': 'per_user'}

在每个请求中,Session可以设置为每次引用不同的模式:

session = Session()
session.connection(execution_options={
    "schema_translate_map": {"per_user": "account_one"}})

# will query from the ``account_one.user`` table
session.query(User).get(5)

#2685 T0>

核心SQL结构的“友好”字符串化,没有方言

在Core SQL构造上调用str()现在会在比以前更多的情况下产生一个字符串,支持默认SQL中通常不存在的各种SQL构造,如RETURNING,数组索引和非标准数据类型:

>>> from sqlalchemy import table, column
t>>> t = table('x', column('a'), column('b'))
>>> print(t.insert().returning(t.c.a, t.c.b))
INSERT INTO x (a, b) VALUES (:a, :b) RETURNING x.a, x.b

现在,str()函数调用一个完全独立的方言/编译器,用于纯粹的字符串打印,没有设置特定的方言,所以更多的“只显示一个字符串!可以添加到这个方言/编译器,而不会影响真正的方言行为。

#3631 T0>

type_coerce函数现在是一个持久化的SQL元素

之前的expression.type_coerce()函数将根据输入返回一个类型为BindParameterLabel的对象。这样做的效果是,在使用表达式转换的情况下,例如从Column转换为BindParameter的元素对于ORM级懒惰加载时,类型强制信息将不会被使用,因为它已经丢失了。

为了改善这种行为,函数现在在给定的表达式周围返回一个持久的TypeCoerce容器,它本身不受影响;这个构造是由SQL编译器明确地评估的。这允许保持内部表达式的强制,而不管语句如何被修改,包括如果被包含的元素被替换为不同的元素,如在ORM的延迟加载特征中常见的那样。

说明这种效果的测试用例使用了一个异构的primaryjoin条件,并结合了自定义类型和延迟加载。给定一个将CAST作为“绑定表达式”应用的自定义类型:

class StringAsInt(TypeDecorator):
    impl = String

    def column_expression(self, col):
        return cast(col, Integer)

    def bind_expression(self, value):
        return cast(value, String)

然后,我们将一个表上的字符串“id”列与另一个表上的整数“id”列相等的映射:

class Person(Base):
    __tablename__ = 'person'
    id = Column(StringAsInt, primary_key=True)

    pets = relationship(
        'Pets',
        primaryjoin=(
            'foreign(Pets.person_id)'
            '==cast(type_coerce(Person.id, Integer), Integer)'
        )
    )

class Pets(Base):
    __tablename__ = 'pets'
    id = Column('id', Integer, primary_key=True)
    person_id = Column('person_id', Integer)

relationship.primaryjoin表达式中,我们使用type_coerce()来处理通过lazyloading作为整数传递的绑定参数,因为我们已经知道这些参数将来自StringAsInt类型,它在Python中将值保持为整数。然后我们使用cast(),所以作为一个SQL表达式,VARCHAR“id”列将被CAST为一个常规非转换连接的整数,如同Query.join()orm.joinedload()也就是说,.pets的连接加载看起来像:

SELECT person.id AS person_id, pets_1.id AS pets_1_id,
       pets_1.person_id AS pets_1_person_id
FROM person
LEFT OUTER JOIN pets AS pets_1
ON pets_1.person_id = CAST(person.id AS INTEGER)

如果在连接的ON子句中没有CAST,则强类型数据库(如Postgresql)将拒绝隐式比较整数和失败。

.pets的lazyload情况依赖于在加载时使用绑定参数替换Person.id列,该绑定参数接收一个Python加载的值。这个替换是特别的,我们的type_coerce()函数的意图将会丢失。在变化之前,这个懒惰的负载出现如下:

SELECT pets.id AS pets_id, pets.person_id AS pets_person_id
FROM pets
WHERE pets.person_id = CAST(CAST(%(param_1)s AS VARCHAR) AS INTEGER)
{'param_1': 5}

在上面的例子中,我们看到,我们的Python内部的值是5,先是CAST到VARCHAR,然后返回到SQL中的INTEGER;一个双重CAST的工作,但并不是我们所要求的。

随着改变,即使在列被换出来作为绑定参数之后,type_coerce()函数也会维护一个包装器,现在查询如下所示:

SELECT pets.id AS pets_id, pets.person_id AS pets_person_id
FROM pets
WHERE pets.person_id = CAST(%(param_1)s AS INTEGER)
{'param_1': 5}

在我们的主要连接的外部CAST仍然生效的地方,但是根据type_coerce()函数的意图,去除了StringAsInt自定义类型的一部分的不必要的CAST。

#3531 T0>

关键行为改变 - ORM

关键行为改变 - 核心

当位置通过时,TextClause.columns()将按位置匹配列,而不是按名称匹配

The new behavior of the TextClause.columns() method, which itself was recently added as of the 0.9 series, is that when columns are passed positionally without any additional keyword arguments, they are linked to the ultimate result set columns positionally, and no longer on name. 希望这种改变的影响会很低,因为这个方法总是被记录下来,说明按照与文本SQL语句相同的顺序传递的列,尽管内部不是那么直观不检查这个。

通过将Column对象传递给它的应用程序必须确保这些Column对象的位置与文本SQL中所列的位置匹配。

例如,代码如下:

stmt = text("SELECT id, name, description FROM table")

# no longer matches by name
stmt = stmt.columns(my_table.c.name, my_table.c.description, my_table.c.id)

将不再按预期工作;给出的列的顺序现在是重要的:

# correct version
stmt = stmt.columns(my_table.c.id, my_table.c.name, my_table.c.description)

可能更有可能的是,这样的陈述:

stmt = text("SELECT * FROM table")
stmt = stmt.columns(my_table.c.id, my_table.c.name, my_table.c.description)

现在稍有风险,因为“*”规范通常会按照它们在表格中出现的顺序来传递列。如果由于模式更改而导致表的结构发生更改,则此排序可能不再相同。因此,在使用TextClause.columns()时,建议在文本SQL中显式列出所需的列,尽管在文本SQL中不再需要担心名称本身。

方言的改进和改变 - Postgresql

支持INSERT..ON CONFLICT(DO UPDATE | DO NOTHING)

The ON CONFLICT clause of INSERT added to Postgresql as of version 9.5 is now supported using a Postgresql-specific version of the Insert object, via sqlalchemy.dialects.postgresql.dml.insert(). This Insert subclass adds two new methods Insert.on_conflict_do_update() and Insert.on_conflict_do_nothing() which implement the full syntax supported by Posgresql 9.5 in this area:

from sqlalchemy.dialects.postgresql import insert

insert_stmt = insert(my_table). \\
    values(id='some_id', data='some data to insert')

do_update_stmt = insert_stmt.on_conflict_do_update(
    index_elements=[my_table.c.id],
    set_=dict(data='some data to update')
)

conn.execute(do_update_stmt)

以上将呈现:

INSERT INTO my_table (id, data)
VALUES (:id, :data)
ON CONFLICT id DO UPDATE SET data=:data_2

#3529 T0>

现在,ARRAY和JSON类型正确指定“不可用”

Changes regarding “unhashable” types中所述,当查询的选定实体将完整的ORM实体与列表达式混合时,ORM依赖于能够为列值生成散列函数。现在在所有PG的“数据结构”类型(包括postgresql.ARRAYpostgresql.JSON)上正确设置了hashable=False标志。JSONBHSTORE类型已包含此标志。对于postgresql.ARRAY,这是基于postgresql.ARRAY.as_tuple标志的条件,但是不应该再设置该标志来获得数组值呈现在组成的ORM行中。

#3499 T0>

从ARRAY,JSON,HSTORE的索引访问建立正确的SQL类型

For all three of ARRAY, JSON and HSTORE, the SQL type assigned to the expression returned by indexed access, e.g. col[someindex], should be correct in all cases.

这包括:

  • 分配给ARRAY的索引访问的SQL类型考虑了配置的维数。具有三个维度的ARRAY将返回一个具有少于一个维度的ARRAY类型的SQL表达式。给定一个类型为ARRAY(Integer, dimensions = 3)的列,我们现在可以执行下面的表达式:

    int_expr = col[5][6][7]   # returns an Integer expression object

    以前,对col[5]的索引访问将返回一个Integer类型的表达式,我们不能再对剩余的维度执行索引访问,除非我们使用cast()type_coerce()

  • 现在,JSONJSONB类型反映了Postgresql本身为索引访问所做的工作。This means that all indexed access for a JSON or JSONB type returns an expression that itself is always JSON or JSONB itself, unless the astext modifier is used. 这意味着无论JSON结构的索引访问最终是指字符串,列表,数字还是其他JSON结构,Postgresql始终认为它本身是JSON本身,除非明确地进行了不同的转换。ARRAY类型一样,这意味着现在可以直接生成具有多级索引访问的JSON表达式:

    json_expr = json_col['key1']['attr1'][5]
  • The “textual” type that is returned by indexed access of HSTORE as well as the “textual” type that is returned by indexed access of JSON and JSONB in conjunction with the astext modifier is now configurable; it defaults to Text in both cases but can be set to a user-defined type using the postgresql.JSON.astext_type or postgresql.HSTORE.text_type parameters.

#3499 #3487

JSON cast()操作现在需要.astext被显式调用

As part of the changes in Correct SQL Types are Established from Indexed Access of ARRAY, JSON, HSTORE, the workings of the ColumnElement.cast() operator on postgresql.JSON and postgresql.JSONB no longer implictly invoke the postgresql.JSON.Comparator.astext modifier; Postgresql’s JSON/JSONB types support CAST operations to each other without the “astext” aspect.

这意味着在大多数情况下,这样做的应用程序:

expr = json_col['somekey'].cast(Integer)

现在需要改变这个:

expr = json_col['somekey'].astext.cast(Integer)

带有ENUM的ARRAY现在将为ENUM 发出CREATE TYPE

像下面这样的表定义现在将按照预期发出CREATE TYPE:

enum = Enum(
    'manager', 'place_admin', 'carwash_admin',
    'parking_admin', 'service_admin', 'tire_admin',
    'mechanic', 'carwasher', 'tire_mechanic', name="work_place_roles")

class WorkPlacement(Base):
    __tablename__ = 'work_placement'
    id = Column(Integer, primary_key=True)
    roles = Column(ARRAY(enum))


e = create_engine("postgresql://scott:tiger@localhost/test", echo=True)
Base.metadata.create_all(e)

发出:

CREATE TYPE work_place_roles AS ENUM (
    'manager', 'place_admin', 'carwash_admin', 'parking_admin',
    'service_admin', 'tire_admin', 'mechanic', 'carwasher',
    'tire_mechanic')

CREATE TABLE work_placement (
    id SERIAL NOT NULL,
    roles work_place_roles[],
    PRIMARY KEY (id)
)

#2729 T0>

检查约束现在反映

Postgresql方言现在支持在方法Inspector.get_check_constraints()以及Table.constraints内的Table反射内的CHECK约束的反射。采集。

可以单独检查“Plain”和“Materialized”视图

新参数PGInspector.get_view_names.include允许指定应返回哪些子类型的视图:

from sqlalchemy import inspect
insp = inspect(engine)

plain_views = insp.get_view_names(include='plain')
all_views = insp.get_view_names(include=('plain', 'materialized'))

#3588 T0>

添加索引的表空间选项

为了指定TABLESPACE,Index对象现在接受参数postgresql_tablespace,这与Table对象接受的方式相同。

#3720 T0>

支持PyGreSQL

现在支持PyGreSQL DBAPI。

也可以看看

pygresql

“postgres”模块被删除

长期弃用的sqlalchemy.dialects.postgres模块被删除;这已经发出了多年的警告,项目应该调用sqlalchemy.dialects.postgresql不过,postgres://格式的引擎网址仍然可以继续使用。

支持FOR UPDATE SKIP LOCKED /无密钥更新/用于密钥共享

Core和ORM中的新参数GenerativeSelect.with_for_update.skip_lockedGenerativeSelect.with_for_update.key_share对“SELECT ... FOR UPDATE”或“SELECT” ..共享“在Postgresql后端查询:

  • 选择无钥匙更新:

    stmt = select([table]).with_for_update(key_share=True)
  • SELECT FOR UPDATE SKIP LOCKED:

    stmt = select([table]).with_for_update(skip_locked=True)
  • 选择关键共享:

    stmt = select([table]).with_for_update(read=True, key_share=True)

方言的改进和改变 - MySQL

MySQL JSON支持

新的类型mysql.JSON被添加到支持新添加到MySQL 5.7的JSON类型的MySQL方言中。这种类型提供了JSON的持久性以及内部使用JSON_EXTRACT函数的基本索引访问。可以通过使用MySQL和Postgresql共同的types.JSON数据类型实现跨MySQL和Postgresql的可索引JSON列。

#3547 T0>

增加了对AUTOCOMMIT“隔离级别”的支持

The MySQL dialect now accepts the value “AUTOCOMMIT” for the create_engine.isolation_level and Connection.execution_options.isolation_level parameters:

connection = engine.connect()
connection = connection.execution_options(
    isolation_level="AUTOCOMMIT"
)

隔离级别利用了大多数MySQL DBAPI提供的各种“自动提交”属性。

#3332 T0>

不再为复合主键生成一个隐式的键w / AUTO_INCREMENT

MySQL方言具有这样的行为,如果InnoDB表上的组合主键在其不是第一列的列之一上具有AUTO_INCREMENT,例如:

t = Table(
    'some_table', metadata,
    Column('x', Integer, primary_key=True, autoincrement=False),
    Column('y', Integer, primary_key=True, autoincrement=True),
    mysql_engine='InnoDB'
)

将会生成如下所示的DDL:

CREATE TABLE some_table (
    x INTEGER NOT NULL,
    y INTEGER NOT NULL AUTO_INCREMENT,
    PRIMARY KEY (x, y),
    KEY idx_autoinc_y (y)
)ENGINE=InnoDB

注意上面的“KEY”是一个自动生成的名字;这是多年前在方言中发现的一个变化,以回应AUTO_INCREMENT在没有这个额外的KEY的情况下会在InnoDB上失败的问题。

这个解决方法已经被删除,并用主键内的AUTO_INCREMENT列first更好的系统替换:

CREATE TABLE some_table (
    x INTEGER NOT NULL,
    y INTEGER NOT NULL AUTO_INCREMENT,
    PRIMARY KEY (y, x)
)ENGINE=InnoDB

为了明确地控制主键列的排序,明确地使用PrimaryKeyConstraint结构(1.1.0b2)(以及MySQL要求的自动增量列的KEY),例如:

t = Table(
    'some_table', metadata,
    Column('x', Integer, primary_key=True),
    Column('y', Integer, primary_key=True, autoincrement=True),
    PrimaryKeyConstraint('x', 'y'),
    UniqueConstraint('y'),
    mysql_engine='InnoDB'
)

随着The .autoincrement directive is no longer implicitly enabled for a composite primary key column .autoincrement指令不再为组合主键列隐式启用,现在可以更容易地指定具有或不具有自动增量的组合主键; Column.autoincrement现在默认为"auto"值,不再需要autoincrement=False指令:

t = Table(
    'some_table', metadata,
    Column('x', Integer, primary_key=True),
    Column('y', Integer, primary_key=True, autoincrement=True),
    mysql_engine='InnoDB'
)

方言的改进和改变 - SQLite

SQLite版本3.7.16提升了右嵌套连接解决方​​法

在0.9版本中,由Many JOIN and LEFT OUTER JOIN expressions will no longer be wrapped in (SELECT * FROM ..) AS ANON_1Ironically, the version of SQLite noted in that migration note, 3.7.15.2, was the last version of SQLite to actually have this limitation! 下一个版本是3.7.16,并且正确的添加了对嵌套连接的支持。在1.1中,确定进行此更改的特定SQLite版本和源提交的工作已经完成(SQlite的更改日志中引用了隐含短语“增强查询优化器利用传递连接约束”,而不链接到任何问题编号,更改数字或进一步的解释),并且当DBAPI报告3.7.16或更高版本生效时,现在为SQLite解除了此更改中提供的解决方法。

#3634 T0>

为SQLite版本3.10.0解除了虚线列名解决方法

对于数据库驱动程序不报告某些SQL结果集的正确列名的问题,特别是在使用UNION时,SQLite方言早就有了一个解决方法。解决方法详见Dotted Column Names,并要求SQLAlchemy假定任何带有点的列名实际上都是通过此错误行为提供的tablename.columnname组合,可以通过sqlite_raw_colnames执行选项将其关闭。

从SQLite版本3.10.0开始,UNION和其他查询中的bug已经修复;就像Right-nested join workaround lifted for SQLite version 3.7.16中描述的更改一样,SQLite的更改日志仅将其标识为“将sqlite3_index_info的colUsed字段添加到sqlite3_module.xBestIndex方法”此版本不再需要SQLAlchemy对这些虚线列名称的翻译,因此在检测到版本3.10.0或更高版本时关闭。

总的来说,1.0版本的SQLAlchemy ResultProxy在为Core和ORM SQL结构提供结果时,对结果集中的列名的依赖要少得多,所以这个问题在任何情况下的重要性都已经减轻了。

#3633 T0>

改进了对远程模式的支持

The SQLite dialect now implements Inspector.get_schema_names() and additionally has improved support for tables and indexes that are created and reflected from a remote schema, which in SQLite is a dataase that is assigned a name via the ATTACH statement; previously, the``CREATE INDEX`` DDL didn’t work correctly for a schema-bound table and the Inspector.get_foreign_keys() method will now indicate the given schema in the results. 跨架构外键不受支持。

PRIMARY KEY约束名称的反映

现在,SQLite后端利用SQLite的“sqlite_master”视图来从原始DDL中提取表的主键约束的名称,这与最近SQLAlchemy版本中的外键约束所实现的方式相同。

#3629 T0>

检查约束现在反映

The SQLite dialect now supports reflection of CHECK constraints both within the method Inspector.get_check_constraints() as well as within Table reflection within the Table.constraints collection.

ON DELETE和ON UPDATE外键关键词现在反映

The Inspector will now include ON DELETE and ON UPDATE phrases from foreign key constraints on the SQLite dialect, and the ForeignKeyConstraint object as reflected as part of a Table will also indicate these phrases.

方言的改进和改变 - SQL Server

增加了对SQL Server 的事务隔离级别支持

All SQL Server dialects support transaction isolation level settings via the create_engine.isolation_level and Connection.execution_options.isolation_level parameters. 支持四个标准级别以及SNAPSHOT

engine = create_engine(
    "mssql+pyodbc://scott:tiger@ms_2008",
    isolation_level="REPEATABLE READ"
)

#3534 T0>

字符串/ varlength类型不再在反射上明确表示“max”

反映StringText等类型时其中包含一个长度,SQL Server下的“un-extended”类型会将“length”参数复制为值"max"

>>> from sqlalchemy import create_engine, inspect
>>> engine = create_engine('mssql+pyodbc://scott:tiger@ms_2008', echo=True)
>>> engine.execute("create table s (x varchar(max), y varbinary(max))")
>>> insp = inspect(engine)
>>> for col in insp.get_columns("s"):
...     print(col['type'].__class__, col['type'].length)
...
<class 'sqlalchemy.sql.sqltypes.VARCHAR'> max
<class 'sqlalchemy.dialects.mssql.base.VARBINARY'> max

基本类型中的“length”参数预期为整数值或None; None表示SQL Server方言解释为“max”的无限长度。然后,修正是这样,这些长度出现为None,以便类型对象在非SQL Server上下文中工作:

>>> for col in insp.get_columns("s"):
...     print(col['type'].__class__, col['type'].length)
...
<class 'sqlalchemy.sql.sqltypes.VARCHAR'> None
<class 'sqlalchemy.dialects.mssql.base.VARBINARY'> None

可能依赖于“长度”值与字符串“max”直接比较的应用程序应该考虑None的值来表示相同的事情。

#3504 T0>

支持主键上的“非聚簇”,以允许在其他地方聚簇

UniqueConstraintPrimaryKeyConstraintIndex上可用的mssql_clustered标志现在默认为None并且可以设置为False,这将会为主键提供NONCLUSTERED关键字,允许使用不同的索引作为“聚集”。

legacy_schema_aliasing标志现在被设置为False

SQLAlchemy 1.0.5 introduced the legacy_schema_aliasing flag to the MSSQL dialect, allowing so-called “legacy mode” aliasing to be turned off. 此别名尝试将模式限定的表转换为别名;给定一个表如:

account_table = Table(
    'account', metadata,
    Column('id', Integer, primary_key=True),
    Column('info', String(100)),
    schema="customer_schema"
)

传统的行为模式将尝试将符合模式的表名称转换为别名:

>>> eng = create_engine("mssql+pymssql://mydsn", legacy_schema_aliasing=True)
>>> print(account_table.select().compile(eng))
SELECT account_1.id, account_1.info
FROM customer_schema.account AS account_1

但是,这种别名已被证明是不必要的,并且在许多情况下会产生不正确的SQL。

在SQLAlchemy 1.1中,legacy_schema_aliasing标志现在默认为False,禁用了这种行为模式,并允许MSSQL方言对使用模式限定的表正常运行。对于可能依赖于此行为的应用程序,请将该标志设置为True。

#3434 T0>

方言的改进和改变 - Oracle

支持SKIP LOCKED

Core和ORM中的新参数GenerativeSelect.with_for_update.skip_locked将生成“SELECT ... FOR UPDATE”或“SELECT .. FOR SHARE”查询的“SKIP LOCKED”后缀。