DBridge 语义引擎与语义模型
1. 概述
DBridge 语义层(Semantic Layer)是连接业务用户与底层数据库的桥梁。它将物理数据库表抽象为业务友好的 Cube(数据模型),通过 Measures(度量) 和 Dimensions(维度) 定义业务指标体系,使非技术人员也能通过自然语言或简单配置完成复杂数据分析。
语义层的核心价值:
- 业务语义封装:将 SQL 表达式、聚合逻辑、表关联封装为可复用的业务指标
- 统一查询接口:无论底层是 MySQL、PostgreSQL 还是其他数据库,上层使用统一的查询语法
- AI 友好:为大语言模型(LLM)提供结构化的元数据,使 AI 能准确理解业务数据并生成正确查询
2. 核心概念
2.1 Cube(数据模型)
Cube 是语义层的基本单元,代表一个业务主题域(如"订单"、“用户”、“产品”)。
数据库存储:semantic_cubes 表
| 字段 | 类型 | 说明 |
|---|---|---|
id |
UUID | 主键 |
name |
string | 唯一标识,如 orders |
display_name |
string | 显示名称,如 订单 |
description |
text | 业务描述 |
data_source_id |
string | 关联的数据源 ID |
database |
string | 数据库名(可选) |
schema_name |
string | Schema 名(可选) |
sql_table |
string | 物理表名,如 public.orders |
sql_query |
text | 自定义 SQL 查询(与 sql_table 二选一) |
measures_json |
JSON | 度量定义数组 |
dimensions_json |
JSON | 维度定义数组 |
joins_json |
JSON | 关联定义数组 |
pre_aggs_json |
JSON | 预聚合定义数组 |
version |
int | 版本号,每次更新递增 |
tenant_id |
string | 租户 ID(多租户隔离) |
2.2 Measure(度量)
度量是可聚合的业务指标,如"订单总额"、“用户数”。
{
"name": "total_amount",
"display_name": "订单总额",
"description": "所有订单的金额总和",
"type": "sum",
"sql": "SUM({field})",
"field": "amount"
}
支持的聚合类型:
sum— 求和avg— 平均值min/max— 最小/最大值count— 计数count_distinct— 去重计数number— 数值(不聚合)running_total— 累计值
2.3 Dimension(维度)
维度是数据的分组或过滤属性,如"日期"、“地区”、“状态”。
{
"name": "date",
"display_name": "订单日期",
"description": "订单创建日期",
"sql": "order_date",
"time_granularity": "day"
}
时间维度粒度:day、week、month、quarter、year
2.4 Join(关联)
Cube 之间可以通过 Join 关联,实现跨模型查询。
{
"join_cube_name": "users",
"relationship": "many_to_one",
"sql": "orders.user_id = users.id",
"join_type": "left"
}
3. 语义引擎架构
3.1 整体架构
┌─────────────────────────────────────────────────────────┐
│ 接入层 │
│ REST API │ MCP Server │ Agent SDK │ 前端 UI │
└────────────┴──────────────┴─────────────┴────────────────┘
│
▼
┌─────────────────────┐
│ SemanticQueryService│ ← 查询编排服务
─────────────────────┘
│
┌─────────────┼─────────────┐
▼ ▼ ▼
验证服务 审计日志 行级安全
│
▼
┌──────────────┐
│ Adapter Registry │ ← 适配器注册中心
└──────────────┘
│
┌──────┴──────┐
▼ ▼
LocalAdapter CubeJSAdapter ← 可插拔引擎
│
▼
┌──────────────┐
│ LocalCompiler │ ← SQL 编译器
└──────────────┘
│
▼
──────────────┐
│ QueryService │ ← 底层 SQL 执行
└──────────────┘
3.2 核心组件
SemanticQueryService(查询编排)
位置:internal/semantic/service/query_service.go
负责完整的查询生命周期:
- 验证 — 校验字段格式、运算符白名单、Limit 上限
- 路由 — 根据引擎名称选择适配器
- 行级安全 — 应用数据权限过滤
- 执行 — 调用适配器的 Query 方法
- 审计 — 记录查询日志(用户、SQL、耗时、行数)
Adapter Registry(适配器注册中心)
位置:internal/semantic/adapter/registry.go
管理所有语义引擎实例:
- 支持
local和cubejs两种适配器类型 - 线程安全的并发访问
- 支持设置默认引擎
LocalAdapter(本地引擎)
位置:internal/semantic/adapter/local_adapter.go
将语义查询编译为 SQL 并在目标数据库执行:
- 从 Registry 加载 Cube 定义到内存缓存
- 调用 LocalCompiler 将 QueryRequest 编译为 SQL
- 通过 QueryService 执行 SQL 并返回结果
LocalCompiler(SQL 编译器)
位置:internal/semantic/adapter/local_compiler.go
核心编译逻辑:
- SELECT:将 Measure 的 SQL 模板(如
SUM({field}))替换为实际字段,包裹聚合函数 - FROM:使用 Cube 的
sql_table或sql_query - JOIN:仅关联查询中引用的 Cube
- WHERE:将 Filter 结构转换为 SQL 条件(支持
equals、contains、gt、in等) - GROUP BY:所有非聚合维度
- ORDER BY / LIMIT:透传
4. MCP Tools 集成
4.1 三个语义工具
DBridge 通过 MCP(Model Context Protocol)向外部 AI 客户端暴露三个语义工具:
| 工具名 | 功能 | 输入 | 输出 |
|---|---|---|---|
semantic_list_engines |
列出所有语义引擎 | 无 | 引擎名称 + 能力列表 |
semantic_list_cubes |
列出所有 Cube | engine(可选) |
Cube 元数据(名称、度量、维度) |
semantic_query |
执行语义查询 | engine、measures、dimensions、filters 等 |
columns、rows、sql、duration_ms |
4.2 工具定义
位置:internal/mcp/tools/semantic.go
// semantic_query 输入结构
type SemanticQueryInput struct {
Engine string `json:"engine"`
DataSourceID string `json:"data_source_id"`
Measures []string `json:"measures"` // ["orders.total_amount"]
Dimensions []string `json:"dimensions"` // ["orders.date"]
Filters []engine.Filter `json:"filters"`
Order []engine.OrderItem `json:"order"`
Limit int `json:"limit"`
Offset int `json:"offset"`
}
// semantic_query 输出结构
type SemanticQueryOutput struct {
Columns []string `json:"columns"`
Rows []map[string]any `json:"rows"`
TotalRows int `json:"total_rows"`
DurationMs int64 `json:"duration_ms"`
SQL string `json:"sql"`
CacheHit bool `json:"cache_hit"`
Engine string `json:"engine"`
}
4.3 工具注册
位置:internal/mcp/server.go (lines 332-355)
if svc.Semantic != nil {
// 注册 semantic_list_engines
mcp.AddTool(server, &mcp.Tool{
Name: "semantic_list_engines",
Description: "列出所有已注册的语义层引擎及其能力信息",
InputSchema: mcp.ToolInputSchema{Type: "object", Properties: map[string]any{}},
}, handleListEngines)
// 注册 semantic_list_cubes
mcp.AddTool(server, &mcp.Tool{...}, handleListCubes)
// 注册 semantic_query
mcp.AddTool(server, &mcp.Tool{...}, handleSemanticQuery)
}
所有语义工具的权限要求为 read,类别为 semantic。
5. Agent LLM 调用流程
5.1 两条调用路径
路径 A:Agent 内置工具(agentsdkwrap)
位置:internal/service/agentsdkwrap/runner.go (lines 506-601)
当 Agent 执行时,语义工具作为内置工具直接注入:
// 默认只读工具包含语义发现工具
DefaultReadTools = []string{
"semantic_list_engines",
"semantic_list_cubes",
// ... 其他工具
}
// 语义查询工具实现
semanticQueryTool := types.Tool{
Name: "semantic_query",
Exec: func(ctx context.Context, argsJSON string) (string, error) {
var input SemanticQueryInput
json.Unmarshal([]byte(argsJSON), &input)
queryReq := &engine.QueryRequest{
Measures: input.Measures,
Dimensions: input.Dimensions,
Filters: input.Filters,
}
result, err := semanticSvc.Query(ctx, queryReq, nil, "", "", "", "", "agent")
return formatQueryResult(result), nil
},
}
路径 B:MCP 协议(外部客户端)
位置:internal/mcp/server.go
外部 MCP 客户端(如 Claude Desktop、Codex)通过 MCP 协议连接,获取相同的三个工具。
5.2 完整调用示例
场景:用户问"帮我查一下上个月的订单总额"
步骤 1: LLM 调用 semantic_list_cubes
── 输入: {}
├── 返回: [
│ { name: "orders", display_name: "订单",
│ measures: ["total_amount", "order_count"],
│ dimensions: ["date", "region", "status"] },
│ { name: "users", ... }
│ ]
└── LLM 理解: 订单数据在 "orders" Cube 中
步骤 2: LLM 调用 semantic_query
── 输入: {
│ measures: ["orders.total_amount"],
│ dimensions: ["orders.date"],
│ filters: [{
│ field: "orders.date",
│ op: "gte",
│ value: "2026-09-01"
│ }, {
│ field: "orders.date",
│ op: "lt",
│ value: "2026-10-01"
│ }]
│ }
├── 内部流程:
│ ├── ValidateQueryRequest() — 验证字段格式
│ ├── resolveEngine() — 选择引擎
│ ├── LocalCompiler.Compile() — 生成 SQL:
│ │ SELECT SUM(amount) AS total_amount,
│ │ order_date AS date
│ │ FROM orders
│ │ WHERE order_date >= '2026-09-01'
│ │ AND order_date < '2026-10-01'
│ │ GROUP BY order_date
│ ├── querySvc.Execute() — 执行 SQL
│ ── 返回: { columns, rows, sql, duration_ms }
└── LLM 生成回答: "上个月订单总额为 ¥1,234,567"
5.3 ToolResolver 工具解析
位置:internal/service/agentexec/mcp_bridge.go
Agent 执行时,ToolResolver 按以下优先级解析工具:
- 内置 Go 工具 — 从
agentsdkwrap.BuiltinTools匹配(语义工具在此) - MCP 工具定义 — 从数据库
mcp_tool_definitions表加载(自定义 SQL 工具等) - 未解析 — 记录警告日志
每个工具执行前经过 PermissionGuard.Check() 权限校验,执行后通过 captureResult() 记录审计。
6. 数据模型详解
6.1 CubeDefinition(内存结构)
位置:internal/semantic/engine/metadata.go
type CubeDefinition struct {
Name string `json:"name"`
DisplayName string `json:"display_name"`
Description string `json:"description"`
DataSourceID string `json:"data_source_id"`
Database string `json:"database,omitempty"`
Schema string `json:"schema,omitempty"`
SQLTable string `json:"sql_table,omitempty"`
SQLQuery string `json:"sql_query,omitempty"`
Measures []*MeasureDetail `json:"measures"`
Dimensions []*DimensionDetail `json:"dimensions"`
Joins []*JoinDetail `json:"joins,omitempty"`
PreAggregations []*PreAggDetail `json:"pre_aggregations,omitempty"`
Version int `json:"version"`
UpstreamTables []string `json:"upstream_tables,omitempty"`
}
6.2 MeasureDetail
type MeasureDetail struct {
MeasureMeta
SQL string `json:"sql"` // SQL 表达式模板
Filters []Filter `json:"filters"` // 度量级过滤
DrillMembers []string `json:"drill_members"` // 下钻成员
Metadata map[string]any `json:"metadata"`
}
type MeasureMeta struct {
Name string `json:"name"`
DisplayName string `json:"display_name"`
Description string `json:"description"`
Type string `json:"type"` // sum, avg, count, etc.
}
6.3 DimensionDetail
type DimensionDetail struct {
DimensionMeta
SQL string `json:"sql"`
PrimaryKey bool `json:"primary_key"`
TimeGranularity string `json:"time_granularity"` // day, week, month, etc.
Metadata map[string]any `json:"metadata"`
}
6.4 CubeMeta(摘要视图)
用于列表展示,仅包含名称和成员列表:
type CubeMeta struct {
Name string `json:"name"`
DisplayName string `json:"display_name"`
Description string `json:"description"`
Measures []string `json:"measures"` // 仅名称
Dimensions []string `json:"dimensions"` // 仅名称
DataSource string `json:"data_source"`
}
7. 查询验证规则
位置:internal/semantic/service/validation.go
7.1 字段格式
所有 Measure/Dimension/Filter 字段必须遵循 cube_name.member_name 格式:
✅ orders.total_amount
✅ users.created_date
❌ total_amount // 缺少 Cube 前缀
orders. // 缺少成员名
.total_amount // 缺少 Cube 名
正则:^[a-zA-Z_][a-zA-Z0-9_]*\.[a-zA-Z_][a-zA-Z0-9_]*$
7.2 运算符白名单
| 运算符 | 说明 | 示例 |
|---|---|---|
equals |
等于 | status equals "completed" |
notEquals |
不等于 | status notEquals "cancelled" |
contains |
包含 | name contains "张" |
gt / gte |
大于 / 大于等于 | amount gt 100 |
lt / lte |
小于 / 小于等于 | date lte "2026-12-31" |
in |
在列表中 | region in ["北京", "上海"] |
set / notSet |
已设置 / 未设置 | email set |
7.3 Limit 上限
最大返回行数限制为 10,000,防止大结果集导致内存溢出。
8. 与数据源的关联
8.1 数据源配置
Cube 通过 data_source_id 关联到具体的数据库连接。数据源配置存储在 data_sources 表中,包含:
- 数据库类型(MySQL、PostgreSQL、SQL Server 等)
- 连接信息(Host、Port、Database、Username、Password)
- 连接池配置
8.2 查询路由
当执行语义查询时:
- 从 Cube 定义中获取
data_source_id - 通过
DataSourceService获取连接配置 - 将编译后的 SQL 发送到目标数据库执行
- 返回结果集
8.3 多数据源支持
不同 Cube 可以关联不同的数据源,实现跨库查询:
Cube "orders" → MySQL 数据库 A
Cube "users" → PostgreSQL 数据库 B
通过 Join 定义,可以在查询时自动关联不同数据源的 Cube(需引擎支持)。
9. 行级数据安全
9.1 配置方式
在 SemanticQueryService 中配置行级过滤器:
rowLevelFilters := []engine.RowLevelFilter{
{
CubeName: "orders",
Field: "region",
Resolver: func(ctx context.Context, userRoles []string) []string {
// 根据用户角色返回允许访问的地区
return getUserRegions(ctx)
},
},
}
9.2 执行时机
行级过滤器在查询执行前应用,自动追加到 WHERE 条件:
-- 原始查询
SELECT SUM(amount) FROM orders WHERE date >= '2026-09-01'
-- 应用行级安全后
SELECT SUM(amount) FROM orders
WHERE date >= '2026-09-01'
AND region IN ('华东', '华北') -- 自动追加
10. 审计日志
每次语义查询都会记录审计日志,包含:
| 字段 | 说明 |
|---|---|
user_id |
执行查询的用户 ID |
cube_name |
查询的 Cube 名称 |
sql |
生成的 SQL 语句 |
duration_ms |
查询耗时(毫秒) |
row_count |
返回行数 |
status |
执行状态(success/error) |
error_message |
错误信息(如有) |
created_at |
执行时间 |
审计日志存储在 audit_logs 表中,模块标识为 semantic。
11. 前端集成
11.1 Cube 管理界面
位置:web/src/pages/semantic/CubeEdit.tsx
提供 Cube 的创建、编辑、查看功能:
- 基本信息配置(名称、描述、数据源、表名)
- 度量定义(名称、类型、SQL 表达式)
- 维度定义(名称、SQL 表达式、时间粒度)
- Join 配置(关联 Cube、关联条件、关联类型)
11.2 AI 生成立方体
位置:web/src/pages/semantic/CubeEdit.tsx (lines 253-278)
用户可以用自然语言描述需求,AI Agent 自动生成 Cube 配置:
用户输入: "创建一个订单分析模型,包含订单总额、订单数量等指标,按日期和地区分组"
↓
AI 调用 LLM 生成 Cube JSON
↓
前端解析 JSON 并填充表单
↓
用户确认后保存
11.3 语义查询 API
前端通过 REST API 执行语义查询:
// 列出 Cube
GET /api/v1/semantic/cubes
// 执行查询
POST /api/v1/semantic/query
{
"engine": "local",
"measures": ["orders.total_amount"],
"dimensions": ["orders.date"],
"filters": [...],
"limit": 100
}
12. 配置示例
12.1 配置文件
位置:configs/config.yaml
semantic:
# 默认适配器
default_adapter: "local"
# Cube.js 适配器(可选)
cubejs:
api_url: "http://localhost:4000"
api_token: "${CUBEJS_API_TOKEN}"
12.2 环境变量
| 配置项 | 环境变量 |
|---|---|
semantic.default_adapter |
DBRIDGE_SEMANTIC_DEFAULT_ADAPTER |
semantic.cubejs.api_url |
DBRIDGE_SEMANTIC_CUBEJS_API_URL |
semantic.cubejs.api_token |
DBRIDGE_SEMANTIC_CUBEJS_API_TOKEN |
13. 关键代码位置索引
| 组件 | 文件路径 | 说明 |
|---|---|---|
| 语义查询服务 | internal/semantic/service/query_service.go |
查询编排核心 |
| 查询验证 | internal/semantic/service/validation.go |
字段格式、运算符校验 |
| 适配器注册中心 | internal/semantic/adapter/registry.go |
引擎管理 |
| 本地适配器 | internal/semantic/adapter/local_adapter.go |
SQL 编译与执行 |
| SQL 编译器 | internal/semantic/adapter/local_compiler.go |
语义→SQL 转换 |
| 本地注册表 | internal/semantic/adapter/local_registry.go |
Cube 缓存管理 |
| 引擎元数据 | internal/semantic/engine/metadata.go |
Cube/Measure/Dimension 定义 |
| MCP 工具定义 | internal/mcp/tools/semantic.go |
三个语义工具实现 |
| MCP 服务注册 | internal/mcp/server.go |
工具注册到 MCP Server |
| Agent 内置工具 | internal/service/agentsdkwrap/runner.go |
Agent 直接调用路径 |
| 工具解析器 | internal/service/agentexec/mcp_bridge.go |
工具匹配与权限校验 |
| 数据库模型 | internal/repository/models.go (L875-911) |
SemanticCube 表结构 |
| 前端 Cube 编辑 | web/src/pages/semantic/CubeEdit.tsx |
Cube 管理界面 |
| 前端 API 调用 | web/src/api/semantic.ts |
语义层 REST API 封装 |
14. 最佳实践
14.1 Cube 设计原则
- 单一职责:每个 Cube 代表一个明确的业务主题
- 命名规范:使用英文小写 + 下划线(如
order_items) - 度量命名:使用业务友好的名称(如
total_revenue而非sum_amount) - 维度粒度:时间维度明确粒度(日/周/月/季/年)
14.2 性能优化
- 预聚合:对高频查询配置 PreAggregation
- Limit 限制:始终设置合理的 Limit,避免全表扫描
- 索引优化:在常用过滤字段上建立数据库索引
- 缓存策略:对重复查询启用查询缓存
14.3 安全建议
- 行级安全:为敏感数据配置行级过滤器
- 权限控制:通过 MCP Token 限制可访问的 Cube
- 审计日志:定期检查查询日志,发现异常访问
- SQL 注入防护:所有参数使用参数化查询,禁止拼接 SQL
15. 故障排查
15.1 常见问题
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| 查询返回空结果 | Cube 未关联数据源 | 检查 data_source_id 配置 |
| SQL 语法错误 | Measure SQL 模板错误 | 检查 {field} 占位符是否正确 |
| 权限不足 | MCP Token 缺少 read 权限 | 检查 Token 权限配置 |
| 查询超时 | 数据量过大或无索引 | 添加 Limit 或优化数据库索引 |
| Cube 未加载 | Registry 未初始化 | 检查 semantic.default_adapter 配置 |
15.2 调试方法
- 查看审计日志:
SELECT * FROM audit_logs WHERE module = 'semantic' ORDER BY created_at DESC LIMIT 20 - 检查生成的 SQL:
semantic_query返回的sql字段包含实际执行的 SQL - 验证 Cube 定义:通过
semantic_list_cubes查看 Cube 元数据是否正确 - 测试数据源连接:在数据源管理页面测试连接是否正常
附录 A:完整查询示例
A.1 简单聚合查询
需求:查询订单总额
{
"measures": ["orders.total_amount"],
"limit": 1
}
生成 SQL:
SELECT SUM(amount) AS total_amount
FROM orders
LIMIT 1
A.2 带维度分组
需求:按地区统计订单总额
{
"measures": ["orders.total_amount"],
"dimensions": ["orders.region"],
"order": [{"field": "orders.total_amount", "direction": "desc"}],
"limit": 10
}
生成 SQL:
SELECT SUM(amount) AS total_amount,
region_name AS region
FROM orders
GROUP BY region_name
ORDER BY total_amount DESC
LIMIT 10
A.3 带时间过滤
需求:查询 2026 年 Q3 的订单数量
{
"measures": ["orders.order_count"],
"dimensions": ["orders.date"],
"filters": [
{"field": "orders.date", "op": "gte", "value": "2026-07-01"},
{"field": "orders.date", "op": "lt", "value": "2026-10-01"}
]
}
生成 SQL:
SELECT COUNT(id) AS order_count,
order_date AS date
FROM orders
WHERE order_date >= '2026-07-01'
AND order_date < '2026-10-01'
GROUP BY order_date
A.4 跨 Cube 关联查询
需求:查询每个用户的订单总额
{
"measures": ["orders.total_amount"],
"dimensions": ["users.name"],
"filters": []
}
生成 SQL(假设已配置 Join):
SELECT SUM(o.amount) AS total_amount,
u.name AS name
FROM orders o
LEFT JOIN users u ON o.user_id = u.id
GROUP BY u.name
附录 B:MCP Tool 调用示例
B.1 Claude Desktop 配置
{
"mcpServers": {
"dbridge": {
"url": "http://localhost:8003/mcp",
"headers": {
"Authorization": "Bearer db_mcp_xxxxxxxxxxxx"
}
}
}
}
B.2 自然语言查询
在 Claude Desktop 中直接输入:
帮我分析一下上个月的销售数据,按产品类别分组,只看销售额前 10 的类别
Claude 会自动:
- 调用
semantic_list_cubes找到销售相关 Cube - 调用
semantic_query执行查询 - 用自然语言总结结果
文档版本: 1.0
最后更新: 2026-10-19
维护者: DBridge 开发团队