目录

SQLAlchemy 1.1有什么新功能?

优质
小牛编辑
131浏览
2023-12-01

关于此文档

本文档描述了SQLAlchemy版本1.0和SQLAlchemy版本1.1之间的更改。

介绍

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

请仔细查看有关行为变化的章节,了解行为中潜在的向后不兼容的变化。

平台/安装程序更改

安装现在需要安装工具

SQLAlchemy setup.py 多年来,无论安装了安装工具还是不安装安装安装工具,文件都支持操作;支持使用直distuils的“回退”模式。作为一个安装工具,没有python的环境现在是闻所未闻的,为了更全面地支持安装工具的特性集,特别是支持py.test与它的集成以及诸如“extras”之类的东西, setup.py 现在完全取决于设置工具。

参见

安装指南

#3489

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

C扩展在安装期间默认构建,只要可能。要禁用C扩展生成,请 DISABLE_SQLALCHEMY_CEXT 环境变量从sqlAlchemy 0.8.6/0.9.4起可用。以前使用 --without-cextensions 参数已被删除,因为它依赖于不推荐使用的安装工具功能。

参见

安装C延长件

#3500

新功能和改进-ORM

新会话生命周期事件

这个 Session 长期支持的事件允许对对象的状态更改进行某种程度的跟踪,包括 SessionEvents.before_attach()SessionEvents.after_attach()SessionEvents.before_flush() . 会议文件还记录了以下主要目标国: Quickie对象状态简介 . 然而,从未有过跟踪系统,特别是在对象通过这些转换时。此外,“已删除”对象的状态在历史上一直是模糊的,因为对象的行为介于“持久”和“分离”状态之间。

为了清理此区域并使会话状态转换领域完全透明,添加了一系列新的事件,旨在涵盖对象可能在状态之间转换的所有可能方式,此外,“已删除”状态在会话对象状态领域内被赋予其自己的官方状态名。S.

新状态转换事件

对象的所有状态之间的转换,例如 persistentpending 而现在可以根据会话级事件拦截其他事件,以涵盖特定的转换。对象移动到 Session 走出一个 Session ,甚至在事务回滚时使用 Session.rollback() 在的接口中显式存在 SessionEvents .

总共有 十个新事件 . 这些事件的摘要在一个新的书面文档部分 对象生命周期事件 .

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

这个 persistent 对象的状态 Session 始终记录为具有有效数据库标识的对象;但是,对于在刷新过程中删除的对象,它们始终处于灰色区域,在该区域中,它们不会真正与 Session 但是,因为它们仍然可以在回滚中恢复,但实际上并不是“持久的”,因为它们的数据库标识已被删除,并且不在标识映射中。

为了解决这个灰色区域的新事件,一个新的对象状态 deleted 介绍。这种状态存在于“持久”和“分离”状态之间。通过标记为删除的对象 Session.delete() 保持在“持久”状态,直到刷新进行;此时,它将从标识映射中删除,移动到“已删除”状态,并且 SessionEvents.persistent_to_deleted() 钩子被调用。如果 Session 对象的事务将回滚,对象将作为持久性还原;对象 SessionEvents.deleted_to_persistent() 调用转换。否则,如果 Session 对象的事务已提交, SessionEvents.deleted_to_detached() 调用转换。

另外, InstanceState.persistent 存取器 不再返回真 对于处于新“已删除”状态的对象;相反, InstanceState.deleted 访问器已经得到了增强,可以可靠地报告这个新状态。当对象被分离时, InstanceState.deleted 返回false和 InstanceState.detached 访问器为true。要确定对象是在当前事务还是在上一个事务中被删除,请使用 InstanceState.was_deleted 访问器。

不推荐使用强标识映射

新一系列转换事件的灵感之一是在对象移入和移出标识映射时启用对象的防漏跟踪,以便维护“强引用”,以镜像移入和移出此映射的对象。有了这个新功能,就不再需要 Session.weak_identity_map 参数和相应的 StrongIdentityMap 对象。由于“强引用”行为曾经是唯一可用的行为,因此该选项在SQLAlchemy中保留了多年,并且许多应用程序都是为假定该行为而编写的。长期以来,人们一直建议对对象进行强引用跟踪,而不是 Session 相反,它是根据应用程序的需要构建的应用程序级构造;新的事件模型甚至允许复制强标识映射的确切行为。见 会话引用行为 关于如何替换强标识图的新配方。

#2677

new init_scalar()事件在ORM级别截取默认值

ORM产生的值为 None 首次访问未设置的属性时,对于非持久对象:

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

在python值中有这样一个用例,它对应于核心生成的默认值,甚至在对象被持久化之前。为了适应这个用例,一个新的事件 AttributeEvents.init_scalar() 添加。新实例 active_column_defaults.py属性检测 演示了一个示例使用,因此效果可以是:

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

#1311

有关“不可清除”类型的更改,影响ORM行的删除

这个 Query 对象具有众所周知的“重复数据消除”返回行行为,这些返回行至少包含一个ORM映射实体(例如,完全映射对象,而不是单个列值)。这样做的主要目的是使实体的处理与标识映射一起顺利进行,包括适应通常在联接的热切加载中表示的重复实体,以及当联接用于过滤附加列时。

这种重复数据消除依赖于行中元素的哈希性。介绍了PostgreSQL的特殊类型,比如 ARRAYHSTOREJSON ,行中类型不可显示和遇到问题的经验比以前更普遍。

实际上,从0.8版开始,sqlAlchemy就在数据类型上包含了一个标记,标记为“不可显示”,但是这个标记并没有在内置类型上一致地使用。如上所述 数组和JSON类型现在正确地指定“unhashable” ,此标志现在为所有PostgreSQL的“结构”类型一致设置。

“unhashible”标志也设置在 NullType 类型为 NullType 用于引用任何未知类型的表达式。

