简体中文 English 繁體中文 日本語 Русский
本文为静态镜像,内容以交互版为准 在交互式文档中心打开 →

SQL 查询构建器

ErisPulse 的 Storage 模块提供链式调用风格的通用 SQL 查询构建器,支持自定义表的创建、查询、更新和删除操作。

架构设计

Bases/storage.py                    Core/storage.py
┌─────────────────────┐             ┌──────────────────────────┐
│  BaseStorage (ABC)  │◄────────────│  StorageManager          │
│  BaseQueryBuilder   │             │  (SQLite concrete impl)  │
│    (ABC)            │             │                          │
└─────────────────────┘             │  SQLiteQueryBuilder      │
                                    │  AlterTableBuilder       │
                                    └──────────────────────────┘

导入

from ErisPulse import sdk
# 或
from ErisPulse.Core import storage

# ABC 基类(用于类型标注或自定义实现)
from ErisPulse.Core.Bases.storage import BaseStorage, BaseQueryBuilder

表管理

创建表

sdk.storage.CreateTable("users", {
    "id": "INTEGER PRIMARY KEY AUTOINCREMENT",
    "name": "TEXT NOT NULL",
    "age": "INTEGER DEFAULT 0",
    "email": "TEXT"
})

检查表是否存在

if sdk.storage.HasTable("users"):
    print("users 表已存在")

删除表

sdk.storage.DropTable("users")

修改表结构

# 添加列
sdk.storage.AlterTable("users").AddColumn("email", "TEXT").Execute()

# 重命名表
sdk.storage.AlterTable("users").RenameTo("members").Execute()

# 链式多个操作
sdk.storage.AlterTable("users") \
    .AddColumn("phone", "TEXT") \
    .AddColumn("address", "TEXT") \
    .Execute()

链式查询

插入数据

# 单行插入(传入字典)
sdk.storage.Table("users").Insert({"name": "Alice", "age": 30}).Execute()

# 批量插入(传入字典列表)
sdk.storage.Table("users").InsertMulti([
    {"name": "Bob", "age": 25},
    {"name": "Charlie", "age": 35},
    {"name": "Dave", "age": 40}
]).Execute()

查询数据

重要:Select() 返回的是 list[tuple](元组列表),不是字典。你需要按列顺序用索引访问。

# 查询所有列
rows = sdk.storage.Table("users").Select().Execute()
# rows: [(1, "Alice", 30), (2, "Bob", 25), ...]

# 查询指定列
rows = sdk.storage.Table("users").Select("name", "age").Execute()
# rows: [("Alice", 30), ("Bob", 25), ...]

# 按索引取值
for row in rows:
    name = row[0]   # "Alice"
    age = row[1]    # 30

将元组转为字典

推荐直接在链上调用 ToDict(),SELECT 结果自动以字典返回(列名 → 值):

# ToDict 链:结果为 list[dict],列名自动取自查询元数据(SELECT * 同样支持)
rows = sdk.storage.Table("users").Select("name", "age").ToDict().Execute()
# rows: [{"name": "Alice", "age": 30}, {"name": "Bob", "age": 25}, ...]

for row in rows:
    print(row["name"], row["age"])

# ExecuteOne 同样生效
row = sdk.storage.Table("users").Select("name", "age") \
    .Where("id = ?", 1) \
    .ToDict() \
    .ExecuteOne()
# row: {"name": "Alice", "age": 30} 或 None

ToDict() 是链式标记(返回 self):未调用它的链保持原有 list[tuple] 行为,完全向后兼容;copy() 会保留该标志。

手动 zip 方式(与 ToDict 等价,适合无法改链的场景):

columns = ["id", "name", "age"]
rows = sdk.storage.Table("users").Select(*columns).Execute()

# 方式一:循环中 zip
for row in rows:
    record = dict(zip(columns, row))
    print(record["name"], record["age"])

# 方式二:一次性转为字典列表
records = [dict(zip(columns, row)) for row in rows]

获取单条记录

row = sdk.storage.Table("users").Select("name", "age") \
    .Where("id = ?", 1) \
    .ExecuteOne()

