Install
openclaw skills install sql-linkerUse this skill whenever you need to query, insert, update, or delete database records. Triggers include: (1) querying database data with SELECT statements and parameter binding; (2) inserting or batch-inserting records; (3) updating or deleting records with WHERE conditions; (4) transaction control with begin/commit/rollback; (5) read-only mode enforcement for safe data access; (6) audit trail for identity tracking, SQL logging, and compliance. Also use when the user mentions database operations, SQL queries, CRUD tasks, or any data manipulation involving MySQL, PostgreSQL, or SQLite databases.
openclaw skills install sql-linkerImportant: All
scripts/paths are relative to this skill directory. Run with:cd {skill_dir} && python scripts/...or use thecwdparameter.
(中文)SQL-Linker 提供跨数据库的 CRUD 操作能力,支持 MySQL、PostgreSQL、SQLite 三种主流数据库。内置审计日志模块,每次操作自动记录操作人身份、IP、SQL 语句、操作时间,确保数据可溯源、安全可控。业务层(db_bridge)负责字段白名单过滤和时间戳自动注入,数据操作层(sql_linker)负责连接管理、CRUD 执行和审计记录,两层严格分离,互不干扰。
(English)SQL-Linker provides cross-database CRUD operations, supporting MySQL, PostgreSQL, and SQLite, with a built-in audit trail module that automatically records operator identity, IP, SQL statements, and timestamps for full traceability and compliance. The business layer (db_bridge) handles field whitelist filtering and automatic timestamp injection, while the data operation layer (sql_linker) manages database connections, CRUD execution, and audit logging. The two layers are strictly separated and independent.
(中文)系统由两层组成,业务层和数据操作层职责分明:
(English)The system consists of two layers with clearly defined responsibilities:
workspace/
└── .sql_linker/ ← 配置文件根目录
├── config_home/
│ ├── config.yaml ← 数据库连接配置
│ ├── audit_config.json ← 审计配置
│ └── extra_tables.json ← 特权表配置(JSON)
└── table_home/
└── table_dictionary.json ← 主词典(JSON,所有受控表)
skills/sql-linker/scripts/
├── controller_layer/ ← 数据操作层
│ ├── sql_linker.py ← 连接管理 + CRUD 执行 + 审计上下文注入
│ └── sql_audit.py ← 审计模块(sql_linker.py 内部使用)
└── service_layer/ ← 业务层
└── db_bridge.py ← 四层访问控制 + 时间戳注入 + 字段白名单
(中文)业务层(service_layer):读取 table_dictionary.json,过滤字段,注入时间戳,校验访问权限,调用数据操作层,不直接操作数据库。
(English)Business Layer (service_layer): Reads table_dictionary.json, filters fields, injects timestamps, verifies access rights, and calls the data operation layer. Does not directly access the database.
(中文)数据操作层(controller_layer):管理数据库连接,执行 CRUD 操作,写入审计日志,处理参数化查询,不处理业务逻辑。
(English)Data Operation Layer (controller_layer): Manages database connections, executes CRUD operations, writes audit logs, handles parameterized queries. Does not process business logic.
(中文)系统通过四层访问模型实现精确的表访问控制:
(English)The system implements precise table access control through a four-layer access model:
SYSTEM (系统表 sql_audit_log)
NORMAL (主词典表格)
PRIVILEGED (特权表格)
BLOCKED (禁用)
(中文)访问判定流程:提取 SQL 中的表名 → 检查 SYSTEM → 检查主词典(NORMAL)→ 检查 extra_tables(PRIVILEGED)→ 其余 BLOCKED。
(English)Access Decision Flow: Extract table name from SQL → Check SYSTEM → Check main dictionary (NORMAL) → Check extra_tables (PRIVILEGED) → Rest BLOCKED.
(中文)首次使用或缺少配置文件时,系统自动生成默认模板(幂等操作,不会覆盖已有文件):
(English)On first use or when config files are missing, the system automatically generates default templates (idempotent, will not overwrite existing files):
from db_bridge import DBBridge
db = DBBridge(user_label="openclaw-control-ui", session_id="agent:hr:main")
created = db.bootstrap()
print(f'Created: {created}')
# ['...\\config.yaml', '...\\audit_config.json', ...]
(中文)自动生成的文件列表:
(English)Auto-generated files:
| 文件路径 | 默认内容 |
|---|---|
.sql_linker/config_home/config.yaml | 连接模板(host/port/user 占位,password_env 引用 .env) |
.sql_linker/config_home/audit_config.json | 审计默认开启,log_select=false |
.sql_linker/config_home/extra_tables.json | 特权表,默认关闭,max_extra_tables=10 |
.sql_linker/table_home/table_dictionary.json | 带示例表的空模板 |
.sql_linker/.env | 凭据占位符提示 |
table_home/table_dictionary.json — 主词典 / Main Dictionary(中文)所有受控业务表必须在主词典中声明,字段白名单仅对 NORMAL 层生效:
(English)All controlled business tables must be declared in the main dictionary. Field whitelist only applies to NORMAL layer:
{
"version": 1,
"tables": [
{
"table_name": "supplier_table",
"comment": "供应商信息表",
"fields": [
{ "name": "id", "type": "BIGINT", "pk": true, "auto": true },
{ "name": "supplier_code", "type": "VARCHAR(32)", "pk": false, "auto": false },
{ "name": "supplier_name", "type": "VARCHAR(128)", "pk": false, "auto": false },
{ "name": "short_name", "type": "VARCHAR(64)", "pk": false, "auto": false },
{ "name": "supplier_level","type": "VARCHAR(16)", "pk": false, "auto": false },
{ "name": "contact_person","type": "VARCHAR(64)", "pk": false, "auto": false },
{ "name": "contact_phone", "type": "VARCHAR(32)", "pk": false, "auto": false },
{ "name": "contact_email", "type": "VARCHAR(128)", "pk": false, "auto": false },
{ "name": "status", "type": "VARCHAR(16)", "pk": false, "auto": false },
{ "name": "created_at", "type": "DATETIME", "pk": false, "auto": false },
{ "name": "updated_at", "type": "DATETIME", "pk": false, "auto": false }
]
}
]
}
config_home/extra_tables.json — 特权表配置 / Privileged Table Config(中文)词典外表格需通过此配置显式授权,enabled=false 时所有非词典表均 BLOCKED:
(English)Tables outside the dictionary require explicit authorization via this config. When enabled=false, all non-dictionary tables are BLOCKED:
{
"version": 1,
"enabled": false,
"max_extra_tables": 10,
"tables": [
{ "table_name": "employee_table" },
{ "table_name": "payroll_table" }
]
}
| 字段 | 说明 |
|---|---|
enabled | false=关闭词典外访问(默认)/ true=开启特权模式 |
max_extra_tables | 最大允许声明数量,防止配置失控 |
tables[].table_name | 特权表名 |
config_home/config.yaml — 连接配置 / Connection Config(中文)数据库连接配置,password 不直接写在文件中,通过 password_env 引用 .env 中的 key:
(English)Database connection config. Password is not written directly; password_env references a key in .env:
type: mysql
host: 127.0.0.1
port: 3306
database: db_dev
user: admin
password_env: MYSQLPW_ENV # 引用 .env 中的 key(不直接写明文)
read_only: false
max_rows: 1000
timeout: 30
extra_tables_enabled: false # 默认关闭词典外访问
(中文)使用业务层 API(推荐),完整支持四层访问控制和时间戳自动注入:
(English)Use the business layer API (recommended), with full four-layer access control and automatic timestamp injection:
import sys
sys.path.insert(0, "skills/sql-linker/scripts/service_layer")
from db_bridge import DBBridge
db = DBBridge(
user_label="openclaw-control-ui", # ← OpenClaw metadata.label
session_id="agent:hr:main" # ← OpenClaw metadata.id
)
(中文)INSERT 操作自动生成 created_at 和 updated_at(两者同值),仅写入主词典中声明的字段:
(English)INSERT operations automatically generate created_at and updated_at (same value), writing only fields declared in the main dictionary:
db.insert("supplier_table", {
"supplier_code": "LX001",
"supplier_name": "立讯精密",
"supplier_level": "A",
"status": "active"
})
# → created_at / updated_at 自动生成,无需手动传入
(中文)UPDATE 操作自动刷新 updated_at,created_at 保持不变:
(English)UPDATE operations automatically refresh updated_at, leaving created_at unchanged:
db.update(
"supplier_table",
{"supplier_level": "AA"},
"supplier_code = %s",
("LX001",)
)
# → updated_at 自动刷新为当前时间
db.delete("supplier_table", "status = %s", ("inactive",))
rows = db.query(
"SELECT * FROM supplier_table WHERE status = %s AND supplier_level = %s",
("active", "A")
)
for row in rows:
print(row)
db.tables() # 返回主词典中所有表格名
db.extra_tables() # 返回当前特权表列表
db.system_tables() # 返回受保护的系统表列表
db.fields("supplier_table") # 返回表字段列表(privilege 表返回空)
db.bootstrap() # 执行引导初始化,返回被创建的文件路径列表
from db_bridge import TableAccessDenied, SystemTableWriteDenied
try:
db.query("SELECT * FROM unknown_table LIMIT 1")
except TableAccessDenied as e:
print("访问被拒绝 / Access denied:", e)
try:
db.update("sql_audit_log", {"status": "tampered"}, "id = %s", (1,))
except SystemTableWriteDenied as e:
print("系统表写入被拒绝 / System table write denied:", e)
(中文)直接使用数据操作层,跳过业务层字段过滤和时间戳注入(适用于高级用户):
(English)Use the data operation layer directly, bypassing business layer field filtering and timestamp injection (for advanced users):
import sys
sys.path.insert(0, "skills/sql-linker/scripts/controller_layer")
from sql_linker import SQLLinker
linker = SQLLinker()
linker.connect()
linker.set_user_context_auto() # 必须:注入审计上下文
| 操作 / Operation | created_at | updated_at | 适用层级 / Applicable Layer |
|---|---|---|---|
| INSERT | Auto | Auto (same as created) | NORMAL |
| UPDATE | Unchanged | Auto-refresh | NORMAL |
| DELETE | N/A | N/A | NORMAL |
| PRIVILEGED | N/A | N/A | PRIVILEGED |
(中文)配置位置:.sql_linker/config_home/audit_config.json
(English)Config location: .sql_linker/config_home/audit_config.json
{
"username": "HR",
"audit": {
"enabled": true,
"log_table": "sql_audit_log",
"log_select": false,
"mask_values": true
}
}
(中文)审计记录字段(自动注入,不可为空):
(English)Audit record fields (automatically injected, must not be empty):
| 字段 | 说明 | 来源 |
|---|---|---|
user_name | 操作人 | audit_config.json username |
user_label | 来源标签 | OPENCLAW_LABEL 环境变量 |
ip_address | 本机局域网 IP | 自动检测(UDP/gethostbyname) |
session_id | OpenClaw Session Key | OPENCLAW_SESSION 环境变量 |
operation | 操作类型 | SELECT / INSERT / UPDATE / DELETE |
table_name | 目标表 | 从 SQL 中提取 |
sql_statement | SQL 语句 | 参数化脱敏(%s) |
rows_affected | 影响行数 | 数据库返回 |
status | 操作状态 | SUCCESS / FAILED |
| type 值 | 说明 |
|---|---|
BIGINT | 主键/自增ID |
VARCHAR(n) | 字符串,最大 n 字符 |
TEXT | 长文本 |
INT | 整数 |
DECIMAL(m,n) | 小数,m位总长,n位小数 |
DATETIME | 日期时间(YYYY-MM-DD HH:MM:SS) |
DATE | 日期 |
BOOL | 布尔值 |
(中文)
NORMAL 表只写入 table_dictionary.json 中声明的字段,非法字段自动过滤%s + tuple 防止 SQL 注入password_env 引用 .env,不写在 config.yaml 中sql_audit_log 禁止 UPDATE/DELETE,SystemTableWriteDenied 异常强制拦截(English)
NORMAL tables only write fields declared in table_dictionary.json; illegal fields are automatically filtered%s + tuple to prevent SQL injectionpassword_env references .env key, not written in config.yamlsql_audit_log prohibits UPDATE/DELETE, SystemTableWriteDenied exception enforced| 错误 / Error | 原因 / Cause | 解法 / Solution |
|---|---|---|
TableAccessDenied: Table 'xxx' not in dictionary | 表格不在词典且未授权 / Table not in dictionary and not authorized | 加入 extra_tables.json + enabled:true |
SystemTableWriteDenied: sql_audit_log does not allow UPDATE | 尝试篡改审计日志 / Attempt to tamper audit log | 属正常拦截,如确认误判请通过 DBA 直接操作数据库 / Normal interception; if misjudged, contact DBA |
created_at / updated_at 为 NULL | 操作非 NORMAL 表格 / Operating on non-NORMAL table | privilege 表格不注入时间戳,属正常行为 / Privilege tables do not inject timestamps, this is normal |
Access denied for user ... (using password: NO) | password_env 未解析 / password_env not resolved | sql_linker ≥0.4.0 已修复,从 .env 读取 / Fixed in sql_linker ≥0.4.0, reads from .env |
Config file not found | 配置文件缺失 / Config file missing | 调用 db.bootstrap() 自动生成,或检查 .sql_linker/ 目录结构 / Call db.bootstrap() to auto-generate, or check .sql_linker/ directory structure |
Table not found | 表未在 table_dictionary.json 中声明 / Table not declared | 在主词典中添加表配置 / Add table config in main dictionary |
workspace/
└── .sql_linker/
├── .env ← 敏感凭据(不随技能迁移)
├── config_home/
│ ├── config.yaml ← 连接配置(extra_tables_enabled 开关)
│ ├── audit_config.json ← 审计配置
│ └── extra_tables.json ← 特权表列表(JSON)
└── table_home/
└── table_dictionary.json ← 主词典(JSON,所有受控表)
└── tables[] ← 每表 fields[] 白名单 + comment
skills/sql-linker/
├── SKILL.md ← 本文档
└── scripts/
├── controller_layer/ ← 数据操作层
│ ├── sql_linker.py ← 连接管理 + CRUD + 审计
│ └── sql_audit.py ← 审计模块
└── service_layer/ ← 业务层
└── db_bridge.py ← 四层访问 + 时间戳 + Bootstrap