目录

SQLAlchemy 1.0有什么新功能?

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

关于此文档

本文档描述了截至2014年5月正在进行维护发布的SQLAlchemy版本0.9和2015年4月发布的SQLAlchemy版本1.0之间的更改。

文件上次更新时间:2015年6月9日

介绍

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

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

新功能和改进-ORM

新会话批量插入/更新API

一系列新的 Session 已经创建了直接在工作单元的设施中提供挂钩以发出插入和更新语句的方法。如果使用正确,这个面向专家的系统可以允许使用ORM映射来生成批量插入和更新批处理到ExecuteMany组中的语句,从而允许语句以与直接使用核心相媲美的速度继续运行。

参见

散装作业 -介绍和完整文档

#3100

新性能示例套件

灵感来源于为 散装作业 功能以及 如何分析一个由SQLAlchemy支持的应用程序? 在FAQ部分,添加了一个新的示例部分,其中包含一些旨在说明各种核心和ORM技术的相对性能概要的脚本。这些脚本被组织成用例,并打包在一个控制台界面下,这样就可以运行任何演示组合、转储计时、python概要文件结果和/或runsnake概要文件显示。

参见

性能

“烘烤”查询

“baked”查询特性是一种不寻常的新方法,它允许直接构造调用 Query 使用缓存的对象,在连续调用时,它的特点是大大减少了Python函数调用开销(超过75%)。通过指定 Query 对象作为一系列仅调用一次的lambda,作为预编译单元的查询开始可行:

from sqlalchemy.ext import baked
from sqlalchemy import bindparam

bakery = baked.bakery()

def search_for_user(session, username, email=None):

    baked_query = bakery(lambda session: session.query(User))
    baked_query += lambda q: q.filter(User.name == bindparam('username'))

    baked_query += lambda q: q.order_by(User.id)

    if email:
        baked_query += lambda q: q.filter(User.email == bindparam('email'))

    result = baked_query(session).params(username=username, email=email).all()

    return result

参见

烘焙查询

#3054

声明性混合的改进, @declared_attr 以及相关功能

declared_attr 已经过大修以支持新的能力。

装饰有 declared_attr 现在只调用 之后 生成任何基于mixin的列副本。这意味着函数可以调用mixin已建立的列,并将接收对正确的 Column 对象:

class HasFooBar(object):
    foobar = Column(Integer)

    @declared_attr
    def foobar_prop(cls):
        return column_property('foobar: ' + cls.foobar)

class SomeClass(HasFooBar, Base):
    __tablename__ = 'some_table'
    id = Column(Integer, primary_key=True)

上面, SomeClass.foobar_prop 将针对 SomeClassSomeClass.foobar 将是决赛 Column 要映射到的对象 SomeClass ,而不是直接出现在 HasFooBar ,即使尚未映射列。

这个 declared_attr 现在函数 回忆录 按类返回的值,以便重复调用同一属性将返回相同的值。我们可以改变例子来说明这一点:

class HasFooBar(object):
    @declared_attr
    def foobar(cls):
        return Column(Integer)

    @declared_attr
    def foobar_prop(cls):
        return column_property('foobar: ' + cls.foobar)

class SomeClass(HasFooBar, Base):
    __tablename__ = 'some_table'
    id = Column(Integer, primary_key=True)

以前, SomeClass 将映射为 foobar 列,但 foobar_prop 通过呼吁 foobar 第二次将产生不同的列。价值 SomeClass.foobar 现在是在声明性设置期间被记忆化的,这样即使在映射器映射属性之前,临时列值也将保持一致,无论 declared_attr 被召唤。

上面的两个行为对于从其他属性派生的许多类型的映射器属性的声明性定义应该有很大帮助,其中 declared_attr 函数是从其他函数调用的 declared_attr 在实际映射类之前本地存在的函数。

对于一个希望构建声明性mixin(为每个子类建立不同的列)的非常小的边缘情况,一个新的修饰符 declared_attr.cascading 添加。使用此修饰符,将为映射的继承层次结构中的每个类单独调用修饰函数。虽然这已经是特殊属性的行为,例如 __table_args____mapper_args__ 对于列和其他属性,默认情况下的行为假定属性仅附加到基类,并且仅从子类继承。用 declared_attr.cascading ,可以应用个人行为:

class HasIdMixin(object):
    @declared_attr.cascading
    def id(cls):
        if has_inherited_table(cls):
            return Column(ForeignKey('myclass.id'), primary_key=True)
        else:
            return Column(Integer, primary_key=True)

class MyClass(HasIdMixin, Base):
    __tablename__ = 'myclass'
    # ...

class MySubClass(MyClass):
    ""
    # ...

参见

在继承场景中混入列

最后, AbstractConcreteBase 类已被重写,以便可以在抽象基上以内联方式设置关系或其他映射器属性::

from sqlalchemy import Column, Integer, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import (declarative_base, declared_attr,
    AbstractConcreteBase)

Base = declarative_base()

class Something(Base):
    __tablename__ = u'something'
    id = Column(Integer, primary_key=True)


class Abstract(AbstractConcreteBase, Base):
    id = Column(Integer, primary_key=True)

    @declared_attr
    def something_id(cls):
        return Column(ForeignKey(Something.id))

    @declared_attr
    def something(cls):
        return relationship(Something)


class Concrete(Abstract):
    __tablename__ = u'cca'
    __mapper_args__ = {'polymorphic_identity': 'cca', 'concrete': True}

上面的映射将建立一个表 cca 两者兼有 id 和A something_id 列,以及 Concrete 也会有关系 something . 新功能是 Abstract 也将具有独立配置的关系 something 它建立在基础的多态联合之上。

#3150 #2670 #3149 #2952 #3050

ORM完全对象获取速度快25%

机械的 loading.py 模块以及标识映射已经经历了几次内联、重构和修剪过程,因此原始的行负载现在可以以大约25%的速度填充基于ORM的对象。假设有一个1米的行表,下面这样的脚本说明了最改进的加载类型:

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

Base = declarative_base()

class Foo(Base):
    __table__ = Table(
        'foo', Base.metadata,
        Column('id', Integer, primary_key=True),
        Column('a', Integer(), nullable=False),
        Column('b', Integer(), nullable=False),
        Column('c', Integer(), nullable=False),
    )

engine = create_engine(
    'mysql+mysqldb://scott:tiger@localhost/test', echo=True)

sess = Session(engine)

now = time.time()

# avoid using all() so that we don't have the overhead of building
# a large list of full objects in memory
for obj in sess.query(Foo).yield_per(100).limit(1000000):
    pass

print("Total time: %d" % (time.time() - now))

本地MacBookPro结果工作台从19秒0.9下降到14秒1.0。这个 Query.yield_per() 当成批处理大量行时,调用总是一个好主意,因为它可以防止Python解释器必须一次为所有对象及其指令插入分配大量内存。没有 Query.yield_per() 在MacBookPro上,上面的脚本在0.9上是31秒,在1.0上是26秒,这是设置非常大的内存缓冲区所花费的额外时间。

新的keyedtuple实现速度显著加快

我们看了一下 KeyedTuple 实现,希望改进这样的查询:

rows = sess.query(Foo.a, Foo.b, Foo.c).all()

这个 KeyedTuple 类被使用而不是python的 collections.namedtuple() 因为后者有一个非常复杂的类型创建例程,其基准测试速度比 KeyedTuple . 然而,当获取数十万行时, collections.namedtuple() 快速超车 KeyedTuple 随着实例调用的增加,速度会明显变慢。怎么办?一种在两种方法之间进行对冲的新类型。对于“大小”(返回的行数)和“num”(不同查询数)这三种类型,新的“轻量级键控元组”要么优于这两种类型,要么稍微落后于速度更快的对象,具体取决于哪种情况。在“最佳位置”中,我们都在创建大量的新类型,并获取大量的行,轻量级对象完全吸走了name-duple和keyedtuple::

-----------------
size=10 num=10000                 # few rows, lots of queries
namedtuple: 3.60302400589         # namedtuple falls over
keyedtuple: 0.255059957504        # KeyedTuple very fast
lw keyed tuple: 0.582715034485    # lw keyed trails right on KeyedTuple
-----------------
size=100 num=1000                 # <--- sweet spot
namedtuple: 0.365247011185
keyedtuple: 0.24896979332
lw keyed tuple: 0.0889317989349   # lw keyed blows both away!
-----------------
size=10000 num=100
namedtuple: 0.572599887848
keyedtuple: 2.54251694679
lw keyed tuple: 0.613876104355
-----------------
size=1000000 num=10               # few queries, lots of rows
namedtuple: 5.79669594765         # namedtuple very fast
keyedtuple: 28.856498003          # KeyedTuple falls over
lw keyed tuple: 6.74346804619     # lw keyed trails right on namedtuple

#3176

结构记忆使用的显著改善

结构内存的使用已经通过更显著地使用 __slots__ 对于许多内部对象。这种优化特别适用于具有大量表和列的大型应用程序的基本内存大小,并减少了各种大容量对象的内存大小,包括事件侦听内部构件、比较器对象以及ORM属性和加载程序策略系统的一部分。

使用大量测量nova启动规模的工作台说明,在“nova.db.sqlacalchemy.models”的基本导入中,sqlacalchemy的对象、关联字典以及weakrefs所占的内存量减少了大约3.7兆兆,即46%:

# reported by heapy, summation of SQLAlchemy objects +
# associated dicts + weakref-related objects with core of Nova imported:

    Before: total count 26477 total bytes 7975712
    After: total count 18181 total bytes 4236456

# reported for the Python module space overall with the
# core of Nova imported:

    Before: Partition of a set of 355558 objects. Total size = 61661760 bytes.
    After: Partition of a set of 346034 objects. Total size = 57808016 bytes.

更新语句现在与executeMany()一起以刷新方式批处理

现在可以在ORM flush中将update语句批处理到更高性能的executemany()调用中,类似于如何对insert语句进行批处理;这将在flush中根据以下条件调用:

  • 顺序中的两个或多个UPDATE语句涉及要修改的相同列集。

  • 语句在set子句中没有嵌入的SQL表达式。

  • 映射不使用 mapper.version_id_col 或者后端方言支持executemany()操作的“健全”行数;大多数dbapis现在都正确地支持这一点。

session.get_bind()处理各种各样的继承方案

这个 Session.get_bind() 每当查询或工作单元刷新进程查找与特定类对应的数据库引擎时,都会调用方法。该方法已得到改进,可以处理各种面向继承的场景,包括:

  • 绑定到mixin或抽象类:

    class MyClass(SomeMixin, Base):
        __tablename__ = 'my_table'
        # ...
    
    session = Session(binds={SomeMixin: some_engine})
  • 基于表单独绑定到继承的具体子类:

    class BaseClass(Base):
        __tablename__ = 'base'
    
        # ...
    
    class ConcreteSubClass(BaseClass):
        __tablename__ = 'concrete'
    
        # ...
    
        __mapper_args__ = {'concrete': True}
    
    
    session = Session(binds={
        base_table: some_engine,
        concrete_table: some_other_engine
    })

#3035

session.get_bind()将在所有相关查询案例中接收映射器

修复了一系列问题, Session.get_bind() 无法接收主要邮件 MapperQuery ,即使此映射器随时可用(主映射器是单个映射器,或者是与 Query 对象)。

这个 Mapper 对象,当传递给 Session.get_bind() 通常由利用 Session.binds 参数将映射器与一系列引擎相关联(尽管在此用例中,由于绑定将通过映射表对象定位,因此在大多数情况下都经常“工作”),或者更具体地说,实现用户定义的 Session.get_bind() 提供基于映射器选择引擎的某种模式的方法,例如水平分片或将查询路由到不同后端的所谓“路由”会话。

这些场景包括:

  • Query.count() ::

    session.query(User).count()
  • Query.update()Query.delete() 对于更新/删除语句以及“fetch”策略使用的select:

    session.query(User).filter(User.id == 15).update(
            {"name": "foob"}, synchronize_session='fetch')
    
    session.query(User).filter(User.id == 15).delete(
            synchronize_session='fetch')
  • 对单个列的查询::

    session.query(User.id, User.name).all()
  • 针对间接映射的SQL函数和其他表达式,例如 column_property ::

    class User(Base):
        # ...
    
        score = column_property(func.coalesce(self.tables.users.c.name, None)))
    
    session.query(func.max(User.score)).scalar()

#3227 #3242 #1326

.信息字典改进

这个 InspectionAttr.info 集合现在可用于从 Mapper.all_orm_descriptors 收集。这包括 hybrid_propertyassociation_proxy() . 但是,由于这些对象是类绑定的描述符,因此必须访问它们 分别地 从它们附加到的类中获取属性。下面使用 Mapper.all_orm_descriptors 命名空间::

class SomeObject(Base):
    # ...

    @hybrid_property
    def some_prop(self):
        return self.value + 5


inspect(SomeObject).all_orm_descriptors.some_prop.info['foo'] = 'bar'

它也可以作为所有 SchemaItem 对象(例如) ForeignKeyUniqueConstraint 以及其他ORM结构,如 synonym() .

#2971

#2963

columnProperty构造在别名、排序方式等方面工作得更好