自从 NullType 适用于 func 作为 func 实际上对大多数情况下给出的函数名一无所知, 使用func()通常会禁用行重复数据消除,除非应用显式类型。 . 以下示例说明 func.substr() 应用于字符串表达式,以及 func.date() 应用于日期时间表达式;除非应用显式类型,否则两个示例都将返回重复的行,这是由于联接的热切加载所致:

result = session.query(
    func.substr(A.some_thing, 0, 4), A
).options(joinedload(A.bs)).all()

users = session.query(
    func.date(
        User.date_created, 'start of month'
    ).label('month'),
    User,
).options(joinedload(User.orders)).all()

为了保持除尘,上述示例应指定为:

result = session.query(
    func.substr(A.some_thing, 0, 4, type_=String), A
).options(joinedload(A.bs)).all()

users = session.query(
    func.date(
        User.date_created, 'start of month', type_=DateTime
    ).label('month'),
    User,
).options(joinedload(User.orders)).all()

此外,所谓的“不可清洗”类型的处理方式与以前的版本略有不同;在内部,我们使用 id() 函数从这些结构中获取“哈希值”,就像我们从任何普通的映射对象中获取一样。这将替换先前对对象应用计数器的方法。

#3499

为将映射类、实例作为SQL文本传递而添加的特定检查

打字系统现在对在上下文中传递sqlachemy“inspectable”对象进行了特定的检查,否则这些对象将作为文本值处理。任何合法传递为SQL值的sqlAlchemy内置对象(它还不是 ClauseElement 实例)包含一个方法 __clause_element__() 为该对象提供有效的SQL表达式。对于不提供此功能的SQLAlchemy对象(如映射类、映射器和映射实例),将发出一条信息更丰富的错误消息,而不是允许DBAPI接收对象并稍后失败。下面给出了一个示例,其中基于字符串的属性 User.name 与的完整实例进行比较 User() ,而不是针对字符串值:

>>> 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 是基于字符串(例如,面向列)的属性。改变是否 not 影响将多对一关系属性与对象进行比较的常见情况,这一情况得到了明确处理:

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

#3321

新的可索引ORM扩展

这个 可转位的 扩展是对混合属性特性的扩展,它允许构造引用“可索引”数据类型的特定元素的属性,例如数组或json字段:

class Person(Base):
    __tablename__ = 'person'

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

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

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

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

当修改属性时,扩展也会触发一个变更事件,这样就不需要使用 MutableDict 以便跟踪此更改。

参见

可转位的

允许在默认值上显式持久化空值的新选项

与添加到PostgreSQL中作为 JSON“null”按预期与ORM操作一起插入,不存在时省略。 基地 TypeEngine 类现在支持一个方法 TypeEngine.evaluates_none() 它允许 None 要作为空值持久化的属性上的值,而不是从insert语句中省略列,这将使用列级默认值。这允许对现有对象级分配技术进行映射器级配置 null() 属性。

参见

对具有默认值的列强制空值

#3250

单表继承查询的进一步修复

从1.0继续 使用from_self(),count()时更改为单表继承条件 , the Query 当查询针对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

当存储点被数据库取消时,会话状态得到改善

MySQL的一个常见情况是,当事务中发生死锁时,会取消保存点。这个 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 在这里,我们发出一个警告,以便在Python2上运行时可以看到原始错误,即使保存点异常优先。在python 3上,异常被链接,因此两个失败都被单独报告。

#3680

错误的“new instance x conflicts with persistent instance y”刷新错误已修复

这个 Session.rollback() 方法负责删除插入到数据库中的对象,例如在当前回滚的事务中从挂起移动到持久化。在弱引用集合中跟踪进行此状态更改的对象,如果对象是从该集合中垃圾收集的,则 Session 不再担心它(否则它将无法扩展在事务中插入许多新对象的操作)。但是,如果应用程序在回滚发生之前重新加载事务中相同的垃圾收集行,则会出现问题;如果对该对象的强引用仍保留在下一个事务中,则不会插入并应删除该对象的事实将丢失,并且刷新操作将错误地引发错误::

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

上面,工作单元现在为我们将要报告为冲突的行进行选择,看到它不存在,并正常进行。此选择的费用仅在我们在任何情况下都会错误地提出异常时发生。

#3677

被动删除连接继承映射的功能

联接的表继承映射现在可以允许由于 Session.delete() 它只为基表而不是子类表发出DELETE,从而允许为配置的外键执行在DELETE CASCADE上配置。此配置使用 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'
    }

通过上面的映射, mapper.passive_deletes 选项是在基本映射器上配置的;它对作为具有选项集的映射器后代的所有非基本映射器生效。删除类型的对象 B 不再需要检索的主键值 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

相同名称的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

继承映射器时相同的命名关系不再发出警告

在继承方案中创建两个映射器时,在两个具有相同名称的映射器上放置关系将发出警告“relationship'<name>”(位于mapper上)<name>取代继承的映射器上的相同关系“<name>”;这可能会在刷新期间导致依赖关系问题)。示例如下:

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


class ASub(A):
    __tablename__ = 'a_sub'
    id = Column(Integer, ForeignKey('a.id'), primary_key=True)
    bs = relationship("B")


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

此警告可追溯到2007年的0.4系列,它基于工作单元代码的一个版本,该版本后来被完全重写。目前,在基类和子类上放置相同的命名关系没有已知问题,因此警告被解除。但是,请注意,由于警告,此用例在实际使用中可能并不普遍。虽然为这个用例添加了基本的测试支持,但是有可能识别出这个模式的一些新问题。

1.1.0b3 新版功能.

#3749

混合属性和方法现在传播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

然而,要做到这一点,混合属性的机制必然会变得更加复杂。以前,混合的类级访问器将是一个简单的直通,也就是说,此测试将成功:

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

session.merge与persistent解决挂起的冲突

这个 Session.merge() 方法现在将跟踪图形中给定对象的标识,以在发出插入之前保持主键的唯一性。当遇到相同标识的重复对象时,非主键属性为 覆写的 当遇到对象时,这本质上是不确定性的。此行为与处理持久对象(即已通过主键位于数据库中的对象)的方式相匹配,因此此行为在内部更为一致。

