SqlAlchemy 2.0 中文文档(三十七)(1)

简介: SqlAlchemy 2.0 中文文档(三十七)


原文:docs.sqlalchemy.org/en/20/contents.html

自定义 SQL 构造和编译扩展

原文:docs.sqlalchemy.org/en/20/core/compiler.html

提供了用于创建自定义 ClauseElements 和编译器的 API。

概要

使用涉及创建一个或多个ClauseElement子类和一个或多个定义其编译的可调用对象:

from sqlalchemy.ext.compiler import compiles
from sqlalchemy.sql.expression import ColumnClause
class MyColumn(ColumnClause):
    inherit_cache = True
@compiles(MyColumn)
def compile_mycolumn(element, compiler, **kw):
    return "[%s]" % element.name

在上面,MyColumn扩展了ColumnClause,这是命名列对象的基本表达式元素。compiles装饰器MyColumn类注册自身,以便在将对象编译为字符串时调用它:

from sqlalchemy import select
s = select(MyColumn('x'), MyColumn('y'))
print(str(s))

产生:

SELECT [x], [y]

方言特定的编译规则

编译器也可以是特定于方言的。将为使用的方言调用适当的编译器:

from sqlalchemy.schema import DDLElement
class AlterColumn(DDLElement):
    inherit_cache = False
    def __init__(self, column, cmd):
        self.column = column
        self.cmd = cmd
@compiles(AlterColumn)
def visit_alter_column(element, compiler, **kw):
    return "ALTER COLUMN %s ..." % element.column.name
@compiles(AlterColumn, 'postgresql')
def visit_alter_column(element, compiler, **kw):
    return "ALTER TABLE %s ALTER COLUMN %s ..." % (element.table.name,
                                                   element.column.name)

当使用任何postgresql方言时,第二个visit_alter_table将被调用。

编译自定义表达式构造的子元素

compiler参数是正在使用的Compiled对象。可以检查此对象的任何有关进行中编译的信息,包括compiler.dialectcompiler.statement等。SQLCompilerDDLCompiler都包括一个process()方法,可用于编译嵌入属性:

from sqlalchemy.sql.expression import Executable, ClauseElement
class InsertFromSelect(Executable, ClauseElement):
    inherit_cache = False
    def __init__(self, table, select):
        self.table = table
        self.select = select
@compiles(InsertFromSelect)
def visit_insert_from_select(element, compiler, **kw):
    return "INSERT INTO %s (%s)" % (
        compiler.process(element.table, asfrom=True, **kw),
        compiler.process(element.select, **kw)
    )
insert = InsertFromSelect(t1, select(t1).where(t1.c.x>5))
print(insert)

产生:

"INSERT INTO mytable (SELECT mytable.x, mytable.y, mytable.z
                      FROM mytable WHERE mytable.x > :x_1)"

注意

上述的InsertFromSelect构造仅是一个示例,这种实际功能已经可以使用Insert.from_select()方法。

在 SQL 和 DDL 编译器之间进行交叉编译

SQL 和 DDL 构造分别使用不同的基本编译器 - SQLCompilerDDLCompiler。一个常见的需求是从 DDL 表达式中访问 SQL 表达式的编译规则。DDLCompiler包含一个访问器sql_compiler,因此我们可以生成嵌入 SQL 表达式的 CHECK 约束,如下所示:

@compiles(MyConstraint)
def compile_my_constraint(constraint, ddlcompiler, **kw):
    kw['literal_binds'] = True
    return "CONSTRAINT %s CHECK (%s)" % (
        constraint.name,
        ddlcompiler.sql_compiler.process(
            constraint.expression, **kw)
    )

在上面,我们在SQLCompiler.process()中调用的过程步骤中添加了一个额外的标志,即literal_binds标志。这表示任何引用BindParameter对象或其他“literal”对象(如引用字符串或整数的对象)的 SQL 表达式应该原地呈现,而不是作为绑定参数引用;在发出 DDL 时,通常不支持绑定参数。

更改现有构造的默认编译

编译器扩展同样适用于现有的结构。当覆盖内置 SQL 结构的编译时,@compiles 装饰器会调用适当的类(确保使用类,即 InsertSelect,而不是创建函数,比如 insert()select())。

