目录

SQLAlchemy 0.9有什么新功能?

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

关于此文档

本文档描述了截至2013年5月正在进行维护发布的SQLAlchemy版本0.8和2013年12月30日首次发布生产版本的SQLAlchemy版本0.9之间的变化。

文件上次更新日期:2015年6月10日

介绍

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

请仔细检查 行为改变-ORM行为改变-核心 对于可能向后不兼容的更改。

平台支撑

针对python 2.6及更高版本,python 3不带2to3

0.9版本的第一个成果是消除对2to 3工具的依赖,以实现Python3的兼容性。为了更简单地说明这一点,现在针对的最低版本是2.6,它与Python3具有广泛的交叉兼容性。所有的sqlAlchemy模块和单元测试现在都可以和2.6版本的任何python解释器(包括3.1和3.2解释器)一样好地解释。

#2671

python 3支持的C扩展

C扩展已经被移植来支持Python3,现在构建在Python2和Python3环境中。

#2161

行为改变-ORM

当基于每个属性进行查询时,复合属性现在作为其对象窗体返回。

使用A Query 现在,与复合属性一起返回由该复合属性维护的对象类型,而不是分解为单独的列。使用位于的映射设置 组合列类型 ::

>>> session.query(Vertex.start, Vertex.end).\
...     filter(Vertex.start == Point(3, 4)).all()
[(Point(x=3, y=4), Point(x=5, y=6))]

此更改与期望将单个属性扩展为单个列的代码向后不兼容。要获得这种行为,请使用 .clauses 访问器:

>>> session.query(Vertex.start.clauses, Vertex.end.clauses).\
...     filter(Vertex.start == Point(3, 4)).all()
[(3, 4, 5, 6)]

参见

ORM查询的列束

#2824

Query.select_from() 不再将该子句应用于相应的实体

这个 Query.select_from() 方法在最近的版本中已经被广泛应用,作为控制 Query 对象“从中选择”,通常用于控制连接的呈现方式。

考虑下面的例子与通常的相反 User 制图:

select_stmt = select([User]).where(User.id == 7).alias()

q = session.query(User).\
           join(select_stmt, User.id == select_stmt.c.id).\
           filter(User.name == 'ed')

上面的语句可以预见地呈现SQL如下:

SELECT "user".id AS user_id, "user".name AS user_name
FROM "user" JOIN (SELECT "user".id AS id, "user".name AS name
FROM "user"
WHERE "user".id = :id_1) AS anon_1 ON "user".id = anon_1.id
WHERE "user".name = :name_1

如果我们想颠倒连接的左右元素的顺序,文档将使我们相信我们可以使用 Query.select_from() 这样做:

q = session.query(User).\
        select_from(select_stmt).\
        join(User, User.id == select_stmt.c.id).\
        filter(User.name == 'ed')

但是,在0.8及更低版本中,上述使用 Query.select_from() 将适用于 select_stmt代替 这个 User 实体,从中选择 user 与兼容的表 User ::

-- SQLAlchemy 0.8 and earlier...
SELECT anon_1.id AS anon_1_id, anon_1.name AS anon_1_name
FROM (SELECT "user".id AS id, "user".name AS name
FROM "user"
WHERE "user".id = :id_1) AS anon_1 JOIN "user" ON anon_1.id = anon_1.id
WHERE anon_1.name = :name_1

上面的陈述一团糟,on子句指 anon_1.id = anon_1.id ,我们的where子句已替换为 anon_1 也。

这种行为是非常有意的,但是它有一个不同的用例,这个用例在 Query.select_from() . 上述行为现在可以通过一种新的方法 Query.select_entity_from() . 这是一种较不常用的行为,在现代的SQLAlchemy中,它大致相当于从自定义的 aliased() 结构:

select_stmt = select([User]).where(User.id == 7)
user_from_stmt = aliased(User, select_stmt.alias())

q = session.query(user_from_stmt).filter(user_from_stmt.name == 'ed')

因此,对于sqlacalchemy 0.9,我们的查询选择 select_stmt 生成我们期望的SQL::

-- SQLAlchemy 0.9
SELECT "user".id AS user_id, "user".name AS user_name
FROM (SELECT "user".id AS id, "user".name AS name
FROM "user"
WHERE "user".id = :id_1) AS anon_1 JOIN "user" ON "user".id = id
WHERE "user".name = :name_1

这个 Query.select_entity_from() 方法将在sqlAlchemy中可用 0.8.2 ,因此依赖于旧行为的应用程序可以首先转换到该方法,确保所有测试继续运行,然后升级到0.9而不会出现问题。

#2736

viewonly=Truerelationship() 阻止历史生效

这个 viewonly 旗上 relationship() 用于防止对目标属性的更改在刷新过程中产生任何影响。这是通过消除在刷新过程中考虑的属性来实现的。但是,到目前为止,对属性的更改仍然会将父对象注册为“脏的”,并触发潜在的刷新。变化是 viewonly 现在,标志还可以防止为目标属性设置历史记录。属性事件(如backrefs和用户定义的事件)仍继续正常工作。

变更说明如下:

from sqlalchemy import Column, Integer, ForeignKey, create_engine
from sqlalchemy.orm import backref, relationship, Session
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import inspect

Base = declarative_base()

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(Integer, ForeignKey('a.id'))
    a = relationship("A", backref=backref("bs", viewonly=True))

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

a = A()
b = B()

sess = Session(e)
sess.add_all([a, b])
sess.commit()

b.a = a

assert b in sess.dirty

# before 0.9.0
# assert a in sess.dirty
# assert inspect(a).attrs.bs.history.has_changes()

# after 0.9.0
assert a not in sess.dirty
assert not inspect(a).attrs.bs.history.has_changes()

#2833

关联代理SQL表达式的改进和修复

这个 ==!= 由引用标量关系上的标量值的关联代理实现的运算符现在生成一个更完整的SQL表达式,目的是在比较时考虑“关联”行是否存在 None .

考虑此映射:

class A(Base):
    __tablename__ = 'a'

    id = Column(Integer, primary_key=True)

    b_id = Column(Integer, ForeignKey('b.id'), primary_key=True)
    b = relationship("B")
    b_value = association_proxy("b", "value")

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

在0.8之前,类似以下的查询:

s.query(A).filter(A.b_value == None).all()

将产生:

SELECT a.id AS a_id, a.b_id AS a_b_id
FROM a
WHERE EXISTS (SELECT 1
FROM b
WHERE b.id = a.b_id AND b.value IS NULL)

在0.9中,它现在产生:

SELECT a.id AS a_id, a.b_id AS a_b_id
FROM a
WHERE (EXISTS (SELECT 1
FROM b
WHERE b.id = a.b_id AND b.value IS NULL)) OR a.b_id IS NULL

