tempest-db-js 0.2.0 → 0.3.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 +2 -2
- package/dist/bin.cjs +89 -30
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +2 -2
- package/dist/{chunk-QMW4NKMH.js → chunk-OP7FRDI5.js} +199 -34
- package/dist/chunk-OP7FRDI5.js.map +1 -0
- package/dist/{chunk-AGDD7K3F.js → chunk-Q32CBI2A.js} +133 -16
- package/dist/chunk-Q32CBI2A.js.map +1 -0
- package/dist/index.cjs +131 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +28 -6
- package/dist/index.d.ts +28 -6
- package/dist/index.js +1 -1
- package/dist/migrations/index.cjs +196 -30
- package/dist/migrations/index.cjs.map +1 -1
- package/dist/migrations/index.d.cts +42 -5
- package/dist/migrations/index.d.ts +42 -5
- package/dist/migrations/index.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-AGDD7K3F.js.map +0 -1
- package/dist/chunk-QMW4NKMH.js.map +0 -1
package/dist/index.d.cts
CHANGED
|
@@ -380,14 +380,14 @@ declare function del<C extends ModelClass>(model: C): DeleteBuilder<InferModel<C
|
|
|
380
380
|
* detection, so URLs copied from a Python service still work here.
|
|
381
381
|
*/
|
|
382
382
|
/** A database dialect tempest-db-js can target. */
|
|
383
|
-
type Dialect = "sqlite" | "postgresql";
|
|
383
|
+
type Dialect = "sqlite" | "postgresql" | "mysql";
|
|
384
384
|
/** A parsed database URL, dialect-neutral. */
|
|
385
385
|
interface ParsedDatabaseUrl {
|
|
386
386
|
/** The detected dialect. */
|
|
387
387
|
readonly dialect: Dialect;
|
|
388
388
|
/** Driver after the `+` in the scheme (e.g. `better-sqlite3`), or `null`. */
|
|
389
389
|
readonly driver: string | null;
|
|
390
|
-
/** Host (PostgreSQL), or `null` for SQLite. */
|
|
390
|
+
/** Host (PostgreSQL/MySQL), or `null` for SQLite. */
|
|
391
391
|
readonly host: string | null;
|
|
392
392
|
/** Port, or `null`. */
|
|
393
393
|
readonly port: number | null;
|
|
@@ -610,7 +610,7 @@ type QueryNode = SelectNode | InsertNode | UpdateNode | DeleteNode | JoinNode;
|
|
|
610
610
|
* actually differs between databases (placeholder syntax, `ILIKE` support).
|
|
611
611
|
*/
|
|
612
612
|
declare abstract class BaseDialect {
|
|
613
|
-
abstract readonly name:
|
|
613
|
+
abstract readonly name: Dialect;
|
|
614
614
|
/**
|
|
615
615
|
* INSERT SQL templates keyed by structure (dialect|table|columns|rowCount|
|
|
616
616
|
* returning). Shared across dialect instances — the key namespaces by dialect
|
|
@@ -647,10 +647,19 @@ declare abstract class BaseDialect {
|
|
|
647
647
|
* So a per-row insert loop compiles the string once and reuses it every row.
|
|
648
648
|
*/
|
|
649
649
|
private insertTemplate;
|
|
650
|
+
/**
|
|
651
|
+
* Render the conflict-handling clause. Standard SQL (SQLite/PostgreSQL) uses
|
|
652
|
+
* `ON CONFLICT (...) DO NOTHING | DO UPDATE SET ...`; MySQL overrides this.
|
|
653
|
+
*
|
|
654
|
+
* @param onConflict The conflict clause from the node.
|
|
655
|
+
* @param conflictCols The columns to overwrite on `DO UPDATE` (empty for nothing).
|
|
656
|
+
* @param nextPlaceholder Yields the next positional placeholder (advances the count).
|
|
657
|
+
*/
|
|
658
|
+
protected renderConflict(onConflict: NonNullable<InsertNode["onConflict"]>, conflictCols: readonly string[], nextPlaceholder: () => string): string;
|
|
650
659
|
private compileUpdate;
|
|
651
660
|
private compileDelete;
|
|
652
661
|
private compileJoin;
|
|
653
|
-
|
|
662
|
+
protected compileReturning(returning: readonly string[] | "*" | null): string;
|
|
654
663
|
/**
|
|
655
664
|
* Compile a condition tree (fields / and / or / not) to SQL. `idFor` renders a
|
|
656
665
|
* key to a quoted identifier — `quoteId` for single-table, `qualify` for joins —
|
|
@@ -672,8 +681,21 @@ declare class PostgresDialect extends BaseDialect {
|
|
|
672
681
|
protected placeholder(index: number): string;
|
|
673
682
|
protected ilike(column: string, param: string): string;
|
|
674
683
|
}
|
|
684
|
+
/**
|
|
685
|
+
* MySQL dialect: `?` placeholders, backtick identifiers, `ON DUPLICATE KEY
|
|
686
|
+
* UPDATE` for upsert, and case-insensitive `LIKE` (default collation). MySQL has
|
|
687
|
+
* no `RETURNING`, so requesting it throws.
|
|
688
|
+
*/
|
|
689
|
+
declare class MysqlDialect extends BaseDialect {
|
|
690
|
+
readonly name: "mysql";
|
|
691
|
+
protected placeholder(): string;
|
|
692
|
+
protected ilike(column: string, param: string): string;
|
|
693
|
+
protected quoteId(name: string): string;
|
|
694
|
+
protected renderConflict(onConflict: NonNullable<InsertNode["onConflict"]>, conflictCols: readonly string[], nextPlaceholder: () => string): string;
|
|
695
|
+
protected compileReturning(returning: readonly string[] | "*" | null): string;
|
|
696
|
+
}
|
|
675
697
|
/** Get a dialect instance by name. */
|
|
676
|
-
declare function getDialect(name:
|
|
698
|
+
declare function getDialect(name: Dialect): BaseDialect;
|
|
677
699
|
|
|
678
700
|
/** The outcome of running one statement. */
|
|
679
701
|
interface DriverResult {
|
|
@@ -1364,4 +1386,4 @@ type InferInsert<C extends ModelClass> = Simplify<{
|
|
|
1364
1386
|
[K in Exclude<ColumnKeys<InstanceType<C>>, OptionalInsertKeys<InstanceType<C>>>]: ColValue<InstanceType<C>[K]>;
|
|
1365
1387
|
}>;
|
|
1366
1388
|
|
|
1367
|
-
export { ActiveRecord, type ActiveRecordManager, Agg, type AggregateTerm, type AsyncDriver, AsyncEngine, AsyncResult, AsyncSession, BaseDialect, BaseRepository, type BelongsTo, type ColRef, type ColType, Column, type ColumnFlags, type ColumnType, type ColumnTypeKind, type ColumnTypeMeta, type CompiledQuery, type CondNode, type Condition, type DefaultValue, DeleteBuilder, type DeleteNode, type Dialect, type DriverResult, type EngineOptions, type Executable, type HasMany, type InferInsert, type InferModel, InsertBuilder, type InsertNode, InvalidDatabaseUrl, JoinBuilder, type JoinClause, type JoinNode, type JoinOn, type JoinSelection, type JoinWhereInput, Model, type ModelClass, NoResultError, NodeSqliteDriver, OPERATORS, type OnConflict, type Operator, type OperatorsFor, type OrderTerm, type PaginationFilter, type PaginationResult, type ParsedDatabaseUrl, type PoolOptions, type PortableExpression, PostgresDialect, QueryExecutionError, type QueryLogger, type QueryNode, RecordNotFound, type Relation, type RelationValue, type ReservedAsyncDriver, type Returning, type RowOf, SelectBuilder, type SelectNode, type SortDirection, type Sources, SqliteDialect, type SyncDriver, SyncEngine, SyncResult, SyncSession, UpdateBuilder, type UpdateNode, ValidationError, type WhereArg, type WhereInput, type WithRelations, activeRecord, and, avg, belongsTo, column, columnsOf, count, createEngine, createSyncEngine, del, detectDialect, fromDict, getDialect, hasMany, insert, isCondition, join, loadRelations, max, min, not, or, parse, parseDatabaseUrl, select, sql, stringify, sum, toCondNode, toDict, toJSON, update };
|
|
1389
|
+
export { ActiveRecord, type ActiveRecordManager, Agg, type AggregateTerm, type AsyncDriver, AsyncEngine, AsyncResult, AsyncSession, BaseDialect, BaseRepository, type BelongsTo, type ColRef, type ColType, Column, type ColumnFlags, type ColumnType, type ColumnTypeKind, type ColumnTypeMeta, type CompiledQuery, type CondNode, type Condition, type DefaultValue, DeleteBuilder, type DeleteNode, type Dialect, type DriverResult, type EngineOptions, type Executable, type HasMany, type InferInsert, type InferModel, InsertBuilder, type InsertNode, InvalidDatabaseUrl, JoinBuilder, type JoinClause, type JoinNode, type JoinOn, type JoinSelection, type JoinWhereInput, Model, type ModelClass, MysqlDialect, NoResultError, NodeSqliteDriver, OPERATORS, type OnConflict, type Operator, type OperatorsFor, type OrderTerm, type PaginationFilter, type PaginationResult, type ParsedDatabaseUrl, type PoolOptions, type PortableExpression, PostgresDialect, QueryExecutionError, type QueryLogger, type QueryNode, RecordNotFound, type Relation, type RelationValue, type ReservedAsyncDriver, type Returning, type RowOf, SelectBuilder, type SelectNode, type SortDirection, type Sources, SqliteDialect, type SyncDriver, SyncEngine, SyncResult, SyncSession, UpdateBuilder, type UpdateNode, ValidationError, type WhereArg, type WhereInput, type WithRelations, activeRecord, and, avg, belongsTo, column, columnsOf, count, createEngine, createSyncEngine, del, detectDialect, fromDict, getDialect, hasMany, insert, isCondition, join, loadRelations, max, min, not, or, parse, parseDatabaseUrl, select, sql, stringify, sum, toCondNode, toDict, toJSON, update };
|
package/dist/index.d.ts
CHANGED
|
@@ -380,14 +380,14 @@ declare function del<C extends ModelClass>(model: C): DeleteBuilder<InferModel<C
|
|
|
380
380
|
* detection, so URLs copied from a Python service still work here.
|
|
381
381
|
*/
|
|
382
382
|
/** A database dialect tempest-db-js can target. */
|
|
383
|
-
type Dialect = "sqlite" | "postgresql";
|
|
383
|
+
type Dialect = "sqlite" | "postgresql" | "mysql";
|
|
384
384
|
/** A parsed database URL, dialect-neutral. */
|
|
385
385
|
interface ParsedDatabaseUrl {
|
|
386
386
|
/** The detected dialect. */
|
|
387
387
|
readonly dialect: Dialect;
|
|
388
388
|
/** Driver after the `+` in the scheme (e.g. `better-sqlite3`), or `null`. */
|
|
389
389
|
readonly driver: string | null;
|
|
390
|
-
/** Host (PostgreSQL), or `null` for SQLite. */
|
|
390
|
+
/** Host (PostgreSQL/MySQL), or `null` for SQLite. */
|
|
391
391
|
readonly host: string | null;
|
|
392
392
|
/** Port, or `null`. */
|
|
393
393
|
readonly port: number | null;
|
|
@@ -610,7 +610,7 @@ type QueryNode = SelectNode | InsertNode | UpdateNode | DeleteNode | JoinNode;
|
|
|
610
610
|
* actually differs between databases (placeholder syntax, `ILIKE` support).
|
|
611
611
|
*/
|
|
612
612
|
declare abstract class BaseDialect {
|
|
613
|
-
abstract readonly name:
|
|
613
|
+
abstract readonly name: Dialect;
|
|
614
614
|
/**
|
|
615
615
|
* INSERT SQL templates keyed by structure (dialect|table|columns|rowCount|
|
|
616
616
|
* returning). Shared across dialect instances — the key namespaces by dialect
|
|
@@ -647,10 +647,19 @@ declare abstract class BaseDialect {
|
|
|
647
647
|
* So a per-row insert loop compiles the string once and reuses it every row.
|
|
648
648
|
*/
|
|
649
649
|
private insertTemplate;
|
|
650
|
+
/**
|
|
651
|
+
* Render the conflict-handling clause. Standard SQL (SQLite/PostgreSQL) uses
|
|
652
|
+
* `ON CONFLICT (...) DO NOTHING | DO UPDATE SET ...`; MySQL overrides this.
|
|
653
|
+
*
|
|
654
|
+
* @param onConflict The conflict clause from the node.
|
|
655
|
+
* @param conflictCols The columns to overwrite on `DO UPDATE` (empty for nothing).
|
|
656
|
+
* @param nextPlaceholder Yields the next positional placeholder (advances the count).
|
|
657
|
+
*/
|
|
658
|
+
protected renderConflict(onConflict: NonNullable<InsertNode["onConflict"]>, conflictCols: readonly string[], nextPlaceholder: () => string): string;
|
|
650
659
|
private compileUpdate;
|
|
651
660
|
private compileDelete;
|
|
652
661
|
private compileJoin;
|
|
653
|
-
|
|
662
|
+
protected compileReturning(returning: readonly string[] | "*" | null): string;
|
|
654
663
|
/**
|
|
655
664
|
* Compile a condition tree (fields / and / or / not) to SQL. `idFor` renders a
|
|
656
665
|
* key to a quoted identifier — `quoteId` for single-table, `qualify` for joins —
|
|
@@ -672,8 +681,21 @@ declare class PostgresDialect extends BaseDialect {
|
|
|
672
681
|
protected placeholder(index: number): string;
|
|
673
682
|
protected ilike(column: string, param: string): string;
|
|
674
683
|
}
|
|
684
|
+
/**
|
|
685
|
+
* MySQL dialect: `?` placeholders, backtick identifiers, `ON DUPLICATE KEY
|
|
686
|
+
* UPDATE` for upsert, and case-insensitive `LIKE` (default collation). MySQL has
|
|
687
|
+
* no `RETURNING`, so requesting it throws.
|
|
688
|
+
*/
|
|
689
|
+
declare class MysqlDialect extends BaseDialect {
|
|
690
|
+
readonly name: "mysql";
|
|
691
|
+
protected placeholder(): string;
|
|
692
|
+
protected ilike(column: string, param: string): string;
|
|
693
|
+
protected quoteId(name: string): string;
|
|
694
|
+
protected renderConflict(onConflict: NonNullable<InsertNode["onConflict"]>, conflictCols: readonly string[], nextPlaceholder: () => string): string;
|
|
695
|
+
protected compileReturning(returning: readonly string[] | "*" | null): string;
|
|
696
|
+
}
|
|
675
697
|
/** Get a dialect instance by name. */
|
|
676
|
-
declare function getDialect(name:
|
|
698
|
+
declare function getDialect(name: Dialect): BaseDialect;
|
|
677
699
|
|
|
678
700
|
/** The outcome of running one statement. */
|
|
679
701
|
interface DriverResult {
|
|
@@ -1364,4 +1386,4 @@ type InferInsert<C extends ModelClass> = Simplify<{
|
|
|
1364
1386
|
[K in Exclude<ColumnKeys<InstanceType<C>>, OptionalInsertKeys<InstanceType<C>>>]: ColValue<InstanceType<C>[K]>;
|
|
1365
1387
|
}>;
|
|
1366
1388
|
|
|
1367
|
-
export { ActiveRecord, type ActiveRecordManager, Agg, type AggregateTerm, type AsyncDriver, AsyncEngine, AsyncResult, AsyncSession, BaseDialect, BaseRepository, type BelongsTo, type ColRef, type ColType, Column, type ColumnFlags, type ColumnType, type ColumnTypeKind, type ColumnTypeMeta, type CompiledQuery, type CondNode, type Condition, type DefaultValue, DeleteBuilder, type DeleteNode, type Dialect, type DriverResult, type EngineOptions, type Executable, type HasMany, type InferInsert, type InferModel, InsertBuilder, type InsertNode, InvalidDatabaseUrl, JoinBuilder, type JoinClause, type JoinNode, type JoinOn, type JoinSelection, type JoinWhereInput, Model, type ModelClass, NoResultError, NodeSqliteDriver, OPERATORS, type OnConflict, type Operator, type OperatorsFor, type OrderTerm, type PaginationFilter, type PaginationResult, type ParsedDatabaseUrl, type PoolOptions, type PortableExpression, PostgresDialect, QueryExecutionError, type QueryLogger, type QueryNode, RecordNotFound, type Relation, type RelationValue, type ReservedAsyncDriver, type Returning, type RowOf, SelectBuilder, type SelectNode, type SortDirection, type Sources, SqliteDialect, type SyncDriver, SyncEngine, SyncResult, SyncSession, UpdateBuilder, type UpdateNode, ValidationError, type WhereArg, type WhereInput, type WithRelations, activeRecord, and, avg, belongsTo, column, columnsOf, count, createEngine, createSyncEngine, del, detectDialect, fromDict, getDialect, hasMany, insert, isCondition, join, loadRelations, max, min, not, or, parse, parseDatabaseUrl, select, sql, stringify, sum, toCondNode, toDict, toJSON, update };
|
|
1389
|
+
export { ActiveRecord, type ActiveRecordManager, Agg, type AggregateTerm, type AsyncDriver, AsyncEngine, AsyncResult, AsyncSession, BaseDialect, BaseRepository, type BelongsTo, type ColRef, type ColType, Column, type ColumnFlags, type ColumnType, type ColumnTypeKind, type ColumnTypeMeta, type CompiledQuery, type CondNode, type Condition, type DefaultValue, DeleteBuilder, type DeleteNode, type Dialect, type DriverResult, type EngineOptions, type Executable, type HasMany, type InferInsert, type InferModel, InsertBuilder, type InsertNode, InvalidDatabaseUrl, JoinBuilder, type JoinClause, type JoinNode, type JoinOn, type JoinSelection, type JoinWhereInput, Model, type ModelClass, MysqlDialect, NoResultError, NodeSqliteDriver, OPERATORS, type OnConflict, type Operator, type OperatorsFor, type OrderTerm, type PaginationFilter, type PaginationResult, type ParsedDatabaseUrl, type PoolOptions, type PortableExpression, PostgresDialect, QueryExecutionError, type QueryLogger, type QueryNode, RecordNotFound, type Relation, type RelationValue, type ReservedAsyncDriver, type Returning, type RowOf, SelectBuilder, type SelectNode, type SortDirection, type Sources, SqliteDialect, type SyncDriver, SyncEngine, SyncResult, SyncSession, UpdateBuilder, type UpdateNode, ValidationError, type WhereArg, type WhereInput, type WithRelations, activeRecord, and, avg, belongsTo, column, columnsOf, count, createEngine, createSyncEngine, del, detectDialect, fromDict, getDialect, hasMany, insert, isCondition, join, loadRelations, max, min, not, or, parse, parseDatabaseUrl, select, sql, stringify, sum, toCondNode, toDict, toJSON, update };
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { ActiveRecord, Agg, AsyncEngine, AsyncResult, AsyncSession, BaseDialect, BaseRepository, Column, DeleteBuilder, InsertBuilder, InvalidDatabaseUrl, JoinBuilder, Model, NoResultError, NodeSqliteDriver, OPERATORS, PostgresDialect, QueryExecutionError, RecordNotFound, SelectBuilder, SqliteDialect, SyncEngine, SyncResult, SyncSession, UpdateBuilder, ValidationError, activeRecord, and, avg, belongsTo, column, columnsOf, count, createEngine, createSyncEngine, del, detectDialect, fromDict, getDialect, hasMany, insert, isCondition, join, loadRelations, max, min, not, or, parse, parseDatabaseUrl, select, sql, stringify, sum, toCondNode, toDict, toJSON, update } from './chunk-
|
|
1
|
+
export { ActiveRecord, Agg, AsyncEngine, AsyncResult, AsyncSession, BaseDialect, BaseRepository, Column, DeleteBuilder, InsertBuilder, InvalidDatabaseUrl, JoinBuilder, Model, MysqlDialect, NoResultError, NodeSqliteDriver, OPERATORS, PostgresDialect, QueryExecutionError, RecordNotFound, SelectBuilder, SqliteDialect, SyncEngine, SyncResult, SyncSession, UpdateBuilder, ValidationError, activeRecord, and, avg, belongsTo, column, columnsOf, count, createEngine, createSyncEngine, del, detectDialect, fromDict, getDialect, hasMany, insert, isCondition, join, loadRelations, max, min, not, or, parse, parseDatabaseUrl, select, sql, stringify, sum, toCondNode, toDict, toJSON, update } from './chunk-Q32CBI2A.js';
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
|
3
3
|
//# sourceMappingURL=index.js.map
|
|
@@ -140,8 +140,8 @@ function invertAll(ops) {
|
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
// src/migrations/ddl.ts
|
|
143
|
-
function quoteId(name) {
|
|
144
|
-
return `"${name.replace(/"/g, '""')}"`;
|
|
143
|
+
function quoteId(name, dialect) {
|
|
144
|
+
return dialect === "mysql" ? `\`${name.replace(/`/g, "``")}\`` : `"${name.replace(/"/g, '""')}"`;
|
|
145
145
|
}
|
|
146
146
|
function quoteLiteral(value) {
|
|
147
147
|
return `'${value.replace(/'/g, "''")}'`;
|
|
@@ -166,6 +166,45 @@ function renderColumnType(type, dialect) {
|
|
|
166
166
|
return "TEXT";
|
|
167
167
|
}
|
|
168
168
|
}
|
|
169
|
+
if (dialect === "mysql") {
|
|
170
|
+
switch (kind) {
|
|
171
|
+
case "smallint":
|
|
172
|
+
return "SMALLINT";
|
|
173
|
+
case "integer":
|
|
174
|
+
return "INT";
|
|
175
|
+
case "bigint":
|
|
176
|
+
return "BIGINT";
|
|
177
|
+
case "numeric":
|
|
178
|
+
return meta.precision !== void 0 ? `DECIMAL(${meta.precision}${meta.scale !== void 0 ? `, ${meta.scale}` : ""})` : "DECIMAL";
|
|
179
|
+
case "real":
|
|
180
|
+
return "FLOAT";
|
|
181
|
+
case "double":
|
|
182
|
+
return "DOUBLE";
|
|
183
|
+
case "varchar":
|
|
184
|
+
return `VARCHAR(${meta.length ?? 255})`;
|
|
185
|
+
case "char":
|
|
186
|
+
return `CHAR(${meta.length ?? 255})`;
|
|
187
|
+
case "text":
|
|
188
|
+
return "TEXT";
|
|
189
|
+
case "boolean":
|
|
190
|
+
return "TINYINT(1)";
|
|
191
|
+
case "date":
|
|
192
|
+
return "DATE";
|
|
193
|
+
case "time":
|
|
194
|
+
return "TIME";
|
|
195
|
+
case "datetime":
|
|
196
|
+
case "timestamp":
|
|
197
|
+
return "DATETIME";
|
|
198
|
+
case "blob":
|
|
199
|
+
return "BLOB";
|
|
200
|
+
case "json":
|
|
201
|
+
return "JSON";
|
|
202
|
+
case "uuid":
|
|
203
|
+
return "CHAR(36)";
|
|
204
|
+
case "enum":
|
|
205
|
+
return `ENUM(${(meta.values ?? []).map(quoteLiteral).join(", ")})`;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
169
208
|
switch (kind) {
|
|
170
209
|
case "smallint":
|
|
171
210
|
return "SMALLINT";
|
|
@@ -210,19 +249,21 @@ function renderDefault(def, dialect) {
|
|
|
210
249
|
if (typeof expr === "object") return expr.raw;
|
|
211
250
|
switch (expr) {
|
|
212
251
|
case "now":
|
|
213
|
-
return dialect === "
|
|
252
|
+
return dialect === "postgresql" ? "now()" : "CURRENT_TIMESTAMP";
|
|
214
253
|
case "current_date":
|
|
215
254
|
return "CURRENT_DATE";
|
|
216
255
|
case "current_time":
|
|
217
256
|
return "CURRENT_TIME";
|
|
218
257
|
case "uuidv4":
|
|
219
|
-
|
|
258
|
+
if (dialect === "postgresql") return "gen_random_uuid()";
|
|
259
|
+
if (dialect === "mysql") return "(UUID())";
|
|
260
|
+
return "(lower(hex(randomblob(16))))";
|
|
220
261
|
}
|
|
221
262
|
}
|
|
222
263
|
const value = def.value;
|
|
223
264
|
if (value === null) return "NULL";
|
|
224
265
|
if (typeof value === "boolean") {
|
|
225
|
-
return dialect === "
|
|
266
|
+
return dialect === "postgresql" ? value ? "TRUE" : "FALSE" : value ? "1" : "0";
|
|
226
267
|
}
|
|
227
268
|
if (typeof value === "number" || typeof value === "bigint") return String(value);
|
|
228
269
|
if (value instanceof Date) return quoteLiteral(value.toISOString());
|
|
@@ -230,7 +271,7 @@ function renderDefault(def, dialect) {
|
|
|
230
271
|
return quoteLiteral(String(value));
|
|
231
272
|
}
|
|
232
273
|
function renderColumnDef(col, dialect) {
|
|
233
|
-
let sql = `${quoteId(col.name)} ${renderColumnType(col.type, dialect)}`;
|
|
274
|
+
let sql = `${quoteId(col.name, dialect)} ${renderColumnType(col.type, dialect)}`;
|
|
234
275
|
if (col.notNull) sql += " NOT NULL";
|
|
235
276
|
if (col.default !== null) sql += ` DEFAULT ${renderDefault(col.default, dialect)}`;
|
|
236
277
|
return sql;
|
|
@@ -252,23 +293,28 @@ function renderCreateTable(table, dialect) {
|
|
|
252
293
|
if (dialect === "postgresql" && c.type.kind === "enum") {
|
|
253
294
|
const typeName = enumTypeName(table.name, c.name);
|
|
254
295
|
const values = (c.type.meta.values ?? []).map(quoteLiteral).join(", ");
|
|
255
|
-
typeStmts.push(`CREATE TYPE ${quoteId(typeName)} AS ENUM (${values})`);
|
|
256
|
-
let def = `${quoteId(c.name)} ${quoteId(typeName)}`;
|
|
296
|
+
typeStmts.push(`CREATE TYPE ${quoteId(typeName, dialect)} AS ENUM (${values})`);
|
|
297
|
+
let def = `${quoteId(c.name, dialect)} ${quoteId(typeName, dialect)}`;
|
|
257
298
|
if (c.notNull) def += " NOT NULL";
|
|
258
299
|
if (c.default !== null) def += ` DEFAULT ${renderDefault(c.default, dialect)}`;
|
|
259
300
|
return def;
|
|
260
301
|
}
|
|
261
302
|
if (dialect === "postgresql" && isAutoIncrementPk(table, c)) {
|
|
262
|
-
return `${quoteId(c.name)} ${postgresSerialType(c.type.kind)}`;
|
|
303
|
+
return `${quoteId(c.name, dialect)} ${postgresSerialType(c.type.kind)}`;
|
|
304
|
+
}
|
|
305
|
+
if (dialect === "mysql" && isAutoIncrementPk(table, c)) {
|
|
306
|
+
return `${quoteId(c.name, dialect)} ${renderColumnType(c.type, dialect)} NOT NULL AUTO_INCREMENT`;
|
|
263
307
|
}
|
|
264
308
|
return renderColumnDef(c, dialect);
|
|
265
309
|
});
|
|
266
310
|
if (table.primaryKey.length > 0) {
|
|
267
|
-
cols.push(
|
|
311
|
+
cols.push(
|
|
312
|
+
`PRIMARY KEY (${table.primaryKey.map((c) => quoteId(c, dialect)).join(", ")})`
|
|
313
|
+
);
|
|
268
314
|
}
|
|
269
315
|
return [
|
|
270
316
|
...typeStmts,
|
|
271
|
-
`CREATE TABLE ${quoteId(table.name)} (
|
|
317
|
+
`CREATE TABLE ${quoteId(table.name, dialect)} (
|
|
272
318
|
${cols.join(",\n ")}
|
|
273
319
|
)`
|
|
274
320
|
];
|
|
@@ -278,23 +324,27 @@ function renderOperation(op, dialect) {
|
|
|
278
324
|
case "create_table":
|
|
279
325
|
return renderCreateTable(op.table, dialect);
|
|
280
326
|
case "drop_table":
|
|
281
|
-
return [`DROP TABLE ${quoteId(op.table.name)}`];
|
|
327
|
+
return [`DROP TABLE ${quoteId(op.table.name, dialect)}`];
|
|
282
328
|
case "rename_table":
|
|
283
|
-
return [`
|
|
329
|
+
return dialect === "mysql" ? [`RENAME TABLE ${quoteId(op.from, dialect)} TO ${quoteId(op.to, dialect)}`] : [
|
|
330
|
+
`ALTER TABLE ${quoteId(op.from, dialect)} RENAME TO ${quoteId(op.to, dialect)}`
|
|
331
|
+
];
|
|
284
332
|
case "add_column":
|
|
285
333
|
return [
|
|
286
|
-
`ALTER TABLE ${quoteId(op.table)} ADD COLUMN ${renderColumnDef(op.column, dialect)}`
|
|
334
|
+
`ALTER TABLE ${quoteId(op.table, dialect)} ADD COLUMN ${renderColumnDef(op.column, dialect)}`
|
|
287
335
|
];
|
|
288
336
|
case "drop_column":
|
|
289
|
-
return [
|
|
337
|
+
return [
|
|
338
|
+
`ALTER TABLE ${quoteId(op.table, dialect)} DROP COLUMN ${quoteId(op.column.name, dialect)}`
|
|
339
|
+
];
|
|
290
340
|
case "rename_column":
|
|
291
341
|
return [
|
|
292
|
-
`ALTER TABLE ${quoteId(op.table)} RENAME COLUMN ${quoteId(op.from)} TO ${quoteId(op.to)}`
|
|
342
|
+
`ALTER TABLE ${quoteId(op.table, dialect)} RENAME COLUMN ${quoteId(op.from, dialect)} TO ${quoteId(op.to, dialect)}`
|
|
293
343
|
];
|
|
294
344
|
case "alter_column":
|
|
295
345
|
return renderAlterColumn(op.table, op.to, dialect);
|
|
296
346
|
case "recreate_table":
|
|
297
|
-
return dialect === "sqlite" ? renderSqliteRebuild(op.from, op.to) :
|
|
347
|
+
return dialect === "sqlite" ? renderSqliteRebuild(op.from, op.to) : renderTableDiff(op.from, op.to, dialect);
|
|
298
348
|
case "execute":
|
|
299
349
|
return [op.up];
|
|
300
350
|
}
|
|
@@ -304,34 +354,38 @@ function renderSqliteRebuild(from, to) {
|
|
|
304
354
|
const common = Object.keys(to.columns).filter((c) => c in from.columns);
|
|
305
355
|
const cols = Object.values(to.columns).map((c) => renderColumnDef(c, "sqlite"));
|
|
306
356
|
if (to.primaryKey.length > 0) {
|
|
307
|
-
cols.push(
|
|
357
|
+
cols.push(
|
|
358
|
+
`PRIMARY KEY (${to.primaryKey.map((c) => quoteId(c, "sqlite")).join(", ")})`
|
|
359
|
+
);
|
|
308
360
|
}
|
|
309
|
-
const commonSql = common.map(quoteId).join(", ");
|
|
361
|
+
const commonSql = common.map((c) => quoteId(c, "sqlite")).join(", ");
|
|
310
362
|
return [
|
|
311
363
|
"PRAGMA foreign_keys=off",
|
|
312
|
-
`CREATE TABLE ${quoteId(tmp)} (
|
|
364
|
+
`CREATE TABLE ${quoteId(tmp, "sqlite")} (
|
|
313
365
|
${cols.join(",\n ")}
|
|
314
366
|
)`,
|
|
315
|
-
common.length > 0 ? `INSERT INTO ${quoteId(tmp)} (${commonSql}) SELECT ${commonSql} FROM ${quoteId(from.name)}` : `-- no common columns to copy from ${from.name}`,
|
|
316
|
-
`DROP TABLE ${quoteId(from.name)}`,
|
|
317
|
-
`ALTER TABLE ${quoteId(tmp)} RENAME TO ${quoteId(to.name)}`,
|
|
367
|
+
common.length > 0 ? `INSERT INTO ${quoteId(tmp, "sqlite")} (${commonSql}) SELECT ${commonSql} FROM ${quoteId(from.name, "sqlite")}` : `-- no common columns to copy from ${from.name}`,
|
|
368
|
+
`DROP TABLE ${quoteId(from.name, "sqlite")}`,
|
|
369
|
+
`ALTER TABLE ${quoteId(tmp, "sqlite")} RENAME TO ${quoteId(to.name, "sqlite")}`,
|
|
318
370
|
"PRAGMA foreign_keys=on"
|
|
319
371
|
];
|
|
320
372
|
}
|
|
321
|
-
function
|
|
373
|
+
function renderTableDiff(from, to, dialect) {
|
|
322
374
|
const stmts = [];
|
|
323
375
|
for (const [name, col] of Object.entries(to.columns)) {
|
|
324
376
|
if (!(name in from.columns)) {
|
|
325
377
|
stmts.push(
|
|
326
|
-
`ALTER TABLE ${quoteId(to.name)} ADD COLUMN ${renderColumnDef(col,
|
|
378
|
+
`ALTER TABLE ${quoteId(to.name, dialect)} ADD COLUMN ${renderColumnDef(col, dialect)}`
|
|
327
379
|
);
|
|
328
380
|
} else {
|
|
329
|
-
stmts.push(...renderAlterColumn(to.name, col,
|
|
381
|
+
stmts.push(...renderAlterColumn(to.name, col, dialect));
|
|
330
382
|
}
|
|
331
383
|
}
|
|
332
384
|
for (const name of Object.keys(from.columns)) {
|
|
333
385
|
if (!(name in to.columns)) {
|
|
334
|
-
stmts.push(
|
|
386
|
+
stmts.push(
|
|
387
|
+
`ALTER TABLE ${quoteId(to.name, dialect)} DROP COLUMN ${quoteId(name, dialect)}`
|
|
388
|
+
);
|
|
335
389
|
}
|
|
336
390
|
}
|
|
337
391
|
return stmts;
|
|
@@ -339,11 +393,16 @@ function renderPostgresTableDiff(from, to) {
|
|
|
339
393
|
function renderAlterColumn(table, to, dialect) {
|
|
340
394
|
if (dialect === "sqlite") {
|
|
341
395
|
throw new Error(
|
|
342
|
-
`alter_column on SQLite needs
|
|
396
|
+
`alter_column on SQLite needs a table-rebuild (recreate_table); column ${table}.${to.name}`
|
|
343
397
|
);
|
|
344
398
|
}
|
|
345
|
-
|
|
346
|
-
|
|
399
|
+
if (dialect === "mysql") {
|
|
400
|
+
return [
|
|
401
|
+
`ALTER TABLE ${quoteId(table, dialect)} MODIFY COLUMN ${renderColumnDef(to, dialect)}`
|
|
402
|
+
];
|
|
403
|
+
}
|
|
404
|
+
const t = quoteId(table, dialect);
|
|
405
|
+
const c = quoteId(to.name, dialect);
|
|
347
406
|
const stmts = [
|
|
348
407
|
`ALTER TABLE ${t} ALTER COLUMN ${c} TYPE ${renderColumnType(to.type, dialect)}`
|
|
349
408
|
];
|
|
@@ -634,6 +693,112 @@ var MigrationRunner = class {
|
|
|
634
693
|
return reverted;
|
|
635
694
|
}
|
|
636
695
|
};
|
|
696
|
+
function quoteIdent(name, dialect) {
|
|
697
|
+
return dialect === "mysql" ? `\`${name.replace(/`/g, "``")}\`` : `"${name.replace(/"/g, '""')}"`;
|
|
698
|
+
}
|
|
699
|
+
function placeholder(index, dialect) {
|
|
700
|
+
return dialect === "postgresql" ? `$${index}` : "?";
|
|
701
|
+
}
|
|
702
|
+
var AsyncMigrationRunner = class {
|
|
703
|
+
constructor(driver, dialect) {
|
|
704
|
+
this.driver = driver;
|
|
705
|
+
this.dialect = dialect;
|
|
706
|
+
this.vt = quoteIdent(VERSION_TABLE, dialect);
|
|
707
|
+
}
|
|
708
|
+
driver;
|
|
709
|
+
dialect;
|
|
710
|
+
vt;
|
|
711
|
+
/** Create the version-tracking table if it does not exist. */
|
|
712
|
+
async ensureVersionTable() {
|
|
713
|
+
await this.driver.execute(
|
|
714
|
+
`CREATE TABLE IF NOT EXISTS ${this.vt} (revision ${this.textType()} PRIMARY KEY, applied_at ${this.textType()} NOT NULL, down_revision ${this.textType()} NOT NULL)`,
|
|
715
|
+
[]
|
|
716
|
+
);
|
|
717
|
+
}
|
|
718
|
+
/** A portable "text" column type for the version table. */
|
|
719
|
+
textType() {
|
|
720
|
+
return this.dialect === "mysql" ? "VARCHAR(255)" : "TEXT";
|
|
721
|
+
}
|
|
722
|
+
/** The set of applied revision ids. */
|
|
723
|
+
async applied() {
|
|
724
|
+
await this.ensureVersionTable();
|
|
725
|
+
const { rows } = await this.driver.execute(`SELECT revision FROM ${this.vt}`, []);
|
|
726
|
+
return new Set(rows.map((r) => String(r.revision)));
|
|
727
|
+
}
|
|
728
|
+
async runOps(ops) {
|
|
729
|
+
for (const op of ops) {
|
|
730
|
+
for (const stmt of renderOperation(op, this.dialect)) {
|
|
731
|
+
const trimmed = stmt.trim();
|
|
732
|
+
if (trimmed.length === 0 || trimmed.startsWith("--")) continue;
|
|
733
|
+
await this.driver.execute(stmt, []);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
async record(migration, appliedAt) {
|
|
738
|
+
const p = (i) => placeholder(i, this.dialect);
|
|
739
|
+
await this.driver.execute(
|
|
740
|
+
`INSERT INTO ${this.vt} (revision, applied_at, down_revision) VALUES (${p(1)}, ${p(2)}, ${p(3)})`,
|
|
741
|
+
[migration.revision, appliedAt, migration.downRevision.join(",")]
|
|
742
|
+
);
|
|
743
|
+
}
|
|
744
|
+
async forget(revision) {
|
|
745
|
+
await this.driver.execute(
|
|
746
|
+
`DELETE FROM ${this.vt} WHERE revision = ${placeholder(1, this.dialect)}`,
|
|
747
|
+
[revision]
|
|
748
|
+
);
|
|
749
|
+
}
|
|
750
|
+
/**
|
|
751
|
+
* Apply all pending migrations up to the head(s), in DAG order.
|
|
752
|
+
*
|
|
753
|
+
* @param migrations All known migrations.
|
|
754
|
+
* @param appliedAt Timestamp string to stamp on each applied revision.
|
|
755
|
+
* @returns The revision ids that were applied this run.
|
|
756
|
+
*/
|
|
757
|
+
async upgrade(migrations, appliedAt) {
|
|
758
|
+
const done = await this.applied();
|
|
759
|
+
const ordered = topoOrder(migrations);
|
|
760
|
+
const ran = [];
|
|
761
|
+
for (const migration of ordered) {
|
|
762
|
+
if (done.has(migration.revision)) continue;
|
|
763
|
+
const op = new Op();
|
|
764
|
+
migration.up(op);
|
|
765
|
+
await this.runOps(op.operations);
|
|
766
|
+
await this.record(migration, appliedAt);
|
|
767
|
+
ran.push(migration.revision);
|
|
768
|
+
}
|
|
769
|
+
return ran;
|
|
770
|
+
}
|
|
771
|
+
/**
|
|
772
|
+
* Revert the last `steps` applied migrations (default 1), newest first.
|
|
773
|
+
*
|
|
774
|
+
* @param migrations All known migrations.
|
|
775
|
+
* @param steps How many applied revisions to roll back.
|
|
776
|
+
* @returns The revision ids that were reverted.
|
|
777
|
+
*/
|
|
778
|
+
async downgrade(migrations, steps = 1) {
|
|
779
|
+
const done = await this.applied();
|
|
780
|
+
const ordered = topoOrder(migrations).filter((m) => done.has(m.revision));
|
|
781
|
+
const toRevert = ordered.slice(-steps).reverse();
|
|
782
|
+
const reverted = [];
|
|
783
|
+
for (const migration of toRevert) {
|
|
784
|
+
const op = new Op();
|
|
785
|
+
try {
|
|
786
|
+
migration.down(op);
|
|
787
|
+
} catch {
|
|
788
|
+
op.operations.length = 0;
|
|
789
|
+
}
|
|
790
|
+
if (op.operations.length === 0) {
|
|
791
|
+
const upOp = new Op();
|
|
792
|
+
migration.up(upOp);
|
|
793
|
+
op.operations.push(...invertAll(upOp.operations));
|
|
794
|
+
}
|
|
795
|
+
await this.runOps(op.operations);
|
|
796
|
+
await this.forget(migration.revision);
|
|
797
|
+
reverted.push(migration.revision);
|
|
798
|
+
}
|
|
799
|
+
return reverted;
|
|
800
|
+
}
|
|
801
|
+
};
|
|
637
802
|
|
|
638
803
|
// src/migrations/introspect.ts
|
|
639
804
|
function sqliteAffinity(declared) {
|
|
@@ -1123,6 +1288,7 @@ function runMigrationCli(argv, config) {
|
|
|
1123
1288
|
}
|
|
1124
1289
|
}
|
|
1125
1290
|
|
|
1291
|
+
exports.AsyncMigrationRunner = AsyncMigrationRunner;
|
|
1126
1292
|
exports.CyclicMigrationGraph = CyclicMigrationGraph;
|
|
1127
1293
|
exports.IrreversibleMigration = IrreversibleMigration;
|
|
1128
1294
|
exports.MigrationRunner = MigrationRunner;
|