鉴于::

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 对象,并将其用作目标。如果对象存在,表示数据库已经有一行用于 Address 使用主键“1”,我们可以看到 email_address 领域 Address 将被覆盖三次,在这种情况下,值为A、B,最后是C。

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

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

#3601

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

修复了一个bug,涉及用另一个对象替换对某个对象的多对一引用的机制。在属性操作期间,以前引用的对象的位置现在使用数据库提交的外键值,而不是当前的外键值。修复的主要效果是,当进行多对一更改时,对集合的backref事件将更准确地触发,即使外键属性事先被手动移动到新值。假设类的映射 ParentSomeClass 在哪里 SomeClass.parentParentParent.items 指的是 SomeClass 物体::

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

上面,我们做了一个待处理的对象 some_object 把它的外键 Parent 参考它, then 我们实际上建立了关系。在修复错误之前,backref不会触发:

# before the fix
assert some_object not in some_parent.items

现在的解决方法是,当我们试图定位 some_object.parent ,我们忽略手动设置的父ID,并查找数据库提交值。在本例中,由于对象处于挂起状态,所以它为“无”,因此事件系统将记录 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

对带有多态实体的query.correlate方法的改进

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

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) = ?

在修复之前,呼叫 correlate(Person) 会无意中尝试关联到 PersonEngineerManager 作为一个整体,所以 Person 不会关联:

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

查询的字符串化将查询会话以获得正确的方言

调用 str() 在一 Query 对象将查询 Session 以便使用正确的“bind”,以便呈现将传递到数据库的SQL。尤其是这允许 Query 这表示特定于方言的SQL结构是可渲染的,假定 Query 与适当的 Session . 以前,只有当 MetaData 与之关联的映射本身已绑定到目标 Engine .

如果两者都不是基础 MetaData 也不 Session 与任何绑定关联 Engine ,然后使用“默认”方言的回退来生成SQL字符串。

参见

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

#3081

在同一个实体在一行中出现多次的联合预加载

已经修复了这样一种情况,即通过连接的热切加载来加载属性,即使实体已经从不包含该属性的其他“路径”上的行加载。这是一个很难重现的深层次用例,但总体思路如下:

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 表是从两次中选择的;在上下文中选择一次 A.b.c -> c_alias_1 另一个是在 A.c -> c_alias_2 . 此外,我们还可以看到 C 单行的标识是 same 对于两者 c_alias_1c_alias_2 ,表示一行中的两组列只会导致一个新对象被添加到标识映射中。

上面的查询选项只调用属性 C.d 在上下文中加载 c_alias_1 而不是 c_alias_2 . 所以不管最终结果是不是 C 我们在身份图中得到的对象 C.d 加载的属性取决于映射的遍历方式,尽管映射不是完全随机的,但本质上是不确定的。解决方法是即使装载机 c_alias_1 在以下时间之后处理 c_alias_2 对于它们都引用相同标识的单行, C.d 元素仍将被加载。以前,加载程序没有试图修改已通过其他路径加载的实体的加载。首先到达实体的加载程序始终是非确定性的,因此在某些情况下,此修复程序可能被检测为行为变化,而不是其他情况。

修复程序包括对“多条路径到一个实体”案例的两个变体的测试,并且修复程序应该很有可能涵盖所有其他这种性质的场景。

#3431

添加到突变跟踪扩展中的新可变列表和可变集帮助器

新建帮助程序类 MutableListMutableSet 已添加到 突变跟踪 扩展,以补充现有的 MutableDict 帮手。

#3297

新的“RAISE”/“RAISE”on“SQL”加载程序策略

为了帮助防止在加载一系列对象之后发生不必要的延迟加载,新的“lazy='raise'”和“lazy='raise on_sql'”策略和相应的加载程序选项 raiseload() 可应用于关系属性,该属性将导致 InvalidRequestError 当访问非热切加载的属性进行读取时。这两个变量测试任何类型的延迟负载,包括那些只返回“无”或从标识映射中检索的负载:

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

或者仅在将发出SQL的位置执行延迟加载:

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

#3512

mapper.orderu by已弃用

这个来自第一个版本的sqlacalchemy的旧参数是ORM原始设计的一部分,ORM的特点是 Mapper 对象作为面向公共的查询结构。这个角色早已被 Query 对象,我们使用的位置 Query.order_by() 以一致的方式指示结果的顺序,以适用于任何select语句、实体和SQL表达式组合。有很多领域 Mapper.order_by 不按预期工作(或预期不明确),例如查询何时合并到联合中;不支持这些情况。

#3394

新功能和改进-核心

引擎现在使连接失效,运行baseexception的错误处理程序

1.1 新版功能: 此更改是对1.1最终版之前的1.1系列的后期添加,不在1.1测试版中。

Python BaseException 等级低于 Exception 但是是否是系统级异常的可识别基础,例如 KeyboardInterruptSystemExit 尤其是 GreenletExit eventlet和gevent使用的异常。这个异常类现在被 Connection ,包括 ConnectionEvents.handle_error() 事件。这个 Connection 现在是 失效的 如果系统级异常不是 Exception ,因为假定操作被中断,连接可能处于不可用状态。mysql驱动程序是这个变更最主要的目标,但是这个变更跨越了所有dbapis。

请注意,在失效时,由 Connection 被释放,并且 Connection 如果在异常引发之后仍在使用,则下次使用时将使用新的DBAPI连接进行后续操作;但是,任何正在进行的事务的状态都将丢失,并且 .rollback() 如果适用,必须调用方法才能继续此重用。

为了识别这种变化,可以很容易地演示当这些异常发生在执行其工作的连接的中间时,pymysql或mysqlclient/mysql python连接将进入损坏状态;然后,连接将返回到连接池,在那里后续使用将失败,甚至在返回之前到池将导致调用 .rollback() 在异常捕获时。这里的行为可以减少mysql错误“命令不同步”的发生率,以及 ResourceClosedError 当MySQL驱动程序无法报告时会发生这种情况 cursor.description 正确地说,当在绿let条件下运行时,绿let被杀死,或 KeyboardInterrupt 在不完全退出程序的情况下处理异常。

