@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.
Files changed (32) hide show
  1. package/README.md +126 -137
  2. package/dbhub.dameng.toml.example +4 -11
  3. package/dist/{chunk-SQA2ISDE.js → chunk-2JL4KCP5.js} +21 -119
  4. package/dist/chunk-3RJRH3CN.js +121 -0
  5. package/dist/chunk-4276LPPU.js +179 -0
  6. package/dist/chunk-FU2ZJE4E.js +16 -0
  7. package/dist/chunk-HDPZAIDL.js +155 -0
  8. package/dist/{chunk-2A2QF3CS.js → chunk-HGU7ILEZ.js} +215 -68
  9. package/dist/{chunk-IPK7BYBL.js → chunk-JFWX35TB.js} +0 -1
  10. package/dist/chunk-O3PEWKBB.js +302 -0
  11. package/dist/dameng-NKF7NZKE.js +250 -0
  12. package/dist/index.js +461 -251
  13. package/dist/{mariadb-7F72IRB4.js → mariadb-MMC6WMEG.js} +86 -53
  14. package/dist/{mysql-A43SL7UM.js → mysql-TESH7KKV.js} +126 -50
  15. package/dist/{postgres-VFNLBWMF.js → postgres-C24SBQ4I.js} +154 -36
  16. package/dist/public/assets/index-BJ-1UrcV.css +1 -0
  17. package/dist/public/assets/index-BdBaFSHd.js +148 -0
  18. package/dist/public/assets/postgres-BpcazhJg.svg +22 -0
  19. package/dist/public/assets/sqlserver-ByfFYYpV.svg +11 -0
  20. package/dist/public/favicon.svg +57 -0
  21. package/dist/public/index.html +14 -0
  22. package/dist/public/logo-full-light.svg +58 -0
  23. package/dist/{registry-XSX2VZTD.js → registry-WVSFVS35.js} +3 -2
  24. package/dist/{sqlite-IOUAYHGE.js → sqlite-7BNGDMBZ.js} +57 -42
  25. package/dist/sqlserver-KZPPK67Q.js +1042 -0
  26. package/package.json +22 -65
  27. package/dist/chunk-RTB262PR.js +0 -60
  28. package/dist/chunk-ZNQTMARG.js +0 -127
  29. package/dist/dameng-5KYI5JBK.js +0 -898
  30. package/dist/sqlserver-OE4SFH4B.js +0 -587
  31. package/docs/README.md +0 -14
  32. package/docs/dameng-connector-plan.md +0 -143
