@b9g/zen 0.1.3 → 0.1.4
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/CHANGELOG.md +17 -0
- package/README.md +35 -40
- package/package.json +1 -1
- package/src/bun.d.ts +1 -0
- package/src/bun.js +12 -1
- package/src/impl/database.d.ts +18 -2
- package/src/mysql.d.ts +6 -0
- package/src/mysql.js +10 -3
- package/src/postgres.d.ts +6 -0
- package/src/postgres.js +12 -3
- package/src/sqlite.d.ts +6 -0
- package/src/sqlite.js +6 -2
- package/src/zen.js +22 -28
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,23 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.1.4] - 2025-12-28
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `db.explain()` - Get query execution plan (EXPLAIN QUERY PLAN for SQLite, EXPLAIN for PostgreSQL/MySQL)
|
|
13
|
+
- `explain()` method on Driver interface - each driver owns its dialect-specific EXPLAIN syntax
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- README documented fake APIs that never existed (`Users.ddl()`, `Posts.ensureColumn()`, `Posts.ensureIndex()`, `Users.copyColumn()`)
|
|
18
|
+
- README now documents the real APIs: `db.ensureTable()`, `db.ensureView()`, `db.ensureConstraints()`, `db.copyColumn()`
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- `getColumns()` is now required on Driver interface (was optional with fallback)
|
|
23
|
+
- Removed dialect-switching logic from database.ts - drivers own all dialect-specific behavior
|
|
24
|
+
|
|
8
25
|
## [0.1.3] - 2025-12-22
|
|
9
26
|
|
|
10
27
|
### Added
|
package/README.md
CHANGED
|
@@ -595,14 +595,16 @@ IndexedDB-style event-based migrations:
|
|
|
595
595
|
db.addEventListener("upgradeneeded", (e) => {
|
|
596
596
|
e.waitUntil((async () => {
|
|
597
597
|
if (e.oldVersion < 1) {
|
|
598
|
-
await db.
|
|
599
|
-
await db.
|
|
598
|
+
await db.ensureTable(Users);
|
|
599
|
+
await db.ensureTable(Posts);
|
|
600
600
|
}
|
|
601
601
|
if (e.oldVersion < 2) {
|
|
602
|
-
|
|
602
|
+
// Add a new column - just update the schema and call ensureTable again
|
|
603
|
+
await db.ensureTable(Posts); // Adds missing "views" column
|
|
603
604
|
}
|
|
604
605
|
if (e.oldVersion < 3) {
|
|
605
|
-
|
|
606
|
+
// Add constraints after data cleanup
|
|
607
|
+
await db.ensureConstraints(Posts);
|
|
606
608
|
}
|
|
607
609
|
})());
|
|
608
610
|
});
|
|
@@ -623,7 +625,7 @@ await db.open(3); // Opens at version 3, fires upgradeneeded if needed
|
|
|
623
625
|
zen provides idempotent helpers that encourage safe, additive-only migrations:
|
|
624
626
|
|
|
625
627
|
```typescript
|
|
626
|
-
// Add a new column
|
|
628
|
+
// Add a new column - update schema and call ensureTable
|
|
627
629
|
const Posts = table("posts", {
|
|
628
630
|
id: z.string().db.primary(),
|
|
629
631
|
title: z.string(),
|
|
@@ -631,24 +633,32 @@ const Posts = table("posts", {
|
|
|
631
633
|
});
|
|
632
634
|
|
|
633
635
|
if (e.oldVersion < 2) {
|
|
634
|
-
await db.
|
|
636
|
+
await db.ensureTable(Posts); // Adds missing columns from schema
|
|
635
637
|
}
|
|
636
|
-
// → ALTER TABLE "posts" ADD COLUMN
|
|
638
|
+
// → ALTER TABLE "posts" ADD COLUMN "views" REAL DEFAULT 0
|
|
639
|
+
|
|
640
|
+
// Add indexes - defined in schema, applied by ensureTable
|
|
641
|
+
const Posts = table("posts", {
|
|
642
|
+
id: z.string().db.primary(),
|
|
643
|
+
title: z.string().db.index(), // NEW - add index
|
|
644
|
+
views: z.number().db.inserted(() => 0),
|
|
645
|
+
});
|
|
637
646
|
|
|
638
|
-
// Add an index
|
|
639
647
|
if (e.oldVersion < 3) {
|
|
640
|
-
await db.
|
|
648
|
+
await db.ensureTable(Posts); // Adds missing indexes
|
|
641
649
|
}
|
|
642
|
-
// → CREATE INDEX IF NOT EXISTS "
|
|
650
|
+
// → CREATE INDEX IF NOT EXISTS "idx_posts_title" ON "posts"("title")
|
|
643
651
|
|
|
644
652
|
// Safe column rename (additive, non-destructive)
|
|
645
653
|
const Users = table("users", {
|
|
646
|
-
|
|
654
|
+
id: z.string().db.primary(),
|
|
655
|
+
email: z.string().email(), // Keep old column
|
|
656
|
+
emailAddress: z.string().email(), // NEW - add new column
|
|
647
657
|
});
|
|
648
658
|
|
|
649
659
|
if (e.oldVersion < 4) {
|
|
650
|
-
await db.
|
|
651
|
-
await db.
|
|
660
|
+
await db.ensureTable(Users); // Adds emailAddress column
|
|
661
|
+
await db.copyColumn(Users, "email", "emailAddress"); // Copy data
|
|
652
662
|
// Keep old "email" column for backwards compat
|
|
653
663
|
// Drop it in a later migration if needed (manual SQL)
|
|
654
664
|
}
|
|
@@ -656,9 +666,10 @@ if (e.oldVersion < 4) {
|
|
|
656
666
|
```
|
|
657
667
|
|
|
658
668
|
**Helper methods:**
|
|
659
|
-
- `
|
|
660
|
-
- `
|
|
661
|
-
- `
|
|
669
|
+
- `db.ensureTable(table)` - Idempotent CREATE TABLE / ADD COLUMN / CREATE INDEX
|
|
670
|
+
- `db.ensureView(view)` - Idempotent DROP + CREATE VIEW
|
|
671
|
+
- `db.ensureConstraints(table)` - Add unique/FK constraints (with preflight checks)
|
|
672
|
+
- `db.copyColumn(table, from, to)` - Copy data between columns (for safe renames)
|
|
662
673
|
|
|
663
674
|
All helpers read from your table schema (single source of truth) and are safe to run multiple times (idempotent).
|
|
664
675
|
|
|
@@ -937,25 +948,9 @@ const query = db.print`SELECT * FROM ${Posts} WHERE ${Posts.cols.published} = ${
|
|
|
937
948
|
console.log(query.sql); // SELECT * FROM "posts" WHERE "posts"."published" = ?
|
|
938
949
|
console.log(query.params); // [true]
|
|
939
950
|
|
|
940
|
-
// Inspect DDL generation
|
|
941
|
-
const ddl = db.print`${Posts.ddl()}`;
|
|
942
|
-
console.log(ddl.sql); // CREATE TABLE IF NOT EXISTS "posts" (...)
|
|
943
|
-
|
|
944
|
-
// Analyze query execution plan
|
|
945
|
-
const plan = await db.explain`
|
|
946
|
-
SELECT * FROM ${Posts}
|
|
947
|
-
WHERE ${Posts.cols.authorId} = ${userId}
|
|
948
|
-
`;
|
|
949
|
-
console.log(plan);
|
|
950
|
-
// SQLite: [{ detail: "SEARCH posts USING INDEX idx_posts_authorId (authorId=?)" }]
|
|
951
|
-
// PostgreSQL: [{ "QUERY PLAN": "Index Scan using idx_posts_authorId on posts" }]
|
|
952
|
-
|
|
953
951
|
// Debug fragments
|
|
954
952
|
console.log(Posts.set({ title: "Updated" }).toString());
|
|
955
953
|
// SQLFragment { sql: "\"title\" = ?", params: ["Updated"] }
|
|
956
|
-
|
|
957
|
-
console.log(Posts.ddl().toString());
|
|
958
|
-
// DDLFragment { type: "create-table", table: "posts" }
|
|
959
954
|
```
|
|
960
955
|
|
|
961
956
|
## Dialect Support
|
|
@@ -1085,12 +1080,6 @@ const Posts = table("posts", {
|
|
|
1085
1080
|
|
|
1086
1081
|
const rows = [{id: "u1", email: "alice@example.com", deletedAt: null}];
|
|
1087
1082
|
|
|
1088
|
-
// DDL Generation
|
|
1089
|
-
Users.ddl(); // DDLFragment for CREATE TABLE
|
|
1090
|
-
Users.ensureColumn("emailAddress"); // DDLFragment for ALTER TABLE ADD COLUMN
|
|
1091
|
-
Users.ensureIndex(["email"]); // DDLFragment for CREATE INDEX
|
|
1092
|
-
Users.copyColumn("email", "emailAddress"); // SQLFragment for UPDATE (copy data)
|
|
1093
|
-
|
|
1094
1083
|
// Query Fragments
|
|
1095
1084
|
Users.set({email: "alice@example.com"}); // SQLFragment for SET clause
|
|
1096
1085
|
Users.values(rows); // SQLFragment for INSERT VALUES
|
|
@@ -1157,9 +1146,15 @@ await db.transaction(async (tx) => {
|
|
|
1157
1146
|
await tx.exec`SELECT 1`;
|
|
1158
1147
|
});
|
|
1159
1148
|
|
|
1149
|
+
// Schema Management
|
|
1150
|
+
await db.ensureTable(Users); // CREATE TABLE / ADD COLUMN / CREATE INDEX
|
|
1151
|
+
await db.ensureView(AdminUsers); // DROP + CREATE VIEW
|
|
1152
|
+
await db.ensureConstraints(Users); // Add unique/FK constraints
|
|
1153
|
+
await db.copyColumn(Users, "old", "new"); // Copy data between columns
|
|
1154
|
+
|
|
1160
1155
|
// Debugging
|
|
1161
|
-
db.print`SELECT 1`;
|
|
1162
|
-
await db.explain`SELECT * FROM ${Users}`;
|
|
1156
|
+
db.print`SELECT 1`; // Returns { sql, params } without executing
|
|
1157
|
+
await db.explain`SELECT * FROM ${Users}`; // Returns query execution plan
|
|
1163
1158
|
```
|
|
1164
1159
|
|
|
1165
1160
|
### Driver Exports
|
package/package.json
CHANGED
package/src/bun.d.ts
CHANGED
package/src/bun.js
CHANGED
|
@@ -370,7 +370,9 @@ var BunDriver = class {
|
|
|
370
370
|
},
|
|
371
371
|
transaction: async () => {
|
|
372
372
|
throw new Error("Nested transactions are not supported");
|
|
373
|
-
}
|
|
373
|
+
},
|
|
374
|
+
getColumns: this.getColumns.bind(this),
|
|
375
|
+
explain: this.explain.bind(this)
|
|
374
376
|
};
|
|
375
377
|
return await fn(txDriver);
|
|
376
378
|
});
|
|
@@ -524,6 +526,15 @@ var BunDriver = class {
|
|
|
524
526
|
async getColumns(tableName) {
|
|
525
527
|
return await this.#getColumns(tableName);
|
|
526
528
|
}
|
|
529
|
+
async explain(strings, values) {
|
|
530
|
+
await this.#ensureSqliteInit();
|
|
531
|
+
const { sql, params } = buildSQL(strings, values, this.#dialect);
|
|
532
|
+
const explainPrefix = this.#dialect === "sqlite" ? "EXPLAIN QUERY PLAN " : "EXPLAIN ";
|
|
533
|
+
return await this.#sql.unsafe(
|
|
534
|
+
explainPrefix + sql,
|
|
535
|
+
params
|
|
536
|
+
);
|
|
537
|
+
}
|
|
527
538
|
// ==========================================================================
|
|
528
539
|
// Introspection Helpers (private)
|
|
529
540
|
// ==========================================================================
|
package/src/impl/database.d.ts
CHANGED
|
@@ -127,12 +127,14 @@ export interface Driver {
|
|
|
127
127
|
* @returns Number of rows updated
|
|
128
128
|
*/
|
|
129
129
|
copyColumn?<T extends Table<any>>(table: T, fromField: string, toField: string): Promise<number>;
|
|
130
|
-
/**
|
|
131
|
-
getColumns
|
|
130
|
+
/** Introspection: list columns for a table (name, type, nullability). */
|
|
131
|
+
getColumns(tableName: string): Promise<{
|
|
132
132
|
name: string;
|
|
133
133
|
type?: string;
|
|
134
134
|
notnull?: boolean;
|
|
135
135
|
}[]>;
|
|
136
|
+
/** Get the query execution plan for a SQL query. */
|
|
137
|
+
explain(strings: TemplateStringsArray, values: unknown[]): Promise<Record<string, unknown>[]>;
|
|
136
138
|
}
|
|
137
139
|
/**
|
|
138
140
|
* Result from ensure operations.
|
|
@@ -396,6 +398,20 @@ export declare class Database extends EventTarget {
|
|
|
396
398
|
sql: string;
|
|
397
399
|
params: unknown[];
|
|
398
400
|
};
|
|
401
|
+
/**
|
|
402
|
+
* Get the query execution plan without running the query.
|
|
403
|
+
*
|
|
404
|
+
* Returns the database's EXPLAIN output for the given query.
|
|
405
|
+
* The format varies by database:
|
|
406
|
+
* - SQLite: EXPLAIN QUERY PLAN output
|
|
407
|
+
* - PostgreSQL: EXPLAIN output
|
|
408
|
+
* - MySQL: EXPLAIN output
|
|
409
|
+
*
|
|
410
|
+
* @example
|
|
411
|
+
* const plan = await db.explain`SELECT * FROM ${Users} WHERE email = ${"test@example.com"}`;
|
|
412
|
+
* console.log(plan);
|
|
413
|
+
*/
|
|
414
|
+
explain(strings: TemplateStringsArray, ...values: unknown[]): Promise<Record<string, unknown>[]>;
|
|
399
415
|
/**
|
|
400
416
|
* Ensure a table exists with its columns and indexes.
|
|
401
417
|
*
|
package/src/mysql.d.ts
CHANGED
|
@@ -71,4 +71,10 @@ export default class MySQLDriver implements Driver {
|
|
|
71
71
|
* Applies unique and foreign key constraints with preflight checks.
|
|
72
72
|
*/
|
|
73
73
|
ensureConstraints<T extends Table<any>>(table: T): Promise<EnsureResult>;
|
|
74
|
+
getColumns(tableName: string): Promise<{
|
|
75
|
+
name: string;
|
|
76
|
+
type: string;
|
|
77
|
+
notnull: boolean;
|
|
78
|
+
}[]>;
|
|
79
|
+
explain(strings: TemplateStringsArray, values: unknown[]): Promise<Record<string, unknown>[]>;
|
|
74
80
|
}
|
package/src/mysql.js
CHANGED
|
@@ -268,7 +268,9 @@ var MySQLDriver = class {
|
|
|
268
268
|
},
|
|
269
269
|
transaction: async () => {
|
|
270
270
|
throw new Error("Nested transactions are not supported");
|
|
271
|
-
}
|
|
271
|
+
},
|
|
272
|
+
getColumns: this.getColumns.bind(this),
|
|
273
|
+
explain: this.explain.bind(this)
|
|
272
274
|
};
|
|
273
275
|
const result = await fn(txDriver);
|
|
274
276
|
await connection.query("COMMIT");
|
|
@@ -443,7 +445,7 @@ var MySQLDriver = class {
|
|
|
443
445
|
);
|
|
444
446
|
return (rows[0]?.count ?? 0) > 0;
|
|
445
447
|
}
|
|
446
|
-
async
|
|
448
|
+
async getColumns(tableName) {
|
|
447
449
|
const [rows] = await this.#pool.execute(
|
|
448
450
|
`SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = ? ORDER BY ordinal_position`,
|
|
449
451
|
[tableName]
|
|
@@ -454,6 +456,11 @@ var MySQLDriver = class {
|
|
|
454
456
|
notnull: row.IS_NULLABLE === "NO"
|
|
455
457
|
}));
|
|
456
458
|
}
|
|
459
|
+
async explain(strings, values) {
|
|
460
|
+
const { sql, params } = buildSQL(strings, values);
|
|
461
|
+
const [rows] = await this.#pool.execute(`EXPLAIN ${sql}`, params);
|
|
462
|
+
return rows;
|
|
463
|
+
}
|
|
457
464
|
async #getIndexes(tableName) {
|
|
458
465
|
const [rows] = await this.#pool.execute(
|
|
459
466
|
`SELECT
|
|
@@ -508,7 +515,7 @@ var MySQLDriver = class {
|
|
|
508
515
|
return constraints;
|
|
509
516
|
}
|
|
510
517
|
async #ensureMissingColumns(table) {
|
|
511
|
-
const existingCols = await this
|
|
518
|
+
const existingCols = await this.getColumns(table.name);
|
|
512
519
|
const existingColNames = new Set(existingCols.map((c) => c.name));
|
|
513
520
|
const schemaFields = Object.keys(table.schema.shape);
|
|
514
521
|
let applied = false;
|
package/src/postgres.d.ts
CHANGED
|
@@ -71,4 +71,10 @@ export default class PostgresDriver implements Driver {
|
|
|
71
71
|
* Applies unique and foreign key constraints with preflight checks.
|
|
72
72
|
*/
|
|
73
73
|
ensureConstraints<T extends Table<any>>(table: T): Promise<EnsureResult>;
|
|
74
|
+
getColumns(tableName: string): Promise<{
|
|
75
|
+
name: string;
|
|
76
|
+
type: string;
|
|
77
|
+
notnull: boolean;
|
|
78
|
+
}[]>;
|
|
79
|
+
explain(strings: TemplateStringsArray, values: unknown[]): Promise<Record<string, unknown>[]>;
|
|
74
80
|
}
|
package/src/postgres.js
CHANGED
|
@@ -235,7 +235,9 @@ var PostgresDriver = class {
|
|
|
235
235
|
},
|
|
236
236
|
transaction: async () => {
|
|
237
237
|
throw new Error("Nested transactions are not supported");
|
|
238
|
-
}
|
|
238
|
+
},
|
|
239
|
+
getColumns: this.getColumns.bind(this),
|
|
240
|
+
explain: this.explain.bind(this)
|
|
239
241
|
};
|
|
240
242
|
return await fn(txDriver);
|
|
241
243
|
});
|
|
@@ -397,7 +399,7 @@ var PostgresDriver = class {
|
|
|
397
399
|
`;
|
|
398
400
|
return result[0]?.exists ?? false;
|
|
399
401
|
}
|
|
400
|
-
async
|
|
402
|
+
async getColumns(tableName) {
|
|
401
403
|
const result = await this.#sql`
|
|
402
404
|
SELECT column_name, data_type, is_nullable
|
|
403
405
|
FROM information_schema.columns
|
|
@@ -411,6 +413,13 @@ var PostgresDriver = class {
|
|
|
411
413
|
notnull: row.is_nullable === "NO"
|
|
412
414
|
}));
|
|
413
415
|
}
|
|
416
|
+
async explain(strings, values) {
|
|
417
|
+
const { sql, params } = buildSQL(strings, values);
|
|
418
|
+
return await this.#sql.unsafe(
|
|
419
|
+
`EXPLAIN ${sql}`,
|
|
420
|
+
params
|
|
421
|
+
);
|
|
422
|
+
}
|
|
414
423
|
async #getIndexes(tableName) {
|
|
415
424
|
const result = await this.#sql`
|
|
416
425
|
SELECT indexname, indexdef
|
|
@@ -474,7 +483,7 @@ var PostgresDriver = class {
|
|
|
474
483
|
});
|
|
475
484
|
}
|
|
476
485
|
async #ensureMissingColumns(table) {
|
|
477
|
-
const existingCols = await this
|
|
486
|
+
const existingCols = await this.getColumns(table.name);
|
|
478
487
|
const existingColNames = new Set(existingCols.map((c) => c.name));
|
|
479
488
|
const schemaFields = Object.keys(table.schema.shape);
|
|
480
489
|
let applied = false;
|
package/src/sqlite.d.ts
CHANGED
|
@@ -51,4 +51,10 @@ export default class SQLiteDriver implements Driver {
|
|
|
51
51
|
ensureTable<T extends Table<any>>(table: T): Promise<EnsureResult>;
|
|
52
52
|
ensureView<T extends View<any>>(viewObj: T): Promise<EnsureResult>;
|
|
53
53
|
ensureConstraints<T extends Table<any>>(table: T): Promise<EnsureResult>;
|
|
54
|
+
getColumns(tableName: string): Promise<{
|
|
55
|
+
name: string;
|
|
56
|
+
type: string;
|
|
57
|
+
notnull: boolean;
|
|
58
|
+
}[]>;
|
|
59
|
+
explain(strings: TemplateStringsArray, values: unknown[]): Promise<Record<string, unknown>[]>;
|
|
54
60
|
}
|
package/src/sqlite.js
CHANGED
|
@@ -343,7 +343,7 @@ var SQLiteDriver = class {
|
|
|
343
343
|
const result = this.#db.prepare(`SELECT 1 FROM sqlite_master WHERE type='table' AND name=?`).all(tableName);
|
|
344
344
|
return result.length > 0;
|
|
345
345
|
}
|
|
346
|
-
async
|
|
346
|
+
async getColumns(tableName) {
|
|
347
347
|
const result = this.#db.prepare(`PRAGMA table_info(${quoteIdent2(tableName)})`).all();
|
|
348
348
|
return result.map((row) => ({
|
|
349
349
|
name: row.name,
|
|
@@ -351,6 +351,10 @@ var SQLiteDriver = class {
|
|
|
351
351
|
notnull: row.notnull === 1
|
|
352
352
|
}));
|
|
353
353
|
}
|
|
354
|
+
async explain(strings, values) {
|
|
355
|
+
const { sql, params } = buildSQL(strings, values);
|
|
356
|
+
return this.#db.prepare(`EXPLAIN QUERY PLAN ${sql}`).all(...params);
|
|
357
|
+
}
|
|
354
358
|
async #getIndexes(tableName) {
|
|
355
359
|
const indexList = this.#db.prepare(`PRAGMA index_list(${quoteIdent2(tableName)})`).all();
|
|
356
360
|
const indexes = [];
|
|
@@ -403,7 +407,7 @@ var SQLiteDriver = class {
|
|
|
403
407
|
// Schema Ensure Helpers (private)
|
|
404
408
|
// ==========================================================================
|
|
405
409
|
async #ensureMissingColumns(table) {
|
|
406
|
-
const existingCols = await this
|
|
410
|
+
const existingCols = await this.getColumns(table.name);
|
|
407
411
|
const existingColNames = new Set(existingCols.map((c) => c.name));
|
|
408
412
|
const schemaFields = Object.keys(table.schema.shape);
|
|
409
413
|
let applied = false;
|
package/src/zen.js
CHANGED
|
@@ -2018,6 +2018,26 @@ var Database = class extends EventTarget {
|
|
|
2018
2018
|
}
|
|
2019
2019
|
return { sql, params: expandedValues };
|
|
2020
2020
|
}
|
|
2021
|
+
/**
|
|
2022
|
+
* Get the query execution plan without running the query.
|
|
2023
|
+
*
|
|
2024
|
+
* Returns the database's EXPLAIN output for the given query.
|
|
2025
|
+
* The format varies by database:
|
|
2026
|
+
* - SQLite: EXPLAIN QUERY PLAN output
|
|
2027
|
+
* - PostgreSQL: EXPLAIN output
|
|
2028
|
+
* - MySQL: EXPLAIN output
|
|
2029
|
+
*
|
|
2030
|
+
* @example
|
|
2031
|
+
* const plan = await db.explain`SELECT * FROM ${Users} WHERE email = ${"test@example.com"}`;
|
|
2032
|
+
* console.log(plan);
|
|
2033
|
+
*/
|
|
2034
|
+
async explain(strings, ...values) {
|
|
2035
|
+
const { strings: expandedStrings, values: expandedValues } = expandFragments(
|
|
2036
|
+
strings,
|
|
2037
|
+
values
|
|
2038
|
+
);
|
|
2039
|
+
return await this.#driver.explain(expandedStrings, expandedValues);
|
|
2040
|
+
}
|
|
2021
2041
|
// ==========================================================================
|
|
2022
2042
|
// Schema Ensure Methods
|
|
2023
2043
|
// ==========================================================================
|
|
@@ -2190,34 +2210,8 @@ var Database = class extends EventTarget {
|
|
|
2190
2210
|
* Queries the actual table structure to verify column existence.
|
|
2191
2211
|
*/
|
|
2192
2212
|
async #checkColumnExists(tableName, columnName) {
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
return columns.some((col) => col.name === columnName);
|
|
2196
|
-
}
|
|
2197
|
-
try {
|
|
2198
|
-
const pragmaStrings = makeTemplate(["PRAGMA table_info(", ")"]);
|
|
2199
|
-
const pragmaValues = [ident(tableName)];
|
|
2200
|
-
const columns = await this.#driver.all(
|
|
2201
|
-
pragmaStrings,
|
|
2202
|
-
pragmaValues
|
|
2203
|
-
);
|
|
2204
|
-
if (columns.length > 0) {
|
|
2205
|
-
return columns.some((col) => col.name === columnName);
|
|
2206
|
-
}
|
|
2207
|
-
} catch {
|
|
2208
|
-
}
|
|
2209
|
-
try {
|
|
2210
|
-
const schemaStrings = makeTemplate([
|
|
2211
|
-
"SELECT column_name FROM information_schema.columns WHERE table_name = ",
|
|
2212
|
-
" AND column_name = ",
|
|
2213
|
-
" LIMIT 1"
|
|
2214
|
-
]);
|
|
2215
|
-
const schemaValues = [tableName, columnName];
|
|
2216
|
-
const result = await this.#driver.all(schemaStrings, schemaValues);
|
|
2217
|
-
return result.length > 0;
|
|
2218
|
-
} catch {
|
|
2219
|
-
return true;
|
|
2220
|
-
}
|
|
2213
|
+
const columns = await this.#driver.getColumns(tableName);
|
|
2214
|
+
return columns.some((col) => col.name === columnName);
|
|
2221
2215
|
}
|
|
2222
2216
|
// ==========================================================================
|
|
2223
2217
|
// Transactions
|