这种行为与通常的自动失效特性不同,因为它不假定后端数据库本身已关闭或重新启动;它不会像通常的DBAPI断开连接异常那样回收整个连接池。

对于所有用户来说,除了 any application that currently intercepts ``KeyboardInterrupt`` or ``GreenletExit`` and wishes to continue working within the same transaction . 理论上,这种操作对于其他似乎不受影响的DBAPI来说是可能的。 KeyboardInterrupt 例如psycopg2。对于这些dbapis,以下解决方法将禁止针对特定异常回收连接:

engine = create_engine("postgresql+psycopg2://")

@event.listens_for(engine, "handle_error")
def cancel_disconnect(ctx):
    if isinstance(ctx.original_exception, KeyboardInterrupt):
        ctx.is_disconnect = False

#3803

CTE支持插入、更新、删除

最广泛要求的功能之一是支持公共表表达式(CTE),该表达式可用于插入、更新、删除,现在已实现。插入/更新/删除既可以从SQL顶部声明的WITH子句中提取,也可以在较大语句的上下文中用作CTE本身。

作为此更改的一部分,包含CTE的insert-from-select现在将在整个语句的顶部呈现CTE,而不是像1.0中那样嵌套在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

在窗口函数中支持范围和行规范

新的 over.range_over.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)

over.range_over.rows 指定为2个元组,并指示特定范围的负值和正值,“当前行”为0,“无边界”为无。

参见

窗口功能

#3049

支持SQL横向关键字

目前已知,只有PostgreSQL 9.3及更高版本才支持侧向关键字,但由于它是SQL标准对该关键字支持的一部分,所以它被添加到了核心中。实施 Select.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

参见

横向相关

Lateral

Select.lateral()

#2857

支持表格样本

可以使用 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

这个 .autoincrement 不再为复合主键列隐式启用指令

sqlAlchemy始终具有为单列整数主键启用后端数据库的“autoincrement”功能的便利功能;通过“autoincrement”,我们的意思是数据库列将包含数据库提供的任何DDL指令,以指示自动递增的整数标识符,如serial关键字。在PostgreSQL或MySQL上自动递增,并且方言将从执行 Table.insert() 使用适合该后端的技术构造。

改变的是,此功能不再自动打开 混合成的 主键;以前是表定义,如:

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

将“autoincrement”语义应用于 '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)
)

为了预测一些潜在的向后不兼容的场景, Table.insert() 构造将对没有设置自动增量的复合主键列上缺少的主键值执行更彻底的检查;给定一个表,例如::

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

如果没有此表的值而发出插入,将产生此警告::

SAWarning: 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())
)

对于复合主键实际上打算将空值存储在其一个或多个列(仅在sqlite和mysql上支持)中的非常不可能的情况,请使用 nullable=True ::

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

在相关变更中, autoincrement 在具有客户端或服务器端默认值的列上,标志可以设置为true。这通常不会对插入期间的列行为产生太大的影响。

参见

不再为复合主键生成隐式键w/auto_increment

#3216

对的支持与

新算子 ColumnOperators.is_distinct_from()ColumnOperators.isnot_distinct_from() 允许与SQL操作不同且不不同:

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

为空、真和假提供了处理:

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

对于没有此运算符的sqlite,将呈现“is”/“is not”,与其他后端不同,sqlite上的“is”/“is not”用于空值:

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

用于完全外部连接的核心和ORM支持

新旗帜 FromClause.outerjoin.full 在核心和ORM级别可用,指示编译器呈现 FULL OUTER JOIN 在通常情况下 LEFT OUTER JOIN ::

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

该标志也在ORM级别工作:

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

#1957

结果集列匹配增强功能;文本SQL的位置列设置

我们对 ResultProxy 作为1.0系列的一部分 #918 ,它重新组织内部结构,以便在位置上将光标绑定的结果列与表/ORM元数据匹配,而不是通过匹配名称来匹配包含要返回的结果行完整信息的已编译SQL构造。这就大大节省了Python开销,同时也大大提高了将ORM和核心SQL表达式链接到结果行的准确性。在1.1中,这种重组在内部进行得更深入,并且通过使用最近添加的 TextClause.columns() 方法。

textasfrom.columns()现在按位置工作

这个 TextClause.columns() 方法(在0.9中添加)按位置接受基于列的参数;在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(stmt).\
    options(contains_eager(User.addresses))
result = query.all()

上面,文本SQL包含三次列“id”,这通常是不明确的。使用新功能,我们可以应用 UserAddress 直接类,甚至链接 Address.user_id 列到 users.id 文本SQL中的列用于娱乐,以及 Query 对象将接收可根据需要正确定向的行,包括用于紧急加载的行。

这种变化是 向后不兼容 使用将列传递给方法的代码,其顺序与文本语句中的顺序不同。我们希望这种影响会很小,因为这种方法总是被记录在案,说明列的传递顺序与文本SQL语句的传递顺序相同,这看起来很直观,即使内部没有对此进行检查。在任何情况下,该方法本身仅在0.9之前添加,可能尚未广泛使用。关于如何为使用它的应用程序处理这种行为变化的说明,请参见 当按位置传递时,textclause.columns()将按位置而不是按名称匹配列 .

参见

指定结果列行为 -在核心教程中

当按位置传递时,textclause.columns()将按位置而不是按名称匹配列 -向后兼容性备注

对于核心/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列的精确匹配,而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']

更不可能收到“含糊不清的列”错误消息

作为此更改的一部分,错误消息的措辞 Ambiguous column name '<name>' in result set! try 'use_labels' option on select statement. 已被拨回;由于在使用ORM或核心编译的SQL构造时,此消息现在应该非常罕见,因此它只会声明 Ambiguous column name '<name>' in result set column descriptions ,并且仅当使用实际上不明确的字符串名称检索结果列时,例如 row['user_id'] 在上面的例子中。它现在还引用了呈现的SQL语句本身的实际不明确名称,而不是指示用于获取的构造的本地键或名称。

