@zz1996/dbhub-dameng 0.1.7 → 1.2.5-dameng.621
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 +126 -137
- package/dbhub.dameng.toml.example +4 -11
- package/dist/{chunk-SQA2ISDE.js → chunk-2JL4KCP5.js} +21 -119
- package/dist/chunk-3RJRH3CN.js +121 -0
- package/dist/chunk-4276LPPU.js +179 -0
- package/dist/chunk-FU2ZJE4E.js +16 -0
- package/dist/chunk-HDPZAIDL.js +155 -0
- package/dist/{chunk-2A2QF3CS.js → chunk-HGU7ILEZ.js} +215 -68
- package/dist/{chunk-IPK7BYBL.js → chunk-JFWX35TB.js} +0 -1
- package/dist/chunk-O3PEWKBB.js +302 -0
- package/dist/dameng-NKF7NZKE.js +250 -0
- package/dist/index.js +461 -251
- package/dist/{mariadb-7F72IRB4.js → mariadb-MMC6WMEG.js} +86 -53
- package/dist/{mysql-A43SL7UM.js → mysql-TESH7KKV.js} +126 -50
- package/dist/{postgres-VFNLBWMF.js → postgres-C24SBQ4I.js} +154 -36
- package/dist/public/assets/index-BJ-1UrcV.css +1 -0
- package/dist/public/assets/index-BdBaFSHd.js +148 -0
- package/dist/public/assets/postgres-BpcazhJg.svg +22 -0
- package/dist/public/assets/sqlserver-ByfFYYpV.svg +11 -0
- package/dist/public/favicon.svg +57 -0
- package/dist/public/index.html +14 -0
- package/dist/public/logo-full-light.svg +58 -0
- package/dist/{registry-XSX2VZTD.js → registry-WVSFVS35.js} +3 -2
- package/dist/{sqlite-IOUAYHGE.js → sqlite-7BNGDMBZ.js} +57 -42
- package/dist/sqlserver-KZPPK67Q.js +1042 -0
- package/package.json +22 -65
- package/dist/chunk-RTB262PR.js +0 -60
- package/dist/chunk-ZNQTMARG.js +0 -127
- package/dist/dameng-5KYI5JBK.js +0 -898
- package/dist/sqlserver-OE4SFH4B.js +0 -587
- package/docs/README.md +0 -14
- package/docs/dameng-connector-plan.md +0 -143
package/README.md
CHANGED
|
@@ -1,183 +1,172 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
| | | | | |
|
|
39
|
-
+------------------+ +--------------+ +------------------+
|
|
40
|
-
MCP Clients MCP Server Databases
|
|
1
|
+
# DBHub Dameng
|
|
2
|
+
|
|
3
|
+
让 AI 直接连接达梦 DM8,查询数据、查看表结构和字段说明。
|
|
4
|
+
基于 [Bytebase DBHub](https://github.com/bytebase/dbhub),增加最小达梦适配。
|
|
5
|
+
使用已发布的 npm 包即可,无需克隆仓库或编译源码。
|
|
6
|
+
|
|
7
|
+
默认只有两个 MCP 工具:
|
|
8
|
+
|
|
9
|
+
| 工具 | 用途 |
|
|
10
|
+
| --- | --- |
|
|
11
|
+
| `search_objects` | 查找 schema、表、视图、字段、索引和存储过程 |
|
|
12
|
+
| `execute_sql` | 执行 SQL;按下方配置只允许读取,最多返回 100 行 |
|
|
13
|
+
|
|
14
|
+
## 最简单:把这份 README 和连接串交给 AI
|
|
15
|
+
|
|
16
|
+
在能够读写本机项目文件的 AI 编程客户端中,发送下面这段话。
|
|
17
|
+
将最后一行替换为自己的连接串;也可以提供已有本地连接配置文件的路径。
|
|
18
|
+
|
|
19
|
+
```text
|
|
20
|
+
请根据以下 README,直接帮我在当前项目配置 DBHub 达梦 MCP,并验证连接:
|
|
21
|
+
https://github.com/zuozh11/dbhub-dameng/blob/main/README.md
|
|
22
|
+
|
|
23
|
+
要求:
|
|
24
|
+
1. 检查 Node.js >= 22.5,以及 npm/npx 是否可用。
|
|
25
|
+
2. 使用 @zz1996/dbhub-dameng@latest,以 stdio 启动。
|
|
26
|
+
3. 创建本机专用的 .agents/dbhub.dameng.toml,开启 readonly = true、max_rows = 100,
|
|
27
|
+
只启用 execute_sql 和 search_objects。不要执行初始化脚本。
|
|
28
|
+
4. 将含真实连接串的文件排除出 Git;如果同名文件已被跟踪,改用仓库外的本地文件。
|
|
29
|
+
不要把密码写进 README、提交记录或最终回复。
|
|
30
|
+
5. 检查当前客户端已有的 MCP 配置,只新增或更新 DBHub 条目,保留其他配置。
|
|
31
|
+
--config 使用配置文件的绝对路径。能识别客户端就直接配置,无法识别时再问我。
|
|
32
|
+
6. 重连 MCP,确认两个工具可用,执行 SELECT 1 AS OK FROM DUAL,
|
|
33
|
+
再用 search_objects 查询当前 schema 下的一个表名;不要读取业务数据或执行写操作。
|
|
34
|
+
如果无法控制客户端重连,请明确告诉我需要在哪一步重连,不要把文件写好当成连接成功。
|
|
35
|
+
7. 完成后说明配置文件位置、验证结果,以及是否还需要我重启客户端。
|
|
36
|
+
|
|
37
|
+
我的数据库连接串:dameng://user:password@host:5236/APP
|
|
41
38
|
```
|
|
42
39
|
|
|
43
|
-
|
|
40
|
+
如果只使用普通聊天网页,AI 可以生成配置,但需要你自己保存到本机并在 MCP 客户端中启用。
|
|
41
|
+
真实连接信息仅提供给你信任的客户端;共享或生产库请使用最小权限只读账号。
|
|
44
42
|
|
|
45
|
-
|
|
46
|
-
- **Multi-Database**: PostgreSQL, MySQL, MariaDB, SQL Server, SQLite, and Dameng/DM8 through a single interface
|
|
47
|
-
- **Multi-Connection**: Connect to multiple databases simultaneously with TOML configuration
|
|
48
|
-
- **Guardrails**: Read-only mode, row limiting, and query timeout to prevent runaway operations
|
|
49
|
-
- **Secure Access**: SSH tunneling and SSL/TLS encryption
|
|
43
|
+
## 连接串怎么写
|
|
50
44
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
## MCP Tools
|
|
56
|
-
|
|
57
|
-
DBHub implements MCP tools for database operations:
|
|
58
|
-
|
|
59
|
-
- **[execute_sql](https://dbhub.ai/tools/execute-sql)**: Execute SQL queries with transaction support and safety controls
|
|
60
|
-
- **[search_objects](https://dbhub.ai/tools/search-objects)**: Search and explore database schemas, tables, columns, indexes, and procedures with progressive disclosure
|
|
61
|
-
- **[Custom Tools](https://dbhub.ai/tools/custom-tools)**: Define reusable, parameterized SQL operations in your `dbhub.toml` configuration file
|
|
62
|
-
|
|
63
|
-
## Workbench
|
|
64
|
-
|
|
65
|
-
DBHub includes a [built-in web interface](https://dbhub.ai/workbench/overview) for interacting with your database tools. It provides a visual way to execute queries, run custom tools, and view request traces without requiring an MCP client.
|
|
66
|
-
|
|
67
|
-

|
|
45
|
+
```text
|
|
46
|
+
dameng://用户名:密码@主机:5236/Schema名称
|
|
47
|
+
```
|
|
68
48
|
|
|
69
|
-
|
|
49
|
+
例如下面是虚构的配置:
|
|
70
50
|
|
|
71
|
-
|
|
51
|
+
```text
|
|
52
|
+
dameng://reader:example_password@127.0.0.1:5236/APP
|
|
53
|
+
```
|
|
72
54
|
|
|
73
|
-
|
|
55
|
+
- 默认端口为 `5236`。
|
|
56
|
+
- 路径中的 `APP` 是 schema,不是数据库实例名;按数据库中的实际大小写填写。
|
|
57
|
+
- 用户名、密码中的特殊字符要进行 URL 编码,例如 `@` → `%40`、`#` → `%23`、`/` → `%2F`。
|
|
58
|
+
已编码的连接串不要重复编码。
|
|
59
|
+
- 启动 MCP 的电脑必须能够访问该地址;内网数据库可能需要先连接 VPN。
|
|
74
60
|
|
|
75
|
-
|
|
61
|
+
## 手动配置
|
|
76
62
|
|
|
77
|
-
|
|
78
|
-
docker run --rm --init \
|
|
79
|
-
--name dbhub \
|
|
80
|
-
--publish 8080:8080 \
|
|
81
|
-
bytebase/dbhub \
|
|
82
|
-
--transport http \
|
|
83
|
-
--port 8080 \
|
|
84
|
-
--dsn "postgres://user:password@localhost:5432/dbname?sslmode=disable"
|
|
85
|
-
```
|
|
63
|
+
### 1. 准备 Node.js
|
|
86
64
|
|
|
87
|
-
|
|
65
|
+
需要 Node.js **>= 22.5**,并能访问 npm registry:
|
|
88
66
|
|
|
89
|
-
```
|
|
90
|
-
|
|
67
|
+
```sh
|
|
68
|
+
node --version
|
|
69
|
+
npm --version
|
|
70
|
+
npx --version
|
|
91
71
|
```
|
|
92
72
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
```bash
|
|
96
|
-
npx -y @zz1996/dbhub-dameng --transport stdio --config ./dbhub.dameng.toml
|
|
97
|
-
```
|
|
73
|
+
### 2. 创建本机数据库配置
|
|
98
74
|
|
|
99
|
-
|
|
75
|
+
创建 `.agents/dbhub.dameng.toml`,替换下面的示例连接串:
|
|
100
76
|
|
|
101
77
|
```toml
|
|
102
78
|
[[sources]]
|
|
103
|
-
id = "
|
|
104
|
-
|
|
105
|
-
dsn = "dameng://${DM8_USER}:${DM8_PASSWORD}@${DM8_HOST}:5236/${DM8_SCHEMA}"
|
|
106
|
-
query_timeout = 30
|
|
107
|
-
lazy = true
|
|
108
|
-
|
|
109
|
-
[[tools]]
|
|
110
|
-
name = "search_objects"
|
|
111
|
-
source = "dm8_readonly"
|
|
79
|
+
id = "default"
|
|
80
|
+
dsn = "dameng://reader:example_password@127.0.0.1:5236/APP"
|
|
112
81
|
|
|
113
82
|
[[tools]]
|
|
114
83
|
name = "execute_sql"
|
|
115
|
-
source = "
|
|
84
|
+
source = "default"
|
|
116
85
|
readonly = true
|
|
117
86
|
max_rows = 100
|
|
87
|
+
|
|
88
|
+
[[tools]]
|
|
89
|
+
name = "search_objects"
|
|
90
|
+
source = "default"
|
|
118
91
|
```
|
|
119
92
|
|
|
120
|
-
|
|
121
|
-
|
|
93
|
+
将 `/.agents/dbhub.dameng.toml` 加入项目的 `.gitignore`,确保该文件未被 Git 跟踪。
|
|
94
|
+
已被跟踪的文件不会因加入 `.gitignore` 自动停止跟踪,此时请改用仓库外的本地配置文件。
|
|
122
95
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
pnpm verify:dameng -- --config ./dbhub.dameng.toml --source dm8_readonly
|
|
126
|
-
```
|
|
96
|
+
也可以使用 `dsn = "${DAMENG_DSN}"`,由客户端向 MCP 进程传入环境变量。
|
|
97
|
+
不要假设从桌面打开的客户端会继承终端里临时 `export` 的变量。
|
|
127
98
|
|
|
128
|
-
|
|
99
|
+
### 3. 添加到 MCP 客户端
|
|
129
100
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
101
|
+
以下示例中的 `/absolute/path/to/project/.agents/dbhub.dameng.toml`
|
|
102
|
+
必须替换为上一步文件的**绝对路径**。已有配置请合并,不要覆盖整个文件。
|
|
103
|
+
|
|
104
|
+
**使用 `mcpServers` JSON 格式的客户端:**
|
|
133
105
|
|
|
134
|
-
|
|
106
|
+
将下面条目合并到客户端的 MCP 配置中;不同客户端的配置文件位置由客户端决定。
|
|
135
107
|
|
|
136
|
-
```
|
|
137
|
-
|
|
108
|
+
```json
|
|
109
|
+
{
|
|
110
|
+
"mcpServers": {
|
|
111
|
+
"dbhub": {
|
|
112
|
+
"command": "npx",
|
|
113
|
+
"args": [
|
|
114
|
+
"--yes",
|
|
115
|
+
"@zz1996/dbhub-dameng@latest",
|
|
116
|
+
"--transport", "stdio",
|
|
117
|
+
"--config", "/absolute/path/to/project/.agents/dbhub.dameng.toml"
|
|
118
|
+
]
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
138
122
|
```
|
|
139
123
|
|
|
140
|
-
|
|
141
|
-
>
|
|
142
|
-
> The HTTP transport also has built-in DNS-rebinding protection: it only accepts requests whose `Host` is loopback, this machine's own hostname/IPs, or a name you allow via [`--allowed-hosts`](https://dbhub.ai/config/command-line#allowed-hosts). If a client behind a reverse proxy or custom DNS name gets a `403`, add that hostname with `--allowed-hosts`.
|
|
124
|
+
**Codex:**
|
|
143
125
|
|
|
144
|
-
|
|
126
|
+
将下面内容合并到项目的 `.codex/config.toml`(项目需受信任),
|
|
127
|
+
或用户级的 `~/.codex/config.toml`:
|
|
145
128
|
|
|
146
|
-
|
|
129
|
+
```toml
|
|
130
|
+
[mcp_servers.dbhub]
|
|
131
|
+
command = "npx"
|
|
132
|
+
args = ["--yes", "@zz1996/dbhub-dameng@latest", "--transport", "stdio", "--config", "/absolute/path/to/project/.agents/dbhub.dameng.toml"]
|
|
133
|
+
```
|
|
147
134
|
|
|
148
|
-
|
|
135
|
+
配置位置和字段可参考 [Codex 官方 MCP 文档](https://developers.openai.com/zh-Hans/docs/extend/mcp)。
|
|
136
|
+
Windows 上若客户端无法直接启动 `npx`,可将 `command` 改为 `npx.cmd`;
|
|
137
|
+
TOML/JSON 中的 Windows 路径可以使用正斜杠,例如 `C:/projects/app/.agents/dbhub.dameng.toml`。
|
|
149
138
|
|
|
150
|
-
|
|
139
|
+
### 4. 重连并验证
|
|
151
140
|
|
|
152
|
-
|
|
141
|
+
在客户端重新连接 MCP,必要时重启客户端。第一次启动需要下载 npm 包。
|
|
142
|
+
让 AI 执行:
|
|
153
143
|
|
|
154
|
-
|
|
144
|
+
```text
|
|
145
|
+
请用 execute_sql 执行 SELECT 1 AS OK FROM DUAL,
|
|
146
|
+
再用 search_objects 查看当前 schema 的一个表名(object_type=table、detail_level=names、limit=1)。
|
|
147
|
+
```
|
|
155
148
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
pnpm install
|
|
149
|
+
看到 SQL 返回 `OK = 1`,且对象查询没有连接错误,即完成基本连通性验证。
|
|
150
|
+
schema 中没有可见表时可以返回空列表。检索字段或索引时,应同时提供 `schema` 和 `table`。
|
|
159
151
|
|
|
160
|
-
|
|
161
|
-
pnpm dev
|
|
152
|
+
终端排查启动问题时,也可以直接运行:
|
|
162
153
|
|
|
163
|
-
|
|
164
|
-
|
|
154
|
+
```sh
|
|
155
|
+
npx --yes @zz1996/dbhub-dameng@latest --transport stdio --config /absolute/path/to/project/.agents/dbhub.dameng.toml
|
|
165
156
|
```
|
|
166
157
|
|
|
167
|
-
|
|
158
|
+
stdio 模式启动后等待 MCP 客户端请求是正常行为,这不是 SQL 交互终端;单纯启动不代表数据库查询已验证。
|
|
168
159
|
|
|
169
|
-
##
|
|
160
|
+
## 更新与边界
|
|
170
161
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
162
|
+
- 使用 `@latest` 的配置在重新启动 MCP 时获取当前版本;已经运行的进程不会热更新。
|
|
163
|
+
- 需要固定版本时,将 `@latest` 换成明确的 npm 版本号。
|
|
164
|
+
- 只读模式不能替代数据库权限,尤其不能隔离数据库函数内部的副作用。
|
|
165
|
+
- 达梦版覆盖普通 SQL 与元数据浏览;不支持包含内部分号的匿名 PL/SQL 块,
|
|
166
|
+
暂不提供达梦的 `explain_sql`、`health_check`。
|
|
174
167
|
|
|
175
|
-
|
|
168
|
+
[npm 包](https://www.npmjs.com/package/@zz1996/dbhub-dameng) ·
|
|
169
|
+
[开发与自动发布说明](https://github.com/zuozh11/dbhub-dameng/blob/main/DAMENG.md) ·
|
|
170
|
+
[上游 DBHub](https://github.com/bytebase/dbhub)
|
|
176
171
|
|
|
177
|
-
|
|
178
|
-
<picture>
|
|
179
|
-
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=bytebase/dbhub&type=date&theme=dark&legend=top-left" />
|
|
180
|
-
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=bytebase/dbhub&type=date&legend=top-left" />
|
|
181
|
-
<img alt="Star History Chart" src="https://api.star-history.com/chart?repos=bytebase/dbhub&type=date&legend=top-left" />
|
|
182
|
-
</picture>
|
|
183
|
-
</a>
|
|
172
|
+
本项目沿用上游 MIT 许可证,详见 [LICENSE](https://github.com/zuozh11/dbhub-dameng/blob/main/LICENSE)。
|
|
@@ -1,20 +1,13 @@
|
|
|
1
|
-
# Example DBHub configuration for the local Dameng/DM8 fork.
|
|
2
|
-
# Do not commit real credentials. Prefer environment variables.
|
|
3
|
-
|
|
4
1
|
[[sources]]
|
|
5
|
-
id = "
|
|
6
|
-
|
|
7
|
-
dsn = "dameng://${DM8_USER}:${DM8_PASSWORD}@${DM8_HOST}:5236/${DM8_SCHEMA}"
|
|
8
|
-
connection_timeout = 5
|
|
9
|
-
query_timeout = 30
|
|
10
|
-
lazy = true
|
|
2
|
+
id = "default"
|
|
3
|
+
dsn = "${DAMENG_DSN}"
|
|
11
4
|
|
|
12
5
|
[[tools]]
|
|
13
6
|
name = "execute_sql"
|
|
14
|
-
source = "
|
|
7
|
+
source = "default"
|
|
15
8
|
readonly = true
|
|
16
9
|
max_rows = 100
|
|
17
10
|
|
|
18
11
|
[[tools]]
|
|
19
12
|
name = "search_objects"
|
|
20
|
-
source = "
|
|
13
|
+
source = "default"
|
|
@@ -91,7 +91,9 @@ var SafeURL = class {
|
|
|
91
91
|
}
|
|
92
92
|
});
|
|
93
93
|
}
|
|
94
|
-
const
|
|
94
|
+
const pathStart = urlString.indexOf("/");
|
|
95
|
+
const authorityEnd = pathStart === -1 ? urlString.length : pathStart;
|
|
96
|
+
const atIndex = urlString.lastIndexOf("@", authorityEnd - 1);
|
|
95
97
|
if (atIndex !== -1) {
|
|
96
98
|
const auth = urlString.substring(0, atIndex);
|
|
97
99
|
urlString = urlString.substring(atIndex + 1);
|
|
@@ -143,118 +145,6 @@ var SafeURL = class {
|
|
|
143
145
|
}
|
|
144
146
|
};
|
|
145
147
|
|
|
146
|
-
// src/utils/dsn-obfuscate.ts
|
|
147
|
-
function parseConnectionInfoFromDSN(dsn) {
|
|
148
|
-
if (!dsn) {
|
|
149
|
-
return null;
|
|
150
|
-
}
|
|
151
|
-
try {
|
|
152
|
-
const type = getDatabaseTypeFromDSN(dsn);
|
|
153
|
-
if (typeof type === "undefined") {
|
|
154
|
-
return null;
|
|
155
|
-
}
|
|
156
|
-
if (type === "sqlite") {
|
|
157
|
-
const prefix = "sqlite:///";
|
|
158
|
-
if (dsn.length > prefix.length) {
|
|
159
|
-
const rawPath = dsn.substring(prefix.length);
|
|
160
|
-
const firstChar = rawPath[0];
|
|
161
|
-
const isWindowsDrive = rawPath.length > 1 && rawPath[1] === ":";
|
|
162
|
-
const isSpecialPath = firstChar === ":" || firstChar === "." || firstChar === "~" || isWindowsDrive;
|
|
163
|
-
return {
|
|
164
|
-
type,
|
|
165
|
-
database: isSpecialPath ? rawPath : "/" + rawPath
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
return { type };
|
|
169
|
-
}
|
|
170
|
-
const url = new SafeURL(dsn);
|
|
171
|
-
const info = { type };
|
|
172
|
-
if (url.hostname) {
|
|
173
|
-
info.host = url.hostname;
|
|
174
|
-
}
|
|
175
|
-
if (url.port) {
|
|
176
|
-
info.port = parseInt(url.port, 10);
|
|
177
|
-
}
|
|
178
|
-
if (url.pathname && url.pathname.length > 1) {
|
|
179
|
-
info.database = url.pathname.substring(1);
|
|
180
|
-
}
|
|
181
|
-
if (url.username) {
|
|
182
|
-
info.user = url.username;
|
|
183
|
-
}
|
|
184
|
-
return info;
|
|
185
|
-
} catch {
|
|
186
|
-
return null;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
function obfuscateDSNPassword(dsn) {
|
|
190
|
-
if (!dsn) {
|
|
191
|
-
return dsn;
|
|
192
|
-
}
|
|
193
|
-
try {
|
|
194
|
-
const type = getDatabaseTypeFromDSN(dsn);
|
|
195
|
-
if (type === "sqlite") {
|
|
196
|
-
return dsn;
|
|
197
|
-
}
|
|
198
|
-
const url = new SafeURL(dsn);
|
|
199
|
-
if (!url.password) {
|
|
200
|
-
return dsn;
|
|
201
|
-
}
|
|
202
|
-
const obfuscatedPassword = "*".repeat(Math.min(url.password.length, 8));
|
|
203
|
-
const protocol = dsn.split(":")[0];
|
|
204
|
-
let result;
|
|
205
|
-
if (url.username) {
|
|
206
|
-
result = `${protocol}://${url.username}:${obfuscatedPassword}@${url.hostname}`;
|
|
207
|
-
} else {
|
|
208
|
-
result = `${protocol}://${obfuscatedPassword}@${url.hostname}`;
|
|
209
|
-
}
|
|
210
|
-
if (url.port) {
|
|
211
|
-
result += `:${url.port}`;
|
|
212
|
-
}
|
|
213
|
-
result += url.pathname;
|
|
214
|
-
if (url.searchParams.size > 0) {
|
|
215
|
-
const params = [];
|
|
216
|
-
url.forEachSearchParam((value, key) => {
|
|
217
|
-
params.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`);
|
|
218
|
-
});
|
|
219
|
-
result += `?${params.join("&")}`;
|
|
220
|
-
}
|
|
221
|
-
return result;
|
|
222
|
-
} catch {
|
|
223
|
-
return dsn;
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
function getDatabaseTypeFromDSN(dsn) {
|
|
227
|
-
if (!dsn) {
|
|
228
|
-
return void 0;
|
|
229
|
-
}
|
|
230
|
-
const protocol = dsn.split(":")[0];
|
|
231
|
-
return protocolToConnectorType(protocol);
|
|
232
|
-
}
|
|
233
|
-
function protocolToConnectorType(protocol) {
|
|
234
|
-
const mapping = {
|
|
235
|
-
"postgres": "postgres",
|
|
236
|
-
"postgresql": "postgres",
|
|
237
|
-
"mysql": "mysql",
|
|
238
|
-
"mariadb": "mariadb",
|
|
239
|
-
"sqlserver": "sqlserver",
|
|
240
|
-
"sqlite": "sqlite",
|
|
241
|
-
"dameng": "dameng",
|
|
242
|
-
"dm": "dameng"
|
|
243
|
-
};
|
|
244
|
-
return mapping[protocol];
|
|
245
|
-
}
|
|
246
|
-
function getDefaultPortForType(type) {
|
|
247
|
-
const ports = {
|
|
248
|
-
"postgres": 5432,
|
|
249
|
-
"mysql": 3306,
|
|
250
|
-
"mariadb": 3306,
|
|
251
|
-
"sqlserver": 1433,
|
|
252
|
-
"sqlite": void 0,
|
|
253
|
-
"dameng": 5236
|
|
254
|
-
};
|
|
255
|
-
return ports[type];
|
|
256
|
-
}
|
|
257
|
-
|
|
258
148
|
// src/utils/sql-parser.ts
|
|
259
149
|
var TokenType = { Plain: 0, Comment: 1, QuotedBlock: 2 };
|
|
260
150
|
function plainToken(i) {
|
|
@@ -431,12 +321,12 @@ function scanTokenSQLServer(sql, i) {
|
|
|
431
321
|
return scanSingleLineComment(sql, i) ?? scanMultiLineComment(sql, i) ?? scanSingleQuotedString(sql, i) ?? scanDoubleQuotedString(sql, i) ?? scanBracketQuotedIdentifier(sql, i) ?? plainToken(i);
|
|
432
322
|
}
|
|
433
323
|
var dialectScanners = {
|
|
324
|
+
dameng: scanTokenAnsi,
|
|
434
325
|
postgres: scanTokenPostgres,
|
|
435
326
|
mysql: scanTokenMySQL,
|
|
436
327
|
mariadb: scanTokenMySQL,
|
|
437
328
|
sqlite: scanTokenSQLite,
|
|
438
|
-
sqlserver: scanTokenSQLServer
|
|
439
|
-
dameng: scanTokenAnsi
|
|
329
|
+
sqlserver: scanTokenSQLServer
|
|
440
330
|
};
|
|
441
331
|
function getScanner(dialect) {
|
|
442
332
|
return dialect ? dialectScanners[dialect] ?? scanTokenAnsi : scanTokenAnsi;
|
|
@@ -466,6 +356,21 @@ function stripCommentsAndStrings(sql, dialect) {
|
|
|
466
356
|
}
|
|
467
357
|
return parts.join("");
|
|
468
358
|
}
|
|
359
|
+
function blankCommentsAndStrings(sql, dialect) {
|
|
360
|
+
const scanToken = getScanner(dialect);
|
|
361
|
+
let result = "";
|
|
362
|
+
let i = 0;
|
|
363
|
+
while (i < sql.length) {
|
|
364
|
+
const token = scanToken(sql, i);
|
|
365
|
+
if (token.type === TokenType.Plain) {
|
|
366
|
+
result += sql[i];
|
|
367
|
+
} else {
|
|
368
|
+
result += " ".repeat(token.end - i);
|
|
369
|
+
}
|
|
370
|
+
i = token.end;
|
|
371
|
+
}
|
|
372
|
+
return result;
|
|
373
|
+
}
|
|
469
374
|
function splitSQLStatements(sql, dialect) {
|
|
470
375
|
const scanToken = getScanner(dialect);
|
|
471
376
|
const statements = [];
|
|
@@ -494,10 +399,7 @@ function splitSQLStatements(sql, dialect) {
|
|
|
494
399
|
export {
|
|
495
400
|
ConnectorRegistry,
|
|
496
401
|
SafeURL,
|
|
497
|
-
parseConnectionInfoFromDSN,
|
|
498
|
-
obfuscateDSNPassword,
|
|
499
|
-
getDatabaseTypeFromDSN,
|
|
500
|
-
getDefaultPortForType,
|
|
501
402
|
stripCommentsAndStrings,
|
|
403
|
+
blankCommentsAndStrings,
|
|
502
404
|
splitSQLStatements
|
|
503
405
|
};
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SafeURL
|
|
3
|
+
} from "./chunk-2JL4KCP5.js";
|
|
4
|
+
|
|
5
|
+
// src/utils/dsn-obfuscate.ts
|
|
6
|
+
function parseConnectionInfoFromDSN(dsn) {
|
|
7
|
+
if (!dsn) {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
try {
|
|
11
|
+
const type = getDatabaseTypeFromDSN(dsn);
|
|
12
|
+
if (typeof type === "undefined") {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
if (type === "sqlite") {
|
|
16
|
+
const prefix = "sqlite:///";
|
|
17
|
+
if (dsn.length > prefix.length) {
|
|
18
|
+
const rawPath = dsn.substring(prefix.length);
|
|
19
|
+
const firstChar = rawPath[0];
|
|
20
|
+
const isWindowsDrive = rawPath.length > 1 && rawPath[1] === ":";
|
|
21
|
+
const isSpecialPath = firstChar === ":" || firstChar === "." || firstChar === "~" || isWindowsDrive;
|
|
22
|
+
return {
|
|
23
|
+
type,
|
|
24
|
+
database: isSpecialPath ? rawPath : "/" + rawPath
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
return { type };
|
|
28
|
+
}
|
|
29
|
+
const url = new SafeURL(dsn);
|
|
30
|
+
const info = { type };
|
|
31
|
+
if (url.hostname) {
|
|
32
|
+
info.host = url.hostname;
|
|
33
|
+
}
|
|
34
|
+
if (url.port) {
|
|
35
|
+
info.port = parseInt(url.port, 10);
|
|
36
|
+
}
|
|
37
|
+
if (url.pathname && url.pathname.length > 1) {
|
|
38
|
+
info.database = url.pathname.substring(1);
|
|
39
|
+
}
|
|
40
|
+
if (url.username) {
|
|
41
|
+
info.user = url.username;
|
|
42
|
+
}
|
|
43
|
+
return info;
|
|
44
|
+
} catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function obfuscateDSNPassword(dsn) {
|
|
49
|
+
if (!dsn) {
|
|
50
|
+
return dsn;
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
const type = getDatabaseTypeFromDSN(dsn);
|
|
54
|
+
if (type === "sqlite") {
|
|
55
|
+
return dsn;
|
|
56
|
+
}
|
|
57
|
+
const url = new SafeURL(dsn);
|
|
58
|
+
if (!url.password) {
|
|
59
|
+
return dsn;
|
|
60
|
+
}
|
|
61
|
+
const obfuscatedPassword = "*".repeat(Math.min(url.password.length, 8));
|
|
62
|
+
const protocol = dsn.split(":")[0];
|
|
63
|
+
let result;
|
|
64
|
+
if (url.username) {
|
|
65
|
+
result = `${protocol}://${url.username}:${obfuscatedPassword}@${url.hostname}`;
|
|
66
|
+
} else {
|
|
67
|
+
result = `${protocol}://${obfuscatedPassword}@${url.hostname}`;
|
|
68
|
+
}
|
|
69
|
+
if (url.port) {
|
|
70
|
+
result += `:${url.port}`;
|
|
71
|
+
}
|
|
72
|
+
result += url.pathname;
|
|
73
|
+
if (url.searchParams.size > 0) {
|
|
74
|
+
const params = [];
|
|
75
|
+
url.forEachSearchParam((value, key) => {
|
|
76
|
+
params.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`);
|
|
77
|
+
});
|
|
78
|
+
result += `?${params.join("&")}`;
|
|
79
|
+
}
|
|
80
|
+
return result;
|
|
81
|
+
} catch {
|
|
82
|
+
return dsn;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function getDatabaseTypeFromDSN(dsn) {
|
|
86
|
+
if (!dsn) {
|
|
87
|
+
return void 0;
|
|
88
|
+
}
|
|
89
|
+
const protocol = dsn.split(":")[0];
|
|
90
|
+
return protocolToConnectorType(protocol);
|
|
91
|
+
}
|
|
92
|
+
function protocolToConnectorType(protocol) {
|
|
93
|
+
const mapping = {
|
|
94
|
+
"dameng": "dameng",
|
|
95
|
+
"postgres": "postgres",
|
|
96
|
+
"postgresql": "postgres",
|
|
97
|
+
"mysql": "mysql",
|
|
98
|
+
"mariadb": "mariadb",
|
|
99
|
+
"sqlserver": "sqlserver",
|
|
100
|
+
"sqlite": "sqlite"
|
|
101
|
+
};
|
|
102
|
+
return mapping[protocol];
|
|
103
|
+
}
|
|
104
|
+
function getDefaultPortForType(type) {
|
|
105
|
+
const ports = {
|
|
106
|
+
"dameng": 5236,
|
|
107
|
+
"postgres": 5432,
|
|
108
|
+
"mysql": 3306,
|
|
109
|
+
"mariadb": 3306,
|
|
110
|
+
"sqlserver": 1433,
|
|
111
|
+
"sqlite": void 0
|
|
112
|
+
};
|
|
113
|
+
return ports[type];
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export {
|
|
117
|
+
parseConnectionInfoFromDSN,
|
|
118
|
+
obfuscateDSNPassword,
|
|
119
|
+
getDatabaseTypeFromDSN,
|
|
120
|
+
getDefaultPortForType
|
|
121
|
+
};
|