Release: 1.4.25 | Release Date: September 22, 2021

SQLAlchemy 1.4 Documentation

更改和迁移

sqlacalchemy 1.2有什么新功能?

关于此文档

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

介绍

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

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

平台支撑

针对python 2.7及更高版本

sqlacalchemi1.2现在将最低的python版本移动到2.7,不再支持2.6。新的语言特性预计将被合并到1.2系列中,而这在Python2.6中是不受支持的。对于python 3支持,sqlAlchemy目前在3.5和3.6版本上进行了测试。

新功能和改进-ORM

“烘焙”加载现在是惰性加载的默认值

这个 sqlalchemy.ext.baked 在1.0系列中首次引入的扩展允许构造所谓的 BakedQuery 对象,它是生成 Query 对象和表示查询结构的缓存键;然后将此缓存键链接到生成的字符串SQL语句,以便随后使用另一个 BakedQuery 同一结构将绕过建筑物的所有架空 Query 对象,构建核心 select() 对象,以及 select() 将通常与构造和发出ORM相关联的大部分函数调用开销切去为一个字符串 Query 对象。

这个 BakedQuery 当ORM为一个 relationship() 构造,例如默认的构造 lazy="select" 关系加载器策略。这将允许在应用程序使用延迟加载查询加载集合和相关对象的范围内显著减少函数调用。以前,通过使用全局API方法或使用 baked_select 策略,它现在是这个行为的唯一实现。该特性也得到了改进,这样对于在延迟加载之后具有其他加载器选项的对象仍然可以进行缓存。

可以使用 relationship.bake_queries 标志,可用于非常特殊的情况,例如使用自定义项的关系 Query 实现与缓存不兼容。

#3954

新的“selectin”渴望加载,一次加载所有集合,使用in

添加了一个名为“selectin”加载的新的热切加载程序,该加载程序在许多方面与“subquery”加载程序类似,但它会生成一个更简单的、可缓存且更高效的SQL语句。

给出如下查询:

q = session.query(User).\
    filter(User.name.like('%ed%')).\
    options(subqueryload(User.addresses))

生成的SQL将是针对 User 然后是的子查询加载 User.addresses (注意,参数也会列出)::

SELECT users.id AS users_id, users.name AS users_name
FROM users
WHERE users.name LIKE ?
('%ed%',)

SELECT addresses.id AS addresses_id,
       addresses.user_id AS addresses_user_id,
       addresses.email_address AS addresses_email_address,
       anon_1.users_id AS anon_1_users_id
FROM (SELECT users.id AS users_id
FROM users
WHERE users.name LIKE ?) AS anon_1
JOIN addresses ON anon_1.users_id = addresses.user_id
ORDER BY anon_1.users_id
('%ed%',)

加载“select in”后,我们将得到一个select,它引用父查询中加载的实际主键值:

q = session.query(User).\
    filter(User.name.like('%ed%')).\
    options(selectinload(User.addresses))

生产::

SELECT users.id AS users_id, users.name AS users_name
FROM users
WHERE users.name LIKE ?
('%ed%',)

SELECT users_1.id AS users_1_id,
       addresses.id AS addresses_id,
       addresses.user_id AS addresses_user_id,
       addresses.email_address AS addresses_email_address
FROM users AS users_1
JOIN addresses ON users_1.id = addresses.user_id
WHERE users_1.id IN (?, ?)
ORDER BY users_1.id
(1, 3)

上面的select语句包括以下优点:

  • 它不使用子查询,只使用内部联接,这意味着它在像mysql这样不喜欢子查询的数据库上的性能会更好。

  • 它的结构独立于原始查询;与新的 expanding IN parameter system 在大多数情况下,我们可以使用“baked”查询来缓存字符串SQL,从而显著减少每个查询的开销。

  • 由于查询只获取给定的主键标识符列表,因此“selectin”加载可能与 Query.yield_per() 在数据库驱动程序允许多个同时的游标(sqlite、postgresql)的情况下,一次对选择结果的块进行操作; not MySQL驱动程序或SQL Server ODBC驱动程序)。联接的抢先加载和子查询抢先加载都不兼容 Query.yield_per() .

selectin预先加载的缺点是潜在的大型SQL查询,以及大量的in参数列表。输入参数本身的列表按500个分组,因此超过500个前导对象的结果集后面将有更多的“select-in”查询。另外,对复合主键的支持取决于数据库使用包含in的元组的能力,例如 (table.column_one, table_column_two) IN ((?, ?), (?, ?) (?, ?)) . 目前,PostgreSQL和MySQL都与此语法兼容,而SQLite则不兼容。

#3944

“selectin”多态加载,使用单独的in查询加载子类

与刚才描述的“selectin”关系加载功能类似 新的“selectin”渴望加载,一次加载所有集合,使用in 是“selectin”多态加载。这是一个多态加载特性,主要针对联合的预加载而定制,它允许基本实体的加载继续执行一个简单的select语句,但随后用附加的select语句加载附加子类的属性:

from sqlalchemy.orm import selectin_polymorphic

query = session.query(Employee).options(
    selectin_polymorphic(Employee, [Manager, Engineer])
)

query.all() SELECT employee.id AS employee_id, employee.name AS employee_name, employee.type AS employee_type FROM employee () SELECT engineer.id AS engineer_id, employee.id AS employee_id, employee.type AS employee_type, engineer.engineer_name AS engineer_engineer_name FROM employee JOIN engineer ON employee.id = engineer.id WHERE employee.id IN (?, ?) ORDER BY employee.id (1, 2) SELECT manager.id AS manager_id, employee.id AS employee_id, employee.type AS employee_type, manager.manager_name AS manager_manager_name FROM employee JOIN manager ON employee.id = manager.id WHERE employee.id IN (?) ORDER BY employee.id (3,)

#3948

可以接收临时SQL表达式的ORM属性

新的ORM属性类型 query_expression() 添加了类似于 deferred() ,但其SQL表达式在查询时使用新选项确定 with_expression() ;如果未指定,则属性默认为 None ::

from sqlalchemy.orm import query_expression
from sqlalchemy.orm import with_expression

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

    # will be None normally...
    expr = query_expression()

# but let's give it x + y
a1 = session.query(A).options(
    with_expression(A.expr, A.x + A.y)).first()