#3501

对python本机的支持 enum 类型和兼容形式

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

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


class MyEnum(enum.Enum):
    one = 1
    two = 2
    three = 3


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

这个 Enum.enums 集合现在是列表而不是元组

作为更改的一部分 Enum , the Enum.enums 元素集合现在是列表而不是元组。这是因为列表适用于同质项的可变长度序列,其中元素的位置在语义上不重要。

#3292

核心结果行容纳的负整数索引

这个 RowProxy 对象现在可以像常规的python序列一样容纳单个负整数索引,无论是纯python还是C扩展版本。以前,负值只在切片中起作用:

>>> 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本机枚举对象,以及在数组中使用非本机枚举类型且check约束不可行的边缘情况,可以使用 Enum 数据类型现在添加了输入值的python验证,当 Enum.validate_strings 使用标志(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'}]]

默认情况下,此验证被关闭,因为已经识别出用户不希望进行此类验证的用例(如字符串比较)。对于非字符串类型,它在所有情况下都必须发生。当返回来自数据库的值时,也会在结果处理端无条件地进行检查。

此验证是对使用非本机枚举类型时创建检查约束的现有行为的补充。现在可以使用新的 Enum.create_constraint 旗帜。

#3095

所有情况下强制为零/一/无的非本机布尔整数值

这个 Boolean 对于没有本机布尔类型(如sqlite和mysql)的后端,datatype将python booleans强制为整数值。在这些后端,通常会设置一个检查约束,以确保数据库中的值实际上是这两个值中的一个。但是,MySQL忽略了检查约束,该约束是可选的,并且现有数据库可能没有该约束。这个 Boolean 数据类型已被修复,使得已经是整数值的传入python端值被强制为零或一,而不仅仅是按原样传递;此外,用于结果的int-to-boolean处理器的C扩展版本现在使用相同的python布尔值解释,而不是断言精确的一个或零值。这与纯python int to boolean处理器是一致的,并且更容易理解数据库中已有的数据。none/null的值与之前一样保留为none/null。

注解

这种更改有一个意想不到的副作用,即非整数值(如字符串)的解释在行为上也发生了变化,以致字符串值 "0" 将被解释为“true”,但仅在没有本机布尔数据类型的后端上-在“本机布尔”后端上,如postgresql,字符串值 "0" 直接传递给驱动程序并解释为“假”。这是与以前的实现没有发生的不一致。应该注意,传递字符串或 NoneTrueFalse10Boolean 数据类型是 不支持 版本1.2将为此场景引发一个错误(或者可能只是发出一个警告,tbd)。也见 #4102 .

#3730

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

在日志记录、异常报告以及 repr() 行本身:

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

添加到核心的JSON支持

因为mysql现在除了postgresql-json数据类型之外还有一个json数据类型,所以core现在获得了一个 sqlalchemy.types.JSON 数据类型,它是这两种数据类型的基础。使用这种类型可以访问“getitem”操作符和“getpath”操作符,这在PostgreSQL和MySQL中是不可知的。

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

参见

MySQL JSON支持

JSON

JSON

JSON

#3619

JSON“null”按预期与ORM操作一起插入,不存在时省略。

这个 JSON 类型及其后代类型 JSONJSON 有一面旗帜 JSON.none_as_null 如果设置为true,则表示python值 None 应转换为SQL空值,而不是JSON空值。此标志默认为false,这意味着python值 None 应导致JSON空值。

在以下情况下,此逻辑将失败,现在可以更正:

1。如果列还包含默认值或服务器默认值,则为 None 对于预期保持JSON的映射属性,“null”仍将导致触发列级默认值,替换 None 价值:

class MyObject(Base):
    # ...

    json_value = Column(JSON(none_as_null=False), default="some default")

# would insert "some default" instead of "'null'",
# now will insert "'null'"
obj = MyObject(json_value=None)
session.add(obj)
session.commit()

2。当列 没有 包含默认值或服务器默认值,配置为none_as_null=false的JSON列上缺少的值仍将呈现JSON空值,而不是返回到不插入任何值,与所有其他数据类型的行为不一致:

class MyObject(Base):
    # ...

    some_other_value = Column(String(50))
    json_value = Column(JSON(none_as_null=False))

# would result in NULL for some_other_value,
# but json "'null'" for json_value.  Now results in NULL for both
# (the json_value is omitted from the INSERT)
obj = MyObject()
session.add(obj)
session.commit()

这是一个向后不兼容的行为更改,对于一个依赖它来默认丢失值为JSON NULL的应用程序来说。这基本上证明了 缺少的值与无的当前值不同 . 见 如果没有提供值并且没有建立默认值,JSON列将不会插入JSON空值。 更多细节。

三。当 Session.bulk_insert_mappings() 使用方法, None 在任何情况下都将被忽略:

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

这个 JSON 类型现在实现 TypeEngine.should_evaluate_none 标志,表示 None 此处不应忽略;它是根据 JSON.none_as_null . 多亏了 #3061 ,我们可以区分 None 是由用户主动设置的,而不是从未设置的。

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

#3514

添加了新的json.null常量

以确保应用程序始终能够完全控制 JSONJSONJSONJSONB 列应接收SQL空值或JSON "null" 值,常数 JSON.NULL 已添加,与 null() 可用于完全确定SQL空值和JSON之间的关系 "null" 不管怎样 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()

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

#3514

添加到核心的数组支持;新增任意和所有运算符

以及对PostgreSQL的增强 ARRAY 类型描述见 通过对array、json、hstore的索引访问建立正确的SQL类型 ,的基类 ARRAY 在一个新的班级里,它已经被移到了核心。 ARRAY .

数组是SQL标准的一部分,还有一些面向数组的函数,如 array_agg()unnest() . 为了支持这些结构,不仅是PostgreSQL,将来还可能支持其他支持数组的后端(如DB2),现在大多数SQL表达式的数组逻辑都处于核心。这个 ARRAY 类型仍然 仅适用于PostgreSQL 但是,它可以直接使用,支持特殊的数组用例,如索引访问,以及对any和all:的支持:

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

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

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

为了支持所有人, ARRAY 类型保持不变 Comparator.any()Comparator.all() 方法,但也将这些操作导出到新的独立运算符函数 any_()all_() . 这两个函数以更传统的SQL方式工作,允许右侧表达式形式,如:

from sqlalchemy import any_, all_

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

对于特定于PostgreSQL的运算符“contains”、“contained_by”和“overlaps”,应该继续使用 ARRAY 直接键入,它提供 ARRAY 类型也一样。

这个 any_()all_() 运营商在核心级别是开放的,但是后端数据库对其的解释是有限的。在PostgreSQL后端,这两个操作符 仅接受数组值 . 而在mysql后端,它们 只接受子查询值 . 在MySQL上,可以使用如下表达式:

from sqlalchemy import any_, all_

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

#3516

新的函数特性,“组内”,数组agg和集合聚合函数

与新 ARRAY 类型我们还可以为 array_agg() 返回数组的SQL函数,该数组现在可以使用 array_agg ::

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

聚合order by的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方言本身也提供了 array_agg() 包装以确保 ARRAY 类型:

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

此外,功能如下 percentile_cont()percentile_disc()rank()dense_rank() 以及其他需要通过 WITHIN GROUP (ORDER BY <expr>) 现在可通过 FunctionElement.within_group() 修饰语:

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_rankmodepercent_rankcume_dist .

#3132 #1370

typedecorator现在可以自动处理枚举、布尔值、“架构”类型。

这个 SchemaType 类型包括类型,例如 EnumBoolean 它除了对应于数据库类型外,还生成一个check约束,或者在PostgreSQL枚举的情况下,生成一个新的create type语句,现在将自动使用 TypeDecorator 食谱。以前,A TypeDecorator 对于一个 ENUM 必须看起来像这样:

# 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

表对象的多租户模式转换

支持使用同一组 Table 许多模式中的对象,例如每个用户的模式,一个新的执行选项 Connection.execution_options.schema_translate_map 添加。使用这个映射,一组 Table 对象可以基于每个连接来引用任何模式集,而不是 Table.schema 他们被分配到的地方。这种转换既适用于DDL和SQL生成,也适用于ORM。

例如,如果 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

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

调用 str() 在核心SQL构造上,现在将在比以前更多的情况下生成字符串,支持默认SQL中通常不存在的各种SQL构造,例如返回、数组索引和非标准数据类型:

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

类型强制函数现在是一个持久的SQL元素

这个 type_coerce() 函数以前将返回一个类型为 BindParameterLabel ,取决于输入。这样做的一个效果是,在使用表达式转换的情况下,例如从 Column 到A BindParameter 这对ORM级的延迟加载至关重要,因为类型强制信息已经丢失,所以不会使用它。

为了改进这种行为,函数现在返回一个持久的 TypeCoerce 给定表达式周围的容器,它本身不受影响;此构造由SQL编译器显式计算。这允许强制维护内部表达式,无论如何修改语句,包括是否将包含的元素替换为其他元素,这在ORM的延迟加载功能中很常见。

说明这种效果的测试用例将异构的PrimaryJoin条件与自定义类型和延迟加载结合使用。给定将强制转换应用为“绑定表达式”的自定义类型:

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”列将被强制转换为整数,用于常规的未转换联接 Query.join()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)

