@bytebase/dbhub 0.20.0 → 0.21.1

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.
@@ -0,0 +1,320 @@
1
+ import {
2
+ quoteIdentifier
3
+ } from "./chunk-JFWX35TB.js";
4
+ import {
5
+ SQLRowLimiter
6
+ } from "./chunk-BRXZ5ZQB.js";
7
+ import {
8
+ ConnectorRegistry,
9
+ SafeURL,
10
+ obfuscateDSNPassword,
11
+ splitSQLStatements
12
+ } from "./chunk-C7WEAPX4.js";
13
+
14
+ // src/connectors/sqlite/index.ts
15
+ import Database from "better-sqlite3";
16
+ var SQLiteDSNParser = class {
17
+ async parse(dsn, config) {
18
+ if (!this.isValidDSN(dsn)) {
19
+ const obfuscatedDSN = obfuscateDSNPassword(dsn);
20
+ const expectedFormat = this.getSampleDSN();
21
+ throw new Error(
22
+ `Invalid SQLite DSN format.
23
+ Provided: ${obfuscatedDSN}
24
+ Expected: ${expectedFormat}`
25
+ );
26
+ }
27
+ try {
28
+ const url = new SafeURL(dsn);
29
+ let dbPath;
30
+ if (url.hostname === "" && url.pathname === "/:memory:") {
31
+ dbPath = ":memory:";
32
+ } else {
33
+ if (url.pathname.startsWith("//")) {
34
+ dbPath = url.pathname.substring(2);
35
+ } else if (url.pathname.match(/^\/[A-Za-z]:\//)) {
36
+ dbPath = url.pathname.substring(1);
37
+ } else {
38
+ dbPath = url.pathname;
39
+ }
40
+ }
41
+ return { dbPath };
42
+ } catch (error) {
43
+ throw new Error(
44
+ `Failed to parse SQLite DSN: ${error instanceof Error ? error.message : String(error)}`
45
+ );
46
+ }
47
+ }
48
+ getSampleDSN() {
49
+ return "sqlite:///path/to/database.db";
50
+ }
51
+ isValidDSN(dsn) {
52
+ try {
53
+ return dsn.startsWith("sqlite://");
54
+ } catch (error) {
55
+ return false;
56
+ }
57
+ }
58
+ };
59
+ var SQLiteConnector = class _SQLiteConnector {
60
+ constructor() {
61
+ this.id = "sqlite";
62
+ this.name = "SQLite";
63
+ this.dsnParser = new SQLiteDSNParser();
64
+ this.db = null;
65
+ this.dbPath = ":memory:";
66
+ // Default to in-memory database
67
+ // Source ID is set by ConnectorManager after cloning
68
+ this.sourceId = "default";
69
+ }
70
+ getId() {
71
+ return this.sourceId;
72
+ }
73
+ clone() {
74
+ return new _SQLiteConnector();
75
+ }
76
+ /**
77
+ * Connect to SQLite database
78
+ * Note: SQLite does not support connection timeouts as it's a local file-based database.
79
+ * The config parameter is accepted for interface compliance but ignored.
80
+ */
81
+ async connect(dsn, initScript, config) {
82
+ const parsedConfig = await this.dsnParser.parse(dsn, config);
83
+ this.dbPath = parsedConfig.dbPath;
84
+ try {
85
+ const dbOptions = {};
86
+ if (config?.readonly && this.dbPath !== ":memory:") {
87
+ dbOptions.readonly = true;
88
+ }
89
+ this.db = new Database(this.dbPath, dbOptions);
90
+ this.db.defaultSafeIntegers(true);
91
+ if (initScript) {
92
+ this.db.exec(initScript);
93
+ }
94
+ } catch (error) {
95
+ console.error("Failed to connect to SQLite database:", error);
96
+ throw error;
97
+ }
98
+ }
99
+ async disconnect() {
100
+ if (this.db) {
101
+ try {
102
+ if (!this.db.inTransaction) {
103
+ this.db.close();
104
+ } else {
105
+ try {
106
+ this.db.exec("ROLLBACK");
107
+ } catch (rollbackError) {
108
+ }
109
+ this.db.close();
110
+ }
111
+ this.db = null;
112
+ } catch (error) {
113
+ console.error("Error during SQLite disconnect:", error);
114
+ this.db = null;
115
+ }
116
+ }
117
+ return Promise.resolve();
118
+ }
119
+ async getSchemas() {
120
+ if (!this.db) {
121
+ throw new Error("Not connected to SQLite database");
122
+ }
123
+ return ["main"];
124
+ }
125
+ async getTables(schema) {
126
+ if (!this.db) {
127
+ throw new Error("Not connected to SQLite database");
128
+ }
129
+ try {
130
+ const rows = this.db.prepare(
131
+ `
132
+ SELECT name FROM sqlite_master
133
+ WHERE type='table' AND name NOT LIKE 'sqlite_%'
134
+ ORDER BY name
135
+ `
136
+ ).all();
137
+ return rows.map((row) => row.name);
138
+ } catch (error) {
139
+ throw error;
140
+ }
141
+ }
142
+ async tableExists(tableName, schema) {
143
+ if (!this.db) {
144
+ throw new Error("Not connected to SQLite database");
145
+ }
146
+ try {
147
+ const row = this.db.prepare(
148
+ `
149
+ SELECT name FROM sqlite_master
150
+ WHERE type='table' AND name = ?
151
+ `
152
+ ).get(tableName);
153
+ return !!row;
154
+ } catch (error) {
155
+ throw error;
156
+ }
157
+ }
158
+ async getTableIndexes(tableName, schema) {
159
+ if (!this.db) {
160
+ throw new Error("Not connected to SQLite database");
161
+ }
162
+ try {
163
+ const indexInfoRows = this.db.prepare(
164
+ `
165
+ SELECT
166
+ name as index_name,
167
+ 0 as is_unique
168
+ FROM sqlite_master
169
+ WHERE type = 'index'
170
+ AND tbl_name = ?
171
+ `
172
+ ).all(tableName);
173
+ const quotedTableName = quoteIdentifier(tableName, "sqlite");
174
+ const indexListRows = this.db.prepare(`PRAGMA index_list(${quotedTableName})`).all();
175
+ const indexUniqueMap = /* @__PURE__ */ new Map();
176
+ for (const indexListRow of indexListRows) {
177
+ indexUniqueMap.set(indexListRow.name, indexListRow.unique === 1);
178
+ }
179
+ const tableInfo = this.db.prepare(`PRAGMA table_info(${quotedTableName})`).all();
180
+ const pkColumns = tableInfo.filter((col) => col.pk > 0).map((col) => col.name);
181
+ const results = [];
182
+ for (const indexInfo of indexInfoRows) {
183
+ const quotedIndexName = quoteIdentifier(indexInfo.index_name, "sqlite");
184
+ const indexDetailRows = this.db.prepare(`PRAGMA index_info(${quotedIndexName})`).all();
185
+ const columnNames = indexDetailRows.map((row) => row.name);
186
+ results.push({
187
+ index_name: indexInfo.index_name,
188
+ column_names: columnNames,
189
+ is_unique: indexUniqueMap.get(indexInfo.index_name) || false,
190
+ is_primary: false
191
+ });
192
+ }
193
+ if (pkColumns.length > 0) {
194
+ results.push({
195
+ index_name: "PRIMARY",
196
+ column_names: pkColumns,
197
+ is_unique: true,
198
+ is_primary: true
199
+ });
200
+ }
201
+ return results;
202
+ } catch (error) {
203
+ throw error;
204
+ }
205
+ }
206
+ async getTableSchema(tableName, schema) {
207
+ if (!this.db) {
208
+ throw new Error("Not connected to SQLite database");
209
+ }
210
+ try {
211
+ const quotedTableName = quoteIdentifier(tableName, "sqlite");
212
+ const rows = this.db.prepare(`PRAGMA table_info(${quotedTableName})`).all();
213
+ const columns = rows.map((row) => ({
214
+ column_name: row.name,
215
+ data_type: row.type,
216
+ // In SQLite, primary key columns are automatically NOT NULL even if notnull=0
217
+ is_nullable: row.notnull === 1 || row.pk > 0 ? "NO" : "YES",
218
+ column_default: row.dflt_value,
219
+ description: null
220
+ }));
221
+ return columns;
222
+ } catch (error) {
223
+ throw error;
224
+ }
225
+ }
226
+ async getStoredProcedures(schema, routineType) {
227
+ if (!this.db) {
228
+ throw new Error("Not connected to SQLite database");
229
+ }
230
+ return [];
231
+ }
232
+ async getStoredProcedureDetail(procedureName, schema) {
233
+ if (!this.db) {
234
+ throw new Error("Not connected to SQLite database");
235
+ }
236
+ throw new Error(
237
+ "SQLite does not support stored procedures. Functions are defined programmatically through the SQLite API, not stored in the database."
238
+ );
239
+ }
240
+ async executeSQL(sql, options, parameters) {
241
+ if (!this.db) {
242
+ throw new Error("Not connected to SQLite database");
243
+ }
244
+ try {
245
+ const statements = splitSQLStatements(sql, "sqlite");
246
+ if (statements.length === 1) {
247
+ let processedStatement = statements[0];
248
+ const trimmedStatement = statements[0].toLowerCase().trim();
249
+ 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"));
250
+ if (options.maxRows) {
251
+ processedStatement = SQLRowLimiter.applyMaxRows(processedStatement, options.maxRows);
252
+ }
253
+ if (isReadStatement) {
254
+ if (parameters && parameters.length > 0) {
255
+ try {
256
+ const rows = this.db.prepare(processedStatement).all(...parameters);
257
+ return { rows, rowCount: rows.length };
258
+ } catch (error) {
259
+ console.error(`[SQLite executeSQL] ERROR: ${error.message}`);
260
+ console.error(`[SQLite executeSQL] SQL: ${processedStatement}`);
261
+ console.error(`[SQLite executeSQL] Parameters: ${JSON.stringify(parameters)}`);
262
+ throw error;
263
+ }
264
+ } else {
265
+ const rows = this.db.prepare(processedStatement).all();
266
+ return { rows, rowCount: rows.length };
267
+ }
268
+ } else {
269
+ let result;
270
+ if (parameters && parameters.length > 0) {
271
+ try {
272
+ result = this.db.prepare(processedStatement).run(...parameters);
273
+ } catch (error) {
274
+ console.error(`[SQLite executeSQL] ERROR: ${error.message}`);
275
+ console.error(`[SQLite executeSQL] SQL: ${processedStatement}`);
276
+ console.error(`[SQLite executeSQL] Parameters: ${JSON.stringify(parameters)}`);
277
+ throw error;
278
+ }
279
+ } else {
280
+ result = this.db.prepare(processedStatement).run();
281
+ }
282
+ return { rows: [], rowCount: result.changes };
283
+ }
284
+ } else {
285
+ if (parameters && parameters.length > 0) {
286
+ throw new Error("Parameters are not supported for multi-statement queries in SQLite");
287
+ }
288
+ const readStatements = [];
289
+ const writeStatements = [];
290
+ for (const statement of statements) {
291
+ const trimmedStatement = statement.toLowerCase().trim();
292
+ 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"))) {
293
+ readStatements.push(statement);
294
+ } else {
295
+ writeStatements.push(statement);
296
+ }
297
+ }
298
+ let totalChanges = 0;
299
+ for (const statement of writeStatements) {
300
+ const result = this.db.prepare(statement).run();
301
+ totalChanges += result.changes;
302
+ }
303
+ let allRows = [];
304
+ for (let statement of readStatements) {
305
+ statement = SQLRowLimiter.applyMaxRows(statement, options.maxRows);
306
+ const result = this.db.prepare(statement).all();
307
+ allRows.push(...result);
308
+ }
309
+ return { rows: allRows, rowCount: totalChanges + allRows.length };
310
+ }
311
+ } catch (error) {
312
+ throw error;
313
+ }
314
+ }
315
+ };
316
+ var sqliteConnector = new SQLiteConnector();
317
+ ConnectorRegistry.register(sqliteConnector);
318
+ export {
319
+ SQLiteConnector
320
+ };