区别在于,它不仅检查 b.value ,它还检查 a 指不 b 全行。对于使用这种比较类型的系统(其中某些父行没有关联行),这将返回与以前版本不同的结果。

更关键的是,为 A.b_value != None . 在0.8中,这将返回 True 对于 A 没有的行 b ::

SELECT a.id AS a_id, a.b_id AS a_b_id
FROM a
WHERE NOT (EXISTS (SELECT 1
FROM b
WHERE b.id = a.b_id AND b.value IS NULL))

现在在0.9版本中,检查已经被重新处理,以确保a.b_id行存在,此外 B.value 非空:

SELECT a.id AS a_id, a.b_id AS a_b_id
FROM a
WHERE EXISTS (SELECT 1
FROM b
WHERE b.id = a.b_id AND b.value IS NOT NULL)

此外, has() 运算符得到了增强,这样您就可以对一个仅无条件的标量列值调用它,它将生成检查关联行是否存在的条件::

s.query(A).filter(A.b_value.has()).all()

输出:

SELECT a.id AS a_id, a.b_id AS a_b_id
FROM a
WHERE EXISTS (SELECT 1
FROM b
WHERE b.id = a.b_id)

这相当于 A.b.has() ,但允许查询 b_value 直接。

#2751

关联代理缺少标量返回无

从标量属性到标量的关联代理现在将返回 None 如果代理对象不存在。这与缺少“多对一”在sqlacalchemy中不返回“无”的事实是一致的,代理值也是如此。例如。::

from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.associationproxy import association_proxy

Base = declarative_base()

class A(Base):
    __tablename__ = 'a'

    id = Column(Integer, primary_key=True)
    b = relationship("B", uselist=False)

    bname = association_proxy("b", "name")

class B(Base):
    __tablename__ = 'b'

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

a1 = A()

# this is how m2o's always have worked
assert a1.b is None

# but prior to 0.9, this would raise AttributeError,
# now returns None just like the proxied value.
assert a1.bname is None

#2810

attributes.get_history()默认情况下,如果值不存在,将从数据库查询

关于 get_history() 允许基于列的属性向数据库查询卸载的值,假定 passive 标志的默认值为 PASSIVE_OFF . 在此之前,此标志将不起作用。另外,一种新的方法 AttributeState.load_history() 添加以补充 AttributeState.history 属性,它将为卸载的属性发出加载程序可调用文件。

这是一个小的变化,演示如下:

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

Base = declarative_base()

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

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

sess = Session(e)

a1 = A(data='a1')
sess.add(a1)
sess.commit()  # a1 is now expired

# history doesn't emit loader callables
assert inspect(a1).attrs.data.history == (None, None, None)

# in 0.8, this would fail to load the unloaded state.
assert attributes.get_history(a1, 'data') == ((), ['a1',], ())

# load_history() is now equivalent to get_history() with
# passive=PASSIVE_OFF ^ INIT_OK
assert inspect(a1).attrs.data.load_history() == ((), ['a1',], ())

#2787

行为改变-核心

类型对象不再接受忽略的关键字参数

在0.8系列中,大多数类型对象接受任意关键字参数,这些参数被静默忽略::

from sqlalchemy import Date, Integer

# storage_format argument here has no effect on any backend;
# it needs to be on the SQLite-specific type
d = Date(storage_format="%(day)02d.%(month)02d.%(year)04d")

# display_width argument here has no effect on any backend;
# it needs to be on the MySQL-specific type
i = Integer(display_width=5)

这是一个非常老的bug,在0.8系列中添加了一个拒绝警告,但是因为没有人使用“-w”标志运行python,所以它几乎从未被发现:

$ python -W always::DeprecationWarning ~/dev/sqlalchemy/test.py
/Users/classic/dev/sqlalchemy/test.py:5: SADeprecationWarning: Passing arguments to
type object constructor <class 'sqlalchemy.types.Date'> is deprecated
  d = Date(storage_format="%(day)02d.%(month)02d.%(year)04d")
/Users/classic/dev/sqlalchemy/test.py:9: SADeprecationWarning: Passing arguments to
type object constructor <class 'sqlalchemy.types.Integer'> is deprecated
  i = Integer(display_width=5)

从0.9系列开始,“catch all”构造函数将从 TypeEngine 而这些毫无意义的论点也不再被接受。

正确使用方言特定参数的方法,如 storage_formatdisplay_width 使用适当的方言特定类型:

from sqlalchemy.dialects.sqlite import DATE
from sqlalchemy.dialects.mysql import INTEGER

d = DATE(storage_format="%(day)02d.%(month)02d.%(year)04d")

i = INTEGER(display_width=5)

那我们还想要方言不可知论类型的例子呢?我们使用 TypeEngine.with_variant() 方法:

from sqlalchemy import Date, Integer
from sqlalchemy.dialects.sqlite import DATE
from sqlalchemy.dialects.mysql import INTEGER

d = Date().with_variant(
        DATE(storage_format="%(day)02d.%(month)02d.%(year)04d"),
        "sqlite"
    )

i = Integer().with_variant(
        INTEGER(display_width=5),
        "mysql"
    )

TypeEngine.with_variant() 不是新的,它是在sqlacalchemy 0.7.2中添加的。因此,在0.8系列上运行的代码可以更正为使用这种方法,并在升级到0.9之前进行测试。

None 不能再用作“分部和”构造函数

None 不能再作为“后盾”零碎地形成和条件。这个模式不是一个文档化的模式,即使一些SQLAlchemy内部使用它:

condition = None

for cond in conditions:
    condition = condition & cond

if condition is not None:
    stmt = stmt.where(condition)

上述顺序,当 conditions 非空,将在0.9上生成 SELECT .. WHERE <condition> AND NULL . 这个 None 不再被隐式忽略,而是与 None 在其他上下文中解释,除了连词。

0.8和0.9的正确代码应为:

from sqlalchemy.sql import and_

if conditions:
    stmt = stmt.where(and_(*conditions))

另一个变体在0.9的所有后端上都有效,但在0.8上只在支持布尔常量的后端上有效:

from sqlalchemy.sql import true

condition = true()

for cond in conditions:
    condition = cond & condition

stmt = stmt.where(condition)

在0.8上,这将生成一个select语句,该语句始终 AND true 在WHERE子句中,不支持布尔常量(mysql、mssql)的后端不接受它。0.9, true 常量将在 and_() 连词。

参见

改进了布尔常量、空常量和连词的呈现

的“密码”部分 create_engine() 不再考虑 + 作为编码空间签名

