@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,302 @@
1
+ import {
2
+ hasMutatingKeyword
3
+ } from "./chunk-HDPZAIDL.js";
4
+ import {
5
+ blankCommentsAndStrings
6
+ } from "./chunk-2JL4KCP5.js";
7
+
8
+ // src/utils/sql-row-limiter.ts
9
+ var SQLRowLimiter = class {
10
+ /**
11
+ * Check if a SQL statement is a row-returning query that can benefit from row limiting.
12
+ *
13
+ * Classification runs on the comment-blanked text, so a query introduced by a
14
+ * `--` or `/* ... *\/` comment (a query tag, say) is classified by its real
15
+ * leading keyword. The SQL sent to the server is never rewritten by this
16
+ * check, so an attribution comment survives verbatim.
17
+ *
18
+ * `WITH` leads a CTE whose final statement is normally a SELECT, so it is
19
+ * limitable too, except for a data-modifying CTE
20
+ * (`WITH x AS (DELETE ... RETURNING *) SELECT ...`), where a LIMIT would cap
21
+ * the rows handed back while the write still runs in full. Detection there is
22
+ * the same keyword heuristic the read-only classifier uses, so a false
23
+ * positive only means the statement keeps the old behaviour of not being limited.
24
+ *
25
+ * `dialect` selects the connector's scanner so dialect-specific quoting
26
+ * (PostgreSQL dollar quotes, MySQL backticks, SQL Server brackets) is
27
+ * blanked the same way the read-only classifier blanks it; without it only
28
+ * ANSI syntax is recognized.
29
+ */
30
+ static isSelectQuery(sql, dialect) {
31
+ const blankedSQL = blankCommentsAndStrings(sql, dialect).trim().toLowerCase();
32
+ const firstKeyword = /^[(;\s]*([a-z_]+)/.exec(blankedSQL)?.[1] ?? "";
33
+ if (firstKeyword === "select") {
34
+ return true;
35
+ }
36
+ return firstKeyword === "with" && !hasMutatingKeyword(blankedSQL, dialect);
37
+ }
38
+ /**
39
+ * Check if a SQL statement has a LIMIT clause of its own (not one nested in
40
+ * a CTE body or subquery).
41
+ */
42
+ static hasLimitClause(sql, dialect) {
43
+ return this.findTopLevelLimit(sql, dialect) !== null;
44
+ }
45
+ /**
46
+ * Check if a SQL statement has a TOP clause of its own (SQL Server).
47
+ */
48
+ static hasTopClause(sql) {
49
+ return this.findTopLevelTop(sql) !== null;
50
+ }
51
+ /**
52
+ * Extract the statement's own LIMIT value. Null when it has no LIMIT of its
53
+ * own, or when that LIMIT is a parameter placeholder rather than a literal
54
+ * (see hasParameterizedLimit).
55
+ */
56
+ static extractLimitValue(sql, dialect) {
57
+ return this.findTopLevelLimit(sql, dialect)?.value ?? null;
58
+ }
59
+ /**
60
+ * Extract the statement's own TOP value (SQL Server), or null when it has none.
61
+ */
62
+ static extractTopValue(sql) {
63
+ return this.findTopLevelTop(sql)?.value ?? null;
64
+ }
65
+ /**
66
+ * Check if the statement's own LIMIT clause uses a parameter placeholder
67
+ * ($1, ?, @p1) instead of a literal number.
68
+ */
69
+ static hasParameterizedLimit(sql, dialect) {
70
+ const limit = this.findTopLevelLimit(sql, dialect);
71
+ return limit !== null && limit.value === null;
72
+ }
73
+ /**
74
+ * Add or tighten the LIMIT clause of a SQL statement
75
+ */
76
+ static applyLimitToQuery(sql, maxRows, dialect) {
77
+ const limit = this.findTopLevelLimit(sql, dialect);
78
+ if (limit !== null && limit.value !== null) {
79
+ const effectiveLimit = Math.min(limit.value, maxRows);
80
+ return `${sql.slice(0, limit.index)}LIMIT ${effectiveLimit}${sql.slice(limit.index + limit.length)}`;
81
+ }
82
+ const { sql: sqlWithoutSemicolon, semicolon } = trimSemicolon(sql);
83
+ return `${sqlWithoutSemicolon}
84
+ LIMIT ${maxRows}${semicolon}`;
85
+ }
86
+ /**
87
+ * Scan blanked (comment/string-free, length-preserving) SQL for parenthesis
88
+ * depth, invoking onMatch for every regex hit at depth 0 (i.e. not nested
89
+ * inside a subquery, function call, or window OVER clause).
90
+ */
91
+ static scanTopLevel(blankedSQL, regex, onMatch) {
92
+ let depth = 0;
93
+ let m;
94
+ while ((m = regex.exec(blankedSQL)) !== null) {
95
+ const token = m[0];
96
+ if (token === "(") {
97
+ depth++;
98
+ } else if (token === ")") {
99
+ depth--;
100
+ } else if (depth === 0) {
101
+ onMatch(m);
102
+ }
103
+ }
104
+ }
105
+ /**
106
+ * The first or last regex match at parenthesis depth 0, i.e. one belonging
107
+ * to the statement itself rather than to a nested query. Match indices refer
108
+ * to positions in the original `sql`, since blankCommentsAndStrings
109
+ * preserves length. The regex must offer `\(` and `\)` alternatives so depth
110
+ * can be tracked, and must carry the `g` flag.
111
+ */
112
+ static findTopLevelMatch(sql, regex, pick, dialect) {
113
+ let found = null;
114
+ this.scanTopLevel(blankCommentsAndStrings(sql, dialect), regex, (m) => {
115
+ if (pick === "last" || found === null) {
116
+ found = m;
117
+ }
118
+ });
119
+ return found;
120
+ }
121
+ /** The statement's own LIMIT clause, literal or parameterized ($1, ?, @p1). */
122
+ static findTopLevelLimit(sql, dialect) {
123
+ const match = this.findTopLevelMatch(
124
+ sql,
125
+ /\(|\)|\blimit\s+(?:(\d+)|\$\d+|\?|@p\d+)/gi,
126
+ "last",
127
+ dialect
128
+ );
129
+ if (match === null) {
130
+ return null;
131
+ }
132
+ return {
133
+ index: match.index,
134
+ length: match[0].length,
135
+ value: match[1] !== void 0 ? parseInt(match[1], 10) : null
136
+ };
137
+ }
138
+ /** The statement's own `SELECT TOP n` clause (SQL Server). */
139
+ static findTopLevelTop(sql) {
140
+ const match = this.findTopLevelMatch(sql, /\(|\)|\bselect\s+top\s+(\d+)/gi, "first", "sqlserver");
141
+ if (match === null) {
142
+ return null;
143
+ }
144
+ return { index: match.index, length: match[0].length, value: parseInt(match[1], 10) };
145
+ }
146
+ /**
147
+ * The statement's own SELECT keyword (SQL Server). For a CTE this is the
148
+ * final SELECT, not the one inside a CTE body.
149
+ */
150
+ static findTopLevelSelect(sql) {
151
+ return this.findTopLevelMatch(sql, /\(|\)|\bselect\b/gi, "first", "sqlserver");
152
+ }
153
+ /**
154
+ * Check if a SQL statement combines multiple SELECTs with a set operator
155
+ * (UNION [ALL], INTERSECT, EXCEPT) at the top level — i.e. not nested
156
+ * inside a subquery already wrapped in parentheses.
157
+ */
158
+ static hasSetOperator(sql) {
159
+ return this.findTopLevelMatch(sql, /\(|\)|\bunion\b|\bintersect\b|\bexcept\b/gi, "first", "sqlserver") !== null;
160
+ }
161
+ /**
162
+ * Find the start index of a top-level trailing ORDER BY clause (not one
163
+ * nested inside a subquery or a window function's OVER (...) clause).
164
+ * Returns -1 if none exists.
165
+ */
166
+ static findTopLevelOrderByIndex(sql) {
167
+ return this.findTopLevelMatch(sql, /\(|\)|\border\s+by\b/gi, "last", "sqlserver")?.index ?? -1;
168
+ }
169
+ /**
170
+ * Add or modify TOP clause in a SQL statement (SQL Server)
171
+ */
172
+ static applyTopToQuery(sql, maxRows) {
173
+ if (this.hasSetOperator(sql)) {
174
+ const { sql: sqlWithoutSemicolon, semicolon } = trimSemicolon(sql);
175
+ const cteIndex = this.findTopLevelSelect(sqlWithoutSemicolon)?.index ?? 0;
176
+ const ctePrefix = sqlWithoutSemicolon.slice(0, cteIndex);
177
+ const body = sqlWithoutSemicolon.slice(cteIndex);
178
+ const orderByIndex = this.findTopLevelOrderByIndex(body);
179
+ if (orderByIndex !== -1) {
180
+ const innerSql = body.slice(0, orderByIndex).trimEnd();
181
+ const orderByClause = body.slice(orderByIndex).trim();
182
+ return `${ctePrefix}SELECT TOP ${maxRows} * FROM (${innerSql}
183
+ ) AS subq ${orderByClause}${semicolon}`;
184
+ }
185
+ return `${ctePrefix}SELECT TOP ${maxRows} * FROM (${body}
186
+ ) AS subq${semicolon}`;
187
+ }
188
+ const existingTop = this.findTopLevelTop(sql);
189
+ if (existingTop !== null) {
190
+ const effectiveTop = Math.min(existingTop.value, maxRows);
191
+ return `${sql.slice(0, existingTop.index)}SELECT TOP ${effectiveTop}${sql.slice(existingTop.index + existingTop.length)}`;
192
+ }
193
+ const selectMatch = this.findTopLevelSelect(sql);
194
+ if (selectMatch === null) {
195
+ return sql;
196
+ }
197
+ return `${sql.slice(0, selectMatch.index)}SELECT TOP ${maxRows}${sql.slice(selectMatch.index + selectMatch[0].length)}`;
198
+ }
199
+ /**
200
+ * Apply maxRows limit to a row-returning query only
201
+ *
202
+ * This method is used by PostgreSQL, MySQL, MariaDB, and SQLite connectors which all support
203
+ * the LIMIT clause syntax. SQL Server uses applyMaxRowsForSQLServer() instead with TOP syntax.
204
+ *
205
+ * For parameterized LIMIT clauses (e.g., LIMIT $1 or LIMIT ?), we wrap the query in a subquery
206
+ * to enforce max_rows as a hard cap, since the parameter value is not known until runtime.
207
+ *
208
+ * `dialect` selects the connector's comment/string scanner (see isSelectQuery).
209
+ */
210
+ static applyMaxRows(sql, maxRows, dialect) {
211
+ if (!maxRows || !this.isSelectQuery(sql, dialect)) {
212
+ return sql;
213
+ }
214
+ if (this.hasParameterizedLimit(sql, dialect)) {
215
+ const { sql: sqlWithoutSemicolon, semicolon } = trimSemicolon(sql);
216
+ return `SELECT * FROM (${sqlWithoutSemicolon}
217
+ ) AS subq LIMIT ${maxRows}${semicolon}`;
218
+ }
219
+ return this.applyLimitToQuery(sql, maxRows, dialect);
220
+ }
221
+ /**
222
+ * Apply maxRows limit to a row-returning query using SQL Server TOP syntax
223
+ */
224
+ static applyMaxRowsForSQLServer(sql, maxRows) {
225
+ if (!maxRows || !this.isSelectQuery(sql, "sqlserver")) {
226
+ return sql;
227
+ }
228
+ return this.applyTopToQuery(sql, maxRows);
229
+ }
230
+ /**
231
+ * Like applyMaxRows, but requests one probe row (maxRows + 1) whenever the
232
+ * cap is the binding constraint, so callers can detect truncation exactly
233
+ * instead of guessing from `rowCount === maxRows` (which a table with
234
+ * exactly maxRows rows also produces).
235
+ *
236
+ * `probeApplied` is false when the cap cannot fire: no maxRows, not a
237
+ * SELECT, or the query's own literal LIMIT is already within the cap (the
238
+ * user asked for fewer rows than the cap — that is their limit, not
239
+ * truncation).
240
+ */
241
+ static applyMaxRowsWithTruncationProbe(sql, maxRows, dialect) {
242
+ if (!maxRows || !this.isSelectQuery(sql, dialect)) {
243
+ return { sql, probeApplied: false };
244
+ }
245
+ if (!this.hasParameterizedLimit(sql, dialect)) {
246
+ const existingLimit = this.extractLimitValue(sql, dialect);
247
+ if (existingLimit !== null && existingLimit <= maxRows) {
248
+ return { sql, probeApplied: false };
249
+ }
250
+ }
251
+ return { sql: this.applyMaxRows(sql, maxRows + 1, dialect), probeApplied: true };
252
+ }
253
+ /**
254
+ * SQL Server variant of applyMaxRowsWithTruncationProbe, using TOP syntax.
255
+ * A set-operator statement is always wrapped (any TOP on an individual
256
+ * branch caps only that branch, not the combined output), so the probe
257
+ * applies there regardless of branch-level TOPs.
258
+ */
259
+ static applyMaxRowsForSQLServerWithTruncationProbe(sql, maxRows) {
260
+ if (!maxRows || !this.isSelectQuery(sql, "sqlserver")) {
261
+ return { sql, probeApplied: false };
262
+ }
263
+ if (!this.hasSetOperator(sql)) {
264
+ const existingTop = this.extractTopValue(sql);
265
+ if (existingTop !== null && existingTop <= maxRows) {
266
+ return { sql, probeApplied: false };
267
+ }
268
+ }
269
+ return { sql: this.applyMaxRowsForSQLServer(sql, maxRows + 1), probeApplied: true };
270
+ }
271
+ /**
272
+ * Post-process a result set produced by a truncation-probe rewrite: when
273
+ * the probe row came back (more than maxRows rows), drop it, clamp the
274
+ * count to maxRows, and mark the set truncated so consumers can tell a
275
+ * capped result from a complete one.
276
+ */
277
+ static flagTruncation(resultSet, maxRows, probeApplied) {
278
+ if (!probeApplied || !maxRows || resultSet.rows.length <= maxRows) {
279
+ return;
280
+ }
281
+ resultSet.rows = resultSet.rows.slice(0, maxRows);
282
+ resultSet.rowCount = maxRows;
283
+ resultSet.truncated = true;
284
+ }
285
+ };
286
+ function trimSemicolon(sql) {
287
+ const trimmed = sql.trim();
288
+ return trimmed.endsWith(";") ? { sql: trimmed.slice(0, -1), semicolon: ";" } : { sql: trimmed, semicolon: "" };
289
+ }
290
+
291
+ // src/utils/resource-cleanup.ts
292
+ async function closeQuietly(close) {
293
+ try {
294
+ await close();
295
+ } catch {
296
+ }
297
+ }
298
+
299
+ export {
300
+ SQLRowLimiter,
301
+ closeQuietly
302
+ };
@@ -0,0 +1,250 @@
1
+ import {
2
+ SQLRowLimiter,
3
+ closeQuietly
4
+ } from "./chunk-O3PEWKBB.js";
5
+ import "./chunk-HDPZAIDL.js";
6
+ import {
7
+ ConnectorRegistry,
8
+ SafeURL,
9
+ splitSQLStatements
10
+ } from "./chunk-2JL4KCP5.js";
11
+
12
+ // src/connectors/dameng/index.ts
13
+ import { randomUUID } from "node:crypto";
14
+ import dmdb from "dmdb";
15
+ var DamengDSNParser = class {
16
+ isValidDSN(dsn) {
17
+ return dsn.startsWith("dameng://");
18
+ }
19
+ getSampleDSN() {
20
+ return "dameng://user:password@localhost:5236/APP";
21
+ }
22
+ async parse(dsn, config) {
23
+ if (!this.isValidDSN(dsn)) throw new Error("Expected a dameng:// DSN");
24
+ const url = new SafeURL(dsn);
25
+ if (!url.hostname || !url.username) throw new Error("Dameng DSN requires host and user");
26
+ const params = new URLSearchParams();
27
+ url.forEachSearchParam((value, key) => params.set(key, value));
28
+ if (url.pathname.length > 1) params.set("schema", decodeURIComponent(url.pathname.slice(1)));
29
+ if (config?.connectionTimeoutSeconds !== void 0) {
30
+ params.set("connectTimeout", String(config.connectionTimeoutSeconds * 1e3));
31
+ }
32
+ if (config?.queryTimeoutSeconds !== void 0) {
33
+ params.set("sessionTimeout", String(config.queryTimeoutSeconds));
34
+ }
35
+ return {
36
+ connectString: `dm://${encodeURIComponent(url.username)}:${encodeURIComponent(url.password)}@${url.hostname}:${url.port || 5236}?${params}`
37
+ };
38
+ }
39
+ };
40
+ var DamengConnector = class _DamengConnector {
41
+ constructor() {
42
+ this.id = "dameng";
43
+ this.name = "Dameng";
44
+ this.dsnParser = new DamengDSNParser();
45
+ this.sourceId = "default";
46
+ this.pool = null;
47
+ this.defaultSchema = null;
48
+ }
49
+ getId() {
50
+ return this.sourceId;
51
+ }
52
+ clone() {
53
+ return new _DamengConnector();
54
+ }
55
+ async connect(dsn, initScript, config) {
56
+ const attributes = await this.dsnParser.parse(dsn, config);
57
+ this.pool = await dmdb.createPool({ ...attributes, poolAlias: randomUUID() });
58
+ try {
59
+ await this.withConnection(async (conn) => {
60
+ const result = await conn.execute(
61
+ "SELECT SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA') AS SCHEMA_NAME FROM DUAL",
62
+ [],
63
+ { outFormat: dmdb.OUT_FORMAT_OBJECT }
64
+ );
65
+ this.defaultSchema = result.rows[0].SCHEMA_NAME;
66
+ if (initScript) {
67
+ for (const sql of splitSQLStatements(initScript, "dameng")) {
68
+ await conn.execute(sql, [], { autoCommit: true });
69
+ }
70
+ }
71
+ });
72
+ } catch (error) {
73
+ await closeQuietly(() => this.disconnect());
74
+ throw error;
75
+ }
76
+ }
77
+ async disconnect() {
78
+ const pool = this.pool;
79
+ this.pool = null;
80
+ this.defaultSchema = null;
81
+ if (pool) await pool.close(0);
82
+ }
83
+ async getDefaultSchema() {
84
+ return this.defaultSchema;
85
+ }
86
+ async withConnection(run) {
87
+ if (!this.pool) throw new Error("Not connected to Dameng");
88
+ const conn = await this.pool.getConnection();
89
+ try {
90
+ return await run(conn);
91
+ } finally {
92
+ await conn.close();
93
+ }
94
+ }
95
+ async query(sql, parameters = []) {
96
+ return this.withConnection(async (conn) => {
97
+ const result = await conn.execute(sql, parameters, {
98
+ outFormat: dmdb.OUT_FORMAT_OBJECT
99
+ });
100
+ return result.rows ?? [];
101
+ });
102
+ }
103
+ owner(schema) {
104
+ const owner = schema ?? this.defaultSchema;
105
+ if (!owner) throw new Error("No Dameng schema selected");
106
+ return owner;
107
+ }
108
+ async getSchemas() {
109
+ const rows = await this.query("SELECT USERNAME FROM ALL_USERS ORDER BY USERNAME");
110
+ return rows.map((row) => row.USERNAME);
111
+ }
112
+ async getTables(schema) {
113
+ const rows = await this.query(
114
+ "SELECT TABLE_NAME FROM ALL_TABLES WHERE OWNER = ? ORDER BY TABLE_NAME",
115
+ [this.owner(schema)]
116
+ );
117
+ return rows.map((row) => row.TABLE_NAME);
118
+ }
119
+ async getViews(schema) {
120
+ const rows = await this.query(
121
+ "SELECT VIEW_NAME FROM ALL_VIEWS WHERE OWNER = ? ORDER BY VIEW_NAME",
122
+ [this.owner(schema)]
123
+ );
124
+ return rows.map((row) => row.VIEW_NAME);
125
+ }
126
+ async tableExists(tableName, schema) {
127
+ const rows = await this.query(
128
+ `SELECT COUNT(*) AS CNT FROM ALL_OBJECTS
129
+ WHERE OWNER = ? AND OBJECT_NAME = ? AND OBJECT_TYPE IN ('TABLE', 'VIEW')`,
130
+ [this.owner(schema), tableName]
131
+ );
132
+ return Number(rows[0].CNT) > 0;
133
+ }
134
+ async getTableSchema(tableName, schema) {
135
+ const rows = await this.query(
136
+ `SELECT c.COLUMN_NAME, c.DATA_TYPE, c.NULLABLE, c.DATA_DEFAULT, cc.COMMENTS
137
+ FROM ALL_TAB_COLUMNS c LEFT JOIN ALL_COL_COMMENTS cc
138
+ ON cc.OWNER = c.OWNER AND cc.TABLE_NAME = c.TABLE_NAME AND cc.COLUMN_NAME = c.COLUMN_NAME
139
+ WHERE c.OWNER = ? AND c.TABLE_NAME = ? ORDER BY c.COLUMN_ID`,
140
+ [this.owner(schema), tableName]
141
+ );
142
+ return rows.map((row) => ({
143
+ column_name: row.COLUMN_NAME,
144
+ data_type: row.DATA_TYPE,
145
+ is_nullable: row.NULLABLE === "Y" ? "YES" : "NO",
146
+ column_default: row.DATA_DEFAULT ?? null,
147
+ description: row.COMMENTS ?? null
148
+ }));
149
+ }
150
+ async getTableIndexes(tableName, schema) {
151
+ const rows = await this.query(
152
+ `SELECT i.INDEX_NAME, i.UNIQUENESS, ic.COLUMN_NAME,
153
+ CASE WHEN c.CONSTRAINT_TYPE = 'P' THEN 1 ELSE 0 END AS IS_PRIMARY
154
+ FROM ALL_INDEXES i JOIN ALL_IND_COLUMNS ic
155
+ ON i.OWNER = ic.INDEX_OWNER AND i.INDEX_NAME = ic.INDEX_NAME
156
+ LEFT JOIN ALL_CONSTRAINTS c
157
+ ON c.OWNER = i.TABLE_OWNER AND c.TABLE_NAME = i.TABLE_NAME
158
+ AND c.INDEX_NAME = i.INDEX_NAME AND c.CONSTRAINT_TYPE = 'P'
159
+ WHERE i.TABLE_OWNER = ? AND i.TABLE_NAME = ? ORDER BY i.INDEX_NAME, ic.COLUMN_POSITION`,
160
+ [this.owner(schema), tableName]
161
+ );
162
+ const indexes = /* @__PURE__ */ new Map();
163
+ for (const row of rows) {
164
+ let index = indexes.get(row.INDEX_NAME);
165
+ if (!index) {
166
+ index = {
167
+ index_name: row.INDEX_NAME,
168
+ column_names: [],
169
+ is_unique: row.UNIQUENESS === "UNIQUE",
170
+ is_primary: Number(row.IS_PRIMARY) === 1
171
+ };
172
+ indexes.set(row.INDEX_NAME, index);
173
+ }
174
+ index.column_names.push(row.COLUMN_NAME);
175
+ }
176
+ return [...indexes.values()];
177
+ }
178
+ async getStoredProcedures(schema, routineType) {
179
+ const rows = await this.query(
180
+ `SELECT OBJECT_NAME FROM ALL_OBJECTS WHERE OWNER = ?
181
+ AND OBJECT_TYPE IN ('PROCEDURE', 'FUNCTION') ${routineType ? "AND OBJECT_TYPE = ?" : ""}
182
+ ORDER BY OBJECT_NAME`,
183
+ routineType ? [this.owner(schema), routineType.toUpperCase()] : [this.owner(schema)]
184
+ );
185
+ return rows.map((row) => row.OBJECT_NAME);
186
+ }
187
+ async getStoredProcedureDetail(procedureName, schema) {
188
+ const owner = this.owner(schema);
189
+ const rows = await this.query(
190
+ `SELECT OBJECT_NAME, OBJECT_TYPE FROM ALL_OBJECTS WHERE OWNER = ? AND OBJECT_NAME = ?
191
+ AND OBJECT_TYPE IN ('PROCEDURE', 'FUNCTION')`,
192
+ [owner, procedureName]
193
+ );
194
+ if (!rows.length) throw new Error(`Stored procedure '${procedureName}' not found`);
195
+ const source = await this.query(
196
+ "SELECT TEXT FROM ALL_SOURCE WHERE OWNER = ? AND NAME = ? ORDER BY LINE",
197
+ [owner, procedureName]
198
+ );
199
+ return {
200
+ procedure_name: rows[0].OBJECT_NAME,
201
+ procedure_type: rows[0].OBJECT_TYPE === "FUNCTION" ? "function" : "procedure",
202
+ language: "sql",
203
+ parameter_list: "",
204
+ definition: source.map((row) => row.TEXT).join("")
205
+ };
206
+ }
207
+ async getTableRowCount(tableName, schema) {
208
+ const rows = await this.query(
209
+ "SELECT NUM_ROWS FROM ALL_TABLES WHERE OWNER = ? AND TABLE_NAME = ?",
210
+ [this.owner(schema), tableName]
211
+ );
212
+ return rows[0]?.NUM_ROWS == null ? null : Number(rows[0].NUM_ROWS);
213
+ }
214
+ async getTableComment(tableName, schema) {
215
+ const rows = await this.query(
216
+ "SELECT COMMENTS FROM ALL_TAB_COMMENTS WHERE OWNER = ? AND TABLE_NAME = ?",
217
+ [this.owner(schema), tableName]
218
+ );
219
+ return rows[0]?.COMMENTS ?? null;
220
+ }
221
+ async executeSQL(sql, options, parameters) {
222
+ const statements = splitSQLStatements(sql, "dameng");
223
+ if (parameters?.length && statements.length !== 1) {
224
+ throw new Error("Parameters require a single Dameng statement");
225
+ }
226
+ return this.withConnection(async (conn) => {
227
+ const resultSets = [];
228
+ for (const statement of statements) {
229
+ const result = await conn.execute(statement, parameters ?? [], {
230
+ outFormat: dmdb.OUT_FORMAT_OBJECT,
231
+ autoCommit: true,
232
+ maxRows: options.maxRows ? options.maxRows + 1 : 0
233
+ });
234
+ const rows = result.rows ?? [];
235
+ const resultSet = {
236
+ sql: statement,
237
+ rows,
238
+ rowCount: result.rowsAffected ?? rows.length
239
+ };
240
+ SQLRowLimiter.flagTruncation(resultSet, options.maxRows, !!options.maxRows);
241
+ resultSets.push(resultSet);
242
+ }
243
+ return { resultSets };
244
+ });
245
+ }
246
+ };
247
+ ConnectorRegistry.register(new DamengConnector());
248
+ export {
249
+ DamengConnector
250
+ };