sdpy-kit 0.2.1__tar.gz → 0.2.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sdpy_kit-0.2.1 → sdpy_kit-0.2.3}/PKG-INFO +107 -21
- {sdpy_kit-0.2.1 → sdpy_kit-0.2.3}/README.md +106 -20
- {sdpy_kit-0.2.1 → sdpy_kit-0.2.3}/kit/__init__.py +25 -1
- {sdpy_kit-0.2.1 → sdpy_kit-0.2.3}/kit/db/__init__.py +25 -1
- {sdpy_kit-0.2.1 → sdpy_kit-0.2.3}/kit/db/base_db_kit.py +83 -5
- {sdpy_kit-0.2.1 → sdpy_kit-0.2.3}/kit/db/engine.py +15 -8
- {sdpy_kit-0.2.1 → sdpy_kit-0.2.3}/kit/db/exceptions.py +11 -0
- {sdpy_kit-0.2.1 → sdpy_kit-0.2.3}/kit/db/mysql_kit.py +100 -42
- {sdpy_kit-0.2.1 → sdpy_kit-0.2.3}/kit/db/pg_kit.py +161 -52
- sdpy_kit-0.2.3/kit/db/sqlite_kit.py +456 -0
- sdpy_kit-0.2.1/kit/db/sqlite_kit.py +0 -271
- {sdpy_kit-0.2.1 → sdpy_kit-0.2.3}/.gitignore +0 -0
- {sdpy_kit-0.2.1 → sdpy_kit-0.2.3}/kit/ai/__init__.py +0 -0
- {sdpy_kit-0.2.1 → sdpy_kit-0.2.3}/kit/ai/base_service.py +0 -0
- {sdpy_kit-0.2.1 → sdpy_kit-0.2.3}/kit/ai/chat_service.py +0 -0
- {sdpy_kit-0.2.1 → sdpy_kit-0.2.3}/kit/ai/embedding_service.py +0 -0
- {sdpy_kit-0.2.1 → sdpy_kit-0.2.3}/kit/ai/exceptions.py +0 -0
- {sdpy_kit-0.2.1 → sdpy_kit-0.2.3}/kit/ai/failover.py +0 -0
- {sdpy_kit-0.2.1 → sdpy_kit-0.2.3}/kit/ai/image_service.py +0 -0
- {sdpy_kit-0.2.1 → sdpy_kit-0.2.3}/kit/ai/model_builder.py +0 -0
- {sdpy_kit-0.2.1 → sdpy_kit-0.2.3}/kit/ai/model_client.py +0 -0
- {sdpy_kit-0.2.1 → sdpy_kit-0.2.3}/kit/ai/pool.py +0 -0
- {sdpy_kit-0.2.1 → sdpy_kit-0.2.3}/kit/ai/profiles.py +0 -0
- {sdpy_kit-0.2.1 → sdpy_kit-0.2.3}/kit/ai/rerank_service.py +0 -0
- {sdpy_kit-0.2.1 → sdpy_kit-0.2.3}/kit/ai/runner.py +0 -0
- {sdpy_kit-0.2.1 → sdpy_kit-0.2.3}/kit/ai/types.py +0 -0
- {sdpy_kit-0.2.1 → sdpy_kit-0.2.3}/kit/anydoc_kit.py +0 -0
- {sdpy_kit-0.2.1 → sdpy_kit-0.2.3}/kit/common_kit.py +0 -0
- {sdpy_kit-0.2.1 → sdpy_kit-0.2.3}/kit/config/__init__.py +0 -0
- {sdpy_kit-0.2.1 → sdpy_kit-0.2.3}/kit/config/kit_config.py +0 -0
- {sdpy_kit-0.2.1 → sdpy_kit-0.2.3}/kit/config/log_config.py +0 -0
- {sdpy_kit-0.2.1 → sdpy_kit-0.2.3}/kit/db/crud.py +0 -0
- {sdpy_kit-0.2.1 → sdpy_kit-0.2.3}/kit/db/models/__init__.py +0 -0
- {sdpy_kit-0.2.1 → sdpy_kit-0.2.3}/kit/db/models/base.py +0 -0
- {sdpy_kit-0.2.1 → sdpy_kit-0.2.3}/kit/db/query.py +0 -0
- {sdpy_kit-0.2.1 → sdpy_kit-0.2.3}/kit/db/session.py +0 -0
- {sdpy_kit-0.2.1 → sdpy_kit-0.2.3}/kit/db/transaction.py +0 -0
- {sdpy_kit-0.2.1 → sdpy_kit-0.2.3}/kit/docx_kit.py +0 -0
- {sdpy_kit-0.2.1 → sdpy_kit-0.2.3}/kit/file_kit.py +0 -0
- {sdpy_kit-0.2.1 → sdpy_kit-0.2.3}/kit/marker_kit.py +0 -0
- {sdpy_kit-0.2.1 → sdpy_kit-0.2.3}/kit/mcp_kit.py +0 -0
- {sdpy_kit-0.2.1 → sdpy_kit-0.2.3}/kit/md2doc_kit.py +0 -0
- {sdpy_kit-0.2.1 → sdpy_kit-0.2.3}/kit/mineru_kit.py +0 -0
- {sdpy_kit-0.2.1 → sdpy_kit-0.2.3}/kit/redis_kit.py +0 -0
- {sdpy_kit-0.2.1 → sdpy_kit-0.2.3}/kit/wx_article_kit.py +0 -0
- {sdpy_kit-0.2.1 → sdpy_kit-0.2.3}/kit/zvec_kit.py +0 -0
- {sdpy_kit-0.2.1 → sdpy_kit-0.2.3}/pyproject.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: sdpy-kit
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: SDPY Kit——统一数据库访问层(PG/MySQL/SQLite)与 AI/MCP/Redis/文件/文档/向量工具集
|
|
5
5
|
Author-email: Clark <changhongyuan@126.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -370,13 +370,23 @@ from kit import session_scope, transactional, DBError # 等价
|
|
|
370
370
|
| 表 | `list_tables() -> list[str]` | 列出全部表名 |
|
|
371
371
|
| 表 | `table_exists(table_name) -> bool` | 判断表是否存在 |
|
|
372
372
|
| 表 | `create_tables(models) -> None` | 按模型列表建表 |
|
|
373
|
-
| 表 | `get_table_info(table_name) -> list[dict]` |
|
|
373
|
+
| 表 | `get_table_info(table_name) -> list[dict]` | 获取列信息(按列序;公共键 4 个:`column_name`/`data_type`/`is_nullable`/`column_default`) |
|
|
374
|
+
|
|
375
|
+
> **列表参数(`IN`)**:`query` / `execute` 的 `params` 中值为 `list`/`tuple`/`set`/`frozenset`
|
|
376
|
+
> 时按 `IN` 展开,可直接写 `WHERE id IN :ids`(三库通用)。`IN` **不保证顺序**;参数列表过大
|
|
377
|
+
> 应分批(PG 单语句上限 65535 参数)。
|
|
378
|
+
>
|
|
379
|
+
> **`LIKE` 的方言差异**:SQLite 对 ASCII 不区分大小写,PostgreSQL 区分(用 `ILIKE`);
|
|
380
|
+
> 跨库一致写法:`LOWER(col) LIKE LOWER(:pattern)`。
|
|
381
|
+
>
|
|
382
|
+
> **导出面**:`from kit.db import PgKit, SQLiteKit, build_pg_url, to_vector_literal, …`
|
|
383
|
+
> (`kit` 顶层与 `kit.db` 导出同一套符号)。
|
|
374
384
|
|
|
375
385
|
```python
|
|
376
386
|
from sqlalchemy import BigInteger
|
|
377
387
|
from sqlmodel import SQLModel, Field
|
|
378
388
|
from kit.common_kit import CommonKit
|
|
379
|
-
from kit.db.sqlite_kit import SQLiteKit
|
|
389
|
+
from kit.db import SQLiteKit # 也可 from kit.db.sqlite_kit import SQLiteKit
|
|
380
390
|
|
|
381
391
|
class User(SQLModel, table=True):
|
|
382
392
|
# 主键:64 位整数 + 雪花 ID(应用侧生成,不用数据库自增)
|
|
@@ -393,6 +403,7 @@ users = SQLiteKit.list(User, skip=0, limit=10, order_by="id", desc=True)
|
|
|
393
403
|
|
|
394
404
|
rows = SQLiteKit.query("SELECT * FROM user WHERE name = :name", {"name": "李四"})
|
|
395
405
|
affected = SQLiteKit.execute("UPDATE user SET name = '王五' WHERE id = :id", {"id": user.id})
|
|
406
|
+
rows = SQLiteKit.query("SELECT * FROM user WHERE id IN :ids", {"ids": [1, 2, 3]}) # 列表参数
|
|
396
407
|
|
|
397
408
|
with SQLiteKit.transaction() as session: # 多操作原子提交
|
|
398
409
|
SQLiteKit.create(User, {"name": "批量一"})
|
|
@@ -400,8 +411,9 @@ with SQLiteKit.transaction() as session: # 多操作原子提
|
|
|
400
411
|
```
|
|
401
412
|
|
|
402
413
|
> 切换数据库:把 import 的 `SQLiteKit` 换成 `PgKit` / `MySqlKit` 即可,其余代码不变。
|
|
403
|
-
>
|
|
404
|
-
>
|
|
414
|
+
> 注意三点例外:① `create_raw_table` 的列定义文本须按目标库方言改写;② 向量能力
|
|
415
|
+
> **三库同名同参但能力不同**(可由 `VECTOR_CAPABILITIES` feature-detect),尤其
|
|
416
|
+
> **MySQL 侧检索不可用**;③ `create_vector_table` 的 `dims` 为**必填**参数。
|
|
405
417
|
|
|
406
418
|
### 2. 向量方法(三库同名同参,能力差异化)
|
|
407
419
|
|
|
@@ -412,21 +424,34 @@ with SQLiteKit.transaction() as session: # 多操作原子提
|
|
|
412
424
|
|
|
413
425
|
| 方法 | 说明 |
|
|
414
426
|
|------|------|
|
|
415
|
-
| `create_vector_table(table_name, id_col="id",
|
|
416
|
-
| `insert_vectors(table_name, vectors, ids=None, extra_data=None) -> int` | 批量插入向量(单事务提交);`ids=None` 时由 Kit 生成雪花 ID |
|
|
417
|
-
| `search_vectors(table_name, query_vector, top_k=10, distance_type="
|
|
427
|
+
| `create_vector_table(table_name, dims, id_col="id", column="embedding", extra_cols=None) -> None` | 建含向量列的表;**`dims` 必填**(SQLite 用 `CHECK` 落实为数据库层约束;PG 硬约束并回读校验已存在表;MySQL 为上限语义) |
|
|
428
|
+
| `insert_vectors(table_name, vectors, ids=None, extra_data=None, id_col="id", column="embedding") -> int` | 批量插入向量(单事务提交);`ids=None` 时由 Kit 生成雪花 ID;接受 `list[list[float]]` 或二维 numpy 数组 |
|
|
429
|
+
| `search_vectors(table_name, query_vector, top_k=10, distance_type="L2", filter_sql=None, exact=True, id_col="id", column="embedding") -> list[dict]` | KNN 检索,按距离升序返回 `{"id", "distance"}`;`exact=True`(默认)禁止近似加速;**MySQL 侧抛 `VectorNotSupportedError`** |
|
|
418
430
|
| `vector_version() -> str` | 向量后端版本;不可用时返回 `"unavailable"`(MySQL 侧为服务端版本号) |
|
|
419
431
|
|
|
420
|
-
|
|
432
|
+
**`id_col` / `column`(逃生口)**:让**自建 DDL 的向量表**也能读写/检索。需要复合主键、额外约束、
|
|
433
|
+
分区或与既有业务表合表时,用 `execute()` 自建 DDL 再用这两个参数接入;SQLite 侧要求 `id_col`
|
|
434
|
+
为 rowid 别名(`INTEGER PRIMARY KEY`)。
|
|
435
|
+
|
|
436
|
+
**`exact` 参数(默认 `True` = 禁止近似加速)**:
|
|
437
|
+
|
|
438
|
+
| 后端 | `exact=True`(默认) | `exact=False` | 是否提速 |
|
|
439
|
+
|------|---------------------|---------------|----------|
|
|
440
|
+
| `PgKit` | 同事务 `SET LOCAL enable_indexscan = off`(有索引也精确;**代价:该查询不使用任何索引**,含过滤列索引) | 不干预查询计划(有索引即近似召回,`distance` 仍为真值) | ✅ 仅"大表 + 已建索引"时 |
|
|
441
|
+
| `SQLiteKit` | `vector_full_scan`(**真值 L2**) | `vector_quantize_scan`(量化 L2 = 真值 × 255) | ❌ **不提速**(每次调用重建量化) |
|
|
442
|
+
| `MySqlKit` | 不适用 | 同左 | — |
|
|
443
|
+
|
|
444
|
+
**能力差异(仅 L2 可跨库比较数值;float32 vs float64,需带容差)**:
|
|
421
445
|
|
|
422
446
|
| 项 | `SQLiteKit`(sqlite-vector) | `PgKit`(pgvector) | `MySqlKit`(9.x 原生) |
|
|
423
447
|
|----|------------------------------|---------------------|------------------------|
|
|
424
|
-
| 支持的 `distance_type` | **仅 L2
|
|
425
|
-
|
|
|
426
|
-
|
|
|
427
|
-
|
|
|
428
|
-
|
|
|
429
|
-
|
|
|
448
|
+
| 支持的 `distance_type` | **仅 L2** | `COSINE` / `L2` / `L1` / `DOT`(负内积) / `SQUARED_L2` | **无(检索不可用)** |
|
|
449
|
+
| 默认 `distance_type` | `"L2"` | `"L2"`(三库统一,不按后端覆写) | `"L2"`(形参保留,调用即抛错) |
|
|
450
|
+
| 非法取值 | 抛 `ValueError` | 抛 `ValueError` | `search_vectors` 抛 `VectorNotSupportedError` |
|
|
451
|
+
| `distance` 口径 | `exact=True` 真值 L2 / `exact=False` 量化(×255) | 真实距离 | — |
|
|
452
|
+
| 维度约束 | `CHECK`(仅新表;旧表靠方法内校验) | 强制(不匹配即报错;建表时回读比对) | 上限约束(超限报错) |
|
|
453
|
+
| `filter_sql` 与 `top_k` | **先过滤再取 top_k**(与 PG 一致) | **先过滤再取 top_k** | 不适用 |
|
|
454
|
+
| 索引 | 无(精确路径为全表扫描) | kit 不建索引;**自建后须 `exact=False` 才会被使用** | 无法建(B-Tree 拒绝向量列) |
|
|
430
455
|
|
|
431
456
|
> MySQL `DISTANCE()` / `VECTOR_DISTANCE()` 仅 HeatWave on OCI 与 MySQL AI 提供
|
|
432
457
|
> (Community 实测报 `1305 FUNCTION ... does not exist`);故 `MySqlKit` 只覆盖存储侧。
|
|
@@ -438,24 +463,85 @@ with SQLiteKit.transaction() as session: # 多操作原子提
|
|
|
438
463
|
多实例部署需各自 `CommonKit.configure(datacenter_id, machine_id)`(默认 `(1,1)` 会重复)。
|
|
439
464
|
|
|
440
465
|
```python
|
|
441
|
-
# SQLite
|
|
466
|
+
# SQLite(默认精确:distance 为真值 L2)
|
|
442
467
|
from kit.db.sqlite_kit import SQLiteKit
|
|
443
|
-
SQLiteKit.create_vector_table("images",
|
|
468
|
+
SQLiteKit.create_vector_table("images", 384) # dims 必填
|
|
444
469
|
SQLiteKit.insert_vectors("images", [[0.1] * 384, [0.2] * 384])
|
|
445
470
|
rows = SQLiteKit.search_vectors("images", [0.1] * 384, top_k=5)
|
|
471
|
+
rows = SQLiteKit.search_vectors("images", [0.1] * 384, top_k=5, exact=False) # 旧量化口径(不提速)
|
|
446
472
|
|
|
447
473
|
# PostgreSQL(签名相同;可选 COSINE/L2/L1/DOT/SQUARED_L2)
|
|
448
474
|
from kit.db.pg_kit import PgKit
|
|
449
|
-
PgKit.create_vector_table("images",
|
|
475
|
+
PgKit.create_vector_table("images", 384)
|
|
450
476
|
PgKit.insert_vectors("images", [[0.1] * 384, [0.2] * 384])
|
|
451
|
-
rows = PgKit.search_vectors("images", [0.1] * 384, top_k=5, distance_type="
|
|
477
|
+
rows = PgKit.search_vectors("images", [0.1] * 384, top_k=5, distance_type="COSINE")
|
|
478
|
+
# 大表 + 自建索引时才用 exact=False(否则索引不被使用,见上表)
|
|
479
|
+
PgKit.execute("CREATE INDEX ON images USING hnsw (embedding vector_cosine_ops)")
|
|
480
|
+
rows = PgKit.search_vectors("images", [0.1] * 384, top_k=5,
|
|
481
|
+
distance_type="COSINE", exact=False)
|
|
452
482
|
|
|
453
|
-
# MySQL(仅存储;search_vectors
|
|
483
|
+
# MySQL(仅存储;search_vectors 抛 VectorNotSupportedError)
|
|
454
484
|
from kit.db.mysql_kit import MySqlKit
|
|
455
|
-
MySqlKit.create_vector_table("images",
|
|
485
|
+
MySqlKit.create_vector_table("images", 384)
|
|
456
486
|
MySqlKit.insert_vectors("images", [[0.1] * 384, [0.2] * 384])
|
|
487
|
+
|
|
488
|
+
# 能力自省(无需 try/except)
|
|
489
|
+
PgKit.VECTOR_CAPABILITIES
|
|
490
|
+
# {'store': True, 'search': True, 'exact': True,
|
|
491
|
+
# 'distances': ('COSINE', 'L2', 'L1', 'DOT', 'SQUARED_L2'),
|
|
492
|
+
# 'distance_kind': 'true', 'fast_distance_kind': 'true'}
|
|
493
|
+
|
|
494
|
+
# 自建 DDL 的表:用 id_col / column 接入
|
|
495
|
+
PgKit.execute("CREATE TABLE my_vec (vid BIGINT PRIMARY KEY, vec vector(384))")
|
|
496
|
+
PgKit.insert_vectors("my_vec", [[0.1] * 384], ids=[1], id_col="vid", column="vec")
|
|
497
|
+
PgKit.search_vectors("my_vec", [0.1] * 384, top_k=5, id_col="vid", column="vec")
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
#### 0.2.2 行为变更(升级须知)
|
|
501
|
+
|
|
502
|
+
| 编号 | 变更 | 影响 |
|
|
503
|
+
|------|------|------|
|
|
504
|
+
| B1 | `search_vectors` 新增 `exact: bool = True`,默认**精确** | SQLite 返回的 `distance` 由量化值(≈L2×255)变为**真值 L2**;需旧口径请显式 `exact=False` |
|
|
505
|
+
| B2 | `filter_sql` 语义统一为**先过滤、再取 top_k** | 传 `filter_sql` 的调用返回值**变多/变全**(旧结果 ⊆ 新结果);曾自行放大 `top_k` 兜底的调用可收紧 |
|
|
506
|
+
| B3 | 默认 `distance_type` 由 `"COSINE"` 改为 `"L2"` | 依赖默认值的调用方请显式传 `distance_type="COSINE"` |
|
|
507
|
+
| B4 | `create_vector_table` 的 `dims` **必填**,参数顺序为 `(table_name, dims, id_col, column, extra_cols)` | `create_vector_table("t")` 类调用需补维度;位置传 `id_col` 的调用改为关键字 |
|
|
508
|
+
| B5 | MySQL 的 `search_vectors` 异常由 `NotImplementedError` 改为 `VectorNotSupportedError`(继承 `DBError`) | `except NotImplementedError` 需改为 `except VectorNotSupportedError`(或 `except DBError`) |
|
|
509
|
+
|
|
510
|
+
> 新增(非破坏性):`VECTOR_CAPABILITIES` 能力声明、`id_col` / `column` 逃生口参数、
|
|
511
|
+
> `insert_vectors` 支持二维 numpy 数组与批量提交。
|
|
512
|
+
|
|
513
|
+
#### 0.2.3 变更
|
|
514
|
+
|
|
515
|
+
| 编号 | 变更 | 影响 |
|
|
516
|
+
|------|------|------|
|
|
517
|
+
| B6 | **`PgKit.get_table_info` 按数据库解析语义定位表**(`search_path` 解析到的那张表) | 多 schema 下同名表**不再混行**;单 schema / `public` 场景**无影响**;只接受非限定表名 |
|
|
518
|
+
| — | `query` / `execute` 支持**列表参数**(`IN :ids`,三库通用) | 纯新增;`set`/`frozenset` 自动归一化为 `list` |
|
|
519
|
+
| — | 新增 `SQLiteKit.encode_vector` / `decode_vector`(小端 float32) | 纯新增:可在自有事务内写向量表,见下 |
|
|
520
|
+
| — | 导出面统一:`from kit.db import PgKit, MySqlKit, SQLiteKit, build_pg_url, …` | 纯新增;既有导出未删除/未改名 |
|
|
521
|
+
|
|
522
|
+
**两段查询范式(推荐用法)**:`search_vectors` 只返回 `[{"id", "distance"}]`,载荷二次取回:
|
|
523
|
+
|
|
524
|
+
```python
|
|
525
|
+
hits = SQLiteKit.search_vectors("item_vectors", query_vector, top_k=5)
|
|
526
|
+
rows = SQLiteKit.query("SELECT id, title FROM items WHERE id IN :ids",
|
|
527
|
+
{"ids": [h["id"] for h in hits]}) # IN 不保证顺序,需按 hits 重排
|
|
457
528
|
```
|
|
458
529
|
|
|
530
|
+
**自有事务内写向量(SQLite)**:
|
|
531
|
+
|
|
532
|
+
```python
|
|
533
|
+
conn = SQLiteKit.raw_connection() # DBAPI 连接:位置参数 ? + 手工 commit
|
|
534
|
+
try:
|
|
535
|
+
conn.execute("INSERT INTO item_vectors (id, embedding) VALUES (?, ?)",
|
|
536
|
+
(item_id, SQLiteKit.encode_vector(vec)))
|
|
537
|
+
conn.commit()
|
|
538
|
+
finally:
|
|
539
|
+
conn.close()
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
> 相似度换算建议:SQLite 仅支持 L2,若向量入库前已**归一化**,则 `cos = 1 − L2²/2`;
|
|
543
|
+
> PG 可直接用 `distance_type="COSINE"`。
|
|
544
|
+
|
|
459
545
|
### 3. CRUDBase(配合 session_scope,不自动提交)
|
|
460
546
|
|
|
461
547
|
```python
|
|
@@ -300,13 +300,23 @@ from kit import session_scope, transactional, DBError # 等价
|
|
|
300
300
|
| 表 | `list_tables() -> list[str]` | 列出全部表名 |
|
|
301
301
|
| 表 | `table_exists(table_name) -> bool` | 判断表是否存在 |
|
|
302
302
|
| 表 | `create_tables(models) -> None` | 按模型列表建表 |
|
|
303
|
-
| 表 | `get_table_info(table_name) -> list[dict]` |
|
|
303
|
+
| 表 | `get_table_info(table_name) -> list[dict]` | 获取列信息(按列序;公共键 4 个:`column_name`/`data_type`/`is_nullable`/`column_default`) |
|
|
304
|
+
|
|
305
|
+
> **列表参数(`IN`)**:`query` / `execute` 的 `params` 中值为 `list`/`tuple`/`set`/`frozenset`
|
|
306
|
+
> 时按 `IN` 展开,可直接写 `WHERE id IN :ids`(三库通用)。`IN` **不保证顺序**;参数列表过大
|
|
307
|
+
> 应分批(PG 单语句上限 65535 参数)。
|
|
308
|
+
>
|
|
309
|
+
> **`LIKE` 的方言差异**:SQLite 对 ASCII 不区分大小写,PostgreSQL 区分(用 `ILIKE`);
|
|
310
|
+
> 跨库一致写法:`LOWER(col) LIKE LOWER(:pattern)`。
|
|
311
|
+
>
|
|
312
|
+
> **导出面**:`from kit.db import PgKit, SQLiteKit, build_pg_url, to_vector_literal, …`
|
|
313
|
+
> (`kit` 顶层与 `kit.db` 导出同一套符号)。
|
|
304
314
|
|
|
305
315
|
```python
|
|
306
316
|
from sqlalchemy import BigInteger
|
|
307
317
|
from sqlmodel import SQLModel, Field
|
|
308
318
|
from kit.common_kit import CommonKit
|
|
309
|
-
from kit.db.sqlite_kit import SQLiteKit
|
|
319
|
+
from kit.db import SQLiteKit # 也可 from kit.db.sqlite_kit import SQLiteKit
|
|
310
320
|
|
|
311
321
|
class User(SQLModel, table=True):
|
|
312
322
|
# 主键:64 位整数 + 雪花 ID(应用侧生成,不用数据库自增)
|
|
@@ -323,6 +333,7 @@ users = SQLiteKit.list(User, skip=0, limit=10, order_by="id", desc=True)
|
|
|
323
333
|
|
|
324
334
|
rows = SQLiteKit.query("SELECT * FROM user WHERE name = :name", {"name": "李四"})
|
|
325
335
|
affected = SQLiteKit.execute("UPDATE user SET name = '王五' WHERE id = :id", {"id": user.id})
|
|
336
|
+
rows = SQLiteKit.query("SELECT * FROM user WHERE id IN :ids", {"ids": [1, 2, 3]}) # 列表参数
|
|
326
337
|
|
|
327
338
|
with SQLiteKit.transaction() as session: # 多操作原子提交
|
|
328
339
|
SQLiteKit.create(User, {"name": "批量一"})
|
|
@@ -330,8 +341,9 @@ with SQLiteKit.transaction() as session: # 多操作原子提
|
|
|
330
341
|
```
|
|
331
342
|
|
|
332
343
|
> 切换数据库:把 import 的 `SQLiteKit` 换成 `PgKit` / `MySqlKit` 即可,其余代码不变。
|
|
333
|
-
>
|
|
334
|
-
>
|
|
344
|
+
> 注意三点例外:① `create_raw_table` 的列定义文本须按目标库方言改写;② 向量能力
|
|
345
|
+
> **三库同名同参但能力不同**(可由 `VECTOR_CAPABILITIES` feature-detect),尤其
|
|
346
|
+
> **MySQL 侧检索不可用**;③ `create_vector_table` 的 `dims` 为**必填**参数。
|
|
335
347
|
|
|
336
348
|
### 2. 向量方法(三库同名同参,能力差异化)
|
|
337
349
|
|
|
@@ -342,21 +354,34 @@ with SQLiteKit.transaction() as session: # 多操作原子提
|
|
|
342
354
|
|
|
343
355
|
| 方法 | 说明 |
|
|
344
356
|
|------|------|
|
|
345
|
-
| `create_vector_table(table_name, id_col="id",
|
|
346
|
-
| `insert_vectors(table_name, vectors, ids=None, extra_data=None) -> int` | 批量插入向量(单事务提交);`ids=None` 时由 Kit 生成雪花 ID |
|
|
347
|
-
| `search_vectors(table_name, query_vector, top_k=10, distance_type="
|
|
357
|
+
| `create_vector_table(table_name, dims, id_col="id", column="embedding", extra_cols=None) -> None` | 建含向量列的表;**`dims` 必填**(SQLite 用 `CHECK` 落实为数据库层约束;PG 硬约束并回读校验已存在表;MySQL 为上限语义) |
|
|
358
|
+
| `insert_vectors(table_name, vectors, ids=None, extra_data=None, id_col="id", column="embedding") -> int` | 批量插入向量(单事务提交);`ids=None` 时由 Kit 生成雪花 ID;接受 `list[list[float]]` 或二维 numpy 数组 |
|
|
359
|
+
| `search_vectors(table_name, query_vector, top_k=10, distance_type="L2", filter_sql=None, exact=True, id_col="id", column="embedding") -> list[dict]` | KNN 检索,按距离升序返回 `{"id", "distance"}`;`exact=True`(默认)禁止近似加速;**MySQL 侧抛 `VectorNotSupportedError`** |
|
|
348
360
|
| `vector_version() -> str` | 向量后端版本;不可用时返回 `"unavailable"`(MySQL 侧为服务端版本号) |
|
|
349
361
|
|
|
350
|
-
|
|
362
|
+
**`id_col` / `column`(逃生口)**:让**自建 DDL 的向量表**也能读写/检索。需要复合主键、额外约束、
|
|
363
|
+
分区或与既有业务表合表时,用 `execute()` 自建 DDL 再用这两个参数接入;SQLite 侧要求 `id_col`
|
|
364
|
+
为 rowid 别名(`INTEGER PRIMARY KEY`)。
|
|
365
|
+
|
|
366
|
+
**`exact` 参数(默认 `True` = 禁止近似加速)**:
|
|
367
|
+
|
|
368
|
+
| 后端 | `exact=True`(默认) | `exact=False` | 是否提速 |
|
|
369
|
+
|------|---------------------|---------------|----------|
|
|
370
|
+
| `PgKit` | 同事务 `SET LOCAL enable_indexscan = off`(有索引也精确;**代价:该查询不使用任何索引**,含过滤列索引) | 不干预查询计划(有索引即近似召回,`distance` 仍为真值) | ✅ 仅"大表 + 已建索引"时 |
|
|
371
|
+
| `SQLiteKit` | `vector_full_scan`(**真值 L2**) | `vector_quantize_scan`(量化 L2 = 真值 × 255) | ❌ **不提速**(每次调用重建量化) |
|
|
372
|
+
| `MySqlKit` | 不适用 | 同左 | — |
|
|
373
|
+
|
|
374
|
+
**能力差异(仅 L2 可跨库比较数值;float32 vs float64,需带容差)**:
|
|
351
375
|
|
|
352
376
|
| 项 | `SQLiteKit`(sqlite-vector) | `PgKit`(pgvector) | `MySqlKit`(9.x 原生) |
|
|
353
377
|
|----|------------------------------|---------------------|------------------------|
|
|
354
|
-
| 支持的 `distance_type` | **仅 L2
|
|
355
|
-
|
|
|
356
|
-
|
|
|
357
|
-
|
|
|
358
|
-
|
|
|
359
|
-
|
|
|
378
|
+
| 支持的 `distance_type` | **仅 L2** | `COSINE` / `L2` / `L1` / `DOT`(负内积) / `SQUARED_L2` | **无(检索不可用)** |
|
|
379
|
+
| 默认 `distance_type` | `"L2"` | `"L2"`(三库统一,不按后端覆写) | `"L2"`(形参保留,调用即抛错) |
|
|
380
|
+
| 非法取值 | 抛 `ValueError` | 抛 `ValueError` | `search_vectors` 抛 `VectorNotSupportedError` |
|
|
381
|
+
| `distance` 口径 | `exact=True` 真值 L2 / `exact=False` 量化(×255) | 真实距离 | — |
|
|
382
|
+
| 维度约束 | `CHECK`(仅新表;旧表靠方法内校验) | 强制(不匹配即报错;建表时回读比对) | 上限约束(超限报错) |
|
|
383
|
+
| `filter_sql` 与 `top_k` | **先过滤再取 top_k**(与 PG 一致) | **先过滤再取 top_k** | 不适用 |
|
|
384
|
+
| 索引 | 无(精确路径为全表扫描) | kit 不建索引;**自建后须 `exact=False` 才会被使用** | 无法建(B-Tree 拒绝向量列) |
|
|
360
385
|
|
|
361
386
|
> MySQL `DISTANCE()` / `VECTOR_DISTANCE()` 仅 HeatWave on OCI 与 MySQL AI 提供
|
|
362
387
|
> (Community 实测报 `1305 FUNCTION ... does not exist`);故 `MySqlKit` 只覆盖存储侧。
|
|
@@ -368,24 +393,85 @@ with SQLiteKit.transaction() as session: # 多操作原子提
|
|
|
368
393
|
多实例部署需各自 `CommonKit.configure(datacenter_id, machine_id)`(默认 `(1,1)` 会重复)。
|
|
369
394
|
|
|
370
395
|
```python
|
|
371
|
-
# SQLite
|
|
396
|
+
# SQLite(默认精确:distance 为真值 L2)
|
|
372
397
|
from kit.db.sqlite_kit import SQLiteKit
|
|
373
|
-
SQLiteKit.create_vector_table("images",
|
|
398
|
+
SQLiteKit.create_vector_table("images", 384) # dims 必填
|
|
374
399
|
SQLiteKit.insert_vectors("images", [[0.1] * 384, [0.2] * 384])
|
|
375
400
|
rows = SQLiteKit.search_vectors("images", [0.1] * 384, top_k=5)
|
|
401
|
+
rows = SQLiteKit.search_vectors("images", [0.1] * 384, top_k=5, exact=False) # 旧量化口径(不提速)
|
|
376
402
|
|
|
377
403
|
# PostgreSQL(签名相同;可选 COSINE/L2/L1/DOT/SQUARED_L2)
|
|
378
404
|
from kit.db.pg_kit import PgKit
|
|
379
|
-
PgKit.create_vector_table("images",
|
|
405
|
+
PgKit.create_vector_table("images", 384)
|
|
380
406
|
PgKit.insert_vectors("images", [[0.1] * 384, [0.2] * 384])
|
|
381
|
-
rows = PgKit.search_vectors("images", [0.1] * 384, top_k=5, distance_type="
|
|
407
|
+
rows = PgKit.search_vectors("images", [0.1] * 384, top_k=5, distance_type="COSINE")
|
|
408
|
+
# 大表 + 自建索引时才用 exact=False(否则索引不被使用,见上表)
|
|
409
|
+
PgKit.execute("CREATE INDEX ON images USING hnsw (embedding vector_cosine_ops)")
|
|
410
|
+
rows = PgKit.search_vectors("images", [0.1] * 384, top_k=5,
|
|
411
|
+
distance_type="COSINE", exact=False)
|
|
382
412
|
|
|
383
|
-
# MySQL(仅存储;search_vectors
|
|
413
|
+
# MySQL(仅存储;search_vectors 抛 VectorNotSupportedError)
|
|
384
414
|
from kit.db.mysql_kit import MySqlKit
|
|
385
|
-
MySqlKit.create_vector_table("images",
|
|
415
|
+
MySqlKit.create_vector_table("images", 384)
|
|
386
416
|
MySqlKit.insert_vectors("images", [[0.1] * 384, [0.2] * 384])
|
|
417
|
+
|
|
418
|
+
# 能力自省(无需 try/except)
|
|
419
|
+
PgKit.VECTOR_CAPABILITIES
|
|
420
|
+
# {'store': True, 'search': True, 'exact': True,
|
|
421
|
+
# 'distances': ('COSINE', 'L2', 'L1', 'DOT', 'SQUARED_L2'),
|
|
422
|
+
# 'distance_kind': 'true', 'fast_distance_kind': 'true'}
|
|
423
|
+
|
|
424
|
+
# 自建 DDL 的表:用 id_col / column 接入
|
|
425
|
+
PgKit.execute("CREATE TABLE my_vec (vid BIGINT PRIMARY KEY, vec vector(384))")
|
|
426
|
+
PgKit.insert_vectors("my_vec", [[0.1] * 384], ids=[1], id_col="vid", column="vec")
|
|
427
|
+
PgKit.search_vectors("my_vec", [0.1] * 384, top_k=5, id_col="vid", column="vec")
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
#### 0.2.2 行为变更(升级须知)
|
|
431
|
+
|
|
432
|
+
| 编号 | 变更 | 影响 |
|
|
433
|
+
|------|------|------|
|
|
434
|
+
| B1 | `search_vectors` 新增 `exact: bool = True`,默认**精确** | SQLite 返回的 `distance` 由量化值(≈L2×255)变为**真值 L2**;需旧口径请显式 `exact=False` |
|
|
435
|
+
| B2 | `filter_sql` 语义统一为**先过滤、再取 top_k** | 传 `filter_sql` 的调用返回值**变多/变全**(旧结果 ⊆ 新结果);曾自行放大 `top_k` 兜底的调用可收紧 |
|
|
436
|
+
| B3 | 默认 `distance_type` 由 `"COSINE"` 改为 `"L2"` | 依赖默认值的调用方请显式传 `distance_type="COSINE"` |
|
|
437
|
+
| B4 | `create_vector_table` 的 `dims` **必填**,参数顺序为 `(table_name, dims, id_col, column, extra_cols)` | `create_vector_table("t")` 类调用需补维度;位置传 `id_col` 的调用改为关键字 |
|
|
438
|
+
| B5 | MySQL 的 `search_vectors` 异常由 `NotImplementedError` 改为 `VectorNotSupportedError`(继承 `DBError`) | `except NotImplementedError` 需改为 `except VectorNotSupportedError`(或 `except DBError`) |
|
|
439
|
+
|
|
440
|
+
> 新增(非破坏性):`VECTOR_CAPABILITIES` 能力声明、`id_col` / `column` 逃生口参数、
|
|
441
|
+
> `insert_vectors` 支持二维 numpy 数组与批量提交。
|
|
442
|
+
|
|
443
|
+
#### 0.2.3 变更
|
|
444
|
+
|
|
445
|
+
| 编号 | 变更 | 影响 |
|
|
446
|
+
|------|------|------|
|
|
447
|
+
| B6 | **`PgKit.get_table_info` 按数据库解析语义定位表**(`search_path` 解析到的那张表) | 多 schema 下同名表**不再混行**;单 schema / `public` 场景**无影响**;只接受非限定表名 |
|
|
448
|
+
| — | `query` / `execute` 支持**列表参数**(`IN :ids`,三库通用) | 纯新增;`set`/`frozenset` 自动归一化为 `list` |
|
|
449
|
+
| — | 新增 `SQLiteKit.encode_vector` / `decode_vector`(小端 float32) | 纯新增:可在自有事务内写向量表,见下 |
|
|
450
|
+
| — | 导出面统一:`from kit.db import PgKit, MySqlKit, SQLiteKit, build_pg_url, …` | 纯新增;既有导出未删除/未改名 |
|
|
451
|
+
|
|
452
|
+
**两段查询范式(推荐用法)**:`search_vectors` 只返回 `[{"id", "distance"}]`,载荷二次取回:
|
|
453
|
+
|
|
454
|
+
```python
|
|
455
|
+
hits = SQLiteKit.search_vectors("item_vectors", query_vector, top_k=5)
|
|
456
|
+
rows = SQLiteKit.query("SELECT id, title FROM items WHERE id IN :ids",
|
|
457
|
+
{"ids": [h["id"] for h in hits]}) # IN 不保证顺序,需按 hits 重排
|
|
387
458
|
```
|
|
388
459
|
|
|
460
|
+
**自有事务内写向量(SQLite)**:
|
|
461
|
+
|
|
462
|
+
```python
|
|
463
|
+
conn = SQLiteKit.raw_connection() # DBAPI 连接:位置参数 ? + 手工 commit
|
|
464
|
+
try:
|
|
465
|
+
conn.execute("INSERT INTO item_vectors (id, embedding) VALUES (?, ?)",
|
|
466
|
+
(item_id, SQLiteKit.encode_vector(vec)))
|
|
467
|
+
conn.commit()
|
|
468
|
+
finally:
|
|
469
|
+
conn.close()
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
> 相似度换算建议:SQLite 仅支持 L2,若向量入库前已**归一化**,则 `cos = 1 − L2²/2`;
|
|
473
|
+
> PG 可直接用 `distance_type="COSINE"`。
|
|
474
|
+
|
|
389
475
|
### 3. CRUDBase(配合 session_scope,不自动提交)
|
|
390
476
|
|
|
391
477
|
```python
|
|
@@ -23,26 +23,37 @@ Author: Clark Chang
|
|
|
23
23
|
数据库访问通过 kit.db 子包提供(基于 SQLModel)。
|
|
24
24
|
"""
|
|
25
25
|
|
|
26
|
-
__version__ = "0.2.
|
|
26
|
+
__version__ = "0.2.3"
|
|
27
27
|
|
|
28
28
|
from kit.db import (
|
|
29
29
|
MYSQL,
|
|
30
30
|
POSTGRESQL,
|
|
31
31
|
SQLITE,
|
|
32
32
|
CRUDBase,
|
|
33
|
+
DEFAULT_DISTANCE_TYPE,
|
|
33
34
|
DBError,
|
|
35
|
+
MySqlKit,
|
|
34
36
|
NotFoundError,
|
|
37
|
+
PgKit,
|
|
38
|
+
SQLiteKit,
|
|
39
|
+
VectorNotSupportedError,
|
|
40
|
+
build_mysql_url,
|
|
41
|
+
build_pg_url,
|
|
42
|
+
build_sqlite_url,
|
|
35
43
|
close_all_engines,
|
|
36
44
|
close_engine,
|
|
37
45
|
create_tables,
|
|
46
|
+
generate_snowflake_ids,
|
|
38
47
|
get_engine,
|
|
39
48
|
get_session,
|
|
40
49
|
list_tables,
|
|
41
50
|
session_scope,
|
|
42
51
|
table_exists,
|
|
52
|
+
to_vector_literal,
|
|
43
53
|
transactional,
|
|
44
54
|
)
|
|
45
55
|
|
|
56
|
+
# 顶层公共面与 kit.db 保持一致(同一套符号,避免两处入口漂移)
|
|
46
57
|
__all__ = [
|
|
47
58
|
# 引擎管理
|
|
48
59
|
"get_engine",
|
|
@@ -51,18 +62,31 @@ __all__ = [
|
|
|
51
62
|
"table_exists",
|
|
52
63
|
"close_all_engines",
|
|
53
64
|
"close_engine",
|
|
65
|
+
# 连接串构造(单一来源)
|
|
66
|
+
"build_pg_url",
|
|
67
|
+
"build_mysql_url",
|
|
68
|
+
"build_sqlite_url",
|
|
54
69
|
# 常量
|
|
55
70
|
"POSTGRESQL",
|
|
56
71
|
"MYSQL",
|
|
57
72
|
"SQLITE",
|
|
73
|
+
# 数据库 Kit
|
|
74
|
+
"PgKit",
|
|
75
|
+
"MySqlKit",
|
|
76
|
+
"SQLiteKit",
|
|
58
77
|
# 会话
|
|
59
78
|
"session_scope",
|
|
60
79
|
"get_session",
|
|
61
80
|
# 异常
|
|
62
81
|
"DBError",
|
|
63
82
|
"NotFoundError",
|
|
83
|
+
"VectorNotSupportedError",
|
|
64
84
|
# CRUD
|
|
65
85
|
"CRUDBase",
|
|
66
86
|
# 事务
|
|
67
87
|
"transactional",
|
|
88
|
+
# 向量工具(公共)
|
|
89
|
+
"to_vector_literal",
|
|
90
|
+
"generate_snowflake_ids",
|
|
91
|
+
"DEFAULT_DISTANCE_TYPE",
|
|
68
92
|
]
|
|
@@ -29,11 +29,19 @@ Author: Clark Chang
|
|
|
29
29
|
- SQLModel 混入类(models/base.py)
|
|
30
30
|
"""
|
|
31
31
|
|
|
32
|
+
from kit.db.base_db_kit import (
|
|
33
|
+
DEFAULT_DISTANCE_TYPE,
|
|
34
|
+
generate_snowflake_ids,
|
|
35
|
+
to_vector_literal,
|
|
36
|
+
)
|
|
32
37
|
from kit.db.crud import CRUDBase
|
|
33
38
|
from kit.db.engine import (
|
|
34
39
|
MYSQL,
|
|
35
40
|
POSTGRESQL,
|
|
36
41
|
SQLITE,
|
|
42
|
+
build_mysql_url,
|
|
43
|
+
build_pg_url,
|
|
44
|
+
build_sqlite_url,
|
|
37
45
|
close_all_engines,
|
|
38
46
|
close_engine,
|
|
39
47
|
create_tables,
|
|
@@ -41,8 +49,11 @@ from kit.db.engine import (
|
|
|
41
49
|
list_tables,
|
|
42
50
|
table_exists,
|
|
43
51
|
)
|
|
44
|
-
from kit.db.exceptions import DBError, NotFoundError
|
|
52
|
+
from kit.db.exceptions import DBError, NotFoundError, VectorNotSupportedError
|
|
53
|
+
from kit.db.mysql_kit import MySqlKit
|
|
54
|
+
from kit.db.pg_kit import PgKit
|
|
45
55
|
from kit.db.session import get_session, session_scope
|
|
56
|
+
from kit.db.sqlite_kit import SQLiteKit
|
|
46
57
|
from kit.db.transaction import transactional
|
|
47
58
|
|
|
48
59
|
__all__ = [
|
|
@@ -53,18 +64,31 @@ __all__ = [
|
|
|
53
64
|
"table_exists",
|
|
54
65
|
"close_all_engines",
|
|
55
66
|
"close_engine",
|
|
67
|
+
# 连接串构造(单一来源,测试/工具可复用)
|
|
68
|
+
"build_pg_url",
|
|
69
|
+
"build_mysql_url",
|
|
70
|
+
"build_sqlite_url",
|
|
56
71
|
# 常量
|
|
57
72
|
"POSTGRESQL",
|
|
58
73
|
"MYSQL",
|
|
59
74
|
"SQLITE",
|
|
75
|
+
# 数据库 Kit
|
|
76
|
+
"PgKit",
|
|
77
|
+
"MySqlKit",
|
|
78
|
+
"SQLiteKit",
|
|
60
79
|
# 会话
|
|
61
80
|
"session_scope",
|
|
62
81
|
"get_session",
|
|
63
82
|
# 异常
|
|
64
83
|
"DBError",
|
|
65
84
|
"NotFoundError",
|
|
85
|
+
"VectorNotSupportedError",
|
|
66
86
|
# CRUD
|
|
67
87
|
"CRUDBase",
|
|
68
88
|
# 事务
|
|
69
89
|
"transactional",
|
|
90
|
+
# 向量工具(公共)
|
|
91
|
+
"to_vector_literal",
|
|
92
|
+
"generate_snowflake_ids",
|
|
93
|
+
"DEFAULT_DISTANCE_TYPE",
|
|
70
94
|
]
|
|
@@ -43,13 +43,14 @@ create_raw_table / raw_insert / raw_get_by)上移到本基类,使三个 Kit
|
|
|
43
43
|
"""
|
|
44
44
|
|
|
45
45
|
import builtins
|
|
46
|
+
import re
|
|
46
47
|
import threading
|
|
47
48
|
from abc import ABC, abstractmethod
|
|
48
49
|
from collections.abc import Generator
|
|
49
50
|
from contextlib import contextmanager
|
|
50
51
|
from typing import cast
|
|
51
52
|
|
|
52
|
-
from sqlalchemy import text
|
|
53
|
+
from sqlalchemy import TextClause, bindparam, text
|
|
53
54
|
from sqlalchemy.pool import PoolProxiedConnection
|
|
54
55
|
from sqlmodel import Session, SQLModel
|
|
55
56
|
|
|
@@ -70,6 +71,11 @@ logger = get_default_logger(__name__)
|
|
|
70
71
|
# 字段数据:列名到值的映射(object 覆盖全部 SQLModel 支持类型)
|
|
71
72
|
type FieldData = dict[str, object]
|
|
72
73
|
|
|
74
|
+
# 向量检索的默认距离类型:三库能力集的交集(SQLite 仅支持 L2),
|
|
75
|
+
# 由三个数据库 Kit 的 search_vectors 共用,避免各 Kit 默认值漂移。
|
|
76
|
+
# 默认值不得按后端覆写——否则同一份代码换 import 后会在静默中改变度量语义。
|
|
77
|
+
DEFAULT_DISTANCE_TYPE = "L2"
|
|
78
|
+
|
|
73
79
|
# CRUD 实例缓存:CRUDBase 无状态(仅存 model),按 model 复用避免重复实例化
|
|
74
80
|
# 值类型取宽基类 SQLModel,规避 PEP 695 类型参数上界约束
|
|
75
81
|
_crud_cache: dict[type, CRUDBase[SQLModel, SQLModel, SQLModel]] = {}
|
|
@@ -98,6 +104,26 @@ def to_vector_literal(vector: builtins.list[float]) -> str:
|
|
|
98
104
|
return "[" + ",".join(repr(float(value)) for value in vector) + "]"
|
|
99
105
|
|
|
100
106
|
|
|
107
|
+
def vector_insert_placeholders(
|
|
108
|
+
columns: builtins.list[str], id_col: str, column: str
|
|
109
|
+
) -> str:
|
|
110
|
+
"""生成向量表 INSERT 的占位符串(供三个数据库 Kit 的 insert_vectors 共用)
|
|
111
|
+
|
|
112
|
+
绑定参数名与列名**解耦**:id 列固定为 ``:id``、向量列固定为 ``:vec``、
|
|
113
|
+
其余列用 ``:列名``。这样调用方覆写 ``id_col`` / ``column``(自建 DDL 场景)时,
|
|
114
|
+
参数字典的键不受列名影响。
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
def _one(name: str) -> str:
|
|
118
|
+
if name == column:
|
|
119
|
+
return ":vec"
|
|
120
|
+
if name == id_col:
|
|
121
|
+
return ":id"
|
|
122
|
+
return f":{name}"
|
|
123
|
+
|
|
124
|
+
return ", ".join(_one(name) for name in columns)
|
|
125
|
+
|
|
126
|
+
|
|
101
127
|
def generate_snowflake_ids(count: int) -> builtins.list[int]:
|
|
102
128
|
"""生成 count 个雪花 ID(int64;当前 18 位,上限 19 位)
|
|
103
129
|
|
|
@@ -110,6 +136,50 @@ def generate_snowflake_ids(count: int) -> builtins.list[int]:
|
|
|
110
136
|
return [CommonKit.generate_snowflake_id() for _ in range(count)]
|
|
111
137
|
|
|
112
138
|
|
|
139
|
+
# 可参与 IN 展开的参数类型(按类型白名单判断,绝不用 Iterable:
|
|
140
|
+
# 否则字符串会被逐字符展开)
|
|
141
|
+
_EXPANDING_TYPES = (list, tuple, set, frozenset)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _prepare_statement(
|
|
145
|
+
sql: str, params: FieldData | None
|
|
146
|
+
) -> tuple[TextClause, FieldData | None]:
|
|
147
|
+
"""把 SQL 文本 + 参数字典转为(可执行语句, 归一化后的参数)
|
|
148
|
+
|
|
149
|
+
`params` 中值为**序列类型**(list / tuple / set / frozenset)的键按 SQLAlchemy
|
|
150
|
+
**expanding** 语义绑定,使 ``WHERE id IN :ids`` 可直接书写(三库通用)。
|
|
151
|
+
|
|
152
|
+
三条实现约束:
|
|
153
|
+
|
|
154
|
+
1. 按类型白名单判断(见 `_EXPANDING_TYPES`),字符串不会被当作列表;
|
|
155
|
+
2. 只对**确实出现在 SQL 中**的参数名做 expanding —— 否则传入"未使用的序列参数"
|
|
156
|
+
会触发 SQLAlchemy 的 ``ArgumentError: This text() construct doesn't define
|
|
157
|
+
a bound parameter …``,该报错对使用方没有意义;
|
|
158
|
+
3. `set` / `frozenset` 归一化为 `list` —— SQLAlchemy 的 expanding 只接受
|
|
159
|
+
**可下标序列**(直接传 set 会抛 ``TypeError: 'set' object is not subscriptable``);
|
|
160
|
+
``IN`` 与顺序无关,故归一化不改变语义。
|
|
161
|
+
"""
|
|
162
|
+
statement = text(sql)
|
|
163
|
+
if not params:
|
|
164
|
+
return statement, params
|
|
165
|
+
|
|
166
|
+
normalized: FieldData = {}
|
|
167
|
+
expanding: list[object] = []
|
|
168
|
+
for name, value in params.items():
|
|
169
|
+
if isinstance(value, _EXPANDING_TYPES):
|
|
170
|
+
if isinstance(value, (set, frozenset)):
|
|
171
|
+
value = list(value)
|
|
172
|
+
normalized[name] = value
|
|
173
|
+
if re.search(rf":{re.escape(name)}\b", sql):
|
|
174
|
+
expanding.append(bindparam(name, expanding=True))
|
|
175
|
+
else:
|
|
176
|
+
normalized[name] = value
|
|
177
|
+
|
|
178
|
+
if expanding:
|
|
179
|
+
statement = statement.bindparams(*expanding) # type: ignore[arg-type]
|
|
180
|
+
return statement, normalized
|
|
181
|
+
|
|
182
|
+
|
|
113
183
|
class BaseDbKit(ABC):
|
|
114
184
|
"""数据库工具类共享基类
|
|
115
185
|
|
|
@@ -360,13 +430,19 @@ class BaseDbKit(ABC):
|
|
|
360
430
|
|
|
361
431
|
Args:
|
|
362
432
|
sql: SQL 查询语句(使用命名参数 :param)
|
|
363
|
-
params:
|
|
433
|
+
params: 参数字典。值为 list / tuple / set / frozenset 的参数按 ``IN`` 展开,
|
|
434
|
+
可直接书写 ``WHERE id IN :ids``(三库通用)
|
|
364
435
|
|
|
365
436
|
Returns:
|
|
366
437
|
查询结果列表,每行为一个字典
|
|
438
|
+
|
|
439
|
+
Note:
|
|
440
|
+
``IN`` **不保证返回顺序**,需要顺序请自行按 id 重排;参数列表过大时
|
|
441
|
+
(如 PG 单语句参数上限 65535)应分批查询。
|
|
367
442
|
"""
|
|
443
|
+
statement, bound = _prepare_statement(sql, params)
|
|
368
444
|
with session_scope(cls.DB_TYPE, autocommit=False) as session:
|
|
369
|
-
result = session.execute(
|
|
445
|
+
result = session.execute(statement, bound or {})
|
|
370
446
|
rows = result.fetchall()
|
|
371
447
|
columns = result.keys()
|
|
372
448
|
data = [dict(zip(columns, row)) for row in rows]
|
|
@@ -379,13 +455,15 @@ class BaseDbKit(ABC):
|
|
|
379
455
|
|
|
380
456
|
Args:
|
|
381
457
|
sql: SQL 语句(使用命名参数 :param)
|
|
382
|
-
params:
|
|
458
|
+
params: 参数字典。值为 list / tuple / set / frozenset 的参数按 ``IN`` 展开
|
|
459
|
+
(如 ``DELETE FROM t WHERE id IN :ids``,三库通用)
|
|
383
460
|
|
|
384
461
|
Returns:
|
|
385
462
|
受影响的行数
|
|
386
463
|
"""
|
|
464
|
+
statement, bound = _prepare_statement(sql, params)
|
|
387
465
|
with session_scope(cls.DB_TYPE) as session:
|
|
388
|
-
result = session.execute(
|
|
466
|
+
result = session.execute(statement, bound or {})
|
|
389
467
|
affected = getattr(result, "rowcount", 0) or 0
|
|
390
468
|
logger.info(f"✅ 执行更新 → 影响 {affected} 行")
|
|
391
469
|
return affected
|