版本: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的“后备”模式。由于Setuptools-less Python环境现在还没有人听说过,为了更全面地支持Setuptools的特性,特别是支持py.test与它的集成以及诸如“extras”之类的东西,setup.py

也可以看看

Installation Guide

#3489 T0>

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

只要有可能,C扩展默认在安装过程中生成。要禁用C扩展构建,可以使用DISABLE_SQLALCHEMY_CEXT环境变量,从SQLAlchemy 0.8.6 / 0.9.4开始。之前使用--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等对象的所有状态之间的转换。当对象移动到Session中时,会跳出Session,甚至是使用Session.rollback()明确存在于SessionEvents的界面中。

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

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

始终将Session中对象的persistent状态记录为具有有效数据库标识的对象;然而,对于在刷新内删除的对象,它们一直处于灰色区域,它们并未真正“脱离”Session,因为它们仍可在回滚中恢复,但并不真正“持久”,因为它们的数据库标识已被删除,并且它们不存在于标识映射中。

为了解决给定新事件的灰色区域,引入了一个新的对象状态deleted这种状态存在于“持久”和“分离”状态之间。通过Session.delete()标记为删除的对象保持“持久”状态,直到刷新结束;此时,它将从身份映射中移除,移至“已删除”状态,并调用SessionEvents.persistent_to_deleted()钩子。如果回滚Session对象的事务,该对象将作为持久性恢复;调用SessionEvents.deleted_to_persistent()转换。否则,如果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

对于这个Python内部值来说,有一个用例与Core生成的默认值相对应,甚至在该对象被保存之前。为了适应这种用例,添加了一个新事件AttributeEvents.init_scalar()Attribute Instrumentation处的新示例active_column_defaults.py演示了一个示例用法,因此效果可以是:

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

#1311 T0>

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

Query对象具有众所周知的“扣除”返回行的行为,该行包含至少一个ORM映射实体(例如,完全映射的对象,而不是单个列值)。这样做的主要目的是使实体的处理能够与标识映射一起平稳地工作,包括适应通常在已加入的加载加载中表示的重复实体,以及何时使用连接来过滤额外的列。

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

事实上,SQLAlchemy从0.8版本开始在数据类型中包含一个标记,标记为“不可干扰”,但是此标志在内置类型中并未一致使用。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的新选项

与作为JSON “null” is inserted as expected with ORM operations, regardless of column default present,基本的TypeEngine类现在支持TypeEngine.evaluates_none()方法,该方法允许将属性上的None值的肯定集保留为NULL,而不是从INSERT语句中省略列,它具有使用列级别缺省的效果。这允许将现有对象级别的技术分配给属性的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 flush fixed

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功能

现在,由于Session.delete(),连接表继承映射现在可以允许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'))

在上面,尽管类A和类A1有一个名为b的关系,所以不会发生冲突警告或错误,因为类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之前保持主键唯一性。当遇到相同身份的重复对象时,遇到对象时非主键属性被覆盖,这基本上是非确定性的。此行为与持久对象(即通过主键已位于数据库中的对象)已被处理的方式相匹配,因此此行为在内部更一致。

鉴于:

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对象,但是每个对象都被赋予相同的主键。Session.merge()的当前行为是查找此Address对象的标识映射,并将其用作目标。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

在上面,我们创建了一个挂起的对象some_object,将它的外键操作为Parent来引用它,然后我们实际建立了关系。在错误修复之前,backref不会被触发:

# before the fix
assert some_object not in some_parent.items

现在的修正是,当我们试图找到some_object.parent的前一个值时,我们忽略手动设置的父ID,然后查找数据库提交的值。在这种情况下,它是None,因为对象处于挂起状态,所以事件系统将some_object.parent记录为净更改:

# 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>

使用多模实体对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中是否使用正确的“绑定”,以便呈现SQL被传递给数据库。特别是,假设Query与适当的Session相关联,这允许引用特定于方言的SQL结构的Query是可呈现的。以前,如果与映射关联的MetaData本身绑定到目标Engine,此行为才会生效。

如果底层MetaDataSession都不与任何绑定的Engine相关联,则使用“默认”方言的回退来生成SQL字符串。

#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_alias_1的上下文中加载属性C.d,而不是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”加载器策略

