drixio 1.1.10 → 1.2.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/README.md +1 -0
- package/dist/cli.js +999 -458
- package/dist/studio/assets/index-DOTfHOMW.js +3244 -0
- package/dist/studio/assets/index-DPNeFArq.css +1 -0
- package/dist/studio/index.html +38 -34
- package/package.json +2 -1
- package/dist/studio/assets/index-CgrvFEsQ.js +0 -2324
- package/dist/studio/assets/index-ru5pIVFY.css +0 -1
package/dist/cli.js
CHANGED
|
@@ -283,6 +283,23 @@ async function saveDatabaseUrl(url) {
|
|
|
283
283
|
);
|
|
284
284
|
}
|
|
285
285
|
}
|
|
286
|
+
function assembleConnectionUrl(type, host, port, user, password, database) {
|
|
287
|
+
const targetHost = host || "localhost";
|
|
288
|
+
const targetPort = port || (type === "postgres" ? "5432" : "3306");
|
|
289
|
+
const targetUser = user || (type === "postgres" ? "postgres" : "root");
|
|
290
|
+
const auth = password ? `${encodeURIComponent(targetUser)}:${encodeURIComponent(password)}` : encodeURIComponent(targetUser);
|
|
291
|
+
const defaultDb = type === "postgres" ? "postgres" : "";
|
|
292
|
+
const dbNameStr = database || defaultDb;
|
|
293
|
+
return `${type}://${auth}@${targetHost}:${targetPort}/${dbNameStr}`;
|
|
294
|
+
}
|
|
295
|
+
function resolveLocalDbPath(targetUrl, cwd) {
|
|
296
|
+
const cleanPath = targetUrl.replace(/^file:/, "").trim();
|
|
297
|
+
return path.isAbsolute(cleanPath) ? cleanPath : path.resolve(cwd, cleanPath);
|
|
298
|
+
}
|
|
299
|
+
function isConnectionString(url) {
|
|
300
|
+
const trimmed = url.trim();
|
|
301
|
+
return trimmed.startsWith("postgres://") || trimmed.startsWith("postgresql://") || trimmed.startsWith("mysql://");
|
|
302
|
+
}
|
|
286
303
|
var DB_ENV_KEYS;
|
|
287
304
|
var init_loader = __esm({
|
|
288
305
|
"src/logic/loader.ts"() {
|
|
@@ -307,11 +324,319 @@ var init_loader = __esm({
|
|
|
307
324
|
}
|
|
308
325
|
});
|
|
309
326
|
|
|
327
|
+
// src/logic/dialect.ts
|
|
328
|
+
function getDialect(type) {
|
|
329
|
+
switch (type) {
|
|
330
|
+
case "sqlite":
|
|
331
|
+
return new SqliteDialect();
|
|
332
|
+
case "postgres":
|
|
333
|
+
return new PostgresDialect();
|
|
334
|
+
case "mysql":
|
|
335
|
+
return new MysqlDialect();
|
|
336
|
+
default:
|
|
337
|
+
return new SqliteDialect();
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
function buildSearchWhereClause(adapter, dbType, columns, searchInput) {
|
|
341
|
+
const searchVal = (searchInput || "").trim();
|
|
342
|
+
if (!searchVal) return "";
|
|
343
|
+
const isSqlCondition = /[=<>]|LIKE|IN|AND|OR/i.test(searchVal);
|
|
344
|
+
if (isSqlCondition) {
|
|
345
|
+
return searchVal;
|
|
346
|
+
}
|
|
347
|
+
const dialect = getDialect(dbType);
|
|
348
|
+
const strCols = columns.filter((c) => {
|
|
349
|
+
const t = c.type.toLowerCase();
|
|
350
|
+
return t.includes("char") || t.includes("text") || t.includes("string") || t.includes("uuid");
|
|
351
|
+
});
|
|
352
|
+
if (strCols.length > 0) {
|
|
353
|
+
const likeOp = dbType === "postgres" ? "ILIKE" : "LIKE";
|
|
354
|
+
const escaped = dialect.escapeString(searchVal);
|
|
355
|
+
const conditions = strCols.map(
|
|
356
|
+
(c) => `${adapter.quoteIdentifier(c.name)} ${likeOp} '%${escaped}%'`
|
|
357
|
+
);
|
|
358
|
+
return conditions.join(" OR ");
|
|
359
|
+
}
|
|
360
|
+
if (columns.length > 0) {
|
|
361
|
+
const col = columns[0];
|
|
362
|
+
const escaped = dialect.escapeString(searchVal);
|
|
363
|
+
return `${adapter.quoteIdentifier(col.name)} = '${escaped}'`;
|
|
364
|
+
}
|
|
365
|
+
return "";
|
|
366
|
+
}
|
|
367
|
+
function formatSqlDefaultValue(rawDefault) {
|
|
368
|
+
if (rawDefault === void 0 || rawDefault === null) return null;
|
|
369
|
+
let trimmed = String(rawDefault).trim();
|
|
370
|
+
if (!trimmed || trimmed.toLowerCase() === "null" || trimmed === "-") {
|
|
371
|
+
return null;
|
|
372
|
+
}
|
|
373
|
+
while (trimmed.startsWith("''") && trimmed.endsWith("''") && trimmed.length > 4) {
|
|
374
|
+
trimmed = trimmed.slice(1, -1);
|
|
375
|
+
}
|
|
376
|
+
if (/^-?\d+(\.\d+)?$/.test(trimmed)) {
|
|
377
|
+
return trimmed;
|
|
378
|
+
}
|
|
379
|
+
const lower = trimmed.toLowerCase();
|
|
380
|
+
if (lower === "true" || lower === "false") {
|
|
381
|
+
return lower.toUpperCase();
|
|
382
|
+
}
|
|
383
|
+
const upper = trimmed.toUpperCase();
|
|
384
|
+
if (upper === "CURRENT_TIMESTAMP" || upper === "CURRENT_DATE" || upper === "CURRENT_TIME" || upper === "NOW()" || upper === "TIMESTAMP" || upper.startsWith("CURRENT_TIMESTAMP") || upper.startsWith("NOW()") || upper.startsWith("GEN_RANDOM_UUID(") || upper.startsWith("UUID_GENERATE_") || upper.startsWith("UUID(") || trimmed.startsWith("(") && trimmed.endsWith(")")) {
|
|
385
|
+
return upper === "TIMESTAMP" ? "CURRENT_TIMESTAMP" : trimmed;
|
|
386
|
+
}
|
|
387
|
+
const pgCastMatch = trimmed.match(/^('[\s\S]*')(?:::[\w\s()]+)$/);
|
|
388
|
+
if (pgCastMatch) {
|
|
389
|
+
return pgCastMatch[1];
|
|
390
|
+
}
|
|
391
|
+
if (trimmed.startsWith("'") && trimmed.endsWith("'") && trimmed.length >= 2) {
|
|
392
|
+
return trimmed;
|
|
393
|
+
}
|
|
394
|
+
if (trimmed.startsWith('"') && trimmed.endsWith('"') && trimmed.length >= 2) {
|
|
395
|
+
const inner = trimmed.slice(1, -1).replace(/'/g, "''");
|
|
396
|
+
return `'${inner}'`;
|
|
397
|
+
}
|
|
398
|
+
const escaped = trimmed.replace(/'/g, "''");
|
|
399
|
+
return `'${escaped}'`;
|
|
400
|
+
}
|
|
401
|
+
function generateExplainQuery(rawSql, dbType) {
|
|
402
|
+
let cleanSql = rawSql.replace(/--.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "").trim().replace(/;+$/, "").trim();
|
|
403
|
+
if (!cleanSql) {
|
|
404
|
+
return { sql: "", error: "SQL query is empty after stripping comments" };
|
|
405
|
+
}
|
|
406
|
+
if (/^(CREATE|DROP|ALTER|TRUNCATE)\s+/i.test(cleanSql)) {
|
|
407
|
+
return {
|
|
408
|
+
sql: "",
|
|
409
|
+
error: "DDL statements (CREATE, DROP, ALTER, TRUNCATE) do not have query execution plans to explain."
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
const hasUserExplain = /^EXPLAIN\b/i.test(cleanSql);
|
|
413
|
+
let targetSql = cleanSql;
|
|
414
|
+
if (hasUserExplain) {
|
|
415
|
+
targetSql = cleanSql.replace(/^EXPLAIN\s+(QUERY\s+PLAN\s+)?/i, "").replace(
|
|
416
|
+
/^\((ANALYZE|COSTS|VERBOSE|BUFFERS|FORMAT\s+\w+|,|\s)+\)\s*/i,
|
|
417
|
+
""
|
|
418
|
+
).trim();
|
|
419
|
+
}
|
|
420
|
+
let explainSql = "";
|
|
421
|
+
const isDmlWrite = /^(INSERT|UPDATE|DELETE)\s+/i.test(targetSql);
|
|
422
|
+
if (dbType === "sqlite") {
|
|
423
|
+
explainSql = `EXPLAIN QUERY PLAN ${targetSql}`;
|
|
424
|
+
} else if (dbType === "postgres") {
|
|
425
|
+
if (isDmlWrite) {
|
|
426
|
+
explainSql = `EXPLAIN (COSTS, VERBOSE) ${targetSql}`;
|
|
427
|
+
} else {
|
|
428
|
+
explainSql = `EXPLAIN (ANALYZE, COSTS, VERBOSE, BUFFERS) ${targetSql}`;
|
|
429
|
+
}
|
|
430
|
+
} else if (dbType === "mysql") {
|
|
431
|
+
explainSql = `EXPLAIN ${targetSql}`;
|
|
432
|
+
} else {
|
|
433
|
+
explainSql = `EXPLAIN ${targetSql}`;
|
|
434
|
+
}
|
|
435
|
+
return { sql: explainSql };
|
|
436
|
+
}
|
|
437
|
+
var SqliteDialect, PostgresDialect, MysqlDialect;
|
|
438
|
+
var init_dialect = __esm({
|
|
439
|
+
"src/logic/dialect.ts"() {
|
|
440
|
+
"use strict";
|
|
441
|
+
SqliteDialect = class {
|
|
442
|
+
quoteIdentifier(name) {
|
|
443
|
+
return `"${name}"`;
|
|
444
|
+
return `"${name.replace(/"/g, '""')}"`;
|
|
445
|
+
}
|
|
446
|
+
escapeString(val) {
|
|
447
|
+
return val.replace(/'/g, "''");
|
|
448
|
+
}
|
|
449
|
+
buildCreateTable(tableName, columns) {
|
|
450
|
+
const lines = columns.map((col) => {
|
|
451
|
+
const isPk = !!(col.isPk || col.primaryKey);
|
|
452
|
+
const tLower = col.type.toLowerCase();
|
|
453
|
+
let typeStr = col.type;
|
|
454
|
+
if (tLower === "integer" || tLower === "int") typeStr = "INTEGER";
|
|
455
|
+
else if (tLower === "text" || tLower === "string") typeStr = "TEXT";
|
|
456
|
+
else if (tLower === "boolean" || tLower === "bool")
|
|
457
|
+
typeStr = "BOOLEAN";
|
|
458
|
+
else if (tLower === "decimal" || tLower === "numeric" || tLower === "float" || tLower === "double")
|
|
459
|
+
typeStr = "REAL";
|
|
460
|
+
else if (tLower === "datetime" || tLower === "timestamp")
|
|
461
|
+
typeStr = "DATETIME";
|
|
462
|
+
else if (col.enumValues && col.enumValues.length > 0) {
|
|
463
|
+
typeStr = `TEXT CHECK(${this.quoteIdentifier(col.name)} IN (${col.enumValues.map((v) => `'${this.escapeString(v)}'`).join(", ")}))`;
|
|
464
|
+
}
|
|
465
|
+
if (isPk && (tLower.includes("int") || typeStr === "INTEGER")) {
|
|
466
|
+
typeStr = "INTEGER PRIMARY KEY AUTOINCREMENT";
|
|
467
|
+
} else if (isPk) {
|
|
468
|
+
typeStr += " PRIMARY KEY";
|
|
469
|
+
} else {
|
|
470
|
+
if (!col.nullable) typeStr += " NOT NULL";
|
|
471
|
+
if (col.isUnique) typeStr += " UNIQUE";
|
|
472
|
+
}
|
|
473
|
+
if (col.defaultValue && col.defaultValue !== "AutoInc" && !col.defaultValue.startsWith("FK ->")) {
|
|
474
|
+
const formatted = formatSqlDefaultValue(col.defaultValue);
|
|
475
|
+
if (formatted !== null) {
|
|
476
|
+
typeStr += ` DEFAULT ${formatted}`;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
return ` ${this.quoteIdentifier(col.name)} ${typeStr}`;
|
|
480
|
+
});
|
|
481
|
+
const fks = columns.filter(
|
|
482
|
+
(col) => col.fkTarget && col.fkTarget.table && col.fkTarget.column
|
|
483
|
+
).map((col) => {
|
|
484
|
+
let fkStr = ` FOREIGN KEY (${this.quoteIdentifier(col.name)}) REFERENCES ${this.quoteIdentifier(col.fkTarget.table)}(${this.quoteIdentifier(col.fkTarget.column)})`;
|
|
485
|
+
if (col.fkTarget.onDelete && col.fkTarget.onDelete !== "NO ACTION") {
|
|
486
|
+
fkStr += ` ON DELETE ${col.fkTarget.onDelete}`;
|
|
487
|
+
}
|
|
488
|
+
if (col.fkTarget.onUpdate && col.fkTarget.onUpdate !== "NO ACTION") {
|
|
489
|
+
fkStr += ` ON UPDATE ${col.fkTarget.onUpdate}`;
|
|
490
|
+
}
|
|
491
|
+
return fkStr;
|
|
492
|
+
});
|
|
493
|
+
if (fks.length > 0) {
|
|
494
|
+
lines.push(...fks);
|
|
495
|
+
}
|
|
496
|
+
return `CREATE TABLE ${this.quoteIdentifier(tableName)} (
|
|
497
|
+
${lines.join(",\n")}
|
|
498
|
+
);`;
|
|
499
|
+
}
|
|
500
|
+
};
|
|
501
|
+
PostgresDialect = class {
|
|
502
|
+
quoteIdentifier(name) {
|
|
503
|
+
return `"${name}"`;
|
|
504
|
+
return `"${name.replace(/"/g, '""')}"`;
|
|
505
|
+
}
|
|
506
|
+
escapeString(val) {
|
|
507
|
+
return val.replace(/'/g, "''");
|
|
508
|
+
}
|
|
509
|
+
buildCreateTable(tableName, columns) {
|
|
510
|
+
const lines = columns.map((col) => {
|
|
511
|
+
const isPk = !!(col.isPk || col.primaryKey);
|
|
512
|
+
const tLower = col.type.toLowerCase();
|
|
513
|
+
let typeStr = col.type;
|
|
514
|
+
if (isPk && (tLower === "integer" || tLower === "int")) {
|
|
515
|
+
typeStr = "SERIAL PRIMARY KEY";
|
|
516
|
+
} else {
|
|
517
|
+
if (tLower === "integer" || tLower === "int") typeStr = "INTEGER";
|
|
518
|
+
else if (tLower === "text" || tLower === "string") typeStr = "TEXT";
|
|
519
|
+
else if (tLower === "boolean" || tLower === "bool")
|
|
520
|
+
typeStr = "BOOLEAN";
|
|
521
|
+
else if (tLower === "decimal" || tLower === "numeric")
|
|
522
|
+
typeStr = "NUMERIC";
|
|
523
|
+
else if (tLower === "datetime" || tLower === "timestamp")
|
|
524
|
+
typeStr = "TIMESTAMP";
|
|
525
|
+
else if (col.enumValues && col.enumValues.length > 0) {
|
|
526
|
+
if (col.type && ![
|
|
527
|
+
"enum",
|
|
528
|
+
"varchar",
|
|
529
|
+
"varchar(255)",
|
|
530
|
+
"text",
|
|
531
|
+
"string"
|
|
532
|
+
].includes(tLower)) {
|
|
533
|
+
typeStr = this.quoteIdentifier(col.type);
|
|
534
|
+
} else {
|
|
535
|
+
typeStr = `VARCHAR(255) CHECK(${this.quoteIdentifier(col.name)} IN (${col.enumValues.map((v) => `'${this.escapeString(v)}'`).join(", ")}))`;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
if (isPk) typeStr += " PRIMARY KEY";
|
|
539
|
+
if (!col.nullable && !isPk) typeStr += " NOT NULL";
|
|
540
|
+
if (col.isUnique && !isPk) typeStr += " UNIQUE";
|
|
541
|
+
}
|
|
542
|
+
if (col.defaultValue && col.defaultValue !== "AutoInc" && !col.defaultValue.startsWith("FK ->")) {
|
|
543
|
+
const formatted = formatSqlDefaultValue(col.defaultValue);
|
|
544
|
+
if (formatted !== null) {
|
|
545
|
+
typeStr += ` DEFAULT ${formatted}`;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
return ` ${this.quoteIdentifier(col.name)} ${typeStr}`;
|
|
549
|
+
});
|
|
550
|
+
const fks = columns.filter(
|
|
551
|
+
(col) => col.fkTarget && col.fkTarget.table && col.fkTarget.column
|
|
552
|
+
).map((col) => {
|
|
553
|
+
let fkStr = ` FOREIGN KEY (${this.quoteIdentifier(col.name)}) REFERENCES ${this.quoteIdentifier(col.fkTarget.table)}(${this.quoteIdentifier(col.fkTarget.column)})`;
|
|
554
|
+
if (col.fkTarget.onDelete && col.fkTarget.onDelete !== "NO ACTION") {
|
|
555
|
+
fkStr += ` ON DELETE ${col.fkTarget.onDelete}`;
|
|
556
|
+
}
|
|
557
|
+
if (col.fkTarget.onUpdate && col.fkTarget.onUpdate !== "NO ACTION") {
|
|
558
|
+
fkStr += ` ON UPDATE ${col.fkTarget.onUpdate}`;
|
|
559
|
+
}
|
|
560
|
+
return fkStr;
|
|
561
|
+
});
|
|
562
|
+
if (fks.length > 0) {
|
|
563
|
+
lines.push(...fks);
|
|
564
|
+
}
|
|
565
|
+
return `CREATE TABLE ${this.quoteIdentifier(tableName)} (
|
|
566
|
+
${lines.join(",\n")}
|
|
567
|
+
);`;
|
|
568
|
+
}
|
|
569
|
+
};
|
|
570
|
+
MysqlDialect = class {
|
|
571
|
+
quoteIdentifier(name) {
|
|
572
|
+
return `\`${name}\``;
|
|
573
|
+
return `\`${name.replace(/`/g, "``")}\``;
|
|
574
|
+
}
|
|
575
|
+
escapeString(val) {
|
|
576
|
+
return val.replace(/'/g, "''");
|
|
577
|
+
return val.replace(/\\/g, "\\\\").replace(/'/g, "''");
|
|
578
|
+
}
|
|
579
|
+
buildCreateTable(tableName, columns) {
|
|
580
|
+
const lines = columns.map((col) => {
|
|
581
|
+
const isPk = !!(col.isPk || col.primaryKey);
|
|
582
|
+
const tLower = col.type.toLowerCase();
|
|
583
|
+
let typeStr = col.type;
|
|
584
|
+
if (tLower === "integer" || tLower === "int") typeStr = "INT";
|
|
585
|
+
else if (tLower === "text" || tLower === "string")
|
|
586
|
+
typeStr = "VARCHAR(255)";
|
|
587
|
+
else if (tLower === "boolean" || tLower === "bool")
|
|
588
|
+
typeStr = "BOOLEAN";
|
|
589
|
+
else if (tLower === "decimal" || tLower === "numeric")
|
|
590
|
+
typeStr = "DOUBLE";
|
|
591
|
+
else if (tLower === "datetime" || tLower === "timestamp")
|
|
592
|
+
typeStr = "DATETIME";
|
|
593
|
+
else if (col.enumValues && col.enumValues.length > 0) {
|
|
594
|
+
typeStr = `ENUM(${col.enumValues.map((v) => `'${this.escapeString(v)}'`).join(", ")})`;
|
|
595
|
+
}
|
|
596
|
+
if (isPk && (tLower.includes("int") || typeStr === "INT")) {
|
|
597
|
+
typeStr += " AUTO_INCREMENT PRIMARY KEY";
|
|
598
|
+
} else if (isPk) {
|
|
599
|
+
typeStr += " PRIMARY KEY";
|
|
600
|
+
}
|
|
601
|
+
if (!col.nullable && !isPk) typeStr += " NOT NULL";
|
|
602
|
+
if (col.isUnique && !isPk) typeStr += " UNIQUE";
|
|
603
|
+
if (col.defaultValue && col.defaultValue !== "AutoInc" && !col.defaultValue.startsWith("FK ->")) {
|
|
604
|
+
const formatted = formatSqlDefaultValue(col.defaultValue);
|
|
605
|
+
if (formatted !== null) {
|
|
606
|
+
typeStr += ` DEFAULT ${formatted}`;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
return ` ${this.quoteIdentifier(col.name)} ${typeStr}`;
|
|
610
|
+
});
|
|
611
|
+
const fks = columns.filter(
|
|
612
|
+
(col) => col.fkTarget && col.fkTarget.table && col.fkTarget.column
|
|
613
|
+
).map((col) => {
|
|
614
|
+
let fkStr = ` FOREIGN KEY (${this.quoteIdentifier(col.name)}) REFERENCES ${this.quoteIdentifier(col.fkTarget.table)}(${this.quoteIdentifier(col.fkTarget.column)})`;
|
|
615
|
+
if (col.fkTarget.onDelete && col.fkTarget.onDelete !== "NO ACTION") {
|
|
616
|
+
fkStr += ` ON DELETE ${col.fkTarget.onDelete}`;
|
|
617
|
+
}
|
|
618
|
+
if (col.fkTarget.onUpdate && col.fkTarget.onUpdate !== "NO ACTION") {
|
|
619
|
+
fkStr += ` ON UPDATE ${col.fkTarget.onUpdate}`;
|
|
620
|
+
}
|
|
621
|
+
return fkStr;
|
|
622
|
+
});
|
|
623
|
+
if (fks.length > 0) {
|
|
624
|
+
lines.push(...fks);
|
|
625
|
+
}
|
|
626
|
+
return `CREATE TABLE ${this.quoteIdentifier(tableName)} (
|
|
627
|
+
${lines.join(",\n")}
|
|
628
|
+
);`;
|
|
629
|
+
}
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
});
|
|
633
|
+
|
|
310
634
|
// src/logic/adapters/sqlite.ts
|
|
311
635
|
var SqliteAdapter;
|
|
312
636
|
var init_sqlite = __esm({
|
|
313
637
|
"src/logic/adapters/sqlite.ts"() {
|
|
314
638
|
"use strict";
|
|
639
|
+
init_dialect();
|
|
315
640
|
SqliteAdapter = class {
|
|
316
641
|
dbPath;
|
|
317
642
|
db = null;
|
|
@@ -589,15 +914,21 @@ var init_sqlite = __esm({
|
|
|
589
914
|
const isCompositePk = pkColumns.length > 1;
|
|
590
915
|
const colDefs = [];
|
|
591
916
|
for (const col of newColumns) {
|
|
592
|
-
let
|
|
917
|
+
let colType = col.type || "TEXT";
|
|
918
|
+
if (col.enumValues && col.enumValues.length > 0) {
|
|
919
|
+
const vals = col.enumValues.map((v) => `'${v.replace(/'/g, "''")}'`).join(", ");
|
|
920
|
+
colType = `TEXT CHECK(${this.quoteIdentifier(col.name)} IN (${vals}))`;
|
|
921
|
+
}
|
|
922
|
+
let def = `${this.quoteIdentifier(col.name)} ${colType}`;
|
|
593
923
|
if (col.isPk && !isCompositePk) {
|
|
594
924
|
def += " PRIMARY KEY";
|
|
595
925
|
}
|
|
596
926
|
if (!col.nullable && (!col.isPk || isCompositePk)) {
|
|
597
927
|
def += " NOT NULL";
|
|
598
928
|
}
|
|
599
|
-
|
|
600
|
-
|
|
929
|
+
const formattedDef = formatSqlDefaultValue(col.defaultValue);
|
|
930
|
+
if (formattedDef !== null) {
|
|
931
|
+
def += ` DEFAULT ${formattedDef}`;
|
|
601
932
|
}
|
|
602
933
|
if (col.fkTarget && col.fkTarget.table && col.fkTarget.column) {
|
|
603
934
|
def += ` REFERENCES ${this.quoteIdentifier(col.fkTarget.table)}(${this.quoteIdentifier(col.fkTarget.column)})`;
|
|
@@ -704,17 +1035,45 @@ var PostgresAdapter;
|
|
|
704
1035
|
var init_postgres = __esm({
|
|
705
1036
|
"src/logic/adapters/postgres.ts"() {
|
|
706
1037
|
"use strict";
|
|
707
|
-
PostgresAdapter = class {
|
|
1038
|
+
PostgresAdapter = class _PostgresAdapter {
|
|
708
1039
|
connectionString;
|
|
709
1040
|
pool = null;
|
|
1041
|
+
currentSchema;
|
|
710
1042
|
constructor(connection) {
|
|
711
1043
|
this.connectionString = connection;
|
|
1044
|
+
this.currentSchema = _PostgresAdapter.extractSchema(connection);
|
|
1045
|
+
}
|
|
1046
|
+
/**
|
|
1047
|
+
* Extract the schema parameter from a PostgreSQL connection URL.
|
|
1048
|
+
* Supports both `?schema=xxx` (Prisma-style) and `?options=-c search_path%3Dxxx`.
|
|
1049
|
+
* Defaults to 'public' if not found.
|
|
1050
|
+
*/
|
|
1051
|
+
static extractSchema(url) {
|
|
1052
|
+
try {
|
|
1053
|
+
const qIdx = url.indexOf("?");
|
|
1054
|
+
if (qIdx === -1) return "public";
|
|
1055
|
+
const params = new URLSearchParams(url.slice(qIdx + 1));
|
|
1056
|
+
const schema = params.get("schema");
|
|
1057
|
+
if (schema) return schema;
|
|
1058
|
+
const opts = params.get("options");
|
|
1059
|
+
if (opts) {
|
|
1060
|
+
const m = opts.match(/search_path[=](\w+)/);
|
|
1061
|
+
if (m) return m[1];
|
|
1062
|
+
}
|
|
1063
|
+
} catch {
|
|
1064
|
+
}
|
|
1065
|
+
return "public";
|
|
712
1066
|
}
|
|
713
1067
|
getPool() {
|
|
714
1068
|
if (!this.pool) {
|
|
715
1069
|
this.pool = new pg.Pool(buildPgPoolConfig(this.connectionString));
|
|
716
1070
|
this.pool.on("error", () => {
|
|
717
1071
|
});
|
|
1072
|
+
this.pool.on("connect", (client) => {
|
|
1073
|
+
const quoted = this.currentSchema.replace(/"/g, '""');
|
|
1074
|
+
client.query(`SET search_path TO "${quoted}", public`).catch(() => {
|
|
1075
|
+
});
|
|
1076
|
+
});
|
|
718
1077
|
}
|
|
719
1078
|
return this.pool;
|
|
720
1079
|
}
|
|
@@ -775,15 +1134,15 @@ var init_postgres = __esm({
|
|
|
775
1134
|
const query = `
|
|
776
1135
|
SELECT tablename
|
|
777
1136
|
FROM pg_catalog.pg_tables
|
|
778
|
-
WHERE schemaname =
|
|
1137
|
+
WHERE schemaname = $1
|
|
779
1138
|
ORDER BY tablename;
|
|
780
1139
|
`;
|
|
781
|
-
const res = await this.getPool().query(query);
|
|
1140
|
+
const res = await this.getPool().query(query, [this.currentSchema]);
|
|
782
1141
|
return res.rows.map((row) => row.tablename);
|
|
783
1142
|
}
|
|
784
1143
|
async getSchema(tableName) {
|
|
785
1144
|
const query = `
|
|
786
|
-
SELECT c.column_name, c.data_type, c.is_nullable, c.column_default,
|
|
1145
|
+
SELECT c.column_name, c.data_type, c.udt_name, c.is_nullable, c.column_default,
|
|
787
1146
|
(SELECT count(*)
|
|
788
1147
|
FROM information_schema.key_column_usage kcu
|
|
789
1148
|
JOIN information_schema.table_constraints tc
|
|
@@ -841,11 +1200,44 @@ var init_postgres = __esm({
|
|
|
841
1200
|
AND kcu.column_name = c.column_name
|
|
842
1201
|
LIMIT 1) as fk_on_update
|
|
843
1202
|
FROM information_schema.columns c
|
|
844
|
-
WHERE c.table_name = $1 AND c.table_schema =
|
|
1203
|
+
WHERE c.table_name = $1 AND c.table_schema = $2
|
|
845
1204
|
ORDER BY c.ordinal_position;
|
|
846
1205
|
`;
|
|
847
|
-
const res = await this.getPool().query(query, [
|
|
1206
|
+
const res = await this.getPool().query(query, [
|
|
1207
|
+
tableName,
|
|
1208
|
+
this.currentSchema
|
|
1209
|
+
]);
|
|
848
1210
|
const enumMap = /* @__PURE__ */ new Map();
|
|
1211
|
+
try {
|
|
1212
|
+
const nativeEnumQuery = `
|
|
1213
|
+
SELECT
|
|
1214
|
+
a.attname AS column_name,
|
|
1215
|
+
t.typname AS enum_name,
|
|
1216
|
+
e.enumlabel AS enum_value
|
|
1217
|
+
FROM pg_attribute a
|
|
1218
|
+
JOIN pg_class c ON a.attrelid = c.oid
|
|
1219
|
+
JOIN pg_namespace nc ON nc.oid = c.relnamespace
|
|
1220
|
+
JOIN pg_type t ON a.atttypid = t.oid
|
|
1221
|
+
JOIN pg_enum e ON t.oid = e.enumtypid
|
|
1222
|
+
WHERE c.relname = $1
|
|
1223
|
+
AND nc.nspname = $2
|
|
1224
|
+
AND a.attnum > 0
|
|
1225
|
+
AND NOT a.attisdropped
|
|
1226
|
+
ORDER BY a.attnum, e.enumsortorder;
|
|
1227
|
+
`;
|
|
1228
|
+
const nativeEnumRes = await this.getPool().query(nativeEnumQuery, [
|
|
1229
|
+
tableName,
|
|
1230
|
+
this.currentSchema
|
|
1231
|
+
]);
|
|
1232
|
+
for (const r of nativeEnumRes.rows) {
|
|
1233
|
+
const colName = r.column_name;
|
|
1234
|
+
if (!enumMap.has(colName)) {
|
|
1235
|
+
enumMap.set(colName, []);
|
|
1236
|
+
}
|
|
1237
|
+
enumMap.get(colName).push(r.enum_value);
|
|
1238
|
+
}
|
|
1239
|
+
} catch {
|
|
1240
|
+
}
|
|
849
1241
|
try {
|
|
850
1242
|
const checkQuery = `
|
|
851
1243
|
SELECT cc.check_clause
|
|
@@ -853,16 +1245,19 @@ var init_postgres = __esm({
|
|
|
853
1245
|
JOIN information_schema.table_constraints tc
|
|
854
1246
|
ON cc.constraint_name = tc.constraint_name
|
|
855
1247
|
AND cc.constraint_schema = tc.constraint_schema
|
|
856
|
-
WHERE tc.table_name = $1 AND tc.table_schema =
|
|
1248
|
+
WHERE tc.table_name = $1 AND tc.table_schema = $2;
|
|
857
1249
|
`;
|
|
858
|
-
const checkRes = await this.getPool().query(checkQuery, [
|
|
1250
|
+
const checkRes = await this.getPool().query(checkQuery, [
|
|
1251
|
+
tableName,
|
|
1252
|
+
this.currentSchema
|
|
1253
|
+
]);
|
|
859
1254
|
for (const r of checkRes.rows) {
|
|
860
1255
|
const inRegex = /["'`]?(\w+)["'`]?\s+(?:COLLATE\s+\w+\s+)?IN\s*\(([^)]+)\)/gi;
|
|
861
1256
|
let inMatch;
|
|
862
1257
|
while ((inMatch = inRegex.exec(r.check_clause)) !== null) {
|
|
863
1258
|
const colName = inMatch[1];
|
|
864
1259
|
const values = inMatch[2].split(",").map((s) => s.trim().replace(/^['"`]|['"`]$/g, "")).filter((s) => s.length > 0);
|
|
865
|
-
if (values.length > 0) {
|
|
1260
|
+
if (values.length > 0 && !enumMap.has(colName)) {
|
|
866
1261
|
enumMap.set(colName, values);
|
|
867
1262
|
}
|
|
868
1263
|
}
|
|
@@ -880,13 +1275,22 @@ var init_postgres = __esm({
|
|
|
880
1275
|
onUpdate: col.fk_on_update && col.fk_on_update.toUpperCase() !== "NO ACTION" ? col.fk_on_update.toUpperCase() : void 0
|
|
881
1276
|
};
|
|
882
1277
|
}
|
|
1278
|
+
let colType = col.data_type;
|
|
1279
|
+
if (col.data_type === "USER-DEFINED" && col.udt_name) {
|
|
1280
|
+
colType = col.udt_name;
|
|
1281
|
+
}
|
|
883
1282
|
return {
|
|
884
1283
|
name: col.column_name,
|
|
885
|
-
type:
|
|
1284
|
+
type: colType,
|
|
886
1285
|
isPk: parseInt(col.is_pk) > 0,
|
|
887
1286
|
nullable: parseInt(col.is_pk) > 0 ? false : col.is_nullable === "YES",
|
|
888
1287
|
isUnique: parseInt(col.is_pk) > 0 || parseInt(col.is_unique) > 0,
|
|
889
|
-
defaultValue:
|
|
1288
|
+
defaultValue: (() => {
|
|
1289
|
+
if (col.column_default == null) return void 0;
|
|
1290
|
+
const raw = String(col.column_default).trim();
|
|
1291
|
+
const castMatch = raw.match(/^('[\s\S]*')(?:::[\w\s()]+)$/);
|
|
1292
|
+
return castMatch ? castMatch[1] : raw;
|
|
1293
|
+
})(),
|
|
890
1294
|
enumValues: enumMap.get(col.column_name),
|
|
891
1295
|
fkTarget
|
|
892
1296
|
};
|
|
@@ -900,22 +1304,22 @@ var init_postgres = __esm({
|
|
|
900
1304
|
ix.indisunique as is_unique,
|
|
901
1305
|
ix.indisprimary as is_primary
|
|
902
1306
|
FROM
|
|
903
|
-
pg_class t
|
|
904
|
-
|
|
905
|
-
pg_index ix
|
|
906
|
-
|
|
1307
|
+
pg_class t
|
|
1308
|
+
JOIN pg_namespace n ON n.oid = t.relnamespace
|
|
1309
|
+
JOIN pg_index ix ON t.oid = ix.indrelid
|
|
1310
|
+
JOIN pg_class i ON i.oid = ix.indexrelid
|
|
1311
|
+
JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = ANY(ix.indkey)
|
|
907
1312
|
WHERE
|
|
908
|
-
t.
|
|
909
|
-
AND i.oid = ix.indexrelid
|
|
910
|
-
AND a.attrelid = t.oid
|
|
911
|
-
AND a.attnum = ANY(ix.indkey)
|
|
912
|
-
AND t.relkind = 'r'
|
|
1313
|
+
t.relkind = 'r'
|
|
913
1314
|
AND t.relname = $1
|
|
914
|
-
AND
|
|
1315
|
+
AND n.nspname = $2
|
|
915
1316
|
ORDER BY
|
|
916
1317
|
i.relname, array_position(ix.indkey, a.attnum);
|
|
917
1318
|
`;
|
|
918
|
-
const res = await this.getPool().query(query, [
|
|
1319
|
+
const res = await this.getPool().query(query, [
|
|
1320
|
+
tableName,
|
|
1321
|
+
this.currentSchema
|
|
1322
|
+
]);
|
|
919
1323
|
const rows = res.rows;
|
|
920
1324
|
const indexMap = /* @__PURE__ */ new Map();
|
|
921
1325
|
for (const row of rows) {
|
|
@@ -935,7 +1339,7 @@ var init_postgres = __esm({
|
|
|
935
1339
|
async getData(tableName, limit = 50, offset = 0, whereClause, orderBy) {
|
|
936
1340
|
const schema = await this.getSchema(tableName);
|
|
937
1341
|
const columns = schema.map((col) => col.name);
|
|
938
|
-
let sql = `SELECT * FROM ${this.
|
|
1342
|
+
let sql = `SELECT * FROM ${this.quoteTable(tableName)}`;
|
|
939
1343
|
if (whereClause) {
|
|
940
1344
|
sql += ` WHERE ${whereClause}`;
|
|
941
1345
|
}
|
|
@@ -977,7 +1381,7 @@ var init_postgres = __esm({
|
|
|
977
1381
|
await client.query("BEGIN");
|
|
978
1382
|
for (const row of rows) {
|
|
979
1383
|
const placeholders = cols.map((_, i) => `$${i + 1}`).join(", ");
|
|
980
|
-
const sql = `INSERT INTO ${this.
|
|
1384
|
+
const sql = `INSERT INTO ${this.quoteTable(tableName)} (${colsQuoted}) VALUES (${placeholders})`;
|
|
981
1385
|
const values = cols.map((c) => row[c]);
|
|
982
1386
|
await client.query(sql, values);
|
|
983
1387
|
}
|
|
@@ -990,11 +1394,82 @@ var init_postgres = __esm({
|
|
|
990
1394
|
}
|
|
991
1395
|
}
|
|
992
1396
|
async truncateTable(tableName) {
|
|
993
|
-
const quoted = this.
|
|
1397
|
+
const quoted = this.quoteTable(tableName);
|
|
994
1398
|
await this.getPool().query(
|
|
995
1399
|
`TRUNCATE TABLE ${quoted} RESTART IDENTITY CASCADE;`
|
|
996
1400
|
);
|
|
997
1401
|
}
|
|
1402
|
+
/**
|
|
1403
|
+
* Quote a table name with schema prefix, e.g. "zen_stream"."users".
|
|
1404
|
+
* When the schema is 'public', only the table name is quoted for simplicity.
|
|
1405
|
+
*/
|
|
1406
|
+
quoteTable(tableName) {
|
|
1407
|
+
if (this.currentSchema === "public") {
|
|
1408
|
+
return this.quoteIdentifier(tableName);
|
|
1409
|
+
}
|
|
1410
|
+
return `${this.quoteIdentifier(this.currentSchema)}.${this.quoteIdentifier(tableName)}`;
|
|
1411
|
+
}
|
|
1412
|
+
/**
|
|
1413
|
+
* List all user-accessible schemas (excluding internal PostgreSQL schemas).
|
|
1414
|
+
*/
|
|
1415
|
+
async getSchemas() {
|
|
1416
|
+
const query = `
|
|
1417
|
+
SELECT schema_name
|
|
1418
|
+
FROM information_schema.schemata
|
|
1419
|
+
WHERE schema_name NOT IN ('pg_catalog', 'information_schema', 'pg_toast')
|
|
1420
|
+
AND schema_name NOT LIKE 'pg_temp_%'
|
|
1421
|
+
AND schema_name NOT LIKE 'pg_toast_temp_%'
|
|
1422
|
+
ORDER BY schema_name;
|
|
1423
|
+
`;
|
|
1424
|
+
const res = await this.getPool().query(query);
|
|
1425
|
+
return res.rows.map((row) => row.schema_name);
|
|
1426
|
+
}
|
|
1427
|
+
getCurrentSchema() {
|
|
1428
|
+
return this.currentSchema;
|
|
1429
|
+
}
|
|
1430
|
+
async setSchema(schema) {
|
|
1431
|
+
this.currentSchema = schema;
|
|
1432
|
+
if (this.pool) {
|
|
1433
|
+
await this.pool.end();
|
|
1434
|
+
this.pool = null;
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
/**
|
|
1438
|
+
* List all user-defined enum types in the current schema or public.
|
|
1439
|
+
* Prioritizes enums in the current active schema over public.
|
|
1440
|
+
*/
|
|
1441
|
+
async getCustomEnums() {
|
|
1442
|
+
const query = `
|
|
1443
|
+
SELECT
|
|
1444
|
+
t.typname AS name,
|
|
1445
|
+
n.nspname AS schema_name,
|
|
1446
|
+
array_agg(e.enumlabel ORDER BY e.enumsortorder) AS values
|
|
1447
|
+
FROM pg_type t
|
|
1448
|
+
JOIN pg_enum e ON t.oid = e.enumtypid
|
|
1449
|
+
JOIN pg_namespace n ON n.oid = t.typnamespace
|
|
1450
|
+
WHERE n.nspname = $1 OR n.nspname = 'public'
|
|
1451
|
+
GROUP BY t.oid, t.typname, n.nspname
|
|
1452
|
+
ORDER BY (CASE WHEN n.nspname = $1 THEN 0 ELSE 1 END), t.typname;
|
|
1453
|
+
`;
|
|
1454
|
+
try {
|
|
1455
|
+
const res = await this.getPool().query(query, [this.currentSchema]);
|
|
1456
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1457
|
+
const result = [];
|
|
1458
|
+
for (const row of res.rows) {
|
|
1459
|
+
const lower = row.name.toLowerCase();
|
|
1460
|
+
if (!seen.has(lower)) {
|
|
1461
|
+
seen.add(lower);
|
|
1462
|
+
result.push({
|
|
1463
|
+
name: row.name,
|
|
1464
|
+
values: Array.isArray(row.values) ? row.values : []
|
|
1465
|
+
});
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
return result;
|
|
1469
|
+
} catch {
|
|
1470
|
+
return [];
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
998
1473
|
};
|
|
999
1474
|
}
|
|
1000
1475
|
});
|
|
@@ -1265,245 +1740,20 @@ var init_adapters = __esm({
|
|
|
1265
1740
|
// src/logic/factory.ts
|
|
1266
1741
|
function createDBAdapter(config) {
|
|
1267
1742
|
switch (config.type) {
|
|
1268
|
-
case "sqlite":
|
|
1269
|
-
return new SqliteAdapter(config.targetUrl);
|
|
1270
|
-
case "postgres":
|
|
1271
|
-
return new PostgresAdapter(config.targetUrl);
|
|
1272
|
-
case "mysql":
|
|
1273
|
-
return new MysqlAdapter(config.targetUrl);
|
|
1274
|
-
default:
|
|
1275
|
-
throw new Error(`Unsupported database type: ${config.type}`);
|
|
1276
|
-
}
|
|
1277
|
-
}
|
|
1278
|
-
var init_factory = __esm({
|
|
1279
|
-
"src/logic/factory.ts"() {
|
|
1280
|
-
"use strict";
|
|
1281
|
-
init_adapters();
|
|
1282
|
-
}
|
|
1283
|
-
});
|
|
1284
|
-
|
|
1285
|
-
// src/logic/dialect.ts
|
|
1286
|
-
function getDialect(type) {
|
|
1287
|
-
switch (type) {
|
|
1288
|
-
case "sqlite":
|
|
1289
|
-
return new SqliteDialect();
|
|
1290
|
-
case "postgres":
|
|
1291
|
-
return new PostgresDialect();
|
|
1292
|
-
case "mysql":
|
|
1293
|
-
return new MysqlDialect();
|
|
1294
|
-
default:
|
|
1295
|
-
return new SqliteDialect();
|
|
1296
|
-
}
|
|
1297
|
-
}
|
|
1298
|
-
function buildSearchWhereClause(adapter, dbType, columns, searchInput) {
|
|
1299
|
-
const searchVal = (searchInput || "").trim();
|
|
1300
|
-
if (!searchVal) return "";
|
|
1301
|
-
const isSqlCondition = /[=<>]|LIKE|IN|AND|OR/i.test(searchVal);
|
|
1302
|
-
if (isSqlCondition) {
|
|
1303
|
-
return searchVal;
|
|
1304
|
-
}
|
|
1305
|
-
const strCols = columns.filter((c) => {
|
|
1306
|
-
const t = c.type.toLowerCase();
|
|
1307
|
-
return t.includes("char") || t.includes("text") || t.includes("string") || t.includes("uuid");
|
|
1308
|
-
});
|
|
1309
|
-
if (strCols.length > 0) {
|
|
1310
|
-
const likeOp = dbType === "postgres" ? "ILIKE" : "LIKE";
|
|
1311
|
-
const escaped = searchVal.replace(/'/g, "''");
|
|
1312
|
-
const conditions = strCols.map(
|
|
1313
|
-
(c) => `${adapter.quoteIdentifier(c.name)} ${likeOp} '%${escaped}%'`
|
|
1314
|
-
);
|
|
1315
|
-
return conditions.join(" OR ");
|
|
1316
|
-
}
|
|
1317
|
-
if (columns.length > 0) {
|
|
1318
|
-
const col = columns[0];
|
|
1319
|
-
const escaped = searchVal.replace(/'/g, "''");
|
|
1320
|
-
return `${adapter.quoteIdentifier(col.name)} = '${escaped}'`;
|
|
1321
|
-
}
|
|
1322
|
-
return "";
|
|
1323
|
-
}
|
|
1324
|
-
var SqliteDialect, PostgresDialect, MysqlDialect;
|
|
1325
|
-
var init_dialect = __esm({
|
|
1326
|
-
"src/logic/dialect.ts"() {
|
|
1327
|
-
"use strict";
|
|
1328
|
-
SqliteDialect = class {
|
|
1329
|
-
quoteIdentifier(name) {
|
|
1330
|
-
return `"${name}"`;
|
|
1331
|
-
}
|
|
1332
|
-
escapeString(val) {
|
|
1333
|
-
return val.replace(/'/g, "''");
|
|
1334
|
-
}
|
|
1335
|
-
buildCreateTable(tableName, columns) {
|
|
1336
|
-
const lines = columns.map((col) => {
|
|
1337
|
-
const isPk = !!(col.isPk || col.primaryKey);
|
|
1338
|
-
const tLower = col.type.toLowerCase();
|
|
1339
|
-
let typeStr = col.type;
|
|
1340
|
-
if (tLower === "integer" || tLower === "int") typeStr = "INTEGER";
|
|
1341
|
-
else if (tLower === "text" || tLower === "string") typeStr = "TEXT";
|
|
1342
|
-
else if (tLower === "boolean" || tLower === "bool")
|
|
1343
|
-
typeStr = "BOOLEAN";
|
|
1344
|
-
else if (tLower === "decimal" || tLower === "numeric" || tLower === "float" || tLower === "double")
|
|
1345
|
-
typeStr = "REAL";
|
|
1346
|
-
else if (tLower === "datetime" || tLower === "timestamp")
|
|
1347
|
-
typeStr = "DATETIME";
|
|
1348
|
-
else if (col.enumValues && col.enumValues.length > 0) {
|
|
1349
|
-
typeStr = `TEXT CHECK(${this.quoteIdentifier(col.name)} IN (${col.enumValues.map((v) => `'${this.escapeString(v)}'`).join(", ")}))`;
|
|
1350
|
-
}
|
|
1351
|
-
if (isPk && (tLower.includes("int") || typeStr === "INTEGER")) {
|
|
1352
|
-
typeStr = "INTEGER PRIMARY KEY AUTOINCREMENT";
|
|
1353
|
-
} else if (isPk) {
|
|
1354
|
-
typeStr += " PRIMARY KEY";
|
|
1355
|
-
} else {
|
|
1356
|
-
if (!col.nullable) typeStr += " NOT NULL";
|
|
1357
|
-
if (col.isUnique) typeStr += " UNIQUE";
|
|
1358
|
-
}
|
|
1359
|
-
if (col.defaultValue && col.defaultValue !== "AutoInc" && !col.defaultValue.startsWith("FK ->")) {
|
|
1360
|
-
if (col.defaultValue === "Timestamp") {
|
|
1361
|
-
typeStr += " DEFAULT CURRENT_TIMESTAMP";
|
|
1362
|
-
} else {
|
|
1363
|
-
typeStr += ` DEFAULT ${col.defaultValue}`;
|
|
1364
|
-
}
|
|
1365
|
-
}
|
|
1366
|
-
return ` ${this.quoteIdentifier(col.name)} ${typeStr}`;
|
|
1367
|
-
});
|
|
1368
|
-
const fks = columns.filter(
|
|
1369
|
-
(col) => col.fkTarget && col.fkTarget.table && col.fkTarget.column
|
|
1370
|
-
).map((col) => {
|
|
1371
|
-
let fkStr = ` FOREIGN KEY (${this.quoteIdentifier(col.name)}) REFERENCES ${this.quoteIdentifier(col.fkTarget.table)}(${this.quoteIdentifier(col.fkTarget.column)})`;
|
|
1372
|
-
if (col.fkTarget.onDelete && col.fkTarget.onDelete !== "NO ACTION") {
|
|
1373
|
-
fkStr += ` ON DELETE ${col.fkTarget.onDelete}`;
|
|
1374
|
-
}
|
|
1375
|
-
if (col.fkTarget.onUpdate && col.fkTarget.onUpdate !== "NO ACTION") {
|
|
1376
|
-
fkStr += ` ON UPDATE ${col.fkTarget.onUpdate}`;
|
|
1377
|
-
}
|
|
1378
|
-
return fkStr;
|
|
1379
|
-
});
|
|
1380
|
-
if (fks.length > 0) {
|
|
1381
|
-
lines.push(...fks);
|
|
1382
|
-
}
|
|
1383
|
-
return `CREATE TABLE ${this.quoteIdentifier(tableName)} (
|
|
1384
|
-
${lines.join(",\n")}
|
|
1385
|
-
);`;
|
|
1386
|
-
}
|
|
1387
|
-
};
|
|
1388
|
-
PostgresDialect = class {
|
|
1389
|
-
quoteIdentifier(name) {
|
|
1390
|
-
return `"${name}"`;
|
|
1391
|
-
}
|
|
1392
|
-
escapeString(val) {
|
|
1393
|
-
return val.replace(/'/g, "''");
|
|
1394
|
-
}
|
|
1395
|
-
buildCreateTable(tableName, columns) {
|
|
1396
|
-
const lines = columns.map((col) => {
|
|
1397
|
-
const isPk = !!(col.isPk || col.primaryKey);
|
|
1398
|
-
const tLower = col.type.toLowerCase();
|
|
1399
|
-
let typeStr = col.type;
|
|
1400
|
-
if (isPk && (tLower === "integer" || tLower === "int")) {
|
|
1401
|
-
typeStr = "SERIAL PRIMARY KEY";
|
|
1402
|
-
} else {
|
|
1403
|
-
if (tLower === "integer" || tLower === "int") typeStr = "INTEGER";
|
|
1404
|
-
else if (tLower === "text" || tLower === "string") typeStr = "TEXT";
|
|
1405
|
-
else if (tLower === "boolean" || tLower === "bool")
|
|
1406
|
-
typeStr = "BOOLEAN";
|
|
1407
|
-
else if (tLower === "decimal" || tLower === "numeric")
|
|
1408
|
-
typeStr = "NUMERIC";
|
|
1409
|
-
else if (tLower === "datetime" || tLower === "timestamp")
|
|
1410
|
-
typeStr = "TIMESTAMP";
|
|
1411
|
-
else if (col.enumValues && col.enumValues.length > 0) {
|
|
1412
|
-
typeStr = `VARCHAR(255) CHECK(${this.quoteIdentifier(col.name)} IN (${col.enumValues.map((v) => `'${this.escapeString(v)}'`).join(", ")}))`;
|
|
1413
|
-
}
|
|
1414
|
-
if (isPk) typeStr += " PRIMARY KEY";
|
|
1415
|
-
if (!col.nullable && !isPk) typeStr += " NOT NULL";
|
|
1416
|
-
if (col.isUnique && !isPk) typeStr += " UNIQUE";
|
|
1417
|
-
}
|
|
1418
|
-
if (col.defaultValue && col.defaultValue !== "AutoInc" && !col.defaultValue.startsWith("FK ->")) {
|
|
1419
|
-
if (col.defaultValue === "Timestamp") {
|
|
1420
|
-
typeStr += " DEFAULT CURRENT_TIMESTAMP";
|
|
1421
|
-
} else {
|
|
1422
|
-
typeStr += ` DEFAULT ${col.defaultValue}`;
|
|
1423
|
-
}
|
|
1424
|
-
}
|
|
1425
|
-
return ` ${this.quoteIdentifier(col.name)} ${typeStr}`;
|
|
1426
|
-
});
|
|
1427
|
-
const fks = columns.filter(
|
|
1428
|
-
(col) => col.fkTarget && col.fkTarget.table && col.fkTarget.column
|
|
1429
|
-
).map((col) => {
|
|
1430
|
-
let fkStr = ` FOREIGN KEY (${this.quoteIdentifier(col.name)}) REFERENCES ${this.quoteIdentifier(col.fkTarget.table)}(${this.quoteIdentifier(col.fkTarget.column)})`;
|
|
1431
|
-
if (col.fkTarget.onDelete && col.fkTarget.onDelete !== "NO ACTION") {
|
|
1432
|
-
fkStr += ` ON DELETE ${col.fkTarget.onDelete}`;
|
|
1433
|
-
}
|
|
1434
|
-
if (col.fkTarget.onUpdate && col.fkTarget.onUpdate !== "NO ACTION") {
|
|
1435
|
-
fkStr += ` ON UPDATE ${col.fkTarget.onUpdate}`;
|
|
1436
|
-
}
|
|
1437
|
-
return fkStr;
|
|
1438
|
-
});
|
|
1439
|
-
if (fks.length > 0) {
|
|
1440
|
-
lines.push(...fks);
|
|
1441
|
-
}
|
|
1442
|
-
return `CREATE TABLE ${this.quoteIdentifier(tableName)} (
|
|
1443
|
-
${lines.join(",\n")}
|
|
1444
|
-
);`;
|
|
1445
|
-
}
|
|
1446
|
-
};
|
|
1447
|
-
MysqlDialect = class {
|
|
1448
|
-
quoteIdentifier(name) {
|
|
1449
|
-
return `\`${name}\``;
|
|
1450
|
-
}
|
|
1451
|
-
escapeString(val) {
|
|
1452
|
-
return val.replace(/'/g, "''");
|
|
1453
|
-
}
|
|
1454
|
-
buildCreateTable(tableName, columns) {
|
|
1455
|
-
const lines = columns.map((col) => {
|
|
1456
|
-
const isPk = !!(col.isPk || col.primaryKey);
|
|
1457
|
-
const tLower = col.type.toLowerCase();
|
|
1458
|
-
let typeStr = col.type;
|
|
1459
|
-
if (tLower === "integer" || tLower === "int") typeStr = "INT";
|
|
1460
|
-
else if (tLower === "text" || tLower === "string")
|
|
1461
|
-
typeStr = "VARCHAR(255)";
|
|
1462
|
-
else if (tLower === "boolean" || tLower === "bool")
|
|
1463
|
-
typeStr = "BOOLEAN";
|
|
1464
|
-
else if (tLower === "decimal" || tLower === "numeric")
|
|
1465
|
-
typeStr = "DOUBLE";
|
|
1466
|
-
else if (tLower === "datetime" || tLower === "timestamp")
|
|
1467
|
-
typeStr = "DATETIME";
|
|
1468
|
-
else if (col.enumValues && col.enumValues.length > 0) {
|
|
1469
|
-
typeStr = `VARCHAR(255) CHECK(${this.quoteIdentifier(col.name)} IN (${col.enumValues.map((v) => `'${this.escapeString(v)}'`).join(", ")}))`;
|
|
1470
|
-
}
|
|
1471
|
-
if (isPk && (tLower.includes("int") || typeStr === "INT")) {
|
|
1472
|
-
typeStr += " AUTO_INCREMENT PRIMARY KEY";
|
|
1473
|
-
} else if (isPk) {
|
|
1474
|
-
typeStr += " PRIMARY KEY";
|
|
1475
|
-
}
|
|
1476
|
-
if (!col.nullable && !isPk) typeStr += " NOT NULL";
|
|
1477
|
-
if (col.isUnique && !isPk) typeStr += " UNIQUE";
|
|
1478
|
-
if (col.defaultValue && col.defaultValue !== "AutoInc" && !col.defaultValue.startsWith("FK ->")) {
|
|
1479
|
-
if (col.defaultValue === "Timestamp") {
|
|
1480
|
-
typeStr += " DEFAULT CURRENT_TIMESTAMP";
|
|
1481
|
-
} else {
|
|
1482
|
-
typeStr += ` DEFAULT ${col.defaultValue}`;
|
|
1483
|
-
}
|
|
1484
|
-
}
|
|
1485
|
-
return ` ${this.quoteIdentifier(col.name)} ${typeStr}`;
|
|
1486
|
-
});
|
|
1487
|
-
const fks = columns.filter(
|
|
1488
|
-
(col) => col.fkTarget && col.fkTarget.table && col.fkTarget.column
|
|
1489
|
-
).map((col) => {
|
|
1490
|
-
let fkStr = ` FOREIGN KEY (${this.quoteIdentifier(col.name)}) REFERENCES ${this.quoteIdentifier(col.fkTarget.table)}(${this.quoteIdentifier(col.fkTarget.column)})`;
|
|
1491
|
-
if (col.fkTarget.onDelete && col.fkTarget.onDelete !== "NO ACTION") {
|
|
1492
|
-
fkStr += ` ON DELETE ${col.fkTarget.onDelete}`;
|
|
1493
|
-
}
|
|
1494
|
-
if (col.fkTarget.onUpdate && col.fkTarget.onUpdate !== "NO ACTION") {
|
|
1495
|
-
fkStr += ` ON UPDATE ${col.fkTarget.onUpdate}`;
|
|
1496
|
-
}
|
|
1497
|
-
return fkStr;
|
|
1498
|
-
});
|
|
1499
|
-
if (fks.length > 0) {
|
|
1500
|
-
lines.push(...fks);
|
|
1501
|
-
}
|
|
1502
|
-
return `CREATE TABLE ${this.quoteIdentifier(tableName)} (
|
|
1503
|
-
${lines.join(",\n")}
|
|
1504
|
-
);`;
|
|
1505
|
-
}
|
|
1506
|
-
};
|
|
1743
|
+
case "sqlite":
|
|
1744
|
+
return new SqliteAdapter(config.targetUrl);
|
|
1745
|
+
case "postgres":
|
|
1746
|
+
return new PostgresAdapter(config.targetUrl);
|
|
1747
|
+
case "mysql":
|
|
1748
|
+
return new MysqlAdapter(config.targetUrl);
|
|
1749
|
+
default:
|
|
1750
|
+
throw new Error(`Unsupported database type: ${config.type}`);
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
var init_factory = __esm({
|
|
1754
|
+
"src/logic/factory.ts"() {
|
|
1755
|
+
"use strict";
|
|
1756
|
+
init_adapters();
|
|
1507
1757
|
}
|
|
1508
1758
|
});
|
|
1509
1759
|
|
|
@@ -1611,12 +1861,18 @@ async function batchMutateTableData(adapter, dbType, options) {
|
|
|
1611
1861
|
}
|
|
1612
1862
|
let executedCount = 0;
|
|
1613
1863
|
try {
|
|
1864
|
+
await adapter.executeSql("BEGIN;");
|
|
1614
1865
|
for (const sql of sqls) {
|
|
1615
1866
|
await adapter.executeSql(sql);
|
|
1616
1867
|
executedCount++;
|
|
1617
1868
|
}
|
|
1869
|
+
await adapter.executeSql("COMMIT;");
|
|
1618
1870
|
return ok({ modifiedCount: executedCount, executedCount });
|
|
1619
1871
|
} catch (e) {
|
|
1872
|
+
try {
|
|
1873
|
+
await adapter.executeSql("ROLLBACK;");
|
|
1874
|
+
} catch {
|
|
1875
|
+
}
|
|
1620
1876
|
return err(
|
|
1621
1877
|
e.message || "Failed to execute database mutations",
|
|
1622
1878
|
void 0,
|
|
@@ -1635,6 +1891,33 @@ var init_mutation = __esm({
|
|
|
1635
1891
|
// src/logic/schema.ts
|
|
1636
1892
|
async function createTable(adapter, dbType, tableName, columns) {
|
|
1637
1893
|
try {
|
|
1894
|
+
if (dbType === "postgres") {
|
|
1895
|
+
const processedEnums = /* @__PURE__ */ new Set();
|
|
1896
|
+
for (const col of columns) {
|
|
1897
|
+
if (col.enumValues && col.enumValues.length > 0 && col.type && !["enum", "varchar", "varchar(255)", "text", "string"].includes(
|
|
1898
|
+
col.type.toLowerCase()
|
|
1899
|
+
) && !processedEnums.has(col.type.toLowerCase())) {
|
|
1900
|
+
processedEnums.add(col.type.toLowerCase());
|
|
1901
|
+
const vals = col.enumValues.map((v) => `'${v.replace(/'/g, "''")}'`).join(", ");
|
|
1902
|
+
const enumTypeQuoted = adapter.quoteIdentifier(col.type);
|
|
1903
|
+
const escapedName = col.type.replace(/'/g, "''");
|
|
1904
|
+
const createTypeSql = `
|
|
1905
|
+
DO $$
|
|
1906
|
+
BEGIN
|
|
1907
|
+
IF NOT EXISTS (
|
|
1908
|
+
SELECT 1 FROM pg_type t
|
|
1909
|
+
JOIN pg_namespace n ON n.oid = t.typnamespace
|
|
1910
|
+
WHERE t.typname = '${escapedName}'
|
|
1911
|
+
AND (n.nspname = current_schema() OR n.nspname = 'public')
|
|
1912
|
+
) THEN
|
|
1913
|
+
CREATE TYPE ${enumTypeQuoted} AS ENUM (${vals});
|
|
1914
|
+
END IF;
|
|
1915
|
+
END$$;
|
|
1916
|
+
`;
|
|
1917
|
+
await adapter.executeSql(createTypeSql);
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1638
1921
|
const dialect = getDialect(dbType);
|
|
1639
1922
|
const sql = dialect.buildCreateTable(tableName, columns);
|
|
1640
1923
|
await adapter.executeSql(sql);
|
|
@@ -1725,6 +2008,7 @@ async function applySchemaChanges(adapter, dbType, options) {
|
|
|
1725
2008
|
let nullable = edits.nullable !== void 0 ? !!edits.nullable : col.nullable;
|
|
1726
2009
|
let defaultValue = edits.defaultValue !== void 0 ? edits.defaultValue : col.defaultValue;
|
|
1727
2010
|
let isUnique = edits.isUnique !== void 0 ? !!edits.isUnique : col.isPk && !isPk ? false : !!col.isUnique;
|
|
2011
|
+
let enumValues = edits.enumValues !== void 0 ? edits.enumValues : col.enumValues;
|
|
1728
2012
|
if (edits.name && edits.name !== col.name) {
|
|
1729
2013
|
renames[col.name] = edits.name;
|
|
1730
2014
|
}
|
|
@@ -1735,7 +2019,8 @@ async function applySchemaChanges(adapter, dbType, options) {
|
|
|
1735
2019
|
nullable,
|
|
1736
2020
|
defaultValue,
|
|
1737
2021
|
isUnique,
|
|
1738
|
-
fkTarget
|
|
2022
|
+
fkTarget,
|
|
2023
|
+
enumValues
|
|
1739
2024
|
});
|
|
1740
2025
|
}
|
|
1741
2026
|
for (const ins of pendingInserts) {
|
|
@@ -1770,7 +2055,8 @@ async function applySchemaChanges(adapter, dbType, options) {
|
|
|
1770
2055
|
nullable: ins.nullable !== void 0 ? !!ins.nullable : true,
|
|
1771
2056
|
defaultValue: ins.defaultValue,
|
|
1772
2057
|
isUnique: !!ins.isUnique,
|
|
1773
|
-
fkTarget
|
|
2058
|
+
fkTarget,
|
|
2059
|
+
enumValues: ins.enumValues
|
|
1774
2060
|
});
|
|
1775
2061
|
}
|
|
1776
2062
|
targetColumns = updated;
|
|
@@ -1828,8 +2114,32 @@ async function applySchemaChanges(adapter, dbType, options) {
|
|
|
1828
2114
|
const quotedCol = dialect.quoteIdentifier(currentName);
|
|
1829
2115
|
const type = edits.type || "TEXT";
|
|
1830
2116
|
if (dbType === "postgres") {
|
|
2117
|
+
let pgType = type;
|
|
2118
|
+
if (edits.enumValues && edits.enumValues.length > 0) {
|
|
2119
|
+
if (edits.isNewEnum && edits.type) {
|
|
2120
|
+
const escapedName = edits.type.replace(/'/g, "''");
|
|
2121
|
+
const vals = edits.enumValues.map((v) => `'${dialect.escapeString(v)}'`).join(", ");
|
|
2122
|
+
const quotedType = dialect.quoteIdentifier(edits.type);
|
|
2123
|
+
sqls.push(`
|
|
2124
|
+
DO $$
|
|
2125
|
+
BEGIN
|
|
2126
|
+
IF NOT EXISTS (
|
|
2127
|
+
SELECT 1 FROM pg_type t
|
|
2128
|
+
JOIN pg_namespace n ON n.oid = t.typnamespace
|
|
2129
|
+
WHERE t.typname = '${escapedName}'
|
|
2130
|
+
AND (n.nspname = current_schema() OR n.nspname = 'public')
|
|
2131
|
+
) THEN
|
|
2132
|
+
CREATE TYPE ${quotedType} AS ENUM (${vals});
|
|
2133
|
+
END IF;
|
|
2134
|
+
END$$;
|
|
2135
|
+
`);
|
|
2136
|
+
pgType = quotedType;
|
|
2137
|
+
} else if (edits.type) {
|
|
2138
|
+
pgType = dialect.quoteIdentifier(edits.type);
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
1831
2141
|
sqls.push(
|
|
1832
|
-
`ALTER TABLE ${quotedTable} ALTER COLUMN ${quotedCol} TYPE ${
|
|
2142
|
+
`ALTER TABLE ${quotedTable} ALTER COLUMN ${quotedCol} TYPE ${pgType} USING ${quotedCol}::text::${pgType};`
|
|
1833
2143
|
);
|
|
1834
2144
|
if (edits.nullable === false) {
|
|
1835
2145
|
sqls.push(
|
|
@@ -1840,17 +2150,29 @@ async function applySchemaChanges(adapter, dbType, options) {
|
|
|
1840
2150
|
`ALTER TABLE ${quotedTable} ALTER COLUMN ${quotedCol} DROP NOT NULL;`
|
|
1841
2151
|
);
|
|
1842
2152
|
}
|
|
1843
|
-
if (edits.defaultValue) {
|
|
1844
|
-
const
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
2153
|
+
if (edits.defaultValue !== void 0) {
|
|
2154
|
+
const formatted = formatSqlDefaultValue(edits.defaultValue);
|
|
2155
|
+
if (formatted !== null) {
|
|
2156
|
+
sqls.push(
|
|
2157
|
+
`ALTER TABLE ${quotedTable} ALTER COLUMN ${quotedCol} SET DEFAULT ${formatted};`
|
|
2158
|
+
);
|
|
2159
|
+
} else {
|
|
2160
|
+
sqls.push(
|
|
2161
|
+
`ALTER TABLE ${quotedTable} ALTER COLUMN ${quotedCol} DROP DEFAULT;`
|
|
2162
|
+
);
|
|
2163
|
+
}
|
|
1848
2164
|
}
|
|
1849
2165
|
} else if (dbType === "mysql") {
|
|
2166
|
+
let mysqlType = type;
|
|
2167
|
+
if (edits.enumValues && edits.enumValues.length > 0) {
|
|
2168
|
+
const vals = edits.enumValues.map((v) => `'${dialect.escapeString(v)}'`).join(", ");
|
|
2169
|
+
mysqlType = `ENUM(${vals})`;
|
|
2170
|
+
}
|
|
1850
2171
|
const nullStr = edits.nullable === false ? "NOT NULL" : "NULL";
|
|
1851
|
-
const
|
|
2172
|
+
const formatted = formatSqlDefaultValue(edits.defaultValue);
|
|
2173
|
+
const defStr = formatted !== null ? `DEFAULT ${formatted}` : "";
|
|
1852
2174
|
sqls.push(
|
|
1853
|
-
`ALTER TABLE ${quotedTable} MODIFY COLUMN ${quotedCol} ${
|
|
2175
|
+
`ALTER TABLE ${quotedTable} MODIFY COLUMN ${quotedCol} ${mysqlType} ${nullStr} ${defStr};`
|
|
1854
2176
|
);
|
|
1855
2177
|
}
|
|
1856
2178
|
}
|
|
@@ -1858,9 +2180,36 @@ async function applySchemaChanges(adapter, dbType, options) {
|
|
|
1858
2180
|
for (const ins of pendingInserts) {
|
|
1859
2181
|
if (!ins.name || ins.name.trim() === "") continue;
|
|
1860
2182
|
const quotedCol = dialect.quoteIdentifier(ins.name.trim());
|
|
1861
|
-
|
|
2183
|
+
let type = ins.type || "TEXT";
|
|
2184
|
+
if (dbType === "postgres" && ins.enumValues && ins.enumValues.length > 0) {
|
|
2185
|
+
if (ins.isNewEnum && ins.type) {
|
|
2186
|
+
const escapedName = ins.type.replace(/'/g, "''");
|
|
2187
|
+
const vals = ins.enumValues.map((v) => `'${dialect.escapeString(v)}'`).join(", ");
|
|
2188
|
+
const quotedType = dialect.quoteIdentifier(ins.type);
|
|
2189
|
+
sqls.push(`
|
|
2190
|
+
DO $$
|
|
2191
|
+
BEGIN
|
|
2192
|
+
IF NOT EXISTS (
|
|
2193
|
+
SELECT 1 FROM pg_type t
|
|
2194
|
+
JOIN pg_namespace n ON n.oid = t.typnamespace
|
|
2195
|
+
WHERE t.typname = '${escapedName}'
|
|
2196
|
+
AND (n.nspname = current_schema() OR n.nspname = 'public')
|
|
2197
|
+
) THEN
|
|
2198
|
+
CREATE TYPE ${quotedType} AS ENUM (${vals});
|
|
2199
|
+
END IF;
|
|
2200
|
+
END$$;
|
|
2201
|
+
`);
|
|
2202
|
+
type = quotedType;
|
|
2203
|
+
} else if (ins.type) {
|
|
2204
|
+
type = dialect.quoteIdentifier(ins.type);
|
|
2205
|
+
}
|
|
2206
|
+
} else if (dbType === "mysql" && ins.enumValues && ins.enumValues.length > 0) {
|
|
2207
|
+
const vals = ins.enumValues.map((v) => `'${dialect.escapeString(v)}'`).join(", ");
|
|
2208
|
+
type = `ENUM(${vals})`;
|
|
2209
|
+
}
|
|
1862
2210
|
const nullStr = ins.nullable === false ? "NOT NULL" : "";
|
|
1863
|
-
const
|
|
2211
|
+
const formattedDef = formatSqlDefaultValue(ins.defaultValue);
|
|
2212
|
+
const defStr = formattedDef !== null ? `DEFAULT ${formattedDef}` : "";
|
|
1864
2213
|
sqls.push(
|
|
1865
2214
|
`ALTER TABLE ${quotedTable} ADD COLUMN ${quotedCol} ${type} ${nullStr} ${defStr};`
|
|
1866
2215
|
);
|
|
@@ -1899,7 +2248,7 @@ async function applySchemaChanges(adapter, dbType, options) {
|
|
|
1899
2248
|
}
|
|
1900
2249
|
async function getTableRowCount(adapter, tableName) {
|
|
1901
2250
|
try {
|
|
1902
|
-
const quoted = adapter.quoteIdentifier(tableName);
|
|
2251
|
+
const quoted = adapter.quoteTable ? adapter.quoteTable(tableName) : adapter.quoteIdentifier(tableName);
|
|
1903
2252
|
const countRes = await adapter.query(
|
|
1904
2253
|
`SELECT COUNT(*) as cnt FROM ${quoted}`
|
|
1905
2254
|
);
|
|
@@ -2071,6 +2420,43 @@ async function exportDatabaseSchemaDdl(adapter, dbType) {
|
|
|
2071
2420
|
);
|
|
2072
2421
|
}
|
|
2073
2422
|
}
|
|
2423
|
+
async function getDatabaseEnums(adapter) {
|
|
2424
|
+
try {
|
|
2425
|
+
if (adapter.getCustomEnums) {
|
|
2426
|
+
const enums = await adapter.getCustomEnums();
|
|
2427
|
+
return ok(enums);
|
|
2428
|
+
}
|
|
2429
|
+
const enumsMap = /* @__PURE__ */ new Map();
|
|
2430
|
+
try {
|
|
2431
|
+
const tables = await adapter.getTables();
|
|
2432
|
+
for (const t of tables.slice(0, 30)) {
|
|
2433
|
+
const cols = await adapter.getSchema(t);
|
|
2434
|
+
for (const col of cols) {
|
|
2435
|
+
if (col.enumValues && col.enumValues.length > 0) {
|
|
2436
|
+
const key = col.type && ![
|
|
2437
|
+
"enum",
|
|
2438
|
+
"varchar",
|
|
2439
|
+
"varchar(255)",
|
|
2440
|
+
"text",
|
|
2441
|
+
"string"
|
|
2442
|
+
].includes(col.type.toLowerCase()) ? col.type : col.name;
|
|
2443
|
+
if (!enumsMap.has(key)) {
|
|
2444
|
+
enumsMap.set(key, col.enumValues);
|
|
2445
|
+
}
|
|
2446
|
+
}
|
|
2447
|
+
}
|
|
2448
|
+
}
|
|
2449
|
+
} catch {
|
|
2450
|
+
}
|
|
2451
|
+
const data = Array.from(enumsMap.entries()).map(([name, values]) => ({
|
|
2452
|
+
name,
|
|
2453
|
+
values
|
|
2454
|
+
}));
|
|
2455
|
+
return ok(data);
|
|
2456
|
+
} catch (e) {
|
|
2457
|
+
return err(e.message || "Failed to get database enums", void 0, e);
|
|
2458
|
+
}
|
|
2459
|
+
}
|
|
2074
2460
|
var init_schema = __esm({
|
|
2075
2461
|
"src/logic/schema.ts"() {
|
|
2076
2462
|
"use strict";
|
|
@@ -2443,6 +2829,27 @@ var init_seeder = __esm({
|
|
|
2443
2829
|
// src/logic/transfer.ts
|
|
2444
2830
|
import fs2 from "fs/promises";
|
|
2445
2831
|
import path2 from "path";
|
|
2832
|
+
async function getRestoreCandidates(dir) {
|
|
2833
|
+
const candidates = [];
|
|
2834
|
+
try {
|
|
2835
|
+
const entries = await fs2.readdir(dir, { withFileTypes: true });
|
|
2836
|
+
for (const entry of entries) {
|
|
2837
|
+
if (entry.isDirectory() && entry.name.startsWith("drixio_backup_")) {
|
|
2838
|
+
candidates.push({
|
|
2839
|
+
name: `\u{1F4C1} ${entry.name} (Backup Directory)`,
|
|
2840
|
+
value: entry.name
|
|
2841
|
+
});
|
|
2842
|
+
} else if (entry.isFile() && (entry.name.endsWith(".sql") || entry.name.endsWith(".json"))) {
|
|
2843
|
+
candidates.push({
|
|
2844
|
+
name: `\u{1F4C4} ${entry.name} (${entry.name.endsWith(".sql") ? "SQL Script" : "JSON Dump"})`,
|
|
2845
|
+
value: entry.name
|
|
2846
|
+
});
|
|
2847
|
+
}
|
|
2848
|
+
}
|
|
2849
|
+
} catch {
|
|
2850
|
+
}
|
|
2851
|
+
return candidates;
|
|
2852
|
+
}
|
|
2446
2853
|
function formatCsvValue(val) {
|
|
2447
2854
|
if (val === null || val === void 0) return "";
|
|
2448
2855
|
const str = String(val);
|
|
@@ -2941,6 +3348,153 @@ function generateDatabaseSqlDumpStream(adapter, dbType) {
|
|
|
2941
3348
|
}
|
|
2942
3349
|
});
|
|
2943
3350
|
}
|
|
3351
|
+
function nodeToWebStream(nodeStream) {
|
|
3352
|
+
return new ReadableStream({
|
|
3353
|
+
start(controller) {
|
|
3354
|
+
nodeStream.on("data", (chunk) => controller.enqueue(chunk));
|
|
3355
|
+
nodeStream.on("end", () => controller.close());
|
|
3356
|
+
nodeStream.on("error", (err2) => controller.error(err2));
|
|
3357
|
+
},
|
|
3358
|
+
cancel() {
|
|
3359
|
+
nodeStream.destroy();
|
|
3360
|
+
}
|
|
3361
|
+
});
|
|
3362
|
+
}
|
|
3363
|
+
async function exportDatabaseNative(dbType, connectionUrl, schemaOnly) {
|
|
3364
|
+
try {
|
|
3365
|
+
const executeNativeDump = (cmd, args, envName) => {
|
|
3366
|
+
return new Promise((resolve, reject) => {
|
|
3367
|
+
const cp = import("child_process").then((m) => m.spawn(cmd, args));
|
|
3368
|
+
let started = false;
|
|
3369
|
+
cp.then((cpInstance) => {
|
|
3370
|
+
const stream2 = nodeToWebStream(cpInstance.stdout);
|
|
3371
|
+
cpInstance.on("error", (e) => {
|
|
3372
|
+
if (!started)
|
|
3373
|
+
reject(
|
|
3374
|
+
new Error(
|
|
3375
|
+
`Native tool '${cmd}' not found. Please install ${envName}.`
|
|
3376
|
+
)
|
|
3377
|
+
);
|
|
3378
|
+
});
|
|
3379
|
+
setTimeout(() => {
|
|
3380
|
+
if (!cpInstance.killed) {
|
|
3381
|
+
started = true;
|
|
3382
|
+
resolve(stream2);
|
|
3383
|
+
}
|
|
3384
|
+
}, 100);
|
|
3385
|
+
});
|
|
3386
|
+
});
|
|
3387
|
+
};
|
|
3388
|
+
let stream;
|
|
3389
|
+
if (dbType === "sqlite") {
|
|
3390
|
+
const dbPath = connectionUrl.replace("file:", "");
|
|
3391
|
+
stream = await executeNativeDump(
|
|
3392
|
+
"sqlite3",
|
|
3393
|
+
[dbPath, schemaOnly ? ".schema" : ".dump"],
|
|
3394
|
+
"SQLite CLI"
|
|
3395
|
+
);
|
|
3396
|
+
} else if (dbType === "mysql") {
|
|
3397
|
+
const parsed = new URL(connectionUrl);
|
|
3398
|
+
const user = parsed.username;
|
|
3399
|
+
const pass = parsed.password;
|
|
3400
|
+
const host = parsed.hostname;
|
|
3401
|
+
const port = parsed.port || "3306";
|
|
3402
|
+
const dbname = parsed.pathname.substring(1);
|
|
3403
|
+
const args = ["-u", user, `-p${pass}`, "-h", host, "-P", port];
|
|
3404
|
+
if (schemaOnly) args.push("--no-data");
|
|
3405
|
+
args.push(dbname);
|
|
3406
|
+
stream = await executeNativeDump(
|
|
3407
|
+
"mysqldump",
|
|
3408
|
+
args,
|
|
3409
|
+
"MySQL Client"
|
|
3410
|
+
);
|
|
3411
|
+
} else if (dbType === "postgres") {
|
|
3412
|
+
const args = [connectionUrl];
|
|
3413
|
+
if (schemaOnly) args.push("--schema-only");
|
|
3414
|
+
stream = await executeNativeDump(
|
|
3415
|
+
"pg_dump",
|
|
3416
|
+
args,
|
|
3417
|
+
"PostgreSQL CLI"
|
|
3418
|
+
);
|
|
3419
|
+
} else {
|
|
3420
|
+
return err("Unsupported database type for native export");
|
|
3421
|
+
}
|
|
3422
|
+
return ok(stream);
|
|
3423
|
+
} catch (e) {
|
|
3424
|
+
return err(e.message || "Failed to export database using native tool", void 0, e);
|
|
3425
|
+
}
|
|
3426
|
+
}
|
|
3427
|
+
async function importDatabaseNative(adapter, dbType, connectionUrl, sqlContent) {
|
|
3428
|
+
try {
|
|
3429
|
+
const executeNativeImport = (cmd, args, fileContent, envName) => {
|
|
3430
|
+
return new Promise((resolve, reject) => {
|
|
3431
|
+
import("child_process").then((m) => {
|
|
3432
|
+
const cp = m.spawn(cmd, args);
|
|
3433
|
+
let started = false;
|
|
3434
|
+
let errStr = "";
|
|
3435
|
+
cp.on("error", (e) => {
|
|
3436
|
+
if (!started)
|
|
3437
|
+
reject(
|
|
3438
|
+
new Error(
|
|
3439
|
+
`Native tool '${cmd}' not found. Please install ${envName}.`
|
|
3440
|
+
)
|
|
3441
|
+
);
|
|
3442
|
+
});
|
|
3443
|
+
cp.stderr.on("data", (d) => errStr += d.toString());
|
|
3444
|
+
cp.on("close", (code) => {
|
|
3445
|
+
if (code === 0) resolve();
|
|
3446
|
+
else reject(new Error(`Native import failed: ${errStr}`));
|
|
3447
|
+
});
|
|
3448
|
+
cp.stdin.write(fileContent);
|
|
3449
|
+
cp.stdin.end();
|
|
3450
|
+
started = true;
|
|
3451
|
+
});
|
|
3452
|
+
});
|
|
3453
|
+
};
|
|
3454
|
+
try {
|
|
3455
|
+
if (dbType === "sqlite") {
|
|
3456
|
+
const dbPath = connectionUrl.replace("file:", "");
|
|
3457
|
+
await executeNativeImport(
|
|
3458
|
+
"sqlite3",
|
|
3459
|
+
[dbPath],
|
|
3460
|
+
sqlContent,
|
|
3461
|
+
"SQLite CLI"
|
|
3462
|
+
);
|
|
3463
|
+
} else if (dbType === "mysql") {
|
|
3464
|
+
const parsed = new URL(connectionUrl);
|
|
3465
|
+
const user = parsed.username;
|
|
3466
|
+
const pass = parsed.password;
|
|
3467
|
+
const host = parsed.hostname;
|
|
3468
|
+
const port = parsed.port || "3306";
|
|
3469
|
+
const dbname = parsed.pathname.substring(1);
|
|
3470
|
+
await executeNativeImport(
|
|
3471
|
+
"mysql",
|
|
3472
|
+
["-u", user, `-p${pass}`, "-h", host, "-P", port, dbname],
|
|
3473
|
+
sqlContent,
|
|
3474
|
+
"MySQL Client"
|
|
3475
|
+
);
|
|
3476
|
+
} else if (dbType === "postgres") {
|
|
3477
|
+
await executeNativeImport(
|
|
3478
|
+
"psql",
|
|
3479
|
+
[connectionUrl],
|
|
3480
|
+
sqlContent,
|
|
3481
|
+
"PostgreSQL CLI"
|
|
3482
|
+
);
|
|
3483
|
+
} else {
|
|
3484
|
+
return err("Unsupported database type for native import");
|
|
3485
|
+
}
|
|
3486
|
+
} catch (e) {
|
|
3487
|
+
if (e.message.includes("Native tool")) {
|
|
3488
|
+
await adapter.executeSql(sqlContent);
|
|
3489
|
+
} else {
|
|
3490
|
+
throw e;
|
|
3491
|
+
}
|
|
3492
|
+
}
|
|
3493
|
+
return ok({ message: "Import completed successfully" });
|
|
3494
|
+
} catch (e) {
|
|
3495
|
+
return err(`Import Failed: ${e.message}`, void 0, e);
|
|
3496
|
+
}
|
|
3497
|
+
}
|
|
2944
3498
|
var init_transfer = __esm({
|
|
2945
3499
|
"src/logic/transfer.ts"() {
|
|
2946
3500
|
"use strict";
|
|
@@ -3000,6 +3554,10 @@ async function createDatabase(options) {
|
|
|
3000
3554
|
});
|
|
3001
3555
|
try {
|
|
3002
3556
|
await conn.query(`CREATE DATABASE IF NOT EXISTS \`${dbName}\``);
|
|
3557
|
+
const quotedDbName = dbName.replace(/`/g, "``");
|
|
3558
|
+
await conn.query(
|
|
3559
|
+
`CREATE DATABASE IF NOT EXISTS \`${quotedDbName}\``
|
|
3560
|
+
);
|
|
3003
3561
|
} finally {
|
|
3004
3562
|
await conn.end();
|
|
3005
3563
|
}
|
|
@@ -3030,6 +3588,8 @@ async function createDatabase(options) {
|
|
|
3030
3588
|
);
|
|
3031
3589
|
if (res.rowCount === 0) {
|
|
3032
3590
|
await conn.query(`CREATE DATABASE "${dbName}"`);
|
|
3591
|
+
const quotedDbName = dbName.replace(/"/g, '""');
|
|
3592
|
+
await conn.query(`CREATE DATABASE "${quotedDbName}"`);
|
|
3033
3593
|
}
|
|
3034
3594
|
} finally {
|
|
3035
3595
|
await conn.end();
|
|
@@ -3081,6 +3641,8 @@ async function dropDatabase(options) {
|
|
|
3081
3641
|
});
|
|
3082
3642
|
try {
|
|
3083
3643
|
await conn.query(`DROP DATABASE IF EXISTS \`${dbName}\``);
|
|
3644
|
+
const quotedDbName = dbName.replace(/`/g, "``");
|
|
3645
|
+
await conn.query(`DROP DATABASE IF EXISTS \`${quotedDbName}\``);
|
|
3084
3646
|
} finally {
|
|
3085
3647
|
await conn.end();
|
|
3086
3648
|
}
|
|
@@ -3107,6 +3669,8 @@ async function dropDatabase(options) {
|
|
|
3107
3669
|
await conn.connect();
|
|
3108
3670
|
try {
|
|
3109
3671
|
await conn.query(`DROP DATABASE IF EXISTS "${dbName}"`);
|
|
3672
|
+
const quotedDbName = dbName.replace(/"/g, '""');
|
|
3673
|
+
await conn.query(`DROP DATABASE IF EXISTS "${quotedDbName}"`);
|
|
3110
3674
|
} finally {
|
|
3111
3675
|
await conn.end();
|
|
3112
3676
|
}
|
|
@@ -3838,7 +4402,10 @@ function generateMigrationSql(diff, dbType) {
|
|
|
3838
4402
|
let typeStr = col.type || "TEXT";
|
|
3839
4403
|
if (col.nullable === false) typeStr += " NOT NULL";
|
|
3840
4404
|
if (col.defaultValue !== void 0 && col.defaultValue !== null) {
|
|
3841
|
-
|
|
4405
|
+
const formatted = formatSqlDefaultValue(col.defaultValue);
|
|
4406
|
+
if (formatted !== null) {
|
|
4407
|
+
typeStr += ` DEFAULT ${formatted}`;
|
|
4408
|
+
}
|
|
3842
4409
|
}
|
|
3843
4410
|
lines.push(`ALTER TABLE ${qTable} ADD COLUMN ${qCol} ${typeStr};`);
|
|
3844
4411
|
}
|
|
@@ -3967,7 +4534,10 @@ function generateRollbackSql(diff, dbType) {
|
|
|
3967
4534
|
let typeStr = col.type || "TEXT";
|
|
3968
4535
|
if (col.nullable === false) typeStr += " NOT NULL";
|
|
3969
4536
|
if (col.defaultValue !== void 0 && col.defaultValue !== null) {
|
|
3970
|
-
|
|
4537
|
+
const formatted = formatSqlDefaultValue(col.defaultValue);
|
|
4538
|
+
if (formatted !== null) {
|
|
4539
|
+
typeStr += ` DEFAULT ${formatted}`;
|
|
4540
|
+
}
|
|
3971
4541
|
}
|
|
3972
4542
|
lines.push(`ALTER TABLE ${qTable} ADD COLUMN ${qCol} ${typeStr};`);
|
|
3973
4543
|
}
|
|
@@ -4208,9 +4778,18 @@ function extractSnippetParams(sql) {
|
|
|
4208
4778
|
function substituteSnippetParams(sql, params, dbType = "sqlite") {
|
|
4209
4779
|
if (!sql) return "";
|
|
4210
4780
|
const dialect = getDialect(dbType);
|
|
4781
|
+
const identifierKeys = /* @__PURE__ */ new Set([
|
|
4782
|
+
"table",
|
|
4783
|
+
"column",
|
|
4784
|
+
"child_table",
|
|
4785
|
+
"parent_table",
|
|
4786
|
+
"fk_col",
|
|
4787
|
+
"pk_col"
|
|
4788
|
+
]);
|
|
4211
4789
|
let result = sql;
|
|
4212
4790
|
for (const [key, rawValue] of Object.entries(params)) {
|
|
4213
4791
|
const valStr = rawValue !== void 0 && rawValue !== null ? String(rawValue) : "";
|
|
4792
|
+
const isIdentifier = identifierKeys.has(key);
|
|
4214
4793
|
const isNumber = !isNaN(Number(valStr)) && valStr.trim() !== "";
|
|
4215
4794
|
const quotedMustache = new RegExp(`'\\{\\{\\s*${key}\\s*\\}\\}'`, "g");
|
|
4216
4795
|
const quotedColon = new RegExp(`':${key}'`, "g");
|
|
@@ -4220,10 +4799,16 @@ function substituteSnippetParams(sql, params, dbType = "sqlite") {
|
|
|
4220
4799
|
const rawMustache = new RegExp(`\\{\\{\\s*${key}\\s*\\}\\}`, "g");
|
|
4221
4800
|
result = result.replace(rawMustache, () => {
|
|
4222
4801
|
return isNumber ? valStr : valStr;
|
|
4802
|
+
if (isIdentifier) return dialect.quoteIdentifier(valStr);
|
|
4803
|
+
if (isNumber) return valStr;
|
|
4804
|
+
return `'${escapedVal}'`;
|
|
4223
4805
|
});
|
|
4224
4806
|
const rawColon = new RegExp(`(^|[^:]):${key}\\b`, "g");
|
|
4225
4807
|
result = result.replace(rawColon, (_match, prefix) => {
|
|
4226
4808
|
return `${prefix}${valStr}`;
|
|
4809
|
+
if (isIdentifier) return `${prefix}${dialect.quoteIdentifier(valStr)}`;
|
|
4810
|
+
if (isNumber) return `${prefix}${valStr}`;
|
|
4811
|
+
return `${prefix}'${escapedVal}'`;
|
|
4227
4812
|
});
|
|
4228
4813
|
}
|
|
4229
4814
|
return result;
|
|
@@ -4282,8 +4867,8 @@ function getDrixioVersion() {
|
|
|
4282
4867
|
if (cachedVersion) {
|
|
4283
4868
|
return cachedVersion;
|
|
4284
4869
|
}
|
|
4285
|
-
if ("1.
|
|
4286
|
-
cachedVersion = "1.
|
|
4870
|
+
if ("1.2.0") {
|
|
4871
|
+
cachedVersion = "1.2.0";
|
|
4287
4872
|
return cachedVersion;
|
|
4288
4873
|
}
|
|
4289
4874
|
try {
|
|
@@ -4340,6 +4925,30 @@ var init_serialization = __esm({
|
|
|
4340
4925
|
}
|
|
4341
4926
|
});
|
|
4342
4927
|
|
|
4928
|
+
// src/logic/repl.ts
|
|
4929
|
+
function parseReplCommand(rawSql) {
|
|
4930
|
+
const trimmed = rawSql.trim();
|
|
4931
|
+
const connectMatch = trimmed.match(/^CONNECT\s+([^\s;]+)\s*;?$/i);
|
|
4932
|
+
if (connectMatch) {
|
|
4933
|
+
return { type: "connect", url: connectMatch[1], originalSql: rawSql };
|
|
4934
|
+
}
|
|
4935
|
+
if (/^DISCONNECT\s*;?$/i.test(trimmed)) {
|
|
4936
|
+
return { type: "disconnect", originalSql: rawSql };
|
|
4937
|
+
}
|
|
4938
|
+
const createDbMatch = trimmed.match(
|
|
4939
|
+
/^CREATE\s+DATABASE\s+(?:IF\s+NOT\s+EXISTS\s+)?['"`]?([^'";`\s]+)['"`]?\s*;?$/i
|
|
4940
|
+
);
|
|
4941
|
+
if (createDbMatch) {
|
|
4942
|
+
return { type: "create_database", dbName: createDbMatch[1], originalSql: rawSql };
|
|
4943
|
+
}
|
|
4944
|
+
return { type: "sql", originalSql: rawSql };
|
|
4945
|
+
}
|
|
4946
|
+
var init_repl = __esm({
|
|
4947
|
+
"src/logic/repl.ts"() {
|
|
4948
|
+
"use strict";
|
|
4949
|
+
}
|
|
4950
|
+
});
|
|
4951
|
+
|
|
4343
4952
|
// src/logic/index.ts
|
|
4344
4953
|
var init_logic = __esm({
|
|
4345
4954
|
"src/logic/index.ts"() {
|
|
@@ -4360,6 +4969,7 @@ var init_logic = __esm({
|
|
|
4360
4969
|
init_snippets();
|
|
4361
4970
|
init_version();
|
|
4362
4971
|
init_serialization();
|
|
4972
|
+
init_repl();
|
|
4363
4973
|
}
|
|
4364
4974
|
});
|
|
4365
4975
|
|
|
@@ -5862,21 +6472,8 @@ var init_diffWizard = __esm({
|
|
|
5862
6472
|
|
|
5863
6473
|
// src/studio/api.ts
|
|
5864
6474
|
import { Hono } from "hono";
|
|
5865
|
-
import { spawn } from "child_process";
|
|
5866
6475
|
import path18 from "path";
|
|
5867
6476
|
import fs18 from "fs";
|
|
5868
|
-
function nodeToWebStream(nodeStream) {
|
|
5869
|
-
return new ReadableStream({
|
|
5870
|
-
start(controller) {
|
|
5871
|
-
nodeStream.on("data", (chunk) => controller.enqueue(chunk));
|
|
5872
|
-
nodeStream.on("end", () => controller.close());
|
|
5873
|
-
nodeStream.on("error", (err2) => controller.error(err2));
|
|
5874
|
-
},
|
|
5875
|
-
cancel() {
|
|
5876
|
-
nodeStream.destroy();
|
|
5877
|
-
}
|
|
5878
|
-
});
|
|
5879
|
-
}
|
|
5880
6477
|
function registerApiRoutes(app, dbConfig) {
|
|
5881
6478
|
const api = new Hono();
|
|
5882
6479
|
let currentDbConfig = { ...dbConfig };
|
|
@@ -5962,7 +6559,11 @@ function registerApiRoutes(app, dbConfig) {
|
|
|
5962
6559
|
});
|
|
5963
6560
|
const detectHostEnvironment = (cfg) => {
|
|
5964
6561
|
if (cfg.type === "sqlite") {
|
|
5965
|
-
return {
|
|
6562
|
+
return {
|
|
6563
|
+
isRemote: false,
|
|
6564
|
+
host: "local",
|
|
6565
|
+
badgeLabel: "LOCAL (SQLite)"
|
|
6566
|
+
};
|
|
5966
6567
|
}
|
|
5967
6568
|
if (cfg.type === "mysql" || cfg.type === "postgres") {
|
|
5968
6569
|
let host = "localhost";
|
|
@@ -6241,7 +6842,7 @@ function registerApiRoutes(app, dbConfig) {
|
|
|
6241
6842
|
return c.json({ success: false, error: e.message }, 500);
|
|
6242
6843
|
}
|
|
6243
6844
|
});
|
|
6244
|
-
api.post("/analyze
|
|
6845
|
+
api.post("/query/analyze", async (c) => {
|
|
6245
6846
|
try {
|
|
6246
6847
|
const { sql } = await c.req.json().catch(() => ({}));
|
|
6247
6848
|
const result = analyzeDangerousQuery(sql);
|
|
@@ -6259,10 +6860,9 @@ function registerApiRoutes(app, dbConfig) {
|
|
|
6259
6860
|
400
|
|
6260
6861
|
);
|
|
6261
6862
|
}
|
|
6262
|
-
const
|
|
6263
|
-
|
|
6264
|
-
|
|
6265
|
-
const rawUrl = connectMatch[1];
|
|
6863
|
+
const replCmd = parseReplCommand(sql);
|
|
6864
|
+
if (replCmd.type === "connect" && replCmd.url) {
|
|
6865
|
+
const rawUrl = replCmd.url;
|
|
6266
6866
|
const newConfig = await detectDatabase(rawUrl);
|
|
6267
6867
|
const newAdapter = createDBAdapter(newConfig);
|
|
6268
6868
|
await newAdapter.getTables();
|
|
@@ -6294,7 +6894,7 @@ function registerApiRoutes(app, dbConfig) {
|
|
|
6294
6894
|
}
|
|
6295
6895
|
});
|
|
6296
6896
|
}
|
|
6297
|
-
if (
|
|
6897
|
+
if (replCmd.type === "disconnect") {
|
|
6298
6898
|
if (currentAdapter) {
|
|
6299
6899
|
try {
|
|
6300
6900
|
await currentAdapter.close();
|
|
@@ -6321,11 +6921,8 @@ function registerApiRoutes(app, dbConfig) {
|
|
|
6321
6921
|
}
|
|
6322
6922
|
});
|
|
6323
6923
|
}
|
|
6324
|
-
|
|
6325
|
-
|
|
6326
|
-
);
|
|
6327
|
-
if (createDbMatch) {
|
|
6328
|
-
const dbName = createDbMatch[1];
|
|
6924
|
+
if (replCmd.type === "create_database" && replCmd.dbName) {
|
|
6925
|
+
const dbName = replCmd.dbName;
|
|
6329
6926
|
if (currentAdapter && (currentDbConfig.type === "postgres" || currentDbConfig.type === "mysql")) {
|
|
6330
6927
|
const quote = currentDbConfig.type === "mysql" ? "`" : '"';
|
|
6331
6928
|
await currentAdapter.executeSql(
|
|
@@ -6407,24 +7004,19 @@ function registerApiRoutes(app, dbConfig) {
|
|
|
6407
7004
|
);
|
|
6408
7005
|
}
|
|
6409
7006
|
const adapter = getAdapter();
|
|
6410
|
-
const
|
|
6411
|
-
|
|
6412
|
-
|
|
6413
|
-
explainSql = `EXPLAIN QUERY PLAN ${cleanSql}`;
|
|
6414
|
-
} else if (currentDbConfig.type === "postgres") {
|
|
6415
|
-
explainSql = `EXPLAIN (ANALYZE, COSTS, VERBOSE, BUFFERS) ${cleanSql}`;
|
|
6416
|
-
} else if (currentDbConfig.type === "mysql") {
|
|
6417
|
-
explainSql = `EXPLAIN ${cleanSql}`;
|
|
6418
|
-
} else {
|
|
6419
|
-
explainSql = `EXPLAIN ${cleanSql}`;
|
|
7007
|
+
const explainRes = generateExplainQuery(sql, currentDbConfig.type);
|
|
7008
|
+
if (explainRes.error || !explainRes.sql) {
|
|
7009
|
+
return c.json({ success: false, error: explainRes.error }, 400);
|
|
6420
7010
|
}
|
|
7011
|
+
let explainSql = explainRes.sql;
|
|
6421
7012
|
const startTime = performance.now();
|
|
6422
7013
|
let res;
|
|
6423
7014
|
try {
|
|
6424
7015
|
res = await adapter.query(explainSql);
|
|
6425
7016
|
} catch (primaryErr) {
|
|
6426
7017
|
if (currentDbConfig.type === "postgres") {
|
|
6427
|
-
|
|
7018
|
+
const fallbackRes = generateExplainQuery(sql, "unknown");
|
|
7019
|
+
explainSql = fallbackRes.sql;
|
|
6428
7020
|
res = await adapter.query(explainSql);
|
|
6429
7021
|
} else {
|
|
6430
7022
|
throw primaryErr;
|
|
@@ -6517,68 +7109,25 @@ function registerApiRoutes(app, dbConfig) {
|
|
|
6517
7109
|
}
|
|
6518
7110
|
const type = currentDbConfig.type;
|
|
6519
7111
|
const url = currentDbConfig.targetUrl;
|
|
6520
|
-
let child;
|
|
6521
7112
|
let filename = "backup.sql";
|
|
6522
|
-
const executeNativeDump = (cmd, args, envName) => {
|
|
6523
|
-
return new Promise((resolve, reject) => {
|
|
6524
|
-
const cp = spawn(cmd, args);
|
|
6525
|
-
let started = false;
|
|
6526
|
-
const stream = nodeToWebStream(cp.stdout);
|
|
6527
|
-
cp.on("error", (err2) => {
|
|
6528
|
-
if (!started)
|
|
6529
|
-
reject(
|
|
6530
|
-
new Error(
|
|
6531
|
-
`Native tool '${cmd}' not found. Please install ${envName}.`
|
|
6532
|
-
)
|
|
6533
|
-
);
|
|
6534
|
-
});
|
|
6535
|
-
setTimeout(() => {
|
|
6536
|
-
if (!cp.killed) {
|
|
6537
|
-
started = true;
|
|
6538
|
-
resolve(stream);
|
|
6539
|
-
}
|
|
6540
|
-
}, 100);
|
|
6541
|
-
});
|
|
6542
|
-
};
|
|
6543
7113
|
try {
|
|
6544
|
-
let stream;
|
|
6545
7114
|
const baseName = getDbName();
|
|
6546
7115
|
const timeStr = getDatetimeStr();
|
|
6547
7116
|
filename = `${baseName}_backup_${timeStr}.sql`;
|
|
6548
|
-
|
|
6549
|
-
|
|
6550
|
-
|
|
6551
|
-
|
|
6552
|
-
|
|
6553
|
-
|
|
6554
|
-
);
|
|
6555
|
-
} else if (type === "mysql") {
|
|
6556
|
-
const parsed = new URL(url);
|
|
6557
|
-
const user = parsed.username;
|
|
6558
|
-
const pass = parsed.password;
|
|
6559
|
-
const host = parsed.hostname;
|
|
6560
|
-
const port = parsed.port || "3306";
|
|
6561
|
-
const dbname = parsed.pathname.substring(1);
|
|
6562
|
-
stream = await executeNativeDump(
|
|
6563
|
-
"mysqldump",
|
|
6564
|
-
["-u", user, `-p${pass}`, "-h", host, "-P", port, dbname],
|
|
6565
|
-
"MySQL Client"
|
|
6566
|
-
);
|
|
6567
|
-
} else if (type === "postgres") {
|
|
6568
|
-
stream = await executeNativeDump(
|
|
6569
|
-
"pg_dump",
|
|
6570
|
-
[url],
|
|
6571
|
-
"PostgreSQL CLI"
|
|
6572
|
-
);
|
|
6573
|
-
} else {
|
|
6574
|
-
throw new Error("Unsupported database type for native export");
|
|
7117
|
+
const exportRes = await exportDatabaseNative(
|
|
7118
|
+
type,
|
|
7119
|
+
url,
|
|
7120
|
+
false
|
|
7121
|
+
);
|
|
7122
|
+
if (!exportRes.success) {
|
|
7123
|
+
throw new Error(exportRes.error);
|
|
6575
7124
|
}
|
|
6576
7125
|
c.header(
|
|
6577
7126
|
"Content-Disposition",
|
|
6578
7127
|
`attachment; filename="${filename}"`
|
|
6579
7128
|
);
|
|
6580
7129
|
c.header("Content-Type", "application/sql");
|
|
6581
|
-
return c.body(
|
|
7130
|
+
return c.body(exportRes.data);
|
|
6582
7131
|
} catch (err2) {
|
|
6583
7132
|
if (err2.message.includes("Native tool")) {
|
|
6584
7133
|
const baseName = getDbName();
|
|
@@ -6634,67 +7183,14 @@ function registerApiRoutes(app, dbConfig) {
|
|
|
6634
7183
|
const sqlContent = await file.text();
|
|
6635
7184
|
const type = currentDbConfig.type;
|
|
6636
7185
|
const url = currentDbConfig.targetUrl;
|
|
6637
|
-
const
|
|
6638
|
-
|
|
6639
|
-
|
|
6640
|
-
|
|
6641
|
-
|
|
6642
|
-
|
|
6643
|
-
|
|
6644
|
-
|
|
6645
|
-
new Error(
|
|
6646
|
-
`Native tool '${cmd}' not found. Please install ${envName}.`
|
|
6647
|
-
)
|
|
6648
|
-
);
|
|
6649
|
-
});
|
|
6650
|
-
cp.stderr.on("data", (d) => errStr += d.toString());
|
|
6651
|
-
cp.on("close", (code) => {
|
|
6652
|
-
if (code === 0) resolve();
|
|
6653
|
-
else reject(new Error(`Native import failed: ${errStr}`));
|
|
6654
|
-
});
|
|
6655
|
-
cp.stdin.write(fileContent);
|
|
6656
|
-
cp.stdin.end();
|
|
6657
|
-
started = true;
|
|
6658
|
-
});
|
|
6659
|
-
};
|
|
6660
|
-
try {
|
|
6661
|
-
if (type === "sqlite") {
|
|
6662
|
-
const dbPath = url.replace("file:", "");
|
|
6663
|
-
await executeNativeImport(
|
|
6664
|
-
"sqlite3",
|
|
6665
|
-
[dbPath],
|
|
6666
|
-
sqlContent,
|
|
6667
|
-
"SQLite CLI"
|
|
6668
|
-
);
|
|
6669
|
-
} else if (type === "mysql") {
|
|
6670
|
-
const parsed = new URL(url);
|
|
6671
|
-
const user = parsed.username;
|
|
6672
|
-
const pass = parsed.password;
|
|
6673
|
-
const host = parsed.hostname;
|
|
6674
|
-
const port = parsed.port || "3306";
|
|
6675
|
-
const dbname = parsed.pathname.substring(1);
|
|
6676
|
-
await executeNativeImport(
|
|
6677
|
-
"mysql",
|
|
6678
|
-
["-u", user, `-p${pass}`, "-h", host, "-P", port, dbname],
|
|
6679
|
-
sqlContent,
|
|
6680
|
-
"MySQL Client"
|
|
6681
|
-
);
|
|
6682
|
-
} else if (type === "postgres") {
|
|
6683
|
-
await executeNativeImport(
|
|
6684
|
-
"psql",
|
|
6685
|
-
[url],
|
|
6686
|
-
sqlContent,
|
|
6687
|
-
"PostgreSQL CLI"
|
|
6688
|
-
);
|
|
6689
|
-
} else {
|
|
6690
|
-
throw new Error("Unsupported database type for native import");
|
|
6691
|
-
}
|
|
6692
|
-
} catch (err2) {
|
|
6693
|
-
if (err2.message.includes("Native tool")) {
|
|
6694
|
-
await getAdapter().executeSql(sqlContent);
|
|
6695
|
-
} else {
|
|
6696
|
-
throw err2;
|
|
6697
|
-
}
|
|
7186
|
+
const importRes = await importDatabaseNative(
|
|
7187
|
+
getAdapter(),
|
|
7188
|
+
type,
|
|
7189
|
+
url,
|
|
7190
|
+
sqlContent
|
|
7191
|
+
);
|
|
7192
|
+
if (!importRes.success) {
|
|
7193
|
+
return c.json({ success: false, error: importRes.error }, 500);
|
|
6698
7194
|
}
|
|
6699
7195
|
return c.json({
|
|
6700
7196
|
success: true,
|
|
@@ -7034,8 +7530,7 @@ function registerApiRoutes(app, dbConfig) {
|
|
|
7034
7530
|
const dialect = (dbType || "sqlite").toLowerCase();
|
|
7035
7531
|
if (dialect === "sqlite") {
|
|
7036
7532
|
const rawPath = body.url || body.sqlitePath || dbName || "drixio.sqlite";
|
|
7037
|
-
const
|
|
7038
|
-
const fullPath = path18.isAbsolute(cleanPath) ? cleanPath : path18.resolve(process.cwd(), cleanPath);
|
|
7533
|
+
const fullPath = resolveLocalDbPath(rawPath, process.cwd());
|
|
7039
7534
|
if (mode === "create") {
|
|
7040
7535
|
const dir = path18.dirname(fullPath);
|
|
7041
7536
|
if (!fs18.existsSync(dir)) {
|
|
@@ -7095,12 +7590,7 @@ function registerApiRoutes(app, dbConfig) {
|
|
|
7095
7590
|
}
|
|
7096
7591
|
let targetUrl = "";
|
|
7097
7592
|
if (mode === "create") {
|
|
7098
|
-
|
|
7099
|
-
const targetPort = port || (dialect === "postgres" ? "5432" : "3306");
|
|
7100
|
-
const targetUser = user || (dialect === "postgres" ? "postgres" : "root");
|
|
7101
|
-
const auth = password ? `${encodeURIComponent(targetUser)}:${encodeURIComponent(password)}` : encodeURIComponent(targetUser);
|
|
7102
|
-
const defaultDb = dialect === "postgres" ? "postgres" : "";
|
|
7103
|
-
targetUrl = `${dialect}://${auth}@${targetHost}:${targetPort}/${defaultDb}`;
|
|
7593
|
+
targetUrl = assembleConnectionUrl(dialect, host, port, user, password, "");
|
|
7104
7594
|
} else {
|
|
7105
7595
|
const rawUrl = body.url;
|
|
7106
7596
|
if (!rawUrl) {
|
|
@@ -7168,6 +7658,73 @@ function registerApiRoutes(app, dbConfig) {
|
|
|
7168
7658
|
return c.json({ success: false, error: e.message }, 500);
|
|
7169
7659
|
}
|
|
7170
7660
|
});
|
|
7661
|
+
api.get("/schemas", async (c) => {
|
|
7662
|
+
try {
|
|
7663
|
+
const adapter = currentAdapter;
|
|
7664
|
+
const supported = !!adapter && typeof adapter.getSchemas === "function" && currentDbConfig.type === "postgres";
|
|
7665
|
+
if (!supported || !adapter) {
|
|
7666
|
+
return c.json({
|
|
7667
|
+
success: true,
|
|
7668
|
+
data: { supported: false, schemas: [], currentSchema: "public" }
|
|
7669
|
+
});
|
|
7670
|
+
}
|
|
7671
|
+
const schemas = await adapter.getSchemas();
|
|
7672
|
+
const currentSchema = adapter.getCurrentSchema ? adapter.getCurrentSchema() : "public";
|
|
7673
|
+
return c.json({
|
|
7674
|
+
success: true,
|
|
7675
|
+
data: { supported: true, schemas, currentSchema }
|
|
7676
|
+
});
|
|
7677
|
+
} catch (e) {
|
|
7678
|
+
return c.json({ success: false, error: e.message }, 500);
|
|
7679
|
+
}
|
|
7680
|
+
});
|
|
7681
|
+
api.post("/schemas/switch", async (c) => {
|
|
7682
|
+
try {
|
|
7683
|
+
const { schema } = await c.req.json().catch(() => ({}));
|
|
7684
|
+
if (!schema || typeof schema !== "string") {
|
|
7685
|
+
return c.json(
|
|
7686
|
+
{ success: false, error: "Schema name is required" },
|
|
7687
|
+
400
|
|
7688
|
+
);
|
|
7689
|
+
}
|
|
7690
|
+
const adapter = currentAdapter;
|
|
7691
|
+
if (!adapter || typeof adapter.setSchema !== "function" || currentDbConfig.type !== "postgres") {
|
|
7692
|
+
return c.json(
|
|
7693
|
+
{
|
|
7694
|
+
success: false,
|
|
7695
|
+
error: "Schema switching is only supported for PostgreSQL"
|
|
7696
|
+
},
|
|
7697
|
+
400
|
|
7698
|
+
);
|
|
7699
|
+
}
|
|
7700
|
+
await adapter.setSchema(schema);
|
|
7701
|
+
const tables = await adapter.getTables();
|
|
7702
|
+
return c.json({
|
|
7703
|
+
success: true,
|
|
7704
|
+
data: {
|
|
7705
|
+
schema,
|
|
7706
|
+
tableCount: tables.length
|
|
7707
|
+
}
|
|
7708
|
+
});
|
|
7709
|
+
} catch (e) {
|
|
7710
|
+
return c.json({ success: false, error: e.message }, 500);
|
|
7711
|
+
}
|
|
7712
|
+
});
|
|
7713
|
+
api.get("/database/enums", async (c) => {
|
|
7714
|
+
try {
|
|
7715
|
+
const adapter = currentAdapter;
|
|
7716
|
+
if (!adapter) {
|
|
7717
|
+
return c.json({ success: true, data: [] });
|
|
7718
|
+
}
|
|
7719
|
+
const result = await getDatabaseEnums(adapter);
|
|
7720
|
+
if (!result.success) {
|
|
7721
|
+
return c.json({ success: false, error: result.error }, 500);
|
|
7722
|
+
}
|
|
7723
|
+
return c.json({ success: true, data: result.data });
|
|
7724
|
+
} catch (e) {
|
|
7725
|
+
return c.json({ success: false, error: e.message }, 500);
|
|
7726
|
+
}
|
|
7727
|
+
});
|
|
7171
7728
|
app.route("/api", api);
|
|
7172
7729
|
}
|
|
7173
7730
|
var init_api = __esm({
|
|
@@ -7785,23 +8342,7 @@ async function runRestoreCommand(dbConfig, args, options = {}) {
|
|
|
7785
8342
|
const { input: input11, select: select16, confirm: confirm8 } = await import("@inquirer/prompts");
|
|
7786
8343
|
if (!targetPath) {
|
|
7787
8344
|
try {
|
|
7788
|
-
const
|
|
7789
|
-
withFileTypes: true
|
|
7790
|
-
});
|
|
7791
|
-
const candidates = [];
|
|
7792
|
-
for (const entry of entries) {
|
|
7793
|
-
if (entry.isDirectory() && entry.name.startsWith("drixio_backup_")) {
|
|
7794
|
-
candidates.push({
|
|
7795
|
-
name: `\u{1F4C1} ${entry.name} (Backup Directory)`,
|
|
7796
|
-
value: entry.name
|
|
7797
|
-
});
|
|
7798
|
-
} else if (entry.isFile() && (entry.name.endsWith(".sql") || entry.name.endsWith(".json"))) {
|
|
7799
|
-
candidates.push({
|
|
7800
|
-
name: `\u{1F4C4} ${entry.name} (${entry.name.endsWith(".sql") ? "SQL Script" : "JSON Dump"})`,
|
|
7801
|
-
value: entry.name
|
|
7802
|
-
});
|
|
7803
|
-
}
|
|
7804
|
-
}
|
|
8345
|
+
const candidates = await getRestoreCandidates(process.cwd());
|
|
7805
8346
|
if (candidates.length > 0) {
|
|
7806
8347
|
candidates.push({
|
|
7807
8348
|
name: "\u270F\uFE0F Enter a custom path manually...",
|
|
@@ -8596,7 +9137,7 @@ Usage examples:`));
|
|
|
8596
9137
|
}
|
|
8597
9138
|
}
|
|
8598
9139
|
let diffResult;
|
|
8599
|
-
const isRemoteUrl =
|
|
9140
|
+
const isRemoteUrl = isConnectionString(targetArg) || targetArg.startsWith("file:");
|
|
8600
9141
|
if (isRemoteUrl) {
|
|
8601
9142
|
console.log(
|
|
8602
9143
|
pc17.cyan(
|