@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
|
@@ -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
|
+
};
|