不管出于什么原因,Python函数 unquote_plus() 应用于URL的“Password”字段,这是对中描述的编码规则的错误应用 RFC 1738 因为它以加号的形式逃逸空格。URL的字符串化现在只对“:”、“@”或“/”进行编码,而不对其他内容进行编码,并且现在应用于 usernamepassword 字段(以前它只应用于密码)。解析时,将转换编码的字符,但加号和空格将按原样传递::

# password: "pass word + other:words"
dbtype://user:pass word + other%3Awords@host/dbname

# password: "apples/oranges"
dbtype://username:apples%2Foranges@hostspec/database

# password: "apples@oranges@@"
dbtype://username:apples%40oranges%40%40@hostspec/database

# password: '', username is "username@"
dbtype://username%40:@hostspec/database

#2873

排序规则已更改

以前的表达式如下:

print((column('x') == 'somevalue').collate("en_EN"))

将生成如下表达式:

-- 0.8 behavior
(x = :x_1) COLLATE en_EN

上面的内容被MSSQL误解了,通常不是建议用于任何数据库的语法。表达式现在将生成大多数数据库文档所示的语法:

-- 0.9 behavior
x = :x_1 COLLATE en_EN

如果 ColumnOperators.collate() 正在将运算符应用于右侧列,如下所示:

print(column('x') == literal('somevalue').collate("en_EN"))

在0.8中,这产生:

x = :param_1 COLLATE en_EN

然而,在0.9中,现在将产生更准确的,但可能不是你想要的,形式为:

x = (:param_1 COLLATE en_EN)

这个 ColumnOperators.collate() 操作员现在在 ORDER BY 表达式,以及 ASCDESC 再次确保不生成括号的运算符::

>>> # 0.8
>>> print(column('x').collate('en_EN').desc())
(x COLLATE en_EN) DESC

>>> # 0.9
>>> print(column('x').collate('en_EN').desc())
x COLLATE en_EN DESC

#2879

PostgreSQL create type<x>as enum现在应用于引用值

这个 ENUM 类型现在将对枚举值中的单引号符号应用转义::

>>> from sqlalchemy.dialects import postgresql
>>> type = postgresql.ENUM('one', 'two', "three's", name="myenum")
>>> from sqlalchemy.dialects.postgresql import base
>>> print(base.CreateEnumType(type).compile(dialect=postgresql.dialect()))
CREATE TYPE myenum AS ENUM ('one','two','three''s')

已经避开单引号符号的现有解决方案需要修改,否则现在将加倍避开。

#2878

新特点

事件删除API

事件建立使用 listen()listens_for() 现在可以使用新的 remove() 功能。这个 targetidentifierfn 参数发送到 remove() 需要与发送用于监听的事件完全匹配,并且将从已建立该事件的所有位置删除该事件::

@event.listens_for(MyClass, "before_insert", propagate=True)
def my_before_insert(mapper, connection, target):
    """listen for before_insert"""
    # ...

event.remove(MyClass, "before_insert", my_before_insert)

在上面的示例中, propagate=True 设置标志。这意味着 my_before_insert() 作为听众 MyClass 以及 MyClass . 系统会跟踪 my_before_insert() 侦听器函数已作为此调用的结果放置,并由于调用而将其移除 remove() .

删除系统使用注册表来关联传递给 listen() 事件监听器集合,在许多情况下,这些集合是原始用户提供函数的包装版本。这个注册表大量使用弱引用,以便在包含的所有内容(如侦听器目标)超出作用域时进行垃圾收集。

#2268

新的查询选项API; load_only() 选项

装载机选项系统,如 joinedload()subqueryload()lazyload()defer() 等等,都建立在一个新的系统上 Load . Load 提供“方法链接”(A.K.A. generative )方法是使用加载程序选项,这样就不必使用点或多个属性名称将长路径连接在一起,而是为每个路径提供显式加载程序样式。

虽然新方法稍微更详细一些,但更容易理解的是,在将哪些选项应用到哪些路径上时没有含糊不清;它简化了选项的方法签名,并提供了更大的灵活性,特别是对于基于列的选项。旧系统也将无限期地保持功能,所有样式都可以混合使用。

老路

要设置多元素路径中每个链接的特定加载样式,请 _all() 必须使用选项:

query(User).options(joinedload_all("orders.items.keywords"))

新途径

加载器选项现在可以链接,所以相同 joinedload(x) 方法同样适用于每个链接,无需在 joinedload()joinedload_all() ::

query(User).options(joinedload("orders").joinedload("items").joinedload("keywords"))

老路

在基于子类的路径上设置一个选项需要将路径中的所有链接拼写为类绑定属性,因为 PropComparator.of_type() 需要调用方法::

session.query(Company).\
    options(
        subqueryload_all(
            Company.employees.of_type(Engineer),
            Engineer.machines
        )
    )

新途径

只有路径中实际需要的元素 PropComparator.of_type() 需要设置为类绑定属性,之后可以恢复基于字符串的名称::

session.query(Company).\
    options(
        subqueryload(Company.employees.of_type(Engineer)).
        subqueryload("machines")
        )
    )

老路

在长路径中的最后一个链接上设置loader选项使用的语法看起来很像是为路径中的所有链接设置选项,导致混淆:

query(User).options(subqueryload("orders.items.keywords"))

新途径

现在可以使用 defaultload() 对于路径中现有加载程序样式应保持不变的条目。更详细,但目的更明确:

query(User).options(defaultload("orders").defaultload("items").subqueryload("keywords"))

虚线样式仍然可以利用,特别是在跳过多个路径元素的情况下:

query(User).options(defaultload("orders.items").subqueryload("keywords"))

老路

这个 defer() 路径上的选项需要用每列的完整路径拼写:

query(User).options(defer("orders.description"), defer("orders.isopen"))

新途径

单一的 Load 到达目标路径的对象可以具有 Load.defer() 反复召唤:

query(User).options(defaultload("orders").defer("description").defer("isopen"))

负载类

这个 Load 类可直接用于提供“绑定”目标,特别是当存在多个父实体时:

from sqlalchemy.orm import Load

query(User, Address).options(Load(Address).joinedload("entries"))

仅加载

一种新的选择 load_only() 实现“延迟除外的所有内容”的加载样式,仅加载给定的列并延迟其余的列:

from sqlalchemy.orm import load_only

query(User).options(load_only("name", "fullname"))

# specify explicit parent entity
query(User, Address).options(Load(User).load_only("name", "fullname"))

# specify path
query(User).options(joinedload(User.addresses).load_only("email_address"))

类特定的通配符

使用 Load ,通配符可用于设置给定实体上所有关系(或列)的加载,而不影响任何其他关系::

# lazyload all User relationships
query(User).options(Load(User).lazyload("*"))

# undefer all User columns
query(User).options(Load(User).undefer("*"))

# lazyload all Address relationships
query(User).options(defaultload(User.addresses).lazyload("*"))

