@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,412 @@
|
|
|
1
|
+
import {
|
|
2
|
+
quoteIdentifier
|
|
3
|
+
} from "./chunk-IPK7BYBL.js";
|
|
4
|
+
import {
|
|
5
|
+
SQLRowLimiter
|
|
6
|
+
} from "./chunk-ZNQTMARG.js";
|
|
7
|
+
import {
|
|
8
|
+
ConnectorRegistry,
|
|
9
|
+
SafeURL,
|
|
10
|
+
obfuscateDSNPassword,
|
|
11
|
+
splitSQLStatements
|
|
12
|
+
} from "./chunk-SQA2ISDE.js";
|
|
13
|
+
|
|
14
|
+
// src/connectors/sqlite/suppress-experimental-warning.ts
|
|
15
|
+
var installed = false;
|
|
16
|
+
function suppressSqliteExperimentalWarning() {
|
|
17
|
+
if (installed) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
installed = true;
|
|
21
|
+
const originalEmitWarning = process.emitWarning.bind(process);
|
|
22
|
+
process.emitWarning = (warning, ...args) => {
|
|
23
|
+
const message = typeof warning === "string" ? warning : warning?.message;
|
|
24
|
+
if (message && message.includes("SQLite is an experimental feature")) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
return originalEmitWarning(warning, ...args);
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// src/connectors/sqlite/index.ts
|
|
32
|
+
var SQLiteDSNParser = class {
|
|
33
|
+
async parse(dsn, config) {
|
|
34
|
+
if (!this.isValidDSN(dsn)) {
|
|
35
|
+
const obfuscatedDSN = obfuscateDSNPassword(dsn);
|
|
36
|
+
const expectedFormat = this.getSampleDSN();
|
|
37
|
+
throw new Error(
|
|
38
|
+
`Invalid SQLite DSN format.
|
|
39
|
+
Provided: ${obfuscatedDSN}
|
|
40
|
+
Expected: ${expectedFormat}`
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
const url = new SafeURL(dsn);
|
|
45
|
+
let dbPath;
|
|
46
|
+
if (url.hostname === "" && url.pathname === "/:memory:") {
|
|
47
|
+
dbPath = ":memory:";
|
|
48
|
+
} else {
|
|
49
|
+
if (url.pathname.startsWith("//")) {
|
|
50
|
+
dbPath = url.pathname.substring(2);
|
|
51
|
+
} else if (url.pathname.match(/^\/[A-Za-z]:\//)) {
|
|
52
|
+
dbPath = url.pathname.substring(1);
|
|
53
|
+
} else {
|
|
54
|
+
dbPath = url.pathname;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return { dbPath };
|
|
58
|
+
} catch (error) {
|
|
59
|
+
throw new Error(
|
|
60
|
+
`Failed to parse SQLite DSN: ${error instanceof Error ? error.message : String(error)}`
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
getSampleDSN() {
|
|
65
|
+
return "sqlite:///path/to/database.db";
|
|
66
|
+
}
|
|
67
|
+
isValidDSN(dsn) {
|
|
68
|
+
try {
|
|
69
|
+
return dsn.startsWith("sqlite://");
|
|
70
|
+
} catch (error) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
var SQLiteConnector = class _SQLiteConnector {
|
|
76
|
+
constructor() {
|
|
77
|
+
this.id = "sqlite";
|
|
78
|
+
this.name = "SQLite";
|
|
79
|
+
this.dsnParser = new SQLiteDSNParser();
|
|
80
|
+
this.db = null;
|
|
81
|
+
this.dbPath = ":memory:";
|
|
82
|
+
// Default to in-memory database
|
|
83
|
+
// Source ID is set by ConnectorManager after cloning
|
|
84
|
+
this.sourceId = "default";
|
|
85
|
+
}
|
|
86
|
+
getId() {
|
|
87
|
+
return this.sourceId;
|
|
88
|
+
}
|
|
89
|
+
clone() {
|
|
90
|
+
return new _SQLiteConnector();
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Prepare a user-facing query statement with BigInt reads enabled, so 64-bit
|
|
94
|
+
* integer values in the result rows keep full precision.
|
|
95
|
+
*
|
|
96
|
+
* better-sqlite3 exposed this connection-wide via `defaultSafeIntegers(true)`;
|
|
97
|
+
* `node:sqlite` only offers it per-statement via `setReadBigInts`. This is for
|
|
98
|
+
* `executeSQL` only — schema introspection reads small integer flags and uses
|
|
99
|
+
* `queryAll`/`queryOne` (plain numbers) so comparisons like `=== 1` work.
|
|
100
|
+
*/
|
|
101
|
+
prepare(sql) {
|
|
102
|
+
if (!this.db) {
|
|
103
|
+
throw new Error("Not connected to SQLite database");
|
|
104
|
+
}
|
|
105
|
+
const statement = this.db.prepare(sql);
|
|
106
|
+
statement.setReadBigInts(true);
|
|
107
|
+
return statement;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Run an introspection query and return all rows. Integers come back as plain
|
|
111
|
+
* numbers (no `setReadBigInts`), which is what the schema-reading callers need
|
|
112
|
+
* for boolean flag comparisons. node:sqlite types `.all()` as a generic record
|
|
113
|
+
* array, so the result is cast to the caller's expected row shape.
|
|
114
|
+
*/
|
|
115
|
+
queryAll(sql, ...params) {
|
|
116
|
+
if (!this.db) {
|
|
117
|
+
throw new Error("Not connected to SQLite database");
|
|
118
|
+
}
|
|
119
|
+
return this.db.prepare(sql).all(...params);
|
|
120
|
+
}
|
|
121
|
+
/** Run an introspection query and return a single row (or undefined). */
|
|
122
|
+
queryOne(sql, ...params) {
|
|
123
|
+
if (!this.db) {
|
|
124
|
+
throw new Error("Not connected to SQLite database");
|
|
125
|
+
}
|
|
126
|
+
return this.db.prepare(sql).get(...params);
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Connect to SQLite database
|
|
130
|
+
* Note: SQLite does not support connection timeouts as it's a local file-based database.
|
|
131
|
+
* The config parameter is accepted for interface compliance but ignored.
|
|
132
|
+
*/
|
|
133
|
+
async connect(dsn, initScript, config) {
|
|
134
|
+
const parsedConfig = await this.dsnParser.parse(dsn, config);
|
|
135
|
+
this.dbPath = parsedConfig.dbPath;
|
|
136
|
+
try {
|
|
137
|
+
suppressSqliteExperimentalWarning();
|
|
138
|
+
const { DatabaseSync } = await import("node:sqlite");
|
|
139
|
+
const dbOptions = {
|
|
140
|
+
// node:sqlite enables foreign key constraints by default, whereas
|
|
141
|
+
// better-sqlite3 (and SQLite's own default) leaves them off. Preserve
|
|
142
|
+
// the historical behavior; callers can opt in via `PRAGMA foreign_keys = ON`.
|
|
143
|
+
enableForeignKeyConstraints: false
|
|
144
|
+
};
|
|
145
|
+
if (config?.readonly && this.dbPath !== ":memory:") {
|
|
146
|
+
dbOptions.readOnly = true;
|
|
147
|
+
}
|
|
148
|
+
this.db = new DatabaseSync(this.dbPath, dbOptions);
|
|
149
|
+
if (initScript) {
|
|
150
|
+
this.db.exec(initScript);
|
|
151
|
+
}
|
|
152
|
+
} catch (error) {
|
|
153
|
+
console.error("Failed to connect to SQLite database:", error);
|
|
154
|
+
throw error;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
async disconnect() {
|
|
158
|
+
if (this.db) {
|
|
159
|
+
try {
|
|
160
|
+
const inTransaction = this.db.isTransaction;
|
|
161
|
+
if (!inTransaction) {
|
|
162
|
+
this.db.close();
|
|
163
|
+
} else {
|
|
164
|
+
try {
|
|
165
|
+
this.db.exec("ROLLBACK");
|
|
166
|
+
} catch (rollbackError) {
|
|
167
|
+
}
|
|
168
|
+
this.db.close();
|
|
169
|
+
}
|
|
170
|
+
this.db = null;
|
|
171
|
+
} catch (error) {
|
|
172
|
+
console.error("Error during SQLite disconnect:", error);
|
|
173
|
+
this.db = null;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return Promise.resolve();
|
|
177
|
+
}
|
|
178
|
+
async getSchemas() {
|
|
179
|
+
if (!this.db) {
|
|
180
|
+
throw new Error("Not connected to SQLite database");
|
|
181
|
+
}
|
|
182
|
+
return ["main"];
|
|
183
|
+
}
|
|
184
|
+
async getTables(schema) {
|
|
185
|
+
if (!this.db) {
|
|
186
|
+
throw new Error("Not connected to SQLite database");
|
|
187
|
+
}
|
|
188
|
+
try {
|
|
189
|
+
const rows = this.queryAll(`
|
|
190
|
+
SELECT name FROM sqlite_master
|
|
191
|
+
WHERE type='table' AND name NOT LIKE 'sqlite_%'
|
|
192
|
+
ORDER BY name
|
|
193
|
+
`);
|
|
194
|
+
return rows.map((row) => row.name);
|
|
195
|
+
} catch (error) {
|
|
196
|
+
throw error;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
async getViews(schema) {
|
|
200
|
+
if (!this.db) {
|
|
201
|
+
throw new Error("Not connected to SQLite database");
|
|
202
|
+
}
|
|
203
|
+
try {
|
|
204
|
+
const rows = this.queryAll(`
|
|
205
|
+
SELECT name FROM sqlite_master
|
|
206
|
+
WHERE type='view' AND name NOT LIKE 'sqlite_%'
|
|
207
|
+
ORDER BY name
|
|
208
|
+
`);
|
|
209
|
+
return rows.map((row) => row.name);
|
|
210
|
+
} catch (error) {
|
|
211
|
+
throw error;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
async tableExists(tableName, schema) {
|
|
215
|
+
if (!this.db) {
|
|
216
|
+
throw new Error("Not connected to SQLite database");
|
|
217
|
+
}
|
|
218
|
+
try {
|
|
219
|
+
const row = this.queryOne(
|
|
220
|
+
`
|
|
221
|
+
SELECT name FROM sqlite_master
|
|
222
|
+
WHERE type='table' AND name = ?
|
|
223
|
+
`,
|
|
224
|
+
tableName
|
|
225
|
+
);
|
|
226
|
+
return !!row;
|
|
227
|
+
} catch (error) {
|
|
228
|
+
throw error;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
async getTableIndexes(tableName, schema) {
|
|
232
|
+
if (!this.db) {
|
|
233
|
+
throw new Error("Not connected to SQLite database");
|
|
234
|
+
}
|
|
235
|
+
try {
|
|
236
|
+
const indexInfoRows = this.queryAll(
|
|
237
|
+
`
|
|
238
|
+
SELECT
|
|
239
|
+
name as index_name,
|
|
240
|
+
0 as is_unique
|
|
241
|
+
FROM sqlite_master
|
|
242
|
+
WHERE type = 'index'
|
|
243
|
+
AND tbl_name = ?
|
|
244
|
+
`,
|
|
245
|
+
tableName
|
|
246
|
+
);
|
|
247
|
+
const quotedTableName = quoteIdentifier(tableName, "sqlite");
|
|
248
|
+
const indexListRows = this.queryAll(
|
|
249
|
+
`PRAGMA index_list(${quotedTableName})`
|
|
250
|
+
);
|
|
251
|
+
const indexUniqueMap = /* @__PURE__ */ new Map();
|
|
252
|
+
for (const indexListRow of indexListRows) {
|
|
253
|
+
indexUniqueMap.set(indexListRow.name, indexListRow.unique === 1);
|
|
254
|
+
}
|
|
255
|
+
const tableInfo = this.queryAll(`PRAGMA table_info(${quotedTableName})`);
|
|
256
|
+
const pkColumns = tableInfo.filter((col) => col.pk > 0).map((col) => col.name);
|
|
257
|
+
const results = [];
|
|
258
|
+
for (const indexInfo of indexInfoRows) {
|
|
259
|
+
const quotedIndexName = quoteIdentifier(indexInfo.index_name, "sqlite");
|
|
260
|
+
const indexDetailRows = this.queryAll(
|
|
261
|
+
`PRAGMA index_info(${quotedIndexName})`
|
|
262
|
+
);
|
|
263
|
+
const columnNames = indexDetailRows.map((row) => row.name);
|
|
264
|
+
results.push({
|
|
265
|
+
index_name: indexInfo.index_name,
|
|
266
|
+
column_names: columnNames,
|
|
267
|
+
is_unique: indexUniqueMap.get(indexInfo.index_name) || false,
|
|
268
|
+
is_primary: false
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
if (pkColumns.length > 0) {
|
|
272
|
+
results.push({
|
|
273
|
+
index_name: "PRIMARY",
|
|
274
|
+
column_names: pkColumns,
|
|
275
|
+
is_unique: true,
|
|
276
|
+
is_primary: true
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
return results;
|
|
280
|
+
} catch (error) {
|
|
281
|
+
throw error;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
async getTableSchema(tableName, schema) {
|
|
285
|
+
if (!this.db) {
|
|
286
|
+
throw new Error("Not connected to SQLite database");
|
|
287
|
+
}
|
|
288
|
+
try {
|
|
289
|
+
const quotedTableName = quoteIdentifier(tableName, "sqlite");
|
|
290
|
+
const rows = this.queryAll(`PRAGMA table_info(${quotedTableName})`);
|
|
291
|
+
const columns = rows.map((row) => ({
|
|
292
|
+
column_name: row.name,
|
|
293
|
+
data_type: row.type,
|
|
294
|
+
// In SQLite, primary key columns are automatically NOT NULL even if notnull=0
|
|
295
|
+
is_nullable: row.notnull === 1 || row.pk > 0 ? "NO" : "YES",
|
|
296
|
+
column_default: row.dflt_value,
|
|
297
|
+
description: null
|
|
298
|
+
}));
|
|
299
|
+
return columns;
|
|
300
|
+
} catch (error) {
|
|
301
|
+
throw error;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
async getStoredProcedures(schema, routineType) {
|
|
305
|
+
if (!this.db) {
|
|
306
|
+
throw new Error("Not connected to SQLite database");
|
|
307
|
+
}
|
|
308
|
+
return [];
|
|
309
|
+
}
|
|
310
|
+
async getStoredProcedureDetail(procedureName, schema) {
|
|
311
|
+
if (!this.db) {
|
|
312
|
+
throw new Error("Not connected to SQLite database");
|
|
313
|
+
}
|
|
314
|
+
throw new Error(
|
|
315
|
+
"SQLite does not support stored procedures. Functions are defined programmatically through the SQLite API, not stored in the database."
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
async executeSQL(sql, options, parameters) {
|
|
319
|
+
if (!this.db) {
|
|
320
|
+
throw new Error("Not connected to SQLite database");
|
|
321
|
+
}
|
|
322
|
+
if (options.readonly) {
|
|
323
|
+
this.db.exec("PRAGMA query_only = ON");
|
|
324
|
+
}
|
|
325
|
+
try {
|
|
326
|
+
const statements = splitSQLStatements(sql, "sqlite");
|
|
327
|
+
if (statements.length === 1) {
|
|
328
|
+
let processedStatement = statements[0];
|
|
329
|
+
const trimmedStatement = statements[0].toLowerCase().trim();
|
|
330
|
+
const isReadStatement = trimmedStatement.startsWith("select") || trimmedStatement.startsWith("with") || trimmedStatement.startsWith("explain") || trimmedStatement.startsWith("analyze") || trimmedStatement.startsWith("pragma") && (trimmedStatement.includes("table_info") || trimmedStatement.includes("index_info") || trimmedStatement.includes("index_list") || trimmedStatement.includes("foreign_key_list"));
|
|
331
|
+
if (options.maxRows) {
|
|
332
|
+
processedStatement = SQLRowLimiter.applyMaxRows(processedStatement, options.maxRows);
|
|
333
|
+
}
|
|
334
|
+
if (isReadStatement) {
|
|
335
|
+
if (parameters && parameters.length > 0) {
|
|
336
|
+
try {
|
|
337
|
+
const rows = this.prepare(processedStatement).all(...parameters);
|
|
338
|
+
return { rows, rowCount: rows.length };
|
|
339
|
+
} catch (error) {
|
|
340
|
+
console.error(`[SQLite executeSQL] ERROR: ${error.message}`);
|
|
341
|
+
console.error(`[SQLite executeSQL] SQL: ${processedStatement}`);
|
|
342
|
+
console.error(`[SQLite executeSQL] Parameters: ${JSON.stringify(parameters)}`);
|
|
343
|
+
throw error;
|
|
344
|
+
}
|
|
345
|
+
} else {
|
|
346
|
+
const rows = this.prepare(processedStatement).all();
|
|
347
|
+
return { rows, rowCount: rows.length };
|
|
348
|
+
}
|
|
349
|
+
} else {
|
|
350
|
+
let result;
|
|
351
|
+
if (parameters && parameters.length > 0) {
|
|
352
|
+
try {
|
|
353
|
+
result = this.prepare(processedStatement).run(...parameters);
|
|
354
|
+
} catch (error) {
|
|
355
|
+
console.error(`[SQLite executeSQL] ERROR: ${error.message}`);
|
|
356
|
+
console.error(`[SQLite executeSQL] SQL: ${processedStatement}`);
|
|
357
|
+
console.error(`[SQLite executeSQL] Parameters: ${JSON.stringify(parameters)}`);
|
|
358
|
+
throw error;
|
|
359
|
+
}
|
|
360
|
+
} else {
|
|
361
|
+
result = this.prepare(processedStatement).run();
|
|
362
|
+
}
|
|
363
|
+
return { rows: [], rowCount: Number(result.changes) };
|
|
364
|
+
}
|
|
365
|
+
} else {
|
|
366
|
+
if (parameters && parameters.length > 0) {
|
|
367
|
+
throw new Error("Parameters are not supported for multi-statement queries in SQLite");
|
|
368
|
+
}
|
|
369
|
+
const readStatements = [];
|
|
370
|
+
const writeStatements = [];
|
|
371
|
+
for (const statement of statements) {
|
|
372
|
+
const trimmedStatement = statement.toLowerCase().trim();
|
|
373
|
+
if (trimmedStatement.startsWith("select") || trimmedStatement.startsWith("with") || trimmedStatement.startsWith("explain") || trimmedStatement.startsWith("analyze") || trimmedStatement.startsWith("pragma") && (trimmedStatement.includes("table_info") || trimmedStatement.includes("index_info") || trimmedStatement.includes("index_list") || trimmedStatement.includes("foreign_key_list"))) {
|
|
374
|
+
readStatements.push(statement);
|
|
375
|
+
} else {
|
|
376
|
+
writeStatements.push(statement);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
let totalChanges = 0;
|
|
380
|
+
for (const statement of writeStatements) {
|
|
381
|
+
if (options.readonly) {
|
|
382
|
+
this.db.exec("PRAGMA query_only = ON");
|
|
383
|
+
}
|
|
384
|
+
const result = this.prepare(statement).run();
|
|
385
|
+
totalChanges += Number(result.changes);
|
|
386
|
+
}
|
|
387
|
+
let allRows = [];
|
|
388
|
+
for (let statement of readStatements) {
|
|
389
|
+
if (options.readonly) {
|
|
390
|
+
this.db.exec("PRAGMA query_only = ON");
|
|
391
|
+
}
|
|
392
|
+
statement = SQLRowLimiter.applyMaxRows(statement, options.maxRows);
|
|
393
|
+
const result = this.prepare(statement).all();
|
|
394
|
+
allRows.push(...result);
|
|
395
|
+
}
|
|
396
|
+
return { rows: allRows, rowCount: totalChanges + allRows.length };
|
|
397
|
+
}
|
|
398
|
+
} finally {
|
|
399
|
+
if (options.readonly) {
|
|
400
|
+
try {
|
|
401
|
+
this.db.exec("PRAGMA query_only = OFF");
|
|
402
|
+
} catch {
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
var sqliteConnector = new SQLiteConnector();
|
|
409
|
+
ConnectorRegistry.register(sqliteConnector);
|
|
410
|
+
export {
|
|
411
|
+
SQLiteConnector
|
|
412
|
+
};
|