print(a1.expr)

#3058

ORM支持多表删除

奥姆 Query.delete() 方法支持删除多个表条件,如中介绍的 多表条件支持删除 . 该特性的工作方式与更新的多个表标准相同,首先在0.8中介绍并在中描述。 query.update()支持update..from .

下面,我们针对 SomeEntity ,根据添加FROM子句(或等效的,取决于后端) SomeOtherEntity ::

query(SomeEntity).\
    filter(SomeEntity.id==SomeOtherEntity.id).\
    filter(SomeOtherEntity.foo=='bar').\
    delete()

#959

支持混合动力和复合材料的批量更新

两种混合属性(例如 sqlalchemy.ext.hybrid )以及复合属性 (组合列类型 )现在支持在使用时在update语句的set子句中使用 Query.update() .

对于混合体,可以直接使用简单表达式,也可以使用新的修饰器 hybrid_property.update_expression() 可用于将值拆分为多个列/表达式::

class Person(Base):
    # ...

    first_name = Column(String(10))
    last_name = Column(String(10))

    @hybrid.hybrid_property
    def name(self):
        return self.first_name + ' ' + self.last_name

    @name.expression
    def name(cls):
        return func.concat(cls.first_name, ' ', cls.last_name)

    @name.update_expression
    def name(cls, value):
        f, l = value.split(' ', 1)
        return [(cls.first_name, f), (cls.last_name, l)]

在上面,可以使用以下方式呈现更新:

session.query(Person).filter(Person.id == 5).update(
    {Person.name: "Dr. No"})

复合数据也有类似的功能,其中复合数据值将被分解为单独的列进行批量更新:

session.query(Vertex).update({Edge.start: Point(3, 4)})

混合属性支持子类之间的重用,重新定义@getter

这个 sqlalchemy.ext.hybrid.hybrid_property 类现在支持像 @setter@expression 在子类中多次,现在提供了一个 @getter 突变子,这样一个特定的杂交可以跨子类或其他类重新调整用途。这与 @property 在标准python中:

class FirstNameOnly(Base):
    # ...

    first_name = Column(String)

    @hybrid_property
    def name(self):
        return self.first_name

    @name.setter
    def name(self, value):
        self.first_name = value

class FirstNameLastName(FirstNameOnly):
    # ...

    last_name = Column(String)

    @FirstNameOnly.name.getter
    def name(self):
        return self.first_name + ' ' + self.last_name

    @name.setter
    def name(self, value):
        self.first_name, self.last_name = value.split(' ', maxsplit=1)

    @name.expression
    def name(cls):
        return func.concat(cls.first_name, ' ', cls.last_name)

上面, FirstNameOnly.name 混合动力由 FirstNameLastName 子类,以便专门为新的子类重新调整用途。这是通过将混合对象复制到每个调用中的新对象来实现的。 @getter@setter 以及所有其他的变异方法,比如 @expression 使之前的混合动力的定义保持不变。以前的方法 @setter 将修改现有的混合体,从而干扰超类的定义。

注解

请务必阅读 跨子类重用混合属性 有关如何覆盖的重要说明 hybrid_property.expression()hybrid_property.comparator() ,作为特殊限定符 hybrid_property.overrides 可能需要避免名称与 QueryableAttribute 在某些情况下。

注解

这种变化 @hybrid_property 意味着将setter和其他状态添加到 @hybrid_property , the 方法必须保留原始混合的名称 ,否则具有附加状态的新混合将作为不匹配的名称出现在类上。这和 @property 构成标准python的一部分:

class FirstNameOnly(Base):
    @hybrid_property
    def name(self):
        return self.first_name

    # WRONG - will raise AttributeError: can't set attribute when
    # assigning to .name
    @name.setter
    def _set_name(self, value):
        self.first_name = value

class FirstNameOnly(Base):
    @hybrid_property
    def name(self):
        return self.first_name

    # CORRECT - note regular Python @property works the same way
    @name.setter
    def name(self, value):
        self.first_name = value

#3911

#3912

新批量替换事件

以适应中描述的验证用例 @validates方法在比较之前接收大容量收集集中的所有值 ,一个新的 AttributeEvents.bulk_replace() 方法,它与 AttributeEvents.append()AttributeEvents.remove() 事件。”在“append”和“remove”之前调用大容量“replace”,以便在与现有集合比较之前修改集合。之后,单个项目将附加到一个新的目标集合,并像以前的行为一样,为集合中新的项目触发“附加”事件。下面同时演示了“bulku replace”和“append”,包括如果使用集合分配,“append”将接收已由“bulku replace”处理的对象。新符号 attributes.OP_BULK_REPLACE 可用于确定此“append”事件是否是大容量替换的第二部分:

from sqlalchemy.orm.attributes import OP_BULK_REPLACE

@event.listens_for(SomeObject.collection, "bulk_replace")
def process_collection(target, values, initiator):
    values[:] = [_make_value(value) for value in values]

@event.listens_for(SomeObject.collection, "append", retval=True)
def process_collection(target, value, initiator):
    # make sure bulk_replace didn't already do it
    if initiator is None or initiator.op is not OP_BULK_REPLACE:
        return _make_value(value)
    else:
        return value

#3896

sqlachemy.ext.mutable的新“modified”事件处理程序

新的事件处理程序 AttributeEvents.modified() 添加,它将根据对 flag_modified() 方法,通常从 sqlalchemy.ext.mutable 扩展名:

from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.mutable import MutableDict
from sqlalchemy import event

Base = declarative_base()

class MyDataClass(Base):
    __tablename__ = 'my_data'
    id = Column(Integer, primary_key=True)
    data = Column(MutableDict.as_mutable(JSONEncodedDict))

@event.listens_for(MyDataClass.data, "modified")
def modified_json(instance):
    print("json value modified:", instance.data)

上面,当就地更改为 .data 出现字典。

#3303

向session.refresh添加了“for update”参数

添加了新参数 Session.refresh.with_for_updateSession.refresh() 方法。当 Query.with_lockmode() 方法已弃用,取而代之的是 Query.with_for_update() , the Session.refresh() 方法从未更新以反映新选项::

session.refresh(some_object, with_for_update=True)