如果没有join的on子句中的强制转换,像postgresql这样的强类型数据库将拒绝隐式比较整数并失败。

Lazyload案 .pets 依赖于替换 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}

在上面的位置,我们可以看到我们的in python值 5 首先被转换为varchar,然后在SQL中转换为整数;一个可以工作的双转换,但这不是我们要求的。

随着变化, 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}

我们的外部演员在我们的主要角色中仍然有效,但不必要的演员在 StringAsInt 自定义类型将按 type_coerce() 功能。

#3531

关键行为变化-ORM

如果没有提供值并且没有建立默认值,JSON列将不会插入JSON空值。

详述 JSON“null”按预期与ORM操作一起插入,不存在时省略。JSON 如果该值完全丢失,则不会呈现JSON“空”值。为防止SQL为空,应设置默认值。给定以下映射:

class MyObject(Base):
    # ...

    json_value = Column(JSON(none_as_null=False), nullable=False)

以下刷新操作将失败,并出现完整性错误::

obj = MyObject()  # note no json_value
session.add(obj)
session.commit()  # will fail with integrity error

如果列的默认值应为JSON NULL,请在列上设置此值:

class MyObject(Base):
    # ...

    json_value = Column(
        JSON(none_as_null=False), nullable=False, default=JSON.NULL)

或者,确保对象上存在该值:

obj = MyObject(json_value=None)
session.add(obj)
session.commit()  # will insert JSON NULL

注意这个设置 None 因为违约与完全忽略它是一样的; JSON.none_as_null 标志不影响的值 None 传递给 Column.defaultColumn.server_default ::

# default=None is the same as omitting it entirely, does not apply JSON NULL
json_value = Column(JSON(none_as_null=False), nullable=False, default=None)

参见

JSON“null”按预期与ORM操作一起插入,不存在时省略。

不再使用distinct+order by重复添加列

像下面这样的查询现在将只增加选择列表中缺少的那些列,而不增加重复项:

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 列被不必要地添加。结果不会受到影响,因为在任何情况下,结果中都不包括其他列,但这些列是不必要的。

此外,当postgresql distinct on格式通过将表达式传递给 Query.distinct() 以上“列添加”逻辑完全禁用。

当查询被捆绑到一个子查询中以进行联合的抢先加载时,“增加列列表”规则必然更具攻击性,因此仍然可以满足ORDER BY,因此这种情况保持不变。

#3641

同名的@validates修饰符现在将引发异常

