SqlAlchemy 2.0 中文文档(五十七)(9)

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS SQL Server Serverless,2-4RCU 50GB 3个月
推荐场景:
云数据库 RDS SQL Server,基础系列 2核4GB
简介: SqlAlchemy 2.0 中文文档(五十七)

SqlAlchemy 2.0 中文文档(五十七)(8)https://developer.aliyun.com/article/1563170


替换具有相同名称和键的 Table 对象中的 Columns 的更严格规则

对于将 Column 对象附加到 Table 对象,有更严格的规则,将一些先前的弃用警告转移到异常中,并防止一些先前可能导致表中出现重复列的情况,当 Table.extend_existing 设置为 True 时,对于编程方式的 Table 构建以及在反射操作期间。

  • 无论如何,Table 对象都不应该具有两个或更多具有相同名称的 Column 对象,无论它们的 .key 如何。已经确定并修复了仍然可能发生此情况的边缘情况。
  • 向具有与现有 Column 相同名称或键的 Table 添加 Column 将始终引发 DuplicateColumnError(在 2.0.0b4 中是 ArgumentError 的新子类),除非存在额外参数;对于 Table.append_column(),使用 Table.append_column.replace_existing,以及对于构建具有与现有 Table 相同名称的 Table(使用或不使用反射)时使用 Table.extend_existing。此前,针对此情况已经放置了弃用警告。
  • 如果创建了一个包含Table.extend_existingTable,并且存在一个没有单独的Column.key的传入Column,该传入的Column将完全替换具有键的现有Column,这表明操作不是用户所期望的。这在特别是在次要反射步骤期间可能发生,例如metadata.reflect(extend_existing=True)。警告建议将Table.autoload_replace参数设置为False以防止这种情况发生。在 1.4 及以前的版本中,传入的列会额外添加到现有列中。这是一个错误,在 2.0(截至 2.0.0b4)中是一种行为变更,因为在这种情况发生时,以前的键将不再存在于列集合中。

#8925

ORM 声明式不同的列顺序应用方式;使用sort_order控制行为

声明式已更改了来自混合或抽象基类的映射列与声明类本身上的列一起排序的系统,以便首先放置来自声明类的列,然后是混合列。以下映射:

class Foo:
    col1 = mapped_column(Integer)
    col3 = mapped_column(Integer)
class Bar:
    col2 = mapped_column(Integer)
    col4 = mapped_column(Integer)
class Model(Base, Foo, Bar):
    id = mapped_column(Integer, primary_key=True)
    __tablename__ = "model"

在 1.4 上产生的 CREATE TABLE 如下:

CREATE  TABLE  model  (
  col1  INTEGER,
  col3  INTEGER,
  col2  INTEGER,
  col4  INTEGER,
  id  INTEGER  NOT  NULL,
  PRIMARY  KEY  (id)
)

而在 2.0 上它产生:

CREATE  TABLE  model  (
  id  INTEGER  NOT  NULL,
  col1  INTEGER,
  col3  INTEGER,
  col2  INTEGER,
  col4  INTEGER,
  PRIMARY  KEY  (id)
)

对于上述特定情况,这可以视为一种改进,因为Model上的主键列现在位于人们通常更喜欢的位置。然而,对于以相反方式定义模型的应用程序来说,这并没有什么安慰,因为:

class Foo:
    id = mapped_column(Integer, primary_key=True)
    col1 = mapped_column(Integer)
    col3 = mapped_column(Integer)
class Model(Foo, Base):
    col2 = mapped_column(Integer)
    col4 = mapped_column(Integer)
    __tablename__ = "model"

现在,这将产生以下 CREATE TABLE 输出:

CREATE  TABLE  model  (
  col2  INTEGER,
  col4  INTEGER,
  id  INTEGER  NOT  NULL,
  col1  INTEGER,
  col3  INTEGER,
  PRIMARY  KEY  (id)
)

为解决此问题,SQLAlchemy 2.0.4 引入了mapped_column()上的一个新参数,称为mapped_column.sort_order,它是一个整数值,默认为0,可以设置为正值或负值,以便将列放置在其他列之前或之后,如下例所示:

class Foo:
    id = mapped_column(Integer, primary_key=True, sort_order=-10)
    col1 = mapped_column(Integer, sort_order=-1)
    col3 = mapped_column(Integer)