这个 Session.refresh.with_for_update 参数接受将作为发送到的相同参数传递的选项字典 Query.with_for_update() ::

session.refresh(some_objects, with_for_update={"read": True})

新参数取代 Session.refresh.lockmode 参数。

#3991

就地突变操作员为mutableset、mutablelist工作

实现了原位突变算子 __ior____iand____ixor____isub__ 对于 MutableSet__iadd__ 对于 MutableList . 虽然这些方法以前会成功更新集合,但它们不会正确触发更改事件。运算符像前面一样改变集合,但另外发出正确的更改事件,以便更改成为下一个刷新过程的一部分:

model = session.query(MyModel).first()
model.json_set &= {1, 3}

#3853

associationproxy any(),has(),contains()使用链接的关联代理

这个 AssociationProxy.any()AssociationProxy.has()AssociationProxy.contains() 比较方法现在支持到属性的链接,该属性本身也是 AssociationProxy ,递归。下面, A.b_values 是链接到的关联代理 AtoB.bvalue ,它本身是上的关联代理 B ::

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

    b_values = association_proxy("atob", "b_value")
    c_values = association_proxy("atob", "c_value")


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

    c = relationship("C")


class C(Base):
    __tablename__ = 'c'
    id = Column(Integer, primary_key=True)
    b_id = Column(ForeignKey('b.id'))
    value = Column(String)


class AtoB(Base):
    __tablename__ = 'atob'

    a_id = Column(ForeignKey('a.id'), primary_key=True)
    b_id = Column(ForeignKey('b.id'), primary_key=True)

    a = relationship("A", backref="atob")
    b = relationship("B", backref="atob")

    b_value = association_proxy("b", "value")
    c_value = association_proxy("b", "c")

我们可以查询 A.b_values 使用 AssociationProxy.contains() 跨两个代理进行查询 A.b_valuesAtoB.b_value

>>> s.query(A).filter(A.b_values.contains('hi')).all()
SELECT a.id AS a_id FROM a WHERE EXISTS (SELECT 1 FROM atob WHERE a.id = atob.a_id AND (EXISTS (SELECT 1 FROM b WHERE b.id = atob.b_id AND b.value = :value_1)))

同样,我们可以查询 A.c_values 使用 AssociationProxy.any() 跨两个代理进行查询 A.c_valuesAtoB.c_value

>>> s.query(A).filter(A.c_values.any(value='x')).all()
SELECT a.id AS a_id FROM a WHERE EXISTS (SELECT 1 FROM atob WHERE a.id = atob.a_id AND (EXISTS (SELECT 1 FROM b WHERE b.id = atob.b_id AND (EXISTS (SELECT 1 FROM c WHERE b.id = c.b_id AND c.value = :value_1)))))

#3769

支持切分的身份密钥增强

ORM使用的标识键结构现在包含一个额外的成员,这样来自不同上下文的两个相同的主键就可以在同一标识映射中共存。

例子在 水平切分 已更新以说明此行为。示例显示了一个切分类 WeatherLocation 指的是家属 WeatherReport 对象,其中 WeatherReport 类映射到存储简单整数主键的表。二 WeatherReport 来自不同数据库的对象可能具有相同的主键值。现在的示例说明 identity_token 字段跟踪此差异,以便两个对象可以在同一标识映射中共存::

tokyo = WeatherLocation('Asia', 'Tokyo')
newyork = WeatherLocation('North America', 'New York')

tokyo.reports.append(Report(80.0))
newyork.reports.append(Report(75))

sess = create_session()

sess.add_all([tokyo, newyork, quito])

sess.commit()

# the Report class uses a simple integer primary key.  So across two
# databases, a primary key will be repeated.  The "identity_token" tracks
# in memory that these two identical primary keys are local to different
# databases.

newyork_report = newyork.reports[0]
tokyo_report = tokyo.reports[0]

assert inspect(newyork_report).identity_key == (Report, (1, ), "north_america")
assert inspect(tokyo_report).identity_key == (Report, (1, ), "asia")

# the token representing the originating shard is also available directly

assert inspect(newyork_report).identity_token == "north_america"
assert inspect(tokyo_report).identity_token == "asia"

#4137

新功能和改进-核心

布尔数据类型现在强制执行严格的真/假/无值

在1.1版中,在 所有情况下强制为零/一/无的非本机布尔整数值 产生了意想不到的改变道路的副作用 Boolean 以非整数值(如字符串)表示时的行为。尤其是字符串值 "0" ,之前会导致 False 正在生成,现在将生成 True . 更糟的是,行为的改变只针对某些后端,而不是其他后端,这意味着发送字符串的代码 "0" 值到 Boolean 会在后端不一致地断裂。

这个问题的最终解决办法是 布尔值不支持字符串值 ,所以在1.2 A硬 TypeError 如果传递了非整数/true/false/none值,则引发。此外,只接受整数值0和1。

为了适应希望更自由地解释布尔值的应用程序,请 TypeDecorator 应该使用。下面介绍了一个允许1.1之前版本的“自由”行为的方法。 Boolean 数据类型:

from sqlalchemy import Boolean
from sqlalchemy import TypeDecorator

class LiberalBoolean(TypeDecorator):
    impl = Boolean

    def process_bind_param(self, value, dialect):
        if value is not None:
            value = bool(int(value))
        return value

#4102

将悲观断开检测添加到连接池

连接池文档长期以来一直强调使用 ConnectionEvents.engine_connect() 引擎事件在已签出的连接上发出一个简单语句,以测试其活动性。当与适当的方言结合使用时,此配方的功能现在已添加到连接池本身。使用新参数 create_engine.pool_pre_ping ,每个已签出的连接将在返回前进行新鲜度测试::

engine = create_engine("mysql+pymysql://", pool_pre_ping=True)

虽然“预Ping”方法为连接池签出添加了少量延迟,但对于面向事务的典型应用程序(包括大多数ORM应用程序),这种开销是最小的,并且消除了获取将引发错误的过时连接的问题,要求应用程序放弃或者重试该操作。

这个功能可以 not 适应在正在进行的事务或SQL操作中丢弃的连接。如果应用程序也必须从中恢复,那么它将需要使用自己的操作重试逻辑来预测这些错误。