为了帮助防止在加载一系列对象之后发生不需要的延迟加载,可以应用新的“lazy ='raise'”策略和相应的加载程序选项orm.raiseload()关系属性,当访问非热切加载的属性以进行读取时,会导致该属性引发InvalidRequestError

>>> 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对象作为面向公众的查询结构。这个角色早已被Query对象取代,我们使用Query.order_by()来指示结果的排序,以一种对任意组合SELECT语句,实体和SQL表达式。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_expression.over.rows指定为2元组,指定特定范围的负值和正值,“CURRENT ROW”为0,无对于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

可以使用FromClause.tablesample()方法呈现SQL标准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指令,以指示自动递增的整数标识符,例如MySQL上的Postgresql或AUTO_INCREMENT上的SERIAL关键字,另外方言将接收这些生成的使用适合该后端的技术执行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的位置列设置

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

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

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

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']

不太可能出现“模糊列”错误消息

作为这种改变的一部分,错误消息的措辞不明确 名称 '&lt; name&gt; t4> in 结果 set! 'use_labels' / t10> on 选择 语句。 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>

在所有情况下,非本地布尔整型值强制为零/一/无

Boolean数据类型将Python布尔值强制为不具有本机布尔类型的后端的整数值,例如SQLite和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. 这不是一个向后不兼容的更改,因为查询失败时没有括号;通过修复,查询至少可以在所有其他数据库上工作。

在所有情况下,为了生成一个有限的SELECT语句的UNION,它也适用于SQLite,并且在所有情况下都适用于Oracle,子查询必须是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的情况比那个特性的“右嵌套连接”用例要少得多。

#2528 T0>

对Core 添加了JSON支持

由于MySQL现在除了Postgresql JSON数据类型之外还有一个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. This flag defaults to False, which means that the column should never insert SQL NULL or fall back to a default unless the null() constant were used. 但是,在两种情况下,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'"

The other is when the Session.bulk_insert_mappings() method were used, None would be ignored in all cases:

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常量已添加

为了确保应用程序始终能够完全控制types.JSONpostgresql.JSONmysql.JSON的值级别,或postgresql.JSONB列应该接收到SQL NULL或JSON "null"值时,常量types.JSON.NULL已被添加,与null()结合使用可以完全确定SQL NULL和JSON "null"之间的关系,无论types.JSON.none_as_null被设定为:

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和所有运算符

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中。types.ARRAY类型still 仅适用于Postgresql,但它可以直接使用,支持特殊的数组用例,例如索引访问,以及支持ANY和所有:

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”和“overlapps”,应该继续直接使用postgresql.ARRAY类型,它提供了types.ARRAY类型。

sql.expression.any_()sql.expression.all_()运算符在Core级别是开放式的,但是它们对后端数据库的解释是有限的。在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)

现在为这些函数提供了正确返回类型的占位符,它们包括percentile_contpercentile_discrankdense_rank modepercent_rankcume_dist

#3132 #1370

TypeDecorator现在自动使用Enum,Boolean和“模式”类型

SchemaType类型包括EnumBoolean等类型,除了与数据库类型相对应外,还会生成CHECK约束或Postgresql ENUM一个新的CREATE TYPE语句的情况下,现在将自动与TypeDecorator配方一起工作。以前,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 in-value值是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()将按位置匹配列,而不是按名称匹配

TextClause.columns()方法的新行为本身最近在0.9系列中添加时,是当列位置传递时没有任何其他关键字参数时,它们被链接到最终结果设置列的位置,并不再名称。希望这种改变的影响很小,因为这个方法总是被记录下来,说明按照与文本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依赖于能够为列值生成散列函数。hashable=False标志现在可以在所有PG的“数据结构”类型中正确设置,包括postgresql.ARRAYpostgresql.JSONJSONBHSTORE类型已包含此标志。对于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”进行修改。 ..FOR SHARE“查询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_EXTRACT函数提供JSON的持久性以及基本的索引访问。通过使用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>

不再为复合主键生成隐式KEY 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版或更高版本生效时,此更改中提供的解决方法现已解除。

#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>

检查约束现在反映

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

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 的事务隔离级别支持

所有SQL Server方言都通过create_engine.isolation_levelConnection.execution_options.isolation_level参数支持事务隔离级别设置。支持四种标准级别以及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”后缀。