class Model(Foo, Base):
    col2 = mapped_column(Integer)
    col4 = mapped_column(Integer)
    __tablename__ = "model"

上述模型将“id”放置在所有其他列之前,将“col1”放置在“id”之后:

CREATE  TABLE  model  (
  id  INTEGER  NOT  NULL,
  col1  INTEGER,
  col2  INTEGER,
  col4  INTEGER,
  col3  INTEGER,
  PRIMARY  KEY  (id)
)

未来的 SQLAlchemy 版本可能选择为mapped_column构造提供显式排序提示,因为此排序是 ORM 特定的。

Sequence 构造不再具有任何显式默认的“start”值;影响 MS SQL Server

在 SQLAlchemy 1.4 之前,Sequence 构造将仅在未指定其他参数时发出简单的 CREATE SEQUENCE DDL:

>>> # SQLAlchemy 1.3 (and 2.0)
>>> from sqlalchemy import Sequence
>>> from sqlalchemy.schema import CreateSequence
>>> print(CreateSequence(Sequence("my_seq")))
CREATE  SEQUENCE  my_seq 

但是,由于为 MS SQL Server 添加了 Sequence 支持,其中默认起始值不方便地设置为 -2**63,版本 1.4 决定将 DDL 默认为发出起始值为 1,如果未提供 Sequence.start

>>> # SQLAlchemy 1.4 (only)
>>> from sqlalchemy import Sequence
>>> from sqlalchemy.schema import CreateSequence
>>> print(CreateSequence(Sequence("my_seq")))
CREATE  SEQUENCE  my_seq  START  WITH  1 

此更改引入了其他复杂性,包括当包含 Sequence.min_value 参数时,默认值为 1 实际上应默认为 Sequence.min_value 所述的内容,否则,将看到低于 start_value 的 min_value 可能被视为矛盾。由于研究此问题开始变得有点复杂,包括各种其他边缘情况,我们决定撤消此更改,并恢复 Sequence 的原始行为,即不表达任何观点,只是发出 CREATE SEQUENCE,允许数据库本身决定如何处理 SEQUENCE 的各种参数之间的交互。

因此,为了确保所有后端的起始值都为 1,可能需要显式指定起始值为 1,如下所示:

>>> # All SQLAlchemy versions
>>> from sqlalchemy import Sequence
>>> from sqlalchemy.schema import CreateSequence
>>> print(CreateSequence(Sequence("my_seq", start=1)))
CREATE  SEQUENCE  my_seq  START  WITH  1 

此外,在现代后端(包括 PostgreSQL、Oracle、SQL Server)上自动生成整数主键时,应优先使用 Identity 构造,在 1.4 和 2.0 中也以相同方式工作,行为没有任何更改。

#7211

“with_variant()” 克隆原始 TypeEngine 而不是更改类型

TypeEngine.with_variant() 方法用于将特定类型应用于数据库的备用行为,现在返回原始 TypeEngine 对象的副本,并在内部存储变体信息,而不是将其包装在 Variant 类中。

虽然以前的 Variant 方法能够使用动态属性获取器维护原始类型的所有 Python 行为,但这里的改进是,当调用变体时,返回的类型仍然是原始类型的实例,这与诸如 mypy 和 pylance 的类型检查器更加顺畅地配合。给定以下程序:

import typing
from sqlalchemy import String
from sqlalchemy.dialects.mysql import VARCHAR
type_ = String(255).with_variant(VARCHAR(255, charset="utf8mb4"), "mysql", "mariadb")
if typing.TYPE_CHECKING:
    reveal_type(type_)

类型检查器如 pyright 现在将报告类型为:

info: Type of "type_" is "String"

此外,如上所示,对于单个类型,可以传递多个方言名称,特别是对于被视为分开的"mysql""mariadb"方言的成对,这对于 SQLAlchemy 1.4 很有帮助。

#6980

Python 除法运算符对所有后端执行真除法;添加地板除法