# undefer all Address columns
query(User).options(defaultload(User.addresses).undefer("*"))

#1418

新的 text() 能力

这个 text() 构建收益新方法:

  • TextClause.bindparams() 允许灵活设置绑定参数类型和值:

    # setup values
    stmt = text("SELECT id, name FROM user "
          "WHERE name=:name AND timestamp=:timestamp").\
          bindparams(name="ed", timestamp=datetime(2012, 11, 10, 15, 12, 35))
    
    # setup types and/or values
    stmt = text("SELECT id, name FROM user "
          "WHERE name=:name AND timestamp=:timestamp").\
          bindparams(
              bindparam("name", value="ed"),
              bindparam("timestamp", type_=DateTime()
          ).bindparam(timestamp=datetime(2012, 11, 10, 15, 12, 35))
  • TextClause.columns() 取代了 typemap 选择权 text() ,返回新构造 TextAsFrom ::

    # turn a text() into an alias(), with a .c. collection:
    stmt = text("SELECT id, name FROM user").columns(id=Integer, name=String)
    stmt = stmt.alias()
    
    stmt = select([addresses]).select_from(
                  addresses.join(stmt), addresses.c.user_id == stmt.c.id)
    
    
    # or into a cte():
    stmt = text("SELECT id, name FROM user").columns(id=Integer, name=String)
    stmt = stmt.cte("x")
    
    stmt = select([addresses]).select_from(
                  addresses.join(stmt), addresses.c.user_id == stmt.c.id)

#2877

从选定区域插入

经过数年毫无意义的拖延之后,这个相对较小的语法特征被添加了,并且也被重新转换为0.8.3,所以从技术上讲,在0.9中并不是“新的”。一 select() 构造或其他兼容的构造可以传递给新方法 Insert.from_select() 它将用于渲染 INSERT .. SELECT 结构:

>>> from sqlalchemy.sql import table, column
>>> t1 = table('t1', column('a'), column('b'))
>>> t2 = table('t2', column('x'), column('y'))
>>> print(t1.insert().from_select(['a', 'b'], t2.select().where(t2.c.y == 5)))
INSERT INTO t1 (a, b) SELECT t2.x, t2.y
FROM t2
WHERE t2.y = :y_1

构造足够智能,还可以容纳ORM对象,如类和 Query 物体::

s = Session()
q = s.query(User.id, User.name).filter_by(name='ed')
ins = insert(Address).from_select((Address.id, Address.email_address), q)

致使::

INSERT INTO addresses (id, email_address)
SELECT users.id AS users_id, users.name AS users_name
FROM users WHERE users.name = :name_1

#722

更新支持的新功能 select()Query()

试图简化 FOR UPDATE 条款 SELECT 在core和orm中进行的语句,并为 FOR UPDATE OF PostgreSQL和Oracle支持的SQL。

使用核心 GenerativeSelect.with_for_update() 选项 FOR SHARENOWAIT 可以单独指定,而不是链接到任意字符串代码:

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

在posgtresql上,上面的语句可能呈现如下:

SELECT table.a, table.b FROM table FOR SHARE OF table NOWAIT

这个 Query 对象获得类似的方法 Query.with_for_update() 它们的行为是一样的。此方法取代现有的 Query.with_lockmode() 方法,翻译 FOR UPDATE 使用不同系统的子句。目前,“lockmode”字符串参数仍被 Session.refresh() 方法。

可为本机浮点类型配置的浮点字符串转换精度

每当dbapi返回要转换为python的python浮点类型时,sqlAlchemy所做的转换。 Decimal() 必须涉及将浮点值转换为字符串的中间步骤。用于此字符串转换的刻度以前是硬编码为10的,现在是可配置的。此设置可用于 Numeric 以及 Float 类型,以及所有特定于SQL和方言的后代类型,使用参数 decimal_return_scale . 如果类型支持 .scale 参数,如 Numeric 以及一些浮点类型,例如 DOUBLE 的价值 .scale 用作的默认值 .decimal_return_scale 除非另有规定。如果两者 .scale.decimal_return_scale 如果不存在,则默认值为10。例如。::

from sqlalchemy.dialects.mysql import DOUBLE
import decimal

data = Table('data', metadata,
    Column('double_value',
                mysql.DOUBLE(decimal_return_scale=12, asdecimal=True))
)

conn.execute(
    data.insert(),
    double_value=45.768392065789,
)
result = conn.scalar(select([data.c.double_value]))

# previously, this would typically be Decimal("45.7683920658"),
# e.g. trimmed to 10 decimal places

# now we get 12, as requested, as MySQL can support this
# much precision for DOUBLE
assert result == decimal.Decimal("45.768392065789")

#2867

ORM查询的列束

这个 Bundle 允许查询列集,然后在查询返回的元组下将这些列分组为一个名称。最初的目的是 Bundle 是1。允许“复合”ORM列在基于列的结果集中作为单个值返回,而不是将它们展开为单独的列和2。允许在ORM中使用特殊列和返回类型创建自定义结果集构造,而不涉及映射类的更重的机制。

参见

当基于每个属性进行查询时,复合属性现在作为其对象窗体返回。

列束

#2824

服务器端版本计数

ORM的版本控制特性(现在也记录在 配置版本计数器 )现在可以使用服务器端版本计数方案,例如由触发器或数据库系统列生成的方案,以及版本id_counter函数本身之外的条件编程方案。通过提供价值 Falseversion_id_generator 参数,ORM将使用已设置的版本标识符,或者在发出插入或更新的同时从每行获取版本标识符。当使用服务器生成的版本标识符时,强烈建议仅在具有强返回支持(PostgreSQL、SQL Server;Oracle也支持返回,但cx-Oracle驱动程序的支持有限)的后端上使用此功能,否则附加的select语句将增加显著的性能开销。示例见 服务器端版本计数器 说明PostgreSQL的用法 xmin 系统列,以便将其与ORM的版本控制功能集成。

参见

服务器端版本计数器

#2793

include_backrefs=False option for @validates

这个 validates() 函数现在接受一个选项 include_backrefs=True ,对于从backref启动的事件:

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

Base = declarative_base()

class A(Base):
    __tablename__ = 'a'

    id = Column(Integer, primary_key=True)
    bs = relationship("B", backref="a")

    @validates("bs")
    def validate_bs(self, key, item):
        print("A.bs validator")
        return item

class B(Base):
    __tablename__ = 'b'

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

    @validates("a", include_backrefs=False)
    def validate_a(self, key, item):
        print("B.a validator")
        return item

a1 = A()
a1.bs.append(B())  # prints only "A.bs validator"

#1535

PostgreSQL JSON类型

PostgreSQL方言现在的特点是 JSON 类型以补充 HSTORE 类型。

参见

JSON

#2581

自动映射扩展

新扩展名已添加到 0.9.1 被称为 sqlalchemy.ext.automap . 这是一个 实验的 扩展,扩展声明性和 DeferredReflection 班级。基本上,扩展提供了一个基类 AutomapBase 它根据给定的表元数据自动生成映射类及其之间的关系。

这个 MetaData 通常使用中可能通过反射产生,但不要求使用反射。最基本的用法说明了 sqlalchemy.ext.automap 能够基于反射模式传递映射类,包括关系::

from sqlalchemy.ext.automap import automap_base
from sqlalchemy.orm import Session
from sqlalchemy import create_engine

Base = automap_base()

# engine, suppose it has two tables 'user' and 'address' set up
engine = create_engine("sqlite:///mydatabase.db")

# reflect the tables
Base.prepare(engine, reflect=True)

# mapped classes are now created with names matching that of the table
# name.
User = Base.classes.user
Address = Base.classes.address

session = Session(engine)

# rudimentary relationships are produced
session.add(Address(email_address="foo@bar.com", user=User(name="foo")))
session.commit()

# collection-based relationships are by default named "<classname>_collection"
print(u1.address_collection)

除此之外, AutomapBase 类是声明性基础,支持声明性所做的所有功能。“automapping”功能可以与现有的显式声明的模式一起使用,以仅生成关系和缺少的类。可以使用可调用函数删除命名方案和关系生成例程。

希望 AutomapBase 系统为解决著名的 SQLSoup 还试图解决从现有数据库中快速生成基本对象模型的问题。通过严格地在映射器配置级别解决这个问题,并与现有的声明性类技术完全集成, AutomapBase 寻求提供一种很好的集成方法来方便地自动生成即席映射。

参见

自动程序

行为改善

除了极为罕见和不寻常的假设情况外,不应产生兼容性问题的改进,但在出现意外问题时,最好注意这些改进。

许多联接和左外部联接表达式将不再作为anon_1包装在(select*from..)中。

多年来,sqlAlchemy ORM一直被阻止能够在现有联接的右侧嵌套联接(通常是左外部联接,因为内部联接总是可以扁平化)::

SELECT a.*, b.*, c.* FROM a LEFT OUTER JOIN (b JOIN c ON b.id = c.id) ON a.id

这是因为在 3.7.16 无法分析以上格式的语句::

SQLite version 3.7.15.2 2013-01-09 11:53:05
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> create table a(id integer);
sqlite> create table b(id integer);
sqlite> create table c(id integer);
sqlite> select a.id, b.id, c.id from a left outer join (b join c on b.id=c.id) on b.id=a.id;
Error: no such column: b.id

当然,右外部联接是另一种解决右侧括号化的方法;这将非常复杂,并且在视觉上难以实现,但幸运的是,sqlite也不支持右外部联接:):