#3919

现在可以配置in/not in运算符的空集合行为;简化了默认表达式

一种表达式,如 column.in_([]) ,假定为假,现在生成表达式 1 != 1 默认情况下,而不是 column != column . 本遗嘱 更改结果 与空集比较时,将计算结果为空的SQL表达式或列进行比较的查询,生成布尔值false或true(for not in)而不是null。在这种情况下发出的警告也将被删除。可以使用 create_engine.empty_in_strategy 参数到 create_engine() .

在SQL中,in和not in运算符不支持与显式为空的值集合进行比较;这意味着,此语法是非法的::

mycolumn IN ()

为了解决这个问题,sqlAlchemy和其他数据库库检测到这种情况,并根据“col in()”始终为假(因为“空集”中没有任何内容)的理论,呈现一个计算结果为假(如果不在,则为真)的可选表达式。通常,为了生成一个可跨数据库移植并在WHERE子句上下文中工作的假/真常量,一个简单的重言式,如 1 != 1 用于评估为假和 1 = 1 计算为真(一个简单的常量“0”或“1”通常不能作为WHERE子句的目标)。

早期的SQLAlchemy也开始使用这种方法,但很快它就被理论化为SQL表达式 column IN () 如果“column”为空,则不会计算为false;相反,表达式将生成空值,因为“null”表示“未知”,而与SQL中的空值进行比较通常会生成空值。

为了模拟这个结果,sqlAlchemy从使用 1 != 1 改为使用th表达式 expr != expr 空的“in”和 expr = expr 对于空的“not in”;也就是说,我们不使用固定值,而是使用表达式的实际左侧。如果传递的表达式左侧的计算结果为空,则比较总体也会得到空结果,而不是假或真。

不幸的是,用户最终抱怨这个表达式对某些查询规划者的性能有非常严重的影响。此时,当遇到空的in表达式时,会添加一个警告,这有利于sqlAlchemy继续保持“正确”,并敦促用户避免在谓词中生成空的代码,因为通常可以安全地忽略它们。但是,对于从输入变量动态构建的查询来说,这当然是一个负担,其中一组传入的值可能是空的。

近几个月来,这一决定的最初假设遭到质疑。表达式“null in()”应返回null的概念只是理论上的,无法测试,因为数据库不支持这种语法。然而,事实上,通过如下模拟空集,您可以询问关系数据库它将为“null in()”返回什么值:

SELECT NULL IN (SELECT 1 WHERE 1 != 1)

通过上面的测试,我们发现数据库本身无法在答案上达成一致。PostgreSQL被大多数人认为是最“正确”的数据库,返回false;因为尽管“空”表示“未知”,“空集”表示不存在任何内容,包括所有未知值。另一方面,mysql和mariadb为上述表达式返回空值,默认为“所有与空值的比较返回空值”这一更常见的行为。

SQLAlchemy的SQL体系结构比最初做出此设计决策时更加复杂,因此现在我们可以允许在SQL字符串编译时调用这两种行为。以前,转换成比较表达式是在构建时完成的,也就是说,当 ColumnOperators.in_()ColumnOperators.notin_() 调用了运算符。使用编译时行为,可以指示方言本身调用任何一种方法,即“静态” 1 != 1 比较或“动态” expr != expr 比较。默认值为 改变 作为“静态”比较,因为这与PostgreSQL在任何情况下的行为都是一致的,而且这也是绝大多数用户喜欢的。本遗嘱 更改结果 将空表达式与空集进行比较的查询,尤其是查询求反的查询。 where(~null_expr.in_([])) ,因为现在的计算结果为true,而不是null。

现在可以使用标志控制行为。 create_engine.empty_in_strategy ,默认为 "static" 设置,但也可以设置为 "dynamic""dynamic_warn" 那里 "dynamic_warn" 设置相当于以前的发射行为 expr != expr 以及性能警告。但是,预计大多数用户都会喜欢“静态”的默认设置。

#3907

参数集后期扩展允许在具有缓存语句的表达式中使用

添加了一种新的 bindparam() 称为“扩张”。这是用来 IN 表达式,其中元素列表在语句执行时而不是在语句编译时呈现为单个绑定参数。这既允许将单个绑定参数名链接到多个元素的In表达式,也允许将查询缓存与In表达式一起使用。新特性允许“select in”加载和“polymorphic in”加载的相关特性使用烘焙的查询扩展来减少调用开销:

stmt = select([table]).where(
    table.c.col.in_(bindparam('foo', expanding=True))
conn.execute(stmt, {"foo": [1, 2, 3]})

该特征应被视为 实验的 在1.2系列中。

#3953

比较运算符的扁平运算符优先级

像in、like、equals、is、match和其他比较运算符的运算符优先级已展平为一个级别。当比较运算符组合在一起时,会产生更多的括号,例如:

(column('q') == null()) != (column('y') == null())

现在将生成 (q IS NULL) != (y IS NULL) 而不是 q IS NULL != y IS NULL .

#3999

支持表、列上的SQL注释,包括DDL、反射

核心接受对与表和列关联的字符串注释的支持。这些是通过 Table.commentColumn.comment 参数:

Table(
    'my_table', metadata,
    Column('q', Integer, comment="the Q value"),
    comment="my Q table"
)

上面,将在表创建时适当地呈现DDL,以将上面的注释与模式中的表/列关联起来。当上表自动加载或检查时 Inspector.get_columns() ,包括注释。表注释也可以单独使用 Inspector.get_table_comment() 方法。

当前的后端支持包括mysql、postgresql和oracle。

#1546

多表条件支持删除

这个 Delete construct现在支持多个表条件,这些条件是为支持它的后端实现的,目前它们是PostgreSQL、MySQL和Microsoft SQL Server(支持也添加到当前不工作的Sybase方言中)。该特性的工作原理与0.7和0.8系列中首次引入的用于更新的多表标准相同。

陈述如下:

stmt = users.delete().\
        where(users.c.id == addresses.c.id).\
        where(addresses.c.email_address.startswith('ed%'))
conn.execute(stmt)

PostgreSQL后端上上述语句生成的SQL将呈现为:

DELETE FROM users USING addresses
WHERE users.id = addresses.id
AND (addresses.email_address LIKE %(email_address_1)s || '%%')

#959

startswith()、endswith()的新“自动转义”选项

“autoescape”参数添加到 ColumnOperators.startswith()ColumnOperators.endswith()ColumnOperators.contains() . 此参数设置为 True will automatically escape all occurrences of %, _ with an escape character, which defaults to a forwards slash /; occurrences of the escape character itself are also escaped. The forwards slash is used to avoid conflicts with settings like PostgreSQL's `` 标准_-confirming_-strings```,其默认值在PostgreSQL 9.1和MySQL中发生了更改 NO_BACKSLASH_ESCAPES 设置。如果需要,现在可以使用现有的“escape”参数更改自动转义字符。

注解

自1.2.0起,此功能已从1.2.0b2中的初始实现更改为自动转义,以便自动转义现在作为布尔值传递,而不是作为转义字符使用的特定字符。

表达式,如:

>>> column('x').startswith('total%score', autoescape=True)

呈现为:

x LIKE :x_1 || '%' ESCAPE '/'

其中参数“x_1”的值为 'total/%score' .

类似地,具有反斜杠的表达式:

>>> column('x').startswith('total/score', autoescape=True)

将以相同的方式呈现,参数“x_1”的值为 'total//score' .

#2694

添加到“float”数据类型的强类型

一系列的更改允许使用 Float 数据类型更强烈地将自身链接到python浮点值,而不是更一般的 Numeric . 这些更改主要与确保不会错误地将python浮点值强制为 Decimal() 和被强迫 float 如果需要,在结果端,如果应用程序使用的是普通浮动。

  • 传递给SQL表达式的普通python“float”值现在将被拉入类型为 Float ;以前,类型是 Numeric ,使用默认的“asdecimal=true”标志,这意味着结果类型将强制 Decimal() . 特别是,这将在sqlite上发出一个令人困惑的警告:

    float_value = connection.scalar(
        select([literal(4.56)])   # the "BindParameter" will now be
                                  # Float, not Numeric(asdecimal=True)
    )
  • 数学运算介于 NumericFloatInteger 现在将保留 NumericFloat 键入结果表达式的类型,包括 asdecimal 标志以及类型是否应为 Float ::

    # asdecimal flag is maintained
    expr = column('a', Integer) * column('b', Numeric(asdecimal=False))
    assert expr.type.asdecimal == False
    
    # Float subclass of Numeric is maintained
    expr = column('a', Integer) * column('b', Float())
    assert isinstance(expr.type, Float)
  • 这个 Float 数据类型将应用 float() 如果已知DBAPI支持本机,则处理器将无条件地生成值 Decimal() 模式。有些后端并不总是保证浮点数以普通浮点数的形式返回,而不是像mysql这样的精确数字。

#4017

#4018

#4020

支持分组集、多维数据集、汇总

所有三个分组集、多维数据集和汇总都可以通过 func 命名空间。在cube和rollup的情况下,这些函数已经在以前的版本中工作,但是对于分组集,会向编译器添加一个占位符以允许空间。所有三个函数现在都在文档中命名:

>>> from sqlalchemy import select, table, column, func, tuple_
>>> t = table('t',
...           column('value'), column('x'),
...           column('y'), column('z'), column('q'))
>>> stmt = select([func.sum(t.c.value)]).group_by(
...     func.grouping_sets(
...         tuple_(t.c.x, t.c.y),
...         tuple_(t.c.z, t.c.q),
...     )
... )
>>> print(stmt)
SELECT sum(t.value) AS sum_1
FROM t GROUP BY GROUPING SETS((t.x, t.y), (t.z, t.q))

#3429

带上下文默认生成器的多值插入的参数帮助器

默认生成函数,例如 上下文相关的默认函数 ,可以通过 DefaultExecutionContext.current_parameters 属性。但是,在 Insert 构造,通过 Insert.values() 方法,对每个参数集多次调用用户定义函数,每次调用一次,但是无法知道键的哪一个子集在 DefaultExecutionContext.current_parameters 应用于该列。一个新函数 DefaultExecutionContext.get_current_parameters() 添加,其中包含关键字参数 DefaultExecutionContext.get_current_parameters.isolate_multiinsert_groups 拖欠 True 它执行传递子字典的额外工作 DefaultExecutionContext.current_parameters 其名称本地化为当前正在处理的VALUES子句::

def mydefault(context):
    return context.get_current_parameters()['counter'] + 12

mytable = Table('mytable', meta,
    Column('counter', Integer),
    Column('counter_plus_twelve',
           Integer, default=mydefault, onupdate=mydefault)
)

stmt = mytable.insert().values(
    [{"counter": 5}, {"counter": 18}, {"counter": 20}])

conn.execute(stmt)

#4075

关键行为变化-ORM

after eu rollback()session事件现在在对象过期之前发出

这个 SessionEvents.after_rollback() 在对象的状态过期之前,事件现在可以访问对象的属性状态(例如“快照删除”)。这允许事件与 SessionEvents.after_commit() 在删除“快照”之前也会发出的事件:

sess = Session()

user = sess.query(User).filter_by(name='x').first()

@event.listens_for(sess, "after_rollback")
def after_rollback(session):
    # 'user.name' is now present, assuming it was already
    # loaded.  previously this would raise upon trying
    # to emit a lazy load.
    print("user name: %s" % user.name)

@event.listens_for(sess, "after_commit")
def after_commit(session):
    # 'user.name' is present, assuming it was already
    # loaded.  this is the existing behavior.
    print("user name: %s" % user.name)

if should_rollback:
    sess.rollback()
else:
    sess.commit()

请注意 Session 仍然不允许在此事件中发出SQL;这意味着卸载的属性仍然无法在事件范围内加载。

#3934

修复了涉及单表继承的问题 select_from()

这个 Query.select_from() 方法现在在生成SQL时采用单表继承列鉴别器;以前,只考虑查询列列表中的表达式。

假设 Manager 是的子类 Employee . 类似以下的查询:

sess.query(Manager.id)

将生成以下SQL::

SELECT employee.id FROM employee WHERE employee.type IN ('manager')

然而,如果 Manager 仅由指定 Query.select_from() 不在列列表中,不会添加鉴别器::

sess.query(func.count(1)).select_from(Manager)

将生成:

SELECT count(1) FROM employee

随着修复, Query.select_from() 现在工作正常,我们得到:

SELECT count(1) FROM employee WHERE employee.type IN ('manager')

可能通过手动提供WHERE子句来解决此问题的应用程序可能需要进行调整。

#3891

以前的集合在替换时不再改变

每当映射集合的成员更改时,ORM都会发出事件。在将集合分配给将替换上一个集合的属性的情况下,这样做的一个副作用是被替换的集合也将发生变化,这是误导和不必要的:

>>> a1, a2, a3 = Address('a1'), Address('a2'), Address('a3')
>>> user.addresses = [a1, a2]

>>> previous_collection = user.addresses

# replace the collection with a new one
>>> user.addresses = [a2, a3]

>>> previous_collection
[Address('a1'), Address('a2')]

在变更之前, previous_collection 将删除“a1”成员,与新集合中不再存在的成员相对应。

#3913

@validates方法在比较之前接收大容量收集集中的所有值

一种使用 @validates 现在将在“批量设置”操作期间接收集合的所有成员,然后再对现有集合应用比较。

给定映射为:

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

    @validates('bs')
    def convert_dict_to_b(self, key, value):
        return B(data=value['data'])

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

上面,我们可以使用如下的验证器,将传入字典转换为 B 集合追加时:

a1 = A()
a1.bs.append({"data": "b1"})

但是,集合分配将失败,因为ORM将假定传入对象已经是 B 当它尝试将它们与集合的现有成员进行比较时,在执行实际上调用验证程序的集合附加之前。这将使批量设置操作无法容纳非ORM对象,如需要预先修改的字典:

a1 = A()
a1.bs = [{"data": "b1"}]

新逻辑使用新的 AttributeEvents.bulk_replace() 事件以确保所有值都发送到 @validates 在前面工作。

作为此更改的一部分,这意味着验证器现在将接收 all 批量集合上集合的成员,而不仅仅是新成员。假设一个简单的验证器,例如:

class A(Base):
    # ...

    @validates('bs')
    def validate_b(self, key, value):
        assert value.data is not None
        return value

上面,如果我们从以下集合开始:

a1 = A()

b1, b2 = B(data="one"), B(data="two")
a1.bs = [b1, b2]

然后,将集合替换为与第一个集合重叠的集合:

b3 = B(data="three")
a1.bs = [b2, b3]

以前,第二个任务将触发 A.validate_b 方法仅一次,用于 b3 对象。这个 b2 对象将被视为已存在于集合中且未验证。有了新的行为, b2b3 被传递给 A.validate_b 在传递到集合之前。因此,验证方法使用等量行为来适应这种情况是很重要的。

#3896

使用flag_dirty()将对象标记为“dirty”,而不更改任何属性

如果 flag_modified() 函数用于将未实际加载的属性标记为已修改::

a1 = A(data='adf')
s.add(a1)

s.flush()

# expire, similarly as though we said s.commit()
s.expire(a1, 'data')

# will raise InvalidRequestError
attributes.flag_modified(a1, 'data')

这是因为在任何情况下,如果属性在刷新发生时保持不存在,刷新过程都很可能失败。将一个对象标记为“modified”,而不专门引用任何属性,以便在刷新过程中考虑该对象,以便自定义事件处理程序,例如 SessionEvents.before_flush() 使用新的 flag_dirty() 功能:

from sqlalchemy.orm import attributes

attributes.flag_dirty(a1)

#3753

已从作用域会话中删除“scope”关键字

一个非常古老和未记录的关键字参数 scope 已删除::

from sqlalchemy.orm import scoped_session
Session = scoped_session(sessionmaker())

session = Session(scope=None)

此关键字的目的是尝试允许变量“scope”,其中 None 表示“无范围”,因此将返回新的 Session . 关键字从未被记录,现在将提升 TypeError 如果遇到。预计不会使用此关键字,但是如果用户在测试期间报告与此相关的问题,则可以使用贬低来还原它。

#3796

结合OnUpdate发布更新的改进

使用 relationship.post_update 功能现在可以更好地与具有 Column.onupdate 值集。如果使用列的显式值插入对象,则在更新过程中会重新声明该对象,以便“onUpdate”规则不会覆盖该对象:

class A(Base):
    __tablename__ = 'a'
    id = Column(Integer, primary_key=True)
    favorite_b_id = Column(ForeignKey('b.id', name="favorite_b_fk"))
    bs = relationship("B", primaryjoin="A.id == B.a_id")
    favorite_b = relationship(
        "B", primaryjoin="A.favorite_b_id == B.id", post_update=True)
    updated = Column(Integer, onupdate=my_onupdate_function)

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

a1 = A()
b1 = B()

a1.bs.append(b1)
a1.favorite_b = b1
a1.updated = 5
s.add(a1)
s.flush()

上面,前面的行为是在插入之后发出更新,从而触发“onupdate”并覆盖值“5”。SQL现在看起来像:

INSERT INTO a (favorite_b_id, updated) VALUES (?, ?)
(None, 5)
INSERT INTO b (a_id) VALUES (?)
(1,)
UPDATE a SET favorite_b_id=?, updated=? WHERE a.id = ?
(1, 5, 1)

此外,如果“更新”的值为 not 设置,然后我们正确地返回新生成的值 a1.updated ;以前,刷新或过期属性以允许生成的值存在的逻辑不会因后期更新而触发。这个 InstanceEvents.refresh_flush() 在这种情况下,当在刷新内发生刷新时,也会发出事件。

#3471

#3472

后更新与ORM版本控制集成

后更新功能,记录在 指向自身/相互依赖的行的行 ,涉及到除了通常为目标行发出的insert/update/delete之外,还会根据对特定关系绑定外键的更改发出update语句。此更新语句现在参与版本控制功能,记录在 配置版本计数器 .

给定映射:

class Node(Base):
    __tablename__ = 'node'
    id = Column(Integer, primary_key=True)
    version_id = Column(Integer, default=0)
    parent_id = Column(ForeignKey('node.id'))
    favorite_node_id = Column(ForeignKey('node.id'))

    nodes = relationship("Node", primaryjoin=remote(parent_id) == id)
    favorite_node = relationship(
        "Node", primaryjoin=favorite_node_id == remote(id),
        post_update=True
    )

    __mapper_args__ = {
        'version_id_col': version_id
    }

将另一个节点关联为“收藏”的节点的更新现在将增加版本计数器并与当前版本匹配:

node = Node()
session.add(node)
session.commit()  # node is now version #1

node = session.query(Node).get(node.id)
node.favorite_node = Node()
session.commit()  # node is now version #2

请注意,这意味着一个对象接收到响应其他属性更改的更新,以及由于后更新关系更改而进行的第二次更新,现在将接收到 一次刷新的两个版本计数器更新 . 但是,如果对象要在当前刷新中进行插入,则版本计数器 不会 除非有服务器端版本控制方案,否则会增加一段时间。

post-update发出更新(即使是更新)的原因现在讨论在 为什么post-update除了第一个更新外还发出更新? .

#3496

关键行为变化-核心

自定义运算符的类型行为已保持一致

用户定义的运算符可以使用 Operators.op() 功能。以前,表达式对此类运算符的类型行为是不一致的,也不可控的。

而在1.1中,像下面这样的表达式将产生没有返回类型的结果(假定 -%> 数据库是否支持某些特殊的运算符)::

>>> column('x', types.DateTime).op('-%>')(None).type
NullType()

其他类型将使用使用左侧类型作为返回类型的默认行为::

>>> column('x', types.String(50)).op('-%>')(None).type
String(length=50)

这些行为大多是偶然发生的,因此该行为与第二个表单保持一致,即默认的返回类型与左侧表达式相同:

>>> column('x', types.DateTime).op('-%>')(None).type
DateTime()

由于大多数用户定义的运算符都是“比较”运算符,通常是PostgreSQL定义的许多特殊运算符之一,因此 Operators.op.is_comparison 已修复标志以遵循其文档化的允许返回类型 Boolean 在所有情况下,包括 ARRAYJSON ::

>>> column('x', types.String(50)).op('-%>', is_comparison=True)(None).type
Boolean()
>>> column('x', types.ARRAY(types.Integer)).op('-%>', is_comparison=True)(None).type
Boolean()
>>> column('x', types.JSON()).op('-%>', is_comparison=True)(None).type
Boolean()

为了辅助布尔比较运算符,一种新的速记方法 Operators.bool_op() 已添加。对于动态布尔运算符,应首选此方法:

>>> print(column('x', types.Integer).bool_op('-%>')(5))
x -%> :x_1

现在按条件转义了literal_column()中的百分号

这个 literal_column 根据使用中的dbapi是否使用百分比符号敏感参数样式(例如“format”或“pyformat”),construct现在有条件地对百分比符号字符进行转义。

以前,不可能产生 literal_column 构造一个百分号:

>>> from sqlalchemy import literal_column
>>> print(literal_column('some%symbol'))
some%%symbol

百分比符号现在不受未设置为使用“format”或“pyformat”参数样式的方言的影响;方言,例如大多数mysql方言,如果声明了这些参数样式中的一种,将继续根据需要进行转义:

>>> from sqlalchemy import literal_column
>>> print(literal_column('some%symbol'))
some%symbol
>>> from sqlalchemy.dialects import mysql
>>> print(literal_column('some%symbol').compile(dialect=mysql.dialect()))
some%%symbol

作为此更改的一部分,在使用诸如 ColumnOperators.contains()ColumnOperators.startswith()ColumnOperators.endswith() 也被精炼到只有在适当的时候才会发生。

#3740

column-level collate关键字现在引用排序规则名称。

一个错误 collate()ColumnOperators.collate() 用于在语句级别提供特殊列排序规则的函数是固定的,其中不引用区分大小写的名称::

stmt = select([mytable.c.x, mytable.c.y]).\
    order_by(mytable.c.somecolumn.collate("fr_FR"))

现在呈现:

SELECT mytable.x, mytable.y,
FROM mytable ORDER BY mytable.somecolumn COLLATE "fr_FR"

以前,区分大小写的名称 "fr_FR" 不会被引用。目前,“fr_fr”名称的人工报价是 not 检测到,因此应调整手动引用标识符的应用程序。请注意,此更改不会影响类型级别的排序规则的使用(例如,在类似数据类型的 String 在表级别),已经应用了报价。

#3785

方言改进与变化-PostgreSQL

支持批处理模式/快速执行助手

心理医生2 cursor.executemany() 方法的性能很差,特别是在使用INSERT语句时。为了缓解这一问题,mental copg2增加了 Fast Execution Helpers 它通过批处理发送多个DML语句,将语句重新编写到更少的服务器往返行程中。SQLAlChemy 1.2现在包括对这些帮助器的支持,这些帮助器可以在任何时候透明地使用 Engine 利用 cursor.executemany() 调用针对多个参数集的语句。默认情况下,该功能处于关闭状态,可以使用 use_batch_mode 打开参数 create_engine() ::

engine = create_engine(
    "postgresql+psycopg2://scott:tiger@host/dbname",
    use_batch_mode=True)

该特性目前被认为是试验性的,但在将来的版本中可能会默认启用。

#4109

支持间隔中的字段规范,包括完全反射

postgresql的interval数据类型中的“fields”说明符允许指定存储间隔的哪些字段,包括“year”、“month”、“year to month”等值。 INTERVAL 数据类型现在允许指定这些值::

from sqlalchemy.dialects.postgresql import INTERVAL

Table(
    'my_table', metadata,
    Column("some_interval", INTERVAL(fields="DAY TO SECOND"))
)

此外,现在可以独立于存在的“字段”说明符反映所有间隔数据类型;数据类型本身的“字段”参数也将存在:

>>> inspect(engine).get_columns("my_table")
[{'comment': None,
  'name': u'some_interval', 'nullable': True,
  'default': None, 'autoincrement': False,
  'type': INTERVAL(fields=u'day to second')}]

#3959

方言改进和变化-MySQL

支持在重复密钥更新时插入..

这个 ON DUPLICATE KEY UPDATE 条款 INSERT 现在,使用特定于MySQL的 Insert 对象,通过 sqlalchemy.dialects.mysql.dml.insert() . 这个 Insert 子类添加新方法 Insert.on_duplicate_key_update() 实现mysql的语法:

from sqlalchemy.dialects.mysql import insert

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

on_conflict_stmt = insert_stmt.on_duplicate_key_update(
    data=insert_stmt.inserted.data,
    status='U'
)

conn.execute(on_conflict_stmt)

以上将呈现:

INSERT INTO my_table (id, data)
VALUES (:id, :data)
ON DUPLICATE KEY UPDATE data=VALUES(data), status=:status_1

#4009

方言改进和变化-Oracle

主要重构为cx_Oracle方言,打字系统

随着cx-oracle dbapi的6.x系列的引入,sqlAlchemy的cx-oracle方言已经被改写和简化,以利用cx-oracle最近的改进,并放弃对cx-oracle 5.x系列之前更相关的模式的支持。

  • 支持的最低cx_Oracle版本现在为5.1.3;建议使用5.3或最新的6.x系列。

  • 数据类型的处理已被重构。这个 cursor.setinputsizes() 根据cx-oracle开发人员的建议,方法不再用于除lob类型之外的任何数据类型。因此,参数 auto_setinputsizesexclude_setinputsizes 已弃用,不再有任何效果。

  • 这个 coerce_to_decimal 标志,当设置为false时,指示强制使用精度和小数位数为的数字类型 Decimal 不应发生,只影响非类型化(例如,没有 TypeEngine 对象)语句。包含一个 Numeric 类型或子类型现在将遵循该类型的十进制强制规则。

  • 方言中的“两阶段”事务支持已经为cx_Oracle的6.x系列放弃,现在已完全删除,因为此功能从未正确工作,也不太可能投入生产使用。因此, allow_twophase 方言标志已弃用,也没有任何效果。

  • 修复了返回时出现的列键的错误。陈述如下:

    result = conn.execute(table.insert().values(x=5).returning(table.c.a, table.c.b))

    以前,结果的每一行中的键将是 ret_0ret_1 ,这是cx_Oracle返回实现内部的标识符。钥匙现在是 ab 其他方言也是如此。

  • cx_oracle的lob数据类型将返回值表示为 cx_Oracle.LOB 对象,它是一个与光标关联的代理,通过 .read() 方法。历史上,如果在使用这些LOB对象之前读取了更多的行(特别是,比cursor.arraysize值多的行,这会导致读取新的一批行),则这些LOB对象将引发错误“LOB变量在后续提取之后不再有效”。SQLAlchemy通过自动调用 .read() 在其输入系统中的这些LOB上,以及使用 BufferedColumnResultSet 这将确保在以下情况下缓冲此数据 cursor.fetchmany()cursor.fetchall() 使用。

    方言现在使用cx-oracle输出类型处理程序来处理这些 .read() 调用,以便无论提取多少行,都始终在前面调用它们,以便不再发生此错误。因此,使用 BufferedColumnResultSet 以及堆芯的其他内部构件 ResultSet 已经删除了特定于此用例的。类型对象也被简化,因为它们不再需要处理二进制列结果。

    此外,cx_Oracle6.x已经消除了在任何情况下发生此错误的条件,因此该错误不再可能。如果很少(如果有的话)使用 auto_convert_lobs=False 选项与之前的5.x系列cx_Oracle一起使用,在使用LOB对象之前读取更多行。升级到cx-oracle 6.x将解决这个问题。

Oracle唯一,检查约束现在反映

唯一和检查约束现在通过反映 Inspector.get_unique_constraints()Inspector.get_check_constraints() . 一 Table 反射的对象现在将包括 CheckConstraint 对象也一样。参见注释 约束反射 关于行为怪癖的信息,包括 Table 对象仍不包括任何 UniqueConstraint 这些通常表示通过的对象 Index .

参见

约束反射

#4003

Oracle外键约束名称现在是“名称规范化”

传递到 ForeignKeyConstraint 在表反射期间以及在 Inspector.get_foreign_keys() 方法现在将被“名称规范化”,即,对于不区分大小写的名称,将其表示为小写,而不是Oracle使用的原始大写格式::

>>> insp.get_indexes("addresses")
[{'unique': False, 'column_names': [u'user_id'],
  'name': u'address_idx', 'dialect_options': {}}]

>>> insp.get_pk_constraint("addresses")
{'name': u'pk_cons', 'constrained_columns': [u'id']}

>>> insp.get_foreign_keys("addresses")
[{'referred_table': u'users', 'referred_columns': [u'id'],
  'referred_schema': None, 'name': u'user_id_fk',
  'constrained_columns': [u'user_id']}]

以前,外键结果如下所示:

[{'referred_table': u'users', 'referred_columns': [u'id'],
  'referred_schema': None, 'name': 'USER_ID_FK',
  'constrained_columns': [u'user_id']}]

上面的内容可能会产生问题,特别是在Alembic AutoGenerate中。

#3276

方言改进和更改-SQL Server

支持嵌入点的SQL Server架构名称

SQL Server方言具有这样一种行为,即假定其中带有点的架构名称是“数据库”。“所有者”标识符对,在表和组件反射操作期间以及为架构名称提供报价时,必须将其拆分为这些单独的组件,以便将这两个符号单独引用。利。现在可以使用括号手动指定拆分的位置来传递模式参数,允许数据库和/或所有者名称本身包含一个或多个点:

Table(
    "some_table", metadata,
    Column("q", String(50)),
    schema="[MyDataBase.dbo]"
)

上表将认为“业主”是 MyDataBase.dbo ,也将在呈现时引用,而“数据库”则为“无”。要单独引用数据库名称和所有者,请使用两对括号:

Table(
    "some_table", metadata,
    Column("q", String(50)),
    schema="[MyDataBase.SomeDB].[MyDB.owner]"
)

另外, quoted_name 现在,当SQL Server方言传递到“schema”时,构造将得到认可;如果引号标志为true,则给定符号将不会在点上拆分,并将解释为“owner”。

#2626

自动提交隔离级别支持

pyodbc和pymssql方言现在都支持由设置的“autocommit”隔离级别。 Connection.execution_options() 它将在DBAPI连接对象上建立正确的标志。

Previous: SQLAlchemy 1.3有什么新功能? Next: SQLAlchemy 1.1有什么新功能?