@fragno-dev/db 0.0.1 → 0.1.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/.turbo/turbo-build.log +137 -13
- package/.turbo/turbo-test.log +36 -0
- package/CHANGELOG.md +7 -0
- package/dist/adapters/adapters.d.ts +18 -0
- package/dist/adapters/adapters.d.ts.map +1 -0
- package/dist/adapters/drizzle/drizzle-adapter.d.ts +21 -0
- package/dist/adapters/drizzle/drizzle-adapter.d.ts.map +1 -0
- package/dist/adapters/drizzle/drizzle-adapter.js +62 -0
- package/dist/adapters/drizzle/drizzle-adapter.js.map +1 -0
- package/dist/adapters/drizzle/drizzle-query.d.ts +17 -0
- package/dist/adapters/drizzle/drizzle-query.d.ts.map +1 -0
- package/dist/adapters/drizzle/drizzle-query.js +139 -0
- package/dist/adapters/drizzle/drizzle-query.js.map +1 -0
- package/dist/adapters/drizzle/drizzle-uow-compiler.d.ts +9 -0
- package/dist/adapters/drizzle/drizzle-uow-compiler.d.ts.map +1 -0
- package/dist/adapters/drizzle/drizzle-uow-compiler.js +300 -0
- package/dist/adapters/drizzle/drizzle-uow-compiler.js.map +1 -0
- package/dist/adapters/drizzle/drizzle-uow-decoder.js +82 -0
- package/dist/adapters/drizzle/drizzle-uow-decoder.js.map +1 -0
- package/dist/adapters/drizzle/drizzle-uow-executor.js +125 -0
- package/dist/adapters/drizzle/drizzle-uow-executor.js.map +1 -0
- package/dist/adapters/drizzle/generate.js +273 -0
- package/dist/adapters/drizzle/generate.js.map +1 -0
- package/dist/adapters/drizzle/join-column-utils.js +28 -0
- package/dist/adapters/drizzle/join-column-utils.js.map +1 -0
- package/dist/adapters/drizzle/shared.js +11 -0
- package/dist/adapters/drizzle/shared.js.map +1 -0
- package/dist/adapters/kysely/kysely-adapter.d.ts +23 -0
- package/dist/adapters/kysely/kysely-adapter.d.ts.map +1 -0
- package/dist/adapters/kysely/kysely-adapter.js +119 -0
- package/dist/adapters/kysely/kysely-adapter.js.map +1 -0
- package/dist/adapters/kysely/kysely-query-builder.js +306 -0
- package/dist/adapters/kysely/kysely-query-builder.js.map +1 -0
- package/dist/adapters/kysely/kysely-query-compiler.js +67 -0
- package/dist/adapters/kysely/kysely-query-compiler.js.map +1 -0
- package/dist/adapters/kysely/kysely-query.js +158 -0
- package/dist/adapters/kysely/kysely-query.js.map +1 -0
- package/dist/adapters/kysely/kysely-uow-compiler.js +139 -0
- package/dist/adapters/kysely/kysely-uow-compiler.js.map +1 -0
- package/dist/adapters/kysely/kysely-uow-executor.js +89 -0
- package/dist/adapters/kysely/kysely-uow-executor.js.map +1 -0
- package/dist/adapters/kysely/migration/execute.js +176 -0
- package/dist/adapters/kysely/migration/execute.js.map +1 -0
- package/dist/fragment.d.ts +54 -0
- package/dist/fragment.d.ts.map +1 -0
- package/dist/fragment.js +92 -0
- package/dist/fragment.js.map +1 -0
- package/dist/id.d.ts +2 -0
- package/dist/migration-engine/auto-from-schema.js +116 -0
- package/dist/migration-engine/auto-from-schema.js.map +1 -0
- package/dist/migration-engine/create.d.ts +41 -0
- package/dist/migration-engine/create.d.ts.map +1 -0
- package/dist/migration-engine/create.js +58 -0
- package/dist/migration-engine/create.js.map +1 -0
- package/dist/migration-engine/shared.d.ts +90 -0
- package/dist/migration-engine/shared.d.ts.map +1 -0
- package/dist/migration-engine/shared.js +8 -0
- package/dist/migration-engine/shared.js.map +1 -0
- package/dist/mod.d.ts +55 -2
- package/dist/mod.d.ts.map +1 -1
- package/dist/mod.js +111 -2
- package/dist/mod.js.map +1 -1
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/column-builder.js +108 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/column-builder.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/column.js +55 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/column.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/entity.js +18 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/entity.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/pg-core/columns/common.js +183 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/pg-core/columns/common.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/pg-core/columns/enum.js +58 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/pg-core/columns/enum.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/pg-core/foreign-keys.js +68 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/pg-core/foreign-keys.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/pg-core/unique-constraint.js +56 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/pg-core/unique-constraint.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/pg-core/utils/array.js +65 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/pg-core/utils/array.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/sql/expressions/conditions.js +81 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/sql/expressions/conditions.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/sql/expressions/select.js +13 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/sql/expressions/select.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/sql/functions/aggregate.js +10 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/sql/functions/aggregate.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/sql/sql.js +372 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/sql/sql.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/subquery.js +23 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/subquery.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/table.js +62 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/table.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/table.utils.js +6 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/table.utils.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/tracing-utils.js +8 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/tracing-utils.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/tracing.js +8 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/tracing.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/view-common.js +6 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/view-common.js.map +1 -0
- package/dist/query/condition-builder.d.ts +41 -0
- package/dist/query/condition-builder.d.ts.map +1 -0
- package/dist/query/condition-builder.js +93 -0
- package/dist/query/condition-builder.js.map +1 -0
- package/dist/query/cursor.d.ts +88 -0
- package/dist/query/cursor.d.ts.map +1 -0
- package/dist/query/cursor.js +103 -0
- package/dist/query/cursor.js.map +1 -0
- package/dist/query/orm/orm.d.ts +18 -0
- package/dist/query/orm/orm.d.ts.map +1 -0
- package/dist/query/orm/orm.js +48 -0
- package/dist/query/orm/orm.js.map +1 -0
- package/dist/query/query.d.ts +79 -0
- package/dist/query/query.d.ts.map +1 -0
- package/dist/query/query.js +1 -0
- package/dist/query/result-transform.js +155 -0
- package/dist/query/result-transform.js.map +1 -0
- package/dist/query/unit-of-work.d.ts +435 -0
- package/dist/query/unit-of-work.d.ts.map +1 -0
- package/dist/query/unit-of-work.js +549 -0
- package/dist/query/unit-of-work.js.map +1 -0
- package/dist/schema/create.d.ts +273 -116
- package/dist/schema/create.d.ts.map +1 -1
- package/dist/schema/create.js +410 -222
- package/dist/schema/create.js.map +1 -1
- package/dist/schema/serialize.js +101 -0
- package/dist/schema/serialize.js.map +1 -0
- package/dist/schema-generator/schema-generator.d.ts +15 -0
- package/dist/schema-generator/schema-generator.d.ts.map +1 -0
- package/dist/shared/providers.d.ts +6 -0
- package/dist/shared/providers.d.ts.map +1 -0
- package/dist/util/import-generator.js +26 -0
- package/dist/util/import-generator.js.map +1 -0
- package/dist/util/parse.js +15 -0
- package/dist/util/parse.js.map +1 -0
- package/dist/util/types.d.ts +8 -0
- package/dist/util/types.d.ts.map +1 -0
- package/package.json +63 -2
- package/src/adapters/adapters.ts +22 -0
- package/src/adapters/drizzle/drizzle-adapter-pglite.test.ts +433 -0
- package/src/adapters/drizzle/drizzle-adapter.test.ts +122 -0
- package/src/adapters/drizzle/drizzle-adapter.ts +118 -0
- package/src/adapters/drizzle/drizzle-query.ts +234 -0
- package/src/adapters/drizzle/drizzle-uow-compiler.test.ts +1084 -0
- package/src/adapters/drizzle/drizzle-uow-compiler.ts +546 -0
- package/src/adapters/drizzle/drizzle-uow-decoder.ts +165 -0
- package/src/adapters/drizzle/drizzle-uow-executor.ts +213 -0
- package/src/adapters/drizzle/generate.test.ts +643 -0
- package/src/adapters/drizzle/generate.ts +481 -0
- package/src/adapters/drizzle/join-column-utils.test.ts +79 -0
- package/src/adapters/drizzle/join-column-utils.ts +39 -0
- package/src/adapters/drizzle/migrate-drizzle.test.ts +226 -0
- package/src/adapters/drizzle/shared.ts +22 -0
- package/src/adapters/drizzle/test-utils.ts +56 -0
- package/src/adapters/kysely/kysely-adapter-pglite.test.ts +789 -0
- package/src/adapters/kysely/kysely-adapter.ts +196 -0
- package/src/adapters/kysely/kysely-query-builder.test.ts +1344 -0
- package/src/adapters/kysely/kysely-query-builder.ts +611 -0
- package/src/adapters/kysely/kysely-query-compiler.ts +124 -0
- package/src/adapters/kysely/kysely-query.ts +254 -0
- package/src/adapters/kysely/kysely-uow-compiler.test.ts +916 -0
- package/src/adapters/kysely/kysely-uow-compiler.ts +271 -0
- package/src/adapters/kysely/kysely-uow-executor.ts +149 -0
- package/src/adapters/kysely/kysely-uow-joins.test.ts +811 -0
- package/src/adapters/kysely/migration/execute-mysql.test.ts +1173 -0
- package/src/adapters/kysely/migration/execute-postgres.test.ts +2657 -0
- package/src/adapters/kysely/migration/execute.ts +382 -0
- package/src/adapters/kysely/migration/kysely-migrator.test.ts +197 -0
- package/src/fragment.test.ts +287 -0
- package/src/fragment.ts +198 -0
- package/src/migration-engine/auto-from-schema.test.ts +118 -58
- package/src/migration-engine/auto-from-schema.ts +103 -32
- package/src/migration-engine/create.test.ts +34 -46
- package/src/migration-engine/create.ts +41 -26
- package/src/migration-engine/shared.ts +26 -6
- package/src/mod.ts +197 -1
- package/src/query/condition-builder.test.ts +379 -0
- package/src/query/condition-builder.ts +294 -0
- package/src/query/cursor.test.ts +296 -0
- package/src/query/cursor.ts +147 -0
- package/src/query/orm/orm.ts +92 -0
- package/src/query/query-type.test.ts +429 -0
- package/src/query/query.ts +200 -0
- package/src/query/result-transform.test.ts +795 -0
- package/src/query/result-transform.ts +247 -0
- package/src/query/unit-of-work-types.test.ts +192 -0
- package/src/query/unit-of-work.test.ts +947 -0
- package/src/query/unit-of-work.ts +1199 -0
- package/src/schema/create.test.ts +653 -110
- package/src/schema/create.ts +708 -337
- package/src/schema/serialize.test.ts +559 -0
- package/src/schema/serialize.ts +359 -0
- package/src/schema-generator/schema-generator.ts +12 -0
- package/src/shared/config.ts +0 -8
- package/src/util/import-generator.ts +28 -0
- package/src/util/parse.ts +16 -0
- package/src/util/types.ts +4 -0
- package/tsconfig.json +1 -1
- package/tsdown.config.ts +11 -1
- package/vitest.config.ts +3 -0
- /package/dist/{cuid.js → id.js} +0 -0
- /package/src/{cuid.ts → id.ts} +0 -0
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type ColumnBuilderCallback,
|
|
3
|
+
type Compilable,
|
|
4
|
+
type CreateTableBuilder,
|
|
5
|
+
type Kysely,
|
|
6
|
+
type RawBuilder,
|
|
7
|
+
sql,
|
|
8
|
+
} from "kysely";
|
|
9
|
+
import {
|
|
10
|
+
type CustomOperation,
|
|
11
|
+
isUpdated,
|
|
12
|
+
type ColumnOperation,
|
|
13
|
+
type MigrationOperation,
|
|
14
|
+
type ColumnInfo,
|
|
15
|
+
} from "../../../migration-engine/shared";
|
|
16
|
+
import type { SQLProvider } from "../../../shared/providers";
|
|
17
|
+
import { schemaToDBType } from "../../../schema/serialize";
|
|
18
|
+
import type { KyselyConfig } from "../kysely-adapter";
|
|
19
|
+
|
|
20
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
21
|
+
type KyselyAny = Kysely<any>;
|
|
22
|
+
|
|
23
|
+
export type ExecuteNode = Compilable & {
|
|
24
|
+
execute(): Promise<unknown>;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const errors = {
|
|
28
|
+
IdColumnUpdate:
|
|
29
|
+
"ID columns cannot be updated. Not every database supports updating primary keys and often requires workarounds.",
|
|
30
|
+
SQLiteUpdateColumn: "SQLite doesn't support updating columns. Recreate the table instead.",
|
|
31
|
+
SQLiteUpdateForeignKeys:
|
|
32
|
+
"SQLite doesn't support modifying foreign keys directly. Use `recreate-table` instead.",
|
|
33
|
+
} as const;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Returns the appropriate foreign key action based on the provider.
|
|
37
|
+
* MSSQL doesn't support RESTRICT, so we use NO ACTION (functionally equivalent).
|
|
38
|
+
*/
|
|
39
|
+
function getForeignKeyAction(provider: SQLProvider): "restrict" | "no action" {
|
|
40
|
+
return provider === "mssql" ? "no action" : "restrict";
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Generates MSSQL default constraint name following the DF_tableName_columnName pattern.
|
|
45
|
+
*/
|
|
46
|
+
function getMssqlDefaultConstraintName(tableName: string, columnName: string): string {
|
|
47
|
+
const MSSQL_DEFAULT_CONSTRAINT_PREFIX = "DF" as const;
|
|
48
|
+
return `${MSSQL_DEFAULT_CONSTRAINT_PREFIX}_${tableName}_${columnName}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function createUniqueIndex(
|
|
52
|
+
db: KyselyAny,
|
|
53
|
+
name: string,
|
|
54
|
+
tableName: string,
|
|
55
|
+
cols: string[],
|
|
56
|
+
provider: SQLProvider,
|
|
57
|
+
) {
|
|
58
|
+
const query = db.schema.createIndex(name).on(tableName).columns(cols).unique();
|
|
59
|
+
|
|
60
|
+
if (provider === "mssql") {
|
|
61
|
+
// MSSQL: ignore null values in unique indexes by default
|
|
62
|
+
return query.where((b) => {
|
|
63
|
+
return b.and(cols.map((col) => b(col, "is not", null)));
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return query;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function dropUniqueIndex(db: KyselyAny, name: string, tableName: string, provider: SQLProvider) {
|
|
71
|
+
let query = db.schema.dropIndex(name).ifExists();
|
|
72
|
+
|
|
73
|
+
if (provider === "cockroachdb") {
|
|
74
|
+
query = query.cascade();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (provider === "mssql") {
|
|
78
|
+
query = query.on(tableName);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return query;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function executeColumn(
|
|
85
|
+
tableName: string,
|
|
86
|
+
operation: ColumnOperation,
|
|
87
|
+
config: KyselyConfig,
|
|
88
|
+
): ExecuteNode[] {
|
|
89
|
+
const { db, provider } = config;
|
|
90
|
+
const next = () => db.schema.alterTable(tableName);
|
|
91
|
+
const results: ExecuteNode[] = [];
|
|
92
|
+
|
|
93
|
+
switch (operation.type) {
|
|
94
|
+
case "rename-column":
|
|
95
|
+
results.push(next().renameColumn(operation.from, operation.to));
|
|
96
|
+
return results;
|
|
97
|
+
|
|
98
|
+
case "drop-column":
|
|
99
|
+
results.push(next().dropColumn(operation.name));
|
|
100
|
+
|
|
101
|
+
return results;
|
|
102
|
+
case "create-column": {
|
|
103
|
+
const col = operation.value;
|
|
104
|
+
|
|
105
|
+
results.push(
|
|
106
|
+
next().addColumn(
|
|
107
|
+
col.name,
|
|
108
|
+
sql.raw(schemaToDBType(col, provider)),
|
|
109
|
+
getColumnBuilderCallback(col, provider),
|
|
110
|
+
),
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
return results;
|
|
114
|
+
}
|
|
115
|
+
case "update-column": {
|
|
116
|
+
const col = operation.value;
|
|
117
|
+
|
|
118
|
+
if (col.role === "external-id" || col.role === "internal-id") {
|
|
119
|
+
throw new Error(errors.IdColumnUpdate);
|
|
120
|
+
}
|
|
121
|
+
if (provider === "sqlite") {
|
|
122
|
+
throw new Error(errors.SQLiteUpdateColumn);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (!isUpdated(operation)) {
|
|
126
|
+
return results;
|
|
127
|
+
}
|
|
128
|
+
if (provider === "mysql") {
|
|
129
|
+
results.push(
|
|
130
|
+
next().modifyColumn(
|
|
131
|
+
operation.name,
|
|
132
|
+
sql.raw(schemaToDBType(col, provider)),
|
|
133
|
+
getColumnBuilderCallback(col, provider),
|
|
134
|
+
),
|
|
135
|
+
);
|
|
136
|
+
return results;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// MSSQL requires dropping and recreating default constraints when changing data type or default value
|
|
140
|
+
const mssqlRecreateDefaultConstraint = operation.updateDataType || operation.updateDefault;
|
|
141
|
+
|
|
142
|
+
if (provider === "mssql" && mssqlRecreateDefaultConstraint) {
|
|
143
|
+
results.push(rawToNode(db, mssqlDropDefaultConstraint(tableName, col.name)));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// TODO: We should maybe do some of these operations in a single query
|
|
147
|
+
|
|
148
|
+
if (operation.updateDataType) {
|
|
149
|
+
const dbType = sql.raw(schemaToDBType(col, provider));
|
|
150
|
+
|
|
151
|
+
if (provider === "postgresql" || provider === "cockroachdb") {
|
|
152
|
+
// PostgreSQL/CockroachDB: Use explicit USING clause for type conversion
|
|
153
|
+
results.push(
|
|
154
|
+
rawToNode(
|
|
155
|
+
db,
|
|
156
|
+
sql`ALTER TABLE ${sql.ref(tableName)} ALTER COLUMN ${sql.ref(operation.name)} TYPE ${dbType} USING (${sql.ref(operation.name)}::${dbType})`,
|
|
157
|
+
),
|
|
158
|
+
);
|
|
159
|
+
} else {
|
|
160
|
+
results.push(next().alterColumn(operation.name, (b) => b.setDataType(dbType)));
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (operation.updateNullable) {
|
|
165
|
+
results.push(
|
|
166
|
+
next().alterColumn(operation.name, (build) =>
|
|
167
|
+
col.isNullable ? build.dropNotNull() : build.setNotNull(),
|
|
168
|
+
),
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (provider === "mssql" && mssqlRecreateDefaultConstraint) {
|
|
173
|
+
const defaultValue = defaultValueToDB(col, provider);
|
|
174
|
+
|
|
175
|
+
if (defaultValue) {
|
|
176
|
+
const constraintName = getMssqlDefaultConstraintName(tableName, col.name);
|
|
177
|
+
|
|
178
|
+
results.push(
|
|
179
|
+
rawToNode(
|
|
180
|
+
db,
|
|
181
|
+
sql`ALTER TABLE ${sql.ref(tableName)} ADD CONSTRAINT ${sql.ref(constraintName)} DEFAULT ${defaultValue} FOR ${sql.ref(col.name)}`,
|
|
182
|
+
),
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
} else if (operation.updateDefault) {
|
|
186
|
+
const defaultValue = defaultValueToDB(col, provider);
|
|
187
|
+
|
|
188
|
+
results.push(
|
|
189
|
+
next().alterColumn(operation.name, (build) => {
|
|
190
|
+
if (!defaultValue) {
|
|
191
|
+
return build.dropDefault();
|
|
192
|
+
}
|
|
193
|
+
return build.setDefault(defaultValue);
|
|
194
|
+
}),
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return results;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export function execute(
|
|
204
|
+
operation: MigrationOperation,
|
|
205
|
+
config: KyselyConfig,
|
|
206
|
+
onCustomNode: (op: CustomOperation) => ExecuteNode | ExecuteNode[],
|
|
207
|
+
): ExecuteNode | ExecuteNode[] {
|
|
208
|
+
const { db, provider } = config;
|
|
209
|
+
|
|
210
|
+
function createTable(tableName: string, columns: ColumnInfo[]) {
|
|
211
|
+
let builder = db.schema.createTable(tableName) as CreateTableBuilder<string, string>;
|
|
212
|
+
|
|
213
|
+
// Add columns from the column info array
|
|
214
|
+
for (const columnInfo of columns) {
|
|
215
|
+
builder = builder.addColumn(
|
|
216
|
+
columnInfo.name,
|
|
217
|
+
sql.raw(schemaToDBType(columnInfo, provider)),
|
|
218
|
+
getColumnBuilderCallback(columnInfo, provider),
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return builder;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
switch (operation.type) {
|
|
226
|
+
case "create-table":
|
|
227
|
+
return createTable(operation.name, operation.columns);
|
|
228
|
+
case "rename-table":
|
|
229
|
+
if (provider === "mssql") {
|
|
230
|
+
return rawToNode(
|
|
231
|
+
db,
|
|
232
|
+
sql`EXEC sp_rename ${sql.lit(operation.from)}, ${sql.lit(operation.to)}`,
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return db.schema.alterTable(operation.from).renameTo(operation.to);
|
|
237
|
+
case "alter-table": {
|
|
238
|
+
const results: ExecuteNode[] = [];
|
|
239
|
+
|
|
240
|
+
for (const op of operation.value) {
|
|
241
|
+
results.push(...executeColumn(operation.name, op, config));
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return results;
|
|
245
|
+
}
|
|
246
|
+
case "drop-table":
|
|
247
|
+
return db.schema.dropTable(operation.name);
|
|
248
|
+
case "custom":
|
|
249
|
+
return onCustomNode(operation);
|
|
250
|
+
case "add-foreign-key": {
|
|
251
|
+
if (provider === "sqlite") {
|
|
252
|
+
throw new Error(errors.SQLiteUpdateForeignKeys);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const { table, value } = operation;
|
|
256
|
+
const action = getForeignKeyAction(provider);
|
|
257
|
+
|
|
258
|
+
return db.schema
|
|
259
|
+
.alterTable(table)
|
|
260
|
+
.addForeignKeyConstraint(
|
|
261
|
+
value.name,
|
|
262
|
+
value.columns,
|
|
263
|
+
value.referencedTable,
|
|
264
|
+
value.referencedColumns,
|
|
265
|
+
(b) => b.onUpdate(action).onDelete(action),
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
case "drop-foreign-key": {
|
|
269
|
+
if (provider === "sqlite") {
|
|
270
|
+
throw new Error(errors.SQLiteUpdateForeignKeys);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const { table, name } = operation;
|
|
274
|
+
let query = db.schema.alterTable(table).dropConstraint(name);
|
|
275
|
+
|
|
276
|
+
// MySQL doesn't support IF EXISTS for dropping constraints
|
|
277
|
+
if (provider !== "mysql") {
|
|
278
|
+
query = query.ifExists();
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return query;
|
|
282
|
+
}
|
|
283
|
+
case "add-index": {
|
|
284
|
+
if (operation.unique) {
|
|
285
|
+
return createUniqueIndex(db, operation.name, operation.table, operation.columns, provider);
|
|
286
|
+
}
|
|
287
|
+
return db.schema.createIndex(operation.name).on(operation.table).columns(operation.columns);
|
|
288
|
+
}
|
|
289
|
+
case "drop-index": {
|
|
290
|
+
return dropUniqueIndex(db, operation.name, operation.table, provider);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// ============================================================================
|
|
296
|
+
// Helper Functions
|
|
297
|
+
// ============================================================================
|
|
298
|
+
|
|
299
|
+
function getColumnBuilderCallback(col: ColumnInfo, provider: SQLProvider): ColumnBuilderCallback {
|
|
300
|
+
return (build) => {
|
|
301
|
+
if (!col.isNullable) {
|
|
302
|
+
build = build.notNull();
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Internal ID is the primary key with auto-increment
|
|
306
|
+
if (col.role === "internal-id") {
|
|
307
|
+
build = build.primaryKey();
|
|
308
|
+
// Auto-increment for internal ID
|
|
309
|
+
if (provider === "postgresql" || provider === "cockroachdb") {
|
|
310
|
+
// SERIAL/BIGSERIAL handles auto-increment
|
|
311
|
+
// Already handled in schemaToDBType
|
|
312
|
+
} else if (provider === "mysql") {
|
|
313
|
+
build = build.autoIncrement();
|
|
314
|
+
} else if (provider === "sqlite") {
|
|
315
|
+
build = build.autoIncrement();
|
|
316
|
+
} else if (provider === "mssql") {
|
|
317
|
+
build = build.identity();
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// External ID must be unique
|
|
322
|
+
if (col.role === "external-id") {
|
|
323
|
+
build = build.unique();
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const defaultValue = defaultValueToDB(col, provider);
|
|
327
|
+
if (defaultValue) {
|
|
328
|
+
build = build.defaultTo(defaultValue);
|
|
329
|
+
}
|
|
330
|
+
return build;
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function rawToNode(db: KyselyAny, raw: RawBuilder<unknown>): ExecuteNode {
|
|
335
|
+
return {
|
|
336
|
+
compile() {
|
|
337
|
+
return raw.compile(db);
|
|
338
|
+
},
|
|
339
|
+
execute() {
|
|
340
|
+
return raw.execute(db);
|
|
341
|
+
},
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function mssqlDropDefaultConstraint(tableName: string, columnName: string) {
|
|
346
|
+
return sql`
|
|
347
|
+
DECLARE @ConstraintName NVARCHAR(200);
|
|
348
|
+
|
|
349
|
+
SELECT @ConstraintName = dc.name
|
|
350
|
+
FROM sys.default_constraints dc
|
|
351
|
+
JOIN sys.columns c ON dc.parent_object_id = c.object_id AND dc.parent_column_id = c.column_id
|
|
352
|
+
JOIN sys.tables t ON t.object_id = c.object_id
|
|
353
|
+
JOIN sys.schemas s ON t.schema_id = s.schema_id
|
|
354
|
+
WHERE s.name = 'dbo' AND t.name = ${sql.lit(tableName)} AND c.name = ${sql.lit(columnName)};
|
|
355
|
+
|
|
356
|
+
IF @ConstraintName IS NOT NULL
|
|
357
|
+
BEGIN
|
|
358
|
+
EXEC('ALTER TABLE [dbo].[' + ${sql.lit(tableName)} + '] DROP CONSTRAINT [' + @ConstraintName + ']');
|
|
359
|
+
END`;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function defaultValueToDB(column: ColumnInfo, provider: SQLProvider) {
|
|
363
|
+
const value = column.default;
|
|
364
|
+
if (!value) {
|
|
365
|
+
return undefined;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// MySQL doesn't support default values for TEXT columns
|
|
369
|
+
if (provider === "mysql" && column.type === "string") {
|
|
370
|
+
return undefined;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
if ("runtime" in value && value.runtime === "now") {
|
|
374
|
+
return sql`CURRENT_TIMESTAMP`;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if ("value" in value && value.value !== undefined) {
|
|
378
|
+
return sql.lit(value.value);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
return undefined;
|
|
382
|
+
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { Kysely, PostgresDialect } from "kysely";
|
|
2
|
+
import { describe, expect, beforeAll, test } from "vitest";
|
|
3
|
+
import { KyselyAdapter } from "../kysely-adapter";
|
|
4
|
+
import { column, idColumn, schema } from "../../../schema/create";
|
|
5
|
+
|
|
6
|
+
describe("KyselyMigrator", () => {
|
|
7
|
+
const testSchema = schema((s) => {
|
|
8
|
+
return s
|
|
9
|
+
.addTable("users", (t) => {
|
|
10
|
+
return t.addColumn("id", idColumn()).addColumn("name", column("string"));
|
|
11
|
+
})
|
|
12
|
+
.alterTable("users", (t) => {
|
|
13
|
+
return t
|
|
14
|
+
.addColumn("age", column("integer").nullable())
|
|
15
|
+
.createIndex("name_idx", ["name"])
|
|
16
|
+
.createIndex("age_idx", ["age"]);
|
|
17
|
+
})
|
|
18
|
+
.addTable("posts", (t) => {
|
|
19
|
+
return t
|
|
20
|
+
.addColumn("id", idColumn())
|
|
21
|
+
.addColumn("title", column("string"))
|
|
22
|
+
.addColumn("user_id", column("string"));
|
|
23
|
+
})
|
|
24
|
+
.addReference("author", {
|
|
25
|
+
type: "one",
|
|
26
|
+
from: { table: "posts", column: "user_id" },
|
|
27
|
+
to: { table: "users", column: "id" },
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
32
|
+
let db: Kysely<any>;
|
|
33
|
+
let adapter: KyselyAdapter;
|
|
34
|
+
|
|
35
|
+
beforeAll(async () => {
|
|
36
|
+
// Create a Kysely instance with a PostgresDialect, but not actually connected to a database
|
|
37
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
38
|
+
db = new Kysely({ dialect: new PostgresDialect({} as any) });
|
|
39
|
+
adapter = new KyselyAdapter({ db, provider: "postgresql" });
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("generate SQL for migration 0 -> 1 (create users table)", async () => {
|
|
43
|
+
const migrator = adapter.createMigrationEngine(testSchema, "test_namespace");
|
|
44
|
+
const preparedMigration = await migrator.prepareMigrationTo(1, {
|
|
45
|
+
updateSettings: true,
|
|
46
|
+
fromVersion: 0,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
expect(preparedMigration.operations.length).toBeGreaterThan(0);
|
|
50
|
+
const sql = preparedMigration.getSQL?.();
|
|
51
|
+
expect(sql).toBeDefined();
|
|
52
|
+
expect(sql).toMatchInlineSnapshot(`
|
|
53
|
+
"create table "fragno_db_settings" ("key" varchar(255) primary key, "value" text not null);
|
|
54
|
+
|
|
55
|
+
insert into "fragno_db_settings" ("key", "value") values ('test_namespace.schema_version', '1');
|
|
56
|
+
|
|
57
|
+
create table "users" ("id" varchar(30) not null unique, "name" text not null, "_internalId" bigserial not null primary key, "_version" integer default 0 not null);"
|
|
58
|
+
`);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("generate SQL for migration 0 -> 2 (create users table and add age)", async () => {
|
|
62
|
+
const migrator = adapter.createMigrationEngine(testSchema, "test_namespace");
|
|
63
|
+
const preparedMigration = await migrator.prepareMigrationTo(2, {
|
|
64
|
+
updateSettings: true,
|
|
65
|
+
fromVersion: 0,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
expect(preparedMigration.operations.length).toBeGreaterThan(0);
|
|
69
|
+
const sql = preparedMigration.getSQL?.();
|
|
70
|
+
expect(sql).toBeDefined();
|
|
71
|
+
expect(sql).toMatchInlineSnapshot(`
|
|
72
|
+
"create table "fragno_db_settings" ("key" varchar(255) primary key, "value" text not null);
|
|
73
|
+
|
|
74
|
+
insert into "fragno_db_settings" ("key", "value") values ('test_namespace.schema_version', '2');
|
|
75
|
+
|
|
76
|
+
create table "users" ("id" varchar(30) not null unique, "name" text not null, "_internalId" bigserial not null primary key, "_version" integer default 0 not null);
|
|
77
|
+
|
|
78
|
+
alter table "users" add column "age" integer;
|
|
79
|
+
|
|
80
|
+
create index "name_idx" on "users" ("name");
|
|
81
|
+
|
|
82
|
+
create index "age_idx" on "users" ("age");"
|
|
83
|
+
`);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("generate SQL for migration 1 -> 2 (add age column and indexes)", async () => {
|
|
87
|
+
const migrator = adapter.createMigrationEngine(testSchema, "test_namespace");
|
|
88
|
+
const preparedMigration = await migrator.prepareMigrationTo(2, {
|
|
89
|
+
updateSettings: true,
|
|
90
|
+
fromVersion: 1,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
expect(preparedMigration.operations.length).toBeGreaterThan(0);
|
|
94
|
+
const sql = preparedMigration.getSQL?.();
|
|
95
|
+
expect(sql).toBeDefined();
|
|
96
|
+
expect(sql).toMatchInlineSnapshot(`
|
|
97
|
+
"update "fragno_db_settings" set "value" = '2' where "key" = 'test_namespace.schema_version';
|
|
98
|
+
|
|
99
|
+
alter table "users" add column "age" integer;
|
|
100
|
+
|
|
101
|
+
create index "name_idx" on "users" ("name");
|
|
102
|
+
|
|
103
|
+
create index "age_idx" on "users" ("age");"
|
|
104
|
+
`);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("generate SQL for migration 0 -> 3 (full schema)", async () => {
|
|
108
|
+
const migrator = adapter.createMigrationEngine(testSchema, "test_namespace");
|
|
109
|
+
const preparedMigration = await migrator.prepareMigrationTo(3, {
|
|
110
|
+
updateSettings: true,
|
|
111
|
+
fromVersion: 0,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
expect(preparedMigration.operations.length).toBeGreaterThan(0);
|
|
115
|
+
const sql = preparedMigration.getSQL?.();
|
|
116
|
+
expect(sql).toBeDefined();
|
|
117
|
+
expect(sql).toMatchInlineSnapshot(`
|
|
118
|
+
"create table "fragno_db_settings" ("key" varchar(255) primary key, "value" text not null);
|
|
119
|
+
|
|
120
|
+
insert into "fragno_db_settings" ("key", "value") values ('test_namespace.schema_version', '3');
|
|
121
|
+
|
|
122
|
+
create table "users" ("id" varchar(30) not null unique, "name" text not null, "_internalId" bigserial not null primary key, "_version" integer default 0 not null);
|
|
123
|
+
|
|
124
|
+
alter table "users" add column "age" integer;
|
|
125
|
+
|
|
126
|
+
create index "name_idx" on "users" ("name");
|
|
127
|
+
|
|
128
|
+
create index "age_idx" on "users" ("age");
|
|
129
|
+
|
|
130
|
+
create table "posts" ("id" varchar(30) not null unique, "title" text not null, "user_id" text not null, "_internalId" bigserial not null primary key, "_version" integer default 0 not null);"
|
|
131
|
+
`);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("generate SQL for migration 2 -> 3 (add posts table)", async () => {
|
|
135
|
+
const migrator = adapter.createMigrationEngine(testSchema, "test_namespace");
|
|
136
|
+
const preparedMigration = await migrator.prepareMigrationTo(3, {
|
|
137
|
+
updateSettings: true,
|
|
138
|
+
fromVersion: 2,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
expect(preparedMigration.operations.length).toBeGreaterThan(0);
|
|
142
|
+
const sql = preparedMigration.getSQL?.();
|
|
143
|
+
expect(sql).toBeDefined();
|
|
144
|
+
expect(sql).toMatchInlineSnapshot(`
|
|
145
|
+
"update "fragno_db_settings" set "value" = '3' where "key" = 'test_namespace.schema_version';
|
|
146
|
+
|
|
147
|
+
create table "posts" ("id" varchar(30) not null unique, "title" text not null, "user_id" text not null, "_internalId" bigserial not null primary key, "_version" integer default 0 not null);"
|
|
148
|
+
`);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test("generate empty SQL for migration 2 -> 2 (no changes)", async () => {
|
|
152
|
+
const migrator = adapter.createMigrationEngine(testSchema, "test_namespace");
|
|
153
|
+
const preparedMigration = await migrator.prepareMigrationTo(2, {
|
|
154
|
+
updateSettings: true,
|
|
155
|
+
fromVersion: 2,
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
expect(preparedMigration.operations.length).toBe(0);
|
|
159
|
+
const sql = preparedMigration.getSQL?.();
|
|
160
|
+
expect(sql).toBeDefined();
|
|
161
|
+
expect(sql).toBe("");
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test("throw error for backward migration", async () => {
|
|
165
|
+
const migrator = adapter.createMigrationEngine(testSchema, "test_namespace");
|
|
166
|
+
|
|
167
|
+
await expect(
|
|
168
|
+
migrator.prepareMigrationTo(1, {
|
|
169
|
+
updateSettings: true,
|
|
170
|
+
fromVersion: 2,
|
|
171
|
+
}),
|
|
172
|
+
).rejects.toThrow("Cannot migrate backwards");
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test("throw error for version beyond schema", async () => {
|
|
176
|
+
const migrator = adapter.createMigrationEngine(testSchema, "test_namespace");
|
|
177
|
+
|
|
178
|
+
await expect(
|
|
179
|
+
migrator.prepareMigrationTo(10, {
|
|
180
|
+
updateSettings: true,
|
|
181
|
+
fromVersion: 0,
|
|
182
|
+
}),
|
|
183
|
+
).rejects.toThrow("schema only has version 4");
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test("getDefaultFileName returns correct format", () => {
|
|
187
|
+
const migrator = adapter.createMigrationEngine(testSchema, "test_namespace");
|
|
188
|
+
|
|
189
|
+
expect(migrator.getDefaultFileName).toBeDefined();
|
|
190
|
+
|
|
191
|
+
if (migrator.getDefaultFileName) {
|
|
192
|
+
const filename = migrator.getDefaultFileName("my-fragment", 0, 1);
|
|
193
|
+
// Should match format: YYYYMMDD_namespace_migration_0_to_1.sql
|
|
194
|
+
expect(filename).toMatch(/^\d{8}_my-fragment_migration_0_to_1\.sql$/);
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
});
|