@atscript/db-sqlite 0.1.35 → 0.1.37
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/dist/index.cjs +88 -25
- package/dist/index.d.ts +11 -0
- package/dist/index.mjs +89 -26
- package/package.json +5 -5
package/dist/index.cjs
CHANGED
|
@@ -129,6 +129,7 @@ function buildCreateTable(table, fields, foreignKeys) {
|
|
|
129
129
|
let def = `"${esc(field.physicalName)}" ${sqlType}`;
|
|
130
130
|
if (field.isPrimaryKey && primaryKeys.length === 1) def += " PRIMARY KEY";
|
|
131
131
|
if (!field.optional && !field.isPrimaryKey) def += " NOT NULL";
|
|
132
|
+
if (field.defaultValue?.kind === "value") def += ` DEFAULT ${sqlStringLiteral(field.defaultValue.value)}`;
|
|
132
133
|
colDefs.push(def);
|
|
133
134
|
}
|
|
134
135
|
if (primaryKeys.length > 1) {
|
|
@@ -176,6 +177,9 @@ function buildProjection(select) {
|
|
|
176
177
|
function esc(name) {
|
|
177
178
|
return name.replace(/"/g, "\"\"");
|
|
178
179
|
}
|
|
180
|
+
function sqlStringLiteral(value) {
|
|
181
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
182
|
+
}
|
|
179
183
|
function toSqliteValue(value) {
|
|
180
184
|
if (value === undefined) return null;
|
|
181
185
|
if (value === null) return null;
|
|
@@ -395,13 +399,41 @@ var SqliteAdapter = class extends __atscript_utils_db.BaseDbAdapter {
|
|
|
395
399
|
/** SQLite does not use schemas — override to always exclude schema. */ resolveTableName() {
|
|
396
400
|
return super.resolveTableName(false);
|
|
397
401
|
}
|
|
402
|
+
/** SQLite enforces FK constraints natively via PRAGMA foreign_keys. */ supportsNativeForeignKeys() {
|
|
403
|
+
return true;
|
|
404
|
+
}
|
|
398
405
|
prepareId(id, _fieldType) {
|
|
399
406
|
return id;
|
|
400
407
|
}
|
|
408
|
+
/**
|
|
409
|
+
* Wraps a write operation to catch native SQLite constraint errors
|
|
410
|
+
* and rethrow as structured `DbError`.
|
|
411
|
+
*/ _wrapConstraintError(fn) {
|
|
412
|
+
try {
|
|
413
|
+
return fn();
|
|
414
|
+
} catch (e) {
|
|
415
|
+
if (e instanceof Error) {
|
|
416
|
+
if (e.message.includes("FOREIGN KEY constraint failed")) throw new __atscript_utils_db.DbError("FK_VIOLATION", [{
|
|
417
|
+
path: "",
|
|
418
|
+
message: e.message
|
|
419
|
+
}]);
|
|
420
|
+
const uniqueMatch = e.message.match(/UNIQUE constraint failed:\s*\S+\.(\S+)/);
|
|
421
|
+
if (uniqueMatch) throw new __atscript_utils_db.DbError("CONFLICT", [{
|
|
422
|
+
path: uniqueMatch[1],
|
|
423
|
+
message: e.message
|
|
424
|
+
}]);
|
|
425
|
+
if (e.message.includes("UNIQUE constraint failed")) throw new __atscript_utils_db.DbError("CONFLICT", [{
|
|
426
|
+
path: "",
|
|
427
|
+
message: e.message
|
|
428
|
+
}]);
|
|
429
|
+
}
|
|
430
|
+
throw e;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
401
433
|
async insertOne(data) {
|
|
402
434
|
const { sql, params } = buildInsert(this.resolveTableName(), data);
|
|
403
435
|
this._log(sql, params);
|
|
404
|
-
const result = this.driver.run(sql, params);
|
|
436
|
+
const result = this._wrapConstraintError(() => this.driver.run(sql, params));
|
|
405
437
|
return { insertedId: result.lastInsertRowid };
|
|
406
438
|
}
|
|
407
439
|
async insertMany(data) {
|
|
@@ -410,7 +442,7 @@ var SqliteAdapter = class extends __atscript_utils_db.BaseDbAdapter {
|
|
|
410
442
|
for (const row of data) {
|
|
411
443
|
const { sql, params } = buildInsert(this.resolveTableName(), row);
|
|
412
444
|
this._log(sql, params);
|
|
413
|
-
const result = this.driver.run(sql, params);
|
|
445
|
+
const result = this._wrapConstraintError(() => this.driver.run(sql, params));
|
|
414
446
|
ids.push(result.lastInsertRowid);
|
|
415
447
|
}
|
|
416
448
|
return {
|
|
@@ -455,7 +487,7 @@ var SqliteAdapter = class extends __atscript_utils_db.BaseDbAdapter {
|
|
|
455
487
|
const sql = `UPDATE "${esc(tableName)}" SET ${setClauses.join(", ")} WHERE rowid = (SELECT rowid FROM "${esc(tableName)}" WHERE ${where.sql} LIMIT 1)`;
|
|
456
488
|
const allParams = [...setParams, ...where.params];
|
|
457
489
|
this._log(sql, allParams);
|
|
458
|
-
const result = this.driver.run(sql, allParams);
|
|
490
|
+
const result = this._wrapConstraintError(() => this.driver.run(sql, allParams));
|
|
459
491
|
return {
|
|
460
492
|
matchedCount: result.changes,
|
|
461
493
|
modifiedCount: result.changes
|
|
@@ -465,7 +497,7 @@ var SqliteAdapter = class extends __atscript_utils_db.BaseDbAdapter {
|
|
|
465
497
|
const where = buildWhere(filter);
|
|
466
498
|
const { sql, params } = buildUpdate(this.resolveTableName(), data, where);
|
|
467
499
|
this._log(sql, params);
|
|
468
|
-
const result = this.driver.run(sql, params);
|
|
500
|
+
const result = this._wrapConstraintError(() => this.driver.run(sql, params));
|
|
469
501
|
return {
|
|
470
502
|
matchedCount: result.changes,
|
|
471
503
|
modifiedCount: result.changes
|
|
@@ -480,7 +512,7 @@ var SqliteAdapter = class extends __atscript_utils_db.BaseDbAdapter {
|
|
|
480
512
|
};
|
|
481
513
|
const { sql, params } = buildUpdate(tableName, data, limitedWhere);
|
|
482
514
|
this._log(sql, params);
|
|
483
|
-
const result = this.driver.run(sql, params);
|
|
515
|
+
const result = this._wrapConstraintError(() => this.driver.run(sql, params));
|
|
484
516
|
return {
|
|
485
517
|
matchedCount: result.changes,
|
|
486
518
|
modifiedCount: result.changes
|
|
@@ -490,7 +522,7 @@ var SqliteAdapter = class extends __atscript_utils_db.BaseDbAdapter {
|
|
|
490
522
|
const where = buildWhere(filter);
|
|
491
523
|
const { sql, params } = buildUpdate(this.resolveTableName(), data, where);
|
|
492
524
|
this._log(sql, params);
|
|
493
|
-
const result = this.driver.run(sql, params);
|
|
525
|
+
const result = this._wrapConstraintError(() => this.driver.run(sql, params));
|
|
494
526
|
return {
|
|
495
527
|
matchedCount: result.changes,
|
|
496
528
|
modifiedCount: result.changes
|
|
@@ -501,14 +533,14 @@ var SqliteAdapter = class extends __atscript_utils_db.BaseDbAdapter {
|
|
|
501
533
|
const tableName = this.resolveTableName();
|
|
502
534
|
const sql = `DELETE FROM "${esc(tableName)}" WHERE rowid = (SELECT rowid FROM "${esc(tableName)}" WHERE ${where.sql} LIMIT 1)`;
|
|
503
535
|
this._log(sql, where.params);
|
|
504
|
-
const result = this.driver.run(sql, where.params);
|
|
536
|
+
const result = this._wrapConstraintError(() => this.driver.run(sql, where.params));
|
|
505
537
|
return { deletedCount: result.changes };
|
|
506
538
|
}
|
|
507
539
|
async deleteMany(filter) {
|
|
508
540
|
const where = buildWhere(filter);
|
|
509
541
|
const { sql, params } = buildDelete(this.resolveTableName(), where);
|
|
510
542
|
this._log(sql, params);
|
|
511
|
-
const result = this.driver.run(sql, params);
|
|
543
|
+
const result = this._wrapConstraintError(() => this.driver.run(sql, params));
|
|
512
544
|
return { deletedCount: result.changes };
|
|
513
545
|
}
|
|
514
546
|
async ensureTable() {
|
|
@@ -537,9 +569,11 @@ var SqliteAdapter = class extends __atscript_utils_db.BaseDbAdapter {
|
|
|
537
569
|
renamed.push(field.physicalName);
|
|
538
570
|
}
|
|
539
571
|
for (const field of diff.added) {
|
|
540
|
-
const sqlType =
|
|
572
|
+
const sqlType = this.typeMapper(field);
|
|
541
573
|
let ddl = `ALTER TABLE "${esc(tableName)}" ADD COLUMN "${esc(field.physicalName)}" ${sqlType}`;
|
|
542
|
-
if (!field.optional && !field.isPrimaryKey) ddl +=
|
|
574
|
+
if (!field.optional && !field.isPrimaryKey) ddl += " NOT NULL";
|
|
575
|
+
if (field.defaultValue?.kind === "value") ddl += ` DEFAULT ${sqlStringLiteral(field.defaultValue.value)}`;
|
|
576
|
+
else if (!field.optional && !field.isPrimaryKey) ddl += ` DEFAULT ${defaultValueForType(field.designType)}`;
|
|
543
577
|
this._log(ddl);
|
|
544
578
|
this.driver.exec(ddl);
|
|
545
579
|
added.push(field.physicalName);
|
|
@@ -552,21 +586,39 @@ var SqliteAdapter = class extends __atscript_utils_db.BaseDbAdapter {
|
|
|
552
586
|
async recreateTable() {
|
|
553
587
|
const tableName = this.resolveTableName();
|
|
554
588
|
const tempName = `${tableName}__tmp_${Date.now()}`;
|
|
555
|
-
|
|
556
|
-
this.
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
const
|
|
564
|
-
const
|
|
565
|
-
|
|
566
|
-
|
|
589
|
+
this.driver.exec("PRAGMA foreign_keys = OFF");
|
|
590
|
+
this.driver.exec("PRAGMA legacy_alter_table = ON");
|
|
591
|
+
try {
|
|
592
|
+
const createSql = buildCreateTable(tempName, this._table.fieldDescriptors, this._table.foreignKeys);
|
|
593
|
+
this._log(createSql);
|
|
594
|
+
this.driver.exec(createSql);
|
|
595
|
+
const oldCols = (await this.getExistingColumns()).map((c) => c.name);
|
|
596
|
+
const newCols = this._table.fieldDescriptors.filter((f) => !f.ignored).map((f) => f.physicalName);
|
|
597
|
+
const oldColSet = new Set(oldCols);
|
|
598
|
+
const commonCols = newCols.filter((c) => oldColSet.has(c));
|
|
599
|
+
if (commonCols.length > 0) {
|
|
600
|
+
const fieldsByName = new Map(this._table.fieldDescriptors.map((f) => [f.physicalName, f]));
|
|
601
|
+
const colNames = commonCols.map((c) => `"${esc(c)}"`).join(", ");
|
|
602
|
+
const selectExprs = commonCols.map((c) => {
|
|
603
|
+
const field = fieldsByName.get(c);
|
|
604
|
+
if (field && !field.optional && !field.isPrimaryKey) {
|
|
605
|
+
const fallback = field.defaultValue?.kind === "value" ? sqlStringLiteral(field.defaultValue.value) : defaultValueForType(field.designType);
|
|
606
|
+
return `COALESCE("${esc(c)}", ${fallback}) AS "${esc(c)}"`;
|
|
607
|
+
}
|
|
608
|
+
return `"${esc(c)}"`;
|
|
609
|
+
}).join(", ");
|
|
610
|
+
const copySql = `INSERT INTO "${esc(tempName)}" (${colNames}) SELECT ${selectExprs} FROM "${esc(tableName)}"`;
|
|
611
|
+
this._log(copySql);
|
|
612
|
+
this.driver.exec(copySql);
|
|
613
|
+
}
|
|
614
|
+
const oldName = `${tableName}__old_${Date.now()}`;
|
|
615
|
+
this.driver.exec(`ALTER TABLE "${esc(tableName)}" RENAME TO "${esc(oldName)}"`);
|
|
616
|
+
this.driver.exec(`ALTER TABLE "${esc(tempName)}" RENAME TO "${esc(tableName)}"`);
|
|
617
|
+
this.driver.exec(`DROP TABLE IF EXISTS "${esc(oldName)}"`);
|
|
618
|
+
} finally {
|
|
619
|
+
this.driver.exec("PRAGMA legacy_alter_table = OFF");
|
|
620
|
+
this.driver.exec("PRAGMA foreign_keys = ON");
|
|
567
621
|
}
|
|
568
|
-
this.driver.exec(`DROP TABLE "${esc(tableName)}"`);
|
|
569
|
-
this.driver.exec(`ALTER TABLE "${esc(tempName)}" RENAME TO "${esc(tableName)}"`);
|
|
570
622
|
}
|
|
571
623
|
async dropTable() {
|
|
572
624
|
const tableName = this.resolveTableName();
|
|
@@ -600,13 +652,18 @@ var SqliteAdapter = class extends __atscript_utils_db.BaseDbAdapter {
|
|
|
600
652
|
this._log(ddl);
|
|
601
653
|
this.driver.exec(ddl);
|
|
602
654
|
}
|
|
655
|
+
typeMapper(field) {
|
|
656
|
+
if (field.isPrimaryKey && (field.designType === "number" || field.designType === "integer")) return "INTEGER";
|
|
657
|
+
return sqliteTypeFromDesignType(field.designType);
|
|
658
|
+
}
|
|
603
659
|
async getExistingColumnsForTable(tableName) {
|
|
604
660
|
const rows = this.driver.all(`PRAGMA table_info("${esc(tableName)}")`);
|
|
605
661
|
return rows.map((r) => ({
|
|
606
662
|
name: r.name,
|
|
607
663
|
type: r.type,
|
|
608
664
|
notnull: r.notnull === 1,
|
|
609
|
-
pk: r.pk > 0
|
|
665
|
+
pk: r.pk > 0,
|
|
666
|
+
dflt_value: normalizeSqliteDefault(r.dflt_value)
|
|
610
667
|
}));
|
|
611
668
|
}
|
|
612
669
|
async syncIndexes() {
|
|
@@ -641,6 +698,12 @@ var SqliteAdapter = class extends __atscript_utils_db.BaseDbAdapter {
|
|
|
641
698
|
default: return "''";
|
|
642
699
|
}
|
|
643
700
|
}
|
|
701
|
+
/** Normalizes SQLite PRAGMA dflt_value to match serialized format.
|
|
702
|
+
* PRAGMA returns `'active'` (SQL-quoted), we store `active` (raw). */ function normalizeSqliteDefault(value) {
|
|
703
|
+
if (value == null) return undefined;
|
|
704
|
+
if (value.startsWith("'") && value.endsWith("'")) return value.slice(1, -1);
|
|
705
|
+
return value;
|
|
706
|
+
}
|
|
644
707
|
|
|
645
708
|
//#endregion
|
|
646
709
|
//#region packages/db-sqlite/src/index.ts
|
package/dist/index.d.ts
CHANGED
|
@@ -71,7 +71,14 @@ declare class SqliteAdapter extends BaseDbAdapter {
|
|
|
71
71
|
protected _rollbackTransaction(): Promise<void>;
|
|
72
72
|
/** SQLite does not use schemas — override to always exclude schema. */
|
|
73
73
|
resolveTableName(): string;
|
|
74
|
+
/** SQLite enforces FK constraints natively via PRAGMA foreign_keys. */
|
|
75
|
+
supportsNativeForeignKeys(): boolean;
|
|
74
76
|
prepareId(id: unknown, _fieldType: TAtscriptAnnotatedType): unknown;
|
|
77
|
+
/**
|
|
78
|
+
* Wraps a write operation to catch native SQLite constraint errors
|
|
79
|
+
* and rethrow as structured `DbError`.
|
|
80
|
+
*/
|
|
81
|
+
private _wrapConstraintError;
|
|
75
82
|
insertOne(data: Record<string, unknown>): Promise<TDbInsertResult>;
|
|
76
83
|
insertMany(data: Array<Record<string, unknown>>): Promise<TDbInsertManyResult>;
|
|
77
84
|
findOne(query: DbQuery): Promise<Record<string, unknown> | null>;
|
|
@@ -93,6 +100,10 @@ declare class SqliteAdapter extends BaseDbAdapter {
|
|
|
93
100
|
dropTableByName(tableName: string): Promise<void>;
|
|
94
101
|
dropViewByName(viewName: string): Promise<void>;
|
|
95
102
|
renameTable(oldName: string): Promise<void>;
|
|
103
|
+
typeMapper(field: {
|
|
104
|
+
designType: string;
|
|
105
|
+
isPrimaryKey: boolean;
|
|
106
|
+
}): string;
|
|
96
107
|
getExistingColumnsForTable(tableName: string): Promise<TExistingColumn[]>;
|
|
97
108
|
syncIndexes(): Promise<void>;
|
|
98
109
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AtscriptDbView, BaseDbAdapter, DbSpace } from "@atscript/utils-db";
|
|
1
|
+
import { AtscriptDbView, BaseDbAdapter, DbError, DbSpace } from "@atscript/utils-db";
|
|
2
2
|
import { walkFilter } from "@uniqu/core";
|
|
3
3
|
|
|
4
4
|
//#region rolldown:runtime
|
|
@@ -112,6 +112,7 @@ function buildCreateTable(table, fields, foreignKeys) {
|
|
|
112
112
|
let def = `"${esc(field.physicalName)}" ${sqlType}`;
|
|
113
113
|
if (field.isPrimaryKey && primaryKeys.length === 1) def += " PRIMARY KEY";
|
|
114
114
|
if (!field.optional && !field.isPrimaryKey) def += " NOT NULL";
|
|
115
|
+
if (field.defaultValue?.kind === "value") def += ` DEFAULT ${sqlStringLiteral(field.defaultValue.value)}`;
|
|
115
116
|
colDefs.push(def);
|
|
116
117
|
}
|
|
117
118
|
if (primaryKeys.length > 1) {
|
|
@@ -159,6 +160,9 @@ function buildProjection(select) {
|
|
|
159
160
|
function esc(name) {
|
|
160
161
|
return name.replace(/"/g, "\"\"");
|
|
161
162
|
}
|
|
163
|
+
function sqlStringLiteral(value) {
|
|
164
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
165
|
+
}
|
|
162
166
|
function toSqliteValue(value) {
|
|
163
167
|
if (value === undefined) return null;
|
|
164
168
|
if (value === null) return null;
|
|
@@ -378,13 +382,41 @@ var SqliteAdapter = class extends BaseDbAdapter {
|
|
|
378
382
|
/** SQLite does not use schemas — override to always exclude schema. */ resolveTableName() {
|
|
379
383
|
return super.resolveTableName(false);
|
|
380
384
|
}
|
|
385
|
+
/** SQLite enforces FK constraints natively via PRAGMA foreign_keys. */ supportsNativeForeignKeys() {
|
|
386
|
+
return true;
|
|
387
|
+
}
|
|
381
388
|
prepareId(id, _fieldType) {
|
|
382
389
|
return id;
|
|
383
390
|
}
|
|
391
|
+
/**
|
|
392
|
+
* Wraps a write operation to catch native SQLite constraint errors
|
|
393
|
+
* and rethrow as structured `DbError`.
|
|
394
|
+
*/ _wrapConstraintError(fn) {
|
|
395
|
+
try {
|
|
396
|
+
return fn();
|
|
397
|
+
} catch (e) {
|
|
398
|
+
if (e instanceof Error) {
|
|
399
|
+
if (e.message.includes("FOREIGN KEY constraint failed")) throw new DbError("FK_VIOLATION", [{
|
|
400
|
+
path: "",
|
|
401
|
+
message: e.message
|
|
402
|
+
}]);
|
|
403
|
+
const uniqueMatch = e.message.match(/UNIQUE constraint failed:\s*\S+\.(\S+)/);
|
|
404
|
+
if (uniqueMatch) throw new DbError("CONFLICT", [{
|
|
405
|
+
path: uniqueMatch[1],
|
|
406
|
+
message: e.message
|
|
407
|
+
}]);
|
|
408
|
+
if (e.message.includes("UNIQUE constraint failed")) throw new DbError("CONFLICT", [{
|
|
409
|
+
path: "",
|
|
410
|
+
message: e.message
|
|
411
|
+
}]);
|
|
412
|
+
}
|
|
413
|
+
throw e;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
384
416
|
async insertOne(data) {
|
|
385
417
|
const { sql, params } = buildInsert(this.resolveTableName(), data);
|
|
386
418
|
this._log(sql, params);
|
|
387
|
-
const result = this.driver.run(sql, params);
|
|
419
|
+
const result = this._wrapConstraintError(() => this.driver.run(sql, params));
|
|
388
420
|
return { insertedId: result.lastInsertRowid };
|
|
389
421
|
}
|
|
390
422
|
async insertMany(data) {
|
|
@@ -393,7 +425,7 @@ var SqliteAdapter = class extends BaseDbAdapter {
|
|
|
393
425
|
for (const row of data) {
|
|
394
426
|
const { sql, params } = buildInsert(this.resolveTableName(), row);
|
|
395
427
|
this._log(sql, params);
|
|
396
|
-
const result = this.driver.run(sql, params);
|
|
428
|
+
const result = this._wrapConstraintError(() => this.driver.run(sql, params));
|
|
397
429
|
ids.push(result.lastInsertRowid);
|
|
398
430
|
}
|
|
399
431
|
return {
|
|
@@ -438,7 +470,7 @@ var SqliteAdapter = class extends BaseDbAdapter {
|
|
|
438
470
|
const sql = `UPDATE "${esc(tableName)}" SET ${setClauses.join(", ")} WHERE rowid = (SELECT rowid FROM "${esc(tableName)}" WHERE ${where.sql} LIMIT 1)`;
|
|
439
471
|
const allParams = [...setParams, ...where.params];
|
|
440
472
|
this._log(sql, allParams);
|
|
441
|
-
const result = this.driver.run(sql, allParams);
|
|
473
|
+
const result = this._wrapConstraintError(() => this.driver.run(sql, allParams));
|
|
442
474
|
return {
|
|
443
475
|
matchedCount: result.changes,
|
|
444
476
|
modifiedCount: result.changes
|
|
@@ -448,7 +480,7 @@ var SqliteAdapter = class extends BaseDbAdapter {
|
|
|
448
480
|
const where = buildWhere(filter);
|
|
449
481
|
const { sql, params } = buildUpdate(this.resolveTableName(), data, where);
|
|
450
482
|
this._log(sql, params);
|
|
451
|
-
const result = this.driver.run(sql, params);
|
|
483
|
+
const result = this._wrapConstraintError(() => this.driver.run(sql, params));
|
|
452
484
|
return {
|
|
453
485
|
matchedCount: result.changes,
|
|
454
486
|
modifiedCount: result.changes
|
|
@@ -463,7 +495,7 @@ var SqliteAdapter = class extends BaseDbAdapter {
|
|
|
463
495
|
};
|
|
464
496
|
const { sql, params } = buildUpdate(tableName, data, limitedWhere);
|
|
465
497
|
this._log(sql, params);
|
|
466
|
-
const result = this.driver.run(sql, params);
|
|
498
|
+
const result = this._wrapConstraintError(() => this.driver.run(sql, params));
|
|
467
499
|
return {
|
|
468
500
|
matchedCount: result.changes,
|
|
469
501
|
modifiedCount: result.changes
|
|
@@ -473,7 +505,7 @@ var SqliteAdapter = class extends BaseDbAdapter {
|
|
|
473
505
|
const where = buildWhere(filter);
|
|
474
506
|
const { sql, params } = buildUpdate(this.resolveTableName(), data, where);
|
|
475
507
|
this._log(sql, params);
|
|
476
|
-
const result = this.driver.run(sql, params);
|
|
508
|
+
const result = this._wrapConstraintError(() => this.driver.run(sql, params));
|
|
477
509
|
return {
|
|
478
510
|
matchedCount: result.changes,
|
|
479
511
|
modifiedCount: result.changes
|
|
@@ -484,14 +516,14 @@ var SqliteAdapter = class extends BaseDbAdapter {
|
|
|
484
516
|
const tableName = this.resolveTableName();
|
|
485
517
|
const sql = `DELETE FROM "${esc(tableName)}" WHERE rowid = (SELECT rowid FROM "${esc(tableName)}" WHERE ${where.sql} LIMIT 1)`;
|
|
486
518
|
this._log(sql, where.params);
|
|
487
|
-
const result = this.driver.run(sql, where.params);
|
|
519
|
+
const result = this._wrapConstraintError(() => this.driver.run(sql, where.params));
|
|
488
520
|
return { deletedCount: result.changes };
|
|
489
521
|
}
|
|
490
522
|
async deleteMany(filter) {
|
|
491
523
|
const where = buildWhere(filter);
|
|
492
524
|
const { sql, params } = buildDelete(this.resolveTableName(), where);
|
|
493
525
|
this._log(sql, params);
|
|
494
|
-
const result = this.driver.run(sql, params);
|
|
526
|
+
const result = this._wrapConstraintError(() => this.driver.run(sql, params));
|
|
495
527
|
return { deletedCount: result.changes };
|
|
496
528
|
}
|
|
497
529
|
async ensureTable() {
|
|
@@ -520,9 +552,11 @@ var SqliteAdapter = class extends BaseDbAdapter {
|
|
|
520
552
|
renamed.push(field.physicalName);
|
|
521
553
|
}
|
|
522
554
|
for (const field of diff.added) {
|
|
523
|
-
const sqlType =
|
|
555
|
+
const sqlType = this.typeMapper(field);
|
|
524
556
|
let ddl = `ALTER TABLE "${esc(tableName)}" ADD COLUMN "${esc(field.physicalName)}" ${sqlType}`;
|
|
525
|
-
if (!field.optional && !field.isPrimaryKey) ddl +=
|
|
557
|
+
if (!field.optional && !field.isPrimaryKey) ddl += " NOT NULL";
|
|
558
|
+
if (field.defaultValue?.kind === "value") ddl += ` DEFAULT ${sqlStringLiteral(field.defaultValue.value)}`;
|
|
559
|
+
else if (!field.optional && !field.isPrimaryKey) ddl += ` DEFAULT ${defaultValueForType(field.designType)}`;
|
|
526
560
|
this._log(ddl);
|
|
527
561
|
this.driver.exec(ddl);
|
|
528
562
|
added.push(field.physicalName);
|
|
@@ -535,21 +569,39 @@ var SqliteAdapter = class extends BaseDbAdapter {
|
|
|
535
569
|
async recreateTable() {
|
|
536
570
|
const tableName = this.resolveTableName();
|
|
537
571
|
const tempName = `${tableName}__tmp_${Date.now()}`;
|
|
538
|
-
|
|
539
|
-
this.
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
const
|
|
547
|
-
const
|
|
548
|
-
|
|
549
|
-
|
|
572
|
+
this.driver.exec("PRAGMA foreign_keys = OFF");
|
|
573
|
+
this.driver.exec("PRAGMA legacy_alter_table = ON");
|
|
574
|
+
try {
|
|
575
|
+
const createSql = buildCreateTable(tempName, this._table.fieldDescriptors, this._table.foreignKeys);
|
|
576
|
+
this._log(createSql);
|
|
577
|
+
this.driver.exec(createSql);
|
|
578
|
+
const oldCols = (await this.getExistingColumns()).map((c) => c.name);
|
|
579
|
+
const newCols = this._table.fieldDescriptors.filter((f) => !f.ignored).map((f) => f.physicalName);
|
|
580
|
+
const oldColSet = new Set(oldCols);
|
|
581
|
+
const commonCols = newCols.filter((c) => oldColSet.has(c));
|
|
582
|
+
if (commonCols.length > 0) {
|
|
583
|
+
const fieldsByName = new Map(this._table.fieldDescriptors.map((f) => [f.physicalName, f]));
|
|
584
|
+
const colNames = commonCols.map((c) => `"${esc(c)}"`).join(", ");
|
|
585
|
+
const selectExprs = commonCols.map((c) => {
|
|
586
|
+
const field = fieldsByName.get(c);
|
|
587
|
+
if (field && !field.optional && !field.isPrimaryKey) {
|
|
588
|
+
const fallback = field.defaultValue?.kind === "value" ? sqlStringLiteral(field.defaultValue.value) : defaultValueForType(field.designType);
|
|
589
|
+
return `COALESCE("${esc(c)}", ${fallback}) AS "${esc(c)}"`;
|
|
590
|
+
}
|
|
591
|
+
return `"${esc(c)}"`;
|
|
592
|
+
}).join(", ");
|
|
593
|
+
const copySql = `INSERT INTO "${esc(tempName)}" (${colNames}) SELECT ${selectExprs} FROM "${esc(tableName)}"`;
|
|
594
|
+
this._log(copySql);
|
|
595
|
+
this.driver.exec(copySql);
|
|
596
|
+
}
|
|
597
|
+
const oldName = `${tableName}__old_${Date.now()}`;
|
|
598
|
+
this.driver.exec(`ALTER TABLE "${esc(tableName)}" RENAME TO "${esc(oldName)}"`);
|
|
599
|
+
this.driver.exec(`ALTER TABLE "${esc(tempName)}" RENAME TO "${esc(tableName)}"`);
|
|
600
|
+
this.driver.exec(`DROP TABLE IF EXISTS "${esc(oldName)}"`);
|
|
601
|
+
} finally {
|
|
602
|
+
this.driver.exec("PRAGMA legacy_alter_table = OFF");
|
|
603
|
+
this.driver.exec("PRAGMA foreign_keys = ON");
|
|
550
604
|
}
|
|
551
|
-
this.driver.exec(`DROP TABLE "${esc(tableName)}"`);
|
|
552
|
-
this.driver.exec(`ALTER TABLE "${esc(tempName)}" RENAME TO "${esc(tableName)}"`);
|
|
553
605
|
}
|
|
554
606
|
async dropTable() {
|
|
555
607
|
const tableName = this.resolveTableName();
|
|
@@ -583,13 +635,18 @@ var SqliteAdapter = class extends BaseDbAdapter {
|
|
|
583
635
|
this._log(ddl);
|
|
584
636
|
this.driver.exec(ddl);
|
|
585
637
|
}
|
|
638
|
+
typeMapper(field) {
|
|
639
|
+
if (field.isPrimaryKey && (field.designType === "number" || field.designType === "integer")) return "INTEGER";
|
|
640
|
+
return sqliteTypeFromDesignType(field.designType);
|
|
641
|
+
}
|
|
586
642
|
async getExistingColumnsForTable(tableName) {
|
|
587
643
|
const rows = this.driver.all(`PRAGMA table_info("${esc(tableName)}")`);
|
|
588
644
|
return rows.map((r) => ({
|
|
589
645
|
name: r.name,
|
|
590
646
|
type: r.type,
|
|
591
647
|
notnull: r.notnull === 1,
|
|
592
|
-
pk: r.pk > 0
|
|
648
|
+
pk: r.pk > 0,
|
|
649
|
+
dflt_value: normalizeSqliteDefault(r.dflt_value)
|
|
593
650
|
}));
|
|
594
651
|
}
|
|
595
652
|
async syncIndexes() {
|
|
@@ -624,6 +681,12 @@ var SqliteAdapter = class extends BaseDbAdapter {
|
|
|
624
681
|
default: return "''";
|
|
625
682
|
}
|
|
626
683
|
}
|
|
684
|
+
/** Normalizes SQLite PRAGMA dflt_value to match serialized format.
|
|
685
|
+
* PRAGMA returns `'active'` (SQL-quoted), we store `active` (raw). */ function normalizeSqliteDefault(value) {
|
|
686
|
+
if (value == null) return undefined;
|
|
687
|
+
if (value.startsWith("'") && value.endsWith("'")) return value.slice(1, -1);
|
|
688
|
+
return value;
|
|
689
|
+
}
|
|
627
690
|
|
|
628
691
|
//#endregion
|
|
629
692
|
//#region packages/db-sqlite/src/index.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atscript/db-sqlite",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.37",
|
|
4
4
|
"description": "SQLite adapter for @atscript/utils-db with swappable driver support.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"atscript",
|
|
@@ -38,11 +38,11 @@
|
|
|
38
38
|
"vitest": "3.2.4"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
|
-
"@uniqu/core": "^0.0.
|
|
41
|
+
"@uniqu/core": "^0.0.6",
|
|
42
42
|
"better-sqlite3": ">=11.0.0",
|
|
43
|
-
"@atscript/core": "^0.1.
|
|
44
|
-
"@atscript/typescript": "^0.1.
|
|
45
|
-
"@atscript/utils-db": "^0.1.
|
|
43
|
+
"@atscript/core": "^0.1.37",
|
|
44
|
+
"@atscript/typescript": "^0.1.37",
|
|
45
|
+
"@atscript/utils-db": "^0.1.37"
|
|
46
46
|
},
|
|
47
47
|
"peerDependenciesMeta": {
|
|
48
48
|
"better-sqlite3": {
|