SqlAlchemy 2.0 中文文档(七十六)(4)https://developer.aliyun.com/article/1561152
关键行为更改 - 核心
将完整的 SQL 片段强制转换为 text() 时发出警告
自 SQLAlchemy 成立以来,一直强调不妨碍纯文本的使用。核心和 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()
构造,其中几乎所有组件都被指定为直接字符串。然而,尽管存在这种长期行为和示例,用户显然对这种行为感到惊讶,当在社区中询问时,我无法找到任何一个用户实际上不感到惊讶,即您可以将完整字符串发送到像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 警告过滤器:
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()
现在可以从“sqlalchemy”中导入,而不需要“sql”部分。
此行为也适用于select()
以及Query
上的关键方法,包括Query.filter()
、Query.from_statement()
和Query.having()
。
ORDER BY 和 GROUP BY 是特殊情况
有一种情况下,使用字符串具有特殊含义,并且作为这一变化的一部分,我们已增强了其功能。当我们有一个select()
或Query
引用某个列名或命名标签时,我们可能想要对已知列或标签进行 GROUP BY 和/或 ORDER BY:
stmt = ( select([user.c.name, func.count(user.c.id).label("id_count")]) .group_by("name") .order_by("id_count") )
在上面的语句中,我们期望看到“ORDER BY id_count”,而不是函数的重新声明。在编译过程中,给定的字符串参数会与列子句中的条目进行主动匹配,因此上述语句将按我们的期望产生,没有警告(尽管请注意,"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 子句以及引用列的 DISTINCT ON 子句中(例如 PostgreSQL 语法):
我们仍然可以使用text()
指定任意表达式用于 ORDER BY 或其他操作:
stmt = select([users]).order_by(text("some special expression"))
整个变化的结果是,SQLAlchemy 现在希望我们告诉它,当发送一个字符串时,这个字符串明确是一个text()
构造,或者是一个列、表等,如果我们在 order by、group by 或其他表达式中使用它作为标签名称,SQLAlchemy 期望该字符串解析为已知的内容,否则应再次用text()
或类似的方式进行限定。
#2992 ### 使用多值插入时,每行都会单独调用 Python 端的默认值
当使用Insert.values()
的多值版本时,对于 Python 端列默认值的支持基本上没有实现,并且只会在特定情况下“偶然”工作,当使用的方言使用非位置(例如命名)风格的绑定参数时,并且不需要为每一行调用 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 端的值;如果省略的值仅指向服务器端默认值,则会引发异常:
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()
现在意味着在insert()
上隐含inline=True
。这有助于修复一个 bug,即在支持的后端上,INSERT…FROM SELECT 结构会被错误地编译为“隐式返回”,这会导致在插入零行的情况下出现故障(因为隐式返回期望一行),以及在插入多行的情况下出现任意返回数据(例如,只有许多行中的第一行)。类似的更改也适用于具有多个参数集的 INSERT…VALUES;对于此语句,隐式 RETURNING 也不再发出。由于这两个结构处理可变数量的行,因此ResultProxy.inserted_primary_key
访问器不适用。以前,有一个文档注释,即对于 INSERT…FROM SELECT,可能更喜欢使用inline=True
,因为一些数据库不支持返回,因此无法进行“隐式”返回,但无论如何,INSERT…FROM SELECT 都不需要隐式返回。如果需要返回插入的数据的可变数量的结果行,则应使用常规的显式Insert.returning()
。
#3169 ### autoload_with
现在意味着autoload=True
通过仅传递 Table.autoload_with
,可以设置一个 Table
进行反射:
my_table = Table("my_table", metadata, autoload_with=some_engine)
#3027 ### DBAPI 异常封装和 handle_error() 事件改进
SQLAlchemy 对 DBAPI 异常的封装在以下情况下未发生:当一个 Connection
对象被失效,然后尝试重新连接并遇到错误时;这个问题已经解决了。
另外,最近添加的 ConnectionEvents.handle_error()
事件现在会在初始连接时、重新连接时以及通过 create_engine()
使用自定义连接函数时(通过 create_engine.creator
)调用。
ExceptionContext
对象现在有一个新的数据成员 ExceptionContext.engine
,在那些 Connection
对象不可用的情况下(例如在初始连接时)将始终引用正在使用的 Engine
。
#3266 ### ForeignKeyConstraint.columns 现在是 ColumnCollection
ForeignKeyConstraint.columns
以前是一个普通列表,其中包含字符串或根据ForeignKeyConstraint
如何构建以及是否与表关联而包含Column
对象。该集合现在是一个ColumnCollection
,并且仅在ForeignKeyConstraint
与Table
关联后才初始化。添加了一个新的访问器ForeignKeyConstraint.column_keys
,无条件地返回本地列集的字符串键,而不管对象是如何构建的或其当前状态如何。### MetaData.sorted_tables 访问器是“确定性的”
由MetaData.sorted_tables
访问器导致的表的排序是“确定性的”;无论 Python 哈希如何,排序在所有情况下都应该相同。这是通过首先按名称对表进行排序,然后将它们传递给拓扑算法来实现的,该算法在迭代时保持该顺序。
注意,这种变化尚未应用于在发出MetaData.create_all()
或MetaData.drop_all()
时应用的顺序。
#3084### null()、false()和 true()常量不再是单例
这三个常量在 0.9 中被更改为返回“单例”值;不幸的是,这会导致以下查询无法按预期渲染:
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,还修复了从临时表中反射 UNIQUE 约束的 bug,即 #3203。
#3204 ### 当将完整的 SQL 片段强制转换为文本时发出警告
自 SQLAlchemy 创建以来,始终强调不妨碍使用纯文本的方式。核心和 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()
构造,其中几乎所有组件都被指定为直接字符串。然而,尽管有这种长期存在的行为和示例,用户显然对此行为感到惊讶,当在社区中询问时,我无法找到任何一个用户实际上不感到惊讶的,即您可以将完整的字符串发送到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)
这些警告试图通过显示参数以及字符串接收位置的方式准确指出问题所在。这些警告利用了会话.get_bind()处理更广泛的继承场景,以便可以安全地发出参数化警告,而不会耗尽内存。如往常一样,如果希望将警告作为异常处理,应该使用Python 警告过滤器:
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")) )
还请注意,现在可以从“sqlalchemy”中导入table()
和column()
而不需要“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”,而不是函数的重新声明。在编译过程中,给定的字符串参数会被主动匹配到列子句中的条目,因此上述语句会按我们的期望产生结果,没有警告(尽管请注意"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 子句中以及引用列的 DISTINCT ON 子句中(例如 PostgreSQL 语法)。
我们仍然可以使用text()
指定任意表达式用于 ORDER BY 或其他操作:
stmt = select([users]).order_by(text("some special expression"))
整个更改的要点是,现在 SQLAlchemy 希望我们告诉它,当发送一个字符串时,这个字符串明确是一个text()
构造,或者是一个列、表等,如果我们将其用作 order by、group by 或其他表达式中的标签名称,SQLAlchemy 期望该字符串解析为已知内容,否则应再次使用text()
或类似内容进行限定。
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”,而不是函数的重新声明。在编译过程中,给定的字符串参数会被主动匹配到列子句中的条目,因此上述语句会按我们的期望产生结果,���有警告(尽管请注意"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 子句以及引用列的 DISTINCT ON 子句中(例如 PostgreSQL 语法)。
我们仍然可以使用text()
指定任意表达式用于 ORDER BY 或其他操作:
stmt = select([users]).order_by(text("some special expression"))
整个变化的结果是,SQLAlchemy 现在希望我们告诉它当发送一个字符串时,这个字符串明确是一个text()
构造,或者一个列、表等,如果我们将其用作 ORDER BY、GROUP BY 或其他表达式中的标签名称,SQLAlchemy 期望该字符串解析为已知的内容,否则应再次使用text()
或类似的进行限定。
当使用多值插入时,为每一行分别调用 Python 端默认值
当使用多值版本的Insert.values()
时,对于 Python 端列默认值的支持基本上没有实现,并且只会在特定情况下“偶然”起作用,当使用的方言采用非位置(例如命名)风格的绑定参数时,并且不需要为每一行调用 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 端值;如果省略的值只是引用服务器端默认值,则会引发异常:
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'}
事件监听器不能在该事件的运行程序内添加或移除
在同一事件中从内部移除事件侦听器会在迭代过程中修改列表的元素,这将导致仍然附加的事件侦听器无法静默触发。为了防止这种情况,同时保持性能,列表已被替换为 collections.deque()
,在迭代过程中不允许添加或删除,并且会引发 RuntimeError
。
INSERT…FROM SELECT 结构现在意味着 inline=True
在使用 Insert.from_select()
时,现在会隐含 inline=True
在 insert()
上。这有助于修复一个 bug,即 INSERT…FROM SELECT 结构会在支持的后端意外地被编译为“implicit returning”,这会导致在插入零行的情况下出现故障(因为 implicit returning 需要一行),以及在插入多行的情况下出现任意返回数据(例如,多行中的第一行)。类似的更改也适用于具有多个参数集的 INSERT…VALUES;此语句也不再发出 implicit RETURNING。由于这两个结构处理可变数量的行,因此 ResultProxy.inserted_primary_key
访问器不适用。以前,有一个文档说明,即在某些数据库不支持返回并且因此无法执行“implicit”返回的情况下,可能更喜欢使用 inline=True
与 INSERT…FROM SELECT,但无论如何,INSERT…FROM SELECT 都不需要 implicit returning。如果需要返回插入数据的可变数量的结果行,则应使用常规的显式 Insert.returning()
。
autoload_with
现在意味着 autoload=True
通过仅传递 Table.autoload_with
,可以为 Table
设置反射:
my_table = Table("my_table", metadata, autoload_with=some_engine)
DBAPI 异常包装和 handle_error() 事件改进
在 Connection
对象失效并尝试重新连接并遇到错误的情况下,SQLAlchemy 对 DBAPI 异常的包装未发生,这个问题已经解决。
此外,最近添加的ConnectionEvents.handle_error()
事件现在在初始连接时、重新连接时以及通过create_engine()
给定自定义连接函数时通过create_engine.creator
调用。
ExceptionContext
对象有一个新的数据成员ExceptionContext.engine
,它将始终引用正在使用的Engine
,在Connection
对象不可用的情况下(例如在初始连接时)。
ForeignKeyConstraint.columns 现在是一个 ColumnCollection
ForeignKeyConstraint.columns
以前是一个普通列表,其中包含字符串或Column
对象,取决于ForeignKeyConstraint
的构造方式以及是否与表相关联。该集合现在是一个ColumnCollection
,只有在ForeignKeyConstraint
与Table
相关联后才会初始化。新增了一个访问器ForeignKeyConstraint.column_keys
,无条件地返回本地列集的字符串键,而不管对象是如何构造的或其当前状态如何。
MetaData.sorted_tables 访问器是“确定性的”
由MetaData.sorted_tables
访问器导致的表的排序是“确定性的”;无论 Python 哈希如何,排序在所有情况下应该是相同的。这是通过首先按名称对表进行排序,然后将它们传递给拓扑算法来实现的,该算法在迭代时保持该顺序。
注意,此更改尚未应用于在发出MetaData.create_all()
或MetaData.drop_all()
时应用的排序。
null()、false()和 true()常量不再是单例
这三个常量在 0.9 中被更改为返回“单例”值;不幸的是,这将导致像以下查询一样的内容无法按预期渲染:
select([null(), null()])
仅渲染SELECT NULL AS anon_1
,因为两个null()
构造将产生相同的NULL
对象,并且 SQLAlchemy 的 Core 模型是基于对象标识来确定词法重要性的。0.9 中的变化除了希望节省对象开销外没有任何重要性;一般来说,一个未命名的构造需要保持词法上的唯一性,以便得到唯一的标记。
SQLite/Oracle 有用于临时表/视图名称报告的不同方法
在 SQLite/Oracle 的情况下,Inspector.get_table_names()
和Inspector.get_view_names()
方法还将返回临时表和视图的名称,这是其他方言不提供的(至少在 MySQL 的情况下甚至不可能)。这种逻辑已移至两个新方法Inspector.get_temp_table_names()
和Inspector.get_temp_view_names()
。
注意,特定命名的临时表或临时视图的反射,无论是通过Table('name', autoload=True)
还是通过Inspector.get_columns()
等方法,对大多数(如果不是全部)方言仍然有效。对于 SQLite 特别地,还修复了有关从临时表中反射 UNIQUE 约束的错误,这是#3203。
方言改进和变更 - PostgreSQL
枚举类型创建/删除规则的彻底改造
与创建和删除类型有关的 PostgreSQL ENUM
规则已经更加严格。
通过未明确与MetaData
对象关联创建的ENUM
将根据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’的枚举,上述 DROP 操作现在将失败。为了适应共享枚举类型的用例,元数据关联的枚举行为已经得到增强。
通过与MetaData
对象明确关联创建的ENUM
将不会根据Table.create()
和Table.drop()
创建或删除,除了使用checkfirst=True
标志调用的Table.create()
:
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
新的 PostgreSQL 表选项
在通过Table
构造渲染 DDL 时,添加了对 PG 表选项 TABLESPACE、ON COMMIT、WITH(OUT) OIDS 和 INHERITS 的支持。
另请参见
PostgreSQL 表选项
具有 PostgreSQL 方言的新 get_enums()方法
在 PostgreSQL 的情况下,inspect()
方法返回一个PGInspector
对象,其中包括一个新的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 方言反映了物化视图、外部表
更改如下:
- 使用
autoload=True
的Table
构造现在将匹配数据库中存在的作为物化视图或外部表的名称。 Inspector.get_view_names()
将返回普通和物化视图的名称。Inspector.get_table_names()
对于 PostgreSQL 不会发生变化,它继续只返回普通表的名称。- 添加了一个新方法
PGInspector.get_foreign_table_names()
,它将返回在 PostgreSQL 模式表中明确标记为“外部”的表的名称。
反射的变化涉及在查询 pg_class.relkind
时添加 'm'
和 'f'
到我们使用的修饰符列表,但这个变化是在 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 FILTER 关键字
SQL 标准的 FILTER 关键字现在由 PostgreSQL 支持,从 9.4 版开始。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
ENUM 类型创建/删除规则的全面改革
有关创建和删除 TYPE 的 PostgreSQL ENUM
的规则已经更加严格。
一个ENUM
如果没有明确与MetaData
对象关联,将根据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’的枚举,上述 DROP 操作现在将失败。为了适应共享枚举类型的使用情况,元数据关联的枚举行为已经增强。
一个ENUM
如果明确与MetaData
对象关联,将不会根据Table.create()
和Table.drop()
创建或删除,除非使用checkfirst=True
标志调用Table.create()
:
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
新的 PostgreSQL 表选项
添加了对 PG 表选项 TABLESPACE、ON COMMIT、WITH(OUT) OIDS 和 INHERITS 的支持,通过Table
构造渲染 DDL 时。
另请参阅
PostgreSQL 表选项
具有 PostgreSQL 方言的新 get_enums()方法
inspect()
方法在 PostgreSQL 情况下返回一个PGInspector
对象,其中包括一个新的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 方言反映了物化视图、外部表
更改如下:
- 使用
autoload=True
的Table
构造现在将匹配数据库中存在的物化视图或外部表的名称。 Inspector.get_view_names()
将返回普通视图和物化视图的名称。Inspector.get_table_names()
对于 PostgreSQL 不会发生变化,它仍然只返回普通表的名称。- 添加了一个新方法
PGInspector.get_foreign_table_names()
,它将返回在 PostgreSQL 模式表中明确标记为“外部”的表的名称。
反射的变化涉及在查询pg_class.relkind
时将'm'
和'f'
添加到我们使用的限定符列表中,但这个变化是在 1.0.0 版本中新增的,以避免对于在生产环境中运行 0.9 版本的用户造成任何不兼容的惊喜。
PostgreSQL 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)
PostgreSQL FILTER 关键字
作为 9.4 版本的 PostgreSQL 支持 SQL 标准的 FILTER 关键字。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 TIMESTAMP 类型现在在所有情况下都渲染为 NULL / NOT NULL
MySQL 方言始终通过为 nullable=True
设置的列发出 NULL 来解决与 TIMESTAMP 列关联的隐式 NOT NULL 默认值的问题。然而,MySQL 5.6.6 及以上版本具有一个新标志explicit_defaults_for_timestamp,修复了 MySQL 的非标准行为,使其像任何其他类型一样运行;为了适应这一点,SQLAlchemy 现在无条件地为所有 TIMESTAMP 列发出 NULL/NOT NULL。
另请参阅
TIMESTAMP 列和 NULL
#3155 ### MySQL SET 类型进行了全面改进,以支持空集、unicode、空值处理
SET
类型历史上没有包括处理空集和空值的系统;由于不同的驱动程序对空字符串和空字符串集表示的处理方式不同,因此 SET 类型尝试只在这些行为之间进行权衡,选择将空集视为 set([''])
,这仍然是 MySQL-Connector-Python DBAPI 的当前行为。其中部分理由是否则无法实际在 MySQL SET 中存储空字符串,因为驱动程序返回没有办法区分 set([''])
和 set()
的字符串。留给用户确定 set([''])
实际上是否意味着“空集”或其他情况。
新行为将使用空字符串的用例(这是一个不寻常的情况,甚至在 MySQL 的文档中都没有记录),移入特殊情况中,而SET
的默认行为现在是:
- 将由 MySQL-python 返回的空字符串
''
视为空集set()
; - 将由 MySQL-Connector-Python 返回的单空值集
set([''])
转换为空集set()
; - 为了处理实际希望包含空值
''
在其可能值列表中的集合类型的情况,实施了一个新功能(在这种情况下是必需的),即将集合值持久化和加载为位整数值;添加了标志SET.retrieve_as_bitwise
以启用此功能。
使用 SET.retrieve_as_bitwise
标志允许集合以无歧义的值进行持久化和检索。理论上,只要给定的值列表与数据库中声明的顺序完全匹配,就可以在所有情况下打开此标志;它只是使 SQL 回显输出有点不寻常。
否则,SET
的默认行为保持不变,使用字符串循环传递值。基于字符串的行为现在完全支持 Unicode,包括 MySQL-python 使用 use_unicode=0
。
MySQL 内部的“无此表”异常不会传递给事件处理程序
MySQL 方言现在将禁用 ConnectionEvents.handle_error()
事件,以防止这些语句触发用于内部检测表是否存在的事件。这是通过使用一个执行选项 skip_user_error_events
实现的,该选项在该执行范围内禁用了处理错误事件。这样,重写异常的用户代码不需要担心 MySQL 方言或其他偶尔需要捕获 SQLAlchemy 特定异常的方言。
更改了 MySQL-Connector 的 raise_on_warnings
的默认值
将 MySQL-Connector 的 “raise_on_warnings” 的默认值更改为 False。出于某种原因,它被设置为 True。不幸的是,“buffered” 标志必须保持为 True,因为 MySQLconnector 不允许关闭游标,除非所有结果都被完全获取。
MySQL 布尔符号“true”、“false”再次有效
0.9 版本对 IS/IS NOT 操作符以及 #2682 中的布尔类型进行了彻底改造,禁止 MySQL 方言在“IS”/“IS NOT”的上下文中使用“true”和“false”符号。显然,即使 MySQL 没有“布尔”类型,但当使用特殊的“true”和“false”符号时,它支持 IS/IS NOT,尽管这些符号在其他情况下与“1”和“0”是同义词(并且 IS/IS NOT 与数字不兼容)。
因此,这里的更改是 MySQL 方言仍然是“非本地布尔”,但 true()
和 false()
符号再次生成关键字“true”和“false”,因此像 column.is_(true())
这样的表达式再次在 MySQL 上起作用。
#3186 ### match()
操作符现在返回与 MySQL 浮点返回值兼容的不可知 MatchType
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 Dialect is now an External Dialect
Drizzle的方言现在是一个外部方言,可在bitbucket.org/zzzeek/sqlalchemy-drizzle
上找到。这个方言是在 SQLAlchemy 能够很好地适应第三方方言之前添加到 SQLAlchemy 的;未来,所有不属于“普遍使用”类别的数据库都是第三方方言。该方言的实现没有改变,仍然基于 SQLAlchemy 中的 MySQL + MySQLdb 方言。该方言目前尚未发布,处于“attic”状态;但是它通过了大部分测试,一般工作正常,如果有人想要继续完善它的话。 ### MySQL TIMESTAMP Type now renders NULL / NOT NULL in all cases
MySQL 方言一直通过在nullable=True
的情况下为 TIMESTAMP 列发出 NULL 来解决 MySQL 隐式 NOT NULL 默认值的问题。然而,MySQL 5.6.6 及以上版本引入了一个新标志explicit_defaults_for_timestamp,修复了 MySQL 的非标准行为,使其表现得像任何其他类型;为了适应这一点,SQLAlchemy 现在无条件地为所有 TIMESTAMP 列发出 NULL/NOT NULL。
另请参见
TIMESTAMP Columns and NULL
MySQL SET Type Overhauled to support empty sets, unicode, blank value handling
SET
类型历史上不包括处理空集和空值的系统;由于不同的驱动程序对空字符串和空字符串集表示的处理方式不同,因此 SET 类型仅尝试在这些行为之间做出取舍,选择将空集视为set([''])
,这仍然是 MySQL-Connector-Python DBAPI 的当前行为。这样做的部分原因是,否则无法实际在 MySQL SET 中存储空字符串,因为驱动程序返回的字符串没有办法区分set([''])
和set()
之间的区别。用户需要确定set([''])
是否实际上表示“空集”。
新行为将空字符串的使用情况移至一个特殊情况,这是一个不常见的情况,甚至在 MySQL 的文档中也没有记录,而SET
的默认行为现在是:
- 将由 MySQL-python 返回的空字符串
''
视为空集set()
; - 将 MySQL-Connector-Python 返回的单个空值集
set([''])
转换为空集set()
。 - 为了处理实际希望在其可能值列表中包含空值
''
的集合类型的情况,实现了一个新特性(在此用例中需要),即集合值被持久化并作为位整数值加载;添加了标志SET.retrieve_as_bitwise
以启用此功能。
使用标志SET.retrieve_as_bitwise
允许集合被持久化和检索而没有值的歧义。从理论上讲,只要类型的给定值列表与数据库中声明的顺序完全匹配,就可以在所有情况下打开此标志;它只会使 SQL 回显输出略显不同寻常。
否则,SET
的默认行为保持不变,使用字符串往返值。基于字符串的行为现在完全支持 Unicode,包括 MySQL-python,并且使用use_unicode=0
。
MySQL 内部的“无此表”异常不会传递给事件处理程序。
MySQL 方言现在将禁用 ConnectionEvents.handle_error()
事件对内部用于检测表是否存在的语句触发。这是通过使用一个执行选项 skip_user_error_events
来实现的,该选项在该执行范围内禁用了处理错误事件。这样,重写异常的用户代码不需要担心 MySQL 方言或其他偶尔需要捕获 SQLAlchemy 特定异常的方言。
更改了 MySQL-Connector 的 raise_on_warnings
默认值
将 MySQL-Connector 的“raise_on_warnings”默认值更改为 False。由于某种原因,这被设置为 True。不幸的是,“buffered” 标志必须保持为 True,因为 MySQLconnector 不允许关闭游标,除非所有结果都被完全获取。
MySQL 布尔符号“true”、“false”再次生效
0.9 版本对 IS/IS NOT 操作符以及布尔类型的改进在 #2682 中禁止了 MySQL 方言在“IS”/“IS NOT”上使用“true”和“false”符号。显然,即使 MySQL 没有“布尔”类型,但在使用特殊的“true”和“false”符号时,它支持 IS/IS NOT,尽管这些符号在其他情况下与“1”和“0”是同义的(并且 IS/IS NOT 不能与数字一起使用)。
这里的变化是 MySQL 方言仍然保持“非本地布尔”,但 true()
和 false()
符号再次产生关键字“true”和“false”,因此像 column.is_(true())
这样的表达式在 MySQL 上再次生效。
match()
操作符现在返回一个与 MySQL 浮点返回值兼容的不可知的 MatchType
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') ]
Drizzle 方言现在是外部方言
Drizzle 的方言现在是一个外部方言,可在 bitbucket.org/zzzeek/sqlalchemy-drizzle
上获得。这个方言是在 SQLAlchemy 能够很好地适应第三方方言之前添加到 SQLAlchemy 的;未来,所有不属于“普遍使用”类别的数据库都是第三方方言。方言的实现没有改变,仍然基于 SQLAlchemy 中的 MySQL + MySQLdb 方言。该方言尚未发布,处于“attic”状态;但是它通过了大部分测试,通常工作正常,如果有人想要继续完善它。
方言改进和更改 - SQLite
SQLite 命名和未命名的 UNIQUE 和 FOREIGN KEY 约束将进行检查和反映
在 SQLite 上,现在完全反映了具有和不具有名称的 UNIQUE 和 FOREIGN KEY 约束。以前,外键名称被忽略,未命名的唯一约束被跳过。特别是这将有助于 Alembic 的新 SQLite 迁移功能。
为了实现这一点,对于外键和唯一约束,PRAGMA foreign_keys、index_list 和 index_info 的结果与对 CREATE TABLE 语句的正则表达式解析相结合,以形成对约束名称的完整了解,以及区分作为 UNIQUE 创建的 UNIQUE 约束与未命名的 INDEX。
SQLite 命名和未命名的 UNIQUE 和 FOREIGN KEY 约束将进行检查和反映
在 SQLite 上,现在完全反映了具有和不具有名称的 UNIQUE 和 FOREIGN KEY 约束。以前,外键名称被忽略,未命名的唯一约束被跳过。特别是这将有助于 Alembic 的新 SQLite 迁移功能。
为了实现这一点,对于外键和唯一约束,PRAGMA foreign_keys、index_list 和 index_info 的结果与对 CREATE TABLE 语句的正则表达式解析相结合,以形成对约束名称的完整了解,以及区分作为 UNIQUE 创建的 UNIQUE 约束与未命名的 INDEX。
方言改进和更改 - SQL Server
需要在基于主机名的 SQL Server 连接中提供 PyODBC 驱动程序名称
使用无 DSN 连接的 PyODBC 连接到 SQL Server,例如使用显式主机名,现在需要提供驱动程序名称 - SQLAlchemy 将不再尝试猜测默认值:
engine = create_engine( "mssql+pyodbc://scott:tiger@myhost:port/databasename?driver=SQL+Server+Native+Client+10.0" )
SQLAlchemy 在 Windows 上以前硬编码的默认值“SQL Server”已经过时,SQLAlchemy 不能根据操作系统/驱动程序检测来猜测最佳驱动程序。在使用 ODBC 时,始终首选使用 DSN 来避免这个问题。
SQL Server 2012 大型文本/二进制类型呈现为 VARCHAR、NVARCHAR、VARBINARY
SQL Server 2012 及更高版本的 TextClause
、UnicodeText
和 LargeBinary
类型的呈现已更改,完全控制行为的选项,基于微软的弃用指南。详情请参阅 大型文本/二进制类型弃用。
在基于主机名的 SQL Server 连接中需要 PyODBC 驱动程序名称
使用无 DSN 连接的方式连接到 SQL Server,例如使用显式主机名,现在需要驱动程序名称 - SQLAlchemy 不再尝试猜测默认值:
engine = create_engine( "mssql+pyodbc://scott:tiger@myhost:port/databasename?driver=SQL+Server+Native+Client+10.0" )
SQLAlchemy 在 Windows 上先前固定的默认值“SQL Server”已经过时,不能根据操作系统/驱动程序检测猜测最佳驱动程序。使用 DSN 总是首选,以避免此问题。
SQL Server 2012 大型文本/二进制类型呈现为 VARCHAR、NVARCHAR、VARBINARY
SQL Server 2012 及更高版本的 TextClause
、UnicodeText
和 LargeBinary
类型的呈现已更改,完全控制行为的选项,基于微软的弃用指南。详情请参阅 大型文本/二进制类型弃用。
Oracle 方言的改进和变化 - 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", ) )
新的 Oracle DDL 关键字
关键词如 COMPRESS、ON COMMIT、BITMAP:
Oracle 表选项
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", ) )
新的 Oracle DDL 关键字
关键词如 COMPRESS、ON COMMIT、BITMAP:
Oracle 表选项
Oracle 特定索引选项
“空集”。
新行为将空字符串的使用情况移至一个特殊情况,这是一个不常见的情况,甚至在 MySQL 的文档中也没有记录,而SET
的默认行为现在是:
- 将由 MySQL-python 返回的空字符串
''
视为空集set()
; - 将 MySQL-Connector-Python 返回的单个空值集
set([''])
转换为空集set()
。 - 为了处理实际希望在其可能值列表中包含空值
''
的集合类型的情况,实现了一个新特性(在此用例中需要),即集合值被持久化并作为位整数值加载;添加了标志SET.retrieve_as_bitwise
以启用此功能。
使用标志SET.retrieve_as_bitwise
允许集合被持久化和检索而没有值的歧义。从理论上讲,只要类型的给定值列表与数据库中声明的顺序完全匹配,就可以在所有情况下打开此标志;它只会使 SQL 回显输出略显不同寻常。
否则,SET
的默认行为保持不变,使用字符串往返值。基于字符串的行为现在完全支持 Unicode,包括 MySQL-python,并且使用use_unicode=0
。
MySQL 内部的“无此表”异常不会传递给事件处理程序。
MySQL 方言现在将禁用 ConnectionEvents.handle_error()
事件对内部用于检测表是否存在的语句触发。这是通过使用一个执行选项 skip_user_error_events
来实现的,该选项在该执行范围内禁用了处理错误事件。这样,重写异常的用户代码不需要担心 MySQL 方言或其他偶尔需要捕获 SQLAlchemy 特定异常的方言。
更改了 MySQL-Connector 的 raise_on_warnings
默认值
将 MySQL-Connector 的“raise_on_warnings”默认值更改为 False。由于某种原因,这被设置为 True。不幸的是,“buffered” 标志必须保持为 True,因为 MySQLconnector 不允许关闭游标,除非所有结果都被完全获取。
MySQL 布尔符号“true”、“false”再次生效
0.9 版本对 IS/IS NOT 操作符以及布尔类型的改进在 #2682 中禁止了 MySQL 方言在“IS”/“IS NOT”上使用“true”和“false”符号。显然,即使 MySQL 没有“布尔”类型,但在使用特殊的“true”和“false”符号时,它支持 IS/IS NOT,尽管这些符号在其他情况下与“1”和“0”是同义的(并且 IS/IS NOT 不能与数字一起使用)。
这里的变化是 MySQL 方言仍然保持“非本地布尔”,但 true()
和 false()
符号再次产生关键字“true”和“false”,因此像 column.is_(true())
这样的表达式在 MySQL 上再次生效。
match()
操作符现在返回一个与 MySQL 浮点返回值兼容的不可知的 MatchType
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') ]
Drizzle 方言现在是外部方言
Drizzle 的方言现在是一个外部方言,可在 bitbucket.org/zzzeek/sqlalchemy-drizzle
上获得。这个方言是在 SQLAlchemy 能够很好地适应第三方方言之前添加到 SQLAlchemy 的;未来,所有不属于“普遍使用”类别的数据库都是第三方方言。方言的实现没有改变,仍然基于 SQLAlchemy 中的 MySQL + MySQLdb 方言。该方言尚未发布,处于“attic”状态;但是它通过了大部分测试,通常工作正常,如果有人想要继续完善它。
方言改进和更改 - SQLite
SQLite 命名和未命名的 UNIQUE 和 FOREIGN KEY 约束将进行检查和反映
在 SQLite 上,现在完全反映了具有和不具有名称的 UNIQUE 和 FOREIGN KEY 约束。以前,外键名称被忽略,未命名的唯一约束被跳过。特别是这将有助于 Alembic 的新 SQLite 迁移功能。
为了实现这一点,对于外键和唯一约束,PRAGMA foreign_keys、index_list 和 index_info 的结果与对 CREATE TABLE 语句的正则表达式解析相结合,以形成对约束名称的完整了解,以及区分作为 UNIQUE 创建的 UNIQUE 约束与未命名的 INDEX。
SQLite 命名和未命名的 UNIQUE 和 FOREIGN KEY 约束将进行检查和反映
在 SQLite 上,现在完全反映了具有和不具有名称的 UNIQUE 和 FOREIGN KEY 约束。以前,外键名称被忽略,未命名的唯一约束被跳过。特别是这将有助于 Alembic 的新 SQLite 迁移功能。
为了实现这一点,对于外键和唯一约束,PRAGMA foreign_keys、index_list 和 index_info 的结果与对 CREATE TABLE 语句的正则表达式解析相结合,以形成对约束名称的完整了解,以及区分作为 UNIQUE 创建的 UNIQUE 约束与未命名的 INDEX。
方言改进和更改 - SQL Server
需要在基于主机名的 SQL Server 连接中提供 PyODBC 驱动程序名称
使用无 DSN 连接的 PyODBC 连接到 SQL Server,例如使用显式主机名,现在需要提供驱动程序名称 - SQLAlchemy 将不再尝试猜测默认值:
engine = create_engine( "mssql+pyodbc://scott:tiger@myhost:port/databasename?driver=SQL+Server+Native+Client+10.0" )
SQLAlchemy 在 Windows 上以前硬编码的默认值“SQL Server”已经过时,SQLAlchemy 不能根据操作系统/驱动程序检测来猜测最佳驱动程序。在使用 ODBC 时,始终首选使用 DSN 来避免这个问题。
SQL Server 2012 大型文本/二进制类型呈现为 VARCHAR、NVARCHAR、VARBINARY
SQL Server 2012 及更高版本的 TextClause
、UnicodeText
和 LargeBinary
类型的呈现已更改,完全控制行为的选项,基于微软的弃用指南。详情请参阅 大型文本/二进制类型弃用。
在基于主机名的 SQL Server 连接中需要 PyODBC 驱动程序名称
使用无 DSN 连接的方式连接到 SQL Server,例如使用显式主机名,现在需要驱动程序名称 - SQLAlchemy 不再尝试猜测默认值:
engine = create_engine( "mssql+pyodbc://scott:tiger@myhost:port/databasename?driver=SQL+Server+Native+Client+10.0" )
SQLAlchemy 在 Windows 上先前固定的默认值“SQL Server”已经过时,不能根据操作系统/驱动程序检测猜测最佳驱动程序。使用 DSN 总是首选,以避免此问题。
SQL Server 2012 大型文本/二进制类型呈现为 VARCHAR、NVARCHAR、VARBINARY
SQL Server 2012 及更高版本的 TextClause
、UnicodeText
和 LargeBinary
类型的呈现已更改,完全控制行为的选项,基于微软的弃用指南。详情请参阅 大型文本/二进制类型弃用。
Oracle 方言的改进和变化 - 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", ) )
新的 Oracle DDL 关键字
关键词如 COMPRESS、ON COMMIT、BITMAP:
Oracle 表选项
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", ) )
新的 Oracle DDL 关键字
关键词如 COMPRESS、ON COMMIT、BITMAP:
Oracle 表选项
Oracle 特定索引选项