sqlite> select a.id, b.id, c.id from b join c on b.id=c.id
   ...> right outer join a on b.id=a.id;
Error: RIGHT and FULL OUTER JOINs are not currently supported

早在2005年,还不清楚其他数据库是否在这种形式上有问题,但今天似乎很清楚,除了sqlite之外,所有测试过的数据库现在都支持它(Oracle8,一个非常老的数据库,根本不支持join关键字,但sqlachemy总是有一个简单的重写方案来代替Oracle的语法)。更糟的是,sqlAlchemy通常采用select解决方案,这会降低PostgreSQL和MySQL等平台的性能:

SELECT a.*, anon_1.* FROM a LEFT OUTER JOIN (
                SELECT b.id AS b_id, c.id AS c_id
                FROM b JOIN c ON b.id = c.id
            ) AS anon_1 ON a.id=anon_1.b_id

在处理联接的表继承结构时,像上面的形式一样的联接是常见的;任何时候 Query.join() 用于从某个父级联接到已联接的表子类,或当 joinedload() 与此类似,SQLAlchemy的ORM将始终确保从不呈现嵌套联接,以免查询无法在SQLite上运行。即使核心始终支持更紧凑形式的连接,ORM也必须避免它。

当产生多对多关系的连接时,如果on子句中存在特殊标准,则会出现另一个问题。考虑一个热切的负载联接,如下所示:

session.query(Order).outerjoin(Order.items)

假设从多到多 OrderItem 它实际上是指 Subitem ,上面的SQL如下所示:

SELECT order.id, order.name
FROM order LEFT OUTER JOIN order_item ON order.id = order_item.order_id
LEFT OUTER JOIN item ON order_item.item_id = item.id AND item.type = 'subitem'

上面的查询有什么问题?基本上,它将加载许多 order / order_item 条件所在的行 item.type == 'subitem' 不是真的。

从sqlacalchemy 0.9开始,采用了一种全新的方法。ORM不再担心在封闭连接的右侧嵌套连接,现在它将尽可能频繁地呈现这些连接,同时仍然返回正确的结果。当传递要编译的SQL语句时, 方言编译器重写联接 为了适应目标后端,如果已知该后端不支持正确的嵌套联接(当前仅为sqlite-如果其他后端存在此问题,请通知我们!).

所以有规律 query(Parent).join(Subclass) 现在通常会生成一个更简单的表达式:

SELECT parent.id AS parent_id
FROM parent JOIN (
        base_table JOIN subclass_table
        ON base_table.id = subclass_table.id) ON parent.id = base_table.parent_id

加入渴望的负荷 query(Parent).options(joinedload(Parent.subclasses)) 将为各个表添加别名,而不是将其包装到 ANON_1 ::

SELECT parent.*, base_table_1.*, subclass_table_1.* FROM parent
    LEFT OUTER JOIN (
        base_table AS base_table_1 JOIN subclass_table AS subclass_table_1
        ON base_table_1.id = subclass_table_1.id)
        ON parent.id = base_table_1.parent_id

多对多联接和热切加载将正确嵌套“辅助”和“右”表:

SELECT order.id, order.name
FROM order LEFT OUTER JOIN
(order_item JOIN item ON order_item.item_id = item.id AND item.type = 'subitem')
ON order_item.order_id = order.id

当使用 Select 特别指定的语句 use_labels=True 对于ORM发出的所有查询,都是“join rewriting”的候选者,这是将所有正确的嵌套联接重写为嵌套select语句的过程,同时保持 Select . 因此,即使在2013年也不支持这种非常常见的SQL语法的数据库sqlite本身也具有额外的复杂性,上面的查询重写为:

-- sqlite only!
SELECT parent.id AS parent_id
    FROM parent JOIN (
        SELECT base_table.id AS base_table_id,
                base_table.parent_id AS base_table_parent_id,
                subclass_table.id AS subclass_table_id
        FROM base_table JOIN subclass_table ON base_table.id = subclass_table.id
    ) AS anon_1 ON parent.id = anon_1.base_table_parent_id