这个 validates() 对于特定的属性名,每个类只能创建一次decorator。创建多个现在会引发一个错误,而以前它只会悄悄地选择最后定义的验证器::

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

    data = Column(String)

    @validates("data")
    def _validate_data_one(self):
        assert "x" in data

    @validates("data")
    def _validate_data_two(self):
        assert "y" in data

configure_mappers()

将提高:

sqlalchemy.exc.InvalidRequestError: A validation function for mapped attribute 'data' on mapper Mapper|A|a already exists.

#3776

关键行为变化-核心

当按位置传递时,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中的名称本身。

参见

结果集列匹配增强功能;文本SQL的位置列设置

字符串服务器默认值现在引用文字

传递给的服务器默认值 Column.server_default 作为一个嵌入了引号的普通python字符串,现在通过文本引用系统传递:

>>> from sqlalchemy.schema import MetaData, Table, Column, CreateTable
>>> from sqlalchemy.types import String
>>> t = Table('t', MetaData(), Column('x', String(), server_default="hi ' there"))
>>> print(CreateTable(t))

CREATE TABLE t (
    x VARCHAR DEFAULT 'hi '' there'
)

以前,引用将直接呈现。这个变更可能是向后的,与处理这个问题的这样一个用例的应用程序不兼容。

#3809

带有限制/偏移/顺序的选择的并集或类似项现在用括号括起嵌入的选择

与其他人一样,一个长期以来被sqlite缺乏功能所驱动的问题现在已经得到了增强,可以在所有支持后端上工作。我们引用的查询是select语句的联合,这些语句本身包含行限制或排序功能,这些功能包括limit、offset和/或order by::

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

上面的查询需要在每个子选择中使用括号,以便正确地对子结果进行分组。在sqlacalchemy 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语句生成括号,从而生成一个在所有后端都失败的查询。

上述格式将 在sqlite上继续失败 ;此外,包含order by但没有限制/选择的格式将 继续在Oracle上失败 . 这不是向后不兼容的更改,因为查询也会在没有括号的情况下失败;通过修复,查询至少可以在所有其他数据库上工作。

在所有情况下,为了生成同时在sqlite和Oracle上工作的有限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))

这里的行为与SQLAlchemy 0.9中引入的“join rewriting”行为有许多相似之处。 许多联接和左外部联接表达式将不再作为anon_1包装在(select*from..)中。 但是,在本例中,我们选择不添加新的重写行为来适应SQLite的这种情况。现有的重写行为已经非常复杂了,带有带括号的select语句的联合的情况比该特性的“右嵌套连接”用例更不常见。

#2528

方言改进与变化-PostgreSQL

支持冲突时插入..(不更新不执行任何操作)

这个 ON CONFLICT 条款 INSERT 从9.5版开始添加到PostgreSQL,现在支持使用PostgreSQL特定版本的 Insert 对象,通过 sqlalchemy.dialects.postgresql.dml.insert() . 这个 Insert 子类添加了两个新方法 Insert.on_conflict_do_update()Insert.on_conflict_do_nothing() 实现了PostgreSQL 9.5在该领域支持的完整语法:

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

数组和JSON类型现在正确地指定“unhashable”

如上所述 有关“不可清除”类型的更改,影响ORM行的删除 当查询的选定实体将完整的ORM实体与列表达式混合时,ORM依赖于能够为列值生成哈希函数。这个 hashable=False 标志现在正确设置在所有PG的“数据结构”类型上,包括 ARRAYJSON . 这个 JSONBHSTORE 类型已包含此标志。为了 ARRAY ,这是基于 ARRAY.as_tuple 但是,为了在组合的ORM行中存在数组值,不需要再设置此标志。

参见

有关“不可清除”类型的更改,影响ORM行的删除

通过对array、json、hstore的索引访问建立正确的SQL类型

#3499

通过对array、json、hstore的索引访问建立正确的SQL类型

对于所有三个 ARRAYJSONHSTORE ,指定给索引访问返回的表达式的SQL类型,例如 col[someindex] ,在所有情况下都应正确。

这包括:

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

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

    以前,索引访问 col[5] 将返回类型为的表达式 Integer 除非我们使用 cast()type_coerce() .

  • 这个 JSONJSONB 类型现在镜像PostgreSQL本身对索引访问的作用。这意味着 JSONJSONB 类型返回的表达式本身是 总是 JSONJSONB 本身,除非 Comparator.astext 使用修饰符。这意味着,不管JSON结构的索引访问最终是指字符串、列表、数字还是其他JSON结构,PostgreSQL始终将其视为JSON本身,除非它被显式转换为不同的类型。像 ARRAY 类型,这意味着现在可以直接生成具有多个索引访问级别的JSON表达式:

    json_expr = json_col['key1']['attr1'][5]
  • 索引访问返回的“文本”类型 HSTORE 以及索引访问返回的“文本”类型 JSONJSONBComparator.astext 修饰符现在是可配置的;它默认为 TextClause 在这两种情况下,但可以使用 JSON.astext_typeHSTORE.text_type 参数。

参见

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

#3499 #3487

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

作为变化的一部分 通过对array、json、hstore的索引访问建立正确的SQL类型 ,的工作 ColumnElement.cast() 运算符开启 JSONJSONB 不再隐式调用 Comparator.astext modifier;postgresql的json/jsonb类型支持相互转换操作,而不需要“astext”方面。

这意味着在大多数情况下,执行此操作的应用程序:

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

现在需要更改为:

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

具有枚举的数组现在将为枚举发出创建类型

像下面这样的表定义现在将按预期发出创建类型:

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

检查约束现在反映

PostgreSQL方言现在支持在方法中反射检查约束 Inspector.get_check_constraints() 以及内部 Table 内反射 Table.constraints 收集。

“平面”和“物化”视图可以单独检查

新的论点 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

向索引添加了表空间选项

这个 Index 对象现在接受参数 postgresql_tablespace 为了指定表空间,与 Table 对象。

参见

索引存储参数

#3720

支持PygreSQL

这个 PyGreSQL 现在支持DBAPI。

参见

吡格列克

删除“Postgres”模块

