@harneon-ai/db 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +338 -0
- package/config/datasource.example.yaml +62 -0
- package/dist/config/parser.d.ts +61 -0
- package/dist/config/parser.js +165 -0
- package/dist/config/types.d.ts +358 -0
- package/dist/config/types.js +86 -0
- package/dist/config/writer.d.ts +49 -0
- package/dist/config/writer.js +129 -0
- package/dist/db/connection-manager.d.ts +41 -0
- package/dist/db/connection-manager.js +83 -0
- package/dist/db/metadata-queries.d.ts +82 -0
- package/dist/db/metadata-queries.js +113 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +92 -0
- package/dist/tools/describe-indexes.d.ts +11 -0
- package/dist/tools/describe-indexes.js +36 -0
- package/dist/tools/describe-table.d.ts +14 -0
- package/dist/tools/describe-table.js +45 -0
- package/dist/tools/list-datasources.d.ts +11 -0
- package/dist/tools/list-datasources.js +40 -0
- package/dist/tools/list-tables.d.ts +10 -0
- package/dist/tools/list-tables.js +34 -0
- package/dist/tools/query-metadata.d.ts +17 -0
- package/dist/tools/query-metadata.js +83 -0
- package/dist/tools/save-datasource-config.d.ts +15 -0
- package/dist/tools/save-datasource-config.js +112 -0
- package/dist/tools/sharding-topology.d.ts +20 -0
- package/dist/tools/sharding-topology.js +111 -0
- package/dist/tools/test-connection.d.ts +9 -0
- package/dist/tools/test-connection.js +100 -0
- package/package.json +42 -0
package/README.md
ADDED
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
# Harneon DB MCP Server
|
|
2
|
+
|
|
3
|
+
一个 MCP (Model Context Protocol) Server,为 AI 助手提供只读的 PostgreSQL 数据库元数据查询能力,支持 ShardingSphere 风格的多数据源分片拓扑解析。
|
|
4
|
+
|
|
5
|
+
## 功能概览
|
|
6
|
+
|
|
7
|
+
- 查看多数据源配置信息
|
|
8
|
+
- 查询任意数据源的表列表、表结构、索引信息
|
|
9
|
+
- 解析 ShardingSphere 分片拓扑(逻辑表 → 实际数据节点映射)
|
|
10
|
+
- 执行自定义元数据查询(仅允许 information_schema / pg_catalog)
|
|
11
|
+
- 多层安全防护,严禁任何数据写入和业务数据读取
|
|
12
|
+
|
|
13
|
+
## 快速开始
|
|
14
|
+
|
|
15
|
+
### 1. 全局安装
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install -g @harneon-ai/db
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### 2. 在 Claude Desktop 中使用
|
|
22
|
+
|
|
23
|
+
编辑 Claude Desktop 配置文件(`claude_desktop_config.json`):
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"mcpServers": {
|
|
28
|
+
"harneon-db": {
|
|
29
|
+
"command": "harneon-db"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### 3. 在 Claude Code 中使用
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
claude mcp add harneon-db -- harneon-db
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### 4. 配置数据源
|
|
42
|
+
|
|
43
|
+
配置文件默认保存在 `~/.harneon-ai/db/datasource.yaml`。
|
|
44
|
+
|
|
45
|
+
你可以手动创建该文件,也可以启动后通过 `save_datasource_config` 工具添加数据源(目录会自动创建)。
|
|
46
|
+
|
|
47
|
+
如需使用自定义配置路径:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
harneon-db /path/to/datasource.yaml
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
或通过环境变量:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
DB_CONFIG_PATH=/path/to/datasource.yaml harneon-db
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### 5. 开发模式运行
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
npm run dev
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## 配置文件格式
|
|
66
|
+
|
|
67
|
+
配置文件采用 YAML 格式,兼容 ShardingSphere 配置:
|
|
68
|
+
|
|
69
|
+
```yaml
|
|
70
|
+
dataSources:
|
|
71
|
+
ds_0:
|
|
72
|
+
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
|
|
73
|
+
driverClassName: org.postgresql.Driver
|
|
74
|
+
jdbcUrl: jdbc:postgresql://localhost:5432/shop_db_0
|
|
75
|
+
username: postgres
|
|
76
|
+
password: your_password
|
|
77
|
+
ds_1:
|
|
78
|
+
jdbcUrl: jdbc:postgresql://localhost:5432/shop_db_1
|
|
79
|
+
username: postgres
|
|
80
|
+
password: your_password
|
|
81
|
+
|
|
82
|
+
rules:
|
|
83
|
+
- !SHARDING
|
|
84
|
+
tables:
|
|
85
|
+
tbl_order:
|
|
86
|
+
actualDataNodes: ds_$->{0..1}.tbl_order_$->{0..3}
|
|
87
|
+
tableStrategy:
|
|
88
|
+
standard:
|
|
89
|
+
shardingColumn: order_id
|
|
90
|
+
shardingAlgorithmName: tbl_order_inline
|
|
91
|
+
defaultShardingColumn: corporation_id
|
|
92
|
+
defaultDatabaseStrategy:
|
|
93
|
+
standard:
|
|
94
|
+
shardingColumn: corporation_id
|
|
95
|
+
shardingAlgorithmName: database_inline
|
|
96
|
+
shardingAlgorithms:
|
|
97
|
+
database_inline:
|
|
98
|
+
type: INLINE
|
|
99
|
+
props:
|
|
100
|
+
algorithm-expression: ds_$->{corporation_id % 2}
|
|
101
|
+
tbl_order_inline:
|
|
102
|
+
type: INLINE
|
|
103
|
+
props:
|
|
104
|
+
algorithm-expression: tbl_order_$->{order_id % 4}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
配置路径优先级:命令行参数 > `DB_CONFIG_PATH` 环境变量 > `~/.harneon-ai/db/datasource.yaml`
|
|
108
|
+
|
|
109
|
+
## MCP Tools 说明
|
|
110
|
+
|
|
111
|
+
### list_datasources
|
|
112
|
+
|
|
113
|
+
列出所有已配置的数据源信息。密码字段显示为 `***`。
|
|
114
|
+
|
|
115
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
116
|
+
|------|------|------|------|
|
|
117
|
+
| (无参数) | | | |
|
|
118
|
+
|
|
119
|
+
返回示例:
|
|
120
|
+
|
|
121
|
+
```json
|
|
122
|
+
[
|
|
123
|
+
{
|
|
124
|
+
"name": "ds_0",
|
|
125
|
+
"host": "localhost",
|
|
126
|
+
"port": 5432,
|
|
127
|
+
"database": "shop_db_0",
|
|
128
|
+
"username": "postgres",
|
|
129
|
+
"password": "***"
|
|
130
|
+
}
|
|
131
|
+
]
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### list_tables
|
|
135
|
+
|
|
136
|
+
列出指定数据源中的所有用户表和视图。
|
|
137
|
+
|
|
138
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
139
|
+
|------|------|------|------|
|
|
140
|
+
| datasource | string | 是 | 数据源名称,如 `ds_0` |
|
|
141
|
+
| schema | string | 否 | Schema 名称,默认 `public` |
|
|
142
|
+
|
|
143
|
+
返回示例:
|
|
144
|
+
|
|
145
|
+
```json
|
|
146
|
+
[
|
|
147
|
+
{ "table_name": "tbl_order_0", "table_type": "BASE TABLE" },
|
|
148
|
+
{ "table_name": "tbl_order_1", "table_type": "BASE TABLE" }
|
|
149
|
+
]
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### describe_table
|
|
153
|
+
|
|
154
|
+
查询指定表的详细结构,包含列信息、约束和统计数据。
|
|
155
|
+
|
|
156
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
157
|
+
|------|------|------|------|
|
|
158
|
+
| datasource | string | 是 | 数据源名称 |
|
|
159
|
+
| table | string | 是 | 表名 |
|
|
160
|
+
| schema | string | 否 | Schema 名称,默认 `public` |
|
|
161
|
+
|
|
162
|
+
返回示例:
|
|
163
|
+
|
|
164
|
+
```json
|
|
165
|
+
{
|
|
166
|
+
"columns": [
|
|
167
|
+
{
|
|
168
|
+
"column_name": "id",
|
|
169
|
+
"data_type": "bigint",
|
|
170
|
+
"is_nullable": "NO",
|
|
171
|
+
"column_default": "nextval('tbl_order_0_id_seq'::regclass)",
|
|
172
|
+
"character_maximum_length": null,
|
|
173
|
+
"ordinal_position": 1,
|
|
174
|
+
"comment": "主键ID"
|
|
175
|
+
}
|
|
176
|
+
],
|
|
177
|
+
"constraints": [
|
|
178
|
+
{
|
|
179
|
+
"constraint_name": "tbl_order_0_pkey",
|
|
180
|
+
"constraint_type": "PRIMARY KEY",
|
|
181
|
+
"columns": "id"
|
|
182
|
+
}
|
|
183
|
+
],
|
|
184
|
+
"stats": {
|
|
185
|
+
"estimated_rows": 15000,
|
|
186
|
+
"total_size": "2048 kB",
|
|
187
|
+
"table_size": "1536 kB",
|
|
188
|
+
"index_size": "512 kB"
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### describe_indexes
|
|
194
|
+
|
|
195
|
+
查询指定表的所有索引信息。
|
|
196
|
+
|
|
197
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
198
|
+
|------|------|------|------|
|
|
199
|
+
| datasource | string | 是 | 数据源名称 |
|
|
200
|
+
| table | string | 是 | 表名 |
|
|
201
|
+
| schema | string | 否 | Schema 名称,默认 `public` |
|
|
202
|
+
|
|
203
|
+
返回示例:
|
|
204
|
+
|
|
205
|
+
```json
|
|
206
|
+
[
|
|
207
|
+
{
|
|
208
|
+
"index_name": "tbl_order_0_pkey",
|
|
209
|
+
"index_columns": "id",
|
|
210
|
+
"is_unique": true,
|
|
211
|
+
"index_method": "btree",
|
|
212
|
+
"index_definition": "CREATE UNIQUE INDEX tbl_order_0_pkey ON public.tbl_order_0 USING btree (id)"
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
"index_name": "idx_order_corporation",
|
|
216
|
+
"index_columns": "corporation_id",
|
|
217
|
+
"is_unique": false,
|
|
218
|
+
"index_method": "btree",
|
|
219
|
+
"index_definition": "CREATE INDEX idx_order_corporation ON public.tbl_order_0 USING btree (corporation_id)"
|
|
220
|
+
}
|
|
221
|
+
]
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### sharding_topology
|
|
225
|
+
|
|
226
|
+
查看分片拓扑信息。这是纯配置解析操作,不连接数据库。
|
|
227
|
+
|
|
228
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
229
|
+
|------|------|------|------|
|
|
230
|
+
| table | string | 否 | 逻辑表名(不传则返回所有表的拓扑) |
|
|
231
|
+
|
|
232
|
+
返回示例:
|
|
233
|
+
|
|
234
|
+
```json
|
|
235
|
+
[
|
|
236
|
+
{
|
|
237
|
+
"logicalTable": "tbl_order",
|
|
238
|
+
"actualDataNodes": [
|
|
239
|
+
"ds_0.tbl_order_0",
|
|
240
|
+
"ds_0.tbl_order_1",
|
|
241
|
+
"ds_1.tbl_order_0",
|
|
242
|
+
"ds_1.tbl_order_1"
|
|
243
|
+
],
|
|
244
|
+
"nodeCount": 4,
|
|
245
|
+
"tableStrategy": "standard",
|
|
246
|
+
"shardingColumn": "order_id",
|
|
247
|
+
"algorithmName": "tbl_order_inline",
|
|
248
|
+
"algorithmType": "INLINE",
|
|
249
|
+
"algorithmExpression": "tbl_order_$->{order_id % 2}",
|
|
250
|
+
"databaseStrategy": "standard",
|
|
251
|
+
"databaseShardingColumn": "corporation_id"
|
|
252
|
+
}
|
|
253
|
+
]
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### query_metadata
|
|
257
|
+
|
|
258
|
+
执行自定义的元数据查询。仅允许查询 `information_schema` 或 `pg_catalog` 的 SELECT 语句。
|
|
259
|
+
|
|
260
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
261
|
+
|------|------|------|------|
|
|
262
|
+
| datasource | string | 是 | 数据源名称 |
|
|
263
|
+
| sql | string | 是 | SQL 查询语句 |
|
|
264
|
+
|
|
265
|
+
安全限制:
|
|
266
|
+
- 必须以 `SELECT` 开头
|
|
267
|
+
- 不允许包含分号(禁止多语句)
|
|
268
|
+
- 不允许包含写操作关键字(INSERT/UPDATE/DELETE/DROP/ALTER 等)
|
|
269
|
+
- `FROM` 子句必须直接引用 `information_schema` 或 `pg_catalog`
|
|
270
|
+
|
|
271
|
+
合法查询示例:
|
|
272
|
+
|
|
273
|
+
```sql
|
|
274
|
+
SELECT column_name, data_type FROM information_schema.columns WHERE table_name = 'tbl_order_0'
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
```sql
|
|
278
|
+
SELECT relname, reltuples FROM pg_catalog.pg_class WHERE relkind = 'r'
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## 安全机制
|
|
282
|
+
|
|
283
|
+
本工具采用多层防御策略确保数据安全:
|
|
284
|
+
|
|
285
|
+
| 层级 | 措施 | 说明 |
|
|
286
|
+
|------|------|------|
|
|
287
|
+
| 连接层 | `default_transaction_read_only=on` | pg.Pool 级别强制只读,即使 SQL 注入也无法写入 |
|
|
288
|
+
| SQL 层 | 白名单过滤 | query_metadata 仅允许 SELECT + information_schema/pg_catalog |
|
|
289
|
+
| Tool 层 | 固定查询 | 预定义 tool 只执行硬编码的参数化查询 |
|
|
290
|
+
| 输出层 | 密码遮蔽 | 所有输出中密码显示为 `***` |
|
|
291
|
+
|
|
292
|
+
## 项目结构
|
|
293
|
+
|
|
294
|
+
```
|
|
295
|
+
src/
|
|
296
|
+
├── index.ts # MCP Server 入口,组装所有组件
|
|
297
|
+
├── config/
|
|
298
|
+
│ ├── types.ts # Zod 配置 schema 和 TypeScript 类型定义
|
|
299
|
+
│ └── parser.ts # YAML 解析、DataNodes 展开、JDBC URL 解析
|
|
300
|
+
├── db/
|
|
301
|
+
│ ├── connection-manager.ts # 多数据源连接池管理(懒初始化、并发安全)
|
|
302
|
+
│ └── metadata-queries.ts # PostgreSQL 元数据查询 SQL 封装
|
|
303
|
+
└── tools/
|
|
304
|
+
├── list-datasources.ts # Tool: 列出数据源
|
|
305
|
+
├── list-tables.ts # Tool: 列出表
|
|
306
|
+
├── describe-table.ts # Tool: 表结构详情
|
|
307
|
+
├── describe-indexes.ts # Tool: 索引详情
|
|
308
|
+
├── sharding-topology.ts # Tool: 分片拓扑
|
|
309
|
+
└── query-metadata.ts # Tool: 通用元数据查询(带安全过滤)
|
|
310
|
+
config/
|
|
311
|
+
├── datasource.yaml # 实际配置文件(.gitignore 忽略,含密码)
|
|
312
|
+
└── datasource.example.yaml # 示例配置文件
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
## 常用场景
|
|
316
|
+
|
|
317
|
+
### 了解数据库全貌
|
|
318
|
+
|
|
319
|
+
1. 调用 `list_datasources` 查看有哪些数据源
|
|
320
|
+
2. 调用 `sharding_topology` 了解分片规则和数据分布
|
|
321
|
+
3. 对感兴趣的数据源调用 `list_tables` 查看有哪些表
|
|
322
|
+
|
|
323
|
+
### 了解某张表的结构
|
|
324
|
+
|
|
325
|
+
1. 调用 `describe_table` 查看列定义、约束和统计信息
|
|
326
|
+
2. 调用 `describe_indexes` 查看索引设计
|
|
327
|
+
|
|
328
|
+
### 跨分片对比表结构
|
|
329
|
+
|
|
330
|
+
1. 调用 `sharding_topology` 获取逻辑表对应的所有物理节点
|
|
331
|
+
2. 对不同数据源的分片表分别调用 `describe_table` 对比结构差异
|
|
332
|
+
|
|
333
|
+
### 自定义元数据查询
|
|
334
|
+
|
|
335
|
+
使用 `query_metadata` 执行灵活的 information_schema / pg_catalog 查询,例如:
|
|
336
|
+
- 查找所有包含某列名的表
|
|
337
|
+
- 统计各 schema 下的表数量
|
|
338
|
+
- 查看表的存储参数
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
dataSources:
|
|
2
|
+
ds_0:
|
|
3
|
+
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
|
|
4
|
+
driverClassName: org.postgresql.Driver
|
|
5
|
+
jdbcUrl: jdbc:postgresql://localhost:5432/shop_db_0
|
|
6
|
+
username: postgres
|
|
7
|
+
password: your_password_here
|
|
8
|
+
ds_1:
|
|
9
|
+
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
|
|
10
|
+
driverClassName: org.postgresql.Driver
|
|
11
|
+
jdbcUrl: jdbc:postgresql://localhost:5432/shop_db_1
|
|
12
|
+
username: postgres
|
|
13
|
+
password: your_password_here
|
|
14
|
+
|
|
15
|
+
rules:
|
|
16
|
+
- !SHARDING
|
|
17
|
+
tables:
|
|
18
|
+
tbl_order_master:
|
|
19
|
+
actualDataNodes: ds_$->{0..1}.tbl_order_master_$->{0..1}
|
|
20
|
+
tableStrategy:
|
|
21
|
+
standard:
|
|
22
|
+
shardingColumn: order_id
|
|
23
|
+
shardingAlgorithmName: tbl_order_master_inline
|
|
24
|
+
tbl_order_detail:
|
|
25
|
+
actualDataNodes: ds_$->{0..1}.tbl_order_detail_$->{0..1}
|
|
26
|
+
tableStrategy:
|
|
27
|
+
standard:
|
|
28
|
+
shardingColumn: order_id
|
|
29
|
+
shardingAlgorithmName: tbl_order_detail_inline
|
|
30
|
+
tbl_shop:
|
|
31
|
+
actualDataNodes: ds_$->{0..1}.tbl_shop_$->{0..1}
|
|
32
|
+
tableStrategy:
|
|
33
|
+
standard:
|
|
34
|
+
shardingColumn: shop_id
|
|
35
|
+
shardingAlgorithmName: tbl_shop_inline
|
|
36
|
+
defaultShardingColumn: corporation_id
|
|
37
|
+
defaultDatabaseStrategy:
|
|
38
|
+
standard:
|
|
39
|
+
shardingColumn: corporation_id
|
|
40
|
+
shardingAlgorithmName: database_inline
|
|
41
|
+
defaultTableStrategy:
|
|
42
|
+
none:
|
|
43
|
+
shardingAlgorithms:
|
|
44
|
+
database_inline:
|
|
45
|
+
type: INLINE
|
|
46
|
+
props:
|
|
47
|
+
algorithm-expression: ds_$->{corporation_id % 2}
|
|
48
|
+
tbl_order_master_inline:
|
|
49
|
+
type: INLINE
|
|
50
|
+
props:
|
|
51
|
+
algorithm-expression: tbl_order_master_$->{order_id % 2}
|
|
52
|
+
tbl_order_detail_inline:
|
|
53
|
+
type: INLINE
|
|
54
|
+
props:
|
|
55
|
+
algorithm-expression: tbl_order_detail_$->{order_id % 2}
|
|
56
|
+
tbl_shop_inline:
|
|
57
|
+
type: INLINE
|
|
58
|
+
props:
|
|
59
|
+
algorithm-expression: tbl_shop_$->{shop_id % 2}
|
|
60
|
+
|
|
61
|
+
props:
|
|
62
|
+
sql-show: true
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* YAML 配置文件解析器
|
|
3
|
+
*
|
|
4
|
+
* 负责将 ShardingSphere 风格的 YAML 配置文件解析为 ParsedConfig 结构。
|
|
5
|
+
*
|
|
6
|
+
* 核心难点:
|
|
7
|
+
* 1. ShardingSphere YAML 使用自定义标签 `!SHARDING`,标准 YAML 解析器不认识该标签
|
|
8
|
+
* 解决: 使用 yaml 库的 customTags 将 `!SHARDING` 标签的 map 解析为普通 JS 对象
|
|
9
|
+
* 2. actualDataNodes 使用 `$->{start..end}` 语法表示范围,需展开为笛卡尔积
|
|
10
|
+
* 示例: ds_$->{0..1}.tbl_$->{0..2} 展开为 6 个节点
|
|
11
|
+
* 3. JDBC URL 格式 `jdbc:postgresql://host:port/db` 需要提取为 pg.Pool 可用的参数
|
|
12
|
+
*/
|
|
13
|
+
import { type ParsedConfig } from './types.js';
|
|
14
|
+
/**
|
|
15
|
+
* 解析 YAML 配置文件
|
|
16
|
+
*
|
|
17
|
+
* 解析流程:
|
|
18
|
+
* 1. 读取 YAML 文件,使用自定义标签处理器解析 `!SHARDING` 标签
|
|
19
|
+
* 2. 逐个验证 dataSources 中的数据源配置(Zod 校验)
|
|
20
|
+
* 3. 从 rules 数组中查找分片规则(通过检测 tables/shardingAlgorithms 等键来识别)
|
|
21
|
+
*
|
|
22
|
+
* @param filePath YAML 配置文件路径
|
|
23
|
+
* @returns 解析后的配置对象
|
|
24
|
+
* @throws Zod 验证错误(如必填字段缺失)或文件读取错误
|
|
25
|
+
*/
|
|
26
|
+
export declare function parseConfig(filePath: string): ParsedConfig;
|
|
27
|
+
/**
|
|
28
|
+
* 展开 ShardingSphere 的 actualDataNodes 表达式为具体节点列表
|
|
29
|
+
*
|
|
30
|
+
* 表达式语法: 使用 `$->{start..end}` 表示数字范围
|
|
31
|
+
* 支持多段范围,结果为所有范围的笛卡尔积
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* expandDataNodes('ds_$->{0..1}.tbl_order_$->{0..1}')
|
|
35
|
+
* // 结果: ['ds_0.tbl_order_0', 'ds_0.tbl_order_1', 'ds_1.tbl_order_0', 'ds_1.tbl_order_1']
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* expandDataNodes('ds_0.tbl_config') // 无范围表达式,原样返回
|
|
39
|
+
* // 结果: ['ds_0.tbl_config']
|
|
40
|
+
*
|
|
41
|
+
* @param expression ShardingSphere actualDataNodes 表达式
|
|
42
|
+
* @returns 展开后的所有数据节点列表,格式为 "数据源名.表名"
|
|
43
|
+
*/
|
|
44
|
+
export declare function expandDataNodes(expression: string): string[];
|
|
45
|
+
/**
|
|
46
|
+
* 从 JDBC URL 中提取 PostgreSQL 连接参数
|
|
47
|
+
*
|
|
48
|
+
* 支持的格式:
|
|
49
|
+
* - jdbc:postgresql://host:port/database
|
|
50
|
+
* - jdbc:postgresql://host/database (端口默认 5432)
|
|
51
|
+
* - postgresql://host:port/database (无 jdbc: 前缀)
|
|
52
|
+
*
|
|
53
|
+
* @param jdbcUrl JDBC 格式的数据库连接 URL
|
|
54
|
+
* @returns { host, port, database } 供 pg.Pool 使用的连接参数
|
|
55
|
+
* @throws 当 URL 格式不匹配时抛出错误
|
|
56
|
+
*/
|
|
57
|
+
export declare function parseJdbcUrl(jdbcUrl: string): {
|
|
58
|
+
host: string;
|
|
59
|
+
port: number;
|
|
60
|
+
database: string;
|
|
61
|
+
};
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* YAML 配置文件解析器
|
|
3
|
+
*
|
|
4
|
+
* 负责将 ShardingSphere 风格的 YAML 配置文件解析为 ParsedConfig 结构。
|
|
5
|
+
*
|
|
6
|
+
* 核心难点:
|
|
7
|
+
* 1. ShardingSphere YAML 使用自定义标签 `!SHARDING`,标准 YAML 解析器不认识该标签
|
|
8
|
+
* 解决: 使用 yaml 库的 customTags 将 `!SHARDING` 标签的 map 解析为普通 JS 对象
|
|
9
|
+
* 2. actualDataNodes 使用 `$->{start..end}` 语法表示范围,需展开为笛卡尔积
|
|
10
|
+
* 示例: ds_$->{0..1}.tbl_$->{0..2} 展开为 6 个节点
|
|
11
|
+
* 3. JDBC URL 格式 `jdbc:postgresql://host:port/db` 需要提取为 pg.Pool 可用的参数
|
|
12
|
+
*/
|
|
13
|
+
import fs from 'node:fs';
|
|
14
|
+
import { parse as parseYaml, YAMLMap } from 'yaml';
|
|
15
|
+
import { DataSourceConfigSchema, ShardingRuleConfigSchema, } from './types.js';
|
|
16
|
+
/**
|
|
17
|
+
* 解析 YAML 配置文件
|
|
18
|
+
*
|
|
19
|
+
* 解析流程:
|
|
20
|
+
* 1. 读取 YAML 文件,使用自定义标签处理器解析 `!SHARDING` 标签
|
|
21
|
+
* 2. 逐个验证 dataSources 中的数据源配置(Zod 校验)
|
|
22
|
+
* 3. 从 rules 数组中查找分片规则(通过检测 tables/shardingAlgorithms 等键来识别)
|
|
23
|
+
*
|
|
24
|
+
* @param filePath YAML 配置文件路径
|
|
25
|
+
* @returns 解析后的配置对象
|
|
26
|
+
* @throws Zod 验证错误(如必填字段缺失)或文件读取错误
|
|
27
|
+
*/
|
|
28
|
+
export function parseConfig(filePath) {
|
|
29
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
30
|
+
const raw = parseYaml(content, {
|
|
31
|
+
customTags: [
|
|
32
|
+
{
|
|
33
|
+
// ShardingSphere 使用 `!SHARDING` YAML 标签标记分片规则
|
|
34
|
+
// 这里将其解析为普通 JS 对象,后续由 Zod schema 验证
|
|
35
|
+
tag: '!SHARDING',
|
|
36
|
+
collection: 'map',
|
|
37
|
+
resolve(value) {
|
|
38
|
+
if (value instanceof YAMLMap) {
|
|
39
|
+
return value.toJSON();
|
|
40
|
+
}
|
|
41
|
+
return value;
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
});
|
|
46
|
+
// 解析数据源配置: 每个数据源独立用 Zod 验证
|
|
47
|
+
const rawDs = raw['dataSources'];
|
|
48
|
+
if (!rawDs) {
|
|
49
|
+
throw new Error('配置文件缺少 dataSources 字段');
|
|
50
|
+
}
|
|
51
|
+
const dataSources = {};
|
|
52
|
+
for (const [name, dsConfig] of Object.entries(rawDs)) {
|
|
53
|
+
dataSources[name] = DataSourceConfigSchema.parse(dsConfig);
|
|
54
|
+
}
|
|
55
|
+
// 解析分片规则: rules 是一个数组,其中 `!SHARDING` 标签的元素已被解析为普通对象
|
|
56
|
+
// 通过检测对象中是否包含分片相关的键来识别分片规则(而非依赖 YAML 标签信息)
|
|
57
|
+
let shardingRule;
|
|
58
|
+
const rules = raw['rules'];
|
|
59
|
+
if (rules && Array.isArray(rules)) {
|
|
60
|
+
for (const rule of rules) {
|
|
61
|
+
if (rule && typeof rule === 'object') {
|
|
62
|
+
const ruleObj = rule;
|
|
63
|
+
if (ruleObj['tables'] || ruleObj['shardingAlgorithms'] || ruleObj['defaultShardingColumn']) {
|
|
64
|
+
shardingRule = ShardingRuleConfigSchema.parse(ruleObj);
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const props = raw['props'];
|
|
71
|
+
return { dataSources, shardingRule, props };
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* 展开 ShardingSphere 的 actualDataNodes 表达式为具体节点列表
|
|
75
|
+
*
|
|
76
|
+
* 表达式语法: 使用 `$->{start..end}` 表示数字范围
|
|
77
|
+
* 支持多段范围,结果为所有范围的笛卡尔积
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* expandDataNodes('ds_$->{0..1}.tbl_order_$->{0..1}')
|
|
81
|
+
* // 结果: ['ds_0.tbl_order_0', 'ds_0.tbl_order_1', 'ds_1.tbl_order_0', 'ds_1.tbl_order_1']
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* expandDataNodes('ds_0.tbl_config') // 无范围表达式,原样返回
|
|
85
|
+
* // 结果: ['ds_0.tbl_config']
|
|
86
|
+
*
|
|
87
|
+
* @param expression ShardingSphere actualDataNodes 表达式
|
|
88
|
+
* @returns 展开后的所有数据节点列表,格式为 "数据源名.表名"
|
|
89
|
+
*/
|
|
90
|
+
export function expandDataNodes(expression) {
|
|
91
|
+
// 匹配 $->{start..end} 模式,如 $->{0..3}
|
|
92
|
+
const rangePattern = /\$->\{(\d+)\.\.(\d+)\}/g;
|
|
93
|
+
// 收集表达式中的所有范围段
|
|
94
|
+
const ranges = [];
|
|
95
|
+
let match;
|
|
96
|
+
while ((match = rangePattern.exec(expression)) !== null) {
|
|
97
|
+
ranges.push({
|
|
98
|
+
start: parseInt(match[1], 10),
|
|
99
|
+
end: parseInt(match[2], 10),
|
|
100
|
+
match: match[0], // 完整匹配文本,用于后续替换
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
// 无范围表达式时直接返回
|
|
104
|
+
if (ranges.length === 0) {
|
|
105
|
+
return [expression];
|
|
106
|
+
}
|
|
107
|
+
// 为每个范围生成数值数组,然后计算笛卡尔积
|
|
108
|
+
const valueSets = ranges.map(r => {
|
|
109
|
+
const values = [];
|
|
110
|
+
for (let i = r.start; i <= r.end; i++) {
|
|
111
|
+
values.push(i);
|
|
112
|
+
}
|
|
113
|
+
return values;
|
|
114
|
+
});
|
|
115
|
+
const combinations = cartesianProduct(valueSets);
|
|
116
|
+
// 将每种组合代入表达式模板,生成具体节点名
|
|
117
|
+
return combinations.map(combo => {
|
|
118
|
+
let result = expression;
|
|
119
|
+
for (let i = 0; i < ranges.length; i++) {
|
|
120
|
+
result = result.replace(ranges[i].match, String(combo[i]));
|
|
121
|
+
}
|
|
122
|
+
return result;
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* 计算多个数组的笛卡尔积
|
|
127
|
+
* @example cartesianProduct([[0,1], [0,1]]) → [[0,0], [0,1], [1,0], [1,1]]
|
|
128
|
+
*/
|
|
129
|
+
function cartesianProduct(arrays) {
|
|
130
|
+
if (arrays.length === 0)
|
|
131
|
+
return [[]];
|
|
132
|
+
const [first, ...rest] = arrays;
|
|
133
|
+
const restProduct = cartesianProduct(rest);
|
|
134
|
+
const result = [];
|
|
135
|
+
for (const val of first) {
|
|
136
|
+
for (const combo of restProduct) {
|
|
137
|
+
result.push([val, ...combo]);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return result;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* 从 JDBC URL 中提取 PostgreSQL 连接参数
|
|
144
|
+
*
|
|
145
|
+
* 支持的格式:
|
|
146
|
+
* - jdbc:postgresql://host:port/database
|
|
147
|
+
* - jdbc:postgresql://host/database (端口默认 5432)
|
|
148
|
+
* - postgresql://host:port/database (无 jdbc: 前缀)
|
|
149
|
+
*
|
|
150
|
+
* @param jdbcUrl JDBC 格式的数据库连接 URL
|
|
151
|
+
* @returns { host, port, database } 供 pg.Pool 使用的连接参数
|
|
152
|
+
* @throws 当 URL 格式不匹配时抛出错误
|
|
153
|
+
*/
|
|
154
|
+
export function parseJdbcUrl(jdbcUrl) {
|
|
155
|
+
const url = jdbcUrl.replace(/^jdbc:/, '');
|
|
156
|
+
const match = url.match(/postgresql:\/\/([^:/]+)(?::(\d+))?\/([^?]+)/);
|
|
157
|
+
if (!match) {
|
|
158
|
+
throw new Error(`无法解析 JDBC URL: ${jdbcUrl}`);
|
|
159
|
+
}
|
|
160
|
+
return {
|
|
161
|
+
host: match[1],
|
|
162
|
+
port: match[2] ? parseInt(match[2], 10) : 5432,
|
|
163
|
+
database: match[3],
|
|
164
|
+
};
|
|
165
|
+
}
|