-- sqlite only!
SELECT parent.id AS parent_id, anon_1.subclass_table_1_id AS subclass_table_1_id,
        anon_1.base_table_1_id AS base_table_1_id,
        anon_1.base_table_1_parent_id AS base_table_1_parent_id
FROM parent LEFT OUTER JOIN (
    SELECT base_table_1.id AS base_table_1_id,
        base_table_1.parent_id AS base_table_1_parent_id,
        subclass_table_1.id AS subclass_table_1_id
    FROM base_table AS base_table_1
    JOIN subclass_table AS subclass_table_1 ON base_table_1.id = subclass_table_1.id
) AS anon_1 ON parent.id = anon_1.base_table_1_parent_id

-- sqlite only!
SELECT "order".id AS order_id
FROM "order" LEFT OUTER JOIN (
        SELECT order_item_1.order_id AS order_item_1_order_id,
            order_item_1.item_id AS order_item_1_item_id,
            item.id AS item_id, item.type AS item_type
FROM order_item AS order_item_1
    JOIN item ON item.id = order_item_1.item_id AND item.type IN (?)
) AS anon_1 ON "order".id = anon_1.order_item_1_order_id

注解

从sqlAlchemy 1.1开始,此功能中针对sqlite的解决方法将在sqlite版本时自动禁用自己。 3.7.16 当sqlite修复了对右嵌套联接的支持时,检测到或更高版本。

这个 Join.alias()aliased()with_polymorphic() 函数现在支持一个新参数, flat=True ,用于构造联接表实体的别名,而不嵌入到select中。默认情况下不启用此标志,以帮助向后兼容-但现在可以将“多态”可选对象作为目标进行联接,而不生成任何子查询::

employee_alias = with_polymorphic(Person, [Engineer, Manager], flat=True)

session.query(Company).join(
                    Company.employees.of_type(employee_alias)
                ).filter(
                    or_(
                        Engineer.primary_language == 'python',
                        Manager.manager_name == 'dilbert'
                    )
                )

生成(除sqlite以外的所有位置)::

SELECT companies.company_id AS companies_company_id, companies.name AS companies_name
FROM companies JOIN (
    people AS people_1
    LEFT OUTER JOIN engineers AS engineers_1 ON people_1.person_id = engineers_1.person_id
    LEFT OUTER JOIN managers AS managers_1 ON people_1.person_id = managers_1.person_id
) ON companies.company_id = people_1.company_id
WHERE engineers.primary_language = %(primary_language_1)s
    OR managers.manager_name = %(manager_name_1)s

#2369 #2587

右嵌套的内部联接在联接的热切加载中可用

从0.9.4版开始,如果连接的抢先加载中“外部”连接链接到右侧的“内部”,则可以启用上述右嵌套连接。

通常情况下,连接的热切负载链如下:

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

不会生成内部联接;由于“用户”-->order中的左外部联接,因此在不更改返回的用户行的情况下,joined-eager加载无法使用“订单”-->items中的内部联接,而是忽略“链接的” innerjoin=True 指令。0.9.0的交付方式应该是这样的,而不是:

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

新的“正确的嵌套连接是正常的”逻辑将启动,我们将得到:

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

由于我们错过了这条船,为了避免进一步的回归,我们通过指定字符串添加了上述功能 "nested"joinedload.innerjoin ::

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

此功能是0.9.4中的新功能。

#2976

ORM可以通过返回来有效地获取刚刚生成的插入/更新默认值

这个 Mapper 长期以来一直支持一个名为 eager_defaults=True . 此标志的效果是,当插入或更新继续进行时,并且已知该行具有服务器生成的默认值,则select将立即跟随它,以便“急切地”加载这些新值。通常,服务器生成的列在对象上标记为“已过期”,因此不会产生开销,除非应用程序在刷新后很快实际访问这些列。这个 eager_defaults 因此,标记的用处不大,因为它只会降低性能,并且仅用于支持特殊的事件方案,在这种情况下,用户需要默认值在刷新过程中立即可用。

在0.9中,由于版本ID增强, eager_defaults 现在可以为这些值发出返回子句,因此在具有强返回支持(尤其是PostgreSQL)的后端上,ORM可以在插入或更新时获取新生成的默认值和SQL表达式值。 eager_defaults 启用后,使用目标后端和 Table 支持“隐式返回”。

对于某些查询,子查询预加载将对最里面的select应用不同的方法。

为了减少在涉及多对一关系时通过子查询预加载可生成的重复行数,当联接以不包含主键的列为目标时,将对最内层的select应用distinct关键字,如在沿多对一加载时。

也就是说,当子查询从a->b加载到多对一时:

SELECT b.id AS b_id, b.name AS b_name, anon_1.b_id AS a_b_id
FROM (SELECT DISTINCT a_b_id FROM a) AS anon_1
JOIN b ON b.id = anon_1.a_b_id

自从 a.b_id 是非互异的外键,互异被应用以使 a.b_id 被淘汰。对于特定的对象,可以无条件地打开或关闭行为。 relationship() 使用旗 distinct_target_key ,将值设置为 True 无条件地, False 无条件关闭,以及 None 当目标选择是针对不包含完整主键的列时,此功能生效。0.9, None 是默认值。

选项也返回到0.8,其中 distinct_target_key 选项默认为 False .

虽然此处的功能旨在通过消除重复行来帮助性能,但是 DISTINCT SQL本身中的关键字可能会对性能产生负面影响。如果选择中的列没有索引, DISTINCT 可能会执行 ORDER BY 在可能很贵的行集上。通过将该特性限制在希望在任何情况下索引的外键上,我们期望新的默认值是合理的。

该特性也不能消除所有可能的重复行方案;如果连接链中的其他位置存在多对一方案,则重复行可能仍然存在。

#2836

backref处理程序现在可以传播多个级别的深度

属性事件通过其“启动器”(即与事件开始关联的对象)传递的机制已更改;而不是 AttributeImpl 正在传递,一个新对象 Event 而是传递;此对象引用 AttributeImpl 以及“操作令牌”,表示操作是追加、删除还是替换操作。

属性事件系统不再查看这个“启动器”对象以停止递归的一系列属性事件。相反,由于相互依赖的backref处理程序而阻止无限递归的系统已被移动到ORM backref事件处理程序中,后者现在接管了确保相互依赖的事件链(如追加到集合A.B,响应中设置多对一属性B.A)不会进入无限的re的角色。诅咒流。这里的基本原理是,如果backref系统提供了更详细的信息并对事件传播进行了控制,那么它最终可以允许进行超过一个级别的深度的操作;典型的情况是,集合追加会导致多对一的替换操作,这反过来会导致从以前的集合中删除该项:

class Parent(Base):
    __tablename__ = 'parent'

    id = Column(Integer, primary_key=True)
    children = relationship("Child", backref="parent")

class Child(Base):
    __tablename__ = 'child'

    id = Column(Integer, primary_key=True)
    parent_id = Column(ForeignKey('parent.id'))

p1 = Parent()
p2 = Parent()
c1 = Child()

p1.children.append(c1)

assert c1.parent is p1  # backref event establishes c1.parent as p1

p2.children.append(c1)

assert c1.parent is p2  # backref event establishes c1.parent as p2
assert c1 not in p1.children  # second backref event removes c1 from p1.children

以上,在此变更之前, c1 对象仍然存在于 p1.children ,即使它也存在于 p2.children 同时,backref处理程序将在替换时停止 c1.parent 具有 p2 而不是 p1 . 在0.9中,使用更详细的 Event 对象以及让backref处理程序对这些对象做出更详细的决定,传播可以继续删除 c1p1.children 同时对进入无休止递归循环的传播进行检查。

a.使用的最终用户代码 AttributeEvents.set()AttributeEvents.append()AttributeEvents.remove() 事件和B。由于这些事件可能需要修改以防止递归循环,因此启动进一步的属性修改操作,因为属性系统不再停止在缺少backref事件处理程序的情况下无休止地传播事件链。此外,依赖于 initiator 需要根据新的API进行调整,而且还必须准备好 initiator 在backref启动的事件字符串中从其原始值更改,因为backref处理程序现在可以在新的 initiator 某些操作的值。

#2789

打字系统现在处理呈现“文本绑定”值的任务。

将新方法添加到 TypeEngine TypeEngine.literal_processor() 以及 TypeDecorator.process_literal_param() 对于 TypeDecorator 它承担了呈现所谓“inline-literal参数”的任务——通常呈现为“bound”值的参数,但由于编译器的配置,这些参数被直接呈现到SQL语句中。此功能在为诸如 CheckConstraint ,以及在使用诸如 op.inline_literal() . 以前,一个简单的“isInstance”检查检查检查了一些基本类型,并且“bind processor”被无条件地使用,这导致了诸如字符串过早编码到utf-8的问题。

用编写的自定义类型 TypeDecorator 应该继续在“inline-literal”场景中工作,因为 TypeDecorator.process_literal_param() 回落到 TypeDecorator.process_bind_param() 默认情况下,因为这些方法通常处理数据操作,而不是向数据库显示数据的方式。 TypeDecorator.process_literal_param() 可以指定为专门生成一个字符串,该字符串表示值应如何呈现到内联DDL语句中。

#2838

模式标识符现在携带自己的引用信息

此更改简化了核心对所谓的“引号”标志的使用,例如 quote 标志传递到 TableColumn . 标志现在在字符串名称本身内部化,该字符串名称现在表示为 quoted_name 一个字符串子类。这个 IdentifierPreparer 现在只依赖于 quoted_name 对象而不是检查任何显式 quote 在大多数情况下,标记。这里解决的问题包括各种区分大小写的方法,例如 Engine.has_table() 以及方言中类似的方法,现在可以使用显式引用的名称运行,而无需复杂化或向后引入那些API(其中许多是第三方)的不兼容更改,以及引用标志的详细信息-特别是,更大范围的标识符现在可以使用所谓的“大写”后端L正常工作。ike oracle、firebird和db2(使用所有大写的不区分大小写名称存储和报告表名和列名的后端)。

这个 quoted_name 对象根据需要在内部使用;但是,如果其他关键字需要固定的引用首选项,则类是公开的。

#2812

改进了布尔常量、空常量和连词的呈现

新功能已添加到 true()false() 常数,特别是与 and_()or_() 函数以及WHERE/HAVING子句与这些类型、布尔类型和 null() 常数。

从这样的表开始:

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

t1 = Table('t', MetaData(), Column('x', Boolean()), Column('y', Integer))

select构造现在将布尔列呈现为后端不具有特征的二进制表达式。 true/false 恒定行为:

>>> from sqlalchemy import select, and_, false, true
>>> from sqlalchemy.dialects import mysql, postgresql

>>> print(select([t1]).where(t1.c.x).compile(dialect=mysql.dialect()))
SELECT t.x, t.y  FROM t WHERE t.x = 1

这个 and_()or_()true()false() 常量存在:

>>> print(select([t1]).where(and_(t1.c.y > 5, false())).compile(
...     dialect=postgresql.dialect()))
SELECT t.x, t.y FROM t WHERE false

true() 可以用作构建表达式的基::

>>> expr = true()
>>> expr = expr & (t1.c.y > 5)
>>> print(select([t1]).where(expr))
SELECT t.x, t.y FROM t WHERE t.y > :y_1

布尔常数 true()false() 它们自己呈现为 0 = 11 = 1 对于没有布尔常量的后端:

>>> print(select([t1]).where(and_(t1.c.y > 5, false())).compile(
...     dialect=mysql.dialect()))
SELECT t.x, t.y FROM t WHERE 0 = 1

解释 None 虽然不是特别有效的SQL,但至少现在一致:

>>> print(select([t1.c.x]).where(None))
SELECT t.x FROM t WHERE NULL

>>> print(select([t1.c.x]).where(None).where(None))
SELECT t.x FROM t WHERE NULL AND NULL

>>> print(select([t1.c.x]).where(and_(None, None)))
SELECT t.x FROM t WHERE NULL AND NULL

#2804

标签构造现在可以按顺序单独呈现为其名称

如果一个 Label 在columns子句和select的order by子句中都使用,如果基础方言报告支持此功能,则标签将在order by子句中呈现为其名称。

例如,例如:

from sqlalchemy.sql import table, column, select, func

t = table('t', column('c1'), column('c2'))
expr = (func.foo(t.c.c1) + t.c.c2).label("expr")

stmt = select([expr]).order_by(expr)

print(stmt)

0.9之前将呈现为:

SELECT foo(t.c1) + t.c2 AS expr
FROM t ORDER BY foo(t.c1) + t.c2

现在呈现为:

SELECT foo(t.c1) + t.c2 AS expr
FROM t ORDER BY expr

order by仅在标签未进一步嵌入order by中的表达式(而不是简单的 ASCDESC .

上述格式适用于所有测试过的数据库,但可能与较旧的数据库版本(MySQL4)存在兼容性问题。Oracle 8?等等)。根据用户报告,我们可以添加一些规则,这些规则将根据数据库版本检测禁用该功能。

#1068

RowProxy 现在有了元组排序行为

这个 RowProxy 对象的行为与元组非常相似,但如果使用 sorted() . 这个 __eq__() 方法现在将两边作为元组进行比较,并且 __lt__() 方法已添加::

