@zz1996/dbhub-dameng 0.1.0
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/LICENSE +21 -0
- package/README.md +143 -0
- package/dbhub.dameng.toml.example +19 -0
- package/dist/chunk-2A2QF3CS.js +2463 -0
- package/dist/chunk-IPK7BYBL.js +35 -0
- package/dist/chunk-RTB262PR.js +60 -0
- package/dist/chunk-SQA2ISDE.js +503 -0
- package/dist/chunk-WVVMH6FJ.js +49 -0
- package/dist/chunk-ZNQTMARG.js +127 -0
- package/dist/dameng-DC7OP4VV.js +504 -0
- package/dist/demo/employee-sqlite/employee.sql +117 -0
- package/dist/demo/employee-sqlite/load_department.sql +10 -0
- package/dist/demo/employee-sqlite/load_dept_emp.sql +1103 -0
- package/dist/demo/employee-sqlite/load_dept_manager.sql +17 -0
- package/dist/demo/employee-sqlite/load_employee.sql +1000 -0
- package/dist/demo/employee-sqlite/load_salary1.sql +9488 -0
- package/dist/demo/employee-sqlite/load_title.sql +1470 -0
- package/dist/demo-loader-PSMTLZ2T.js +46 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1841 -0
- package/dist/mariadb-7F72IRB4.js +520 -0
- package/dist/mysql-A43SL7UM.js +527 -0
- package/dist/postgres-VFNLBWMF.js +564 -0
- package/dist/registry-XSX2VZTD.js +12 -0
- package/dist/sqlite-IOUAYHGE.js +412 -0
- package/dist/sqlserver-OE4SFH4B.js +587 -0
- package/docs/README.md +14 -0
- package/docs/dameng-connector-plan.md +143 -0
- package/package.json +110 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import {
|
|
2
|
+
stripCommentsAndStrings
|
|
3
|
+
} from "./chunk-SQA2ISDE.js";
|
|
4
|
+
|
|
5
|
+
// src/utils/sql-row-limiter.ts
|
|
6
|
+
var SQLRowLimiter = class {
|
|
7
|
+
/**
|
|
8
|
+
* Check if a SQL statement is a SELECT query that can benefit from row limiting
|
|
9
|
+
* Only handles SELECT queries
|
|
10
|
+
*/
|
|
11
|
+
static isSelectQuery(sql) {
|
|
12
|
+
const trimmed = sql.trim().toLowerCase();
|
|
13
|
+
return trimmed.startsWith("select");
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Check if a SQL statement already has a LIMIT clause.
|
|
17
|
+
* Strips comments and string literals first to avoid false positives.
|
|
18
|
+
*/
|
|
19
|
+
static hasLimitClause(sql) {
|
|
20
|
+
const cleanedSQL = stripCommentsAndStrings(sql);
|
|
21
|
+
const limitRegex = /\blimit\s+(?:\d+|\$\d+|\?|@p\d+)/i;
|
|
22
|
+
return limitRegex.test(cleanedSQL);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Check if a SQL statement already has a TOP clause (SQL Server).
|
|
26
|
+
* Strips comments and string literals first to avoid false positives.
|
|
27
|
+
*/
|
|
28
|
+
static hasTopClause(sql) {
|
|
29
|
+
const cleanedSQL = stripCommentsAndStrings(sql);
|
|
30
|
+
const topRegex = /\bselect\s+top\s+\d+/i;
|
|
31
|
+
return topRegex.test(cleanedSQL);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Extract existing LIMIT value from SQL if present.
|
|
35
|
+
* Strips comments and string literals first to avoid false positives.
|
|
36
|
+
*/
|
|
37
|
+
static extractLimitValue(sql) {
|
|
38
|
+
const cleanedSQL = stripCommentsAndStrings(sql);
|
|
39
|
+
const limitMatch = cleanedSQL.match(/\blimit\s+(\d+)/i);
|
|
40
|
+
if (limitMatch) {
|
|
41
|
+
return parseInt(limitMatch[1], 10);
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Extract existing TOP value from SQL if present (SQL Server).
|
|
47
|
+
* Strips comments and string literals first to avoid false positives.
|
|
48
|
+
*/
|
|
49
|
+
static extractTopValue(sql) {
|
|
50
|
+
const cleanedSQL = stripCommentsAndStrings(sql);
|
|
51
|
+
const topMatch = cleanedSQL.match(/\bselect\s+top\s+(\d+)/i);
|
|
52
|
+
if (topMatch) {
|
|
53
|
+
return parseInt(topMatch[1], 10);
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Add or modify LIMIT clause in a SQL statement
|
|
59
|
+
*/
|
|
60
|
+
static applyLimitToQuery(sql, maxRows) {
|
|
61
|
+
const existingLimit = this.extractLimitValue(sql);
|
|
62
|
+
if (existingLimit !== null) {
|
|
63
|
+
const effectiveLimit = Math.min(existingLimit, maxRows);
|
|
64
|
+
return sql.replace(/\blimit\s+\d+/i, `LIMIT ${effectiveLimit}`);
|
|
65
|
+
} else {
|
|
66
|
+
const trimmed = sql.trim();
|
|
67
|
+
const hasSemicolon = trimmed.endsWith(";");
|
|
68
|
+
const sqlWithoutSemicolon = hasSemicolon ? trimmed.slice(0, -1) : trimmed;
|
|
69
|
+
return `${sqlWithoutSemicolon} LIMIT ${maxRows}${hasSemicolon ? ";" : ""}`;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Add or modify TOP clause in a SQL statement (SQL Server)
|
|
74
|
+
*/
|
|
75
|
+
static applyTopToQuery(sql, maxRows) {
|
|
76
|
+
const existingTop = this.extractTopValue(sql);
|
|
77
|
+
if (existingTop !== null) {
|
|
78
|
+
const effectiveTop = Math.min(existingTop, maxRows);
|
|
79
|
+
return sql.replace(/\bselect\s+top\s+\d+/i, `SELECT TOP ${effectiveTop}`);
|
|
80
|
+
} else {
|
|
81
|
+
return sql.replace(/\bselect\s+/i, `SELECT TOP ${maxRows} `);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Check if a LIMIT clause uses a parameter placeholder (not a literal number).
|
|
86
|
+
* Strips comments and string literals first to avoid false positives.
|
|
87
|
+
*/
|
|
88
|
+
static hasParameterizedLimit(sql) {
|
|
89
|
+
const cleanedSQL = stripCommentsAndStrings(sql);
|
|
90
|
+
const parameterizedLimitRegex = /\blimit\s+(?:\$\d+|\?|@p\d+)/i;
|
|
91
|
+
return parameterizedLimitRegex.test(cleanedSQL);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Apply maxRows limit to a SELECT query only
|
|
95
|
+
*
|
|
96
|
+
* This method is used by PostgreSQL, MySQL, MariaDB, and SQLite connectors which all support
|
|
97
|
+
* the LIMIT clause syntax. SQL Server uses applyMaxRowsForSQLServer() instead with TOP syntax.
|
|
98
|
+
*
|
|
99
|
+
* For parameterized LIMIT clauses (e.g., LIMIT $1 or LIMIT ?), we wrap the query in a subquery
|
|
100
|
+
* to enforce max_rows as a hard cap, since the parameter value is not known until runtime.
|
|
101
|
+
*/
|
|
102
|
+
static applyMaxRows(sql, maxRows) {
|
|
103
|
+
if (!maxRows || !this.isSelectQuery(sql)) {
|
|
104
|
+
return sql;
|
|
105
|
+
}
|
|
106
|
+
if (this.hasParameterizedLimit(sql)) {
|
|
107
|
+
const trimmed = sql.trim();
|
|
108
|
+
const hasSemicolon = trimmed.endsWith(";");
|
|
109
|
+
const sqlWithoutSemicolon = hasSemicolon ? trimmed.slice(0, -1) : trimmed;
|
|
110
|
+
return `SELECT * FROM (${sqlWithoutSemicolon}) AS subq LIMIT ${maxRows}${hasSemicolon ? ";" : ""}`;
|
|
111
|
+
}
|
|
112
|
+
return this.applyLimitToQuery(sql, maxRows);
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Apply maxRows limit to a SELECT query using SQL Server TOP syntax
|
|
116
|
+
*/
|
|
117
|
+
static applyMaxRowsForSQLServer(sql, maxRows) {
|
|
118
|
+
if (!maxRows || !this.isSelectQuery(sql)) {
|
|
119
|
+
return sql;
|
|
120
|
+
}
|
|
121
|
+
return this.applyTopToQuery(sql, maxRows);
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
export {
|
|
126
|
+
SQLRowLimiter
|
|
127
|
+
};
|
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ConnectorRegistry,
|
|
3
|
+
SafeURL,
|
|
4
|
+
obfuscateDSNPassword,
|
|
5
|
+
splitSQLStatements,
|
|
6
|
+
stripCommentsAndStrings
|
|
7
|
+
} from "./chunk-SQA2ISDE.js";
|
|
8
|
+
|
|
9
|
+
// src/connectors/dameng/index.ts
|
|
10
|
+
import dmdb from "dmdb";
|
|
11
|
+
var DamengDSNParser = class {
|
|
12
|
+
async parse(dsn, config) {
|
|
13
|
+
if (!this.isValidDSN(dsn)) {
|
|
14
|
+
const obfuscatedDSN = obfuscateDSNPassword(dsn);
|
|
15
|
+
const expectedFormat = this.getSampleDSN();
|
|
16
|
+
throw new Error(
|
|
17
|
+
`Invalid Dameng DSN format.
|
|
18
|
+
Provided: ${obfuscatedDSN}
|
|
19
|
+
Expected: ${expectedFormat}`
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
const url = new SafeURL(dsn);
|
|
24
|
+
const schema = url.pathname ? decodeURIComponent(url.pathname.substring(1)) : void 0;
|
|
25
|
+
const port = url.port ? parseInt(url.port, 10) : 5236;
|
|
26
|
+
const queryParams = [];
|
|
27
|
+
url.forEachSearchParam((value, key) => {
|
|
28
|
+
queryParams.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`);
|
|
29
|
+
});
|
|
30
|
+
const connectString = `dm://${encodeURIComponent(url.username)}:${encodeURIComponent(url.password)}@${url.hostname}:${port}${queryParams.length > 0 ? `?${queryParams.join("&")}` : ""}`;
|
|
31
|
+
const connectionConfig = {
|
|
32
|
+
connectString,
|
|
33
|
+
user: url.username,
|
|
34
|
+
password: url.password,
|
|
35
|
+
schema: schema || void 0,
|
|
36
|
+
poolMin: 0,
|
|
37
|
+
poolMax: 4
|
|
38
|
+
};
|
|
39
|
+
if (config?.connectionTimeoutSeconds !== void 0) {
|
|
40
|
+
connectionConfig.queueTimeout = config.connectionTimeoutSeconds * 1e3;
|
|
41
|
+
}
|
|
42
|
+
return connectionConfig;
|
|
43
|
+
} catch (error) {
|
|
44
|
+
throw new Error(
|
|
45
|
+
`Failed to parse Dameng DSN: ${error instanceof Error ? error.message : String(error)}`
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
getSampleDSN() {
|
|
50
|
+
return "dameng://SYSDBA:password@localhost:5236/SYSDBA";
|
|
51
|
+
}
|
|
52
|
+
isValidDSN(dsn) {
|
|
53
|
+
try {
|
|
54
|
+
return dsn.startsWith("dameng://") || dsn.startsWith("dm://");
|
|
55
|
+
} catch {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
var DamengConnector = class _DamengConnector {
|
|
61
|
+
constructor() {
|
|
62
|
+
this.id = "dameng";
|
|
63
|
+
this.name = "Dameng";
|
|
64
|
+
this.dsnParser = new DamengDSNParser();
|
|
65
|
+
this.pool = null;
|
|
66
|
+
this.sourceId = "default";
|
|
67
|
+
this.defaultSchema = null;
|
|
68
|
+
}
|
|
69
|
+
getId() {
|
|
70
|
+
return this.sourceId;
|
|
71
|
+
}
|
|
72
|
+
clone() {
|
|
73
|
+
return new _DamengConnector();
|
|
74
|
+
}
|
|
75
|
+
async connect(dsn, initScript, config) {
|
|
76
|
+
try {
|
|
77
|
+
const connectionConfig = await this.dsnParser.parse(dsn, config);
|
|
78
|
+
this.defaultSchema = connectionConfig.schema ?? null;
|
|
79
|
+
if (config?.queryTimeoutSeconds !== void 0) {
|
|
80
|
+
this.queryTimeoutMs = config.queryTimeoutSeconds * 1e3;
|
|
81
|
+
}
|
|
82
|
+
this.pool = await dmdb.createPool(connectionConfig);
|
|
83
|
+
await this.withConnection(async (conn) => {
|
|
84
|
+
await conn.execute("SELECT 1 AS OK", [], this.executeOptions());
|
|
85
|
+
if (!this.defaultSchema) {
|
|
86
|
+
const result = await conn.execute(
|
|
87
|
+
"SELECT USER AS SCHEMA_NAME FROM DUAL",
|
|
88
|
+
[],
|
|
89
|
+
this.executeOptions()
|
|
90
|
+
);
|
|
91
|
+
this.defaultSchema = this.rowValue(result.rows?.[0], "SCHEMA_NAME") ?? null;
|
|
92
|
+
}
|
|
93
|
+
if (initScript) {
|
|
94
|
+
for (const statement of splitSQLStatements(initScript, "dameng")) {
|
|
95
|
+
await conn.execute(statement, [], this.executeOptions({ autoCommit: true }));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
} catch (error) {
|
|
100
|
+
console.error("Failed to connect to Dameng database:", error);
|
|
101
|
+
throw error;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
async disconnect() {
|
|
105
|
+
if (this.pool) {
|
|
106
|
+
await this.pool.close(0);
|
|
107
|
+
this.pool = null;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
async getSchemas() {
|
|
111
|
+
const rows = await this.queryRows(`
|
|
112
|
+
SELECT USERNAME AS SCHEMA_NAME
|
|
113
|
+
FROM ALL_USERS
|
|
114
|
+
WHERE USERNAME NOT IN ('SYS', 'SYSTEM', 'SYSAUDITOR', 'SYSSSO', 'CTISYS')
|
|
115
|
+
ORDER BY USERNAME
|
|
116
|
+
`);
|
|
117
|
+
return rows.map((row) => this.rowValue(row, "SCHEMA_NAME")).filter(this.isPresent);
|
|
118
|
+
}
|
|
119
|
+
async getDefaultSchema() {
|
|
120
|
+
return this.defaultSchema;
|
|
121
|
+
}
|
|
122
|
+
async getTables(schema) {
|
|
123
|
+
const owner = await this.resolveSchema(schema);
|
|
124
|
+
const rows = await this.queryRows(
|
|
125
|
+
`
|
|
126
|
+
SELECT TABLE_NAME
|
|
127
|
+
FROM ALL_TABLES
|
|
128
|
+
WHERE OWNER = :1
|
|
129
|
+
ORDER BY TABLE_NAME
|
|
130
|
+
`,
|
|
131
|
+
[owner]
|
|
132
|
+
);
|
|
133
|
+
return rows.map((row) => this.rowValue(row, "TABLE_NAME")).filter(this.isPresent);
|
|
134
|
+
}
|
|
135
|
+
async getViews(schema) {
|
|
136
|
+
const owner = await this.resolveSchema(schema);
|
|
137
|
+
const rows = await this.queryRows(
|
|
138
|
+
`
|
|
139
|
+
SELECT VIEW_NAME
|
|
140
|
+
FROM ALL_VIEWS
|
|
141
|
+
WHERE OWNER = :1
|
|
142
|
+
ORDER BY VIEW_NAME
|
|
143
|
+
`,
|
|
144
|
+
[owner]
|
|
145
|
+
);
|
|
146
|
+
return rows.map((row) => this.rowValue(row, "VIEW_NAME")).filter(this.isPresent);
|
|
147
|
+
}
|
|
148
|
+
async tableExists(tableName, schema) {
|
|
149
|
+
const owner = await this.resolveSchema(schema);
|
|
150
|
+
const rows = await this.queryRows(
|
|
151
|
+
`
|
|
152
|
+
SELECT COUNT(*) AS CNT
|
|
153
|
+
FROM ALL_OBJECTS
|
|
154
|
+
WHERE OWNER = :1
|
|
155
|
+
AND OBJECT_NAME = :2
|
|
156
|
+
AND OBJECT_TYPE IN ('TABLE', 'VIEW')
|
|
157
|
+
`,
|
|
158
|
+
[owner, this.normalizeIdentifier(tableName)]
|
|
159
|
+
);
|
|
160
|
+
return Number(this.rowValue(rows[0], "CNT") ?? 0) > 0;
|
|
161
|
+
}
|
|
162
|
+
async getTableSchema(tableName, schema) {
|
|
163
|
+
const owner = await this.resolveSchema(schema);
|
|
164
|
+
const rows = await this.queryRows(
|
|
165
|
+
`
|
|
166
|
+
SELECT c.COLUMN_NAME,
|
|
167
|
+
c.DATA_TYPE,
|
|
168
|
+
c.DATA_LENGTH,
|
|
169
|
+
c.DATA_PRECISION,
|
|
170
|
+
c.DATA_SCALE,
|
|
171
|
+
c.NULLABLE,
|
|
172
|
+
c.DATA_DEFAULT,
|
|
173
|
+
cc.COMMENTS
|
|
174
|
+
FROM ALL_TAB_COLUMNS c
|
|
175
|
+
LEFT JOIN ALL_COL_COMMENTS cc
|
|
176
|
+
ON cc.OWNER = c.OWNER
|
|
177
|
+
AND cc.TABLE_NAME = c.TABLE_NAME
|
|
178
|
+
AND cc.COLUMN_NAME = c.COLUMN_NAME
|
|
179
|
+
WHERE c.OWNER = :1
|
|
180
|
+
AND c.TABLE_NAME = :2
|
|
181
|
+
ORDER BY c.COLUMN_ID
|
|
182
|
+
`,
|
|
183
|
+
[owner, this.normalizeIdentifier(tableName)]
|
|
184
|
+
);
|
|
185
|
+
return rows.map((row) => {
|
|
186
|
+
const dataType = this.formatDataType(row);
|
|
187
|
+
return {
|
|
188
|
+
column_name: this.rowValue(row, "COLUMN_NAME") ?? "",
|
|
189
|
+
data_type: dataType,
|
|
190
|
+
is_nullable: this.rowValue(row, "NULLABLE") === "Y" ? "YES" : "NO",
|
|
191
|
+
column_default: this.rowValue(row, "DATA_DEFAULT") ?? null,
|
|
192
|
+
description: this.rowValue(row, "COMMENTS") ?? null
|
|
193
|
+
};
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
async getTableIndexes(tableName, schema) {
|
|
197
|
+
const owner = await this.resolveSchema(schema);
|
|
198
|
+
const rows = await this.queryRows(
|
|
199
|
+
`
|
|
200
|
+
SELECT i.INDEX_NAME,
|
|
201
|
+
i.UNIQUENESS,
|
|
202
|
+
ic.COLUMN_NAME,
|
|
203
|
+
ic.COLUMN_POSITION,
|
|
204
|
+
CASE WHEN c.CONSTRAINT_TYPE = 'P' THEN 1 ELSE 0 END AS IS_PRIMARY
|
|
205
|
+
FROM ALL_INDEXES i
|
|
206
|
+
JOIN ALL_IND_COLUMNS ic
|
|
207
|
+
ON i.OWNER = ic.INDEX_OWNER
|
|
208
|
+
AND i.INDEX_NAME = ic.INDEX_NAME
|
|
209
|
+
LEFT JOIN ALL_CONSTRAINTS c
|
|
210
|
+
ON c.OWNER = i.TABLE_OWNER
|
|
211
|
+
AND c.TABLE_NAME = i.TABLE_NAME
|
|
212
|
+
AND c.INDEX_NAME = i.INDEX_NAME
|
|
213
|
+
AND c.CONSTRAINT_TYPE = 'P'
|
|
214
|
+
WHERE i.TABLE_OWNER = :1
|
|
215
|
+
AND i.TABLE_NAME = :2
|
|
216
|
+
ORDER BY i.INDEX_NAME, ic.COLUMN_POSITION
|
|
217
|
+
`,
|
|
218
|
+
[owner, this.normalizeIdentifier(tableName)]
|
|
219
|
+
);
|
|
220
|
+
const indexMap = /* @__PURE__ */ new Map();
|
|
221
|
+
for (const row of rows) {
|
|
222
|
+
const indexName = this.rowValue(row, "INDEX_NAME") ?? "";
|
|
223
|
+
if (!indexMap.has(indexName)) {
|
|
224
|
+
indexMap.set(indexName, {
|
|
225
|
+
index_name: indexName,
|
|
226
|
+
column_names: [],
|
|
227
|
+
is_unique: this.rowValue(row, "UNIQUENESS") === "UNIQUE",
|
|
228
|
+
is_primary: Number(this.rowValue(row, "IS_PRIMARY") ?? 0) === 1
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
const columnName = this.rowValue(row, "COLUMN_NAME");
|
|
232
|
+
if (columnName) {
|
|
233
|
+
indexMap.get(indexName).column_names.push(columnName);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return Array.from(indexMap.values());
|
|
237
|
+
}
|
|
238
|
+
async getStoredProcedures(schema, routineType) {
|
|
239
|
+
const owner = await this.resolveSchema(schema);
|
|
240
|
+
const types = routineType ? [routineType === "procedure" ? "PROCEDURE" : "FUNCTION"] : ["PROCEDURE", "FUNCTION"];
|
|
241
|
+
const rows = await this.queryRows(
|
|
242
|
+
`
|
|
243
|
+
SELECT OBJECT_NAME
|
|
244
|
+
FROM ALL_OBJECTS
|
|
245
|
+
WHERE OWNER = :1
|
|
246
|
+
AND OBJECT_TYPE IN (${types.map((_, i) => `:${i + 2}`).join(", ")})
|
|
247
|
+
ORDER BY OBJECT_NAME
|
|
248
|
+
`,
|
|
249
|
+
[owner, ...types]
|
|
250
|
+
);
|
|
251
|
+
return rows.map((row) => this.rowValue(row, "OBJECT_NAME")).filter(this.isPresent);
|
|
252
|
+
}
|
|
253
|
+
async getStoredProcedureDetail(procedureName, schema) {
|
|
254
|
+
const owner = await this.resolveSchema(schema);
|
|
255
|
+
const name = this.normalizeIdentifier(procedureName);
|
|
256
|
+
const rows = await this.queryRows(
|
|
257
|
+
`
|
|
258
|
+
SELECT OBJECT_NAME, OBJECT_TYPE
|
|
259
|
+
FROM ALL_OBJECTS
|
|
260
|
+
WHERE OWNER = :1
|
|
261
|
+
AND OBJECT_NAME = :2
|
|
262
|
+
AND OBJECT_TYPE IN ('PROCEDURE', 'FUNCTION')
|
|
263
|
+
`,
|
|
264
|
+
[owner, name]
|
|
265
|
+
);
|
|
266
|
+
if (rows.length === 0) {
|
|
267
|
+
throw new Error(`Stored procedure '${procedureName}' not found in ${owner}`);
|
|
268
|
+
}
|
|
269
|
+
const sourceRows = await this.queryRows(
|
|
270
|
+
`
|
|
271
|
+
SELECT TEXT
|
|
272
|
+
FROM ALL_SOURCE
|
|
273
|
+
WHERE OWNER = :1
|
|
274
|
+
AND NAME = :2
|
|
275
|
+
ORDER BY LINE
|
|
276
|
+
`,
|
|
277
|
+
[owner, name]
|
|
278
|
+
);
|
|
279
|
+
const objectType = this.rowValue(rows[0], "OBJECT_TYPE") ?? "PROCEDURE";
|
|
280
|
+
return {
|
|
281
|
+
procedure_name: this.rowValue(rows[0], "OBJECT_NAME") ?? procedureName,
|
|
282
|
+
procedure_type: objectType === "FUNCTION" ? "function" : "procedure",
|
|
283
|
+
language: "sql",
|
|
284
|
+
parameter_list: "",
|
|
285
|
+
definition: sourceRows.map((row) => this.rowValue(row, "TEXT") ?? "").join("")
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
async getTableRowCount(tableName, schema) {
|
|
289
|
+
const owner = await this.resolveSchema(schema);
|
|
290
|
+
const rows = await this.queryRows(
|
|
291
|
+
`
|
|
292
|
+
SELECT NUM_ROWS
|
|
293
|
+
FROM ALL_TABLES
|
|
294
|
+
WHERE OWNER = :1
|
|
295
|
+
AND TABLE_NAME = :2
|
|
296
|
+
`,
|
|
297
|
+
[owner, this.normalizeIdentifier(tableName)]
|
|
298
|
+
);
|
|
299
|
+
const value = this.rowValue(rows[0], "NUM_ROWS");
|
|
300
|
+
return value === null || value === void 0 ? null : Number(value);
|
|
301
|
+
}
|
|
302
|
+
async getTableComment(tableName, schema) {
|
|
303
|
+
const owner = await this.resolveSchema(schema);
|
|
304
|
+
const rows = await this.queryRows(
|
|
305
|
+
`
|
|
306
|
+
SELECT COMMENTS
|
|
307
|
+
FROM ALL_TAB_COMMENTS
|
|
308
|
+
WHERE OWNER = :1
|
|
309
|
+
AND TABLE_NAME = :2
|
|
310
|
+
`,
|
|
311
|
+
[owner, this.normalizeIdentifier(tableName)]
|
|
312
|
+
);
|
|
313
|
+
return this.rowValue(rows[0], "COMMENTS") ?? null;
|
|
314
|
+
}
|
|
315
|
+
async executeSQL(sql, options, parameters) {
|
|
316
|
+
if (!this.pool) {
|
|
317
|
+
throw new Error("Not connected to Dameng database");
|
|
318
|
+
}
|
|
319
|
+
return this.withConnection(async (conn) => {
|
|
320
|
+
const statements = splitSQLStatements(sql, "dameng");
|
|
321
|
+
const allRows = [];
|
|
322
|
+
let rowCount = 0;
|
|
323
|
+
for (const [index, statement] of statements.entries()) {
|
|
324
|
+
const processedSQL = this.applyMaxRows(statement, options.maxRows);
|
|
325
|
+
const { sql: boundSQL, bindValues } = this.replacePositionalParameters(
|
|
326
|
+
processedSQL,
|
|
327
|
+
index === 0 ? parameters ?? [] : []
|
|
328
|
+
);
|
|
329
|
+
const result = await conn.execute(
|
|
330
|
+
boundSQL,
|
|
331
|
+
this.toBindParams(bindValues),
|
|
332
|
+
this.executeOptions({ autoCommit: true, maxRows: options.maxRows })
|
|
333
|
+
);
|
|
334
|
+
const rows = this.normalizeRows(result.rows ?? []);
|
|
335
|
+
allRows.push(...rows);
|
|
336
|
+
rowCount += Number(result.rowsAffected ?? rows.length ?? 0);
|
|
337
|
+
}
|
|
338
|
+
return { rows: allRows, rowCount };
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
async queryRows(sql, bindValues = []) {
|
|
342
|
+
return this.withConnection(async (conn) => {
|
|
343
|
+
const result = await conn.execute(sql, this.toBindParams(bindValues), this.executeOptions());
|
|
344
|
+
return this.normalizeRows(result.rows ?? []);
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
async withConnection(fn) {
|
|
348
|
+
if (!this.pool) {
|
|
349
|
+
throw new Error("Not connected to Dameng database");
|
|
350
|
+
}
|
|
351
|
+
const conn = await this.pool.getConnection();
|
|
352
|
+
try {
|
|
353
|
+
return await fn(conn);
|
|
354
|
+
} finally {
|
|
355
|
+
if (conn.release) {
|
|
356
|
+
await conn.release();
|
|
357
|
+
} else {
|
|
358
|
+
await conn.close();
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
executeOptions(extra = {}) {
|
|
363
|
+
return {
|
|
364
|
+
outFormat: dmdb.OUT_FORMAT_OBJECT,
|
|
365
|
+
...this.queryTimeoutMs !== void 0 && { queryTimeout: this.queryTimeoutMs },
|
|
366
|
+
...extra
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
async resolveSchema(schema) {
|
|
370
|
+
const resolved = schema || this.defaultSchema || await this.getDefaultSchema();
|
|
371
|
+
if (!resolved) {
|
|
372
|
+
throw new Error("No Dameng schema specified and current schema could not be resolved");
|
|
373
|
+
}
|
|
374
|
+
return this.normalizeIdentifier(resolved);
|
|
375
|
+
}
|
|
376
|
+
normalizeIdentifier(identifier) {
|
|
377
|
+
return /[a-z]/.test(identifier) ? identifier.toUpperCase() : identifier;
|
|
378
|
+
}
|
|
379
|
+
normalizeRows(rows) {
|
|
380
|
+
return rows.map((row) => {
|
|
381
|
+
if (!Array.isArray(row)) {
|
|
382
|
+
return row;
|
|
383
|
+
}
|
|
384
|
+
return row;
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
rowValue(row, key) {
|
|
388
|
+
if (!row) {
|
|
389
|
+
return null;
|
|
390
|
+
}
|
|
391
|
+
return row[key] ?? row[key.toLowerCase()] ?? row[key.toUpperCase()] ?? null;
|
|
392
|
+
}
|
|
393
|
+
isPresent(value) {
|
|
394
|
+
return value !== null && value !== "";
|
|
395
|
+
}
|
|
396
|
+
formatDataType(row) {
|
|
397
|
+
const dataType = this.rowValue(row, "DATA_TYPE") ?? "";
|
|
398
|
+
const precision = this.rowValue(row, "DATA_PRECISION");
|
|
399
|
+
const scale = this.rowValue(row, "DATA_SCALE");
|
|
400
|
+
const length = this.rowValue(row, "DATA_LENGTH");
|
|
401
|
+
if (precision !== null && precision !== void 0) {
|
|
402
|
+
return scale !== null && scale !== void 0 ? `${dataType}(${precision},${scale})` : `${dataType}(${precision})`;
|
|
403
|
+
}
|
|
404
|
+
if (length !== null && length !== void 0 && /CHAR|VARCHAR|BINARY/i.test(dataType)) {
|
|
405
|
+
return `${dataType}(${length})`;
|
|
406
|
+
}
|
|
407
|
+
return dataType;
|
|
408
|
+
}
|
|
409
|
+
toBindParams(values) {
|
|
410
|
+
return values.map((value) => ({ val: value }));
|
|
411
|
+
}
|
|
412
|
+
applyMaxRows(sql, maxRows) {
|
|
413
|
+
if (!maxRows) {
|
|
414
|
+
return sql;
|
|
415
|
+
}
|
|
416
|
+
const cleaned = stripCommentsAndStrings(sql, "dameng").trim().toLowerCase();
|
|
417
|
+
if (!cleaned.startsWith("select")) {
|
|
418
|
+
return sql;
|
|
419
|
+
}
|
|
420
|
+
const trimmed = sql.trim();
|
|
421
|
+
const withoutSemicolon = trimmed.endsWith(";") ? trimmed.slice(0, -1) : trimmed;
|
|
422
|
+
return `SELECT * FROM (${withoutSemicolon}) WHERE ROWNUM <= ${maxRows}`;
|
|
423
|
+
}
|
|
424
|
+
replacePositionalParameters(sql, parameters) {
|
|
425
|
+
if (!parameters.length) {
|
|
426
|
+
return { sql, bindValues: [] };
|
|
427
|
+
}
|
|
428
|
+
let index = 0;
|
|
429
|
+
let result = "";
|
|
430
|
+
let i = 0;
|
|
431
|
+
while (i < sql.length) {
|
|
432
|
+
const char = sql[i];
|
|
433
|
+
const next = sql[i + 1];
|
|
434
|
+
if (char === "'") {
|
|
435
|
+
const start = i++;
|
|
436
|
+
while (i < sql.length) {
|
|
437
|
+
if (sql[i] === "'" && sql[i + 1] === "'") {
|
|
438
|
+
i += 2;
|
|
439
|
+
} else if (sql[i] === "'") {
|
|
440
|
+
i++;
|
|
441
|
+
break;
|
|
442
|
+
} else {
|
|
443
|
+
i++;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
result += sql.slice(start, i);
|
|
447
|
+
continue;
|
|
448
|
+
}
|
|
449
|
+
if (char === '"') {
|
|
450
|
+
const start = i++;
|
|
451
|
+
while (i < sql.length) {
|
|
452
|
+
if (sql[i] === '"' && sql[i + 1] === '"') {
|
|
453
|
+
i += 2;
|
|
454
|
+
} else if (sql[i] === '"') {
|
|
455
|
+
i++;
|
|
456
|
+
break;
|
|
457
|
+
} else {
|
|
458
|
+
i++;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
result += sql.slice(start, i);
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
464
|
+
if (char === "-" && next === "-") {
|
|
465
|
+
const start = i;
|
|
466
|
+
i += 2;
|
|
467
|
+
while (i < sql.length && sql[i] !== "\n") {
|
|
468
|
+
i++;
|
|
469
|
+
}
|
|
470
|
+
result += sql.slice(start, i);
|
|
471
|
+
continue;
|
|
472
|
+
}
|
|
473
|
+
if (char === "/" && next === "*") {
|
|
474
|
+
const start = i;
|
|
475
|
+
i += 2;
|
|
476
|
+
while (i < sql.length && !(sql[i] === "*" && sql[i + 1] === "/")) {
|
|
477
|
+
i++;
|
|
478
|
+
}
|
|
479
|
+
i = i < sql.length ? i + 2 : i;
|
|
480
|
+
result += sql.slice(start, i);
|
|
481
|
+
continue;
|
|
482
|
+
}
|
|
483
|
+
if (char === "?") {
|
|
484
|
+
index += 1;
|
|
485
|
+
result += `:${index}`;
|
|
486
|
+
i++;
|
|
487
|
+
continue;
|
|
488
|
+
}
|
|
489
|
+
result += char;
|
|
490
|
+
i++;
|
|
491
|
+
}
|
|
492
|
+
if (index !== parameters.length) {
|
|
493
|
+
throw new Error(
|
|
494
|
+
`Parameter count mismatch: SQL statement has ${index} parameter(s), but ${parameters.length} value(s) were provided.`
|
|
495
|
+
);
|
|
496
|
+
}
|
|
497
|
+
return { sql: result, bindValues: parameters };
|
|
498
|
+
}
|
|
499
|
+
};
|
|
500
|
+
var damengConnector = new DamengConnector();
|
|
501
|
+
ConnectorRegistry.register(damengConnector);
|
|
502
|
+
export {
|
|
503
|
+
DamengConnector
|
|
504
|
+
};
|