inkant/engi

SQL模板引擎

dev-main 2024-05-02 18:53 UTC

This package is auto-updated.

Last update: 2024-10-02 19:41:54 UTC


README

类似于 sprintf,但占位符可自定义、可扩展,其行为灵活且以SQL为导向。

安装

composer require inkant/engi

TL;DR

请勿在生产环境中使用 DummyEscaper

请使用 PgEscaperPdoEscaper 或您自己的自定义转义实现,而不是 DummyEscaper
DummyEscaper 仅用于示例。

i? - 尝试将转换为 int SQL 类型位置参数
i?name1 - 尝试转换为名为 "name1" 的 int SQL 命名参数
? - 尝试将传递的位置参数类型解析为合理的 SQL 类型
?name1 - 尝试将传递的命名参数 "name1" 类型解析为合理的 SQL 类型

$query = new \Inkant\Engi\Query(
    'SELECT i?, ?, ?name1, i?name1', [
    '100',
    100,
    'name1' => '454'
]);

$result = "SELECT 100, 100, '454', 454";
$compiler = new Compiler(new DummyEscaper());
$compiler->compile($query) === $result
    ?: throw new \Exception();

更多大多数 具有子查询和参数类型的描述性测试用例
请查看 Query 中的 resolvers 设置示例 ::resolvers(),它可以扩展以自定义占位符或/和自定义解析器

架构

描述概念的契约

解析器 负责从字符串模板中提供 占位符 作为 抽象语法树 的部分。
抽象语法树composite 模式的实现,其中叶是 SQL 上下文中的 令牌(必须具有字符串表示形式),而 组合(例如 占位符)是更复杂的 SQL 结构。
每个 组合 都必须通过调用 compile(mixed ...$dependencies) 方法转换为 令牌
每个 令牌 都必须通过调用 compile(mixed ...$dependencies) 方法转换为字符串。
令牌 必须由 组装器 调用方法 assemble 组装,提供 令牌...$dependencies

...$dependencies 是在 compile 期间所需的任何依赖项(例如数据库版本/名称,转义字符串机制)。

占位符

占位符只是带有可选名称字符串 [a-zA-Z0-9_]+ 的子字符串
? - 如果没有提供名称,则使用下一个位置参数
?email - 使用数组参数项,键为 email
i?id - 使用数组参数项,键为 id 转换为 int

占位符使同时使用命名和位置参数成为可能。
此外,它还允许在不同上下文中重用同一个值。
i?id - 将字符串值 '100' 转换为 SQL integer 值 100
s?id - (值类型明显,可以用 "?id" 替换) 将字符串值 '100' 转换为 SQL text 值 '100'
?id - 将值 '100' 解析为 SQL text 值 '100',基于其值类型为字符串

$query = new \Inkant\Engi\Query(
    'SELECT i?id, s?id, ?id', [
    'id' => '100'
]);

$result = "SELECT 100, '100', '100'";
$compiler = new Compiler(new DummyEscaper());
$compiler->compile($query) === $result
    ?: throw new \Exception();

为了确定由什么来具体替换占位符,使用 解析器

转义占位符

有时占位符可能会干扰 SQL 语法。例如,PostgreSQL 有 jsonb 操作符 "?",如果使用 "?" 作为占位符,则可以通过将其重复两次来转义。
因此,占位符 "???" 将解析为 "?",不会影响 PostgreSQL 语法。
可以通过 EscapeResolver 实现。

转义值

不同的数据库有不同的转义方式(例如,避免 SQL 注入)值标记(例如,字符串)。
PgEscaperPdoEscaper 实现了转义字符串并避免 SQL 注入所必需的接口。

转义字符串

为了实现数据库无关性,例如,StringToken 在其 ...$dependencies 中需要实现 EscapeStringInterface,在 compile(...$dependencies) 过程中

转义二进制

BinaryToken 在其 ...$dependencies 中需要实现 EscapeBinaryInterface,在 compile(...$dependencies) 过程中

转义标识符

有时需要转义表名、列名等。例如,IdentifierToken 在其 ...$dependencies 中需要实现 EscapeIdentifierInterface,在 compile(...$dependencies) 过程中
PgEscaper 使用 PostgreSQL 驱动程序特定的函数 pg_escape_identifier 实现 EscapeIdentifierInterface
PdoEscaper 也实现了 EscapeIdentifierInterface,但由于 PDO 不提供任何转义标识符函数,因此 PdoEscaper::escapeIdentifier 返回标识符 ASIS。

解析器

所有解析器
用例

解析器负责将传递的值解析为 SQL 值/结构中的占位符。
解析器很简单,这里是一个 StringResolver 的例子

class StringResolver extends ResolverAbstract
{
    public function resolve(mixed $value): ?StringToken
    {
        return is_string($value)
            ? new StringToken($value)
            : null;
    }
}

KeyValueResolver

UPDATE SET 中非常有用,可以将关联数组解析为 key=value 列表 ['column1' => 1, 'column2' => 2] => 'column1=1,column2=2'

ListResolver

IN 中非常有用。如果 array_is_list,则列表解析为逗号分隔的 SQL 列表。
[1, 2.38] => '1,2.38'

AstResolver

使嵌套查询成为可能。
如果值是 AstInterface(例如,Query),则它将被嵌入并编译在 compile

IdentifierResolver

用于表名和列名的占位符,不会作为字符串值进行引号处理。