这个 sqlalchemy.dialects.postgres 长期以来不推荐使用的模块已被删除;这已经发出了多年的警告,项目应该被调用。 sqlalchemy.dialects.postgresql . 表单的引擎URL postgres:// 但仍将继续发挥作用。

支持更新跳过锁定/无密钥更新/密钥共享

新参数 GenerativeSelect.with_for_update.skip_lockedGenerativeSelect.with_for_update.key_share 在core和orm中,对PostgreSQL后端的“select…for update”或“select…for share”查询应用修改:

  • 选择不更新密钥:

    stmt = select([table]).with_for_update(key_share=True)
  • 选择更新跳过锁定::

    stmt = select([table]).with_for_update(skip_locked=True)
  • 为密钥共享选择:

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

方言改进和变化-MySQL

MySQL JSON支持

一种新型 JSON 添加到MySQL方言中,支持新添加到MySQL5.7中的JSON类型。这种类型既提供了JSON的持久性,也提供了使用 JSON_EXTRACT 内部功能。通过使用 JSON mysql和postgresql通用的数据类型。

参见

添加到核心的JSON支持

#3547

增加了对自动提交“隔离级别”的支持

MySQL方言现在接受值“autocommit” create_engine.isolation_levelConnection.execution_options.isolation_level 参数::

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

隔离级别使用了大多数mysql dbapis提供的各种“autocommit”属性。

#3332

不再为复合主键生成隐式键w/auto_increment

mysql方言具有这样的行为:如果innodb表上的复合主键在其列(不是第一列)上具有自动增量,例如:

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将在innodb上失败。

这个解决方法已经被移除,并替换为更好的系统,只需声明自动增量列。 第一 在主键内:

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要求的autoincrement列的键),例如:

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

随着变化 这个 .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版中,由 许多联接和左外部联接表达式将不再作为anon_1包装在(select*from..)中。 为了实现“正确的嵌套连接”效果,在sqlite上进行了大量的工作来支持重写连接以始终使用子查询,因为sqlite多年来不支持这种语法。讽刺的是,在迁移注释3.7.15.2中提到的sqlite版本是 last sqlite的版本实际上有这个限制!下一个版本是3.7.16,并悄悄地添加了对右嵌套连接的支持。在1.1中,识别进行此更改的特定sqlite版本和源提交的工作已经完成(sqlite的changelog用“增强查询优化器以利用可传递的连接约束”这一神秘短语来引用它,而不链接到任何问题号、更改号或进一步的解释),以及此中存在的解决方法当DBAPI报告版本3.7.16或更高版本生效时,SQLite的更改现在被取消。

#3634

针对sqlite 3.10.0版的虚线列名称解决方法已解除

对于数据库驱动程序没有为某些SQL结果集报告正确的列名的问题,特别是在使用union时,sqlite方言早就有了解决方法。解决方法的详细信息如下: 点式列名 ,并要求sqlAlchemy假定其中包含点的任何列名称实际上是 tablename.columnname 通过这种错误行为提供的组合,以及通过 sqlite_raw_colnames 执行选项。

从sqlite 3.10.0版开始,联合查询和其他查询中的错误已修复;如中所述的更改 为sqlite版本3.7.16解除了右嵌套联接工作区 ,sqlite的changelog仅以加密方式将其标识为“将colused字段添加到sqlite3_index_info以供sqlite3_module.xbestindex方法使用”,但是sqlachemy对这些虚线列名称的翻译不再需要此版本,因此在检测到3.10.0或更高版本时将关闭。

总的来说,SQL炼金术 ResultProxy 从1.0系列开始,在为核心和ORM SQL构造传递结果时,对结果集中的列名的依赖要少得多,因此在任何情况下,这个问题的重要性都已经降低了。

#3633

改进了对远程模式的支持

sqlite方言现在实现 Inspector.get_schema_names() 此外,还改进了对从远程模式创建和反映的表和索引的支持,在sqlite中,远程模式是通过 ATTACH 语句;以前,对于绑定到架构的表和 Inspector.get_foreign_keys() 方法现在将在结果中指示给定的模式。不支持跨架构外键。

主键约束名称的反射

现在,sqlite后端利用sqlite的“sqlite_master”视图,以便从原始DDL中提取表的主键约束的名称,这与在最近的sqlachemy版本中为外键约束实现的方法相同。

#3629

检查约束现在反映

sqlite方言现在支持在方法中反射检查约束 Inspector.get_check_constraints() 以及内部 Table 内反射 Table.constraints 收集。

关于删除和更新外键短语现在反映

这个 Inspector 现在将包括on delete和on update短语,它们来自sqlite方言上的外键约束,以及 ForeignKeyConstraint 作为 Table 还将指示这些短语。

方言改进和更改-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

字符串/varlength类型在反射时不再显式表示“max”

当反映一种类型时,例如 StringTextClause 等,其中包括一个长度,SQL Server下的“un lengthed”类型将复制“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”的无边界长度。然后,修复方法是将这些长度显示为“无”,以便类型对象在非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

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

这个 mssql_clustered 标志在上可用 UniqueConstraintPrimaryKeyConstraintIndex 现在默认为 None ,并且可以设置为false,这将呈现非聚集关键字,尤其是对于主键,允许将不同的索引用作“聚集的”。

参见

聚集索引支持

传统的_-schema_别名标志现在设置为false

SQLAlchemy 1.0.5引入了 legacy_schema_aliasing 标记为mssql方言,允许关闭所谓的“传统模式”别名。此别名尝试将模式限定的表转换为别名;给定如下表:

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。

在sqlacalchemy 1.1中, legacy_schema_aliasing 现在,标志默认为false,禁用此行为模式,并允许MSSQL方言使用模式限定表正常工作。对于可能依赖此行为的应用程序,请将标志设置回“真”。

#3434

方言改进和变化-Oracle

支持跳过锁定

新参数 GenerativeSelect.with_for_update.skip_locked 在core和orm中,将为“select…for update”或“select.”生成“skip locked”后缀。“共享”查询。