users.insert().execute(
        dict(user_id=1, user_name='foo'),
        dict(user_id=2, user_name='bar'),
        dict(user_id=3, user_name='def'),
    )

rows = users.select().order_by(users.c.user_name).execute().fetchall()

eq_(rows, [(2, 'bar'), (3, 'def'), (1, 'foo')])

eq_(sorted(rows), [(1, 'foo'), (2, 'bar'), (3, 'def')])

#2848

当类型可用时,没有类型的bindparam()构造将通过copy进行升级。

“升级”的逻辑 bindparam() 采用封闭表达式类型的构造有两种改进方式。首先, bindparam() 对象是 已复制 在分配新类型之前,使 bindparam() 没有发生突变。其次,当 InsertUpdate 构造是编译的,关于在语句中通过 ValuesBase.values() 方法。

如果给定一个非类型化 bindparam() ::

bp = bindparam("some_col")

如果我们使用以下参数:

expr = mytable.c.col == bp

类型为 bp 仍然是 NullType 然而,如果 mytable.c.col 属于类型 String 然后 expr.right ,即二进制表达式的右侧,将 String 类型。以前, bp 它本身本来是可以改变的 String 作为其类型。

类似地,此操作发生在 InsertUpdate ::

stmt = mytable.update().values(col=bp)

上面, bp 保持不变,但 String 类型将在执行语句时使用,我们可以通过检查 binds 字典:

>>> compiled = stmt.compile()
>>> compiled.binds['some_col'].type
String

该特性允许自定义类型在insert/update语句中取得预期效果,而无需在每个 bindparam() 表达式。

可能向后兼容的变更涉及两种不太可能的情况。因为绑定参数是 克隆的 ,用户不应依赖于对 bindparam() 创建后立即构造。另外,代码使用 bindparam() 在一个 InsertUpdate 这是一个依靠事实的陈述 bindparam() 不是根据所分配的列类型键入的,将不再以这种方式工作。

#2850

列可以从通过foreignkey引用的列可靠地获取其类型

有一个长期存在的行为表明 Column 可以声明为不带类型,只要 Column 指的是 ForeignKeyConstraint ,引用列中的类型将复制到此列中。问题是,这个功能一直没有很好地工作,也没有得到维护。核心问题是 ForeignKey 对象不知道目标是什么 Column 它指的是直到被请求为止,通常是第一次使用外键构造 Join . 所以在那之前,父母 Column 不具有类型,或者更具体地说,它将具有默认类型 NullType .

虽然花费了很长时间,但是重新组织初始化的工作 ForeignKey 对象已完成,因此此功能最终可以正常工作。变化的核心是 ForeignKey.column 属性不再懒惰地初始化目标的位置 Column 这个系统的问题是 Column 会被卡住的 NullType 作为它的类型直到 ForeignKey 恰巧被使用了。

在新版本中, ForeignKey 与最终的坐标 Column 它将引用使用内部附件事件,以便 ColumnMetaData ,所有 ForeignKey 引用它的对象将发送一条消息,说明它们需要初始化其父列。这个系统更复杂,但工作更稳定;作为额外的好处,现在有各种各样的测试 Column / ForeignKey 配置方案和错误消息已经得到了改进,以非常具体地适用于不少于七种不同的错误条件。

现在正常工作的场景包括:

  1. A上的类型 Column 一旦目标出现 Column 与相同的 MetaData ;无论先配置哪一侧,这都有效:

    >>> from sqlalchemy import Table, MetaData, Column, Integer, ForeignKey
    >>> metadata = MetaData()
    >>> t2 = Table('t2', metadata, Column('t1id', ForeignKey('t1.id')))
    >>> t2.c.t1id.type
    NullType()
    >>> t1 = Table('t1', metadata, Column('id', Integer, primary_key=True))
    >>> t2.c.t1id.type
    Integer()
  2. 系统现在可与 ForeignKeyConstraint 也::

    >>> from sqlalchemy import Table, MetaData, Column, Integer, ForeignKeyConstraint
    >>> metadata = MetaData()
    >>> t2 = Table('t2', metadata,
    ...     Column('t1a'), Column('t1b'),
    ...     ForeignKeyConstraint(['t1a', 't1b'], ['t1.a', 't1.b']))
    >>> t2.c.t1a.type
    NullType()
    >>> t2.c.t1b.type
    NullType()
    >>> t1 = Table('t1', metadata,
    ...     Column('a', Integer, primary_key=True),
    ...     Column('b', Integer, primary_key=True))
    >>> t2.c.t1a.type
    Integer()
    >>> t2.c.t1b.type
    Integer()
  3. 它甚至适用于“多跳”—也就是说, ForeignKey 指的是 Column 指的是另一个 Column ::

    >>> from sqlalchemy import Table, MetaData, Column, Integer, ForeignKey
    >>> metadata = MetaData()
    >>> t2 = Table('t2', metadata, Column('t1id', ForeignKey('t1.id')))
    >>> t3 = Table('t3', metadata, Column('t2t1id', ForeignKey('t2.t1id')))
    >>> t2.c.t1id.type
    NullType()
    >>> t3.c.t2t1id.type
    NullType()
    >>> t1 = Table('t1', metadata, Column('id', Integer, primary_key=True))
    >>> t2.c.t1id.type
    Integer()
    >>> t3.c.t2t1id.type
    Integer()

#1765

方言变化

火鸟 fdb 现在是默认的火鸟方言。

这个 fdb 如果创建的引擎没有方言说明符,即 firebird:// . fdb 是一个 kinterbasdb 每个Firebird项目的兼容DBAPI现在是他们的官方python驱动程序。

#2504

火鸟 fdbkinterbasdb 设置 retaining=False 默认情况下

两个 fdbkinterbasdb DBAPIS支持标志 retaining=True 可以传递给 commit()rollback() 连接方法。这个标志的文档化的基本原理是,为了提高性能,DBAPI可以为后续事务重用内部事务状态。但是,更新的文档引用了Firebird的“垃圾收集”分析,它表示此标志可能对数据库处理清理任务的能力产生负面影响,并报告为 降低 结果就是表现。

考虑到这些信息,还不清楚这个标志是如何实际使用的,而且由于它似乎只是一个性能增强功能,所以现在默认为 False . 该值可以通过传递标志来控制 retaining=Truecreate_engine() 打电话。这是从0.8.2开始添加的新标志,因此0.8.2上的应用程序可以开始将其设置为 TrueFalse 根据需要。

参见

sqlalchemy.dialects.firebird.fdb

sqlalchemy.dialects.firebird.kinterbasdb

https://pythonhosted.org/fdb/usage-guide.html-有关“保留”标志的信息。

#2763