# row 是 tuple 或 None
if row is not None:
    name = row[0]  # "Alice"
    age = row[1]   # 30

条件过滤

Where(condition, *params) 支持传入多个参数,对应多个 ? 占位符。

# 单条件(一个占位符,一个参数)
rows = sdk.storage.Table("users").Select("name") \
    .Where("age > ?", 18) \
    .Execute()

# 一个 Where 中使用多个占位符
rows = sdk.storage.Table("users").Select("name") \
    .Where("age > ? AND age < ?", 20, 40) \
    .Execute()

# 多次调用 Where(AND 连接)
rows = sdk.storage.Table("users").Select("name") \
    .Where("age > ?", 20) \
    .Where("age < ?", 40) \
    .Execute()

排序、分页

# 升序
rows = sdk.storage.Table("users").Select("name", "age") \
    .OrderBy("name") \
    .Execute()

# 降序
rows = sdk.storage.Table("users").Select("name") \
    .OrderBy("age", desc=True) \
    .Execute()

# 分页
rows = sdk.storage.Table("users").Select("name") \
    .OrderBy("id") \
    .Limit(10) \
    .Offset(20) \
    .Execute()

更新数据

# 条件更新
sdk.storage.Table("users") \
    .Update({"age": 31}) \
    .Where("name = ?", "Alice") \
    .Execute()

# 全量更新
sdk.storage.Table("users") \
    .Update({"status": "active"}) \
    .Execute()

删除数据

# 条件删除
sdk.storage.Table("users") \
    .Delete() \
    .Where("name = ?", "Bob") \
    .Execute()

# 全量删除
sdk.storage.Table("users").Delete().Execute()

计数与存在性检查

# 计数
count = sdk.storage.Table("users").Count()
count = sdk.storage.Table("users").Where("age > ?", 18).Count()

# 存在性检查
exists = sdk.storage.Table("users").Where("name = ?", "Alice").Exists()

复用查询条件

使用 copy() 深拷贝构建器,复用基础条件:

base = sdk.storage.Table("users").Where("age > ?", 20)

# 基于相同条件查询
rows = base.copy().Select("name").OrderBy("name").Limit(5).Execute()

# 基于相同条件计数
count = base.copy().Count()

# 基于相同条件检查存在性
exists = base.copy().Where("name = ?", "Alice").Exists()

重置构建器

builder = sdk.storage.Table("users").Select("name").Where("age > ?", 18)
builder.clear()

# 重新构建查询
builder.Select("name", "age").Where("name = ?", "Alice")
rows = builder.Execute()

事务中使用

链式操作完全支持事务:

# 提交事务
with sdk.storage.transaction():
    sdk.storage.Table("users").Insert({"name": "Eve", "age": 22}).Execute()
    sdk.storage.Table("users").Update({"age": 23}).Where("name = ?", "Eve").Execute()

# 回滚示例
try:
    with sdk.storage.transaction():
        sdk.storage.Table("users").Delete().Where("name = ?", "Alice").Execute()
        raise Exception("force rollback")
except Exception:
    pass
# Alice 的记录仍然存在

异步原生 API

2.8.0 起存储层以异步为原生主接口,所有终止方法都有对应的 a 前缀异步版本, 异步 handler 内推荐使用(避免同步兼容层短暂阻塞事件循环):

# 异步事务
async with sdk.storage.atransaction():
    await sdk.storage.aset("key1", "value1")
    await sdk.storage.aset("key2", {"nested": True})

# 异步链式查询
rows = await sdk.storage.Table("users").Select("name", "age").ToDict().aExecute()
row = await sdk.storage.Table("users").Select("*").Where("id = ?", 1).aExecuteOne()
total = await sdk.storage.Table("users").Where("age > ?", 18).aCount()
exists = await sdk.storage.Table("users").Where("name = ?", "Alice").aExists()