关于 column_property() 已经确定,尤其是关于 aliased() 构造以及0.9中引入的“按标签排序”逻辑(请参见 标签构造现在可以按顺序单独呈现为其名称

给出如下映射:

class A(Base):
    __tablename__ = 'a'

    id = Column(Integer, primary_key=True)

class B(Base):
    __tablename__ = 'b'

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


A.b = column_property(
        select([func.max(B.id)]).where(B.a_id == A.id).correlate(A)
    )

包含“a.b”两次的简单场景将无法正确呈现:

print(sess.query(A, a1).order_by(a1.b))

这将按错误的列排序:

SELECT a.id AS a_id, (SELECT max(b.id) AS max_1 FROM b
WHERE b.a_id = a.id) AS anon_1, a_1.id AS a_1_id,
(SELECT max(b.id) AS max_2
FROM b WHERE b.a_id = a_1.id) AS anon_2
FROM a, a AS a_1 ORDER BY anon_1

新输出:

SELECT a.id AS a_id, (SELECT max(b.id) AS max_1
FROM b WHERE b.a_id = a.id) AS anon_1, a_1.id AS a_1_id,
(SELECT max(b.id) AS max_2
FROM b WHERE b.a_id = a_1.id) AS anon_2
FROM a, a AS a_1 ORDER BY anon_2

还有许多场景,“order by”逻辑无法按标签排序,例如,如果映射是“多态的”::

class A(Base):
    __tablename__ = 'a'

    id = Column(Integer, primary_key=True)
    type = Column(String)

    __mapper_args__ = {'polymorphic_on': type, 'with_polymorphic': '*'}

订单将无法使用标签,因为它将由于多态加载而匿名:

SELECT a.id AS a_id, a.type AS a_type, (SELECT max(b.id) AS max_1
FROM b WHERE b.a_id = a.id) AS anon_1
FROM a ORDER BY (SELECT max(b.id) AS max_2
FROM b WHERE b.a_id = a.id)

现在,order by label跟踪匿名标签,现在可以工作了:

SELECT a.id AS a_id, a.type AS a_type, (SELECT max(b.id) AS max_1
FROM b WHERE b.a_id = a.id) AS anon_1
FROM a ORDER BY anon_1

这些修复中包含了各种可能破坏 aliased() 构造为使标记逻辑再次失败;这些也已被修复。

#3148 #3188

新功能和改进-核心

可以将select/query limit/offset指定为任意SQL表达式

这个 Select.limit()Select.offset() 方法现在接受除整数值之外的任何SQL表达式作为参数。奥姆 Query 对象还通过任何表达式传递给基础 Select 对象。通常,这用于允许传递绑定参数,稍后可以用值替换该参数::

sel = select([table]).limit(bindparam('mylimit')).offset(bindparam('myoffset'))

不支持非整数限制或偏移表达式的方言可能继续不支持此行为;第三方方言也可能需要修改才能利用新行为。当前使用的方言 ._limit._offset 对于那些将限制/偏移量指定为简单整数值的情况,属性将继续工作。但是,当指定SQL表达式时,这两个属性将改为引发 CompileError 访问。希望支持新功能的第三方方言现在应调用 ._limit_clause._offset_clause 属性来接收完整的SQL表达式,而不是整数值。

这个 use_alter 旗上 ForeignKeyConstraint 不再需要

这个 MetaData.create_all()MetaData.drop_all() 方法现在将使用一个系统,该系统自动为表之间相互依赖的循环中涉及的外键约束呈现alter语句,而无需指定 ForeignKeyConstraint.use_alter . 此外,为了通过alter创建,外键约束不再需要有名称;只有drop操作需要名称。在drop的情况下,该特性将确保只有具有显式名称的约束实际包含为alter语句。在液滴内出现不可溶解循环的情况下,如果液滴无法继续进行,系统会立即发出一条简洁清晰的错误消息。

这个 ForeignKeyConstraint.use_alterForeignKey.use_alter 标记保持在适当的位置,并继续具有建立那些在创建/删除场景期间需要更改的约束的相同效果。

从1.0.1版开始,特殊逻辑在不支持alter的sqlite的情况下接管,如果在删除过程中,给定的表有一个不可解析的循环;在这种情况下,会发出警告,并使用 no 排序,通常在sqlite上是可以的,除非启用了约束。要解决警告并继续至少对sqlite数据库进行部分排序,特别是在启用了约束的情况下,请对这些数据库重新应用“use-alter”标志。 ForeignKeyForeignKeyConstraint 应在排序中显式省略的对象。

参见

通过alter创建/删除外键约束 -新行为的完整描述。

#3282

结果代理“自动关闭”现在是“软”关闭

对于许多版本, ResultProxy 对象总是在提取所有结果行时自动关闭。这是为了允许在不需要调用的情况下使用对象 ResultProxy.close() 显式地;当所有DBAPI资源都被释放时,对象可以安全地丢弃。但是,对象保持严格的“关闭”行为,这意味着任何后续调用 ResultProxy.fetchone()ResultProxy.fetchmany()ResultProxy.fetchall() 现在会提高 ResourceClosedError ::

>>> result = connection.execute(stmt)
>>> result.fetchone()
(1, 'x')
>>> result.fetchone()
None  # indicates no more rows
>>> result.fetchone()
exception: ResourceClosedError

这种行为与PEP-249所说的不一致,即即使在结果用尽之后,也可以重复调用fetch方法。它还会干扰一些结果代理实现的行为,例如 BufferedColumnResultProxy 由cx-oracle方言用于某些数据类型。

为了解决这个问题,在 ResultProxy 已分为两种状态:一种“软关闭”,它执行“关闭”的大部分操作,即释放DBAPI光标,在“结果关闭”对象的情况下,也将释放连接,“关闭”状态是“软关闭”包括的所有内容,并将获取方法设置为“关闭”。这个 ResultProxy.close() 方法现在从不隐式调用,只调用 ResultProxy._soft_close() 非公共方法:

>>> result = connection.execute(stmt)
>>> result.fetchone()
(1, 'x')
>>> result.fetchone()
None  # indicates no more rows
>>> result.fetchone()
None  # still None
>>> result.fetchall()
[]
>>> result.close()
>>> result.fetchone()
exception: ResourceClosedError  # *now* it raises

#3330 #3329

检查约束现在支持 %(column_0_name)s 命名约定中的标记

这个 %(column_0_name)s 将从表达式中找到的第一列派生 CheckConstraint ::

metadata = MetaData(
    naming_convention={"ck": "ck_%(table_name)s_%(column_0_name)s"}
)

foo = Table('foo', metadata,
    Column('value', Integer),
)

CheckConstraint(foo.c.value > 5)

将呈现:

CREATE TABLE foo (
    value INTEGER,
    CONSTRAINT ck_foo_value CHECK (value > 5)
)

命名约定与由 SchemaTypeBooleanEnum 现在还将使用所有检查约束约定。

参见

命名检查约束

为布尔、枚举和其他架构类型配置命名

#3299

引用未连接列的约束在附加其引用列时可以自动附加到表

因为至少版本0.8,a Constraint 有能力“自动连接”到 Table 基于传递的表附加列:

from sqlalchemy import Table, Column, MetaData, Integer, UniqueConstraint

m = MetaData()

t = Table('t', m,
    Column('a', Integer),
    Column('b', Integer)
)

uq = UniqueConstraint(t.c.a, t.c.b)  # will auto-attach to Table

assert uq in t.constraints

为了帮助处理一些倾向于提出声明性的情况,即使在 Column 对象尚未与 Table ;确定其他事件,以便 Column 对象是关联的, Constraint 还增加了:

from sqlalchemy import Table, Column, MetaData, Integer, UniqueConstraint

m = MetaData()

a = Column('a', Integer)
b = Column('b', Integer)

uq = UniqueConstraint(a, b)

t = Table('t', m, a, b)

assert uq in t.constraints  # constraint auto-attached

以上功能是从1.0.0b3版开始的最新添加。从1.0.4版开始的修复 #3411 确保在 Constraint 指的是 Column 对象和字符串列名;因为我们还没有跟踪将名称添加到 Table ::

from sqlalchemy import Table, Column, MetaData, Integer, UniqueConstraint

m = MetaData()

a = Column('a', Integer)
b = Column('b', Integer)

uq = UniqueConstraint(a, 'b')

t = Table('t', m, a, b)

# constraint *not* auto-attached, as we do not have tracking
# to locate when a name 'b' becomes available on the table
assert uq not in t.constraints

上面,“A”列到“T”列的附加事件将在“B”列附加之前触发(如“A”所述 Table 构造函数位于“b”)之前,如果约束试图连接,它将无法定位“b”。为了一致性,如果约束引用任何字符串名称,则跳过列附加逻辑上的自动附加。

当然,如果 Table 已经包含所有目标 Column 对象的时间 Constraint 构造::

from sqlalchemy import Table, Column, MetaData, Integer, UniqueConstraint

m = MetaData()

a = Column('a', Integer)
b = Column('b', Integer)


t = Table('t', m, a, b)

uq = UniqueConstraint(a, 'b')

# constraint auto-attached normally as in older versions
assert uq in t.constraints

#3341 #3411

从select插入现在包括python和sql表达式默认值

Insert.from_select() 如果未指定,则现在包括python和sql表达式默认值;现在取消了从select插入时不包括非服务器列默认值的限制,并将这些表达式作为常量呈现到select语句中::

from sqlalchemy import Table, Column, MetaData, Integer, select, func

m = MetaData()

t = Table(
    't', m,
    Column('x', Integer),
    Column('y', Integer, default=func.somefunction()))

stmt = select([t.c.x])
print(t.insert().from_select(['x'], stmt))

将呈现:

INSERT INTO t (x, y) SELECT t.x, somefunction() AS somefunction_1
FROM t

可以使用禁用该功能 Insert.from_select.include_defaults .

列服务器默认值现在呈现文本值

DefaultClause ,由 Column.server_default 作为要编译的SQL表达式存在。这允许嵌入在SQL中的文本正确呈现,例如:

from sqlalchemy import Table, Column, MetaData, Text
from sqlalchemy.schema import CreateTable
from sqlalchemy.dialects.postgresql import ARRAY, array
from sqlalchemy.dialects import postgresql

metadata = MetaData()

tbl = Table("derp", metadata,
    Column("arr", ARRAY(Text),
                server_default=array(["foo", "bar", "baz"])),
)

print(CreateTable(tbl).compile(dialect=postgresql.dialect()))

现在呈现:

CREATE TABLE derp (
    arr TEXT[] DEFAULT ARRAY['foo', 'bar', 'baz']
)

以前,文字值 "foo", "bar", "baz" 将呈现为绑定参数,这在DDL中是无用的。

#3087

uniqueconstraint现在是表反射过程的一部分

A Table 对象填充使用 autoload=True 现在包括 UniqueConstraint 构造以及 Index 构造。这个逻辑对PostgreSQL和MySQL有一些警告:

《PostgreSQL》

PostgreSQL具有这样的行为:当创建唯一约束时,它也隐式地创建与该约束对应的唯一索引。这个 Inspector.get_indexes() 以及 Inspector.get_unique_constraints() 方法将继续 both 清楚地返回这些条目,其中 Inspector.get_indexes() 现在有一个代币 duplicates_constraint 在索引项中,指示检测到的相应约束。但是,当使用 Table(..., autoload=True) , the Index 检测到构造链接到 UniqueConstraintnot 存在于 Table.indexes 收藏;只有 UniqueConstraint 将出现在 Table.constraints 收集。此重复数据消除逻辑通过加入 pg_constraint 查询时的表 pg_index 查看这两个结构是否链接。

MySQL

对于唯一索引和唯一约束,MySQL没有单独的概念。虽然它在创建表和索引时同时支持两种语法,但它不会以任何不同的方式存储它们。这个 Inspector.get_indexes() 以及 Inspector.get_unique_constraints() 方法将继续 both 返回MySQL中唯一索引的条目,其中 Inspector.get_unique_constraints() 提供新的令牌 duplicates_index 在约束项中,指示这是对应于该索引的重复项。但是,当使用 Table(..., autoload=True) , the UniqueConstraint 构造是 not 完全反射的部分 Table 在任何情况下构造;此构造始终由 Indexunique=True 设置存在于 Table.indexes 收集。

参见

PostgreSQL索引反射

MySQL/MariaDB独特的约束和反射

#3184

安全地发出参数化警告的新系统

很长一段时间以来,有一个限制,警告消息不能引用数据元素,这样一个特定的函数可能会发出无限数量的唯一警告。发生这种情况的关键位置是 Unicode type received non-unicode bind param value 警告。在这个消息中放置数据值意味着python __warningregistry__ 对于该模块,或者在某些情况下,对于python全局 warnings.onceregistry ,将无限增长,在大多数警告场景中,这两个集合中的一个将填充每个不同的警告消息。

这里的变化是通过使用特殊的 string 类型有目的地更改字符串的散列方式,我们可以控制大量参数化消息只对一小组可能的散列值进行散列,例如 Unicode type received non-unicode bind param value 可以调整为只发出特定次数;除此之外,python warnings注册表将开始将它们记录为重复项。

为了说明这一点,下面的测试脚本将只显示10个参数集(总共1000个)发出的警告:

from sqlalchemy import create_engine, Unicode, select, cast
import random
import warnings

e = create_engine("sqlite://")

# Use the "once" filter (which is also the default for Python
# warnings).  Exactly ten of these warnings will
# be emitted; beyond that, the Python warnings registry will accumulate
# new values as dupes of one of the ten existing.
warnings.filterwarnings("once")

for i in range(1000):
    e.execute(select([cast(
        ('foo_%d' % random.randint(0, 1000000)).encode('ascii'), Unicode)]))

此处警告的格式为:

/path/lib/sqlalchemy/sql/sqltypes.py:186: SAWarning: Unicode type received
  non-unicode bind param value 'foo_4852'. (this warning may be
  suppressed after 10 occurrences)

#3178

关键行为变化-ORM

query.update()现在将字符串名称解析为映射的属性名称

文件 Query.update() 说明 values 字典是“一个以属性名为键的字典”,这意味着它们是映射的属性名。不幸的是,该函数的设计更多的是为了接收属性和SQL表达式,而不是像传递字符串那样多;当传递字符串时,这些字符串将直接传递到核心更新语句,而不需要任何解析,只要这些名称在映射类上的表示方式,这意味着名称必须与表列的T,而不是该名称的属性如何映射到类。

现在,字符串名称认真地解析为属性名称::

class User(Base):
    __tablename__ = 'user'

    id = Column(Integer, primary_key=True)
    name = Column('user_name', String(50))

上面,柱子 user_name 被映射为 name . 以前,打电话给 Query.update() 传递的字符串必须按如下方式调用:

session.query(User).update({'user_name': 'moonbeam'})

现在根据实体解析给定字符串:

session.query(User).update({'name': 'moonbeam'})

通常,最好直接使用属性,以避免任何模糊性:

session.query(User).update({User.name: 'moonbeam'})

更改还指示同义词和混合属性也可以由字符串名称引用::

class User(Base):
    __tablename__ = 'user'

    id = Column(Integer, primary_key=True)
    name = Column('user_name', String(50))

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

session.query(User).update({'fullname': 'moonbeam'})

#3228

将没有值的对象与关系进行比较时发出警告

此更改自1.0.1起是新的。一些用户正在执行本质上属于此表单的查询:

session.query(Address).filter(Address.user == User(id=None))

sqlacalchemy当前不支持此模式。对于所有版本,它发出类似以下内容的SQL:

SELECT address.id AS address_id, address.user_id AS address_user_id,
address.email_address AS address_email_address
FROM address WHERE ? = address.user_id
(None,)

注意,上面有一个比较 WHERE ? = address.user_id 其中,绑定值 ? 正在接收 NoneNULL 在SQL中。 这在SQL中总是返回false . 理论上,这里的比较将生成以下SQL:

SELECT address.id AS address_id, address.user_id AS address_user_id,
address.email_address AS address_email_address
FROM address WHERE address.user_id IS NULL

但现在, 它不 . 依赖“空=空”这一事实的应用程序在所有情况下都会产生错误的风险,有一天,SQLAlchemy可能会修复此问题以生成“空”,然后查询将产生不同的结果。因此,通过这种操作,您将看到一个警告:

SAWarning: Got None for value of column user.id; this is unsupported
for a relationship comparison and will not currently produce an
IS comparison (but may in a future release)

注意,在大多数情况下,这个模式在1.0.0版中都被破坏了,包括所有的beta;一个类似 SYMBOL('NEVER_SET') 将生成。这个问题已经得到了解决,但是由于识别了这个模式,现在警告就出现了,这样我们就可以更安全地修复这个被破坏的行为(现在在 #3373 )在未来的版本中。

#3371

“negated contains or equals”关系比较将使用属性的当前值,而不是数据库值

从1.0.1开始,这个变化是新的;虽然我们希望它是1.0.0,但它只是由于 #3371 .

给定映射:

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

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

鉴于 A ,主键为7,但在不刷新的情况下改为10:

s = Session(autoflush=False)
a1 = A(id=7)
s.add(a1)
s.commit()

a1.id = 10

针对将此对象作为目标的多对一关系的查询将使用绑定参数中的值10::

s.query(B).filter(B.a == a1)

生产::

SELECT b.id AS b_id, b.a_id AS b_a_id
FROM b
WHERE ? = b.a_id
(10,)

然而,在这一变化之前,否定这一标准将 not 使用10,它将使用7,除非先刷新对象:

s.query(B).filter(B.a != a1)

生成(在0.9和1.0.1之前的所有版本中)::

SELECT b.id AS b_id, b.a_id AS b_a_id
FROM b
WHERE b.a_id != ? OR b.a_id IS NULL
(7,)

对于临时对象,它将生成一个中断的查询::

SELECT b.id, b.a_id
FROM b
WHERE b.a_id != :a_id_1 OR b.a_id IS NULL
{u'a_id_1': symbol('NEVER_SET')}

在本例中,已经修复了这种不一致性,并且在所有查询中都使用了当前属性值 10 ,将立即使用。

#3374

对属性事件的更改以及与没有预先存在值的属性相关的其他操作

在此更改中,默认返回值为 None 访问对象时,现在在每次访问时都动态返回,而不是在首次访问时使用特殊的“set”操作隐式设置属性的状态。这种变化的明显结果是 obj.__dict__ 不是在get上隐式修改的,并且还有一些 get_history() 以及相关功能。

给定一个没有状态的对象:

>>> obj = Foo()

它始终是SQLAlchemy的行为,因此如果我们访问一个从未设置过的标量或多对一属性,它将返回为 None ::

>>> obj.someattr
None

这个值 None 实际上现在是 obj ,并且不像我们已经显式地设置了属性,例如 obj.someattr = None . 但是,这里的“set on get”在历史和事件方面的表现会有所不同。它不会发出任何属性事件,另外,如果我们查看历史记录,我们会看到:

>>> inspect(obj).attrs.someattr.history
History(added=(), unchanged=[None], deleted=())   # 0.9 and below

也就是说,就好像属性总是 None 从未改变过。这与我们先设置属性时明显不同:

>>> obj = Foo()
>>> obj.someattr = None
>>> inspect(obj).attrs.someattr.history
History(added=[None], unchanged=(), deleted=())  # all versions

上面的意思是,我们的“set”操作的行为可能会被之前通过“get”访问该值的事实所破坏。在1.0中,这种不一致性已经得到了解决,当使用默认的“getter”时不再实际设置任何内容。

>>> obj = Foo()
>>> obj.someattr
None
>>> inspect(obj).attrs.someattr.history
History(added=(), unchanged=(), deleted=())  # 1.0
>>> obj.someattr = None
>>> inspect(obj).attrs.someattr.history
History(added=[None], unchanged=(), deleted=())

上述行为没有太大影响的原因是关系数据库中的insert语句在大多数情况下认为缺少的值与空值相同。对于设置为“无”或“不”的特定属性,SQLAlchemy是否接收到历史事件通常无关紧要;因为发送“无”或“不”之间的差异不会产生影响。然而,作为 #3060 (此处介绍 关系绑定属性与fk绑定属性的属性更改优先级可能会发生更改。 )举例说明,我们确实希望在一些很少边缘的情况下 None 集合。此外,在这里允许属性事件意味着现在可以为ORM映射的属性创建“默认值”函数。

作为此更改的一部分,隐式“none”的生成现在在以前发生这种情况的其他情况下被禁用;这包括当接收到多对一的属性集操作时;以前,如果不进行其他设置,“old”值将为“none”;现在它将发送该值 NEVER_SET ,该值现在可以发送到属性侦听器。调用mapper实用程序函数(如 Mapper.primary_key_from_instance() ;如果主键属性完全没有设置,则值为 None 以前,现在是 NEVER_SET 符号,不会更改对象的状态。

#3061

关系绑定属性与fk绑定属性的属性更改优先级可能会发生更改。

作为…的副作用 #3060 ,将关系绑定属性设置为 None 现在是一个跟踪的历史事件,它指的是持续的意图 None 到那个属性。由于一直以来,设置一个关系绑定属性将胜过直接分配给外键属性,因此在这里,当不分配任何属性时,可以看到行为的变化。给定映射:

class A(Base):
    __tablename__ = 'table_a'

    id = Column(Integer, primary_key=True)

class B(Base):
    __tablename__ = 'table_b'

    id = Column(Integer, primary_key=True)
    a_id = Column(ForeignKey('table_a.id'))
    a = relationship(A)

在1.0中,关系绑定属性在任何情况下都优先于FK绑定属性,无论我们分配的值是否引用 A 对象还是 None . 在0.9中,行为是不一致的,只有在分配了值时才会生效;不考虑无:

a1 = A(id=1)
a2 = A(id=2)
session.add_all([a1, a2])
session.flush()

b1 = B()
b1.a = a1   # we expect a_id to be '1'; takes precedence in 0.9 and 1.0

b2 = B()
b2.a = None  # we expect a_id to be None; takes precedence only in 1.0

b1.a_id = 2
b2.a_id = 2

session.add_all([b1, b2])
session.commit()

assert b1.a is a1  # passes in both 0.9 and 1.0
assert b2.a is None  # passes in 1.0, in 0.9 it's a2

#3060

session.expunge()将完全分离已删除的对象

行为 Session.expunge() 有一个bug,导致有关已删除对象的行为不一致。这个 object_session() 功能以及 InstanceState.session 属性仍会将对象报告为属于 Session 删除之后:

u1 = sess.query(User).first()
sess.delete(u1)

sess.flush()

assert u1 not in sess
assert inspect(u1).session is sess  # this is normal before commit

sess.expunge(u1)

assert u1 not in sess
assert inspect(u1).session is None  # would fail

注意这是正常的 u1 not in sessinspect(u1).session 仍然是指会话,而事务在删除操作之后仍在进行,并且 Session.expunge() 尚未调用;完全分离通常在提交事务后完成。这个问题也会影响到依赖 Session.expunge()make_transient() .

#3139

显式不允许使用yield-per进行joined/subquery预加载

为了使 Query.yield_per() 方法更易于使用,如果使用yield-per时,任何子查询抢先加载程序或将使用集合的联接抢先加载程序生效,则会引发异常,因为这些程序当前与yield-per不兼容(但是,子查询加载在理论上可能是这样)。当出现此错误时, lazyload() 选项可以用星号发送:

q = sess.query(Object).options(lazyload('*')).yield_per(100)

或使用 Query.enable_eagerloads() ::

q = sess.query(Object).enable_eagerloads(False).yield_per(100)

这个 lazyload() 选项的优点是,还可以使用附加的多对一联接加载器选项:

q = sess.query(Object).options(
    lazyload('*'), joinedload("some_manytoone")).yield_per(100)

处理重复联接目标时的更改和修复

此处的更改包括错误,在某些情况下,当两次联接到一个实体或针对同一个表的多个单表实体时,如果不使用基于子句的关系,以及当多次联接到同一目标关系时,会发生意外和不一致的行为。

从以下映射开始:

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

Base = declarative_base()

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

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

联接到的查询 A.bs 两次::

print(s.query(A).join(A.bs).join(A.bs))

将呈现:

SELECT a.id AS a_id
FROM a JOIN b ON a.id = b.a_id

查询将消除冗余的 A.bs 因为它正试图支持如下案例:

s.query(A).join(A.bs).\
    filter(B.foo == 'bar').\
    reset_joinpoint().join(A.bs, B.cs).filter(C.bar == 'bat')

也就是说, A.bs 是“路径”的一部分。作为一部分 #3367 ,如果两次到达同一个端点而不作为较大路径的一部分,则现在将发出警告::

SAWarning: Pathed join target A.bs has already been joined to; skipping

更大的变化涉及到在不使用关系绑定路径的情况下加入实体。如果我们加入 B 两次::

print(s.query(A).join(B, B.a_id == A.id).join(B, B.a_id == A.id))

在0.9中,这将呈现如下:

SELECT a.id AS a_id
FROM a JOIN b ON b.a_id = a.id JOIN b AS b_1 ON b_1.a_id = a.id

这是有问题的,因为别名是隐式的,在不同的on子句的情况下,可能会导致不可预知的结果。

在1.0中,没有应用自动别名,我们得到:

SELECT a.id AS a_id
FROM a JOIN b ON b.a_id = a.id JOIN b ON b.a_id = a.id

这将从数据库中引发错误。如果“重复连接目标”的行为是相同的,那么当我们从冗余关系和基于冗余非关系的目标中连接时,可能会很好,但目前我们只在更严重的情况下更改行为,在这种情况下,隐式别名会发生在以前,并且只在关系情况下发出警告。最终,在所有情况下,如果两次连接到同一个对象而没有任何消除歧义的别名,则会导致错误。

更改也会影响单表继承目标。使用如下映射:

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

Base = declarative_base()

class A(Base):
    __tablename__ = "a"

    id = Column(Integer, primary_key=True)
    type = Column(String)

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


class ASub1(A):
    __mapper_args__ = {'polymorphic_identity': 'asub1'}


class ASub2(A):
    __mapper_args__ = {'polymorphic_identity': 'asub2'}


class B(Base):
    __tablename__ = 'b'

    id = Column(Integer, primary_key=True)

    a_id = Column(Integer, ForeignKey("a.id"))

    a = relationship("A", primaryjoin="B.a_id == A.id", backref='b')

s = Session()

print(s.query(ASub1).join(B, ASub1.b).join(ASub2, B.a))

print(s.query(ASub1).join(B, ASub1.b).join(ASub2, ASub2.id == B.a_id))

底部的两个查询是等效的,都应呈现相同的SQL::

SELECT a.id AS a_id, a.type AS a_type
FROM a JOIN b ON b.a_id = a.id JOIN a ON b.a_id = a.id AND a.type IN (:type_1)
WHERE a.type IN (:type_2)

上面的SQL是无效的,因为它两次呈现“a”在“from”列表中。但是,隐式别名错误只会出现在第二个查询中,并将其呈现为:

SELECT a.id AS a_id, a.type AS a_type
FROM a JOIN b ON b.a_id = a.id JOIN a AS a_1
ON a_1.id = b.a_id AND a_1.type IN (:type_1)
WHERE a_1.type IN (:type_2)

在上面的位置,第二个到“a”的连接是别名。虽然这看起来很方便,但单继承查询的工作方式并不是一般的,它具有误导性和不一致性。

最终的结果是依赖这个bug的应用程序现在会有一个由数据库引发的错误。解决方案是使用预期的形式。在查询中引用单个继承实体的多个子类时,必须手动使用别名来消除表的歧义,因为所有子类通常都引用同一个表::

asub2_alias = aliased(ASub2)

print(s.query(ASub1).join(B, ASub1.b).join(asub2_alias, B.a.of_type(asub2_alias)))

#3233 #3367

延迟列不再隐式取消传递

标记为deferred而没有显式取消引用的映射属性现在将保持“deferred”,即使它们的列以某种方式存在于结果集中。这是一种性能增强,因为ORM负载在获得结果集时不再花时间搜索每个延迟的列。但是,对于一直依赖于此的应用程序,显式 undefer() 或者现在应该使用类似的选项,以防止在访问属性时发出选择。

已删除不推荐使用的ORM事件挂钩

已删除以下ORM事件挂钩,其中一些自0.5以来已被弃用: translate_rowpopulate_instanceappend_resultcreate_instance . 这些钩子的用例起源于早期的0.1/0.2系列的sqlAlchemy,并且很久以来都是不必要的。特别是,钩子在很大程度上是不可用的,因为这些事件中的行为契约与周围的内部结构紧密相连,例如需要如何创建和初始化实例,以及列如何位于ORM生成的行中。这些挂钩的拆卸大大简化了ORM物体加载的力学过程。

使用自定义行加载器时,新捆绑功能的API更改

新的 Bundlecreate_row_processor() 方法在自定义类上被重写。以前,示例代码如下所示:

from sqlalchemy.orm import Bundle

class DictBundle(Bundle):
    def create_row_processor(self, query, procs, labels):
        """Override create_row_processor to return values as dictionaries"""
        def proc(row, result):
            return dict(
                        zip(labels, (proc(row, result) for proc in procs))
                    )
        return proc

未使用的 result 成员现在被删除::

from sqlalchemy.orm import Bundle

class DictBundle(Bundle):
    def create_row_processor(self, query, procs, labels):
        """Override create_row_processor to return values as dictionaries"""
        def proc(row):
            return dict(
                        zip(labels, (proc(row) for proc in procs))
                    )
        return proc

参见

列束

右内部联接嵌套现在是joinedLoad的默认值,inner join=true

行为 joinedload.innerjoin 以及 relationship.innerjoin 现在要使用“嵌套的”内部联接(即右嵌套)作为内部联接联接联接的预加载链接到外部联接的预加载时的默认行为。为了获得在存在外部联接时将所有联接的热切负载作为外部联接链接的旧行为,请使用 innerjoin="unnested" .

如中所述 右嵌套的内部联接在联接的热切加载中可用 从0.9版开始, innerjoin="nested" 是指链接到外部连接的内部连接热切负载将使用正确的嵌套连接。 "nested" 现在在使用时暗示 innerjoin=True ::

query(User).options(
    joinedload("orders", innerjoin=False).joinedload("items", innerjoin=True))

使用新的默认值,这将以如下形式呈现FROM子句:

FROM users LEFT OUTER JOIN (orders JOIN items ON <onclause>) ON <onclause>

也就是说,对内部联接使用正确的嵌套联接,以便 users 可以退货。使用内部联接比使用外部联接更有效,并且允许 joinedload.innerjoin 在所有情况下生效的优化参数。

要获得旧行为,请使用 innerjoin="unnested" ::

query(User).options(
    joinedload("orders", innerjoin=False).joinedload("items", innerjoin="unnested"))

这将避免正确的嵌套联接,并使用所有外部联接将联接链接在一起,尽管innerjoin指令为:

FROM users LEFT OUTER JOIN orders ON <onclause> LEFT OUTER JOIN items ON <onclause>

如0.9注释中所述,唯一难以正确嵌套联接的数据库后端是sqlite;从0.9开始的sqlachemy将正确嵌套联接转换为子查询,作为sqlite上的联接目标。

参见

右嵌套的内部联接在联接的热切加载中可用 -0.9.4中介绍的功能说明。

#3008

子查询不再应用于useList=false joined-earch-loads

给定一个像下面这样的连接的渴望负载:

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


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

s = Session()
print(s.query(A).options(joinedload(A.b)).limit(5))

sqlAlchemy考虑了这种关系 A.b “一对多,加载为单个值”,这本质上是一种“一对一”关系。但是,Joined Earge Loading始终将上述情况视为主查询需要位于子查询中的情况,就像主查询有限制的B对象集合通常需要的情况一样:

SELECT anon_1.a_id AS anon_1_a_id, b_1.id AS b_1_id, b_1.a_id AS b_1_a_id
FROM (SELECT a.id AS a_id
FROM a LIMIT :param_1) AS anon_1
LEFT OUTER JOIN b AS b_1 ON anon_1.a_id = b_1.a_id

但是,由于内部查询与外部查询的关系是,在 uselist=False (与多对一的方式相同),在这种情况下,与limit+joined earchy loading一起使用的“子查询”现在被删除:

SELECT a.id AS a_id, b_1.id AS b_1_id, b_1.a_id AS b_1_a_id
FROM a LEFT OUTER JOIN b AS b_1 ON a.id = b_1.a_id
LIMIT :param_1

在左外部联接返回多行的情况下,ORM始终在此处发出警告并忽略 uselist=False ,因此导致该错误情况不应改变。

#3249

query.update()/query.delete()如果与join()一起使用,则会引发,请从u self()中选择u from()。

Query.update()Query.delete() 方法是针对已调用的查询调用的 Query.join()Query.outerjoin()Query.select_from()Query.from_self() . 这些都是不受支持的用例,在0.9.10之前的0.9系列中,这些用例会无声地失败,并在其中发出警告。在1.0中,这些情况会引发异常。

#3349

query.update()使用 synchronize_session='evaluate' 在多表更新时引发

的“评估者” Query.update() 无法使用多表更新,需要设置为 synchronize_session=Falsesynchronize_session='fetch' 当存在多个表时。新的行为是现在引发一个显式异常,并显示一条消息来更改同步设置。这是从0.9.7发出的警告升级而来的。

#3117

复活事件已被删除

“复活”ORM事件已被完全删除。自0.8版从工作单元中删除旧的“可变”系统以来,此事件不再具有任何功能。

使用from_self(),count()时更改为单表继承条件

给定单个表继承映射,例如:

class Widget(Base):
    __table__ = 'widget_table'

class FooWidget(Widget):
    pass

使用 Query.from_self()Query.count() 对于子类,将生成子查询,但随后将子类的“Where”条件添加到外部:

sess.query(FooWidget).from_self().all()

致使::

SELECT
    anon_1.widgets_id AS anon_1_widgets_id,
    anon_1.widgets_type AS anon_1_widgets_type
FROM (SELECT widgets.id AS widgets_id, widgets.type AS widgets_type,
FROM widgets) AS anon_1
WHERE anon_1.widgets_type IN (?)

问题在于,如果内部查询没有指定所有列,那么我们就不能在外部添加WHERE子句(它实际上会尝试,并产生一个错误的查询)。这一决定显然可以追溯到0.6.5,并注明“可能需要对此作出更多调整”。好吧,这些调整已经到了!所以现在上面的查询将呈现:

SELECT
    anon_1.widgets_id AS anon_1_widgets_id,
    anon_1.widgets_type AS anon_1_widgets_type
FROM (SELECT widgets.id AS widgets_id, widgets.type AS widgets_type,
FROM widgets
WHERE widgets.type IN (?)) AS anon_1

因此,不包含“type”的查询仍然有效!::

sess.query(FooWidget.id).count()

渲染:

SELECT count(*) AS count_1
FROM (SELECT widgets.id AS widgets_id
FROM widgets
WHERE widgets.type IN (?)) AS anon_1

#3177

无条件添加到所有ON子句的单表继承条件

当连接到单表继承子类目标时,ORM总是在连接关系时添加“单表条件”。给定映射为:

class Widget(Base):
    __tablename__ = 'widget'
    id = Column(Integer, primary_key=True)
    type = Column(String)
    related_id = Column(ForeignKey('related.id'))
    related = relationship("Related", backref="widget")
    __mapper_args__ = {'polymorphic_on': type}


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


class Related(Base):
    __tablename__ = 'related'
    id = Column(Integer, primary_key=True)

在相当长的一段时间内,关系上的联接将为类型呈现“single inheritance”子句,这是一种行为:

s.query(Related).join(FooWidget, Related.widget).all()

SQL输出:

SELECT related.id AS related_id
FROM related JOIN widget ON related.id = widget.related_id AND widget.type IN (:type_1)

因为我们加入了一个子类 FooWidgetQuery.join() 知道添加 AND widget.type IN ('foo') ON条款的标准。

这里的零钱是 AND widget.type IN() 条件现在附加到 any on子句,而不仅仅是由关系生成的,包括明确声明的:

# ON clause will now render as
# related.id = widget.related_id AND widget.type IN (:type_1)
s.query(Related).join(FooWidget, FooWidget.related_id == Related.id).all()

以及“隐式”联接,当没有任何类型的ON子句时:

# ON clause will now render as
# related.id = widget.related_id AND widget.type IN (:type_1)
s.query(Related).join(FooWidget).all()

以前,这些的ON子句不包括单个继承条件。已经在添加此条件以解决此问题的应用程序将希望删除其显式使用,但如果同时呈现两次此条件,它将继续正常工作。

参见

处理重复联接目标时的更改和修复

#3222

关键行为变化-核心

将完整的SQL片段强制转换为文本()时发出警告

自从sqlacalchemy诞生以来,一直强调不要妨碍纯文本的使用。核心和ORM表达式系统旨在允许用户使用纯文本SQL表达式的任意数量的点,而不仅仅是在您可以向其发送完整的SQL字符串的意义上。 Connection.execute() ,但可以将带有SQL表达式的字符串发送到许多函数中,例如 Select.where()Query.filter()Select.order_by() .

注意,“SQL表达式”是指 SQL字符串的完整片段 ,例如:

# the argument sent to where() is a full SQL expression
stmt = select([sometable]).where("somecolumn = 'value'")

我们是 不谈论字符串参数 也就是说,传递变为参数化的字符串值的正常行为:

# This is a normal Core expression with a string argument -
# we aren't talking about this!!
stmt = select([sometable]).where(sometable.c.somecolumn == 'value')

核心教程中有一个使用此技术的示例,使用 select() 构造它的几乎所有组件都被指定为直字符串。然而,尽管这种长期存在的行为和例子,用户显然对这种行为的存在感到惊讶,当我在社区中询问时,我无法找到任何实际上是 not 很惊讶您可以将一个完整的字符串发送到类似 Query.filter() .

因此,这里的更改是鼓励用户在组成部分或完全由文本片段组成的SQL时限定文本字符串。按如下方式组合选择时:

stmt = select(["a", "b"]).where("a = b").select_from("sometable")

语句是正常构建的,具有与以前相同的强制。但是,您将看到发出以下警告:

SAWarning: Textual column expression 'a' should be explicitly declared
with text('a'), or use column('a') for more specificity
(this warning may be suppressed after 10 occurrences)

SAWarning: Textual column expression 'b' should be explicitly declared
with text('b'), or use column('b') for more specificity
(this warning may be suppressed after 10 occurrences)

SAWarning: Textual SQL expression 'a = b' should be explicitly declared
as text('a = b') (this warning may be suppressed after 10 occurrences)

SAWarning: Textual SQL FROM expression 'sometable' should be explicitly
declared as text('sometable'), or use table('sometable') for more
specificity (this warning may be suppressed after 10 occurrences)

这些警告试图通过显示参数以及接收字符串的位置来准确显示问题所在。警告使用 session.get_bind()处理各种各样的继承方案 这样,参数化的警告就可以安全地发出,而不会耗尽内存,并且一如既往,如果希望警告是异常的,那么 Python Warnings Filter 应使用:

import warnings
warnings.simplefilter("error")   # all warnings raise an exception

鉴于上述警告,我们的语句工作得很好,但为了消除这些警告,我们将重写语句如下:

from sqlalchemy import select, text
stmt = select([
        text("a"),
        text("b")
    ]).where(text("a = b")).select_from(text("sometable"))

如警告所示,如果我们使用 column()table() ::

from sqlalchemy import select, text, column, table

stmt = select([column("a"), column("b")]).\
    where(text("a = b")).select_from(table("sometable"))

其中还要注意 table()column() 现在可以从“sqlacalchemy”导入,而不需要“sql”部分。

这里的行为适用于 select() 以及关键方法 Query 包括 Query.filter()Query.from_statement()Query.having() .

order by和group by是特殊情况

有一种情况下,字符串的使用具有特殊意义,作为这一更改的一部分,我们增强了它的功能。当我们拥有 select()Query 指的是某个列名或命名标签,我们可能希望按已知列或标签分组和/或排序:

stmt = select([
    user.c.name,
    func.count(user.c.id).label("id_count")
]).group_by("name").order_by("id_count")

在上面的语句中,我们希望看到“order by id_count”,而不是函数的重新语句。在编译期间,给定的字符串参数与columns子句中的一个条目主动匹配,因此上面的语句将按预期生成,而不会出现警告(不过请注意 "name" 表达式已解析为 users.name !)::

SELECT users.name, count(users.id) AS id_count
FROM users GROUP BY users.name ORDER BY id_count

但是,如果我们引用了一个无法定位的名称,那么我们会再次收到警告,如下所示:

stmt = select([
        user.c.name,
        func.count(user.c.id).label("id_count")
    ]).order_by("some_label")

输出按我们所说的做,但它再次警告我们:

SAWarning: Can't resolve label reference 'some_label'; converting to
text() (this warning may be suppressed after 10 occurrences)

SELECT users.name, count(users.id) AS id_count
FROM users ORDER BY some_label

上述行为适用于我们可能希望引用所谓“标签引用”的所有位置;ORDER BY和GROUP BY,但也适用于over子句以及引用列(例如PostgreSQL语法)的DISTINCT ON子句。

我们仍然可以使用 text() ::

stmt = select([users]).order_by(text("some special expression"))

整个更改的结果是,sqlAlchemy现在希望我们在发送字符串时告诉它,此字符串显式是 text() 构造或列、表等,如果我们使用它作为ORDER BY、GROUP BY或其他表达式中的标签名称,则sqlAlchemy希望该字符串解析为已知的内容,否则它应再次使用限定符 text() 或者类似的。

#2992

当使用多值插入时,为每行单独调用python端默认值

在使用多值版本的 Insert.values() 在使用的方言使用绑定参数的非位置(如命名)样式时,以及在不需要为每行调用python端可调用时,它们基本上没有实现,只能在特定情况下“偶然”工作。

已对该功能进行了全面检查,使其工作起来更类似于“ExecuteMany”类型的调用:

import itertools

counter = itertools.count(1)
t = Table(
    'my_table', metadata,
    Column('id', Integer, default=lambda: next(counter)),
    Column('data', String)
)

conn.execute(t.insert().values([
    {"data": "d1"},
    {"data": "d2"},
    {"data": "d3"},
]))

上面的示例将调用 next(counter) 对于每一行,如预期的那样:

INSERT INTO my_table (id, data) VALUES (?, ?), (?, ?), (?, ?)
(1, 'd1', 2, 'd2', 3, 'd3')

以前,位置方言将失败,因为不会为其他位置生成绑定::

Incorrect number of bindings supplied. The current statement uses 6,
and there are 4 supplied.
[SQL: u'INSERT INTO my_table (id, data) VALUES (?, ?), (?, ?), (?, ?)']
[parameters: (1, 'd1', 'd2', 'd3')]

对于“命名”方言,“id”的相同值将在每行中重复使用(因此,此更改与依赖于此的系统向后不兼容)::

INSERT INTO my_table (id, data) VALUES (:id, :data_0), (:id, :data_1), (:id, :data_2)
{u'data_2': 'd3', u'data_1': 'd2', u'data_0': 'd1', 'id': 1}

系统还将拒绝调用“服务器端”默认值作为内联呈现的SQL,因为不能保证服务器端默认值与此兼容。如果values子句呈现特定列,则需要python-side值;如果省略的值只引用服务器端默认值,则引发异常:

t = Table(
    'my_table', metadata,
    Column('id', Integer, primary_key=True),
    Column('data', String, server_default='some default')
)

conn.execute(t.insert().values([
    {"data": "d1"},
    {"data": "d2"},
    {},
]))

将提高:

sqlalchemy.exc.CompileError: INSERT value for column my_table.data is
explicitly rendered as a boundparameter in the VALUES clause; a
Python-side value or SQL expression is required

以前,值“d1”将被复制到第三行的值中(但同样,只使用命名格式!)::

INSERT INTO my_table (data) VALUES (:data_0), (:data_1), (:data_0)
{u'data_1': 'd2', u'data_0': 'd1'}

#3288

无法在该事件的运行程序中添加或删除事件侦听器

从同一事件本身内部删除事件侦听器将在迭代过程中修改列表的元素,这将导致仍然附加的事件侦听器静默地无法激发。为了在保持性能的同时防止出现这种情况,列表已替换为 collections.deque() ,它不允许在迭代期间进行任何添加或删除,而是引发 RuntimeError .

#3163

insert…from select结构现在意味着 inline=True

使用 Insert.from_select() 现在暗示 inline=Trueinsert() . 这有助于修复这样一个错误:在支持后端,insert…from select构造无意中被编译为“隐式返回”,在插入零行(隐式返回需要行)的情况下,这将导致中断;在插入多行(例如,插入)的情况下,这将导致任意返回数据。只有第一排)。类似的更改也应用于具有多个参数集的insert..values;隐式返回也不再为此语句发出。由于这两个结构都处理可变行数,因此 ResultProxy.inserted_primary_key 访问器不适用。以前,有一个文档说明,人们可能更喜欢 inline=True 由于某些数据库不支持返回,因此不能执行“隐式”返回,因此使用insert..from select,但在任何情况下,insert…from select都不需要隐式返回。正则显式 Insert.returning() 如果需要插入数据,则应用于返回结果行的可变数目。

#3169

autoload_with now implies autoload=True

A Table 可以设置为通过反射 Table.autoload_with 单独::

my_table = Table('my_table', metadata, autoload_with=some_engine)

#3027

DBAPI异常包装和处理_Error()事件改进

Connection 对象无效,然后尝试重新连接并遇到错误;已解决此问题。

此外,最近增加了 ConnectionEvents.handle_error() 现在,对于在初始连接、重新连接以及何时发生的错误调用事件。 create_engine() 通过提供自定义连接函数 create_engine.creator .

这个 ExceptionContext 对象具有新的数据成员 ExceptionContext.engine 这总是指 Engine 在使用中,在这些情况下, Connection 对象不可用(例如,在初始连接时)。

#3266

ForeignKeyConstraint.Columns现在是ColumnCollection

ForeignKeyConstraint.columns 以前是包含字符串或 Column 对象,取决于 ForeignKeyConstraint 是否与表关联。该系列现在是 ColumnCollection ,并且仅在 ForeignKeyConstraintTable . 一种新的存取器 ForeignKeyConstraint.column_keys 添加为无条件返回本地列集的字符串键,而不管对象是如何构造的或其当前状态如何。

metadata.sorted_tables访问器是“确定性的”

对由 MetaData.sorted_tables 访问器是“确定性的”;在任何情况下,顺序都应该相同,而不考虑Python散列。这是通过在将表传递给拓扑算法之前先按名称对表进行排序来完成的,拓扑算法在迭代时保持这种排序。

注意,这个变化确实 not 但适用于发出时应用的顺序 MetaData.create_all()MetaData.drop_all() .

#3084

空()、假()和真()常量不再是单例

这三个常量被更改为在0.9中返回“singleton”值;不幸的是,这将导致如下查询无法按预期呈现:

select([null(), null()])

仅渲染 SELECT NULL AS anon_1 因为这两个 null() 构造将以相同的形式出现 NULL 对象,而SQLAlchemy的核心模型是基于对象标识来确定词汇意义的。0.9中的更改除了节省对象开销之外没有其他重要意义;一般来说,未命名的构造需要保持在词汇上的唯一性,以便得到唯一的标记。

#3170

对于临时表/视图名称报告,sqlite/oracle有不同的方法

这个 Inspector.get_table_names()Inspector.get_view_names() 对于sqlite/oracle,方法还将返回临时表和视图的名称,这些名称不是由任何其他方言提供的(对于mysql,至少是不可能的)。这种逻辑已被转移到两种新方法中。 Inspector.get_temp_table_names()Inspector.get_temp_view_names() .

请注意,特定命名临时表或临时视图的反射 Table('name', autoload=True) 或者通过类似的方法 Inspector.get_columns() 即使不是所有方言,也会继续发挥作用。特别是对于sqlite,对于临时表中的唯一约束反射也有一个bug修复,即 #3203 .

#3204

方言改进与变化-PostgreSQL

修改枚举类型创建/删除规则

《PostgreSQL规则》 ENUM 在创建和删除类型方面做得更为严格。

ENUM 这是创造出来的 没有MetaData 将创建对象 and 删除对应于 Table.create()Table.drop() ::

table = Table('sometable', metadata,
    Column('some_enum', ENUM('a', 'b', 'c', name='myenum'))
)

table.create(engine)  # will emit CREATE TYPE and CREATE TABLE
table.drop(engine)  # will emit DROP TABLE and DROP TYPE - new for 1.0

这意味着,如果第二个表也有一个名为“myenum”的枚举,则上述删除操作现在将失败。为了适应公共共享枚举类型的用例,增强了与元数据关联的枚举的行为。

ENUM 这是创造出来的 withMetaData 对象意志 not 被创造 or 删除对应于 Table.create()Table.drop() ,除了 Table.create() 调用与 checkfirst=True 旗帜:

my_enum = ENUM('a', 'b', 'c', name='myenum', metadata=metadata)

table = Table('sometable', metadata,
    Column('some_enum', my_enum)
)

# will fail: ENUM 'my_enum' does not exist
table.create(engine)

# will check for enum and emit CREATE TYPE
table.create(engine, checkfirst=True)

table.drop(engine)  # will emit DROP TABLE, *not* DROP TYPE

metadata.drop_all(engine) # will emit DROP TYPE

metadata.create_all(engine) # will emit CREATE TYPE

#3319

新PostgreSQL表选项

添加了对pg table options表空间、on commit、with(out)oid和inherits的支持,当通过 Table 构造。

参见

PostgreSQL表选项

#2051

使用PostgreSQL方言新建get enums()方法

这个 inspect() 方法返回 PGInspector PostgreSQL中的对象,其中包括一个新的 PGInspector.get_enums() 返回所有可用信息的方法 ENUM 类型:

from sqlalchemy import inspect, create_engine

engine = create_engine("postgresql+psycopg2://host/dbname")
insp = inspect(engine)
print(insp.get_enums())

参见

PGInspector.get_enums()

PostgreSQL方言反映物化视图、外来表

变更如下:

对反射的更改包括添加 'm''f' 查询时使用的限定符列表 pg_class.relkind 但是这个变化在1.0.0中是新的,以避免在生产中运行0.9的人遇到任何向后不兼容的意外。

#2891

《PostgreSQL》 has_table() 现在为临时桌工作

这是一个简单的修复程序,这样临时表的“has table”就可以工作了,这样就可以继续执行以下代码:

from sqlalchemy import *

metadata = MetaData()
user_tmp = Table(
    "user_tmp", metadata,
    Column("id", INT, primary_key=True),
    Column('name', VARCHAR(50)),
    prefixes=['TEMPORARY']
)

e = create_engine("postgresql://scott:tiger@localhost/test", echo='debug')
with e.begin() as conn:
    user_tmp.create(conn, checkfirst=True)

    # checkfirst will succeed
    user_tmp.create(conn, checkfirst=True)

这种行为不太可能导致非失败应用程序的行为不同,这是因为PostgreSQL允许非临时表以静默方式覆盖临时表。因此,下面这样的代码现在将完全不同,不再在临时表之后创建实际表:

from sqlalchemy import *

metadata = MetaData()
user_tmp = Table(
    "user_tmp", metadata,
    Column("id", INT, primary_key=True),
    Column('name', VARCHAR(50)),
    prefixes=['TEMPORARY']
)

e = create_engine("postgresql://scott:tiger@localhost/test", echo='debug')
with e.begin() as conn:
    user_tmp.create(conn, checkfirst=True)

    m2 = MetaData()
    user = Table(
        "user_tmp", m2,
        Column("id", INT, primary_key=True),
        Column('name', VARCHAR(50)),
    )

    # in 0.9, *will create* the new table, overwriting the old one.
    # in 1.0, *will not create* the new table
    user.create(conn, checkfirst=True)

#3264

PostgreSQL筛选关键字

从9.4开始,PostgreSQL现在支持聚合函数的SQL标准筛选关键字。SQLAlchemy允许使用 FunctionElement.filter() ::

func.count(1).filter(True)

参见

FunctionElement.filter()

FunctionFilter

PG8000方言支持客户端编码

这个 create_engine.encoding 参数现在被pg8000方言接受,它使用在发出 SET CLIENT_ENCODING 匹配所选编码。

PG8000本地JSONB支持

已经添加了对大于1.10.1的PG8000版本的支持,其中本机支持JSONB。

支持pypy上的psycopg2cffi方言

增加了对pypy psycopg2cffi方言的支持。

参见

sqlalchemy.dialects.postgresql.psycopg2cffi

方言改进和变化-MySQL

MySQL时间戳类型现在在所有情况下都呈现为空/非空

MySQL方言总是绕过与时间戳列关联的MySQL隐式NOT NULL缺省值,方法是为此类类型发出NULL(如果列是用 nullable=True 。但是,MySQL 5.6.6及更高版本提供了一个新标志 explicit_defaults_for_timestamp 它修复了MySQL的非标准行为,使其行为与任何其他类型一样;为了适应这一点,SQLAlChemy现在为所有时间戳列无条件地发出NULL/NOT NULL。

参见

时间戳列和空

#3155

MySQL集合类型大修以支持空集合、Unicode、空值处理

这个 SET 历史上,类型不包括单独处理空集和空值的系统;由于不同的驱动程序在处理空字符串和空字符串集表示时具有不同的行为,因此集合类型仅尝试在这些行为之间进行对冲,选择将空集视为 set(['']) 正如MySQL连接器python dbapi的当前行为一样。这里的部分理由是,在MySQL集合中实际存储一个空字符串是不可能的,因为驱动程序没有办法区分 set([''])set() . 由用户决定 set(['']) 其实意思是“空套”或不是。

新的行为为空白字符串移动用例,这是一个不寻常的情况,甚至在MySQL文档中也没有记录到一个特殊的情况下,默认行为是 SET 现在是:

  • 处理空字符串 '' 由mysql python返回到空集合中 set()

  • 转换单个空白值集 set(['']) 由mysql connector python返回到空集合中 set()

  • 若要处理实际希望的集合类型的实例,则包含空白值 '' 在其可能值列表中,实现了一个新特性(在本用例中是必需的),通过该特性,设置值被持久化并作为一个位整数值加载;标志 SET.retrieve_as_bitwise 是为了启用此功能而添加的。

使用 SET.retrieve_as_bitwise 标志允许持久化和检索集,而不存在值的模糊性。理论上,只要类型的给定值列表与数据库中声明的顺序完全匹配,就可以在所有情况下启用此标志;它只会使SQL Echo输出更加不寻常。

的默认行为 SET 否则将使用字符串保持相同的往返值。基于字符串的行为现在支持unicode,包括mysql python和use_unicode=0。

#3283

MySQL内部“无此类表”异常未传递给事件处理程序

MySQL方言现在将禁用 ConnectionEvents.handle_error() 从内部触发用于检测表是否存在的那些语句的事件。这是通过使用执行选项实现的 skip_user_error_events 为该执行的范围禁用句柄错误事件。通过这种方式,重写异常的用户代码不需要担心MySQL方言或其他偶尔需要捕获特定于sqlAlchemy的异常的方言。

更改了默认值 raise_on_warnings 对于MySQL连接器

将mysql connector的默认值“raise-on-u warnings”更改为false。出于某种原因,这被设为真。不幸的是,“buffered”标志必须保持为true,因为mysqlconnector不允许关闭光标,除非完全获取所有结果。

#2515

MySQL布尔符号“真”、“假”再次起作用

0.9对中的is/is运算符和布尔类型的大修 #2682 禁止MySQL方言在“is”/“is not”上下文中使用“true”和“false”符号。显然,尽管mysql没有“boolean”类型,但它支持使用特殊的“true”和“false”符号时的is/is,即使这些符号在其他方面与“1”和“0”同义(is/is不适用于数字)。

所以这里的变化是MySQL方言仍然是“非本地布尔”,但是 true()false() 符号再次产生关键字“真”和“假”,使表达式像 column.is_(true()) 同样适用于MySQL。

#3186

match()操作符现在返回与mysql的浮点返回值兼容的不可知匹配类型。

返回类型 ColumnOperators.match() 表达式现在是名为 MatchType . 这是 Boolean ,可以被方言截取,以便在SQL执行时生成不同的结果类型。

下面这样的代码现在可以正常工作,并在MySQL上返回浮点值:

>>> connection.execute(
...    select([
...        matchtable.c.title.match('Agile Ruby Programming').label('ruby'),
...        matchtable.c.title.match('Dive Python').label('python'),
...        matchtable.c.title
...    ]).order_by(matchtable.c.id)
... )
[
    (2.0, 0.0, 'Agile Web Development with Ruby On Rails'),
    (0.0, 2.0, 'Dive Into Python'),
    (2.0, 0.0, "Programming Matz's Ruby"),
    (0.0, 0.0, 'The Definitive Guide to Django'),
    (0.0, 1.0, 'Python in a Nutshell')
]

#3263

细雨方言现在是一种外来方言。

的方言 Drizzle 现在是一种外部方言,可从https://bitbucket.org/zzzeek/sqlalchemy-drizzle.获得此方言是在SQLAlChemy能够很好地适应第三方方言之前添加到SQLAlChemy中的;展望未来,所有不属于“无处不在的使用”类别的数据库都是第三方方言。该方言的实现没有改变,仍然基于SQLAlChemy中的MySQL+MySQLdb方言。到目前为止,这种方言还没有发布,处于“阁楼”状态;然而,如果有人想要改进它,它通过了大多数测试,总体上还处于良好的工作状态。

方言改进和变化-sqlite

SQLite命名和未命名的唯一和外键约束将检查和反映

唯一键和外键约束现在完全反映在有名称和没有名称的sqlite上。以前,忽略了外键名,跳过了未命名的唯一约束。尤其是这将有助于Alembic新的sqlite迁移功能。

为了实现这一点,对于外键和唯一约束,将pragma foreign_keys、index_list和index_info的结果与create table语句的正则表达式解析相结合,形成约束名称的完整图片,并区分创建为唯一约束和未命名约束的唯一约束。索引。

#3244

#3261

方言改进和更改-SQL Server

基于主机名的SQL Server连接需要pyodbc驱动程序名

使用无DSN连接(例如使用显式主机名)通过pyodbc连接到SQL Server,现在需要一个驱动程序名-sqlacchemy将不再尝试猜测默认值::

engine = create_engine("mssql+pyodbc://scott:tiger@myhost:port/databasename?driver=SQL+Server+Native+Client+10.0")

SQLAlchemy以前的硬编码默认值“SQL Server”在Windows上已过时,不能根据操作系统/驱动程序检测来猜测最佳驱动程序。使用ODBC时,最好使用DSN,以完全避免此问题。

#3182

SQL Server 2012大型文本/二进制类型呈现为varchar、nvarchar、varbinary

呈现 TextClauseUnicodeTextLargeBinary SQL Server 2012及更高版本的类型已更改,根据Microsoft的弃用准则,可以选择完全控制行为。见 大文本/二进制类型取消预测 有关详细信息。

方言改进和变化-Oracle

改进了对Oracle中CTE的支持

对Oracle的CTE支持已经修复,还有一个新功能 CTE.with_suffixes() 可以协助Oracle的特殊指令:

included_parts = select([
    part.c.sub_part, part.c.part, part.c.quantity
]).where(part.c.part == "p1").\
    cte(name="included_parts", recursive=True).\
    suffix_with(
        "search depth first by part set ord1",
        "cycle part set y_cycle to 1 default 0", dialect='oracle')

#3220

DDL的新Oracle关键字

关键字,如compress、on commit、bitmap:

Oracle表选项

Oracle特定索引选项