核心表达式语言现在支持“真除法”(即 / Python 运算符)和“地板除法”(即 // Python 运算符),包括标准化此方面不同数据库的后端特定行为。

给定两个整数值进行“真除法”操作:

expr = literal(5, Integer) / literal(10, Integer)

PostgreSQL 上的 SQL 除法运算符通常在对整数进行操作时作为“地板除法”(floor division)操作,意味着以上结果将返回整数“0”。对于此类后端,SQLAlchemy 现在使用等效于以下形式的 SQL 来渲染 SQL:

%(param_1)s  /  CAST(%(param_2)s  AS  NUMERIC)

使用 param_1=5param_2=10,以便返回表达式将是 NUMERIC 类型,通常为 Python 值 decimal.Decimal("0.5")

给定两个整数值进行“地板除法”操作:

expr = literal(5, Integer) // literal(10, Integer)

MySQL 和 Oracle 上的 SQL 除法运算符通常在对整数进行操作时作为“真除法”(true division)操作,意味着以上结果将返回浮点值“0.5”。对于这些和类似的后端,SQLAlchemy 现在使用等效于以下形式的 SQL 渲染 SQL:

FLOOR(%(param_1)s  /  %(param_2)s)

使用 param_1=5,param_2=10,以便返回表达式将是整数类型,就像 Python 值 0 一样。

此处的不兼容变更将是,如果一个应用程序使用 PostgreSQL、SQL Server 或 SQLite,并且依赖于 Python 的“truediv”运算符在所有情况下返回整数值。依赖于此行为的应用程序应该使用 Python 的“地板除法”运算符 // 进行这些操作,或者在使用之前的 SQLAlchemy 版本时进行前向兼容,使用 floor 函数:

expr = func.floor(literal(5, Integer) / literal(10, Integer))

在 SQLAlchemy 版本 2.0 之前的任何版本中,将需要上述形式以提供与后端无关的地板除法。

#4926

Session 主动提出当检测到非法并发或重入访问时

Session 现在可以更全面地捕获与多线程或其他并发场景中的非法并发状态更改相关的错误,以及执行意外状态更改的事件钩子。

已知的一个错误是当一个Session同时在多个线程中使用时会出现AttributeError: 'NoneType' object has no attribute 'twophase',这完全是神秘的。当一个线程调用Session.commit()时,内部调用SessionTransaction.close()方法来结束事务上下文,与此同时另一个线程正在运行一个查询,如Session.execute()。在Session.execute()中,获取当前事务的数据库连接的内部方法首先开始断言会话是“活动的”,但在此断言通过后,同时进行的对Session.close()的调用干扰了这种状态,导致上述未定义的条件。

更改将应用到围绕SessionTransaction对象的所有更改状态方法的保护措施,以便在上述情况下,Session.commit()方法将会失败,因为它将试图将状态更改为在已经进行中的方法期间不允许的状态,而该方法希望获取当前连接以运行数据库查询。

使用在#7433中展示的测试脚本,先前的错误案例如下:

Traceback (most recent call last):
File "/home/classic/dev/sqlalchemy/test3.py", line 30, in worker
    sess.execute(select(A)).all()
File "/home/classic/tmp/sqlalchemy/lib/sqlalchemy/orm/session.py", line 1691, in execute
    conn = self._connection_for_bind(bind)
File "/home/classic/tmp/sqlalchemy/lib/sqlalchemy/orm/session.py", line 1532, in _connection_for_bind
    return self._transaction._connection_for_bind(
File "/home/classic/tmp/sqlalchemy/lib/sqlalchemy/orm/session.py", line 754, in _connection_for_bind
    if self.session.twophase and self._parent is None:
AttributeError: 'NoneType' object has no attribute 'twophase'

_connection_for_bind()方法无法继续运行时,因为并发访问使其处于无效状态。使用新方法,状态更改的发起者会抛出错误:

File "/home/classic/dev/sqlalchemy/lib/sqlalchemy/orm/session.py", line 1785, in close
   self._close_impl(invalidate=False)
File "/home/classic/dev/sqlalchemy/lib/sqlalchemy/orm/session.py", line 1827, in _close_impl
   transaction.close(invalidate)
File "<string>", line 2, in close
File "/home/classic/dev/sqlalchemy/lib/sqlalchemy/orm/session.py", line 506, in _go
   raise sa_exc.InvalidRequestError(
sqlalchemy.exc.InvalidRequestError: Method 'close()' can't be called here;
method '_connection_for_bind()' is already in progress and this would cause
an unexpected state change to symbol('CLOSED')

状态转换检查故意不使用显式锁来检测并发线程活动,而是依赖于简单的属性设置/值测试操作,当发生意外的并发更改时会自然失败。其理念是该方法可以检测到完全发生在单个线程内的非法状态更改,例如运行在会话事务事件上的事件处理程序调用了一个未预期的改变状态的方法,或者在  asyncio 中,如果一个特定的Session被多个 asyncio 任务共享,以及在使用类似 gevent 的补丁式并发方法时。

#7433

SQLite 方言使用 QueuePool 用于基于文件的数据库

当使用基于文件的数据库时,SQLite 方言现在默认为 QueuePool。这与将 check_same_thread 参数设置为 False 一起设置。已观察到以前默认为 NullPool 的方法,在释放连接后不保留数据库连接,事实上会产生可测量的负面性能影响。与以往一样,池类可通过 create_engine.poolclass 参数进行自定义。

另请参阅

线程/池行为

#7490

新的 Oracle FLOAT 类型,带有二进制精度;不直接接受十进制精度

Oracle 方言现已添加了新的数据类型 FLOAT,以配合 Double 和数据库特定的 DOUBLEDOUBLE_PRECISIONREAL 数据类型的添加。Oracle 的 FLOAT 接受所谓的“二进制精度”参数,根据 Oracle 文档,这大致是标准“精度”值除以 0.3103:

from sqlalchemy.dialects import oracle
Table("some_table", metadata, Column("value", oracle.FLOAT(126)))

二进制精度值 126 等同于使用 DOUBLE_PRECISION 数据类型,而值 63 相当于使用 REAL 数据类型。其他精度值是特定于 FLOAT 类型本身的。

SQLAlchemy Float 数据类型也接受“precision”参数,但这是十进制精度,Oracle 不接受。与其试图猜测转换,Oracle 方言现在将在针对 Oracle 后端使用带有精度值的 Float 时引发一个信息性错误。要为支持的后端指定带有显式精度值的 Float 数据类型,同时还支持其他后端,请使用以下方法:TypeEngine.with_variant()

from sqlalchemy.types import Float
from sqlalchemy.dialects import oracle
Table(
    "some_table",
    metadata,
    Column("value", Float(5).with_variant(oracle.FLOAT(16), "oracle")),
)

PostgreSQL 后端的新 RANGE / MULTIRANGE 支持和更改

对于 psycopg2、psycopg3 和 asyncpg 方言,已完全实现了 RANGE / MULTIRANGE 支持。新的支持使用了一个新的 SQLAlchemy 特定的 Range 对象,该对象对不同的后端是不可知的,不需要使用特定于后端的导入或扩展步骤。对于多范围支持,使用 Range 对象的列表。

使用之前的 psycopg2 特定类型的代码应该修改为使用 Range,这提供了一个兼容的接口。

Range 对象还具有与 PostgreSQL 相同的比较支持。目前已实现的是 Range.contains()Range.contained_by() 方法,其工作方式与 PostgreSQL 中的 @><@ 相同。将来的版本可能会添加更多的运算符支持。

请查看范围和多范围类型的文档,了解如何使用这个新功能的背景知识。

另请参阅

范围和多范围类型

#7156 #8706

PostgreSQL 上的 match() 运算符使用 plainto_tsquery() 而不是 to_tsquery()

Operators.match() 函数现在在 PostgreSQL 后端上呈现为 col @@ plainto_tsquery(expr),而不是 col @@ to_tsquery()plainto_tsquery() 接受纯文本,而 to_tsquery() 接受专用的查询符号,因此与其他后端的兼容性较差。

所有 PostgreSQL 搜索函数和运算符都可以通过使用 func 来生成 PostgreSQL 特定的函数和 Operators.bool_op()Operators.op() 的布尔类型版本)来生成任意运算符,方式与之前的版本中可用的方式相同。请参阅全文搜索中的示例。

现有的使用Operators.match()内置 PG 指令的 SQLAlchemy 项目应直接使用func.to_tsquery()。要以与 1.4 中相同的形式呈现 SQL,请参阅使用 match() 进行简单纯文本匹配的版本说明。

#7086

eger))