# 异步 KV
await sdk.storage.aset("app.name", "MyApp")
value = await sdk.storage.aget("app.name")
keys = await sdk.storage.aget_all_keys()
同步(兼容层) 异步原生
get / set / delete aget / aset / adelete
get_all_keys / clear aget_all_keys / aclear
get_multi / set_multi / delete_multi aget_multi / aset_multi / adelete_multi
transaction() atransaction()
CreateTable / DropTable / HasTable aCreateTable / aDropTable / aHasTable
Execute / ExecuteOne / Count / Exists aExecute / aExecuteOne / aCount / aExists

返回值说明

操作 返回类型 说明
Select().Execute() list[tuple] 元组列表,按列顺序排列
Select().ExecuteOne() tuple | None 单条元组或 None
Insert().Execute() int 受影响行数
InsertMulti().Execute() int 插入行数
Update().Execute() int 受影响行数
Delete().Execute() int 受影响行数
Count() int 匹配行数
Exists() bool 是否存在

返回值处理示例

# Select 返回元组,按索引取值
rows = sdk.storage.Table("users").Select("name", "age").Execute()
first_name = rows[0][0]  # 第一行第一列 name
first_age = rows[0][1]   # 第一行第二列 age

# 推荐:用列名列表 + zip 转为字典,代码更可读
cols = ["name", "age"]
rows = sdk.storage.Table("users").Select(*cols).Execute()
for row in rows:
    d = dict(zip(cols, row))
    print(d["name"], d["age"])

# ExecuteOne 返回单条元组或 None
row = sdk.storage.Table("users").Select("name").Where("id = ?", 1).ExecuteOne()
name = row[0] if row else None

# Insert/Update/Delete 返回受影响行数
affected = sdk.storage.Table("users").Delete().Where("age < ?", 18).Execute()
print(f"删除了 {affected} 条记录")

参数化查询

所有 WHERE 参数使用 ? 占位符,参数作为 Where() 的后续参数传入(不是元组或列表):

# 正确 ✓ — 多个参数逐一传入
sdk.storage.Table("users").Where("age > ? AND name = ?", 18, "Alice").Execute()

# 正确 ✓ — 多次 Where 调用
sdk.storage.Table("users").Where("age > ?", 18).Where("name = ?", "Alice").Execute()

# 错误 ✗ — 不要传入元组
sdk.storage.Table("users").Where("age > ? AND name = ?", (18, "Alice")).Execute()
# 这会把整个元组当成第一个占位符的值

# 错误 ✗ — 存在 SQL 注入风险
sdk.storage.Table("users").Where(f"name = '{user_input}'").Execute()

Where 参数传递规则

# Where(condition: str, *params: Any)
# params 是可变参数,逐个传入即可

# 单个参数
.Where("name = ?", "Alice")

# 多个参数
.Where("age > ? AND age < ?", 18, 60)

# LIKE 查询
.Where("name LIKE ?", "A%")

# IN 查询(需要手动构造占位符)
.Where("name IN (?, ?, ?)", "Alice", "Bob", "Charlie")

自定义存储后端

2.8.0 起抽象层以异步方法为原生契约:继承 BaseStorage 实现异步抽象方法, 同步 get/set/Execute 等由基类自动桥接提供:

from ErisPulse.Core.Bases.storage import BaseStorage, BaseQueryBuilder

class MyQueryBuilder(BaseQueryBuilder):
    async def aExecute(self):
        # 实现具体执行逻辑
        ...

    async def aExecuteOne(self):
        ...

    async def aCount(self):
        ...

    async def aExists(self):
        ...


class MyStorage(BaseStorage):
    async def aget(self, key, default=None):
        ...

    async def aset(self, key, value):
        ...

    # 实现其他异步抽象方法与事务连接 hook ...
    def Table(self, table_name):
        return MyQueryBuilder(self, table_name)

Tip

若不想实现事务连接路由(conn 关键字参数),保持类属性 _SUPPORTS_CONN_ROUTING = False(默认)即可,事务功能仍可用(隔离性受限)。 纯 SQL 后端可直接继承 Core/Bases/sql_base.py 的 SQLStorageBase + SQLQueryBuilder,只需提供连接管理与方言执行漏斗,详见 存储后端。

相关文档