@prisma-next/target-sqlite 0.13.0 → 0.14.0-dev.2
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/contract-free.d.mts +35 -2
- package/dist/contract-free.d.mts.map +1 -1
- package/dist/contract-free.mjs +3 -22
- package/dist/contract-free.mjs.map +1 -1
- package/dist/control.d.mts +5 -8
- package/dist/control.d.mts.map +1 -1
- package/dist/control.mjs +18 -20
- package/dist/control.mjs.map +1 -1
- package/dist/ddl-DrtjQMFK.mjs +68 -0
- package/dist/ddl-DrtjQMFK.mjs.map +1 -0
- package/dist/{descriptor-meta-Dxx2A6PT.mjs → descriptor-meta-DxmEeTJ-.mjs} +10 -3
- package/dist/descriptor-meta-DxmEeTJ-.mjs.map +1 -0
- package/dist/migration.d.mts +4 -46
- package/dist/migration.d.mts.map +1 -1
- package/dist/migration.mjs +4 -3
- package/dist/migration.mjs.map +1 -1
- package/dist/op-factory-call-DmdfD1yd.mjs +794 -0
- package/dist/op-factory-call-DmdfD1yd.mjs.map +1 -0
- package/dist/op-factory-call.d.mts +22 -12
- package/dist/op-factory-call.d.mts.map +1 -1
- package/dist/op-factory-call.mjs +1 -1
- package/dist/pack.mjs +1 -1
- package/dist/{planner-DSNDwQy9.mjs → planner-Ciq8p_dL.mjs} +80 -12
- package/dist/planner-Ciq8p_dL.mjs.map +1 -0
- package/dist/{planner-produced-sqlite-migration-DowV_vHw.mjs → planner-produced-sqlite-migration-0xPEm3R1.mjs} +9 -5
- package/dist/planner-produced-sqlite-migration-0xPEm3R1.mjs.map +1 -0
- package/dist/{planner-produced-sqlite-migration-C1yqJAiM.d.mts → planner-produced-sqlite-migration-CpgsY-M9.d.mts} +5 -4
- package/dist/planner-produced-sqlite-migration-CpgsY-M9.d.mts.map +1 -0
- package/dist/planner-produced-sqlite-migration.d.mts +1 -1
- package/dist/planner-produced-sqlite-migration.mjs +1 -1
- package/dist/planner.d.mts +5 -2
- package/dist/planner.d.mts.map +1 -1
- package/dist/planner.mjs +1 -1
- package/dist/render-ops-BDW2tUeR.mjs +22 -0
- package/dist/render-ops-BDW2tUeR.mjs.map +1 -0
- package/dist/render-ops.d.mts +2 -1
- package/dist/render-ops.d.mts.map +1 -1
- package/dist/render-ops.mjs +1 -1
- package/dist/runtime.d.mts +1 -0
- package/dist/runtime.d.mts.map +1 -1
- package/dist/runtime.mjs +2 -2
- package/dist/shared-Dhc8mLK1.d.mts.map +1 -1
- package/dist/{sqlite-contract-serializer-jcRu8aHh.mjs → sqlite-contract-serializer--iaDgC8e.mjs} +17 -6
- package/dist/sqlite-contract-serializer--iaDgC8e.mjs.map +1 -0
- package/dist/sqlite-migration-A0rwqPOG.mjs +92 -0
- package/dist/sqlite-migration-A0rwqPOG.mjs.map +1 -0
- package/dist/sqlite-migration-DVfhQwN_.d.mts +75 -0
- package/dist/sqlite-migration-DVfhQwN_.d.mts.map +1 -0
- package/package.json +18 -18
- package/src/contract-free/checks.ts +75 -0
- package/src/core/control-target.ts +4 -4
- package/src/core/errors.ts +28 -0
- package/src/core/migrations/issue-planner.ts +151 -8
- package/src/core/migrations/op-factory-call.ts +332 -45
- package/src/core/migrations/operations/columns.ts +32 -26
- package/src/core/migrations/operations/indexes.ts +31 -27
- package/src/core/migrations/operations/shared.ts +11 -3
- package/src/core/migrations/operations/tables.ts +39 -37
- package/src/core/migrations/planner-ddl-builders.ts +7 -16
- package/src/core/migrations/planner-produced-sqlite-migration.ts +8 -2
- package/src/core/migrations/planner-strategies.ts +3 -3
- package/src/core/migrations/planner.ts +14 -2
- package/src/core/migrations/render-ops.ts +37 -9
- package/src/core/migrations/runner.ts +16 -12
- package/src/core/migrations/sqlite-migration.ts +119 -1
- package/src/core/sqlite-contract-serializer.ts +5 -0
- package/src/core/sqlite-unbound-database.ts +30 -54
- package/src/exports/contract-free.ts +8 -0
- package/src/exports/migration.ts +8 -3
- package/dist/descriptor-meta-Dxx2A6PT.mjs.map +0 -1
- package/dist/descriptor-meta-runtime-BkXK3OjD.mjs +0 -12
- package/dist/descriptor-meta-runtime-BkXK3OjD.mjs.map +0 -1
- package/dist/op-factory-call-DymqdXQW.mjs +0 -279
- package/dist/op-factory-call-DymqdXQW.mjs.map +0 -1
- package/dist/planner-DSNDwQy9.mjs.map +0 -1
- package/dist/planner-produced-sqlite-migration-C1yqJAiM.d.mts.map +0 -1
- package/dist/planner-produced-sqlite-migration-DowV_vHw.mjs.map +0 -1
- package/dist/render-ops-CFRbJ3Yb.mjs +0 -8
- package/dist/render-ops-CFRbJ3Yb.mjs.map +0 -1
- package/dist/sqlite-contract-serializer-jcRu8aHh.mjs.map +0 -1
- package/dist/sqlite-migration-CUqgmzQH.mjs +0 -16
- package/dist/sqlite-migration-CUqgmzQH.mjs.map +0 -1
- package/dist/sqlite-migration-D4XGYzgQ.d.mts +0 -17
- package/dist/sqlite-migration-D4XGYzgQ.d.mts.map +0 -1
- package/dist/tables-CjB7vXCr.mjs +0 -412
- package/dist/tables-CjB7vXCr.mjs.map +0 -1
|
@@ -2,13 +2,11 @@
|
|
|
2
2
|
* SQLite migration IR: one concrete `*Call` class per pure factory under
|
|
3
3
|
* `operations/`, plus a shared `SqliteOpFactoryCallNode` abstract base.
|
|
4
4
|
*
|
|
5
|
-
* Each call class carries fully-resolved literal arguments
|
|
6
|
-
* `
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* TypeScript scaffold is fully self-contained and does not need access to a
|
|
11
|
-
* `storageTypes` map at runtime.
|
|
5
|
+
* Each call class carries fully-resolved literal arguments. `CreateTableCall`
|
|
6
|
+
* holds structured `DdlColumn[]` + `DdlTableConstraint[]` and lowers via the
|
|
7
|
+
* adapter's DDL path; other call classes carry flat SQL fragments. Codec /
|
|
8
|
+
* `typeRef` / default expansion happens upstream in the issue-planner /
|
|
9
|
+
* strategies, mirroring the Postgres `ColumnSpec` pattern.
|
|
12
10
|
*/
|
|
13
11
|
|
|
14
12
|
import { errorUnfilledPlaceholder } from '@prisma-next/errors/migration';
|
|
@@ -16,13 +14,25 @@ import type {
|
|
|
16
14
|
MigrationOperationClass,
|
|
17
15
|
SqlMigrationPlanOperation,
|
|
18
16
|
} from '@prisma-next/family-sql/control';
|
|
17
|
+
import type { ExecuteRequestLowerer, Lowerer } from '@prisma-next/family-sql/control-adapter';
|
|
19
18
|
import type { OpFactoryCall as FrameworkOpFactoryCall } from '@prisma-next/framework-components/control';
|
|
19
|
+
import type {
|
|
20
|
+
AnyDdlColumnDefault,
|
|
21
|
+
DdlColumn,
|
|
22
|
+
DdlTableConstraint,
|
|
23
|
+
} from '@prisma-next/sql-relational-core/ast';
|
|
20
24
|
import { type ImportRequirement, jsonToTsSource, TsExpression } from '@prisma-next/ts-render';
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
25
|
+
import { ifDefined } from '@prisma-next/utils/defined';
|
|
26
|
+
import { columnExistsAst, indexExistsAst, tableExistsAst } from '../../contract-free/checks';
|
|
27
|
+
import * as contractFreeDdl from '../../contract-free/ddl';
|
|
28
|
+
import { quoteIdentifier } from '../sql-utils';
|
|
29
|
+
import { addColumnExecuteSql, dropColumnExecuteSql } from './operations/columns';
|
|
23
30
|
import type { SqliteColumnSpec, SqliteIndexSpec, SqliteTableSpec } from './operations/shared';
|
|
24
|
-
import {
|
|
31
|
+
import { step } from './operations/shared';
|
|
32
|
+
import { recreateTable } from './operations/tables';
|
|
33
|
+
import { buildCreateIndexSql, buildDropIndexSql } from './planner-ddl-builders';
|
|
25
34
|
import type { SqlitePlanTargetDetails } from './planner-target-details';
|
|
35
|
+
import { buildTargetDetails } from './planner-target-details';
|
|
26
36
|
|
|
27
37
|
type Op = SqlMigrationPlanOperation<SqlitePlanTargetDetails>;
|
|
28
38
|
|
|
@@ -32,7 +42,7 @@ abstract class SqliteOpFactoryCallNode extends TsExpression implements Framework
|
|
|
32
42
|
abstract readonly factoryName: string;
|
|
33
43
|
abstract readonly operationClass: MigrationOperationClass;
|
|
34
44
|
abstract readonly label: string;
|
|
35
|
-
abstract toOp(): Op
|
|
45
|
+
abstract toOp(lowerer?: Lowerer): Op | Promise<Op>;
|
|
36
46
|
|
|
37
47
|
importRequirements(): readonly ImportRequirement[] {
|
|
38
48
|
return [{ moduleSpecifier: TARGET_MIGRATION_MODULE, symbol: this.factoryName }];
|
|
@@ -47,27 +57,149 @@ abstract class SqliteOpFactoryCallNode extends TsExpression implements Framework
|
|
|
47
57
|
// Table
|
|
48
58
|
// ============================================================================
|
|
49
59
|
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
// TypeScript rendering helpers for DdlColumn / DdlTableConstraint
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
|
|
64
|
+
function renderDdlColumnDefault(def: AnyDdlColumnDefault | undefined): string {
|
|
65
|
+
if (!def) return '';
|
|
66
|
+
if (def.kind === 'literal') {
|
|
67
|
+
return `lit(${jsonToTsSource(def.value)})`;
|
|
68
|
+
}
|
|
69
|
+
return `fn(${jsonToTsSource(def.expression)})`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function renderDdlColumnAsTsCall(column: DdlColumn): string {
|
|
73
|
+
const opts: string[] = [];
|
|
74
|
+
if (column.notNull) opts.push('notNull: true');
|
|
75
|
+
if (column.primaryKey) opts.push('primaryKey: true');
|
|
76
|
+
if (column.default) opts.push(`default: ${renderDdlColumnDefault(column.default)}`);
|
|
77
|
+
const optsStr = opts.length > 0 ? `, { ${opts.join(', ')} }` : '';
|
|
78
|
+
return `col(${jsonToTsSource(column.name)}, ${jsonToTsSource(column.type)}${optsStr})`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function renderDdlConstraintAsTsCall(constraint: DdlTableConstraint): string {
|
|
82
|
+
switch (constraint.kind) {
|
|
83
|
+
case 'primary-key': {
|
|
84
|
+
const nameOpt = constraint.name ? `, { name: ${jsonToTsSource(constraint.name)} }` : '';
|
|
85
|
+
return `primaryKey(${jsonToTsSource(constraint.columns)}${nameOpt})`;
|
|
86
|
+
}
|
|
87
|
+
case 'foreign-key': {
|
|
88
|
+
const opts: string[] = [];
|
|
89
|
+
if (constraint.name) opts.push(`name: ${jsonToTsSource(constraint.name)}`);
|
|
90
|
+
if (constraint.onDelete) opts.push(`onDelete: ${jsonToTsSource(constraint.onDelete)}`);
|
|
91
|
+
if (constraint.onUpdate) opts.push(`onUpdate: ${jsonToTsSource(constraint.onUpdate)}`);
|
|
92
|
+
const optsStr = opts.length > 0 ? `, { ${opts.join(', ')} }` : '';
|
|
93
|
+
return `foreignKey(${jsonToTsSource(constraint.columns)}, ${jsonToTsSource(constraint.refTable)}, ${jsonToTsSource(constraint.refColumns)}${optsStr})`;
|
|
94
|
+
}
|
|
95
|
+
case 'unique': {
|
|
96
|
+
const nameOpt = constraint.name ? `, { name: ${jsonToTsSource(constraint.name)} }` : '';
|
|
97
|
+
return `unique(${jsonToTsSource(constraint.columns)}${nameOpt})`;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function constraintImportSymbols(constraints: readonly DdlTableConstraint[] | undefined): string[] {
|
|
103
|
+
if (!constraints || constraints.length === 0) return [];
|
|
104
|
+
const symbols = new Set<string>();
|
|
105
|
+
for (const c of constraints) {
|
|
106
|
+
if (c.kind === 'primary-key') symbols.add('primaryKey');
|
|
107
|
+
else if (c.kind === 'foreign-key') symbols.add('foreignKey');
|
|
108
|
+
else if (c.kind === 'unique') symbols.add('unique');
|
|
109
|
+
}
|
|
110
|
+
return [...symbols];
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function defaultImportSymbols(columns: readonly DdlColumn[]): string[] {
|
|
114
|
+
const symbols = new Set<string>();
|
|
115
|
+
for (const col of columns) {
|
|
116
|
+
if (col.default?.kind === 'literal') symbols.add('lit');
|
|
117
|
+
else if (col.default?.kind === 'function') symbols.add('fn');
|
|
118
|
+
}
|
|
119
|
+
return [...symbols];
|
|
120
|
+
}
|
|
121
|
+
|
|
50
122
|
export class CreateTableCall extends SqliteOpFactoryCallNode {
|
|
51
123
|
readonly factoryName = 'createTable' as const;
|
|
52
124
|
readonly operationClass = 'additive' as const;
|
|
53
125
|
readonly tableName: string;
|
|
54
|
-
readonly
|
|
126
|
+
readonly columns: readonly DdlColumn[];
|
|
127
|
+
readonly constraints: readonly DdlTableConstraint[] | undefined;
|
|
55
128
|
readonly label: string;
|
|
56
129
|
|
|
57
|
-
constructor(
|
|
130
|
+
constructor(
|
|
131
|
+
tableName: string,
|
|
132
|
+
columns: readonly DdlColumn[],
|
|
133
|
+
constraints?: readonly DdlTableConstraint[],
|
|
134
|
+
) {
|
|
58
135
|
super();
|
|
59
136
|
this.tableName = tableName;
|
|
60
|
-
this.
|
|
137
|
+
this.columns = Object.freeze([...columns]);
|
|
138
|
+
this.constraints = constraints ? Object.freeze([...constraints]) : undefined;
|
|
61
139
|
this.label = `Create table ${tableName}`;
|
|
62
140
|
this.freeze();
|
|
63
141
|
}
|
|
64
142
|
|
|
65
|
-
toOp(): Op {
|
|
66
|
-
|
|
143
|
+
async toOp(lowerer?: ExecuteRequestLowerer): Promise<Op> {
|
|
144
|
+
if (lowerer === undefined) {
|
|
145
|
+
throw new Error(
|
|
146
|
+
`CreateTableCall.toOp: a DDL lowerer is required on the SQLite planner path (table "${this.tableName}"). Pass the control adapter to createSqliteMigrationPlanner.`,
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
const ddlNode = contractFreeDdl.createTable({
|
|
150
|
+
table: this.tableName,
|
|
151
|
+
columns: this.columns,
|
|
152
|
+
...ifDefined('constraints', this.constraints),
|
|
153
|
+
});
|
|
154
|
+
const statement = await lowerer.lowerToExecuteRequest(ddlNode);
|
|
155
|
+
const tableName = this.tableName;
|
|
156
|
+
const tableChecks = tableExistsAst(tableName);
|
|
157
|
+
const absent = await lowerer.lowerToExecuteRequest(tableChecks.tableAbsent());
|
|
158
|
+
const present = await lowerer.lowerToExecuteRequest(tableChecks.tablePresent());
|
|
159
|
+
return {
|
|
160
|
+
id: `table.${tableName}`,
|
|
161
|
+
label: `Create table ${tableName}`,
|
|
162
|
+
summary: `Creates table ${tableName} with required columns`,
|
|
163
|
+
operationClass: 'additive',
|
|
164
|
+
target: { id: 'sqlite', details: buildTargetDetails('table', tableName) },
|
|
165
|
+
precheck: [step(`ensure table "${tableName}" does not exist`, absent.sql, absent.params)],
|
|
166
|
+
execute: [
|
|
167
|
+
{
|
|
168
|
+
description: `create table "${tableName}"`,
|
|
169
|
+
sql: statement.sql,
|
|
170
|
+
params: statement.params ?? [],
|
|
171
|
+
},
|
|
172
|
+
],
|
|
173
|
+
postcheck: [step(`verify table "${tableName}" exists`, present.sql, present.params)],
|
|
174
|
+
};
|
|
67
175
|
}
|
|
68
176
|
|
|
69
177
|
renderTypeScript(): string {
|
|
70
|
-
|
|
178
|
+
const columnsList = this.columns.map(renderDdlColumnAsTsCall).join(', ');
|
|
179
|
+
const constraintsList = this.constraints
|
|
180
|
+
? this.constraints.map(renderDdlConstraintAsTsCall).join(', ')
|
|
181
|
+
: undefined;
|
|
182
|
+
|
|
183
|
+
const opts: string[] = [];
|
|
184
|
+
opts.push(`table: ${jsonToTsSource(this.tableName)}`);
|
|
185
|
+
opts.push(`columns: [${columnsList}]`);
|
|
186
|
+
if (constraintsList) opts.push(`constraints: [${constraintsList}]`);
|
|
187
|
+
|
|
188
|
+
return `this.createTable({ ${opts.join(', ')} })`;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
override importRequirements(): readonly ImportRequirement[] {
|
|
192
|
+
const req: ImportRequirement[] = [];
|
|
193
|
+
if (this.columns.length > 0) {
|
|
194
|
+
req.push({ moduleSpecifier: TARGET_MIGRATION_MODULE, symbol: 'col' });
|
|
195
|
+
for (const sym of defaultImportSymbols(this.columns)) {
|
|
196
|
+
req.push({ moduleSpecifier: TARGET_MIGRATION_MODULE, symbol: sym });
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
for (const sym of constraintImportSymbols(this.constraints)) {
|
|
200
|
+
req.push({ moduleSpecifier: TARGET_MIGRATION_MODULE, symbol: sym });
|
|
201
|
+
}
|
|
202
|
+
return req;
|
|
71
203
|
}
|
|
72
204
|
}
|
|
73
205
|
|
|
@@ -84,12 +216,35 @@ export class DropTableCall extends SqliteOpFactoryCallNode {
|
|
|
84
216
|
this.freeze();
|
|
85
217
|
}
|
|
86
218
|
|
|
87
|
-
toOp(): Op {
|
|
88
|
-
|
|
219
|
+
async toOp(lowerer?: ExecuteRequestLowerer): Promise<Op> {
|
|
220
|
+
if (lowerer === undefined) {
|
|
221
|
+
throw new Error(
|
|
222
|
+
`DropTableCall.toOp: a lowerer is required on the SQLite planner path (table "${this.tableName}"). Pass the control adapter to createSqliteMigrationPlanner.`,
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
const checks = tableExistsAst(this.tableName);
|
|
226
|
+
const present = await lowerer.lowerToExecuteRequest(checks.tablePresent());
|
|
227
|
+
const absent = await lowerer.lowerToExecuteRequest(checks.tableAbsent());
|
|
228
|
+
return {
|
|
229
|
+
id: `dropTable.${this.tableName}`,
|
|
230
|
+
label: `Drop table ${this.tableName}`,
|
|
231
|
+
summary: `Drops table ${this.tableName} which is not in the contract`,
|
|
232
|
+
operationClass: 'destructive',
|
|
233
|
+
target: { id: 'sqlite', details: buildTargetDetails('table', this.tableName) },
|
|
234
|
+
precheck: [step(`ensure table "${this.tableName}" exists`, present.sql, present.params)],
|
|
235
|
+
execute: [
|
|
236
|
+
step(`drop table "${this.tableName}"`, `DROP TABLE ${quoteIdentifier(this.tableName)}`),
|
|
237
|
+
],
|
|
238
|
+
postcheck: [step(`verify table "${this.tableName}" is gone`, absent.sql, absent.params)],
|
|
239
|
+
};
|
|
89
240
|
}
|
|
90
241
|
|
|
91
242
|
renderTypeScript(): string {
|
|
92
|
-
return `dropTable(${jsonToTsSource(this.tableName)})`;
|
|
243
|
+
return `this.dropTable({ table: ${jsonToTsSource(this.tableName)} })`;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
override importRequirements(): readonly ImportRequirement[] {
|
|
247
|
+
return [];
|
|
93
248
|
}
|
|
94
249
|
}
|
|
95
250
|
|
|
@@ -125,16 +280,24 @@ export class RecreateTableCall extends SqliteOpFactoryCallNode {
|
|
|
125
280
|
this.freeze();
|
|
126
281
|
}
|
|
127
282
|
|
|
128
|
-
toOp(): Op {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
283
|
+
async toOp(lowerer?: ExecuteRequestLowerer): Promise<Op> {
|
|
284
|
+
if (lowerer === undefined) {
|
|
285
|
+
throw new Error(
|
|
286
|
+
`RecreateTableCall.toOp: a lowerer is required on the SQLite planner path (table "${this.tableName}"). Pass the control adapter to createSqliteMigrationPlanner.`,
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
return recreateTable(
|
|
290
|
+
{
|
|
291
|
+
tableName: this.tableName,
|
|
292
|
+
contractTable: this.contractTable,
|
|
293
|
+
schemaColumnNames: this.schemaColumnNames,
|
|
294
|
+
indexes: this.indexes,
|
|
295
|
+
summary: this.summary,
|
|
296
|
+
postchecks: this.postchecks,
|
|
297
|
+
operationClass: this.operationClass,
|
|
298
|
+
},
|
|
299
|
+
lowerer,
|
|
300
|
+
);
|
|
138
301
|
}
|
|
139
302
|
|
|
140
303
|
renderTypeScript(): string {
|
|
@@ -147,7 +310,11 @@ export class RecreateTableCall extends SqliteOpFactoryCallNode {
|
|
|
147
310
|
postchecks: this.postchecks,
|
|
148
311
|
operationClass: this.operationClass,
|
|
149
312
|
};
|
|
150
|
-
return `recreateTable(${jsonToTsSource(args)})`;
|
|
313
|
+
return `this.recreateTable(${jsonToTsSource(args)})`;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
override importRequirements(): readonly ImportRequirement[] {
|
|
317
|
+
return [];
|
|
151
318
|
}
|
|
152
319
|
}
|
|
153
320
|
|
|
@@ -172,12 +339,38 @@ export class AddColumnCall extends SqliteOpFactoryCallNode {
|
|
|
172
339
|
this.freeze();
|
|
173
340
|
}
|
|
174
341
|
|
|
175
|
-
toOp(): Op {
|
|
176
|
-
|
|
342
|
+
async toOp(lowerer?: ExecuteRequestLowerer): Promise<Op> {
|
|
343
|
+
if (lowerer === undefined) {
|
|
344
|
+
throw new Error(
|
|
345
|
+
`AddColumnCall.toOp: a lowerer is required on the SQLite planner path (column "${this.column.name}" on table "${this.tableName}"). Pass the control adapter to createSqliteMigrationPlanner.`,
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
const checks = columnExistsAst(this.tableName, this.column.name);
|
|
349
|
+
const absent = await lowerer.lowerToExecuteRequest(checks.columnAbsent());
|
|
350
|
+
const present = await lowerer.lowerToExecuteRequest(checks.columnPresent());
|
|
351
|
+
return {
|
|
352
|
+
id: `column.${this.tableName}.${this.column.name}`,
|
|
353
|
+
label: `Add column ${this.column.name} on ${this.tableName}`,
|
|
354
|
+
summary: `Adds column ${this.column.name} on ${this.tableName}`,
|
|
355
|
+
operationClass: 'additive',
|
|
356
|
+
target: {
|
|
357
|
+
id: 'sqlite',
|
|
358
|
+
details: buildTargetDetails('column', this.column.name, this.tableName),
|
|
359
|
+
},
|
|
360
|
+
precheck: [step(`ensure column "${this.column.name}" is missing`, absent.sql, absent.params)],
|
|
361
|
+
execute: [
|
|
362
|
+
step(`add column "${this.column.name}"`, addColumnExecuteSql(this.tableName, this.column)),
|
|
363
|
+
],
|
|
364
|
+
postcheck: [step(`verify column "${this.column.name}" exists`, present.sql, present.params)],
|
|
365
|
+
};
|
|
177
366
|
}
|
|
178
367
|
|
|
179
368
|
renderTypeScript(): string {
|
|
180
|
-
return `addColumn(${jsonToTsSource(this.tableName)}, ${jsonToTsSource(this.column)})`;
|
|
369
|
+
return `this.addColumn({ table: ${jsonToTsSource(this.tableName)}, column: ${jsonToTsSource(this.column)} })`;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
override importRequirements(): readonly ImportRequirement[] {
|
|
373
|
+
return [];
|
|
181
374
|
}
|
|
182
375
|
}
|
|
183
376
|
|
|
@@ -196,12 +389,53 @@ export class DropColumnCall extends SqliteOpFactoryCallNode {
|
|
|
196
389
|
this.freeze();
|
|
197
390
|
}
|
|
198
391
|
|
|
199
|
-
toOp(): Op {
|
|
200
|
-
|
|
392
|
+
async toOp(lowerer?: ExecuteRequestLowerer): Promise<Op> {
|
|
393
|
+
if (lowerer === undefined) {
|
|
394
|
+
throw new Error(
|
|
395
|
+
`DropColumnCall.toOp: a lowerer is required on the SQLite planner path (column "${this.columnName}" on table "${this.tableName}"). Pass the control adapter to createSqliteMigrationPlanner.`,
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
const checks = columnExistsAst(this.tableName, this.columnName);
|
|
399
|
+
const present = await lowerer.lowerToExecuteRequest(checks.columnPresent());
|
|
400
|
+
const absent = await lowerer.lowerToExecuteRequest(checks.columnAbsent());
|
|
401
|
+
return {
|
|
402
|
+
id: `dropColumn.${this.tableName}.${this.columnName}`,
|
|
403
|
+
label: `Drop column ${this.columnName} on ${this.tableName}`,
|
|
404
|
+
summary: `Drops column ${this.columnName} on ${this.tableName} which is not in the contract`,
|
|
405
|
+
operationClass: 'destructive',
|
|
406
|
+
target: {
|
|
407
|
+
id: 'sqlite',
|
|
408
|
+
details: buildTargetDetails('column', this.columnName, this.tableName),
|
|
409
|
+
},
|
|
410
|
+
precheck: [
|
|
411
|
+
step(
|
|
412
|
+
`ensure column "${this.columnName}" exists on "${this.tableName}"`,
|
|
413
|
+
present.sql,
|
|
414
|
+
present.params,
|
|
415
|
+
),
|
|
416
|
+
],
|
|
417
|
+
execute: [
|
|
418
|
+
step(
|
|
419
|
+
`drop column "${this.columnName}" from "${this.tableName}"`,
|
|
420
|
+
dropColumnExecuteSql(this.tableName, this.columnName),
|
|
421
|
+
),
|
|
422
|
+
],
|
|
423
|
+
postcheck: [
|
|
424
|
+
step(
|
|
425
|
+
`verify column "${this.columnName}" is gone from "${this.tableName}"`,
|
|
426
|
+
absent.sql,
|
|
427
|
+
absent.params,
|
|
428
|
+
),
|
|
429
|
+
],
|
|
430
|
+
};
|
|
201
431
|
}
|
|
202
432
|
|
|
203
433
|
renderTypeScript(): string {
|
|
204
|
-
return `dropColumn(${jsonToTsSource(this.tableName)}, ${jsonToTsSource(this.columnName)})`;
|
|
434
|
+
return `this.dropColumn({ table: ${jsonToTsSource(this.tableName)}, column: ${jsonToTsSource(this.columnName)} })`;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
override importRequirements(): readonly ImportRequirement[] {
|
|
438
|
+
return [];
|
|
205
439
|
}
|
|
206
440
|
}
|
|
207
441
|
|
|
@@ -226,12 +460,41 @@ export class CreateIndexCall extends SqliteOpFactoryCallNode {
|
|
|
226
460
|
this.freeze();
|
|
227
461
|
}
|
|
228
462
|
|
|
229
|
-
toOp(): Op {
|
|
230
|
-
|
|
463
|
+
async toOp(lowerer?: ExecuteRequestLowerer): Promise<Op> {
|
|
464
|
+
if (lowerer === undefined) {
|
|
465
|
+
throw new Error(
|
|
466
|
+
`CreateIndexCall.toOp: a lowerer is required on the SQLite planner path (index "${this.indexName}" on table "${this.tableName}"). Pass the control adapter to createSqliteMigrationPlanner.`,
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
const checks = indexExistsAst(this.indexName);
|
|
470
|
+
const absent = await lowerer.lowerToExecuteRequest(checks.indexAbsent());
|
|
471
|
+
const present = await lowerer.lowerToExecuteRequest(checks.indexPresent());
|
|
472
|
+
return {
|
|
473
|
+
id: `index.${this.tableName}.${this.indexName}`,
|
|
474
|
+
label: `Create index ${this.indexName} on ${this.tableName}`,
|
|
475
|
+
summary: `Creates index ${this.indexName} on ${this.tableName}`,
|
|
476
|
+
operationClass: 'additive',
|
|
477
|
+
target: {
|
|
478
|
+
id: 'sqlite',
|
|
479
|
+
details: buildTargetDetails('index', this.indexName, this.tableName),
|
|
480
|
+
},
|
|
481
|
+
precheck: [step(`ensure index "${this.indexName}" is missing`, absent.sql, absent.params)],
|
|
482
|
+
execute: [
|
|
483
|
+
step(
|
|
484
|
+
`create index "${this.indexName}"`,
|
|
485
|
+
buildCreateIndexSql(this.tableName, this.indexName, this.columns),
|
|
486
|
+
),
|
|
487
|
+
],
|
|
488
|
+
postcheck: [step(`verify index "${this.indexName}" exists`, present.sql, present.params)],
|
|
489
|
+
};
|
|
231
490
|
}
|
|
232
491
|
|
|
233
492
|
renderTypeScript(): string {
|
|
234
|
-
return `createIndex(${jsonToTsSource(this.tableName)}, ${jsonToTsSource(this.indexName)}, ${jsonToTsSource(this.columns)})`;
|
|
493
|
+
return `this.createIndex({ table: ${jsonToTsSource(this.tableName)}, index: ${jsonToTsSource(this.indexName)}, columns: ${jsonToTsSource(this.columns)} })`;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
override importRequirements(): readonly ImportRequirement[] {
|
|
497
|
+
return [];
|
|
235
498
|
}
|
|
236
499
|
}
|
|
237
500
|
|
|
@@ -250,12 +513,36 @@ export class DropIndexCall extends SqliteOpFactoryCallNode {
|
|
|
250
513
|
this.freeze();
|
|
251
514
|
}
|
|
252
515
|
|
|
253
|
-
toOp(): Op {
|
|
254
|
-
|
|
516
|
+
async toOp(lowerer?: ExecuteRequestLowerer): Promise<Op> {
|
|
517
|
+
if (lowerer === undefined) {
|
|
518
|
+
throw new Error(
|
|
519
|
+
`DropIndexCall.toOp: a lowerer is required on the SQLite planner path (index "${this.indexName}" on table "${this.tableName}"). Pass the control adapter to createSqliteMigrationPlanner.`,
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
const checks = indexExistsAst(this.indexName);
|
|
523
|
+
const present = await lowerer.lowerToExecuteRequest(checks.indexPresent());
|
|
524
|
+
const absent = await lowerer.lowerToExecuteRequest(checks.indexAbsent());
|
|
525
|
+
return {
|
|
526
|
+
id: `dropIndex.${this.tableName}.${this.indexName}`,
|
|
527
|
+
label: `Drop index ${this.indexName} on ${this.tableName}`,
|
|
528
|
+
summary: `Drops index ${this.indexName} on ${this.tableName} which is not in the contract`,
|
|
529
|
+
operationClass: 'destructive',
|
|
530
|
+
target: {
|
|
531
|
+
id: 'sqlite',
|
|
532
|
+
details: buildTargetDetails('index', this.indexName, this.tableName),
|
|
533
|
+
},
|
|
534
|
+
precheck: [step(`ensure index "${this.indexName}" exists`, present.sql, present.params)],
|
|
535
|
+
execute: [step(`drop index "${this.indexName}"`, buildDropIndexSql(this.indexName))],
|
|
536
|
+
postcheck: [step(`verify index "${this.indexName}" is gone`, absent.sql, absent.params)],
|
|
537
|
+
};
|
|
255
538
|
}
|
|
256
539
|
|
|
257
540
|
renderTypeScript(): string {
|
|
258
|
-
return `dropIndex(${jsonToTsSource(this.tableName)}, ${jsonToTsSource(this.indexName)})`;
|
|
541
|
+
return `this.dropIndex({ table: ${jsonToTsSource(this.tableName)}, index: ${jsonToTsSource(this.indexName)} })`;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
override importRequirements(): readonly ImportRequirement[] {
|
|
545
|
+
return [];
|
|
259
546
|
}
|
|
260
547
|
}
|
|
261
548
|
|
|
@@ -292,7 +579,7 @@ export class DataTransformCall extends SqliteOpFactoryCallNode {
|
|
|
292
579
|
this.freeze();
|
|
293
580
|
}
|
|
294
581
|
|
|
295
|
-
toOp(): Op {
|
|
582
|
+
toOp(_lowerer?: Lowerer): Op {
|
|
296
583
|
throw errorUnfilledPlaceholder(this.label);
|
|
297
584
|
}
|
|
298
585
|
|
|
@@ -344,7 +631,7 @@ export class RawSqlCall extends SqliteOpFactoryCallNode {
|
|
|
344
631
|
this.freeze();
|
|
345
632
|
}
|
|
346
633
|
|
|
347
|
-
toOp(): Op {
|
|
634
|
+
toOp(_lowerer?: Lowerer): Op {
|
|
348
635
|
return this.op;
|
|
349
636
|
}
|
|
350
637
|
|
|
@@ -1,39 +1,51 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { ExecuteRequestLowerer } from '@prisma-next/family-sql/control-adapter';
|
|
2
|
+
import { columnExistsAst } from '../../../contract-free/checks';
|
|
3
|
+
import { quoteIdentifier } from '../../sql-utils';
|
|
2
4
|
import { buildTargetDetails } from '../planner-target-details';
|
|
3
5
|
import { type Op, type SqliteColumnSpec, step } from './shared';
|
|
4
6
|
|
|
5
|
-
export function
|
|
7
|
+
export function addColumnExecuteSql(tableName: string, column: SqliteColumnSpec): string {
|
|
6
8
|
const parts = [
|
|
7
9
|
`ALTER TABLE ${quoteIdentifier(tableName)}`,
|
|
8
10
|
`ADD COLUMN ${quoteIdentifier(column.name)} ${column.typeSql}`,
|
|
9
11
|
column.defaultSql,
|
|
10
12
|
column.nullable ? '' : 'NOT NULL',
|
|
11
13
|
].filter(Boolean);
|
|
12
|
-
|
|
14
|
+
return parts.join(' ');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function dropColumnExecuteSql(tableName: string, columnName: string): string {
|
|
18
|
+
return `ALTER TABLE ${quoteIdentifier(tableName)} DROP COLUMN ${quoteIdentifier(columnName)}`;
|
|
19
|
+
}
|
|
13
20
|
|
|
21
|
+
export async function addColumn(
|
|
22
|
+
tableName: string,
|
|
23
|
+
column: SqliteColumnSpec,
|
|
24
|
+
lowerer: ExecuteRequestLowerer,
|
|
25
|
+
): Promise<Op> {
|
|
26
|
+
const checks = columnExistsAst(tableName, column.name);
|
|
27
|
+
const absent = await lowerer.lowerToExecuteRequest(checks.columnAbsent());
|
|
28
|
+
const present = await lowerer.lowerToExecuteRequest(checks.columnPresent());
|
|
14
29
|
return {
|
|
15
30
|
id: `column.${tableName}.${column.name}`,
|
|
16
31
|
label: `Add column ${column.name} on ${tableName}`,
|
|
17
32
|
summary: `Adds column ${column.name} on ${tableName}`,
|
|
18
33
|
operationClass: 'additive',
|
|
19
34
|
target: { id: 'sqlite', details: buildTargetDetails('column', column.name, tableName) },
|
|
20
|
-
precheck: [
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
`SELECT COUNT(*) = 0 FROM pragma_table_info('${escapeLiteral(tableName)}') WHERE name = '${escapeLiteral(column.name)}'`,
|
|
24
|
-
),
|
|
25
|
-
],
|
|
26
|
-
execute: [step(`add column "${column.name}"`, addSql)],
|
|
27
|
-
postcheck: [
|
|
28
|
-
step(
|
|
29
|
-
`verify column "${column.name}" exists`,
|
|
30
|
-
`SELECT COUNT(*) > 0 FROM pragma_table_info('${escapeLiteral(tableName)}') WHERE name = '${escapeLiteral(column.name)}'`,
|
|
31
|
-
),
|
|
32
|
-
],
|
|
35
|
+
precheck: [step(`ensure column "${column.name}" is missing`, absent.sql, absent.params)],
|
|
36
|
+
execute: [step(`add column "${column.name}"`, addColumnExecuteSql(tableName, column))],
|
|
37
|
+
postcheck: [step(`verify column "${column.name}" exists`, present.sql, present.params)],
|
|
33
38
|
};
|
|
34
39
|
}
|
|
35
40
|
|
|
36
|
-
export function dropColumn(
|
|
41
|
+
export async function dropColumn(
|
|
42
|
+
tableName: string,
|
|
43
|
+
columnName: string,
|
|
44
|
+
lowerer: ExecuteRequestLowerer,
|
|
45
|
+
): Promise<Op> {
|
|
46
|
+
const checks = columnExistsAst(tableName, columnName);
|
|
47
|
+
const present = await lowerer.lowerToExecuteRequest(checks.columnPresent());
|
|
48
|
+
const absent = await lowerer.lowerToExecuteRequest(checks.columnAbsent());
|
|
37
49
|
return {
|
|
38
50
|
id: `dropColumn.${tableName}.${columnName}`,
|
|
39
51
|
label: `Drop column ${columnName} on ${tableName}`,
|
|
@@ -41,22 +53,16 @@ export function dropColumn(tableName: string, columnName: string): Op {
|
|
|
41
53
|
operationClass: 'destructive',
|
|
42
54
|
target: { id: 'sqlite', details: buildTargetDetails('column', columnName, tableName) },
|
|
43
55
|
precheck: [
|
|
44
|
-
step(
|
|
45
|
-
`ensure column "${columnName}" exists on "${tableName}"`,
|
|
46
|
-
`SELECT COUNT(*) > 0 FROM pragma_table_info('${escapeLiteral(tableName)}') WHERE name = '${escapeLiteral(columnName)}'`,
|
|
47
|
-
),
|
|
56
|
+
step(`ensure column "${columnName}" exists on "${tableName}"`, present.sql, present.params),
|
|
48
57
|
],
|
|
49
58
|
execute: [
|
|
50
59
|
step(
|
|
51
60
|
`drop column "${columnName}" from "${tableName}"`,
|
|
52
|
-
|
|
61
|
+
dropColumnExecuteSql(tableName, columnName),
|
|
53
62
|
),
|
|
54
63
|
],
|
|
55
64
|
postcheck: [
|
|
56
|
-
step(
|
|
57
|
-
`verify column "${columnName}" is gone from "${tableName}"`,
|
|
58
|
-
`SELECT COUNT(*) = 0 FROM pragma_table_info('${escapeLiteral(tableName)}') WHERE name = '${escapeLiteral(columnName)}'`,
|
|
59
|
-
),
|
|
65
|
+
step(`verify column "${columnName}" is gone from "${tableName}"`, absent.sql, absent.params),
|
|
60
66
|
],
|
|
61
67
|
};
|
|
62
68
|
}
|
|
@@ -1,52 +1,56 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { ExecuteRequestLowerer } from '@prisma-next/family-sql/control-adapter';
|
|
2
|
+
import { indexExistsAst } from '../../../contract-free/checks';
|
|
2
3
|
import { buildCreateIndexSql, buildDropIndexSql } from '../planner-ddl-builders';
|
|
3
4
|
import { buildTargetDetails } from '../planner-target-details';
|
|
4
5
|
import { type Op, step } from './shared';
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
type CheckStep = { sql: string; params?: readonly unknown[] };
|
|
8
|
+
|
|
9
|
+
async function indexExistsSteps(
|
|
10
|
+
lowerer: ExecuteRequestLowerer,
|
|
11
|
+
indexName: string,
|
|
12
|
+
): Promise<{ present: CheckStep; absent: CheckStep }> {
|
|
13
|
+
const checks = indexExistsAst(indexName);
|
|
14
|
+
const present = await lowerer.lowerToExecuteRequest(checks.indexPresent());
|
|
15
|
+
const absent = await lowerer.lowerToExecuteRequest(checks.indexAbsent());
|
|
16
|
+
return { present, absent };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function createIndex(
|
|
20
|
+
tableName: string,
|
|
21
|
+
indexName: string,
|
|
22
|
+
columns: readonly string[],
|
|
23
|
+
lowerer: ExecuteRequestLowerer,
|
|
24
|
+
): Promise<Op> {
|
|
25
|
+
const { present, absent } = await indexExistsSteps(lowerer, indexName);
|
|
7
26
|
return {
|
|
8
27
|
id: `index.${tableName}.${indexName}`,
|
|
9
28
|
label: `Create index ${indexName} on ${tableName}`,
|
|
10
29
|
summary: `Creates index ${indexName} on ${tableName}`,
|
|
11
30
|
operationClass: 'additive',
|
|
12
31
|
target: { id: 'sqlite', details: buildTargetDetails('index', indexName, tableName) },
|
|
13
|
-
precheck: [
|
|
14
|
-
step(
|
|
15
|
-
`ensure index "${indexName}" is missing`,
|
|
16
|
-
`SELECT COUNT(*) = 0 FROM sqlite_master WHERE type = 'index' AND name = '${escapeLiteral(indexName)}'`,
|
|
17
|
-
),
|
|
18
|
-
],
|
|
32
|
+
precheck: [step(`ensure index "${indexName}" is missing`, absent.sql, absent.params)],
|
|
19
33
|
execute: [
|
|
20
34
|
step(`create index "${indexName}"`, buildCreateIndexSql(tableName, indexName, columns)),
|
|
21
35
|
],
|
|
22
|
-
postcheck: [
|
|
23
|
-
step(
|
|
24
|
-
`verify index "${indexName}" exists`,
|
|
25
|
-
`SELECT COUNT(*) > 0 FROM sqlite_master WHERE type = 'index' AND name = '${escapeLiteral(indexName)}'`,
|
|
26
|
-
),
|
|
27
|
-
],
|
|
36
|
+
postcheck: [step(`verify index "${indexName}" exists`, present.sql, present.params)],
|
|
28
37
|
};
|
|
29
38
|
}
|
|
30
39
|
|
|
31
|
-
export function dropIndex(
|
|
40
|
+
export async function dropIndex(
|
|
41
|
+
tableName: string,
|
|
42
|
+
indexName: string,
|
|
43
|
+
lowerer: ExecuteRequestLowerer,
|
|
44
|
+
): Promise<Op> {
|
|
45
|
+
const { present, absent } = await indexExistsSteps(lowerer, indexName);
|
|
32
46
|
return {
|
|
33
47
|
id: `dropIndex.${tableName}.${indexName}`,
|
|
34
48
|
label: `Drop index ${indexName} on ${tableName}`,
|
|
35
49
|
summary: `Drops index ${indexName} on ${tableName} which is not in the contract`,
|
|
36
50
|
operationClass: 'destructive',
|
|
37
51
|
target: { id: 'sqlite', details: buildTargetDetails('index', indexName, tableName) },
|
|
38
|
-
precheck: [
|
|
39
|
-
step(
|
|
40
|
-
`ensure index "${indexName}" exists`,
|
|
41
|
-
`SELECT COUNT(*) > 0 FROM sqlite_master WHERE type = 'index' AND name = '${escapeLiteral(indexName)}'`,
|
|
42
|
-
),
|
|
43
|
-
],
|
|
52
|
+
precheck: [step(`ensure index "${indexName}" exists`, present.sql, present.params)],
|
|
44
53
|
execute: [step(`drop index "${indexName}"`, buildDropIndexSql(indexName))],
|
|
45
|
-
postcheck: [
|
|
46
|
-
step(
|
|
47
|
-
`verify index "${indexName}" is gone`,
|
|
48
|
-
`SELECT COUNT(*) = 0 FROM sqlite_master WHERE type = 'index' AND name = '${escapeLiteral(indexName)}'`,
|
|
49
|
-
),
|
|
50
|
-
],
|
|
54
|
+
postcheck: [step(`verify index "${indexName}" is gone`, absent.sql, absent.params)],
|
|
51
55
|
};
|
|
52
56
|
}
|