在 SQLAlchemy 版本 2.0 之前的任何版本中,将需要上述形式以提供与后端无关的地板除法。
[#4926](https://www.sqlalchemy.org/trac/ticket/4926)
### Session 主动提出当检测到非法并发或重入访问时
`Session` 现在可以更全面地捕获与多线程或其他并发场景中的非法并发状态更改相关的错误,以及执行意外状态更改的事件钩子。
已知的一个错误是当一个`Session`同时在多个线程中使用时会出现`AttributeError: 'NoneType' object has no attribute 'twophase'`,这完全是神秘的。当一个线程调用`Session.commit()`时,内部调用`SessionTransaction.close()`方法来结束事务上下文,与此同时另一个线程正在运行一个查询,如`Session.execute()`。在`Session.execute()`中,获取当前事务的数据库连接的内部方法首先开始断言会话是“活动的”,但在此断言通过后,同时进行的对`Session.close()`的调用干扰了这种状态,导致上述未定义的条件。
更改将应用到围绕`SessionTransaction`对象的所有更改状态方法的保护措施,以便在上述情况下,`Session.commit()`方法将会失败,因为它将试图将状态更改为在已经进行中的方法期间不允许的状态,而该方法希望获取当前连接以运行数据库查询。
使用在[#7433](https://www.sqlalchemy.org/trac/ticket/7433)中展示的测试脚本,先前的错误案例如下:
```py
Traceback (most recent call last):
File "/home/classic/dev/sqlalchemy/test3.py", line 30, in worker
    sess.execute(select(A)).all()
File "/home/classic/tmp/sqlalchemy/lib/sqlalchemy/orm/session.py", line 1691, in execute
    conn = self._connection_for_bind(bind)
File "/home/classic/tmp/sqlalchemy/lib/sqlalchemy/orm/session.py", line 1532, in _connection_for_bind
    return self._transaction._connection_for_bind(
File "/home/classic/tmp/sqlalchemy/lib/sqlalchemy/orm/session.py", line 754, in _connection_for_bind
    if self.session.twophase and self._parent is None:
AttributeError: 'NoneType' object has no attribute 'twophase'

_connection_for_bind()方法无法继续运行时,因为并发访问使其处于无效状态。使用新方法,状态更改的发起者会抛出错误:

File "/home/classic/dev/sqlalchemy/lib/sqlalchemy/orm/session.py", line 1785, in close
   self._close_impl(invalidate=False)
File "/home/classic/dev/sqlalchemy/lib/sqlalchemy/orm/session.py", line 1827, in _close_impl
   transaction.close(invalidate)
File "<string>", line 2, in close
File "/home/classic/dev/sqlalchemy/lib/sqlalchemy/orm/session.py", line 506, in _go
   raise sa_exc.InvalidRequestError(
sqlalchemy.exc.InvalidRequestError: Method 'close()' can't be called here;
method '_connection_for_bind()' is already in progress and this would cause
an unexpected state change to symbol('CLOSED')

状态转换检查故意不使用显式锁来检测并发线程活动,而是依赖于简单的属性设置/值测试操作,当发生意外的并发更改时会自然失败。其理念是该方法可以检测到完全发生在单个线程内的非法状态更改,例如运行在会话事务事件上的事件处理程序调用了一个未预期的改变状态的方法,或者在  asyncio 中,如果一个特定的Session被多个 asyncio 任务共享,以及在使用类似 gevent 的补丁式并发方法时。

#7433

SQLite 方言使用 QueuePool 用于基于文件的数据库

当使用基于文件的数据库时,SQLite 方言现在默认为 QueuePool。这与将 check_same_thread 参数设置为 False 一起设置。已观察到以前默认为 NullPool 的方法,在释放连接后不保留数据库连接,事实上会产生可测量的负面性能影响。与以往一样,池类可通过 create_engine.poolclass 参数进行自定义。

另请参阅

线程/池行为

#7490

新的 Oracle FLOAT 类型,带有二进制精度;不直接接受十进制精度

Oracle 方言现已添加了新的数据类型 FLOAT,以配合 Double 和数据库特定的 DOUBLEDOUBLE_PRECISIONREAL 数据类型的添加。Oracle 的 FLOAT 接受所谓的“二进制精度”参数,根据 Oracle 文档,这大致是标准“精度”值除以 0.3103:

from sqlalchemy.dialects import oracle
Table("some_table", metadata, Column("value", oracle.FLOAT(126)))

二进制精度值 126 等同于使用 DOUBLE_PRECISION 数据类型,而值 63 相当于使用 REAL 数据类型。其他精度值是特定于 FLOAT 类型本身的。

SQLAlchemy Float 数据类型也接受“precision”参数,但这是十进制精度,Oracle 不接受。与其试图猜测转换,Oracle 方言现在将在针对 Oracle 后端使用带有精度值的 Float 时引发一个信息性错误。要为支持的后端指定带有显式精度值的 Float 数据类型,同时还支持其他后端,请使用以下方法:TypeEngine.with_variant()

from sqlalchemy.types import Float
from sqlalchemy.dialects import oracle
Table(
    "some_table",
    metadata,
    Column("value", Float(5).with_variant(oracle.FLOAT(16), "oracle")),
)

PostgreSQL 后端的新 RANGE / MULTIRANGE 支持和更改

对于 psycopg2、psycopg3 和 asyncpg 方言,已完全实现了 RANGE / MULTIRANGE 支持。新的支持使用了一个新的 SQLAlchemy 特定的 Range 对象,该对象对不同的后端是不可知的,不需要使用特定于后端的导入或扩展步骤。对于多范围支持,使用 Range 对象的列表。

使用之前的 psycopg2 特定类型的代码应该修改为使用 Range,这提供了一个兼容的接口。

Range 对象还具有与 PostgreSQL 相同的比较支持。目前已实现的是 Range.contains()Range.contained_by() 方法,其工作方式与 PostgreSQL 中的 @><@ 相同。将来的版本可能会添加更多的运算符支持。

请查看范围和多范围类型的文档,了解如何使用这个新功能的背景知识。

另请参阅

范围和多范围类型

#7156 #8706

PostgreSQL 上的 match() 运算符使用 plainto_tsquery() 而不是 to_tsquery()

Operators.match() 函数现在在 PostgreSQL 后端上呈现为 col @@ plainto_tsquery(expr),而不是 col @@ to_tsquery()plainto_tsquery() 接受纯文本,而 to_tsquery() 接受专用的查询符号,因此与其他后端的兼容性较差。

所有 PostgreSQL 搜索函数和运算符都可以通过使用 func 来生成 PostgreSQL 特定的函数和 Operators.bool_op()Operators.op() 的布尔类型版本)来生成任意运算符,方式与之前的版本中可用的方式相同。请参阅全文搜索中的示例。

现有的使用Operators.match()内置 PG 指令的 SQLAlchemy 项目应直接使用func.to_tsquery()。要以与 1.4 中相同的形式呈现 SQL,请参阅使用 match() 进行简单纯文本匹配的版本说明。

#7086

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
4月前
|
SQL 关系型数据库 API
SqlAlchemy 2.0 中文文档(五十四)(5)
SqlAlchemy 2.0 中文文档(五十四)
46 1
|
4月前
|
SQL 缓存 关系型数据库
SqlAlchemy 2.0 中文文档(五十四)(3)
SqlAlchemy 2.0 中文文档(五十四)
30 1
|
4月前
|
SQL Oracle 关系型数据库
SqlAlchemy 2.0 中文文档(五十七)(8)
SqlAlchemy 2.0 中文文档(五十七)
52 0
|
4月前
|
SQL 关系型数据库 MySQL
SqlAlchemy 2.0 中文文档(五十七)(7)
SqlAlchemy 2.0 中文文档(五十七)
48 0
|
4月前
|
SQL Python
SqlAlchemy 2.0 中文文档(五十七)(5)
SqlAlchemy 2.0 中文文档(五十七)
22 0
|
4月前
|
SQL API Python
SqlAlchemy 2.0 中文文档(五十七)(6)
SqlAlchemy 2.0 中文文档(五十七)
33 0
|
4月前
|
SQL 关系型数据库 数据库
SqlAlchemy 2.0 中文文档(五十七)(2)
SqlAlchemy 2.0 中文文档(五十七)
26 0
|
4月前
|
SQL 关系型数据库 MySQL
SqlAlchemy 2.0 中文文档(五十七)(1)
SqlAlchemy 2.0 中文文档(五十七)
42 0
|
4月前
|
SQL 存储 测试技术
SqlAlchemy 2.0 中文文档(五十七)(4)
SqlAlchemy 2.0 中文文档(五十七)
35 0
|
4月前
|
SQL Oracle 关系型数据库
SqlAlchemy 2.0 中文文档(五十七)(3)
SqlAlchemy 2.0 中文文档(五十七)
29 0