@@ -0,0 +1,179 @@
1
+ import {
2
+ computeHitRatioPct,
3
+ toNullableNumber
4
+ } from "./chunk-FU2ZJE4E.js";
5
+ import {
6
+ obfuscateDSNPassword
7
+ } from "./chunk-3RJRH3CN.js";
8
+
9
+ // src/connectors/mysql-family-health-check.ts
10
+ async function getMySQLFamilyHealthCheck(query) {
11
+ const [processlistRows, maxConnRows, bufferPoolRows, trxOutcome] = await Promise.all([
12
+ query(`
13
+ SELECT
14
+ COUNT(*) AS total,
15
+ SUM(CASE WHEN COMMAND != 'Sleep' THEN 1 ELSE 0 END) AS active,
16
+ SUM(CASE WHEN COMMAND = 'Sleep' THEN 1 ELSE 0 END) AS idle,
17
+ MAX(CASE WHEN COMMAND != 'Sleep' THEN TIME ELSE NULL END) AS longest_active_query_seconds
18
+ FROM information_schema.PROCESSLIST
19
+ WHERE ID <> CONNECTION_ID()
20
+ `),
21
+ query(`SHOW VARIABLES LIKE 'max_connections'`),
22
+ query(`
23
+ SHOW GLOBAL STATUS WHERE Variable_name IN ('Innodb_buffer_pool_read_requests', 'Innodb_buffer_pool_reads')
24
+ `),
25
+ // Joining PROCESSLIST against INNODB_TRX to detect idle-in-transaction
26
+ // sessions requires the PROCESS privilege - unlike a plain PROCESSLIST
27
+ // select, which silently narrows to the caller's own session instead of
28
+ // erroring. Run concurrently with the other queries above, but resolve
29
+ // rather than reject on failure so it degrades instead of failing the
30
+ // whole health check.
31
+ query(`
32
+ SELECT
33
+ COUNT(*) AS idle_in_transaction,
34
+ MAX(TIMESTAMPDIFF(SECOND, t.trx_started, NOW())) AS longest_idle_in_transaction_seconds
35
+ FROM information_schema.INNODB_TRX t
36
+ JOIN information_schema.PROCESSLIST p ON p.ID = t.trx_mysql_thread_id
37
+ WHERE p.COMMAND = 'Sleep'
38
+ `).then(
39
+ (rows) => ({ ok: true, rows }),
40
+ () => ({ ok: false })
41
+ )
42
+ ]);
43
+ const notes = [];
44
+ let idleInTransaction = 0;
45
+ let longestIdleInTransactionSeconds = null;
46
+ if (trxOutcome.ok) {
47
+ idleInTransaction = Number(trxOutcome.rows[0].idle_in_transaction);
48
+ longestIdleInTransactionSeconds = toNullableNumber(trxOutcome.rows[0].longest_idle_in_transaction_seconds);
49
+ } else {
50
+ notes.push(
51
+ "Connected user lacks the PROCESS privilege: PROCESSLIST visibility is restricted to the connecting user's own sessions, and this diagnostic session is excluded from the count, so connection counts and active-query duration may under-report (down to 0). Idle-in-transaction detection is unavailable."
52
+ );
53
+ }
54
+ const proc = processlistRows[0];
55
+ const bufferPoolStats = Object.fromEntries(bufferPoolRows.map((row) => [row.Variable_name, row.Value]));
56
+ const readRequests = Number(bufferPoolStats.Innodb_buffer_pool_read_requests ?? 0);
57
+ const reads = Number(bufferPoolStats.Innodb_buffer_pool_reads ?? 0);
58
+ return {
59
+ connections: {
60
+ total: Number(proc.total),
61
+ active: Number(proc.active),
62
+ idle: Number(proc.idle),
63
+ idleInTransaction,
64
+ // MySQL/MariaDB's InnoDB has no equivalent of Postgres's "idle in
65
+ // transaction (aborted)" state - a failed statement doesn't leave the
66
+ // session's transaction in a distinct aborted-but-open state the way
67
+ // Postgres does - so idleInTransactionAborted is left undefined.
68
+ maxConnections: maxConnRows.length > 0 ? Number(maxConnRows[0].Value) : null,
69
+ longestIdleInTransactionSeconds,
70
+ longestActiveQuerySeconds: toNullableNumber(proc.longest_active_query_seconds)
71
+ },
72
+ bufferCache: {
73
+ hitRatioPct: computeHitRatioPct(readRequests, reads),
74
+ blocksHit: readRequests - reads,
75
+ blocksRead: reads
76
+ },
77
+ ...notes.length > 0 ? { notes } : {}
78
+ };
79
+ }
80
+
81
+ // src/utils/dsn-database.ts
82
+ var MissingDatabaseError = class extends Error {
83
+ constructor(message) {
84
+ super(message);
85
+ this.name = "MissingDatabaseError";
86
+ }
87
+ };
88
+ function requireDatabaseInDSN(database, dsn, label) {
89
+ if (database) {
90
+ return;
91
+ }
92
+ throw new MissingDatabaseError(
93
+ `${label} DSN must name a database.
94
+ Provided: ${obfuscateDSNPassword(dsn)}
95
+ Add the database to the DSN, e.g. ...:3306/mydb
96
+ To work with several databases, define one [[sources]] entry per database in a TOML config file: https://dbhub.ai/config/toml`
97
+ );
98
+ }
99
+
100
+ // src/utils/multi-statement-result-parser.ts
101
+ function isMetadataObject(element) {
102
+ if (!element || typeof element !== "object" || Array.isArray(element)) {
103
+ return false;
104
+ }
105
+ return "affectedRows" in element || "insertId" in element || "fieldCount" in element || "warningStatus" in element;
106
+ }
107
+ function isMultiStatementResult(results) {
108
+ if (!Array.isArray(results) || results.length === 0) {
109
+ return false;
110
+ }
111
+ const firstElement = results[0];
112
+ return isMetadataObject(firstElement) || Array.isArray(firstElement);
113
+ }
114
+ function buildResultSetsFromMultiStatement(results, statementTexts) {
115
+ const sql = statementTexts?.length === results.length ? statementTexts : void 0;
116
+ return results.map((result, index) => {
117
+ if (Array.isArray(result)) {
118
+ return { sql: sql?.[index], rows: result, rowCount: result.length };
119
+ }
120
+ if (isMetadataObject(result)) {
121
+ return { sql: sql?.[index], rows: [], rowCount: result.affectedRows || 0 };
122
+ }
123
+ return { sql: sql?.[index], rows: [], rowCount: 0 };
124
+ });
125
+ }
126
+ function parseQueryResultSets(results, statementTexts) {
127
+ const singleStatementSql = statementTexts?.length === 1 ? statementTexts[0] : void 0;
128
+ if (isMetadataObject(results)) {
129
+ return [{ sql: singleStatementSql, rows: [], rowCount: results.affectedRows || 0 }];
130
+ }
131
+ if (!Array.isArray(results)) {
132
+ return [{ sql: singleStatementSql, rows: [], rowCount: 0 }];
133
+ }
134
+ if (isMultiStatementResult(results)) {
135
+ return buildResultSetsFromMultiStatement(results, statementTexts);
136
+ }
137
+ return [{ sql: singleStatementSql, rows: results, rowCount: results.length }];
138
+ }
139
+
140
+ // src/utils/readonly-transaction.ts
141
+ function isClientSideTimeout(error) {
142
+ return typeof error === "object" && error !== null && error.code === "PROTOCOL_SEQUENCE_TIMEOUT";
143
+ }
144
+ async function withReadOnlyTransaction(conn, readonly, supportsReadOnlyTransaction, execute) {
145
+ if (!readonly) {
146
+ return execute();
147
+ }
148
+ try {
149
+ await conn.query(
150
+ supportsReadOnlyTransaction ? "START TRANSACTION READ ONLY" : "START TRANSACTION"
151
+ );
152
+ const result = await execute();
153
+ await conn.query(supportsReadOnlyTransaction ? "COMMIT" : "ROLLBACK");
154
+ return result;
155
+ } catch (error) {
156
+ if (!isClientSideTimeout(error)) {
157
+ try {
158
+ await conn.query("ROLLBACK");
159
+ } catch {
160
+ }
161
+ }
162
+ throw error;
163
+ }
164
+ }
165
+
166
+ // src/utils/server-flavor.ts
167
+ function isTiDBVersion(version) {
168
+ return typeof version === "string" && /tidb/i.test(version);
169
+ }
170
+
171
+ export {
172
+ getMySQLFamilyHealthCheck,
173
+ MissingDatabaseError,
174
+ requireDatabaseInDSN,
175
+ parseQueryResultSets,
176
+ isClientSideTimeout,
177
+ withReadOnlyTransaction,
178
+ isTiDBVersion
179
+ };
@@ -0,0 +1,16 @@
1
+ // src/connectors/health-check-utils.ts
2
+ function toNullableNumber(value) {
3
+ return value === null || value === void 0 ? null : Number(value);
4
+ }
5
+ function computeHitRatioPct(logicalReads, physicalReads) {
6
+ if (!Number.isFinite(logicalReads) || !Number.isFinite(physicalReads) || logicalReads === 0) {
7
+ return null;
8
+ }
9
+ const pct = Math.round((logicalReads - physicalReads) / logicalReads * 1e4) / 100;
10
+ return Math.min(100, Math.max(0, pct));
11
+ }
12
+
13
+ export {
14
+ toNullableNumber,
15
+ computeHitRatioPct
16
+ };
@@ -0,0 +1,155 @@
1
+ import {
2
+ stripCommentsAndStrings
3
+ } from "./chunk-2JL4KCP5.js";
4
+
5
+ // src/utils/allowed-keywords.ts
6
+ var allowedKeywords = {
7
+ dameng: ["select", "with"],
8
+ postgres: ["select", "with", "explain", "show"],
9
+ mysql: ["select", "with", "explain", "show", "describe", "desc"],
10
+ mariadb: ["select", "with", "explain", "show", "describe", "desc"],
11
+ sqlite: ["select", "with", "explain", "pragma"],
12
+ // SQL Server has no native EXPLAIN statement; the connector translates a
13
+ // leading `EXPLAIN` into a SET SHOWPLAN_XML request (see SQLServerConnector).
14
+ sqlserver: ["select", "with", "explain"]
15
+ };
16
+ var mutatingKeywords = [
17
+ "insert",
18
+ "update",
19
+ "delete",
20
+ "drop",
21
+ "alter",
22
+ "create",
23
+ "truncate",
24
+ "merge",
25
+ "grant",
26
+ "revoke",
27
+ "rename"
28
+ ];
29
+ var mutatingPattern = new RegExp(
30
+ `\\b(?:${mutatingKeywords.join("|")})\\b`,
31
+ "i"
32
+ );
33
+ var mutatingPatternWithReplace = new RegExp(
34
+ `\\b(?:${mutatingKeywords.join("|")}|replace\\s+(?:(?:low_priority|delayed)\\s+)?into)\\b`,
35
+ "i"
36
+ );
37
+ var sqlServerDynamicSqlKeywords = [
38
+ "execute",
39
+ "exec",
40
+ "sp_executesql",
41
+ "xp_cmdshell"
42
+ ];
43
+ var sqlServerDynamicSqlPattern = new RegExp(
44
+ `\\b(?:${sqlServerDynamicSqlKeywords.join("|")})\\b`,
45
+ "i"
46
+ );
47
+ var sqlServerPassThroughKeywords = [
48
+ "openquery",
49
+ "openrowset",
50
+ "opendatasource"
51
+ ];
52
+ var sqlServerPassThroughPattern = new RegExp(
53
+ `\\b(?:${sqlServerPassThroughKeywords.join("|")})\\s*\\(`,
54
+ "i"
55
+ );
56
+ var escapeHatchFunctionKeywords = {
57
+ mysql: ["load_file", "get_lock", "release_lock", "release_all_locks"],
58
+ mariadb: ["load_file", "get_lock", "release_lock", "release_all_locks"],
59
+ postgres: ["pg_read_file", "pg_read_binary_file", "pg_ls_dir"],
60
+ sqlserver: sqlServerPassThroughKeywords
61
+ };
62
+ var escapeHatchFunctionPatterns = Object.fromEntries(
63
+ Object.entries(escapeHatchFunctionKeywords).map(([type, keywords]) => [
64
+ type,
65
+ new RegExp(`\\b(?:${keywords.join("|")})\\s*\\(`, "i")
66
+ ])
67
+ );
68
+ function hasEscapeHatchFunction(strippedSQL, connectorType) {
69
+ const pattern = escapeHatchFunctionPatterns[connectorType];
70
+ return pattern !== void 0 && pattern.test(strippedSQL);
71
+ }
72
+ var mutatingPatternSqlServer = new RegExp(
73
+ `\\b(?:${[...mutatingKeywords, ...sqlServerDynamicSqlKeywords].join("|")})\\b`,
74
+ "i"
75
+ );
76
+ var mutatingPatterns = {
77
+ dameng: mutatingPattern,
78
+ postgres: mutatingPattern,
79
+ mysql: mutatingPatternWithReplace,
80
+ mariadb: mutatingPatternWithReplace,
81
+ sqlite: mutatingPatternWithReplace,
82
+ sqlserver: mutatingPatternSqlServer
83
+ };
84
+ function hasMutatingKeyword(strippedSQL, connectorType) {
85
+ return (mutatingPatterns[connectorType] ?? mutatingPattern).test(strippedSQL);
86
+ }
87
+ var selectIntoPattern = /\bselect\b[\s\S]+\binto\b/i;
88
+ var sqliteReadOnlyArgPragmas = /* @__PURE__ */ new Set([
89
+ "table_info",
90
+ "index_info",
91
+ "index_list",
92
+ "foreign_key_list"
93
+ ]);
94
+ var sqlitePragmaParenPattern = /^pragma\s+(?:[a-z0-9_]+\.)?([a-z0-9_]+)\s*\(/;
95
+ var explainAnalyzePattern = /^explain\s+(?:\([^)]*\banalyze\b(?!\s*(?:=\s*)?(?:false|off|0)\b)[^)]*\)|\banalyze\b(?!\s*(?:=\s*)?(?:false|off|0)\b)(?:\s+verbose\b)?)/i;
96
+ function isReadOnlySQL(sql, connectorType) {
97
+ return checkReadOnly(
98
+ stripCommentsAndStrings(sql, connectorType).trim().toLowerCase(),
99
+ connectorType
100
+ );
101
+ }
102
+ function getFirstKeyword(sql, connectorType) {
103
+ const cleaned = stripCommentsAndStrings(sql, connectorType).trim();
104
+ return cleaned.match(/\S+/)?.[0]?.toLowerCase() ?? "";
105
+ }
106
+ function checkReadOnly(cleanedSQL, connectorType) {
107
+ if (!cleanedSQL) {
108
+ return false;
109
+ }
110
+ const firstWord = cleanedSQL.match(/\S+/)?.[0] ?? "";
111
+ const keywordList = allowedKeywords[connectorType] || [];
112
+ if (!keywordList.includes(firstWord)) {
113
+ return false;
114
+ }
115
+ if (hasEscapeHatchFunction(cleanedSQL, connectorType)) {
116
+ return false;
117
+ }
118
+ if (firstWord === "with" && hasMutatingKeyword(cleanedSQL, connectorType)) {
119
+ return false;
120
+ }
121
+ if (firstWord === "pragma" && connectorType === "sqlite") {
122
+ if (cleanedSQL.includes("=")) {
123
+ return false;
124
+ }
125
+ const parenMatch = cleanedSQL.match(sqlitePragmaParenPattern);
126
+ if (parenMatch && !sqliteReadOnlyArgPragmas.has(parenMatch[1])) {
127
+ return false;
128
+ }
129
+ }
130
+ if ((firstWord === "select" || firstWord === "with") && selectIntoPattern.test(cleanedSQL)) {
131
+ return false;
132
+ }
133
+ if (firstWord === "explain") {
134
+ const m = explainAnalyzePattern.exec(cleanedSQL);
135
+ if (m) {
136
+ const afterExplain = cleanedSQL.slice(m[0].length).trim();
137
+ if (afterExplain && !checkReadOnly(afterExplain, connectorType)) {
138
+ return false;
139
+ }
140
+ }
141
+ }
142
+ return true;
143
+ }
144
+
145
+ export {
146
+ allowedKeywords,
147
+ sqlServerDynamicSqlKeywords,
148
+ sqlServerDynamicSqlPattern,
149
+ sqlServerPassThroughKeywords,
150
+ sqlServerPassThroughPattern,
151
+ hasEscapeHatchFunction,
152
+ hasMutatingKeyword,
153
+ isReadOnlySQL,
154
+ getFirstKeyword
155
+ };