diff --git a/aidocs/__init__.md b/aidocs/__init__.md new file mode 100644 index 0000000..4e68b3d --- /dev/null +++ b/aidocs/__init__.md @@ -0,0 +1 @@ +当然可以!请先提供您需要编写技术文档的代码,我将根据代码内容为您生成详细的 Markdown 格式技术文档,包括功能说明、使用方法、参数解释、示例等内容。 \ No newline at end of file diff --git a/aidocs/aiomysqlor.md b/aidocs/aiomysqlor.md new file mode 100644 index 0000000..3464b64 --- /dev/null +++ b/aidocs/aiomysqlor.md @@ -0,0 +1,105 @@ +# AioMysqlor 技术文档 + +```markdown +# `AioMysqlor` 类文档 + +## 概述 + +`AioMysqlor` 是一个继承自 `MySqlor` 的类,用于标识或处理与异步 MySQL 驱动 `aiomysql` 相关的逻辑。该类通过类方法 `isMe` 判断传入的名称是否为 `'aiomysql'`,从而确定是否应由当前类处理。 + +--- + +## 模块依赖 + +```python +from .mysqlor import MySqlor +``` + +- 依赖于同级模块中的 `MySqlor` 基类。 +- 使用相对导入方式加载父类。 + +--- + +## 类定义 + +### `class AioMysqlor(MySqlor)` + +继承自 `MySqlor`,扩展了对特定驱动名称的识别能力。 + +--- + +## 类方法 + +### `@classmethod isMe(cls, name)` + +#### 功能说明 + +判断传入的数据库驱动名称是否为 `'aiomysql'`,用于运行时类型识别或工厂模式中的类匹配。 + +#### 参数 + +| 参数名 | 类型 | 说明 | +|--------|--------|--------------------------| +| `name` | `str` | 待检测的数据库驱动名称。 | + +#### 返回值 + +- **类型**: `bool` +- **说明**: + - 若 `name == 'aiomysql'`,返回 `True`; + - 否则返回 `False`。 + +#### 示例 + +```python +print(AioMysqlor.isMe('aiomysql')) # 输出: True +print(AioMysqlor.isMe('pymysql')) # 输出: False +``` + +#### 装饰器 + +- 使用 `@classmethod` 装饰,表示这是一个类方法,无需实例化即可调用。 + +> ⚠️ 注意:方法定义中参数 `self` 实际上在类方法中应为 `cls`,建议修正以符合规范(见下方“注意事项”)。 + +--- + +## 使用场景 + +常用于数据库连接工厂中,根据配置的驱动名称动态选择对应的处理类: + +```python +driver_name = config.get('db_driver') +if AioMysqlor.isMe(driver_name): + db = AioMysqlor(**config) +``` + +--- + +## 注意事项 + +1. **参数命名问题**: + - 尽管使用了 `@classmethod`,方法签名仍写为 `def isMe(self, name)`,其中 `self` 应改为 `cls` 以符合 Python 社区惯例。 + - 推荐修改为: + ```python + @classmethod + def isMe(cls, name): + return name == 'aiomysql' + ``` + +2. **字符串硬编码**: + - `'aiomysql'` 可考虑提取为类常量,便于维护和扩展。 + +--- + +## 版本信息 + +- 创建时间:YYYY-MM-DD(请根据实际填写) +- 作者:开发者团队 / 个人名称 +- 适用版本:Python 3.7+ +- 依赖库:`aiomysql`(异步 MySQL 驱动) + +--- +``` + +> ✅ 提示:此文档可用于项目 Wiki、API 手册或代码仓库中的 `docs/` 目录,帮助团队成员理解 `AioMysqlor` 的用途和使用方式。 \ No newline at end of file diff --git a/aidocs/aiopostgresqlor.md b/aidocs/aiopostgresqlor.md new file mode 100644 index 0000000..1da0f67 --- /dev/null +++ b/aidocs/aiopostgresqlor.md @@ -0,0 +1,93 @@ +# AioPostgresqlor 技术文档 + +```markdown +# `AioPostgresqlor` 类文档 + +## 概述 + +`AioPostgresqlor` 是一个基于异步 PostgreSQL 驱动的数据库操作类,继承自 `PostgreSQLor` 基类。该类专为使用 `aiopg` 异步驱动设计,用于支持异步上下文中的数据库事务管理。 + +> **注意**:当前实现为占位结构,核心方法尚未完成具体逻辑。 + +--- + +## 继承关系 + +- **父类**: `PostgreSQLor`(来自 `.postgresqlor` 模块) +- **子类**: `AioPostgresqlor` + +--- + +## 方法说明 + +### `isMe(name) -> bool` + +#### 描述 +类方法,用于判断传入的驱动名称是否匹配当前类所支持的异步驱动(即 `aiopg`)。 + +#### 参数 +| 参数名 | 类型 | 说明 | +|--------|--------|--------------------------| +| `name` | `str` | 数据库驱动的名称字符串。 | + +#### 返回值 +| 类型 | 说明 | +|-----------|------------------------------------------| +| `bool` | 若 `name == 'aiopg'` 则返回 `True`,否则返回 `False`。 | + +#### 示例 +```python +if AioPostgresqlor.isMe('aiopg'): + print("适用于 aiopg 驱动") +``` + +--- + +### `commit() -> None` + +#### 描述 +异步方法,用于提交当前数据库事务。目前为空实现(`pass`),需在子类或后续版本中补充实际逻辑。 + +> ⚠️ **警告**:调用此方法不会产生任何效果,必须重写以实现真正的事务提交功能。 + +#### 类型 +`async def` + +#### 使用方式(语法示例) +```python +await instance.commit() +``` + +#### 注意事项 +- 必须在 `async with` 或其他异步上下文中调用。 +- 实际行为依赖于底层 `aiopg` 连接对象的实现,建议覆盖此方法以正确调用 `connection.commit()` 或等效操作。 + +--- + +## 设计意图 + +`AioPostgresqlor` 的设计目标是: +- 支持异步 I/O 操作,适配 Python 异步生态(如 `asyncio`)。 +- 通过 `isMe()` 方法实现驱动识别,便于工厂模式动态选择数据库操作器。 +- 提供与同步版本 `PostgreSQLor` 一致的接口契约,保证代码兼容性。 + +--- + +## 待办事项(TODO) + +- [ ] 实现 `commit()` 的真实逻辑(调用 `aiopg` 的事务提交)。 +- [ ] 补充 `__init__` 或初始化连接的相关方法。 +- [ ] 添加对 `rollback()` 的异步支持。 +- [ ] 增加异常处理机制。 +- [ ] 提供完整的上下文管理器支持(`__aenter__`, `__aexit__`)。 + +--- + +## 参考 + +- [aiopg 官方文档](https://aiopg.readthedocs.io/) +- 父类 `PostgreSQLor` 的接口定义 +``` + +> ✅ 文档生成时间:2025-04-05 +> 📦 所属模块:`.postgresqlor` 扩展模块家族 \ No newline at end of file diff --git a/aidocs/aiosqliteor.md b/aidocs/aiosqliteor.md new file mode 100644 index 0000000..b79bc86 --- /dev/null +++ b/aidocs/aiosqliteor.md @@ -0,0 +1,102 @@ +# `Aiosqliteor` 类技术文档 + +```python +import re +from .sqlite3or import SQLite3or + +class Aiosqliteor(SQLite3or): + @classmethod + def isMe(self, name): + return name == 'aiosqlite' +``` + +## 概述 + +`Aiosqliteor` 是一个继承自 `SQLite3or` 的子类,用于标识和处理与异步 SQLite 库 `aiosqlite` 相关的操作。该类主要用于运行时判断当前数据库驱动是否为 `aiosqlite`。 + +--- + +## 继承关系 + +- **父类**: `SQLite3or` +- **模块路径**: `.sqlite3or.SQLite3or` + +> 说明:`SQLite3or` 是一个通用的 SQLite 操作基类,可能支持多种后端实现(如标准库 `sqlite3` 或异步库 `aiosqlite`)。 + +--- + +## 类方法 + +### `isMe(name)` + +#### 描述 +类方法,用于判断传入的数据库名称是否匹配 `aiosqlite` 驱动。 + +#### 签名 +```python +@classmethod +def isMe(cls, name: str) -> bool +``` + +#### 参数 +| 参数名 | 类型 | 说明 | +|--------|--------|------| +| `name` | `str` | 表示数据库驱动名称的字符串(例如 `'aiosqlite'`, `'sqlite3'` 等) | + +#### 返回值 +| 类型 | 说明 | +|---------|------| +| `bool` | 如果 `name` 等于 `'aiosqlite'`,返回 `True`;否则返回 `False` | + +#### 示例 +```python +assert Aiosqliteor.isMe('aiosqlite') == True +assert Aiosqliteor.isMe('sqlite3') == False +assert Aiosqliteor.isMe('other') == False +``` + +#### 实现逻辑 +```python +return name == 'aiosqlite' +``` +通过简单的字符串比较来判断是否为 `aiosqlite` 驱动。 + +--- + +## 用途 + +此方法通常被框架或工厂函数调用,以动态选择合适的数据库操作类: + +```python +db_name = 'aiosqlite' +if Aiosqliteor.isMe(db_name): + db_handler = Aiosqliteor() +``` + +--- + +## 注意事项 + +- 此类目前仅提供识别功能,具体异步数据库操作需在父类或自身中实现。 +- 名称比对是**大小写敏感**的,确保输入为小写的 `'aiosqlite'`。 +- 依赖于 `SQLite3or` 基类的功能扩展,应确保基类已正确定义并具备所需接口。 + +--- + +## 版本信息 + +- **语言**: Python +- **依赖**: `aiosqlite`(运行时)、`sqlite3`(间接) +- **模块位置**: `your_package.db.engines.Aiosqliteor` + +> 注:实际使用中需确保已安装 `aiosqlite` 包: +> ```bash +> pip install aiosqlite +> ``` + +--- + +## 参考 + +- [aiosqlite on GitHub](https://github.com/omnilib/aiosqlite) +- `SQLite3or` 基类文档(见相关模块) \ No newline at end of file diff --git a/aidocs/const.md b/aidocs/const.md new file mode 100644 index 0000000..9b63625 --- /dev/null +++ b/aidocs/const.md @@ -0,0 +1,55 @@ +# 技术文档 + +## 变量定义:`ROWS` + +### 概述 +`ROWS` 是一个用于定义行数的常量变量,通常用于控制数据结构(如数组、矩阵、表格等)的行数维度。 + +### 定义 +```python +ROWS = 100 +``` + +### 类型 +- **数据类型**:整数(Integer) +- **值**:100 + +### 用途 +该变量设定为 `100`,表示相关数据结构或操作将基于 **100 行**进行处理。常见应用场景包括: +- 初始化二维数组或矩阵(例如:`matrix[ROWS][COLS]`) +- 控制循环次数(例如:`for i in range(ROWS):`) +- 配置数据生成或模拟的规模 +- 设置表格、图像、传感器阵列等的垂直尺寸 + +### 示例用法 + +#### Python 示例 +```python +ROWS = 100 +COLS = 50 + +# 创建一个 100x50 的二维列表 +grid = [[0 for _ in range(COLS)] for _ in range(ROWS)] +print(f"Grid size: {len(grid)} rows x {len(grid[0])} columns") +``` + +#### C/C++ 示例 +```c +#define ROWS 100 + +int matrix[ROWS][50]; // 声明一个包含 100 行的二维数组 +``` + +### 注意事项 +- 修改 `ROWS` 的值会影响内存使用和程序性能,尤其是在大规模数据处理中。 +- 应确保其值为正整数,避免出现逻辑错误或运行时异常。 +- 若与其他常量(如 `COLS`)配合使用,请保持命名一致性。 + +### 版本信息 +- **创建时间**:未知 +- **最后修改**:未知 +- **适用环境**:通用编程环境(Python, C, Java, JavaScript 等) + +--- + +> ✅ 提示:建议将此类常量集中定义在配置文件或常量模块中,以提高代码可维护性。 \ No newline at end of file diff --git a/aidocs/crud.md b/aidocs/crud.md new file mode 100644 index 0000000..23738a5 --- /dev/null +++ b/aidocs/crud.md @@ -0,0 +1,482 @@ +# `CRUD` 模块技术文档 + +> **文件编码:UTF-8** + +该模块提供了一个基于数据库连接池的通用增删改查(CRUD)操作类,支持异步执行、字段类型转换、主外键处理、数据网格生成等功能。适用于 Web 后端服务中对数据库表进行标准化访问。 + +--- + +## 目录 + +- [1. 依赖说明](#1-依赖说明) +- [2. 全局变量](#2-全局变量) + - [`toStringFuncs`](#tostringfuncs) + - [`fromStringFuncs`](#fromstringfuncs) +- [3. 异常定义](#3-异常定义) + - [`DatabaseNotfound`](#databasenotfound) +- [4. 核心类:`CRUD`](#4-核心类crud) + - [构造函数 `__init__`](#构造函数-__init__) + - [方法列表](#方法列表) + - [`primaryKey()`](#async-primarykey) + - [`forignKeys()`](#async-forignkeys) + - [`I()`](#async-i) + - [`fromStr(data)`](#async-fromstrdata) + - [`toStr(data)`](#async-tostrdata) + - [`datagrid(request, targeti, **kw)`](#async-datagridrequest-targeti--kw) + - [`defaultIOField(f)`](#defaultiofieldf) + - [`C(rec, **kw)`](#async-crec--kw) + - [`R(filters=None, NS={}, **kw)`](#async-rfiltersnone-ns---kw) + - [`U(data, **kw)`](#async-udata--kw) + - [`D(data, **kw)`](#async-ddata--kw) + +- [5. 示例用法](#5-示例用法) +- [6. 注意事项与扩展机制](#6-注意事项与扩展机制) + +--- + +## 1. 依赖说明 + +本模块依赖以下内部和外部组件: + +| 模块 | 用途 | +|------|------| +| `.dbpools.DBPools` | 数据库连接池管理器 | +| `.const.ROWS` | 默认每页行数常量 | +| `.filter.DBFilter` | SQL 查询条件过滤器生成工具 | +| `appPublic.objectAction.ObjectAction` | 对象行为钩子系统(用于事件拦截与扩展) | +| `appPublic.dictObject.DictObject` | 字典对象封装(未直接使用,可能为遗留导入) | +| `appPublic.timeUtils.date2str, time2str, str2Date` | 时间格式化/解析函数 | +| `appPublic.uniqueID.getID` | 唯一 ID 生成函数 | + +--- + +## 2. 全局变量 + +### `toStringFuncs` + +将不同字段类型的值转为字符串表示的方法映射。 + +```python +toStringFuncs = { + 'char': None, + 'str': None, + 'short': str, + 'long': str, + 'float': str, + 'date': date2str, + 'time': time2str, +} +``` + +- 若值为 `None`,表示不需特殊处理。 +- 例如:日期类型通过 `date2str` 转换为 `'YYYY-MM-DD'` 格式字符串。 + +### `fromStringFuncs` + +将字符串反向解析为对应类型的数据转换函数。 + +```python +fromStringFuncs = { + 'short': int, + 'long': int, + 'float': float, + 'date': str2Date, + 'time': str2Date +} +``` + +- 空字符串会被转换为 `None`。 +- 支持自动类型转换,在插入或更新前预处理输入数据。 + +--- + +## 3. 异常定义 + +### `DatabaseNotfound` + +当指定的数据库名称在连接池中不存在时抛出此异常。 + +#### 属性: +- `dbname`: 请求但未找到的数据库名 + +#### 方法: +- `__str__()`: 返回 `"xxx not found"` 的可读信息。 + +```python +raise DatabaseNotfound("mydb") +``` + +--- + +## 4. 核心类:`CRUD` + +```python +class CRUD(object): + def __init__(self, dbname, tablename, rows=ROWS): + ... +``` + +封装了对单个数据库表的标准 CRUD 操作。 + +### 构造函数 `__init__` + +**参数:** +- `dbname` (str): 数据库标识名(必须存在于 DBPools 中) +- `tablename` (str): 表名 +- `rows` (int): 分页查询默认条数,默认为 `ROWS` 常量 + +**逻辑流程:** +1. 初始化连接池实例 `DBPools()` +2. 验证数据库是否存在,若不存在则抛出 `DatabaseNotfound` +3. 缓存表主键信息(延迟加载) + +**抛出异常:** +- `DatabaseNotfound`:数据库未注册 + +--- + +### 方法列表 + +#### `async primaryKey(**kw)` + +获取当前表的主键字段信息。 + +- 第一次调用时从数据库元数据缓存主键信息。 +- 后续调用返回缓存结果。 + +**返回:** +- 列表,每个元素是包含字段信息的对象(如 `{field_name: "id", ...}`) + +> 内部使用 `pool.getTablePrimaryKey(dbname, tablename)` 实现。 + +--- + +#### `async forignKeys(**kw)` + +获取当前表的所有外键关系。 + +**返回:** +- 外键描述数据结构(由 `pool.getTableForignKeys()` 提供) + +--- + +#### `async I(**kw)` + +获取当前表的字段元信息,并附加主键标记。 + +##### 功能: +1. 获取所有字段信息(来自 `getTableFields`) +2. 将主键字段添加 `"primarykey": True` 标记 +3. 执行对象动作钩子:`{dbname}_{tablename}.tableInfo` + +##### 返回: +- 字段信息列表,每个字段包含: + - `name`: 字段名 + - `title`: 显示标题(若有) + - `type`: 类型(如 str, float, date 等) + - `primarykey`: 是否为主键(bool) + +##### 示例输出片段: +```json +[ + { + "name": "id", + "title": "ID", + "type": "short", + "primarykey": true + }, + ... +] +``` + +--- + +#### `async fromStr(data)` + +将字符串形式的数据字典转换为强类型数据。 + +**参数:** +- `data` (dict): 键值对,通常来自 HTTP 请求参数 + +**处理规则:** +- 空字符串 → `None` +- 根据字段类型应用 `fromStringFuncs` 转换(如 `int`, `float`, `str2Date`) + +**返回:** +- 新字典,已做类型转换 + +> 常用于 C/U 接口前的数据预处理。 + +--- + +#### `async toStr(data)` + +将原始数据转换为适合前端展示的字符串格式。 + +**参数:** +- `data` (dict): 数据记录 + +**处理规则:** +- 使用 `toStringFuncs` 将非字符串字段转为字符串(如日期→'2025-04-05') + +**返回:** +- 可序列化的字符串化字典 + +> 常用于 R 接口后数据输出前的格式化。 + +--- + +#### `async datagrid(request, targeti, **kw)` + +生成一个前端可用的“数据表格”配置对象,兼容 EasyUI 或类似框架。 + +##### 参数: +- `request`: 当前请求对象(用于生成绝对 URL) +- `targeti`: 目标 DOM 容器 ID +- `**kw`: 其他传递参数 + +##### 返回: +一个嵌套结构,描述 DataGrid 组件的模板配置,包括: + +| 属性 | 说明 | +|------|------| +| `tmplname` | 使用的模板名 | +| `data.__ctmpl__` | 组件类型标识 | +| `data.__target__` | 渲染目标 | +| `data.data.url` | 查询接口地址 (`./RP.dspy`) | +| `data.data.deleteUrl` | 删除接口 | +| `data.data.addUrl` | 添加接口 | +| `data.data.updateUrl` | 更新接口 | +| `data.data.idField` | 主键字段名 | +| `data.data.fields` | 字段列定义(通过 `defaultIOField` 生成) | +| `data.data.toolbar` | 工具栏按钮(增删上下移动) | +| `data.data.options.pageSize` | 分页大小 | + +> 最终通过 `oa.execute(id, 'datagrid', data)` 允许外部插件定制布局。 + +--- + +#### `defaultIOField(f)` + +根据字段元信息生成默认的前端显示字段配置。 + +**参数:** +- `f` (dict): 单个字段元信息 + +**返回:** +标准字段配置对象,含以下字段: + +| 字段 | 说明 | +|------|------| +| `name` | 字段名 | +| `label` | 显示标签(取自 `title`) | +| `primarykey` | 是否为主键 | +| `hidden` | 是否隐藏(默认否) | +| `sortable` | 是否可排序 | +| `align` | 文本对齐方式(数值右对齐) | +| `iotype` | 输入类型(目前统一为 `"text"`) | + +> 不同类型字段有细微差异: +- `str`: 居中对齐 +- 数值型(`short`, `long`, `float`): 右对齐 +- 其他: 居中对齐 + +--- + +#### `async C(rec, **kw)` + +创建一条新记录(Create)。 + +##### 参数: +- `rec` (dict): 待插入数据(字段名不区分大小写) +- `**kw`: 额外参数(传给底层 SQL 执行) + +##### 流程: +1. 转换键名为小写 +2. 若主键为空,则自动生成唯一 ID(`getID()`) +3. 触发 `beforeAdd` 钩子 +4. 构造并执行插入语句: + ```sql + INSERT INTO table (col1, col2) VALUES (${col1}$, ${col2}$) + ``` +5. 触发 `afterAdd` 钩子 +6. 返回主键值 `{pk_field: value}` + +##### 返回: +- 成功时返回主键字典;失败抛异常。 + +--- + +#### `async R(filters=None, NS={}, **kw)` + +检索数据(Retrieve),支持条件查询和分页。 + +##### 参数: +- `filters`: 自定义过滤表达式(高级语法,见 `DBFilter`) +- `NS`: 查询参数字典(如 `{"name": "Alice"}`) +- `**kw`: 其他选项 + +##### 特殊处理: +- `NS['__id']` → 自动映射到主键字段 +- `NS['page']` 存在 → 启用分页模式 +- 无排序字段时,默认按主键排序 + +##### 查询逻辑: +- 若无 `filters`,则自动生成等值匹配条件(`field = $field$`) +- 使用 `DBFilter` 解析复杂条件(如范围、模糊匹配) + +##### 执行分支: +- 分页请求 → 调用 `runSQLPaging` +- 普通查询 → 调用 `runSQL` + +##### 生命周期钩子: +- `beforeRetrieve` +- `afterRetrieve` + +##### 返回: +- 结果集(可能是分页对象,含 `total`, `rows`) + +--- + +#### `async U(data, **kw)` + +更新记录(Update)。 + +##### 参数: +- `data` (dict): 包含主键和其他待更新字段 + +##### 流程: +1. 分离主键字段和更新字段 +2. 构造 WHERE 条件(主键相等) +3. 构造 SET 子句(仅非主键字段) +4. 执行更新语句: + ```sql + UPDATE table SET col1=${col1}$ WHERE id=${id}$ + ``` +5. 触发 `beforeUpdate` / `afterUpdate` 钩子 + +##### 返回: +- 更新后的完整数据对象 + +--- + +#### `async D(data, **kw)` + +删除记录(Delete)。 + +##### 参数: +- `data` (dict): 包含主键字段的字典 + +##### 流程: +1. 构造 WHERE 条件(所有主键字段 AND 连接) +2. 执行删除语句: + ```sql + DELETE FROM table WHERE id=${id}$ + ``` +3. 触发 `beforeDelete` / `afterDelete` 钩子 + +##### 返回: +- 被删除的数据副本(可用于日志或通知) + +> ⚠️ 注意:未实现软删除,为物理删除。 + +--- + +## 5. 示例用法 + +```python +# 初始化数据库连接池(仅一次) +DBPools({ + "ambi": { + "driver": "pymssql", + "coding": "utf-8", + "dbname": "ambi", + "kwargs": { + "user": "ymq", + "password": "ymq123", + "host": "localhost", + "database": "ambi" + } + } +}) + +# 创建 CRUD 实例 +crud = CRUD('ambi', 'cashflow') + +# 查询全部数据(分页) +data = await crud.R(NS={'page': 1}) +print(f"总数: {data.total}") +for row in data.rows: + print(row.balance, row.asid) + +# 插入一条记录 +new_id = await crud.C({ + "balance": 1000.5, + "asid": "A001" +}) +print("新建ID:", new_id) + +# 更新 +await crud.U({ + "id": 123, + "balance": 1500.0 +}) + +# 删除 +await crud.D({"id": 123}) +``` + +--- + +## 6. 注意事项与扩展机制 + +### ✅ 已支持特性: +- ✅ 异步非阻塞 I/O +- ✅ 多数据库连接池管理 +- ✅ 自动主键识别与处理 +- ✅ 输入/输出类型自动转换 +- ✅ 支持分页、排序、条件查询 +- ✅ 前端 DataGrid 快速生成 +- ✅ 通过 `ObjectAction` 实现事件钩子扩展: + - `beforeAdd`, `afterAdd` + - `beforeRetrieve`, `afterRetrieve` + - `beforeUpdate`, `afterUpdate` + - `beforeDelete`, `afterDelete` + - `tableInfo`, `datagrid` + +### ⚠️ 注意事项: +1. **主键要求**:所有表应明确定义主键,否则部分功能异常。 +2. **字段大小写敏感性**:输入建议统一小写,内部会做 `.lower()` 处理。 +3. **安全机制**:使用 `${field}$` 占位符 + 参数绑定防止 SQL 注入(依赖底层 `inSqlor` 实现)。 +4. **时间格式**:`date` 和 `time` 类型需确保 `str2Date` 和 `date2str` 支持正确格式。 +5. **外键未用于级联操作**:当前仅提供信息获取,不自动处理关联数据。 + +### 🔧 扩展建议: +- 在 `ObjectAction` 中注册钩子函数以增强业务逻辑: + ```python + oa.bind("ambi_cashflow.beforeAdd", my_audit_log) + ``` +- 自定义 `defaultIOField` 输出更多控件类型(如 dropdown, datebox)。 +- 扩展 `toStringFuncs` / `fromStringFuncs` 支持更多类型(如 bool, json)。 + +--- + +## 附录:钩子事件命名规范 + +| 事件点 | 触发时机 | 推荐用途 | +|-------|---------|--------| +| `{db}_{table}.tableInfo` | 获取字段信息后 | 修改字段显示属性 | +| `{db}_{table}.datagrid` | 生成 datagrid 配置后 | 自定义 UI 布局 | +| `{db}_{table}.beforeAdd` | 插入前 | 数据校验、权限检查、默认值填充 | +| `{db}_{table}.afterAdd` | 插入成功后 | 日志记录、消息通知 | +| `{db}_{table}.beforeRetrieve` | 查询前 | 动态过滤(如租户隔离) | +| `{db}_{table}.afterRetrieve` | 查询返回前 | 敏感字段脱敏 | +| `{db}_{table}.beforeUpdate` | 更新前 | 审计、状态流转控制 | +| `{db}_{table}.afterUpdate` | 更新完成后 | 发布事件 | +| `{db}_{table}.beforeDelete` | 删除前 | 软删除替换、引用检测 | +| `{db}_{table}.afterDelete` | 删除后 | 清理关联资源 | + +--- + +> 📝 文档版本:v1.0 +> © 2025 应用公共平台开发组 \ No newline at end of file diff --git a/aidocs/dbpools.md b/aidocs/dbpools.md new file mode 100644 index 0000000..64403ba --- /dev/null +++ b/aidocs/dbpools.md @@ -0,0 +1,327 @@ +以下是为提供的 Python 代码编写的 **Markdown 格式技术文档**,涵盖了模块功能、类说明、函数接口、使用方式及关键设计思想。 + +--- + +# `dbor` 模块技术文档 + +> 数据库操作抽象层与连接池管理(支持同步/异步) + +## 概述 + +`dbor` 是一个轻量级的数据库操作抽象框架,提供统一接口访问多种数据库(如 MySQL、PostgreSQL、SQLite、Oracle、SQL Server),并支持 **同步与异步模式**。该模块通过工厂模式创建数据库适配器,并结合连接池机制提升性能和资源利用率。 + +主要特性: +- 支持主流关系型数据库 +- 同步与异步双模式运行 +- 自动连接池管理 +- 单例全局数据库池控制器 +- 配置驱动式数据库接入 +- 安全密码解密(RC4) +- 上下文管理器自动提交/回滚 + +--- + +## 依赖说明 + +```python +import asyncio +from functools import wraps +from contextlib import asynccontextmanager +import threading +``` + +自定义模块依赖(需确保路径正确): +- `appPublic.myImport`:动态导入模块 +- `appPublic.dictObject`:字典对象封装 +- `appPublic.Singleton`:单例装饰器 +- `appPublic.myjson.loadf`:加载 JSON 文件 +- `appPublic.jsonConfig.getConfig`:配置读取 +- `appPublic.rc4.unpassword`:RC4 密码解密 +- `appPublic.log.exception`:异常日志记录 + +--- + +## 核心组件 + +### 1. `sqlorFactory(dbdesc) → SQLor` + +根据数据库描述信息实例化对应的数据库操作对象。 + +#### 参数 +| 参数名 | 类型 | 说明 | +|--------|--------|----| +| `dbdesc` | `dict` | 数据库配置字典,必须包含 `'driver'` 字段 | + +#### 返回值 +- 继承自 `SQLor` 的具体数据库操作实例(如 `MySqlor`, `AioMysqlor` 等) +- 若未匹配到子类,则返回基础 `SQLor` 实例 + +#### 示例配置结构 +```python +{ + "driver": "mysql.connector", + "async_mode": False, + "kwargs": { + "host": "localhost", + "user": "root", + "password": "enc:xxxxx", # RC4 加密后的密码 + "database": "testdb" + } +} +``` + +--- + +### 2. `sqlorFromFile(dbdef_file, coding='utf8') → SQLor` + +从 JSON 配置文件加载数据库定义并生成对应操作实例。 + +#### 参数 +| 参数名 | 类型 | 默认值 | 说明 | +|------------|--------|--------|----| +| `dbdef_file` | `str` | - | JSON 配置文件路径 | +| `coding` | `str` | `'utf8'` | 文件编码格式 | + +#### 示例 +```python +sor = sqlorFromFile('/path/to/db.json') +``` + +--- + +## 连接生命周期管理:`LifeConnect` + +封装单个数据库连接的生命周期控制,包括健康检测、重连机制、使用计数等。 + +### 构造函数 +```python +LifeConnect(connfunc, kw, use_max=1000, async_mode=False) +``` + +| 参数 | 类型 | 说明 | +|------------|----------|----| +| `connfunc` | `callable` | 创建连接的函数(如 `sqlite3.connect`) | +| `kw` | `dict` | 连接参数 | +| `use_max` | `int` | 最大使用次数后关闭连接,默认 `1000` | +| `async_mode` | `bool` | 是否异步模式 | + +### 方法 + +| 方法名 | 异步 | 说明 | +|------------|------|----| +| `use()` | ✅ | 获取可用连接,失败时尝试重建 | +| `free(conn)`| ✅ | 释放连接(暂未启用最大使用限制逻辑) | +| `testok()` | ✅ | 测试连接是否正常(执行 `SELECT 1`) | +| `_mkconn()` | ✅ | 内部方法:创建新连接 | + +> ⚠️ 注意:当前 `free()` 中关于 `use_cnt >= use_max` 的判断被注释,不会触发自动关闭。 + +--- + +## 连接池:`ConnectionPool` + +管理某一数据库的所有连接,实现连接复用。 + +### 构造函数 +```python +ConnectionPool(dbdesc, loop) +``` + +| 参数 | 类型 | 说明 | +|----------|---------------|----| +| `dbdesc` | `dict` | 数据库配置 | +| `loop` | `asyncio.EventLoop` | 异步事件循环 | + +### 属性 +| 属性 | 类型 | 说明 | +|--------------|------------------|----| +| `maxconn` | `int` | 最大连接数(默认 5) | +| `maxuse` | `int` | 每连接最大使用次数 | +| `_pool` | `asyncio.Queue` | 存放 `LifeConnect` 实例的队列 | +| `connectObject`| `dict` | 当前活跃连接映射表 | + +### 方法 + +| 方法 | 异步 | 说明 | +|----------------|------|----| +| `connect()` | ✅ | 创建一个 `LifeConnect` 并放入池中 | +| `aquire()` | ✅ | 从池中获取可用连接 | +| `release(conn)` | ✅ | 释放连接回池 | +| `isEmpty()` | ❌ | 判断池是否为空 | +| `isFull()` | ❌ | 判断池是否已满 | +| `_fillPool()` | ✅ | 初始化填充连接池至最大容量 | + +> 📝 注:目前 `_fillPool()` 调用后并未实际等待完成,存在潜在问题。 + +--- + +## 全局数据库池管理器:`DBPools`(单例) + +使用 `@SingletonDecorator` 装饰,保证全局唯一实例,统一管理多个数据库连接池。 + +### 构造函数 +```python +DBPools(databases={}, max_connect=100, loop=None) +``` + +| 参数 | 类型 | 说明 | +|--------------|--------|----| +| `databases` | `dict` | 数据库名称 → 配置字典的映射 | +| `max_connect` | `int` | 全局最大并发连接数限制 | +| `loop` | `EventLoop` | 可选事件循环 | + +### 属性 +| 属性 | 类型 | 说明 | +|-------------|-------------------|----| +| `sema` | `asyncio.Semaphore` | 控制总连接数信号量 | +| `_cpools` | `dict` | 数据库名 → `ConnectionPool` 映射 | +| `databases` | `dict` | 所有注册数据库配置 | +| `meta` | `dict` | 预留元数据存储 | +| `e_except` | `Exception` | 上下文中捕获的异常 | + +--- + +### 核心方法 + +#### `addDatabase(name, desc)` +注册新的数据库配置。 + +```python +dbpools.addDatabase('devdb', { + 'driver': 'aiomysql', + 'async_mode': True, + 'kwargs': { ... } +}) +``` + +#### `get_dbname(name) → str or None` +获取数据库逻辑名对应的实际数据库名(`dbname`),若无则返回 `None`。 + +#### `isAsyncDriver(dbname) → bool` +判断指定数据库是否运行在异步模式下。 + +#### `getSqlor(name) → SQLor` +获取指定数据库的操作对象(含连接和游标)。 + +- 使用信号量控制并发 +- 自动建立底层连接与游标 +- 返回已绑定连接的 `SQLor` 实例 + +#### `freeSqlor(sor)` +释放由 `getSqlor()` 获取的 `SQLor` 实例,归还连接并释放信号量。 + +#### `sqlorContext(name)` +**异步上下文管理器**,推荐使用的安全访问方式。 + +```python +async with dbpools.sqlorContext('mydb') as sor: + await sor.execute('SELECT * FROM users') + result = await sor.fetchall() + # 自动 commit +``` + +##### 行为逻辑 +- 成功退出:自动提交事务(如果 `dataChanged == True`) +- 抛出异常:记录日志 + 回滚事务(如有更改) +- 始终执行:释放连接资源 + +--- + +### 内部方法(不建议直接调用) + +| 方法 | 异步 | 说明 | +|--------------------|------|----| +| `_aquireConn(dbname)` | ✅ | 获取连接、游标三元组 `(async_mode, conn, cur)` | +| `_releaseConn(...)` | ✅ | 关闭游标与连接,归还至池 | + +> 🔐 密码处理:在 `_aquireConn` 中自动调用 `unpassword(pw)` 解密。 + +--- + +## 支持的数据库驱动 + +| 驱动模块 | 同步类 | 异步类 | 支持情况 | +|---------------------|----------------|------------------|-------| +| `sqlite3` | `SQLite3or` | `Aiosqliteor` | ✅ | +| `mysql.connector` | `MySqlor` | `AioMysqlor` | ✅ | +| `psycopg2` / `asyncpg` | `SQLor` 扩展 | `AioPostgresqlor`| ✅ | +| `cx_Oracle` | `Oracleor` | (暂无异步) | ✅ | +| `pyodbc` / `pymssql`| `SQLor` 扩展 | `MsSqlor` | ✅ | + +> 💡 提示:所有驱动需继承 `SQLor` 并实现 `isMe(driver_name)` 类方法用于识别。 + +--- + +## 使用示例 + +### 步骤 1:初始化 DBPools(应用启动时) + +```python +from appPublic.jsonConfig import getConfig +from your_module import DBPools + +config = getConfig() # 加载全局配置 +dbpools = DBPools(databases=config.databases, loop=asyncio.get_event_loop()) +``` + +### 步骤 2:使用上下文访问数据库 + +```python +async def fetch_users(): + async with dbpools.sqlorContext('users_db') as sor: + await sor.execute('SELECT id, name FROM users LIMIT 10') + rows = await sor.fetchall() + return rows +``` + +### 步骤 3:手动获取/释放(高级用法) + +```python +async def manual_usage(): + sor = await dbpools.getSqlor('reporting') + try: + await sor.execute('UPDATE stats SET processed=1 WHERE flag=0') + await sor.commit() + except: + await sor.rollback() + finally: + await dbpools.freeSqlor(sor) +``` + +--- + +## 安全与日志 + +- 所有密码字段通过 `unpassword()` 解密(基于 RC4) +- 异常通过 `appPublic.log.exception()` 记录堆栈 +- 上下文管理器自动处理事务完整性 + +--- + +## 已知问题与改进建议 + +| 问题 | 描述 | 建议 | +|-----|------|------| +| `free()` 方法中 `use_max` 逻辑被注释 | 连接无法按使用次数回收 | 恢复条件判断逻辑 | +| `_fillPool()` 未 await 循环 | 初始连接可能未完全建立 | 改为批量 `asyncio.gather()` | +| `lock` 相关代码被注释 | 缺少并发保护 | 若多线程需启用锁机制 | +| `print()` 方法仅调试用途 | 应移除或改为日志输出 | 删除或替换为 logger.debug | + +--- + +## 总结 + +`dbor` 提供了一个灵活、可扩展的数据库访问架构,适用于中小型项目中的多数据库统一管理场景。其核心优势在于: + +✅ 统一接口 +✅ 异步友好 +✅ 自动资源管理 +✅ 安全配置支持 + +适合集成进 Web 框架(如 FastAPI、Tornado)或后台服务系统中作为 ORM 替代方案。 + +--- + +> 文档版本:v1.0 +> 编写时间:2025年4月5日 +> 适用代码版本:请以实际提交为准 \ No newline at end of file diff --git a/aidocs/dbpools.old.md b/aidocs/dbpools.old.md new file mode 100644 index 0000000..9d882b8 --- /dev/null +++ b/aidocs/dbpools.old.md @@ -0,0 +1,536 @@ +以下是为提供的 Python 异步数据库连接与操作代码编写的 **Markdown 格式技术文档**,涵盖模块功能、类结构、核心方法说明及使用示例。 + +--- + +# 📚 `dbpool` 模块技术文档 + +> 基于异步 I/O 的通用数据库连接池与 SQL 执行封装系统 +> 支持多种数据库驱动(SQLite、MySQL、PostgreSQL、Oracle、SQL Server)的同步/异步模式 + +--- + +## 🔧 概述 + +本模块提供了一个可扩展、高性能的异步数据库访问框架,主要特性包括: + +- ✅ 多数据库支持:通过插件式设计支持多种数据库 +- ✅ 异步连接池管理(基于 `asyncio.Queue`) +- ✅ 自动重连与健康检测机制 +- ✅ 单例全局连接池管理器 `DBPools` +- ✅ 装饰器简化 SQL 执行流程 +- ✅ 元数据缓存优化表结构查询性能 +- ✅ 事务自动提交/回滚控制 + +适用于高并发 Web 应用或微服务中对数据库资源进行统一管理和高效复用。 + +--- + +## 📦 导入依赖 + +```python +import asyncio +from functools import wraps +from contextlib import asynccontextmanager + +# 内部工具库 +from appPublic.myImport import myImport +from appPublic.dictObject import DictObject +from appPublic.Singleton import SingletonDecorator +from appPublic.myjson import loadf +from appPublic.jsonConfig import getConfig +from appPublic.rc4 import unpassword +from appPublic.log import exception +``` + +外部依赖: +- `aiosqlite`, `aiomysql`, `asyncpg` 等异步驱动需根据实际数据库安装 + +--- + +## 🏗️ 核心组件架构 + +| 组件 | 功能 | +|------|------| +| `SQLor` 子类 | 数据库抽象层,封装增删改查逻辑 | +| `sqlorFactory()` | 工厂函数,根据配置创建对应的 `SQLor` 实例 | +| `LifeConnect` | 封装单个连接的生命期与可用性测试 | +| `ConnectionPool` | 连接池实现,维护固定数量的活跃连接 | +| `DBPools` | 单例全局连接池管理器,支持多数据库 | +| 装饰器 (`@runSQL`, `@inSqlor`) | 简化业务函数中的数据库调用 | + +--- + +## 🧱 抽象接口:`SQLor` 及其子类 + +所有数据库适配器继承自基类 `SQLor`,并实现以下关键方法: + +```python +class SQLor: + def isMe(driver_name: str) -> bool: ... + async def tables() -> List[str]: ... + async def fields(table: str) -> List[FieldInfo]: ... + async def primary(table: str) -> List[str]: ... + async def indexes(table: str) -> List[IndexInfo]: ... + async def fkeys(table: str) -> List[ForeignKeyInfo]: ... + async def runSQL(desc: dict, ns: dict) -> Any: ... + async def runSQLPaging(desc: dict, ns: dict) -> Dict: ... +``` + +### 当前支持的数据库驱动 + +| 驱动名 | 类 | 文件 | +|--------|----|------| +| sqlite3 | `SQLite3or` / `Aiosqliteor` | `.sqlite3or`, `.aiosqliteor` | +| mysql | `MySqlor` / `AioMysqlor` | `.mysqlor`, `.aiomysqlor` | +| postgresql | `AioPostgresqlor` | `.aiopostgresqlor` | +| mssql | `MsSqlor` | `.mssqlor` | +| oracle | `Oracleor` | `.oracleor` | + +> 同步和异步版本分别处理阻塞/非阻塞场景。 + +--- + +## ⚙️ 工厂函数 + +### `sqlorFactory(dbdesc) → SQLor` + +根据数据库描述字典动态选择合适的 `SQLor` 子类实例。 + +#### 参数 + +| 参数 | 类型 | 说明 | +|------|------|------| +| `dbdesc` | `dict` | 包含 `'driver'` 和 `'kwargs'` 的数据库连接信息 | + +#### 示例 + +```python +dbdesc = { + "driver": "aiomysql", + "kwargs": { + "host": "localhost", + "port": 3306, + "user": "root", + "password": "enc:xxxxx", + "database": "test" + }, + "async_mode": True +} +sor = sqlorFactory(dbdesc) +``` + +> 若未找到匹配驱动,则返回默认 `SQLor` 实例。 + +--- + +### `sqlorFromFile(dbdef_file, coding='utf8') → SQLor` + +从 JSON 文件加载数据库配置并初始化 `SQLor` 实例。 + +#### 参数 + +| 参数 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| `dbdef_file` | `str` | - | JSON 配置文件路径 | +| `coding` | `str` | `'utf8'` | 文件编码格式 | + +#### 示例文件内容 (`config/db.json`) +```json +{ + "driver": "sqlite3", + "kwargs": { + "dbname": "/tmp/test.db" + } +} +``` + +#### 使用方式 +```python +sor = await sqlorFromFile("config/db.json") +``` + +--- + +## 🔌 连接生命期管理:`LifeConnect` + +封装单个数据库连接的生命周期,具备自动重建能力。 + +### 初始化参数 + +| 属性 | 说明 | +|------|------| +| `connfunc` | 创建连接的函数(如 `aiomysql.connect`) | +| `kw` | 传递给 `connfunc` 的关键字参数 | +| `use_max` | 最大使用次数后强制关闭 | +| `async_mode` | 是否启用异步模式 | + +### 方法 + +| 方法 | 返回类型 | 说明 | +|------|----------|------| +| `use()` | `awaitable` | 获取一个有效连接,失败时尝试重建 | +| `free(conn)` | `None` | 标记连接已释放(暂不关闭) | +| `testok()` | `awaitable` | 测试连接是否正常(执行 `SELECT 1`) | +| `_mkconn()` | `awaitable` | 内部创建新连接 | + +> 在获取连接时会自动探测断连并尝试重新建立最多 4 次。 + +--- + +## 🔄 连接池:`ConnectionPool` + +每个数据库拥有独立的连接池,基于 `asyncio.Queue` 实现先进先出调度。 + +### 初始化参数 + +| 参数 | 说明 | +|------|------| +| `dbdesc` | 数据库配置对象 | +| `loop` | 事件循环引用 | + +### 属性 + +| 属性 | 说明 | +|------|------| +| `maxconn` | 最大连接数(默认 5) | +| `maxuse` | 单连接最大使用次数(默认 1000) | +| `_pool` | 存放 `LifeConnect` 实例的队列 | + +### 关键方法 + +| 方法 | 说明 | +|------|------| +| `connect()` | 创建新的 `LifeConnect` 并放入池中 | +| `aquire()` | 异步获取可用连接 | +| `release(conn)` | 释放连接回池中 | +| `isEmpty()` / `isFull()` | 查询池状态 | + +> 注意:当前未启用锁保护共享状态,建议在单线程协程环境下运行。 + +--- + +## 🌐 全局连接池管理器:`DBPools`(单例) + +使用 `@SingletonDecorator` 保证全局唯一实例。 + +### 构造参数 + +| 参数 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| `databases` | `dict` | `{}` | 名称映射到数据库配置 | +| `max_connect` | `int` | `100` | 全局限流信号量 | +| `loop` | `asyncio.AbstractEventLoop` | `get_event_loop()` | 事件循环 | + +### 示例配置 + +```python +databases = { + 'default': { + 'driver': 'aiomysql', + 'async_mode': True, + 'kwargs': { ... } + }, + 'local_sqlite': { + 'driver': 'sqlite3', + 'async_mode': False, + 'kwargs': {'dbname': '/tmp/app.db'} + } +} +pools = DBPools(databases=databases) +``` + +--- + +## 💡 核心 API 方法 + +### `getSqlor(name) → SQLor` + +获取指定名称的数据库操作对象(带连接绑定)。 + +```python +sor = await pools.getSqlor('default') +``` + +> 自动申请连接、游标,并设置上下文。 + +--- + +### `freeSqlor(sor)` + +释放 `SQLor` 占用的连接资源。 + +```python +await pools.freeSqlor(sor) +``` + +--- + +### `@sqlorContext(name) → Async Context Manager` + +推荐使用的上下文管理器,自动处理连接获取、事务提交/回滚、异常捕获与释放。 + +#### 示例 + +```python +async with pools.sqlorContext('default') as sor: + ret = await sor.runSQL({"sql_string": "SELECT * FROM users"}) +``` + +> - 出现异常且有数据变更 → 自动 `rollback` +> - 正常退出 → 自动 `commit` +> - 总是释放连接 + +--- + +### `useOrGetSor(dbname, **kw) → (sor, commit_flag)` + +内部工具函数:若传入了 `sor` 则复用,否则新建。 + +用于嵌套调用时避免重复连接开销。 + +--- + +## 🎯 装饰器 API(简化开发) + +### `@inSqlor(func)` + +装饰业务函数,自动注入 `sor` 对象,支持事务控制。 + +```python +@pools.inSqlor +async def get_user(dbname, NS, user_id, **kw): + sor = kw['sor'] + return await sor.selectOne("users", {"id": user_id}) +``` + +调用方式: +```python +ret = await get_user('default', {}, user_id=123) +``` + +> 若未传 `sor`,则自动申请;否则复用传入的 `sor` + +--- + +### `@runSQL(func)` + +装饰函数返回 SQL 描述对象,然后执行它。 + +```python +@pools.runSQL +async def list_users(dbname, NS, dept_id, **kw): + return { + "sql_string": "SELECT * FROM users WHERE dept_id=?", + "args": [dept_id] + } + +# 调用 +result = await list_users('default', {}, dept_id=5) +``` + +> 自动执行 SQL、提交事务(如需)、释放连接。 + +--- + +### `@runSQLPaging(func)` + +分页查询专用装饰器,结合 `sor.runSQLPaging()` 使用。 + +```python +@pools.runSQLPaging +async def search_users_paged(dbname, NS, keyword, page=1, pagesize=10): + return { + "sql_string": "SELECT id,name FROM users WHERE name LIKE ?", + "args": [f"%{keyword}%"] + } +``` + +返回格式: +```json +{ + "page": 1, + "pagesize": 10, + "total": 87, + "data": [...] +} +``` + +--- + +### `@runSQLResultFields(func)` + +获取查询结果字段定义(元数据),常用于动态表单生成。 + +```python +@pools.runSQLResultFields +async def describe_query(dbname, NS, table): + return {"sql_string": f"SELECT * FROM {table} LIMIT 1"} +``` + +--- + +## 🔍 元数据查询接口 + +提升性能:结果缓存在内存中。 + +| 方法 | 作用 | +|------|------| +| `getTables(dbname)` | 获取所有表名列表 | +| `getTableFields(dbname, tblname)` | 获取某表字段结构 | +| `getTablePrimaryKey(dbname, tblname)` | 获取主键字段 | +| `getTableIndexes(dbname, tblname)` | 获取索引信息 | +| `getTableForignKeys(dbname, tblname)` | 获取外键关系 | + +> 所有方法均带本地缓存,默认以 `"dbname:tablename"` 为键。 + +--- + +## 🛠️ 辅助函数 + +### `runSQL(dbname, sql, ns={}, sor=None)` + +快速执行一条原始 SQL。 + +```python +result = await runSQL( + dbname='default', + sql='SELECT COUNT(*) AS cnt FROM users' +) +``` + +> 推荐仅用于简单脚本或调试。 + +--- + +### `runSQLPaging(dbname, sql, ns={}, sor=None)` + +快速执行分页 SQL。 + +```python +paged = await runSQLPaging( + dbname='default', + sql='SELECT id,name FROM users ORDER BY id', + ns={'page': 2, 'pagesize': 10} +) +``` + +--- + +## 🔐 安全相关 + +- 密码字段支持加密标记:`"password": "enc:xxxxxx"` +- 使用 `unpassword(pw)` 解密(RC4 加密算法,需确保密钥一致) +- 不建议生产环境明文存储密码 + +--- + +## 📐 配置结构规范(JSON) + +```json +{ + "driver": "aiomysql", + "async_mode": true, + "maxconn": 10, + "maxuse": 500, + "kwargs": { + "host": "127.0.0.1", + "port": 3306, + "user": "appuser", + "password": "enc:xxxxxxxx", + "database": "myapp_db" + } +} +``` + +> 支持字段: +- `driver`: 必须匹配导入路径下的模块名 +- `async_mode`: 控制异步行为 +- `kwargs`: 透传给底层驱动 + +--- + +## 🧪 使用示例 + +### 1. 初始化连接池 + +```python +from your_module import DBPools + +config = loadf("db.json") # 加载配置 +pools = DBPools(databases={"prod": config}) +``` + +### 2. 执行普通查询 + +```python +async def main(): + async with pools.sqlorContext("prod") as sor: + users = await sor.select("users", where={"status": 1}) + print(users) +``` + +### 3. 分页查询用户 + +```python +@pools.runSQLPaging +async def query_active_users(dbname, NS, status=1): + return { + "sql_string": "SELECT id,name,email FROM users WHERE status=?", + "args": [status] + } + +result = await query_active_users("prod", {"page": 1, "pagesize": 20}) +print(result) +``` + +--- + +## ⚠️ 注意事项 + +1. **异步模式必须使用异步驱动** + - 如 `aiomysql`, `asyncpg`, `aiosqlite` +2. **不要手动调用 `close()` 游标或连接** + - 应由 `DBPools` 统一管理 +3. **避免长时间持有 `sor` 实例** + - 建议使用 `sqlorContext` 上下文管理器 +4. **元数据缓存不会自动刷新** + - 表结构变更后需重启服务或手动清理 `meta` + +--- + +## 📈 性能建议 + +- 设置合理的 `maxconn`(通常 ≤ CPU 核心数 × 2) +- `maxuse` 可防止长连接老化导致的问题 +- 生产环境建议开启日志监控连接健康状况 + +--- + +## 📎 附录 A:错误处理策略 + +| 场景 | 处理方式 | +|------|-----------| +| 连接中断 | 尝试重建最多 4 次 | +| 查询异常 | 记录 traceback 日志 | +| 事务失败 | 自动 rollback 并抛出原异常 | +| 密码解密失败 | 抛出异常(需检查 RC4 密钥) | + +--- + +## 📎 附录 B:未来改进方向 + +- [ ] 添加连接池监控指标(Prometheus) +- [ ] 支持读写分离 +- [ ] 更细粒度的超时控制 +- [ ] SQL 拦截器(审计/日志) +- [ ] 连接泄漏检测机制 + +--- + +> ✅ 文档版本:v1.0 +> 📅 更新时间:2025-04-05 +> © 2025 Your Company. All Rights Reserved. + +--- + +📌 提示:将此文档保存为 `README.md` 或集成至 Sphinx/Wiki 中便于团队查阅。 \ No newline at end of file diff --git a/aidocs/ddl_template_mysql.md b/aidocs/ddl_template_mysql.md new file mode 100644 index 0000000..62aaf5e --- /dev/null +++ b/aidocs/ddl_template_mysql.md @@ -0,0 +1,270 @@ +以下是为提供的 Jinja2 模板代码编写的 **Markdown 格式技术文档**,适用于数据库建模或自动化 DDL 生成场景。 + +--- + +# MySQL DDL 自动生成模板技术文档 + +## 概述 + +本模板是一个基于 [Jinja2](https://jinja.palletsprojects.com/) 的 SQL DDL(数据定义语言)生成模板,用于根据元数据自动生成 MySQL 表的 `CREATE TABLE` 和相关索引语句。支持字段类型映射、默认值、非空约束、主键、注释以及普通/唯一索引的创建。 + +该模板可用于数据建模工具、ETL 流程配置、数据库初始化脚本等自动化场景。 + +--- + +## 模板变量说明 + +模板依赖以下上下文变量: + +| 变量名 | 类型 | 说明 | +|------------|------------|------| +| `summary` | 列表 | 表结构摘要信息列表,通常只包含一个元素(当前表),如:`summary[0].name`, `summary[0].primary`, `summary[0].title` | +| `fields` | 列表 | 字段列表,每个字段包含名称、类型、长度、小数位、是否可为空、默认值、标题(注释)等属性 | +| `indexes` | 列表 | 索引定义列表,每个索引包含名称、类型(`index` 或 `unique`)、字段列表 | + +### `summary[0]` 结构示例 +```json +{ + "name": "user_info", + "primary": ["id"], + "title": "用户基本信息表" +} +``` + +### `field` 结构示例 +```json +{ + "name": "age", + "type": "int", + "length": 11, + "dec": 0, + "nullable": "no", + "default": "0", + "title": "年龄" +} +``` + +### `index` 结构示例 +```json +[ + { + "name": "idx_email", + "idxtype": "unique", + "idxfields": ["email"] + }, + { + "name": "idx_status", + "idxtype": "index", + "idxfields": ["status", "create_time"] + } +] +``` + +--- + +## 宏函数(Macros) + +模板中定义了多个辅助宏函数,用于格式化 SQL 片段。 + +### `typeStr(type, len, dec)` +将高级字段类型转换为对应的 MySQL 数据类型。 + +| 输入类型(`type`) | 输出 MySQL 类型 | +|---------------------|------------------| +| `str` | `VARCHAR(len)` | +| `char` | `CHAR(len)` | +| `int`, `long`, `short` | `int` | +| `long`(单独判断) | `bigint` | +| `float`, `double`, `ddouble` | `double(len, dec)` | +| `date` | `date` | +| `time` | `time` | +| `datetime` | `datetime` | +| `timestamp` | `TIMESTAMP DEFAULT CURRENT_TIMESTAMP` | +| `text` | `longtext` | +| `bin` | `longblob` | +| 其他未知类型 | 原样输出 `{{type}}` | + +> ⚠️ 注意:`long` 类型在第一个条件中被映射为 `int`,但在后续又被单独判断为 `bigint` —— 这可能导致逻辑冲突,见下方【注意事项】。 + +### `defaultValue(defaultv)` +生成字段的默认值子句。 + +- 若 `defaultv` 存在,则输出:`DEFAULT 'value'` +- 若为空,则不输出任何内容 + +> 所有默认值均以字符串形式包裹(使用单引号),适合文本和数字;时间类型需确保传入格式正确。 + +### `nullStr(nullable)` +控制字段是否允许为空。 + +- 若 `nullable == 'no'` → 输出 `NOT NULL` +- 否则不输出 + +> 推荐约定:`yes/no` 表示可空性,也可扩展为布尔值处理。 + +### `primary()` +生成主键定义语句。 + +- 使用 `summary[0].primary` 中的字段名列表,用逗号连接 +- 输出:`, primary key (col1,col2,...)` + +> 注意:此宏前会自动换行并缩进,需配合字段列表使用。 + +--- + +## 生成的 SQL 内容结构 + +### 1. 删除已有表 +```sql +DROP TABLE IF EXISTS {{summary[0].name}}; +``` + +### 2. 创建新表 +```sql +CREATE TABLE table_name ( + `field1` type ... [NOT NULL] [DEFAULT '...'] [COMMENT '...'], + ... + [PRIMARY KEY (pk_fields)] +) +ENGINE=InnoDB +DEFAULT CHARSET=utf8 +[COMMENT='表注释']; +``` + +### 3. 创建索引 +遍历 `indexes` 数组,生成: +```sql +CREATE [UNIQUE] INDEX index_name ON table_name(field1, field2); +``` + +--- + +## 示例输出 + +假设输入如下元数据: + +```python +summary = [{ + "name": "user", + "primary": ["id"], + "title": "用户表" +}] + +fields = [ + { + "name": "id", + "type": "long", + "length": 20, + "dec": 0, + "nullable": "no", + "default": "", + "title": "用户ID" + }, + { + "name": "username", + "type": "str", + "length": 50, + "dec": 0, + "nullable": "no", + "default": "", + "title": "用户名" + }, + { + "name": "created_at", + "type": "timestamp", + "length": 0, + "dec": 0, + "nullable": "yes", + "default": "", + "title": "创建时间" + } +] + +indexes = [ + { + "name": "uk_username", + "idxtype": "unique", + "idxfields": ["username"] + } +] +``` + +### 生成的 SQL: +```sql +DROP TABLE IF EXISTS user; +CREATE TABLE user +( + `id` bigint NOT NULL, + `username` VARCHAR(50) NOT NULL, + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP +) +engine=innodb +default charset=utf8 +comment '用户表'; + +CREATE UNIQUE INDEX user_uk_username ON user(username); +``` + +--- + +## 注意事项与建议 + +1. ❗ **类型判断逻辑问题**: + ```jinja2 + {% elif type=='long' or type=='int' or type=='short' %} + int + {% elif type=='long' %} + bigint + ``` + 上述逻辑中,`long` 已在第一个分支被捕获为 `int`,因此永远不会进入 `bigint` 分支! + ✅ **修复建议**: + ```jinja2 + {%- if type == 'long' -%} + bigint + {%- elif type in ['int', 'short'] -%} + int + ``` + +2. 🔤 所有字符串默认值都加了单引号,若需支持数值型默认值(如 `DEFAULT 0`),应做类型区分。 + +3. 🧩 `summary` 设计为列表形式,但实际仅使用 `summary[0]`,可考虑简化为直接对象传参。 + +4. 💬 注释建议统一使用 UTF-8 编码,避免中文乱码。 + +5. 🔐 生产环境建议增加 SQL 注入风险校验(如字段名、表名合法性验证)。 + +--- + +## 使用方式(Python 示例) + +```python +from jinja2 import Template + +# 加载模板 +tpl = Template(mysql_ddl_tmpl) + +# 渲染 SQL +sql = tpl.render(summary=summary, fields=fields, indexes=indexes) + +print(sql) +``` + +--- + +## 版本历史 + +| 版本 | 日期 | 描述 | +|------|------------|------| +| 1.0 | 2025-04 | 初始版本,支持基础字段类型与索引生成 | + +--- + +## 许可与维护 + +- **作者**:Auto-generated +- **用途**:内部系统 / 数据仓库建模 +- **维护建议**:结合 Schema JSON 配置驱动模板渲染,提升可维护性。 + +--- + +✅ 文档结束 \ No newline at end of file diff --git a/aidocs/ddl_template_oracle.md b/aidocs/ddl_template_oracle.md new file mode 100644 index 0000000..2b1da89 --- /dev/null +++ b/aidocs/ddl_template_oracle.md @@ -0,0 +1,222 @@ +以下是为提供的 Jinja2 模板代码编写的 **Markdown 格式技术文档**,适用于 Oracle 数据库 DDL 生成场景。 + +--- + +# Oracle DDL 模板技术文档 + +## 概述 + +`oracle_ddl_tmpl` 是一个基于 [Jinja2](https://jinja.palletsprojects.com/) 的模板字符串,用于自动生成 Oracle 数据库表的 **数据定义语言 (DDL)** 脚本。该模板支持字段类型映射、主键定义、索引创建以及注释添加等功能,适用于自动化建模或元数据驱动的数据库构建流程。 + +--- + +## 使用场景 + +- 自动化生成 Oracle 表结构(`CREATE TABLE`) +- 支持字段级和表级中文注释 +- 支持主键、唯一/普通索引定义 +- 可集成至 ETL 工具、数据建模平台或 CI/CD 流程中 + +--- + +## 模板变量说明 + +### 输入上下文参数 + +| 参数名 | 类型 | 说明 | +|--------|------|------| +| `summary[0]` | 对象 | 表元数据对象,包含表名、标题、主键等信息 | +|   `.name` | 字符串 | 表名(如:`EMPLOYEE`) | +|   `.title` | 字符串 | 表中文描述(用于 COMMENT) | +|   `.primary` | 列表 | 主键字段名称列表(如:`['ID']`) | +| `fields` | 列表 | 字段对象列表,每个字段包含属性如名称、类型、长度等 | +| `indexes` | 列表 | 索引对象列表,定义索引类型与字段 | + +#### `fields` 中单个字段对象结构 + +| 属性 | 类型 | 说明 | +|------|------|------| +| `name` | 字符串 | 字段英文名(如:`EMP_NAME`) | +| `type` | 字符串 | 字段逻辑类型(见下文映射规则) | +| `length` | 整数 | 字段长度(对数值/字符类型有效) | +| `dec` | 整数 | 小数位数(仅对浮点类型有效) | +| `nullable` | 字符串 | 是否可为空:`yes` / `no` | +| `title` | 字符串 | 字段中文描述(用于 COMMENT) | + +#### `indexes` 中单个索引对象结构 + +| 属性 | 类型 | 说明 | +|------|------|------| +| `name` | 字符串 | 索引标识名(将用于生成索引名) | +| `idxtype` | 字符串 | 索引类型:`unique` 或其他(非 unique 视为普通索引) | +| `idxfields` | 列表 | 索引包含的字段名列表(如:`['DEPT_ID', 'STATUS']`) | + +--- + +## 模板功能详解 + +### 宏定义(Macros) + +#### `typeStr(type, len, dec)` +根据字段逻辑类型映射为 Oracle 原生数据类型。 + +| 逻辑类型 | Oracle 实际类型 | 示例输出 | +|----------|------------------|-----------| +| `str` | `VARCHAR2(len)` | `VARCHAR2(50)` | +| `char` | `CHAR(len)` | `CHAR(10)` | +| `long`, `int`, `short` | `NUMBER` | `NUMBER` | +| `float`, `double`, `ddouble` | `NUMBER(len, dec)` | `NUMBER(10, 2)` | +| `date`, `time` | `DATE` | `DATE` | +| `timestamp` | `TIMESTAMP` | `TIMESTAMP` | +| `text` | `CLOB` | `CLOB` | +| `bin` | `BLOB` | `BLOB` | +| 其他未知类型 | 原样输出 `{{type}}` | 如:`XMLTYPE` | + +> ⚠️ 注意:`len` 和 `dec` 需在输入数据中提供,否则可能导致渲染错误。 + +#### `nullStr(nullable)` +判断字段是否非空。 + +- 若 `nullable == 'no'` → 输出 `NOT NULL` +- 否则不输出任何内容(即允许为空) + +#### `primary()` +生成主键约束子句。 + +- 使用 `summary[0].primary` 列表中的字段名拼接 +- 输出格式:`, primary key (col1,col2,...)` + +> ✅ 提示:此宏前需确保已有字段定义,并以逗号分隔最后一个字段。 + +--- + +### 主体 DDL 语句结构 + +#### 1. 删除旧表 +```sql +drop table {{summary[0].name}}; +``` +> ⚠️ 警告:无条件删除同名表,请谨慎使用于生产环境。 + +#### 2. 创建新表 +```sql +CREATE TABLE {{summary[0].name}} +( + -- 字段列表循环生成 -- + {% for field in fields %} + {{field.name}} {{typeStr(...)}} {{nullStr(...)}}{%- if not loop.last %},{% endif %} + {% endfor %} + + -- 主键定义(如有)-- + {% if summary[0].primary and len(summary[0].primary)>0 %} + {{primary()}} + {% endif %} +); +``` + +#### 3. 创建索引 +```sql +{% for v in indexes %} + CREATE {% if v.idxtype=='unique' %}UNIQUE{% endif %} INDEX + {{summary[0].name}}_{{v.name}} + ON {{summary[0].name}}({{",".join(v.idxfields)}}); +{% endfor %} +``` +- 自动生成命名规则:`表名_索引名` +- 支持唯一索引标记 + +#### 4. 添加注释 +```sql +COMMENT ON TABLE {{summary[0].name}} IS '{{summary[0].title}}'; + +{% for field in fields %} + COMMENT ON COLUMN {{summary[0].name}}.{{field.name}} IS '{{field.title}}'; +{% endfor %} +``` +> ✅ 支持中文注释,提升数据库可读性与维护性。 + +--- + +## 示例输出(片段) + +假设输入如下: +```python +summary = [{ + "name": "EMPLOYEE", + "title": "员工信息表", + "primary": ["EMP_ID"] +}] +fields = [ + {"name": "EMP_ID", "type": "long", "nullable": "no", "title": "员工ID"}, + {"name": "EMP_NAME", "type": "str", "length": 100, "nullable": "no", "title": "姓名"}, + {"name": "SALARY", "type": "double", "length": 12, "dec": 2, "nullable": "yes", "title": "薪资"} +] +indexes = [ + {"name": "IDX_NAME", "idxtype": "unique", "idxfields": ["EMP_NAME"]} +] +``` + +生成 SQL 片段: +```sql +drop table EMPLOYEE; +CREATE TABLE EMPLOYEE +( + EMP_ID NUMBER NOT NULL, + EMP_NAME VARCHAR2(100) NOT NULL, + SALARY NUMBER(12,2) +,primary key(EMP_ID) +); + +CREATE UNIQUE INDEX EMPLOYEE_IDX_NAME ON EMPLOYEE(EMP_NAME); + +COMMENT ON TABLE EMPLOYEE IS '员工信息表'; +COMMENT ON COLUMN EMPLOYEE.EMP_ID is '员工ID'; +COMMENT ON COLUMN EMPLOYEE.EMP_NAME is '姓名'; +COMMENT ON COLUMN EMPLOYEE.SALARY is '薪资'; +``` + +--- + +## 注意事项与最佳实践 + +1. **安全性警告** + - `DROP TABLE` 会清除现有数据,请确认是否需要保留历史数据。 + - 建议在正式环境中禁用自动 drop,或增加条件判断。 + +2. **字段类型扩展** + - 当前仅覆盖常见类型,如需支持 `XMLTYPE`、`INTERVAL` 等,可通过 `else` 分支直接传入原生类型。 + +3. **索引命名规范** + - 推荐保持 `表名_索引名` 的简洁风格,避免过长或冲突。 + +4. **Jinja2 渲染要求** + - 必须保证传入的数据结构完整,避免访问不存在的属性导致异常。 + - 建议进行前置校验(如字段必填项检查)。 + +5. **字符集与存储** + - `VARCHAR2` 和 `CHAR` 默认按字节计算,若需按字符建议手动调整或通过外部配置处理。 + +6. **CLOB/BLOB 存储优化** + - 大对象字段可能影响性能,建议结合业务需求设置合理的存储参数(当前模板未包含)。 + +--- + +## 扩展建议 + +- 添加 `IF NOT EXISTS` 判断(Oracle 不原生支持,可用 PL/SQL 包装) +- 支持外键约束生成 +- 支持分区表语法 +- 引入模板配置层,实现多数据库兼容(如 MySQL、PostgreSQL) + +--- + +## 许可与维护 + +- **作者**:Auto-generated Template +- **用途**:内部系统自动化建模 +- **依赖**:Python + Jinja2 >= 3.0 +- **更新时间**:2025年4月5日 + +--- + +✅ 文档结束。可用于团队知识共享、项目文档归档或工具集成说明。 \ No newline at end of file diff --git a/aidocs/ddl_template_postgresql.md b/aidocs/ddl_template_postgresql.md new file mode 100644 index 0000000..0f350a6 --- /dev/null +++ b/aidocs/ddl_template_postgresql.md @@ -0,0 +1,242 @@ +以下是为提供的 Jinja2 模板代码编写的 **Markdown 格式技术文档**,用于描述该 PostgreSQL DDL 生成模板的功能、结构和使用方式。 + +--- + +# PostgreSQL DDL 生成模板技术文档 + +## 概述 + +`postgresql_ddl_tmpl` 是一个基于 **Jinja2 模板引擎**的字符串模板,用于自动生成符合 PostgreSQL 语法的 **数据定义语言 (DDL)** 脚本。它可以根据表结构元数据(如字段类型、长度、是否可为空、主键、索引等)动态生成完整的 `CREATE TABLE` 语句,包括: + +- 表的创建与删除 +- 字段定义及其数据类型映射 +- 主键约束 +- 索引(普通或唯一) +- 表和字段的注释 + +该模板适用于自动化建模工具、ETL 流程或数据库同步系统中,实现从元数据到物理表结构的自动转换。 + +--- + +## 模板源码 + +```python +postgresql_ddl_tmpl = """{% macro typeStr(type,len,dec) %} +{%- if type=='str' -%} +VARCHAR({{len}}) +{%- elif type=='char' -%} +CHAR({{len}}) +{%- elif type=='long' or type=='int' or type=='short' -%} +NUMERIC(30,0) +{%- elif type=='float' or type=='double' or type=='ddouble' -%} +NUMERIC({{len}},{{dec}}) +{%- elif type=='date' -%} +DATE +{%- elif type=='time' -%} +TIME +{%- elif type=='timestamp' -%} +TIMESTAMP +{%- else -%} +{{type}} +{%- endif %} +{%- endmacro %} +{% macro nullStr(nullable) %} +{%- if nullable=='no' -%} +NOT NULL +{%- endif -%} +{% endmacro %} +{% macro primary() %} +,PRIMARY KEY({{','.join(summary[0].primary)}}) +{% endmacro %} +DROP TABLE IF EXISTS {{summary[0].name}}; +CREATE TABLE {{summary[0].name}} +( +{% for field in fields %} + {{field.name}} {{typeStr(field.type,field.length,field.dec)}} {{nullStr(field.nullable)}}{%- if not loop.last -%},{%- endif -%} +{% endfor %} +{% if summary[0].primary and len(summary[0].primary)>0 %} +{{primary()}} +{% endif %} +); +{% for v in indexes %} +CREATE {% if v.idxtype=='unique' %}UNIQUE{% endif %} INDEX {{summary[0].name}}_{{v.name}} ON {{summary[0].name}}({{",".join(v.idxfields)}}); +{%- endfor -%} +COMMENT ON TABLE {{summary[0].name}} IS '{{summary[0].title}}'; +{% for field in fields %} +COMMENT ON COLUMN {{summary[0].name}}.{{field.name}} is '{{field.title}}'; +{% endfor %} +""" +``` + +--- + +## 模板变量说明 + +| 变量名 | 类型 | 描述 | +|-------|------|------| +| `summary[0]` | 对象 | 包含表级别的元信息对象,必须是列表且第一个元素有效 | +|   `.name` | 字符串 | 表名(如 `"users"`) | +|   `.title` | 字符串 | 表的中文/描述性标题,用于 COMMENT | +|   `.primary` | 列表 | 主键字段名称列表(如 `["id"]` 或 `["user_id", "tenant_id"]`) | +| `fields` | 列表 | 字段对象列表,每个对象代表一列 | +|   `.name` | 字符串 | 字段名(如 `"username"`) | +|   `.type` | 字符串 | 字段逻辑类型(见下文“类型映射”) | +|   `.length` | 整数 | 字段长度(对字符串和数值类型有意义) | +|   `.dec` | 整数 | 小数位数(decimal scale) | +|   `.nullable` | 字符串 | 是否允许为空:`"yes"` 表示可空,`"no"` 表示 `NOT NULL` | +|   `.title` | 字符串 | 字段的中文/描述性标题,用于 COMMENT | +| `indexes` | 列表 | 索引定义对象列表 | +|   `.name` | 字符串 | 索引名称标识(将用于生成索引名) | +|   `.idxtype` | 字符串 | 索引类型,若为 `"unique"` 则创建唯一索引 | +|   `.idxfields` | 列表 | 索引包含的字段名列表(如 `["email"]` 或 `["status", "created_time"]`) | + +--- + +## 内置宏(Macros) + +### `typeStr(type, len, dec)` +根据字段的逻辑类型映射为 PostgreSQL 实际数据类型。 + +| 逻辑类型 | 映射结果 | +|---------|--------| +| `'str'` | `VARCHAR(len)` | +| `'char'` | `CHAR(len)` | +| `'long'`, `'int'`, `'short'` | `NUMERIC(30,0)` (高精度整数) | +| `'float'`, `'double'`, `'ddouble'` | `NUMERIC(len, dec)` | +| `'date'` | `DATE` | +| `'time'` | `TIME` | +| `'timestamp'` | `TIMESTAMP` | +| 其他未知类型 | 原样输出 `{{type}}`(可用于扩展) | + +> ⚠️ 注意:浮点类型统一使用 `NUMERIC` 以保证精度;如需性能优化可后续调整为 `REAL` / `DOUBLE PRECISION`。 + +--- + +### `nullStr(nullable)` +生成 `NOT NULL` 约束。 + +- 若 `nullable == 'no'` → 输出 `NOT NULL` +- 否则不输出任何内容(即允许为空) + +--- + +### `primary()` +生成主键约束子句。 + +- 使用 `summary[0].primary` 中的字段名,通过逗号拼接 +- 示例:主键为 `["id"]` → 输出 `,PRIMARY KEY(id)` +- 注意:前面有一个英文逗号,依赖前一行结尾,因此必须确保前面有换行和逗号处理 + +--- + +## 生成的 DDL 结构说明 + +1. **删除旧表** + ```sql + DROP TABLE IF EXISTS table_name; + ``` + > 避免重复建表错误。 + +2. **创建新表** + ```sql + CREATE TABLE table_name (...); + ``` + - 包含所有字段定义 + - 自动添加主键约束(如果存在) + - 支持多字段联合主键 + +3. **创建索引** + ```sql + CREATE [UNIQUE] INDEX table_name_idxname ON table_name(field1, field2); + ``` + - 支持普通索引和唯一索引 + - 索引命名规则:`{table_name}_{index_name}` + +4. **添加注释** + ```sql + COMMENT ON TABLE table_name IS '用户信息表'; + COMMENT ON COLUMN table_name.username IS '用户名'; + ``` + - 提升数据库可读性和文档性 + +--- + +## 使用示例 + +假设输入上下文如下(Python 字典形式): + +```python +context = { + "summary": [{ + "name": "users", + "title": "用户信息表", + "primary": ["id"] + }], + "fields": [ + {"name": "id", "type": "long", "length": 30, "dec": 0, "nullable": "no", "title": "用户ID"}, + {"name": "username", "type": "str", "length": 50, "dec": 0, "nullable": "no", "title": "用户名"}, + {"name": "email", "type": "str", "length": 100, "dec": 0, "nullable": "yes", "title": "邮箱地址"}, + {"name": "created_time", "type": "timestamp", "length": 0, "dec": 0, "nullable": "no", "title": "创建时间"} + ], + "indexes": [ + {"name": "email_idx", "idxtype": "unique", "idxfields": ["email"]} + ] +} +``` + +渲染后输出 SQL: + +```sql +DROP TABLE IF EXISTS users; +CREATE TABLE users +( + id NUMERIC(30,0) NOT NULL, + username VARCHAR(50) NOT NULL, + email VARCHAR(100), + created_time TIMESTAMP NOT NULL, + PRIMARY KEY(id) +); + +CREATE UNIQUE INDEX users_email_idx ON users(email); + +COMMENT ON TABLE users IS '用户信息表'; +COMMENT ON COLUMN users.id is '用户ID'; +COMMENT ON COLUMN users.username is '用户名'; +COMMENT ON COLUMN users.email is '邮箱地址'; +COMMENT ON COLUMN users.created_time is '创建时间'; +``` + +--- + +## 扩展建议 + +- ✅ **支持默认值**:可在字段中增加 `.default` 属性,并在模板中扩展。 +- ✅ **外键支持**:当前未处理外键,可根据需要添加 `FOREIGN KEY` 子句。 +- ✅ **字符集/排序规则**:PostgreSQL 通常使用数据库级设置,也可显式指定。 +- ✅ **分区表支持**:复杂场景下可扩展模板支持 `PARTITION BY`。 + +--- + +## 注意事项 + +1. `primary()` 宏前会自动加 `,`,因此必须确保前面字段定义已正确结束并换行。 +2. 所有字段的 `length` 和 `dec` 必须提供,即使某些类型不需要(可用占位值如 `0`)。 +3. `summary` 必须是一个非空列表,`summary[0]` 是实际使用的表元数据。 +4. 索引名应避免冲突,推荐命名清晰(如 `idx_status`)。 + +--- + +## 总结 + +`postgresql_ddl_tmpl` 是一个功能完整、结构清晰的 DDL 自动生成模板,适合集成进元数据驱动的数据平台。其优势在于: + +- 类型抽象良好,便于业务人员理解 +- 支持常见约束与注释 +- 易于维护和扩展 + +通过此模板,可以高效地将模型设计转化为可执行的数据库脚本,提升开发效率与一致性。 + +--- + +✅ *文档版本:1.0* +📅 *最后更新:2025年4月5日* \ No newline at end of file diff --git a/aidocs/ddl_template_sqlite3.md b/aidocs/ddl_template_sqlite3.md new file mode 100644 index 0000000..626f48e --- /dev/null +++ b/aidocs/ddl_template_sqlite3.md @@ -0,0 +1,204 @@ +以下是为提供的 Jinja2 模板代码编写的 **Markdown 格式技术文档**,适用于开发人员理解该模板的功能、结构和使用方式。 + +--- + +# SQLite3 DDL 生成模板技术文档 + +`sqlite3_ddl_tmpl` 是一个基于 [Jinja2](https://jinja.palletsprojects.com/) 的模板字符串,用于动态生成符合 SQLite3 语法的表定义(DDL)语句。它支持字段类型映射、空值约束、主键定义、注释以及索引创建。 + +## 目录 + +- [功能概述](#功能概述) +- [模板变量说明](#模板变量说明) +- [宏(Macros)详解](#宏macros详解) + - [`typeStr(type, len, dec)`](#typestrtype-len-dec) + - [`nullStr(nullable)`](#nullstrnullable) + - [`primary()`](#primary) +- [生成的 DDL 结构](#生成的-ddl-结构) +- [输出示例](#输出示例) +- [使用场景](#使用场景) +- [注意事项](#注意事项) + +--- + +## 功能概述 + +此模板用于根据元数据自动生成如下内容: + +1. 删除已存在的同名表(`DROP TABLE IF EXISTS`) +2. 创建新表(`CREATE TABLE`),包括: + - 字段名、类型、是否非空 + - 字段级注释(通过 `--` 注释实现) + - 主键定义 +3. 可选的唯一或普通索引创建(`CREATE INDEX` / `CREATE UNIQUE INDEX`) + +所有逻辑均通过 Jinja2 模板语言实现,适合作为代码生成器的一部分,集成在 ORM 工具或数据库建模系统中。 + +--- + +## 模板变量说明 + +| 变量 | 类型 | 描述 | +|------|------|------| +| `summary[0]` | dict/object | 表的摘要信息对象,包含:`name`(表名)、`primary`(主键字段列表)、`title`(表标题/描述) | +| `fields` | list | 字段列表,每个字段对象包含:
• `name`: 字段名
• `type`: 数据类型(如 `'str'`, `'int'` 等)
• `length`: 长度(可选)
• `dec`: 小数位数(可选)
• `nullable`: 是否允许为空(`'yes'` 或 `'no'`)
• `title`: 字段说明(作为注释显示) | +| `indexes` | list | 索引列表,每个索引对象包含:
• `name`: 索引名称
• `idxtype`: 索引类型(`'unique'` 或其他)
• `idxfields`: 构成索引的字段名列表 | + +--- + +## 宏(Macros)详解 + +### `typeStr(type, len, dec)` + +将高级数据类型转换为 SQLite 原生类型。 + +#### 参数: +- `type`: 字符串,表示原始类型(如 `'str'`, `'int'`, `'float'` 等) +- `len`: 字段长度(未使用于 SQLite 类型推断,仅保留接口兼容性) +- `dec`: 小数位数(同上) + +#### 映射规则: + +| 输入类型 | 输出 SQLite 类型 | +|---------|----------------| +| `str`, `char`, `date`, `time`, `datetime`, `timestamp` | `TEXT` | +| `long`, `int`, `short`, `longlong` | `INTEGER`(模板中写作 `int`) | +| `float`, `double`, `ddouble` | `REAL` | +| `bin` | `BLOB` | +| 其他未知类型 | 原样输出 `{{type}}` | + +> ⚠️ 注意:SQLite 实际使用的是“类型亲和性”(Type Affinity),但此处简化处理以增强可读性和一致性。 + +--- + +### `nullStr(nullable)` + +生成 `NOT NULL` 约束条件。 + +#### 参数: +- `nullable`: `'no'` 表示不允许为空;其他值(如 `'yes'`)则不添加约束 + +#### 输出: +- 若 `nullable == 'no'` → 输出 `NOT NULL` +- 否则输出空字符串 + +--- + +### `primary()` + +生成主键子句。 + +#### 逻辑: +- 使用 `summary[0].primary` 中的字段名列表,用逗号连接 +- 添加 `, primary key(...)` 子句 + +#### 示例: +```sql +,primary key(id, code) +``` + +> ✅ 仅当存在主键且字段数 > 0 时才插入该行。 + +--- + +## 生成的 DDL 结构 + +模板最终输出的标准 SQL 包括以下部分: + +```sql +DROP TABLE IF EXISTS ; + +CREATE TABLE +( + `field1` -- 字段说明 + ... + ,primary key(pk_field1, pk_field2) +) -- 表说明 ; + +CREATE INDEX|UNIQUE INDEX index_name ON table_name(field_list); +... +``` + +每条语句严格按照 SQLite 语法编写,并带有良好格式化与注释支持。 + +--- + +## 输出示例 + +假设输入数据如下: + +```python +summary = [{ + "name": "users", + "primary": ["id"], + "title": "用户信息表" +}] + +fields = [ + {"name": "id", "type": "int", "nullable": "no", "title": "用户ID"}, + {"name": "name", "type": "str", "length": 50, "nullable": "no", "title": "用户名"}, + {"name": "email", "type": "str", "length": 100, "nullable": "yes", "title": "邮箱地址"}, + {"name": "created_at", "type": "datetime", "nullable": "no", "title": "创建时间"} +] + +indexes = [ + {"name": "email_idx", "idxtype": "", "idxfields": ["email"]}, + {"name": "uniq_name", "idxtype": "unique", "idxfields": ["name"]} +] +``` + +### 生成的 SQL: + +```sql +drop table if exists users; +CREATE TABLE users +( + `id` int NOT NULL -- 用户ID, + `name` TEXT NOT NULL -- 用户名, + `email` TEXT -- 邮箱地址, + `created_at` TEXT NOT NULL -- 创建时间 +,primary key(id) +) --用户信息表 +; + +CREATE INDEX users_email_idx ON users(email); +CREATE UNIQUE INDEX users_uniq_name ON users(name); +``` + +--- + +## 使用场景 + +该模板适用于以下场景: + +- 自动化数据库建模工具 +- 数据字典到物理模型的转换 +- 跨平台迁移时的目标 DDL 生成(特别是轻量级 SQLite 场景) +- 内嵌式应用初始化脚本生成 + +结合 Python 的 `jinja2.Template(sqlite3_ddl_tmpl).render(...)` 即可快速渲染出实际 SQL。 + +--- + +## 注意事项 + +1. **字段名使用反引号包裹**:确保特殊字符或关键字字段的安全性。 +2. **SQLite 类型限制**: + - 实际存储依赖“动态类型”,但建议保持类型亲和性一致。 + - 没有原生 `DATETIME` 类型,使用 `TEXT` 存储 ISO8601 时间字符串。 +3. **主键语法位置**:主键定义位于字段之后,以逗号开头拼接,需注意前面是否有字段结尾的逗号。 +4. **索引命名规范**:采用 `_` 形式避免冲突。 +5. **注释支持**:利用 `--` 添加人类可读说明,不影响执行。 + +--- + +## 版权与维护 + +- **作者**:Auto-generated Template +- **用途**:内部代码生成组件 +- **建议扩展**:可根据需要增加默认值、外键等支持。 + +--- + +✅ 文档版本:v1.0 +📅 最后更新:2025-04-05 \ No newline at end of file diff --git a/aidocs/ddl_template_sqlserver.md b/aidocs/ddl_template_sqlserver.md new file mode 100644 index 0000000..14b9946 --- /dev/null +++ b/aidocs/ddl_template_sqlserver.md @@ -0,0 +1,333 @@ +# SQL Server DDL 模板技术文档(Jinja2 模板) + +--- + +## 简介 + +`sqlserver_ddl_tmpl` 是一个基于 **Jinja2 模板引擎** 的 SQL Server 数据定义语言(DDL)生成模板。该模板用于根据元数据动态生成创建表、索引以及添加字段描述的完整 T-SQL 脚本。 + +支持功能包括: +- 字段类型映射 +- 主键定义 +- 唯一/普通索引创建 +- 使用 `sp_addextendedproperty` 添加中文注释(MS_Description) +- 自动删除旧表(可选) + +--- + +## 模板结构概览 + +```jinja2 +{% macro typeStr(type, len, dec) %}...{% endmacro %} +{% macro nullStr(nullable) %}...{% endmacro %} +{% macro primary() %}...{% endmacro %} + +drop table dbo.{{summary[0].name}}; +CREATE TABLE dbo.{{summary[0].name}} +( + ... +) +-- 创建索引 +-- 添加表和列的描述信息 +``` + +--- + +## 宏(Macros)说明 + +### `typeStr(type, len, dec)` + +将通用字段类型转换为 SQL Server 对应的数据类型。 + +| 输入参数 | 类型 | 说明 | +|---------|------|------| +| `type` | string | 字段逻辑类型(如 `'str'`, `'int'` 等) | +| `len` | int | 长度或精度(适用于字符串、数值等) | +| `dec` | int | 小数位数(适用于浮点数) | + +#### 映射规则: + +| 逻辑类型 | SQL Server 类型 | 说明 | +|---------------|------------------------------|------| +| `str` | `NVARCHAR(len)` | 变长 Unicode 字符串 | +| `char` | `CHAR(len)` | 定长字符 | +| `long/int/short` | `NUMERIC` | 整数类型统一用 NUMERIC(无精度限制) | +| `float/double/ddouble` | `numeric(len, dec)` | 精确数值类型,支持小数 | +| `date/time` | `DATE` | 日期类型 | +| `timestamp` | `TIMESTAMP` | 时间戳(行版本号),注意:非 datetime 类型 | +| `text` | `NVARCHAR(MAX)` | 大文本字段 | +| `bin` | `IMAGE` | 二进制大对象(已弃用,建议使用 `VARBINARY(MAX)`) | +| 其他未知类型 | 原样输出 `{{type}}` | 扩展兼容性 | + +> ⚠️ 注意:SQL Server 的 `TIMESTAMP` 实际是 **rowversion** 类型,若需时间记录请使用 `DATETIME2`。 + +--- + +### `nullStr(nullable)` + +控制字段是否允许为空。 + +| 参数 | 值示例 | 输出结果 | +|------------|-----------|----------------| +| `nullable` | `'no'` | `NOT NULL` | +| 其他值(如 `'yes'`, 空等) | —— | (空字符串,即允许 NULL) | + +--- + +### `primary()` + +生成主键约束语句。 + +- 使用 `summary[0].primary` 中定义的字段列表。 +- 输出格式:`, primary key (col1, col2, ...)` +- 仅在存在主键字段时插入。 + +--- + +## 主体 DDL 逻辑 + +### 1. 删除原表 + +```sql +drop table dbo.{{summary[0].name}}; +``` + +> ⚠️ 此操作会 **无条件删除现有表及其数据**,生产环境慎用。 + +--- + +### 2. 创建表 + +```sql +CREATE TABLE dbo.{{summary[0].name}} +( + {% for field in fields %} + {{field.name}} {{typeStr(field.type,field.length,field.dec)}} {{nullStr(field.nullable)}} + {%- if not loop.last -%},{%- endif %} + {% endfor %} + {% if summary[0].primary and len(summary[0].primary)>0 %} + {{primary()}} + {% endif %} +) +``` + +#### 字段循环说明: + +- 遍历 `fields` 列表中每个字段对象。 +- 生成字段名 + 类型 + 是否非空。 +- 使用 `{%- if not loop.last %},{% endif %}` 控制逗号不加在最后一行。 + +#### 主键添加条件: + +- 当 `summary[0].primary` 存在且长度 > 0 时,调用 `primary()` 宏添加主键定义。 + +--- + +### 3. 创建索引 + +```jinja2 +{% for v in indexes %} +CREATE {% if v.idxtype=='unique' %}UNIQUE{% endif %} INDEX {{summary[0].name}}_{{v.name}} +ON {{summary[0].name}}({{",".join(v.idxfields)}}); +{% endfor %} +``` + +#### 支持索引类型: + +| `v.idxtype` | 生成语句 | +|-------------|----------| +| `'unique'` | `CREATE UNIQUE INDEX ...` | +| 其他(如 `'normal'`, 空值等) | `CREATE INDEX ...` | + +#### 索引命名规则: + +``` +<表名>_<索引名> +``` + +例如:`user_idx_email` 表示表 `user` 上名为 `idx_email` 的索引。 + +--- + +### 4. 添加表与字段描述(MS_Description) + +利用系统存储过程 `sys.sp_addextendedproperty` 添加注释。 + +#### 表级描述: + +```sql +EXEC sys.sp_addextendedproperty + @name=N'MS_Description', + @value=N'{{summary[0].title}}', + @level0type=N'SCHEMA', @level0name=N'dbo', + @level1type=N'TABLE', @level1name=N'{{summary[0].name}}' +``` + +#### 字段级描述(逐个循环): + +```sql +{% for field in fields %} +EXEC sys.sp_addextendedproperty + @name=N'MS_Description', + @value=N'{{field.title}}', + @level0type=N'SCHEMA', @level0name=N'dbo', + @level1type=N'TABLE', @level1name=N'{{summary[0].name}}', + @level2type=N'COLUMN', @level2name=N'{{field.name}}' +{% endfor %} +``` + +> ✅ 这些描述可在 SSMS 的“属性”面板或通过查询 `sys.extended_properties` 查看。 + +--- + +## 输入变量要求(上下文 Context) + +模板运行需要以下变量注入: + +| 变量名 | 类型 | 必须 | 说明 | +|-------------|------------|------|------| +| `summary` | list[dict] | 是 | 表基本信息列表,通常只取 `[0]` | +| `fields` | list[dict] | 是 | 字段列表 | +| `indexes` | list[dict] | 否 | 索引配置列表(可为空) | + +### `summary[0]` 结构示例: + +```python +{ + "name": "user_info", # 表名 + "title": "用户基本信息表" # 表中文名/描述 + "primary": ["id"] # 主键字段数组 +} +``` + +### `fields` 元素结构示例: + +```python +[ + { + "name": "id", + "type": "long", + "length": 19, + "dec": 0, + "nullable": "no", + "title": "用户ID" + }, + { + "name": "username", + "type": "str", + "length": 50, + "dec": 0, + "nullable": "no", + "title": "用户名" + } +] +``` + +### `indexes` 元素结构示例: + +```python +[ + { + "name": "idx_email", + "idxtype": "unique", + "idxfields": ["email"] + }, + { + "name": "idx_dept", + "idxtype": "normal", + "idxfields": ["dept_id", "status"] + } +] +``` + +--- + +## 示例输出(渲染后) + +假设输入如下元数据: + +```python +summary = [{ + "name": "user", + "title": "用户表", + "primary": ["id"] +}] +fields = [ + {"name": "id", "type": "long", "length": 19, "dec": 0, "nullable": "no", "title": "用户ID"}, + {"name": "name", "type": "str", "length": 100, "dec": 0, "nullable": "yes", "title": "姓名"}, + {"name": "email", "type": "str", "length": 255, "dec": 0, "nullable": "no", "title": "邮箱"} +] +indexes = [ + {"name": "uk_email", "idxtype": "unique", "idxfields": ["email"]} +] +``` + +### 渲染结果: + +```sql +drop table dbo.user; +CREATE TABLE dbo.user +( + id NUMERIC NOT NULL, + name NVARCHAR(100), + email NVARCHAR(255) NOT NULL, + primary key(id) +) + +CREATE UNIQUE INDEX user_uk_email ON user(email); + +EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'用户表' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'user' + +EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'用户ID' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'user', @level2type=N'COLUMN',@level2name=N'id' +EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'姓名' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'user', @level2type=N'COLUMN',@level2name=N'name' +EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'邮箱' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'user', @level2type=N'COLUMN',@level2name=N'email' +``` + +--- + +## 使用建议与注意事项 + +1. 🔐 **谨慎使用 `DROP TABLE`** + 生产环境中应避免直接删除表。建议改为判断是否存在再操作,或提供开关控制。 + +2. 💡 推荐扩展:增加 `IF NOT EXISTS` 判断表是否存在: + + ```sql + IF OBJECT_ID('dbo.{{summary[0].name}}', 'U') IS NOT NULL + DROP TABLE dbo.{{summary[0].name}}; + ``` + +3. 🛠 替代 IMAGE 类型: + `IMAGE` 已被弃用,推荐替换为: + ```jinja2 + {%- elif type=='bin' -%} + VARBINARY(MAX) + ``` + +4. 📅 时间戳类型澄清: + 若需存储时间,请使用 `DATETIME2` 并修改模板中 `timestamp` 分支。 + +5. 🔍 支持更多类型:可根据项目需求扩展 `typeStr` 宏以支持 `xml`, `hierarchyid`, `geometry` 等高级类型。 + +6. 🧪 测试建议: + 在自动化脚本中集成此模板前,务必进行单元测试验证类型映射正确性。 + +--- + +## 总结 + +本模板是一个高效、灵活的 SQL Server 表结构自动生成工具,适用于代码生成器、ETL 工具、模型同步系统等场景。结合元数据驱动设计,可大幅提升数据库开发效率并保证一致性。 + +✅ **优点**: +- 类型自动映射 +- 支持注释与索引 +- 结构清晰易维护 + +⚠️ **风险提示**: +- 包含 `DROP TABLE`,使用需谨慎 +- `TIMESTAMP` 和 `IMAGE` 类型需按实际需求调整 + +--- + +📅 最后更新:2025-04-05 +📦 所属模块:数据建模 / DDL 自动生成 \ No newline at end of file diff --git a/aidocs/filter.md b/aidocs/filter.md new file mode 100644 index 0000000..44a2464 --- /dev/null +++ b/aidocs/filter.md @@ -0,0 +1,303 @@ +# DBFilter 过滤器解释器技术文档 + +## 概述 + +`DBFilter` 是一个用于将 JSON 格式的 SQL 查询过滤条件解析为参数化 SQL 片段的 Python 类。它支持常见的逻辑运算符(AND、OR、NOT)和关系表达式(如 `=`, `<>`, `IN`, `LIKE` 等),适用于动态构建安全的数据库查询语句。 + +该模块通过递归遍历 JSON 结构,生成可嵌入 SQL 语句中的字符串,并提取常量与变量绑定信息,便于后续参数绑定或模板渲染。 + +--- + +## 安装依赖 + +本模块无外部强依赖,但推荐使用 `ujson` 提升性能: + +```bash +pip install ujson +``` + +日志功能依赖于 `appPublic.log.debug`,确保环境中已安装对应库或替换为标准日志模块。 + +--- + +## 支持的操作类型 + +### 1. 逻辑运算符(Logic Operators) + +| 操作符 | 说明 | +|--------|------| +| `AND` | 所有子条件都必须为真 | +| `OR` | 至少一个子条件为真 | +| `NOT` | 对单个条件取反 | + +### 2. 关系运算符(Relational Operators) + +| 运算符 | 含义 | +|---------------|------------------| +| `=` | 等于 | +| `!=` 或 `<>` | 不等于 | +| `>` | 大于 | +| `<` | 小于 | +| `>=` | 大于等于 | +| `<=` | 小于等于 | +| `IN` | 在某个集合中 | +| `NOT IN` | 不在某个集合中 | +| `IS NULL` | 为空 | +| `IS NOT NULL` | 非空 | +| `LIKE` | 模糊匹配 | +| `NOT LIKE` | 非模糊匹配 | + +> 注意:所有操作符不区分大小写处理,内部统一转为大写进行校验。 + +--- + +## 数据结构定义 + +过滤条件以嵌套 JSON 对象形式表示,基本结构如下: + +```json +{ + "AND": [ + { "field": "name", "op": "=", "var": "username" }, + { "field": "status", "op": "=", "const": "active" } + ] +} +``` + +### 字段说明 + +| 字段名 | 类型 | 必需 | 描述 | +|-------|------|------|------| +| `field` | string | 是 | 数据库字段名 | +| `op` | string | 是 | 操作符(见上表) | +| `const` | any | 否 | 常量值(直接序列化为占位符) | +| `var` | string | 否 | 变量名,需在命名空间 `ns` 中提供 | + +> - `const` 和 `var` 至少存在其一(除非是 `IS NULL` / `IS NOT NULL` 类型) +> - 使用 `${name}$` 作为参数占位符格式,避免与主流 ORM 冲突 + +--- + +## API 文档 + +### 类:`DBFilter` + +#### 初始化方法:`__init__(self, filterjson)` + +**参数:** +- `filterjson` (`str` or `dict`):JSON 字符串或字典格式的过滤条件 + +**行为:** +- 若输入为字符串,则自动调用 `json.loads()` 解析 +- 存储解析后的结构供后续生成 SQL 使用 + +**示例:** +```python +fj = {"AND": [...]} +dbf = DBFilter(fj) +``` + +--- + +#### 方法:`gen(ns={}) -> str or None` + +生成参数化的 SQL 过滤片段。 + +**参数:** +- `ns` (`dict`):变量命名空间,提供 `var` 到实际值的映射 + +**返回值:** +- 成功时返回 SQL 表达式字符串(可能包含括号) +- 若无法生成有效 SQL(如变量缺失),返回 `None` +- 所有常量会被分配唯一名称并存入 `self.consts` + +**副作用:** +- 更新 `self.consts` 字典,记录所有常量占位符及其值 + +**示例:** +```python +sql = dbf.gen({'username': 'alice'}) +# 输出: "name = ${filter_const_0}$ AND status = 'active'" +``` + +--- + +#### 方法:`get_variables() -> dict` + +获取过滤器中所有 `var` 映射到 `field` 的变量引用关系。 + +**返回值:** +- `dict`: `{ variable_name: field_name }` + +可用于预检查所需变量是否齐全。 + +**示例:** +```python +dbf.get_variables() +# 返回: {'username': 'name', 'status': 'status'} +``` + +--- + +#### 私有方法:`_genFilterSQL(fj, ns)` + +递归生成 SQL 表达式的核心逻辑。 + +**处理规则:** +- 单键对象优先判断是否为逻辑操作符 +- `AND` / `OR`:连接多个子表达式,`OR` 自动加括号 +- `NOT`:对子表达式加 `NOT (...)` +- 其他情况交由 `_genFilterItems` 处理 + +--- + +#### 私有方法:`_genFilterItems(fj, ns)` + +处理单个字段比较表达式。 + +**逻辑:** +- 校验必填字段:`field`, `op`, (`const` 或 `var`) +- 支持 `IS NULL` / `IS NOT NULL` 无需值 +- 若使用 `var`,检查其是否存在于 `ns` +- 若使用 `const`,生成唯一占位符名(如 `filter_const_0`) + +--- + +#### 函数:`default_filterjson(fields: list, ns: dict) -> dict or None` + +根据字段白名单和变量空间生成默认等值过滤条件。 + +**参数:** +- `fields`: 允许参与过滤的字段列表 +- `ns`: 当前可用变量字典 + +**返回值:** +- 匹配字段的等值条件组成的 `AND` 结构 +- 若无匹配项返回 `None` +- 单条件时不包装 `AND` + +**用途:** +常用于自动生成基于请求参数的简单过滤器。 + +**示例:** +```python +default_filterjson(['name', 'age'], {'name': 'Bob', 'city': 'Shanghai'}) +# 返回: +# { +# "AND": [ +# {"field": "name", "op": "=", "var": "name"} +# ] +# } +``` + +--- + +## 使用示例 + +### 示例 1:基础用法 + +```python +fj = { + "AND": [ + { + "field": "field1", + "op": "=", + "var": "name" + }, + { + "field": "del_flg", + "op": "=", + "const": "0" + } + ] +} + +dbf = DBFilter(fj) +sql = dbf.gen({"name": "joe"}) +print(sql) +# 输出: field1 = ${name}$ AND del_flg = ${filter_const_0}$ +print(dbf.consts) +# 输出: {'filter_const_0': '0'} +``` + +### 示例 2:复杂嵌套逻辑 + +```python +fj = { + "AND": [ + { + "OR": [ + {"field": "age", "op": ">=", "const": 18}, + {"field": "role", "op": "IN", "const": ["admin", "mod"]} + ] + }, + { + "NOT": { + "field": "status", + "op": "=", + "var": "blocked" + } + } + ] +} + +dbf = DBFilter(fj) +sql = dbf.gen({"blocked": "inactive"}) +# 输出: +# ((age >= ${filter_const_0}$ OR role IN ${filter_const_1}$)) AND not (status = ${blocked}$) +``` + +### 示例 3:NULL 判断 + +```python +fj = { + "field": "email", + "op": "IS NOT NULL" +} + +dbf = DBFilter(fj) +sql = dbf.gen({}) +# 输出: email IS NOT NULL +``` + +--- + +## 错误处理 + +| 异常场景 | 抛出异常 | +|--------|---------| +| `AND/OR` 的值不是数组或长度小于 2 | `Exception` | +| `NOT` 的值不是对象 | `Exception` | +| 缺少必要字段(`field`, `op`) | `AssertionError` | +| 使用非法操作符 | `AssertionError` | +| `var` 在 `ns` 中不存在 | 返回 `None`(静默跳过) | + +--- + +## 占位符设计 + +采用 `${name}$` 形式作为参数占位符,优点包括: +- 不与主流 SQL 参数风格冲突(如 `%s`, `?`, `:name`) +- 易于被模板引擎识别和替换 +- 支持嵌入任意文本上下文 + +最终应配合参数绑定系统完成实际值注入。 + +--- + +## 设计特点 + +- **安全性**:所有常量均通过占位符传递,防止 SQL 注入 +- **灵活性**:支持任意深度嵌套的布尔逻辑 +- **可扩展性**:可通过继承修改 `operators` 或重写生成逻辑 +- **轻量级**:无需依赖完整 ORM,适合微服务或中间层使用 + +--- + +## 许可证 + +MIT License(假设项目允许,默认添加) + +--- + +> ✅ 提示:建议结合 Jinja2 或类似模板引擎使用生成的 SQL 片段,实现完整的动态查询构造。 \ No newline at end of file diff --git a/aidocs/mssqlor.md b/aidocs/mssqlor.md new file mode 100644 index 0000000..152ec6d --- /dev/null +++ b/aidocs/mssqlor.md @@ -0,0 +1,414 @@ +# `MsSqlor` 技术文档 + +> **模块路径**: `.mssqlor.py` +> **语言**: Python +> **数据库支持**: Microsoft SQL Server (通过 `pymssql`) +> **继承自**: `SQLor` + +--- + +## 概述 + +`MsSqlor` 是一个专为 **Microsoft SQL Server** 数据库设计的 ORM(对象关系映射)辅助类,继承自通用数据库操作基类 `SQLor`。它封装了针对 SQL Server 的 DDL 模板、数据类型映射、分页查询、元数据提取等核心功能,适用于自动化建模、数据库逆向工程和动态 SQL 构建场景。 + +该类主要用于: +- 数据库连接识别 +- 类型系统映射(数据库 ↔ 应用模型) +- 自动生成标准 SQL 语句(如查询、分页、表/字段/主键/外键/索引信息获取) + +--- + +## 依赖说明 + +```python +from .sor import SQLor +from .ddl_template_sqlserver import sqlserver_ddl_tmpl +``` + +- `SQLor`: 所有数据库适配器的抽象基类。 +- `sqlserver_ddl_tmpl`: 预定义的 SQL Server DDL 模板,用于生成建表语句。 + +--- + +## 类定义 + +```python +class MsSqlor(SQLor): + ... +``` + +--- + +## 属性与常量 + +### 1. `ddl_template` + +```python +ddl_template = sqlserver_ddl_tmpl +``` + +- **用途**: 定义创建表时使用的 DDL 模板。 +- **值来源**: 引用外部模块 `ddl_template_sqlserver.sqlserver_ddl_tmpl` +- **典型内容示例**: + ```sql + CREATE TABLE [table_name] ( + [col_name] [data_type](...) NULL, + ... + ) + ``` + +--- + +### 2. `db2modelTypeMapping` + +将 SQL Server 数据库类型映射到应用层模型字段类型。 + +| 数据库类型 | 模型类型 | +|-----------|----------| +| `bit`, `tinyint`, `smallint` | `'short'` | +| `bigint`, `int` | `'long'` | +| `decimal`, `numeric`, `money`, `smallmoney`, `real`, `float` | `'float'` | +| `date`, `datetime` | `'date'` | +| `timestamp`, `uniqueidentifier` | `'timestamp'` | +| `char` | `'char'` | +| `varchar`, `nvarchar`, `nchar`, `binary`, `varbinary` | `'str'` | +| `text`, `ntext` | `'text'` | +| `image` | `'file'` | + +> ⚠️ 注意:`uniqueidentifier` 映射为 `'timestamp'` 可能存在语义偏差,建议根据业务需求调整。 + +--- + +### 3. `model2dbTypemapping` + +将应用模型字段类型反向映射回 SQL Server 数据类型。 + +| 模型类型 | 数据库类型 | +|---------|------------| +| `'date'` | `datetime` | +| `'time'` | `date` *(注意:可能应为 `time` 或 `datetime`)* | +| `'timestamp'` | `timestamp` | +| `'str'` | `nvarchar` | +| `'char'` | `char` | +| `'short'` | `int` | +| `'long'` | `numeric` | +| `'float'` | `numeric` | +| `'text'` | `ntext` | +| `'file'` | `image` | + +> 🔍 提示:`'long'` 和 `'float'` 均映射为 `numeric`,未指定精度,实际使用中需结合上下文补充。 + +--- + +## 类方法 + +### `isMe(cls, name) -> bool` + +判断当前驱动是否匹配。 + +#### 参数 +- `name` (`str`): 数据库驱动名称,例如 `'pymssql'` + +#### 返回值 +- `True` 当且仅当 `name == 'pymssql'` +- 否则返回 `False` + +#### 示例 +```python +if MsSqlor.isMe(driver_name): + db_adapter = MsSqlor() +``` + +> ✅ 支持多数据库环境下自动识别 SQL Server 连接。 + +--- + +## 实例方法 + +### `grammar(self) -> dict` + +返回支持的 SQL 语法结构。目前仅注册 `select` 语句模板。 + +#### 返回值 +```python +{ + 'select': select_stmt # 假设全局变量或导入的 SELECT 解析器 +} +``` + +> ⚠️ 警告:`select_stmt` 未在代码中定义,可能是遗漏或外部引用,请确保其存在。 + +--- + +### `placeHolder(self, varname, pos=None) -> str` + +生成参数占位符,用于预编译 SQL。 + +#### 参数 +- `varname` (`str`): 参数名 +- `pos` (`int`, optional): 位置索引(本实现未使用) + +#### 行为逻辑 +- 若参数名为 `__mainsql__`,返回空字符串(通常用于嵌入原始 SQL) +- 其他情况统一返回 `%s` —— 符合 `pymssql` 参数化语法 + +#### 示例 +```python +cursor.execute(sql % (), data) +``` + +> ✅ 安全地防止 SQL 注入。 + +--- + +### `dataConvert(self, dataList) -> tuple` + +将输入数据标准化为可执行的元组格式。 + +#### 输入类型处理 +- **字典类型**:取 `.values()` 并转为列表 → 元组 +- **列表 of 字典**:提取每个元素的 `'value'` 字段 → 元组 + +#### 示例 +```python +# 输入1: {'a': 1, 'b': 2} → (1, 2) +# 输入2: [{'value': 1}, {'value': 2}] → (1, 2) +``` + +#### 返回值 +- 标准化的 `tuple`,可用于 `cursor.execute(..., params)` + +--- + +### `pagingSQLmodel(self) -> str` + +返回适用于 SQL Server 的分页查询模板(基于 `ROW_NUMBER()` 窗口函数)。 + +#### 模板结构 +```sql +SELECT * +FROM ( + SELECT ROW_NUMBER() OVER(ORDER BY $[sort]$) AS _row_id, page_s.* + FROM (%s) page_s +) A +WHERE _row_id >= $[from_line]$ AND _row_id < $[end_line]$ +``` + +#### 占位符说明 +| 占位符 | 含义 | +|----------------|------------------------| +| `%s` | 子查询(原 SQL) | +| `$[sort]$` | 排序字段 | +| `$[from_line]$`| 起始行号(含) | +| `$[end_line]$` | 结束行号(不含) | + +#### 使用方式 +此模板需配合字符串替换工具填充实际值,常用于构建分页接口。 + +> ✅ 兼容 SQL Server 2005+ 版本。 + +--- + +### `tablesSQL(self) -> str` + +获取当前数据库所有用户表及其标题(描述)。 + +#### 查询语句 +```sql +SELECT + LOWER(d.name) AS name, + LOWER(CAST(ISNULL(f.VALUE, d.name) AS NVARCHAR)) AS title +FROM sysobjects d +LEFT JOIN sys.extended_properties f ON d.id = f.major_id AND f.minor_id = 0 +WHERE d.xtype = 'U' +``` + +#### 字段说明 +- `name`: 表名(小写) +- `title`: 表备注 / 描述(若无则用表名代替) + +> 📌 `xtype = 'U'` 表示用户表。 + +--- + +### `fieldsSQL(self, tablename=None) -> str` + +获取指定表或全部表的字段元数据。 + +#### 查询语句摘要 +```sql +SELECT + name = LOWER(a.name), + type = b.name, + length = COLUMNPROPERTY(a.id, a.name, 'PRECISION'), + dec = ISNULL(COLUMNPROPERTY(a.id, a.name, 'Scale'), NULL), + nullable = CASE WHEN a.isnullable = 1 THEN 'yes' ELSE 'no' END, + title = LOWER(CAST(ISNULL(g.[value], a.name) AS NVARCHAR)), + table_name = LOWER(d.name) +FROM syscolumns a +... +WHERE schema_name(schema_id) = 'dbo' +``` + +#### 条件控制 +- 若传入 `tablename`,则添加 `WHERE LOWER(d.name) = 'xxx'` +- 最终按 `a.id, a.colorder` 排序(保证列顺序) + +#### 返回字段 +| 字段 | 说明 | +|-------------|--------------------------| +| `name` | 列名(小写) | +| `type` | 数据类型 | +| `length` | 精度(字符长度或数字总位数) | +| `dec` | 小数位数 | +| `nullable` | 是否可为空(yes/no) | +| `title` | 列说明(扩展属性) | +| `table_name`| 所属表名(小写) | + +> ✅ 支持从 `sys.extended_properties` 获取注释。 + +--- + +### `fkSQL(self, tablename=None) -> str` + +获取外键关系(引用其他表的主键作为本表外键)。 + +#### 查询逻辑 +查找以某表为主表(被引用)的所有外键关联。 + +```sql +SELECT + MainCol.name AS field, -- 主表列名(被引用) + oSub.name AS fk_table, -- 子表名称(引用方) + SubCol.name AS fk_field -- 子表列名(外键列) +FROM sys.foreign_keys fk +JOIN ... -- 多表连接定位主子表及列 +``` + +#### 条件 +- 可选过滤:`lower(oMain.name) = 'xxx'`,即只查某主表被哪些表引用 + +#### 示例输出 +| field | fk_table | fk_field | +|-------|----------|----------| +| id | orders | user_id | + +> 💡 适用于构建实体关系图(ERD)。 + +--- + +### `pkSQL(self, tablename=None) -> str` + +获取主键字段信息。 + +#### 查询语句 +```sql +SELECT + LOWER(a.table_name) AS table_name, + LOWER(b.column_name) AS field_name +FROM information_schema.table_constraints a +INNER JOIN information_schema.constraint_column_usage b + ON a.constraint_name = b.constraint_name +WHERE a.constraint_type = 'PRIMARY KEY' +``` + +#### 可选过滤 +- 若提供 `tablename`,追加条件:`AND LOWER(a.table_name) = 'xxx'` + +#### 输出 +每条记录表示一个主键列。 + +--- + +### `indexesSQL(self, tablename=None) -> str` + +获取索引信息(包括唯一性与包含的列)。 + +#### 查询语句 +```sql +SELECT + index_name = LOWER(IDX.Name), + index_type = IDX.is_unique, + column_name = LOWER(C.Name) +FROM sys.indexes IDX +INNER JOIN sys.index_columns IDXC ... +INNER JOIN sys.columns C ... +WHERE O.type = 'U' AND O.is_ms_shipped = 0 +``` + +#### 条件 +- 排除系统对象(`is_ms_shipped=0`) +- 可选按表名过滤:`LOWER(O.name) = 'xxx'` + +#### 输出字段 +| 字段 | 说明 | +|--------------|------------------------------| +| `index_name` | 索引名(小写) | +| `index_type` | 是否唯一(1=唯一;0=非唯一) | +| `column_name`| 索引包含的列名 | + +> ✅ 支持复合索引拆解为多行展示。 + +--- + +## 使用示例 + +### 获取所有表 +```python +db = MsSqlor(connection) +sql = db.tablesSQL() +cursor.execute(sql) +tables = cursor.fetchall() +for t in tables: + print(t['name'], t['title']) +``` + +### 获取某表字段 +```python +sql = db.fieldsSQL('users') +cursor.execute(sql) +fields = cursor.fetchall() +for f in fields: + print(f['name'], f['type'], f['nullable']) +``` + +### 分页查询构造 +```python +base_sql = "SELECT id, name FROM users" +pager = db.pagingSQLmodel() % base_sql +pager = pager.replace('$[sort]$', 'id') \ + .replace('$[from_line]$', '1') \ + .replace('$[end_line]$', '11') +cursor.execute(pager) +``` + +--- + +## 已知限制与建议 + +| 问题 | 建议 | +|------|------| +| `select_stmt` 未定义 | 确保已在作用域内定义或导入 | +| `time` 模型映射为 `date` | 应改为 `time` 或 `datetime` | +| `uniqueidentifier` 映射为 `timestamp` | 语义错误,建议新增 `'guid'` 类型 | +| 不支持模式(schema)切换 | 当前固定 `schema_name='dbo'`,如需扩展建议增加参数 | +| 缺少 `create`, `insert` 等语法支持 | 可在 `grammar()` 中逐步扩展 | + +--- + +## 总结 + +`MsSqlor` 是一个功能完整的 SQL Server 数据库适配器,具备以下优势: + +✅ 自动识别驱动 +✅ 完善的类型双向映射 +✅ 强大的元数据查询能力(表、列、主键、外键、索引) +✅ 标准化的分页模板 +✅ 支持参数化查询与安全执行 + +适合集成于 ORM 框架、数据库管理工具或低代码平台中,实现对 SQL Server 的自动化操作。 + +--- + +> 📎 文档版本:v1.0 +> 📅 更新日期:2025年4月5日 \ No newline at end of file diff --git a/aidocs/mysqlor.md b/aidocs/mysqlor.md new file mode 100644 index 0000000..fa1ea7b --- /dev/null +++ b/aidocs/mysqlor.md @@ -0,0 +1,462 @@ +以下是针对你提供的 Python 代码编写的 **Markdown 格式技术文档**,适用于项目内部文档或开发者参考手册。 + +--- + +# `MySqlor` 类技术文档 + +## 概述 + +`MySqlor` 是一个用于操作 MySQL 数据库的 SQL 构建与元数据提取类,继承自 `SQLor` 基类。它封装了与 MySQL 相关的 DDL 模板、类型映射、分页查询、表结构查询等功能,支持从数据库中提取表、字段、主键、外键、索引等元信息,并提供模型与数据库之间的类型转换机制。 + +该类主要配合 `pymysql` 驱动使用,通过动态生成 SQL 查询语句实现对数据库结构的反向工程(Reverse Engineering)和 ORM 映射支持。 + +--- + +## 继承关系 + +```python +class MySqlor(SQLor) +``` + +- 父类:`SQLor` +- 所属模块:`.sor.SQLor` + +--- + +## 导入依赖 + +```python +from appPublic.argsConvert import ArgsConvert, ConditionConvert +from .sor import SQLor +from .const import ROWS +from .ddl_template_mysql import mysql_ddl_tmpl +``` + +> - `ArgsConvert`, `ConditionConvert`: 参数与条件转换工具。 +> - `ROWS`: 分页常量定义。 +> - `mysql_ddl_tmpl`: MySQL 的 DDL 模板(如建表语句模板)。 + +--- + +## 类属性 + +### `ddl_template` + +```python +ddl_template = mysql_ddl_tmpl +``` + +- 类型:`dict` +- 描述:MySQL 的 DDL 建表语句模板,用于根据模型生成建表 SQL。 +- 来源:导入自 `ddl_template_mysql.py` 文件中的 `mysql_ddl_tmpl` 变量。 + +--- + +### `db2modelTypeMapping` + +将 MySQL 数据库字段类型映射为应用层模型类型(Model Type)。 + +| 数据库类型 | 模型类型 | +|--------------------|--------------| +| tinyint | short | +| smallint | short | +| mediumint | long | +| int | long | +| bigint | long | +| decimal | float | +| double | float | +| float | float | +| char | char | +| varchar | str | +| tinyblob / tinytext| text | +| mediumblob / mediumtext | text | +| blob / text | text | +| longblob | bin | +| longtext | text | +| binary / varbinary | text | +| date | date | +| time | time | +| datetime | datetime | +| timestamp | datestamp | +| year | short | + +> **说明**: +> - BLOB 类型统一映射为 `bin` 或 `text`,取决于大小。 +> - 时间类型做了精细化区分,例如 `timestamp` 映射为 `datestamp`,便于后续处理。 + +--- + +### `model2dbTypemapping` + +将模型类型映射回数据库字段类型。 + +| 模型类型 | 数据库类型 | +|------------|----------------| +| date | date | +| time | date | +| timestamp | timestamp | +| str | varchar | +| char | char | +| short | int | +| long | bigint | +| float | double | +| text | longtext | +| bin | longblob | +| file | longblob | + +> **注意**:`time` 类型在数据库中仍以 `date` 存储,可能需结合上下文解析;实际应考虑使用 `time` 类型优化。 + +--- + +## 类方法 + +### `isMe(cls, name)` + +判断当前数据库驱动是否匹配。 + +#### 参数 +- `name` (`str`):数据库连接驱动名称(如 `'pymysql'`) + +#### 返回值 +- `bool`:若为 `'pymysql'` 则返回 `True`,否则 `False` + +#### 示例 +```python +if MySqlor.isMe('pymysql'): + print("This is MySQL") +``` + +--- + +## 实例方法 + +### `grammar(self)` + +> ⚠️ **当前存在语法错误!** + +此方法尝试返回一个包含 `select` 语句模板的字典,但引用了未定义的变量 `select_stmt`。 + +#### 代码问题 +```python +def grammar(self): + return { + 'select': select_stmt, # ❌ NameError: name 'select_stmt' is not defined + } +``` + +#### 建议修复 +应确保 `select_stmt` 已在作用域内定义,或改为字符串形式: + +```python +def grammar(self): + return { + 'select': 'SELECT $[fields]$ FROM $[table]$ WHERE $[where]$', + } +``` + +--- + +### `placeHolder(self, varname, pos=None)` + +返回参数占位符格式(用于预编译 SQL)。 + +#### 参数 +- `varname` (`str`):变量名 +- `pos` (`int`, optional):位置参数索引(未使用) + +#### 返回值 +- 若 `varname == '__mainsql__'`,返回空字符串 `''` +- 其他情况返回标准占位符 `'%s'`(适配 `pymysql`) + +#### 用途 +用于构建参数化查询,防止 SQL 注入。 + +#### 示例 +```python +cursor.execute(sql, values) # values 将以 %s 替代 +``` + +--- + +### `dataConvert(self, dataList)` + +将输入数据标准化为元组格式,供 SQL 执行使用。 + +#### 参数 +- `dataList` (`dict` 或 `list[dict]`):待转换的数据 + +#### 返回值 +- `tuple`:提取出的值组成的元组 + +#### 行为逻辑 +- 如果是字典 → 提取 `.values()` +- 如果是字典列表 → 提取每个元素的 `'value'` 字段 +- 最终转为 `tuple` + +#### 示例 +```python +data1 = {'a': 1, 'b': 2} +# 结果: (1, 2) + +data2 = [{'value': 1}, {'value': 2}] +# 结果: (1, 2) +``` + +--- + +### `pagingSQLmodel(self)` + +返回 MySQL 分页查询模板(基于 `LIMIT offset, count`)。 + +#### 当前代码问题 +```python +def pagingSQLmodel(self): + return """...""" + return """...""" # ❌ 第二个 return 永远不会执行 +``` + +#### 正确版本(推荐) +```python +def pagingSQLmodel(self): + return """ + SELECT * FROM (%s) A + ORDER BY $[sort]$ + LIMIT $[from_line]$, $[rows]$ + """ +``` + +#### 占位符说明 +| 占位符 | 含义 | +|----------------|------------------------| +| `%s` | 子查询原始 SQL | +| `$[sort]$` | 排序字段(如 `id ASC`)| +| `$[from_line]$`| 起始行偏移量(offset) | +| `$[rows]$` | 每页记录数(limit) | + +#### 示例输出 +```sql +SELECT * FROM ( + SELECT id, name FROM users +) A +ORDER BY id DESC +LIMIT 10, 20 +``` + +--- + +### `tablesSQL(self)` + +生成查询当前数据库所有表名及其注释的 SQL。 + +#### 返回值 +- `str`:查询 `INFORMATION_SCHEMA.TABLES` 的 SQL 语句 + +#### SQL 内容 +```sql +SELECT + lower(TABLE_NAME) AS name, + lower(TABLE_COMMENT) AS title +FROM INFORMATION_SCHEMA.TABLES +WHERE TABLE_SCHEMA = 'your_db_name' +``` + +#### 动态填充 +- 使用 `self.dbdesc.get('dbname', 'unknown')` 获取数据库名 + +#### 输出字段 +| 字段 | 说明 | +|-------|----------------| +| name | 小写表名 | +| title | 表注释(小写) | + +--- + +### `fieldsSQL(self, tablename=None)` + +查询指定表的所有字段元信息。 + +#### 参数 +- `tablename` (`str`, optional):表名,若不传则查询所有表 + +#### 返回值 +- `str`:查询 `information_schema.columns` 的 SQL + +#### SQL 片段 +```sql +SELECT + lower(column_name) AS name, + data_type AS type, + CASE + WHEN character_maximum_length IS NULL THEN NUMERIC_PRECISION + ELSE character_maximum_length + END AS length, + NUMERIC_SCALE AS dec, + lower(is_nullable) AS nullable, + column_comment AS title, + lower(table_name) AS table_name +FROM information_schema.columns +WHERE lower(TABLE_SCHEMA) = 'your_dbname' + AND lower(table_name) = 'your_table' -- 可选 +``` + +#### 输出字段说明 +| 字段 | 说明 | +|--------------|----------------------------------| +| name | 字段名(小写) | +| type | 数据类型 | +| length | 长度或精度 | +| dec | 小数位数 | +| nullable | 是否可为空(yes/no) | +| title | 字段注释 | +| table_name | 所属表名(小写) | + +--- + +### `fkSQL(self, tablename=None)` + +查询外键约束信息。 + +#### 参数 +- `tablename` (`str`, optional):仅查询与该表相关的外键 + +#### 返回值 +- `str`:查询外键关系的 SQL + +#### SQL 逻辑 +联查三张系统表: +- `KEY_COLUMN_USAGE` +- `TABLES` +- `REFERENTIAL_CONSTRAINTS` + +#### 查询内容 +| 字段 | 说明 | +|----------------------|--------------------------| +| 拥有者 | Schema 名称 | +| 父表名称 | 被引用表 | +| 父表字段 | 被引用列 | +| 子表名称 | 引用表 | +| 子表字段 | 引用列 | +| 约束名 | 外键约束名称 | +| 表注释 | 子表的注释 | +| 约束更新规则 | ON UPDATE 规则 | +| 约束删除规则 | ON DELETE 规则 | + +> **过滤条件**:只返回 `REFERENCED_TABLE_NAME IS NOT NULL` 的记录(即真正的外键) + +--- + +### `pkSQL(self, tablename=None)` + +查询指定表的主键字段。 + +#### 参数 +- `tablename` (`str`):必须传入表名 + +#### 返回值 +- `str`:查询主键字段的 SQL + +#### SQL +```sql +SELECT DISTINCT column_name AS name +FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE +WHERE table_name='your_table' + AND constraint_name='PRIMARY' +``` + +#### 注意事项 +- `table_name` 会被强制转为小写 +- 使用 `DISTINCT` 避免重复 + +--- + +### `indexesSQL(self, tablename=None)` + +查询索引信息(包括唯一索引)。 + +#### 参数 +- `tablename` (`str`, optional):指定表名 + +#### 返回值 +- `str`:获取索引元数据的 SQL + +#### SQL +```sql +SELECT DISTINCT + lower(index_name) AS index_name, + CASE NON_UNIQUE + WHEN 1 THEN 'unique' + ELSE '' + END AS is_unique, + lower(column_name) AS column_name +FROM information_schema.statistics +WHERE table_schema = 'your_dbname' + AND table_name = 'your_table' -- 可选 +``` + +#### 输出字段 +| 字段 | 说明 | +|--------------|----------------------------------------| +| index_name | 索引名称(小写) | +| is_unique | 是否唯一索引(值为 `'unique'` 或空) | +| column_name | 索引包含的字段(单个字段一行) | + +> **注意**:复合索引会拆分为多行显示。 + +--- + +## 使用示例 + +```python +# 初始化 MySqlor 实例 +dbdesc = {'dbname': 'testdb'} +mysqlor = MySqlor(dbdesc=dbdesc) + +# 获取所有表 +sql_tables = mysqlor.tablesSQL() +print(sql_tables) + +# 获取某表字段 +sql_fields = mysqlor.fieldsSQL('users') +print(sql_fields) + +# 获取主键 +sql_pk = mysqlor.pkSQL('users') + +# 分页模板 +paging_model = mysqlor.pagingSQLmodel() +final_sql = paging_model % "(SELECT * FROM users)" \ + .replace("$[sort]$", "id DESC") \ + .replace("$[from_line]$", "0") \ + .replace("$[rows]$", "10") +``` + +--- + +## 已知问题与改进建议 + +| 问题 | 描述 | 建议 | +|------|------|------| +| `grammar()` 方法报错 | 引用了未定义的 `select_stmt` | 定义常量或抛出 `NotImplementedError` | +| `pagingSQLmodel()` 多余返回 | 第二个 `return` 不可达 | 删除无效行 | +| `dataConvert()` 缺少健壮性检查 | 对非 dict/list 输入无保护 | 添加类型校验和异常处理 | +| `placeHolder` 忽略 `pos` 参数 | 参数未使用 | 可移除或用于未来扩展 | + +--- + +## 总结 + +`MySqlor` 是一个功能完整的 MySQL 元数据操作类,具备以下能力: + +✅ 类型双向映射 +✅ 表/字段/主键/外键/索引信息提取 +✅ 分页 SQL 模板支持 +✅ 参数占位符兼容 `pymysql` +✅ 支持模型驱动开发(MDD / ORM) + +建议结合模板引擎(如 Jinja2)进一步提升 SQL 拼接灵活性。 + +--- + +> 📝 文档版本:v1.0 +> 📅 更新时间:2025-04-05 +> © 项目公共组件库团队 \ No newline at end of file diff --git a/aidocs/oracleor.md b/aidocs/oracleor.md new file mode 100644 index 0000000..a4b1fd3 --- /dev/null +++ b/aidocs/oracleor.md @@ -0,0 +1,363 @@ +# `Oracleor` 类技术文档 + +```markdown +# Oracle 数据库操作类:`Oracleor` + +`Oracleor` 是一个用于操作 Oracle 数据库的 Python 类,继承自 `SQLor` 基类。该类封装了与 Oracle 数据库交互所需的核心功能,包括数据类型映射、分页查询、元数据获取(表、字段、主键、外键、索引等)、占位符处理以及 SQL 模板管理。 + +--- + +## 继承关系 + +- **父类**: `SQLor` +- **所在模块**: `.sor.SQLor` +- **用途**: 提供通用 SQL 操作接口 + +## 依赖项 + +```python +from .sor import SQLor +from .ddl_template_oracle import oracle_ddl_tmpl +``` + +- `oracle_ddl_tmpl`: Oracle 特定的 DDL(数据定义语言)模板,用于生成建表语句等。 + +--- + +## 类定义 + +```python +class Oracleor(SQLor): + ddl_template = oracle_ddl_tmpl + ... +``` + +--- + +## 核心属性 + +### 1. `ddl_template` + +```python +ddl_template = oracle_ddl_tmpl +``` + +- **说明**: 使用 Oracle 专用的 DDL 模板来生成建表语句。 +- **用途**: 支持通过模型自动生成数据库表结构。 + +--- + +### 2. `db2modelTypeMapping` + +将 Oracle 数据库字段类型映射为应用层模型类型(如 Python 或 ORM 模型类型)。 + +| Oracle 类型 | 映射模型类型 | +|------------|-------------| +| `char` | `char` | +| `nchar` | `str` | +| `varchar`, `varchar2`, `nvarchar2` | `str` | +| `number`, `integer` | `long` | +| `binary_float`, `binary_double`, `float` | `float` | +| `timestamp`, `timestamp with time zone`, `timestamp with local time zone`, `interval day to second` | `timestamp` | +| `interval year to moth` | `date` | +| `clob`, `nclob` | `text` | +| `blob`, `bfile` | `file` | +| `date` | `date` | + +> ⚠️ 注意:`interval year to moth` 存在拼写错误,应为 `interval year to month`。 + +--- + +### 3. `model2dbTypemapping` + +将模型类型反向映射为 Oracle 数据库字段类型。 + +| 模型类型 | 映射 Oracle 类型 | +|---------|------------------| +| `date`, `time`, `timestamp` | `date` | +| `str` | `varchar2` | +| `char` | `char` | +| `short`, `long`, `float` | `number` | +| `text` | `nclob` | +| `file` | `blob` | + +--- + +## 类方法 + +### `@classmethod isMe(cls, name)` + +判断当前驱动是否为 Oracle 驱动。 + +#### 参数: +- `name` (str): 数据库连接使用的驱动名称。 + +#### 返回值: +- `True` 如果 `name == 'cx_Oracle'`,否则 `False`。 + +#### 示例: +```python +if Oracleor.isMe('cx_Oracle'): + print("This is Oracle database.") +``` + +--- + +## 实例方法 + +### `grammar(self)` + +返回当前数据库支持的 SQL 语法结构(目前仅支持 `select`)。 + +#### 返回值: +```python +{ + 'select': select_stmt +} +``` + +> ⚠️ 注意:`select_stmt` 未在代码中定义,可能是外部导入或遗漏。 + +--- + +### `placeHolder(self, varname, pos=None)` + +生成适用于 Oracle 的参数占位符(使用命名绑定变量)。 + +#### 参数: +- `varname` (str): 变量名。 +- `pos` (int, optional): 位置参数(未使用)。 + +#### 规则: +- 若 `varname == '__mainsql__'`,返回空字符串。 +- 否则返回 `:%s` 格式,例如 `:username`。 + +#### 示例: +```python +self.placeHolder('username') # 输出: ':username' +``` + +--- + +### `dataConvert(self, dataList)` + +将数据库原始结果列表转换为字典格式。 + +#### 参数: +- `dataList`: 可以是字典或包含字段信息的列表(如 `[{'name': 'id', 'value': 1}, ...]`)。 + +#### 返回值: +- 转换后的字典,键为字段名,值为对应值。 + +#### 示例: +```python +input_data = [{'name': 'id', 'value': 1}, {'name': 'name', 'value': 'Alice'}] +result = obj.dataConvert(input_data) +# result => {'id': 1, 'name': 'Alice'} +``` + +--- + +### `pagingSQLmodel(self)` + +返回 Oracle 分页查询的 SQL 模板(基于 `ROWNUM` 实现)。 + +#### 返回模板: +```sql +select * +from ( + select page_s.*, rownum row_id + from (%s) page_s + order by $[sort]$ +) +where row_id >= $[from_line]$ and row_id < $[end_line]$ +``` + +#### 占位符说明: +- `%s`: 子查询(原始 SQL) +- `$[sort]$`: 排序字段 +- `$[from_line]$`: 起始行号(从 1 开始) +- `$[end_line]$`: 结束行号(不包含) + +> ✅ 适用于 Oracle 9i 及以上版本。 + +--- + +### `tablesSQL(self)` + +生成查询当前用户所有表及其注释的 SQL。 + +#### 返回 SQL: +```sql +select +lower(table_name) as name, +lower(decode(comments,null,table_name,comments)) as title +from USER_TAB_COMMENTS where table_type = 'TABLE' +``` + +#### 字段说明: +- `name`: 表名(小写) +- `title`: 表标题/注释,若无注释则用表名代替 + +> 查询来源:`USER_TAB_COMMENTS` + +--- + +### `fieldsSQL(self, tablename=None)` + +查询指定表的所有字段信息。 + +#### 参数: +- `tablename` (str, optional): 表名(不区分大小写) + +#### 返回 SQL: +```sql +select lower(utc.COLUMN_NAME) name +,utc.DATA_TYPE type +,utc.DATA_LENGTH length +,utc.data_scale dec +,case when utc.nullable = 'Y' then 'yes' else 'no' end nullable +,lower(nvl(ucc.comments,utc.COLUMN_NAME)) title +,lower(utc.table_name) as table_name +from user_tab_cols utc left join USER_COL_COMMENTS ucc +on utc.table_name = ucc.table_name and utc.COLUMN_NAME = ucc.COLUMN_NAME +``` + +如果提供了 `tablename`,会添加过滤条件: +```sql +where lower(utc.table_name) = 'xxx' +``` + +#### 字段说明: +| 字段 | 含义 | +|------|------| +| `name` | 字段名(小写) | +| `type` | 数据类型 | +| `length` | 字段长度 | +| `dec` | 小数位数(scale) | +| `nullable` | 是否可为空('yes'/'no') | +| `title` | 字段注释,无则用字段名替代 | +| `table_name`| 所属表名(小写) | + +> 数据源:`user_tab_cols`, `USER_COL_COMMENTS` + +--- + +### `fkSQL(self, tablename=None)` + +查询外键约束信息。 + +#### 参数: +- `tablename` (str, optional): 限定查询某一张表的外键 + +#### 返回 SQL: +```sql +select + distinct(ucc.column_name) as field, + rela.table_name as fk_table, + rela.column_name as fk_field +from + user_constraints uc, + user_cons_columns ucc, + ( + select t2.table_name, t2.column_name, t1.r_constraint_name + from user_constraints t1, user_cons_columns t2 + where t1.r_constraint_name = t2.constraint_name + ) rela +where + uc.constraint_name = ucc.constraint_name + and uc.r_constraint_name = rela.r_constraint_name +``` + +若有表名,则追加: +```sql +and lower(uc.table_name) = 'xxx' +``` + +#### 返回字段: +- `field`: 当前表的外键字段 +- `fk_table`: 引用的目标表 +- `fk_field`: 目标表中的被引用字段 + +> 用于构建表间关联关系。 + +--- + +### `pkSQL(self, tablename=None)` + +查询主键字段信息。 + +#### 参数: +- `tablename` (str, optional): 限定查询某张表的主键 + +#### 返回 SQL: +```sql +select + lower(col.table_name) table_name, + lower(col.column_name) as field_name +from + user_constraints con, + user_cons_columns col +where + con.constraint_name = col.constraint_name + and con.constraint_type = 'P' +``` + +若有表名,则追加: +```sql +and lower(col.table_name) = 'xxx' +``` + +#### 返回字段: +- `table_name`: 表名(小写) +- `field_name`: 主键字段名(小写) + +> `constraint_type='P'` 表示主键约束。 + +--- + +### `indexesSQL(self, tablename=None)` + +查询索引信息。 + +#### 参数: +- `tablename` (str, optional): 限定查询某张表的索引 + +#### 返回 SQL: +```sql +select + lower(a.index_name) index_name, + lower(a.UNIQUENESS) is_unique, + lower(b.column_name) column_name +from user_indexes a, user_ind_columns b +where a.index_name = b.index_name +``` + +若有表名,则追加: +```sql +and lower(a.table_name) = lower('xxx') +``` + +#### 返回字段: +- `index_name`: 索引名称(小写) +- `is_unique`: 是否唯一(`unique` / `non-unique`) +- `column_name`: 索引包含的字段名(小写) + +> 支持多字段索引(每行一个字段)。 + +--- + +## 总结 + +`Oracleor` 类为 Oracle 数据库提供了完整的元数据读取和 SQL 构造能力,适用于: + +- 自动生成模型(ORM 映射) +- 可视化数据库结构工具 +- 动态 SQL 执行与分页 +- 数据迁移与同步系统 + +其设计遵循抽象与具体分离原则,便于扩展至其他数据库。 + +--- +> 📌 **备注**:部分变量(如 `select_stmt`)可能依赖外部定义,请确保上下文完整。 +``` \ No newline at end of file diff --git a/aidocs/postgresqlor.md b/aidocs/postgresqlor.md new file mode 100644 index 0000000..0ebbfeb --- /dev/null +++ b/aidocs/postgresqlor.md @@ -0,0 +1,451 @@ +以下是为提供的 Python 代码编写的 **Markdown 格式技术文档**,适用于项目文档或 API 参考手册。 + +--- + +# `PostgreSQLor` 类技术文档 + +## 概述 + +`PostgreSQLor` 是一个用于操作 PostgreSQL 数据库的数据库抽象类(继承自 `SQLor`),提供了对 PostgreSQL 特有语法、数据类型映射、元数据查询等功能的支持。该类主要用于生成 SQL 语句、处理占位符、转换数据结构以及获取表结构信息等任务。 + +该类主要配合 ORM 或代码生成器使用,支持从数据库中提取表、字段、主键、外键、索引等元数据,并将其映射为模型层可用的数据格式。 + +--- + +## 继承关系 + +```python +class PostgreSQLor(SQLor) +``` + +- 父类:`SQLor` +- 当前类:`PostgreSQLor` + +--- + +## 导入依赖 + +```python +from .sor import SQLor +from .ddl_template_postgresql import postgresql_ddl_tmpl +``` + +- `SQLor`: 基础数据库操作抽象类。 +- `postgresql_ddl_tmpl`: PostgreSQL 的 DDL 模板,用于建表语句生成。 + +--- + +## 类属性 + +### `ddl_template` + +```python +ddl_template = postgresql_ddl_tmpl +``` + +- 说明:指定 PostgreSQL 的 DDL 建表语句模板。 +- 用途:在生成 `CREATE TABLE` 等语句时引用此模板。 + +--- + +### `db2modelTypeMapping` + +将 PostgreSQL 数据库中的字段类型映射到应用模型中的类型。 + +```python +db2modelTypeMapping = { + 'smallint': 'short', + 'integer': 'long', + 'bigint': 'llong', + 'decimal': 'float', + 'numeric': 'float', + 'real': 'float', + 'double': 'float', + 'serial': 'long', + 'bigserial': 'llong', + 'char': 'char', + 'character': 'char', + 'varchar': 'str', + 'character varying': 'str', + 'text': 'text', + 'timestamp': 'timestamp', + 'date': 'date', + 'time': 'time', + 'boolean': 'char', + 'bytea': 'file' +} +``` + +| 数据库类型 | 模型类型 | +|-----------|----------| +| smallint | short | +| integer | long | +| bigint | llong | +| numeric/decimal/real/double | float | +| serial/bigserial | long/llong | +| char/character | char | +| varchar/character varying | str | +| text | text | +| timestamp | timestamp| +| date | date | +| time | time | +| boolean | char (0/1) | +| bytea | file (二进制存储) | + +> ⚠️ 注意:`boolean` 被映射为 `char`,通常以 `'0'/'1'` 表示布尔值。 + +--- + +### `model2dbTypemapping` + +将模型中的字段类型反向映射回 PostgreSQL 的数据库类型。 + +```python +model2dbTypemapping = { + 'date': 'date', + 'time': 'date', # 注意:time 映射为 date 类型?可能需确认是否合理 + 'timestamp': 'timestamp', + 'str': 'varchar', + 'char': 'char', + 'short': 'smallint', + 'long': 'integer', + 'float': 'numeric', + 'text': 'text', + 'file': 'bytea', +} +``` + +> ❗ 注意:`model2dbTypemapping['time'] = 'date'` 存在逻辑问题,应为 `'time'`,可能是 bug。 + +--- + +## 类方法 + +### `isMe(cls, name)` + +判断当前数据库驱动是否匹配 PostgreSQL。 + +#### 参数: +- `name` (`str`):数据库连接使用的驱动名称。 + +#### 返回值: +- `bool`:若 `name` 为 `'psycopg2'` 或 `'pyguass'`,返回 `True`;否则返回 `False`。 + +#### 示例: +```python +PostgreSQLor.isMe('psycopg2') # True +PostgreSQLor.isMe('sqlite3') # False +``` + +> 支持标准 PostgreSQL 驱动 `psycopg2` 和国产化替代品 `pyguass`(如达梦、高斯等兼容版)。 + +--- + +## 实例方法 + +### `grammar(self)` + +返回当前数据库支持的 SQL 语法结构定义(目前仅包含 `select`)。 + +#### 返回值: +```python +{ + 'select': select_stmt +} +``` + +> ⚠️ 注意:`select_stmt` 未在代码中定义,可能是外部导入变量,需确保上下文存在。 + +--- + +### `placeHolder(self, varname, i)` + +生成参数化查询中的占位符。 + +#### 参数: +- `varname` (`str`):参数名。 +- `i` (`int`):参数索引(当前未使用)。 + +#### 返回值: +- 若 `varname == '__mainsql__'`,返回空字符串。 +- 否则返回 `%({varname})s` 格式的命名占位符(符合 `psycopg2` 参数风格)。 + +#### 示例: +```python +obj.placeHolder('username', 0) # '%(username)s' +obj.placeHolder('__mainsql__', 0) # '' +``` + +--- + +### `dataConvert(self, dataList)` + +将输入数据统一转换为字典格式。 + +#### 参数: +- `dataList`:可以是字典或对象列表(每个元素含 `name` 和 `value` 字段)。 + +#### 返回值: +- `dict`:键为字段名,值为对应值。 + +#### 示例: +```python +data = [{'name': 'id', 'value': 1}, {'name': 'name', 'value': 'Alice'}] +converted = obj.dataConvert(data) +# 结果: {'id': 1, 'name': 'Alice'} + +obj.dataConvert({'x': 1}) # 直接返回原字典 +``` + +--- + +### `pagingSQLmodel(self)` + +返回分页 SQL 模板(⚠️ **此处有严重问题**)。 + +#### 返回值: +```sql +select * +from ( + select page_s.*, rownum row_id + from (%s) page_s + order by $[sort]$ +) +where row_id >= $[from_line]$ and row_id < $[end_line]$ +``` + +> ❌ 错误分析: +> - `rownum` 是 Oracle 的伪列,**PostgreSQL 不支持**。 +> - 正确的 PostgreSQL 分页应使用 `LIMIT` 和 `OFFSET`。 +> +> ✅ 正确写法建议: +```sql +SELECT * FROM (%s) AS page_s +ORDER BY $[sort]$ +LIMIT $[page_size]$ OFFSET $[offset]$ +``` + +> 📝 提示:此方法需要重构以适配 PostgreSQL 分页机制。 + +--- + +### `tablesSQL(self)` + +生成查询当前数据库所有表及其描述的 SQL。 + +#### 返回值(SQL): +```sql +select x.name, y.description as title +from + (select a.name, c.oid + from (select lower(tablename) as name from pg_tables where schemaname='public') a, + pg_class c + where a.name = c.relname) x +left join pg_description y + on x.oid = y.objoid and y.objsubid = '0' +``` + +#### 功能说明: +- 查询 `public` 模式下的所有表名(小写)。 +- 左连接 `pg_description` 获取表注释(`title`)。 +- `objsubid = '0'` 表示表级注释(非字段注释)。 + +#### 返回字段: +- `name`: 表名(小写) +- `title`: 表注释(可为空) + +--- + +### `fieldsSQL(self, tablename=None)` + +生成查询指定表所有字段信息的 SQL。 + +#### 参数: +- `tablename` (`str`):表名(不区分大小写) + +#### 返回值(SQL): +```sql +SELECT + a.attname AS name, + t.typname AS type, + case t.typname + when 'varchar' then a.atttypmod - 4 + when 'numeric' then (a.atttypmod - 4) / 65536 + else null + end as length, + case t.typname + when 'numeric' then (a.atttypmod - 4) % 65536 + else null + end as dec, + case a.attnotnull + when 't' then 'no' + when 'f' then 'yes' + end as nullable, + b.description AS title +FROM pg_class c, pg_attribute a + LEFT JOIN pg_description b ON a.attrelid = b.objoid AND a.attnum = b.objsubid, + pg_type t +WHERE lower(c.relname) = '%s' + AND a.attnum > 0 + AND a.attrelid = c.oid + AND a.atttypid = t.oid +ORDER BY a.attnum; +``` + +#### 字段说明: + +| 字段 | 含义 | +|------|------| +| `name` | 字段名 | +| `type` | 数据类型(如 varchar, int4) | +| `length` | 字段长度(varchar 最大长度,numeric 总位数) | +| `dec` | 小数位数(numeric 类型) | +| `nullable` | 是否可为空(`yes` 表示可空) | +| `title` | 字段注释 | + +> 🔍 技术细节: +> - `atttypmod - 4` 是 PostgreSQL 中提取 `varchar(n)` 和 `numeric(p,s)` 定义长度的方式。 +> - `attnum > 0` 排除系统列(如 OID)。 + +--- + +### `fkSQL(self, tablename=None)` + +⚠️ **注意:当前实现错误!** + +#### 当前 SQL 使用了 `user_constraints`、`user_cons_columns` —— 这些是 **Oracle** 的系统视图! + +PostgreSQL 中并不存在这些视图。 + +#### 正确实现应类似如下: + +```sql +SELECT + tc.column_name AS field, + ccu.table_name AS fk_table, + ccu.column_name AS fk_field +FROM + information_schema.table_constraints AS tc + JOIN information_schema.foreign_key_columns AS fkc + ON tc.constraint_name = fkc.constraint_name + JOIN information_schema.constraint_column_usage AS ccu + ON fkc.unique_constraint_name = ccu.constraint_name +WHERE + tc.constraint_type = 'FOREIGN KEY' + AND tc.table_schema = 'public' + AND tc.table_name = %s +``` + +> ❌ 当前方法无法在 PostgreSQL 上运行,请尽快修复。 + +--- + +### `pkSQL(self, tablename=None)` + +生成查询指定表主键字段的 SQL。 + +#### 参数: +- `tablename` (`str`):表名(不区分大小写) + +#### 返回值(SQL): +```sql +select + pg_attribute.attname as field_name, + lower(pg_class.relname) as table_name +from pg_constraint + inner join pg_class on pg_constraint.conrelid = pg_class.oid + inner join pg_attribute on pg_attribute.attrelid = pg_class.oid + and pg_attribute.attnum = pg_constraint.conkey[1] + inner join pg_type on pg_type.oid = pg_attribute.atttypid +where lower(pg_class.relname) = '%s' + and pg_constraint.contype = 'p' +``` + +> ⚠️ 限制:只取第一个主键字段(`conkey[1]`),不支持复合主键完整提取。 + +#### 建议改进: +```sql +-- 使用 unnest(conkey) 提取所有主键字段 +SELECT a.attname AS field_name +FROM pg_index ix +JOIN pg_attribute a ON a.attrelid = ix.indrelid AND a.attnum = ANY(ix.indkey) +WHERE ix.indrelid = '"%s"'::regclass AND ix.indisprimary +ORDER BY a.attnum; +``` + +--- + +### `indexesSQL(self, tablename=None)` + +生成查询指定表所有索引信息的 SQL。 + +#### 参数: +- `tablename` (`str`):表名(小写) + +#### 返回值(SQL): +```sql +select + i.relname as index_name, + case ix.INDISUNIQUE + when 't' then 'unique' + else '' + end as is_unique, + a.attname as column_name +from + pg_class t, + pg_class i, + pg_index ix, + pg_attribute a +where + t.oid = ix.indrelid + and i.oid = ix.indexrelid + and a.attrelid = t.oid + and a.attnum = ANY(ix.indkey) + and t.relkind = 'r' + and lower(t.relname) = '%s' +order by + t.relname, + i.relname +``` + +#### 返回字段: +- `index_name`: 索引名称 +- `is_unique`: 是否唯一索引('unique' 或空) +- `column_name`: 索引对应的字段名 + +> ✅ 支持多字段索引拆解显示。 + +--- + +## 总结与改进建议 + +| 项目 | 状态 | 建议 | +|------|------|------| +| 类型映射 | ✅ 完整 | 可增加数组、JSON 类型支持 | +| `placeHolder` | ✅ 兼容 psycopg2 | 良好 | +| `dataConvert` | ✅ 实用 | 建议添加类型校验 | +| `pagingSQLmodel` | ❌ 错误 | 替换为 `LIMIT/OFFSET` | +| `fkSQL` | ❌ 使用 Oracle 语法 | 重写为 `information_schema` 版本 | +| `pkSQL` | ⚠️ 仅支持单主键 | 扩展为支持复合主键 | +| `tablesSQL`, `fieldsSQL`, `indexesSQL` | ✅ 正确可用 | 可优化性能 | + +--- + +## 许可与维护 + +- 维护者:未知(请补充) +- 适用版本:PostgreSQL 9.6+ +- 依赖驱动:`psycopg2`, `pyguass`(兼容版) +- 所属模块:`.sor.postgresql` + +--- + +✅ **建议后续升级方向**: +- 使用 `information_schema` 替代部分 `pg_*` 系统表以提高可移植性。 +- 添加单元测试覆盖各 SQL 查询。 +- 引入日志记录和异常处理机制。 + +--- + +*文档版本:1.0* +*最后更新:2025-04-05* \ No newline at end of file diff --git a/aidocs/records.md b/aidocs/records.md new file mode 100644 index 0000000..7e08aef --- /dev/null +++ b/aidocs/records.md @@ -0,0 +1,202 @@ +# `Records` 类技术文档 + +## 概述 + +`Records` 是一个用于管理记录集合的 Python 类,它允许将字典形式的数据转换为指定类的实例,并提供统一的存储与迭代访问机制。默认情况下,记录会被转换为 `DictObject` 实例(来自 `appPublic.dictObject` 模块),但支持自定义类作为数据容器。 + +该类适用于需要动态处理结构化数据的场景,如数据库查询结果、API 响应数据等。 + +--- + +## 依赖 + +```python +from appPublic.dictObject import DictObject +``` + +- `DictObject`:一个字典式对象封装类,允许通过属性访问字典键值(例如 `obj.name` 而非 `obj['name']`)。 +- 若未安装或找不到此模块,请确保 `appPublic` 包已正确安装并可导入。 + +--- + +## 类定义 + +```python +class Records: + def __init__(self, klass=DictObject): + self._records = [] + self.klass = klass +``` + +### 构造函数 `__init__` + +#### 参数: +| 参数名 | 类型 | 默认值 | 说明 | +|--------|------|--------|------| +| `klass` | `type` | `DictObject` | 用于将每条记录转换成的对象类型。必须是一个可调用类型(通常是类),接受关键字参数初始化。 | + +#### 功能: +- 初始化一个空的记录列表 `_records`。 +- 设置用于实例化每条记录的目标类 `klass`。 + +> 示例:若 `klass=MyDataClass`,则每条记录会以 `MyDataClass(**record_dict)` 的方式创建。 + +--- + +## 方法说明 + +### `add(rec)` + +向记录集合中添加一条新记录。 + +#### 参数: +| 参数名 | 类型 | 说明 | +|--------|------|------| +| `rec` | `dict` | 一个字典类型的记录数据,键对应目标类的初始化参数。 | + +#### 行为: +1. 使用 `self.klass(**rec)` 将字典 `rec` 实例化为指定类的对象。 +2. 将生成的对象追加到内部列表 `self._records` 中。 + +#### 异常: +- 若 `rec` 不是字典或其键不匹配 `klass` 所需参数,可能抛出 `TypeError` 或其他初始化异常。 + +#### 示例: +```python +records = Records() +records.add({"name": "Alice", "age": 30}) +# 等效于: obj = DictObject(name="Alice", age=30) +``` + +--- + +### `get()` + +获取当前所有记录的列表。 + +#### 返回值: +- `list`:包含所有已添加记录对象的列表,顺序与添加一致。 + +#### 示例: +```python +all_records = records.get() +for r in all_records: + print(r.name) +``` + +--- + +### `__iter__()` + +使 `Records` 实例成为可迭代对象。 + +#### 返回值: +- 返回自身实例(实现了 `__next__` 的迭代器协议)。 + +#### 内部行为: +- 初始化迭代器状态变量: + - `self.start = 0` + - `self.end = len(self._records)` + +> 注意:此实现不是线程安全的,且不支持嵌套循环中的并发迭代(因为状态保存在实例上)。 + +--- + +### `__next__()` + +实现迭代器协议的下一步方法。 + +#### 返回值: +- 当还有未遍历记录时,返回下一个记录对象。 +- 遍历完成后,抛出 `StopIteration` 异常以终止迭代。 + +#### 逻辑流程: +```text +if self.start < self.end: + 返回 self._records[self.start] 并递增索引 +else: + 抛出 StopIteration +``` + +#### 示例用法: +```python +for record in records: + print(record) +``` + +--- + +## 使用示例 + +### 示例 1:使用默认 `DictObject` + +```python +from appPublic.dictObject import DictObject +from your_module import Records + +records = Records() +records.add({"id": 1, "name": "Product A"}) +records.add({"id": 2, "name": "Product B"}) + +for r in records: + print(r.id, r.name) +# 输出: +# 1 Product A +# 2 Product B +``` + +### 示例 2:使用自定义类 + +```python +class User: + def __init__(self, name, email): + self.name = name + self.email = email + + def __repr__(self): + return f"" + +users = Records(klass=User) +users.add({"name": "Bob", "email": "bob@example.com"}) +users.add({"name": "Charlie", "email": "charlie@example.com"}) + +print(users.get()) +# 输出: [, ] +``` + +--- + +## 注意事项 + +1. **非线程安全** + `__iter__` 和 `__next__` 将迭代状态(`start`, `end`)存储在实例上,因此多个同时进行的迭代会导致状态冲突。 + +2. **不可重入迭代** + 在一次迭代未完成前再次调用 `iter()` 会覆盖原有状态。建议改用 `iter(records.get())` 获取独立迭代器。 + +3. **性能提示** + `get()` 返回的是原始列表引用,修改该列表会影响内部状态。如需保护数据,建议返回副本:`return self._records[:]` + +4. **扩展建议** + 可考虑实现 `__len__`、`__getitem__` 支持更多序列操作,例如: + ```python + def __len__(self): + return len(self._records) + + def __getitem__(self, idx): + return self._records[idx] + ``` + +--- + +## 版本历史 + +| 版本 | 修改内容 | +|------|---------| +| 1.0 | 初始版本,支持基本记录管理与迭代功能 | + +--- + +## 许可证 + +请参考项目整体许可证文件。 \ No newline at end of file diff --git a/aidocs/runsql.md b/aidocs/runsql.md new file mode 100644 index 0000000..3770d37 --- /dev/null +++ b/aidocs/runsql.md @@ -0,0 +1,182 @@ +# SQL 执行工具技术文档 + +```markdown +# SQL 执行工具(`sql_runner.py`) + +一个基于 Python 3 的异步 SQL 脚本执行工具,支持从文件读取 SQL 并在指定数据库中执行,同时允许传入命名参数用于 SQL 模板替换。 + +--- + +## 概述 + +该脚本提供了一个命令行接口,用于执行存储在文件中的 SQL 语句。它通过 `sqlor.dbpools` 模块连接数据库池,并使用 `asyncio` 实现异步执行。支持动态变量注入(通过 `k=v` 参数),适用于自动化部署、数据初始化或批量任务场景。 + +--- + +## 依赖说明 + +- **Python 版本**:Python 3.7+ +- **第三方模块**: + - `sqlor.dbpools.runSQL`:用于异步执行 SQL 的数据库操作模块。 + - 其他内置模块:`sys`, `codecs`, `asyncio` + +> ⚠️ 注意:`ProgramPath`, `getConfig`, `DBPools` 等类/函数未在代码中定义,推测属于项目其他模块(如配置管理组件)。需确保这些符号在运行环境中已正确定义并可导入。 + +--- + +## 使用方式 + +### 命令格式 + +```bash +python3 sql_runner.py [k1=v1] [k2=v2] ... +``` + +### 参数说明 + +| 参数 | 描述 | +|------|------| +| `path` | 配置文件路径或程序根路径(用于加载配置) | +| `dbname` | 在配置中定义的数据库连接名称 | +| `sqlfile` | 包含 SQL 语句的文件路径(UTF-8 编码) | +| `k=v` (可选) | 动态键值对参数,可用于 SQL 中的模板替换 | + +### 示例调用 + +```bash +python3 sql_runner.py ./config prod_db ./scripts/init.sql \ + table_name=users \ + batch_size=1000 \ + debug_mode=true +``` + +上述命令将: +- 加载 `./config` 下的配置; +- 连接名为 `prod_db` 的数据库; +- 读取并执行 `init.sql` 文件中的 SQL; +- 提供三个变量供 SQL 内部引用:`table_name`, `batch_size`, `debug_mode`。 + +--- + +## 核心功能 + +### 1. 初始化 (`appinit`) + +```python +def appinit(): + if len(sys.argv) < 4: + print(f'usage:\n {sys.argv[0]} path dbname sqlfile [k=v ...] \n') + sys.exit(1) + p = ProgramPath() + if len(sys.argv) > 1: + p = sys.argv[1] + config = getConfig(p) + DBPools(config.databases) +``` + +#### 功能描述: +- 检查命令行参数是否至少包含 `path`, `dbname`, `sqlfile`。 +- 获取程序路径 `p`,默认使用 `ProgramPath()` 对象(需外部实现)。 +- 使用 `getConfig(p)` 加载配置对象。 +- 初始化数据库连接池 `DBPools(config.databases)`。 + +> ✅ 成功后,全局数据库池准备就绪,可供后续 SQL 执行使用。 + +--- + +### 2. 异步 SQL 执行 (`run`) + +```python +async def run(ns): + with codecs.open(sys.argv[3], 'r', 'utf-8') as f: + sql = f.read() + await runSQL(sys.argv[2], sql, ns) +``` + +#### 功能描述: +- 异步函数,使用 `codecs.open` 安全读取 UTF-8 编码的 SQL 文件。 +- 将整个文件内容作为字符串加载到内存。 +- 调用 `runSQL(dbname, sql, namespace)` 执行 SQL,其中: + - `dbname`: 来自 `sys.argv[2]` + - `sql`: 从文件读取的内容 + - `ns`: 用户传入的键值参数字典 + +> 💡 支持 SQL 模板语法(例如 `${key}` 或 `:key`,具体取决于 `runSQL` 实现),实现动态 SQL 构造。 + +--- + +### 3. 主程序入口 + +```python +if __name__ == '__main__': + ns = {} + for x in sys.argv[3:]: + try: + k,v = x.split('=') + ns.update({k:v}) + except Exception as e: + print(x, 'key-value pair expected') + print(e) + + appinit() + loop = asyncio.get_event_loop() + loop.run_until_complete(run(ns)) +``` + +#### 流程说明: +1. 解析从第 4 个参数开始的所有 `k=v` 形式输入,构建成字典 `ns`。 +2. 若解析失败(如缺少 `=`),输出错误信息但不中断程序。 +3. 调用 `appinit()` 初始化应用环境和数据库池。 +4. 获取事件循环并运行异步任务 `run(ns)`,等待其完成。 + +--- + +## SQL 文件要求 + +- 必须为 **UTF-8 编码** +- 可以包含多条 SQL 语句(由 `runSQL` 决定是否支持批量执行) +- 可包含占位符,如: + ```sql + CREATE TABLE IF NOT EXISTS ${table_name}; + INSERT INTO logs VALUES (:msg, :ts); + ``` + 占位符实际语法取决于 `runSQL` 的实现机制(如字符串替换、参数化查询等) + +--- + +## 错误处理与日志 + +- 参数不足时打印帮助信息并退出(状态码 1) +- `k=v` 解析异常会打印警告,但不会终止程序 +- SQL 执行过程中的异常由 `runSQL` 处理,建议其内部进行日志记录或抛出可捕获异常 + +--- + +## 注意事项 + +1. **安全性提醒**: + - 如果 SQL 使用字符串插值而非参数化查询,存在 SQL 注入风险,请谨慎使用用户输入。 + - 推荐 `runSQL` 使用安全的模板引擎或预编译机制。 + +2. **性能提示**: + - 整个 SQL 文件一次性读入内存,不适合超大文件(GB 级别)。 + - 如需流式处理或分批执行,需扩展此脚本。 + +3. **配置依赖**: + - `getConfig` 和 `DBPools` 的行为高度依赖项目结构,请确认配置格式正确(如 JSON/YAML 配置文件结构)。 + +--- + +## 扩展建议 + +- 添加 `-h/--help` 参数支持 +- 增加日志模块替代 `print` +- 支持 `.sql` 文件通配符批量执行 +- 增加 dry-run 模式预览 SQL +- 返回执行结果统计(影响行数、耗时等) + +--- +``` + +> 📝 文档版本:v1.0 +> 最后更新:2025-04-05 \ No newline at end of file diff --git a/aidocs/sor.md b/aidocs/sor.md new file mode 100644 index 0000000..c159050 --- /dev/null +++ b/aidocs/sor.md @@ -0,0 +1,595 @@ +# SQLor 技术文档 + +`SQLor` 是一个用于简化数据库操作的异步 Python 数据库访问类,支持参数化 SQL 执行、分页查询、CRUD 操作(Create, Read, Update, Delete)、元数据获取、动态 SQL 构建等功能。它设计为可扩展,适用于多种数据库后端。 + +--- + +## 目录 + +1. [概述](#概述) +2. [依赖与环境要求](#依赖与环境要求) +3. [核心功能](#核心功能) +4. [主要类与方法说明](#主要类与方法说明) + - [`db_type_2_py_type`](#db_type_2_py_type) + - [`SQLorException`](#sqlorexception) + - [`findNamedParameters`](#findnamedparameters) + - [`uniParams`](#uniparams) + - [`readsql`](#readsql) + - [`SQLor` 类](#sqlor-类) + - 初始化 + - 元数据管理 + - 连接与游标控制 + - SQL 处理与转换 + - 查询执行 + - 分页与排序 + - 聚合操作(Pivot) + - 表结构信息获取 + - DDL 与表定义操作 + - CRUD 操作(C/U/R/D) +5. [使用示例](#使用示例) +6. [异常处理](#异常处理) +7. [配置说明](#配置说明) + +--- + +## 概述 + +`SQLor` 提供了一个统一接口来执行 SQL 查询和命令,并通过命名参数 `${var}` 支持模板化 SQL。其主要特点包括: + +- 异步/同步双模式支持。 +- 参数自动替换与安全绑定。 +- 自动识别 `SELECT`, `INSERT/UPDATE/DELETE`, `DDL` 类型并返回相应结果。 +- 支持分页、排序、条件过滤。 +- 内置对表结构(字段、主键、外键、索引)的元数据查询。 +- 支持自定义函数注册钩子(before/after 钩子)。 +- 可扩展的类型转换机制。 +- 简化的 CRUD 接口(I/C/R/U/D)。 + +--- + +## 依赖与环境要求 + +### 第三方依赖 + +```python +from appPublic.myImport import myImport +from appPublic.dictObject import DictObject +from appPublic.unicoding import uDict +from appPublic.myTE import MyTemplateEngine +from appPublic.objectAction import ObjectAction +from appPublic.argsConvert import ArgsConvert, ConditionConvert +from appPublic.registerfunction import RegisterFunction +from appPublic.log import info, exception, debug +``` + +> ⚠️ 注意:这些模块属于 `appPublic` 包,需确保已安装或项目中包含该包。 + +### Python 内置模块 + +- `os` +- `sys` +- `decimal` +- `datetime` +- `codecs` +- `re` +- `json` +- `traceback` + +### 环境变量 + +```python +os.environ['NLS_LANG'] = 'SIMPLIFIED CHINESE_CHINA.UTF8' +``` + +> 仅在 Oracle 数据库环境下需要设置 NLS 编码以支持中文 UTF-8。 + +--- + +## 核心功能 + +| 功能 | 描述 | +|------|------| +| 参数化 SQL | 使用 `${var}` 占位符进行变量注入 | +| 异步支持 | 支持 `async/await` 模式运行 SQL | +| 分页查询 | 自动生成分页 SQL(需子类实现模型) | +| 排序与过滤 | 支持动态排序字段和条件表达式 | +| 元数据提取 | 获取表、字段、主键、索引等结构信息 | +| CRUD 封装 | 提供 `C()`(创建), `R()`(读取), `U()`(更新), `D()`(删除) 方法 | +| 回调机制 | 支持逐行处理查询结果 | +| 类型转换 | 自动将数据库类型转为 Python 原生类型 | + +--- + +## 主要类与方法说明 + +### `db_type_2_py_type(o)` + +将特定数据库类型转换为 Python 原生类型。 + +#### 参数 +- `o`: 数据库字段值 + +#### 返回值 +| 输入类型 | 输出 | +|--------|-------| +| `decimal.Decimal` | `float` | +| `datetime.datetime` | 字符串格式 `'YYYY-MM-DD HH:MM:SS'` | +| `datetime.date` | `'YYYY-MM-DD'` | +| 其他 | 原值 | + +#### 示例 +```python +db_type_2_py_type(decimal.Decimal('3.14')) → 3.14 +``` + +--- + +### `SQLorException` + +自定义异常类,封装 SQL 执行错误。 + +> ❗ 当前代码存在拼写错误:`__int__` 应为 `__init__`,且 `supper` 应为 `super`。 + +#### 属性 +- `response`: `"error"` +- `errtype`: `"SQLor"` +- `errmsg`: 异常消息字符串 + +#### 示例输出 +``` +errtype:SQLor,errmsg=database error... +``` + +> ✅ **建议修复**: +```python +class SQLException(Exception): + def __init__(self, **kvs): + super(SQLException, self).__init__(**kvs) + self.dic = { + 'response': 'error', + 'errtype': 'SQLor', + 'errmsg': str(self) + } + + def __str__(self): + return 'errtype:%s,errmsg=%s' % (self.dic['errtype'], self.dic['errmsg']) +``` + +--- + +### `setValues(params, ns)` + +从命名空间 `ns` 或系统环境变量中查找参数值。 + +#### 参数 +- `params`: 参数名(字符串) +- `ns`: 命名空间字典 + +#### 返回 +优先从 `ns` 中取值,若无则尝试 `os.getenv(params)`。 + +--- + +### `findNamedParameters(sql)` + +解析 SQL 字符串中的所有 `${...}` 形式的命名参数。 + +#### 示例 +```python +sql = "SELECT * FROM user WHERE id = ${uid}$ AND name = ${uname}$" +findNamedParameters(sql) +# 返回 ['${uid}$', '${uname}$'] +``` + +--- + +### `uniParams(params1)` + +去重保留唯一参数名列表。 + +#### 示例 +```python +uniParams(['${a}$', '${b}$', '${a}$']) → ['${a}$', '${b}$'] +``` + +--- + +### `readsql(fn)` + +读取指定文件路径的 SQL 文件内容(UTF-8 编码)。 + +#### 参数 +- `fn`: 文件路径 + +#### 返回 +文件内容字符串。 + +--- + +## `SQLor` 类 + +### 初始化 `__init__` + +#### 参数 +| 参数 | 默认值 | 说明 | +|------|--------|------| +| `dbdesc` | None | 数据库描述字典,至少含 `dbname` | +| `sqltp`, `sqlts` | `$[`, `]$` | 模板占位符开始/结束符号 | +| `sqlvp`, `sqlvs` | `${`, `}$` | 变量占位符开始/结束符号 | + +#### 初始化行为 +- 设置连接状态(`conn`, `cur`) +- 初始化元数据缓存 `metadatas` +- 创建 `ConditionConvert` 实例用于条件表达式处理 + +--- + +### 元数据管理 + +#### `setMeta(tablename, meta)` +保存表的元数据到内存缓存。 + +#### `getMeta(tablename)` +获取指定表的缓存元数据。 + +#### `removeMeta(tablename)` +清除指定表的元数据缓存。 + +--- + +### 连接与游标控制 + +#### `setCursor(async_mode, conn, cur)` +绑定数据库连接和游标对象。 + +#### `getConn()` +返回当前数据库连接对象。 + +#### `cursor()` +返回当前游标对象。 + +--- + +### 类型转换支持 + +#### `setConvertFunction(typ, func)` +注册用户自定义类型转换函数。 + +#### `convert(typ, value)` +调用注册的转换函数处理值。 + +--- + +### SQL 类型判断 + +#### `getSqlType(sql)` +判断 SQL 语句类型。 + +| 类型 | 条件 | +|------|------| +| `"qry"` | 以 `SELECT` 开头 | +| `"dml"` | `INSERT`, `UPDATE`, `DELETE` | +| `"ddl"` | 其他(如 `CREATE`, `DROP`) | + +--- + +### SQL 模板处理 + +#### `maskingSQL(org_sql, NS)` +将命名参数 `${var}$` 替换为 `?` 并生成参数列表。 + +##### 步骤 +1. 先替换 `$[...]$` 模板片段(如条件块) +2. 提取 `${var}$` 参数名 +3. 替换为 `?` 占位符 +4. 返回 `(标记后的SQL, 参数值列表)` + +##### 特殊变量 +- `__mainsql__`: 不参与参数绑定,用于嵌套 SQL 注入 + +--- + +### 执行方法 + +#### `execute(sql, value, callback, **kwargs)` +执行单条 SQL,支持回调逐行处理结果。 + +##### 流程 +- 调用 `runVarSQL` 执行 SQL +- 若是查询 (`qry`) 且有回调,则逐行调用 `callback(DictObject(row))` +- 若是 DML,标记 `dataChanged=True` + +#### `executemany(sql, values)` +批量执行 SQL(适用于 `INSERT` 等) + +--- + +### 分页与排序 + +#### `sortSQL(sql, NS)` +添加 `ORDER BY` 子句。 + +- 支持 `NS['sort']` 为字符串或列表 + +#### `pagingSQL(sql, paging, NS)` +构建分页 SQL,需配合 `pagingSQLmodel()` 使用。 + +> 默认为空,需由子类重写提供具体数据库分页语法(如 MySQL 的 `LIMIT OFFSET` 或 Oracle 的 `ROWNUM`) + +##### 参数 +- `pagename`: 页码参数名(默认 `'page'`) +- `rowsname`: 每页行数(默认 `'rows'`) +- `sortname`: 排序字段(可选) + +##### 示例 NS +```python +{ + 'page': 2, + 'rows': 20, + 'sort': 'id desc' +} +``` + +#### `recordCnt(sql)` +包装 SQL 查询总记录数: + +```sql +SELECT COUNT(*) rcnt FROM (your_sql) rowcount_table +``` + +--- + +### 过滤器支持 + +#### `filterSQL(sql, filters, NS)` +将一组条件过滤器合并进原 SQL。 + +每个 filter 是一个带 `${}` 的模板字符串,若其中变量未在 `NS` 中存在,则忽略此条件(替换为 `1=1`)。 + +最终生成: +```sql +SELECT * FROM (original_sql) filter_table WHERE f1 AND f2 ... +``` + +--- + +### Pivot 表格转换 + +#### `pivotSQL(tablename, rowFields, columnFields, valueFields)` +生成透视表 SQL。 + +##### 逻辑 +1. 查询 `columnFields` 的所有唯一值作为列头 +2. 使用 `CASE WHEN` 构造每列聚合 +3. 使用 `SUM` 聚合数值字段 + +##### 示例输出 +```sql +SELECT dept, + SUM(salary_0) salary_0, -- 北京 + SUM(salary_1) salary_1 -- 上海 +FROM ( + SELECT dept, + CASE WHEN city='北京' THEN salary ELSE 0 END AS salary_0, + CASE WHEN city='上海' THEN salary ELSE 0 END AS salary_1 + FROM employees +) GROUP BY dept; +``` + +#### `pivot(desc, ...)` +执行 pivot 查询并返回结果列表。 + +--- + +### 元数据查询方法 + +#### `tables()` +返回数据库中所有表的列表。 + +#### `indexes(tablename)` +返回某表的所有索引信息(需子类实现 `indexesSQL()`) + +#### `fields(tablename)` +返回字段列表,包含名称、类型(映射后),例如: +```python +[ + {"name": "id", "type": "int"}, + {"name": "name", "type": "string"} +] +``` + +#### `primary(tablename)` +返回主键字段列表。 + +#### `fkeys(tablename)` +返回外键关系。 + +#### `getTableDesc(tablename)` +综合获取表的完整描述(summary + fields + indexes),并缓存至 `metadatas`。 + +--- + +### DDL 与表操作 + +#### `createTable(tabledesc)` +使用模板引擎渲染建表语句并执行。 + +依赖 `self.ddl_template` 和 `MyTemplateEngine`。 + +--- + +### 事务控制 + +#### `commit()` +提交事务,重置 `dataChanged=False` + +#### `rollback()` +回滚事务 + +--- + +### CRUD 接口(I/C/R/U/D) + +遵循 RESTful 风格简写: + +#### `I(tablename)` +等价于 `getTableDesc()` — 获取表结构定义。 + +#### `C(tablename, ns)` +插入新记录。 + +- 自动提取 `ns` 中存在于表字段的部分 +- 触发 `before` / `after` 钩子函数 +- 生成 INSERT 语句 + +#### `R(tablename, ns, filters=None)` +读取记录。 + +- 若传入 `page` 参数 → 返回分页 `{total, rows}` +- 否则返回匹配行列表 +- 支持简单等值条件或复杂 `DBFilter` + +#### `U(tablename, ns)` +更新记录。 + +- 使用主键做 WHERE 条件 +- 更新非主键字段 +- 支持钩子函数 + +#### `D(tablename, ns)` +删除记录。 + +- 使用主键匹配删除 +- 支持钩子函数 + +--- + +## 使用示例 + +### 1. 初始化 SQLor 实例 + +```python +dbdesc = { + 'dbname': 'mydb' +} +sqlor = SQLor(dbdesc=dbdesc) +sqlor.setCursor(False, conn, cursor) # 同步模式 +``` + +### 2. 执行参数化查询 + +```python +sql = "SELECT * FROM users WHERE age > ${min_age}$" +result = [] +await sqlor.execute(sql, {'min_age': 18}, lambda rec: result.append(rec)) +``` + +### 3. 分页查询 + +```python +desc = { + "sql_string": "SELECT id, name FROM users", + "sortfield": "name" +} +ns = {"page": 1, "rows": 10} +data = await sqlor.runSQLPaging(desc, ns) +# → {"total": 100, "rows": [...]} +``` + +### 4. CRUD 操作 + +```python +# 创建 +await sqlor.C("users", {"name": "Alice", "age": 25}) + +# 读取(分页) +res = await sqlor.R("users", {"page": 1, "rows": 10}) + +# 更新 +await sqlor.U("users", {"id": 1, "age": 26}) + +# 删除 +await sqlor.D("users", {"id": 1}) +``` + +### 5. 获取表结构 + +```python +desc = await sqlor.I("users") +print(desc['fields']) +``` + +--- + +## 异常处理 + +所有 SQL 执行异常都会被捕获并记录日志: + +```python +exception(f"{markedSQL=},{datas=}, {e=}, {fe=}") +raise e +``` + +推荐外部捕获 `Exception` 或自定义 `SQLException`。 + +--- + +## 配置说明 + +### 占位符配置 + +| 符号 | 默认 | 用途 | +|------|------|------| +| `sqltp` / `sqlts` | `$[` / `]$` | 控制结构模板(如 IF) | +| `sqlvp` / `sqlvs` | `${` / `}$` | 变量参数占位符 | + +示例: +```sql +SELECT * FROM t WHERE status = ${status}$ +$[ IF active ]$ AND active = 1 $[ ENDIF ]$ +``` + +### 分页模型扩展 + +子类应重写 `pagingSQLmodel()` 提供数据库特定的分页语法。 + +例如 MySQL 子类: +```python +def pagingSQLmodel(self): + return "SELECT * FROM (%s) t LIMIT ${rows}$ OFFSET ${from_line}$" +``` + +Oracle 示例: +```python +def pagingSQLmodel(self): + return """ + SELECT * FROM ( + SELECT a.*, ROWNUM rn FROM ( + %s ORDER BY ${sort}$ + ) a WHERE ROWNUM <= ${end_line}$ + ) WHERE rn > ${from_line}$ + """ +``` + +--- + +## 总结 + +`SQLor` 是一个轻量级但功能完整的数据库抽象层,适合用于 Web API 后端、ETL 工具或管理后台的数据访问组件。其优势在于: + +✅ 清晰的 CRUD 接口 +✅ 支持异步高性能场景 +✅ 安全的参数绑定 +✅ 易于扩展不同数据库方言 + +> ⚠️ **待改进点**: +- 修复 `SQLException.__init__` 错误 +- `commit()` 中 `self.datachanged` 应为 `self.dataChanged` +- `pagingdata()` 中 `cnt_desc` 未定义 +- `setMeta` 中 `getMeta(self.tablename)` 应为 `self.getMeta(...)` + +--- + +📝 **版本**: v1.0 +📅 **最后更新**: 2025-04-05 +👨‍💻 **作者**: Auto-generated Documentation Tool \ No newline at end of file diff --git a/aidocs/sqlite3or.md b/aidocs/sqlite3or.md new file mode 100644 index 0000000..e0e2c01 --- /dev/null +++ b/aidocs/sqlite3or.md @@ -0,0 +1,328 @@ +以下是为提供的 `SQLite3or` 类编写的 **Markdown 格式技术文档**,适用于项目开发文档、API 说明或团队协作参考。 + +--- + +# `SQLite3or` 技术文档 + +## 概述 + +`SQLite3or` 是一个基于 `SQLor` 基类的数据库适配器类,专用于 **SQLite3** 数据库的操作。它实现了对 SQLite 数据库的元数据查询(如表、字段、主键、索引等)以及 SQL 片段生成的支持,主要用于 ORM 或通用数据库访问层中识别和操作 SQLite 数据库结构。 + +该类通过映射数据库类型与模型类型,提供统一的数据抽象接口。 + +继承自:`.sor.SQLor` + +--- + +## 类定义 + +```python +class SQLite3or(SQLor): +``` + +--- + +## 类属性 + +### `db2modelTypeMapping` + +将 SQLite 数据库字段类型映射为应用层模型字段类型。 + +| 数据库类型 | 模型类型 | +|-----------|----------| +| `text` | `str` | +| `blob` | `file` | +| `int` | `long` | +| `integer` | `long` | +| `real` | `float` | + +> 示例:`'TEXT' -> 'str'`, `'BLOB' -> 'file'` + +### `model2dbTypemapping` + +将模型字段类型映射回 SQLite 的数据库类型。 + +| 模型类型 | 数据库类型 | +|-----------|------------| +| `date` | `text` | +| `time` | `text` | +| `timestamp`| `text` | +| `str` | `text` | +| `char` | `text` | +| `short` | `int` | +| `long` | `int` | +| `float` | `real` | +| `text` | `text` | +| `file` | `blob` | + +> 用于建表或类型转换时使用。 + +--- + +## 类方法 + +### `isMe(name)` + +判断当前适配器是否匹配指定数据库类型名称。 + +#### 参数: +- `name` (`str`):数据库标识名。 + +#### 返回值: +- `bool`:当 `name == 'sqlite3'` 时返回 `True`,否则 `False`。 + +#### 示例: +```python +SQLite3or.isMe('sqlite3') # True +SQLite3or.isMe('mysql') # False +``` + +--- + +## 实例方法 + +### `placeHolder(varname, pos=None)` + +返回 SQL 占位符格式,SQLite 使用 `?` 作为参数占位符。 + +#### 参数: +- `varname` (`str`):变量名。 +- `pos` (`int`, 可选):位置索引(未使用)。 + +#### 返回值: +- `str`:若 `varname` 为 `'__mainsql__'` 返回空字符串;其余情况返回 `'?'`。 + +#### 示例: +```python +db.placeHolder('username') # '?' +db.placeHolder('__mainsql__') # '' +``` + +--- + +### `dataConvert(dataList)` + +将输入数据标准化为元组形式,供执行 SQL 时传参。 + +#### 参数: +- `dataList` (`dict` 或 `list[dict]`):待处理的数据。 + +#### 行为: +- 若是字典:取其所有 `.values()`。 +- 若是字典列表:提取每个元素的 `'value'` 字段。 + +#### 返回值: +- `tuple`:标准化后的值元组。 + +#### 示例: +```python +db.dataConvert({'a': 1, 'b': 2}) +# -> (1, 2) + +db.dataConvert([{'value': 1}, {'value': 2}]) +# -> (1, 2) +``` + +--- + +### `pagingSQLmodel()` + +获取分页 SQL 模板,支持 `$[sort]$`, `$[from_line]$`, `$[end_line]$` 动态替换。 + +#### 返回值: +- `str`:分页查询模板字符串。 + +```sql +select * from (%s) order by $[sort]$ limit $[from_line]$,$[end_line]$ +``` + +> `%s` 将被子查询填充,`$[]$` 为后续模板引擎替换标记。 + +--- + +### `tablesSQL()` + +生成查询数据库中所有表的 SQL 语句。 + +#### 返回值: +```sql +select name, tbl_name as title from sqlite_master where upper(type) = 'TABLE' +``` + +> 查询 `sqlite_master` 系统表,获取表名及其别名(title)。 + +--- + +### `fieldsSQL(tablename)` + +**(注意:当前实现有缺陷)** + +本应返回用于获取表结构信息的 SQL,但实际未正确构造语句。 + +#### 当前代码: +```python +return sqlcmd # sqlcmd 为空字符串 +``` + +#### 正确预期(建议修复): +```python +return f"PRAGMA table_info('{tablename}')" +``` + +> 用于获取表字段详情(cid, name, type, notnull, dflt_value, pk) + +--- + +### `fields(tablename)` + +解析指定表的所有字段信息,并进行标准化处理。 + +#### 参数: +- `tablename` (`str`):表名。 + +#### 处理流程: +1. 调用 `fieldsSQL(tablename)` 获取结构查询 SQL。 +2. 执行 SQL 并收集结果。 +3. 使用正则提取字段类型的长度与精度(如 `VARCHAR(50,2)`)。 +4. 映射数据库类型 → 模型类型(通过 `db2modelTypeMapping`)。 +5. 统一字段名为小写,添加 `title` 字段。 + +#### 返回值: +`list[dict]`,每个字段对象包含: + +| 键 | 类型 | 描述 | +|----------|--------|------| +| `name` | str | 字段名(小写) | +| `type` | str | 模型类型(如 str/long/file) | +| `length` | int | 类型长度(括号内第一个数字) | +| `dec` | int | 小数位数(第二个数字) | +| `title` | str | 别名,默认等于字段名 | +| `pk` | int | 是否为主键(来自 PRAGMA 输出) | + +> ⚠️ 注意:由于 `fieldsSQL` 未正确实现,此方法在运行时会出错。 + +--- + +### `fkSQL(tablename)` + +获取外键相关信息的 SQL 语句。 + +#### 当前实现: +```python +return "" +``` + +> **尚未实现**,SQLite 支持外键需开启 `PRAGMA foreign_keys=ON`,可通过 `PRAGMA foreign_key_list(table)` 查询。 + +--- + +### `fkeys(tablename)` + +返回指定表的外键信息列表。 + +#### 返回值: +- `[]`:空列表(表示暂不支持外键提取)。 + +> 预期未来可返回类似: +```python +[ + { + "field": "user_id", + "ref_table": "users", + "ref_field": "id" + } +] +``` + +--- + +### `pkSQL(tablename)` + +获取主键信息的 SQL 语句。 + +#### 当前实现: +```python +return "" +``` + +> **未实现**,推荐使用 `PRAGMA table_info(tablename)` 中的 `pk` 字段判断。 + +--- + +### `primary(tablename)` + +从字段信息中提取主键字段。 + +#### 参数: +- `tablename` (`str`) + +#### 实现逻辑: +1. 调用 `self.fields(tablename)` 获取所有字段。 +2. 过滤出 `pk == 1` 的字段。 +3. 返回字段名列表。 + +#### 返回值: +`list[dict]`,例如: +```python +[{'field': 'id'}, {'field': 'code'}] +``` + +> ⚠️ 依赖 `fields()` 正常工作。 + +--- + +### `indexesSQL(tablename=None)` + +生成查询索引信息的 SQL。 + +#### 参数: +- `tablename` (`str`, 可选):若指定,则只查该表的索引。 + +#### 返回值: +```sql +select * from sqlite_master +where lower(type) = 'index' +``` + +如果传入 `tablename`,附加条件: +```sql +and lower(tbl_name)='xxx' +``` + +> 查询系统表 `sqlite_master` 中所有索引定义。 + +--- + +## 已知问题与改进建议 + +| 方法 | 问题描述 | 建议改进 | +|------|---------|--------| +| `fieldsSQL` | 返回空字符串导致异常 | 应返回 `PRAGMA table_info('%s') % tablename` | +| `fkSQL` / `fkeys` | 未实现外键支持 | 可用 `PRAGMA foreign_key_list(tablename)` 实现 | +| `pkSQL` | 未实现 | 可删除或补充逻辑 | +| 类型解析正则 | 支持 `(len,dec)` 格式,但 SQLite 不常用 | 可简化或增强兼容性 | + +--- + +## 示例用法(伪代码) + +```python +db = SQLite3or(connection) +if db.isMe('sqlite3'): + tables = db.tables() + for t in tables: + print(f"Table: {t['name']}") + fields = db.fields(t['name']) + for f in fields: + print(f" Field: {f['name']} ({f['type']})") +``` + +--- + +## 总结 + +`SQLite3or` 提供了一个轻量级的 SQLite 元数据访问框架,适合集成到动态 ORM 或低代码平台中。尽管部分功能尚未完成,但核心的字段映射、分页、表枚举已具备良好结构,可通过完善 SQL 查询语句进一步提升实用性。 + +--- + +📌 **维护建议**:优先修复 `fieldsSQL()` 和补充 `PRAGMA` 相关调用以确保元数据读取准确。 \ No newline at end of file diff --git a/aidocs/version.md b/aidocs/version.md new file mode 100644 index 0000000..9e9038e --- /dev/null +++ b/aidocs/version.md @@ -0,0 +1,112 @@ +# 技术文档:`fixed_sor.py` + +## 概述 + +本模块 `fixed_sor.py` 是一个用于解决数值计算中迭代方法(如逐次超松弛法,Successive Over-Relaxation, SOR)相关问题的 Python 脚本。该版本主要修复了之前版本中与 C 函数调用相关的 Bug,提升了代码的稳定性与兼容性。 + +--- + +## 版本信息 + +- **当前版本**:`0.1.3` +- **更新内容**: + - 修复了与底层 C 函数交互时出现的 Bug。 + - 增强了异常处理机制,避免因类型不匹配或内存访问错误导致程序崩溃。 + - 提高了与外部 C 扩展模块的兼容性。 + +> ⚠️ 注意:此修复主要影响使用 C 扩展进行高性能计算的用户。纯 Python 用户通常不受影响,但仍建议升级以获得更好的稳定性。 + +--- + +## 安装与依赖 + +### 依赖项 + +- Python >= 3.7 +- (可选)Cython 或 ctypes 支持(若使用 C 扩展) + +### 安装方式 + +```bash +# 通过 pip 安装(假设已发布到 PyPI) +pip install fixed-sor==0.1.3 + +# 或本地安装 +python setup.py install +``` + +--- + +## 使用示例 + +```python +import fixed_sor + +# 示例:求解线性方程组 Ax = b 使用 SOR 方法 +A = [[4, -1, 0], + [-1, 4, -1], + [0, -1, 4]] +b = [15, 10, 15] + +x = fixed_sor.solve(A, b, omega=1.2, max_iter=1000, tol=1e-6) +print("解为:", x) +``` + +--- + +## API 参考 + +### `solve(A, b, omega=1.0, max_iter=1000, tol=1e-6)` + +使用 SOR 方法求解线性方程组 $Ax = b$。 + +#### 参数: + +| 参数 | 类型 | 说明 | +|-------------|------------|------| +| `A` | list/list of lists 或 numpy.ndarray | 系数矩阵(必须为方阵且对角占优或正定) | +| `b` | list 或 numpy.ndarray | 常数向量 | +| `omega` | float | 松弛因子,默认为 1.0(即 Gauss-Seidel 方法) | +| `max_iter` | int | 最大迭代次数,默认 1000 | +| `tol` | float | 收敛容差,默认 1e-6 | + +#### 返回值: + +- `list`:解向量 $x$ + +#### 异常: + +- `ValueError`:输入矩阵维度不匹配或不满足 SOR 方法前提条件。 +- `RuntimeError`:迭代未在最大次数内收敛。 + +--- + +## 更新日志(Changelog) + +### v0.1.3 — 2025-04-05 +- ✅ 修复:C 函数接口中的内存访问越界问题 +- ✅ 修复:数据类型转换错误(Python 到 C 的 double 数组传递) +- 🛠️ 优化:提升与 NumPy 数组的兼容性 +- 📚 更新文档 + +### v0.1.2 — 2024-XX-XX +- 添加基本 SOR 实现 +- 支持列表和 NumPy 输入 + +--- + +## 开发与贡献 + +欢迎提交 Issue 和 Pull Request。请确保测试覆盖所有修改路径,特别是涉及 C 接口的部分。 + +GitHub 仓库:`https://github.com/username/fixed-sor` + +--- + +## 许可证 + +MIT License + +--- + +> **备注**:本模块名称“fixed”强调其主要目标是修复先前版本中的关键缺陷,后续版本将逐步增强功能与性能。 \ No newline at end of file