在新的编译函数中,要获取“原始”的编译例程,使用适当的 visit_XXX 方法 - 这是因为编译器.process() 将调用重写的例程并导致无限循环。比如,要向所有的插入语句添加“前缀”:

from sqlalchemy.sql.expression import Insert
@compiles(Insert)
def prefix_inserts(insert, compiler, **kw):
    return compiler.visit_insert(insert.prefix_with("some prefix"), **kw)

上述编译器在编译时会在所有的 INSERT 语句前加上“某个前缀”。

更改类型的编译

compiler 也适用于类型,比如下面我们为 String/VARCHAR 实现了 MS-SQL 特定的 ‘max’ 关键字:

@compiles(String, 'mssql')
@compiles(VARCHAR, 'mssql')
def compile_varchar(element, compiler, **kw):
    if element.length == 'max':
        return "VARCHAR('max')"
    else:
        return compiler.visit_VARCHAR(element, **kw)
foo = Table('foo', metadata,
    Column('data', VARCHAR('max'))
)

子类化指南

使用编译器扩展的一个重要部分是子类化 SQLAlchemy 表达式结构。为了使这更容易,表达式和模式包含一组旨在用于常见任务的“基础”。概要如下:

  • ClauseElement - 这是根表达式类。任何 SQL 表达式都可以从这个基类派生,并且对于长一些的构造,比如专门的 INSERT 语句,这可能是最好的选择。
  • ColumnElement - 所有“类似列”的元素的根。你在 SELECT 语句的“列”子句中(以及 order by 和 group by)放置的任何东西都可以从这个派生 - 该对象将自动具有 Python 的“比较”行为。
    ColumnElement 类希望有一个 type 成员,该成员是表达式的返回类型。这可以在构造函数的实例级别或类级别上建立:
class timestamp(ColumnElement):
    type = TIMESTAMP()
    inherit_cache = True
  • FunctionElement - 这是 ColumnElement 和“from 子句”类似对象的混合体,并表示 SQL 函数或存储过程类型的调用。由于大多数数据库支持“SELECT FROM <某个函数>”这样的语句,FunctionElement 添加了在 select() 构造的 FROM 子句中使用的能力:
from sqlalchemy.sql.expression import FunctionElement
class coalesce(FunctionElement):
    name = 'coalesce'
    inherit_cache = True
@compiles(coalesce)
def compile(element, compiler, **kw):
    return "coalesce(%s)" % compiler.process(element.clauses, **kw)
@compiles(coalesce, 'oracle')
def compile(element, compiler, **kw):
    if len(element.clauses) > 2:
        raise TypeError("coalesce only supports two arguments on Oracle")
    return "nvl(%s)" % compiler.process(element.clauses, **kw)
  • ExecutableDDLElement - 所有 DDL 表达式的根,比如 CREATE TABLE,ALTER TABLE 等。ExecutableDDLElement的子类的编译由DDLCompiler发出,而不是由SQLCompiler发出。ExecutableDDLElement也可以与DDLEvents.before_create()DDLEvents.after_create()等事件钩子一起用作事件钩子,允许在 CREATE TABLE 和 DROP TABLE 序列期间自动调用构造。
    另请参见
    自定义 DDL - 包含将DDL对象(它们本身是ExecutableDDLElement实例)与DDLEvents事件钩子相关联的示例。
  • Executable - 这是一个 mixin,应该与任何表示可以直接传递给execute()方法的“独立”SQL 语句的表达式类一起使用。它已经隐含在DDLElementFunctionElement中。

上述大部分构造也响应 SQL 语句缓存。子类构造将希望为对象定义缓存行为,这通常意味着将标志inherit_cache设置为FalseTrue的值。有关背景信息,请参见下一节为自定义构造启用缓存支持。

为自定义构造启用缓存支持

从版本 1.4 开始,SQLAlchemy 包括一个 SQL 编译缓存设施,它将允许等效的 SQL 构造缓存它们的字符串形式,以及用于从语句中获取结果的其他结构信息。

如在对象不会生成缓存键,性能影响中讨论的原因,该缓存系统的实现对于在缓存系统中包含自定义 SQL  构造和/或子类采取了保守的方法。这包括任何用户定义的 SQL  构造,包括此扩展的所有示例,除非它们明确声明能够这样做,否则默认情况下不会参与缓存。当在特定子类的类级别设置HasCacheKey.inherit_cache属性为True时,将指示该类的实例可以安全地进行缓存,使用直接父类的缓存键生成方案。例如,这适用于先前指示的“概要”示例:

class MyColumn(ColumnClause):
    inherit_cache = True
@compiles(MyColumn)
def compile_mycolumn(element, compiler, **kw):
    return "[%s]" % element.name

上面,MyColumn类不包含任何影响其 SQL 编译的新状态;MyColumn实例的缓存键将利用ColumnClause超类的缓存键,这意味着它将考虑对象的类(MyColumn)、对象的字符串名称和数据类型:

>>> MyColumn("some_name", String())._generate_cache_key()
CacheKey(
 key=('0', <class '__main__.MyColumn'>,
 'name', 'some_name',
 'type', (<class 'sqlalchemy.sql.sqltypes.String'>,
 ('length', None), ('collation', None))
), bindparams=[])

对于那些在许多更大语句中可能被大量使用作为组件的对象,比如Column子类和自定义 SQL 数据类型,尽可能启用缓存非常重要,否则可能会对性能产生负面影响。

一个包含影响其 SQL 编译的状态的对象示例是在编译自定义表达式构造的子元素中所示的一个示例;这是一个将TableSelect构造组合在一起的“INSERT FROM SELECT”构造,每个构造独立影响构造的 SQL 字符串生成。对于这个类,示例说明它根本不参与缓存:

class InsertFromSelect(Executable, ClauseElement):
    inherit_cache = False
    def __init__(self, table, select):
        self.table = table
        self.select = select
@compiles(InsertFromSelect)
def visit_insert_from_select(element, compiler, **kw):
    return "INSERT INTO %s (%s)" % (
        compiler.process(element.table, asfrom=True, **kw),
        compiler.process(element.select, **kw)
    )

虽然上述InsertFromSelect也可能生成由TableSelect组件组合而成的缓存键,但目前该 API 并不完全公开。然而,对于“INSERT FROM SELECT”构造,它仅用于特定操作,缓存并不像前面的示例那样关键。

对于在相对孤立并且通常是独立的对象,例如自定义 DML 构造,如 “INSERT FROM SELECT”,缓存通常不那么关键,因为对于这种构造缺乏缓存仅对该特定操作具有局部影响。


SqlAlchemy 2.0 中文文档(三十七)(2)https://developer.aliyun.com/article/1562698

相关文章
|
5月前
|
存储 缓存 数据库
SqlAlchemy 2.0 中文文档(四十四)(5)
SqlAlchemy 2.0 中文文档(四十四)
114 4
|
5月前
|
SQL 缓存 关系型数据库
SqlAlchemy 2.0 中文文档(四十四)(2)
SqlAlchemy 2.0 中文文档(四十四)
95 4
|
5月前
|
SQL 存储 API
SqlAlchemy 2.0 中文文档(四十四)(6)
SqlAlchemy 2.0 中文文档(四十四)
109 4
|
5月前
|
SQL 缓存 关系型数据库
SqlAlchemy 2.0 中文文档(三十七)(2)
SqlAlchemy 2.0 中文文档(三十七)
49 2
|
5月前
|
SQL 缓存 数据库连接
SqlAlchemy 2.0 中文文档(四十四)(4)
SqlAlchemy 2.0 中文文档(四十四)
57 3
|
5月前
|
SQL 关系型数据库 数据库
SqlAlchemy 2.0 中文文档(四十四)(9)
SqlAlchemy 2.0 中文文档(四十四)
59 3
|
5月前
|
SQL 存储 缓存
SqlAlchemy 2.0 中文文档(三十七)(3)
SqlAlchemy 2.0 中文文档(三十七)
39 1
|
5月前
|
SQL 缓存 API
SqlAlchemy 2.0 中文文档(三十七)(5)
SqlAlchemy 2.0 中文文档(三十七)
33 1
|
5月前
|
SQL 存储 缓存
SqlAlchemy 2.0 中文文档(三十七)(4)
SqlAlchemy 2.0 中文文档(三十七)
58 1
|
5月前
|
SQL 关系型数据库 数据库
SqlAlchemy 2.0 中文文档(三十四)(5)
SqlAlchemy 2.0 中文文档(三十四)
48 0