@prisma-next/adapter-sqlite 0.12.0-dev.4 → 0.12.0-dev.41
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 +9 -9
- package/dist/adapter-CwTpH1kL.mjs +764 -0
- package/dist/adapter-CwTpH1kL.mjs.map +1 -0
- package/dist/{adapter-Cn_t9TdZ.d.mts → adapter-DJmACrLG.d.mts} +3 -2
- package/dist/adapter-DJmACrLG.d.mts.map +1 -0
- package/dist/adapter.d.mts +1 -1
- package/dist/adapter.mjs +1 -1
- package/dist/control.d.mts +52 -3
- package/dist/control.d.mts.map +1 -1
- package/dist/control.mjs +3 -194
- package/dist/control.mjs.map +1 -1
- package/dist/runtime.d.mts +1 -1
- package/dist/runtime.mjs +2 -2
- package/package.json +22 -22
- package/src/core/adapter.ts +79 -44
- package/src/core/control-adapter.ts +318 -111
- package/src/core/ddl-renderer.ts +67 -0
- package/src/core/ledger-decode.ts +26 -0
- package/src/core/marker-ledger.ts +124 -0
- package/dist/adapter-BOg2xl4V.mjs +0 -348
- package/dist/adapter-BOg2xl4V.mjs.map +0 -1
- package/dist/adapter-Cn_t9TdZ.d.mts.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ContractMarkerRecord } from '@prisma-next/contract/types';
|
|
1
|
+
import type { ContractMarkerRecord, LedgerEntryRecord } from '@prisma-next/contract/types';
|
|
2
2
|
import { parseMarkerRowSafely, withMarkerReadErrorHandling } from '@prisma-next/errors/execution';
|
|
3
3
|
import type { SqlControlAdapter } from '@prisma-next/family-sql/control-adapter';
|
|
4
4
|
import { parseContractMarkerRow } from '@prisma-next/family-sql/verify';
|
|
@@ -6,11 +6,15 @@ import {
|
|
|
6
6
|
APP_SPACE_ID,
|
|
7
7
|
type ControlDriverInstance,
|
|
8
8
|
} from '@prisma-next/framework-components/control';
|
|
9
|
+
import { ledgerOriginFromStored } from '@prisma-next/migration-tools/ledger-origin';
|
|
9
10
|
import type {
|
|
10
11
|
AnyQueryAst,
|
|
12
|
+
DdlNode,
|
|
11
13
|
LoweredStatement,
|
|
12
14
|
LowererContext,
|
|
15
|
+
MarkerReadResult,
|
|
13
16
|
} from '@prisma-next/sql-relational-core/ast';
|
|
17
|
+
import { isDdlNode } from '@prisma-next/sql-relational-core/ast';
|
|
14
18
|
import type {
|
|
15
19
|
PrimaryKey,
|
|
16
20
|
SqlColumnIR,
|
|
@@ -21,36 +25,42 @@ import type {
|
|
|
21
25
|
SqlTableIR,
|
|
22
26
|
SqlUniqueIR,
|
|
23
27
|
} from '@prisma-next/sql-schema-ir/types';
|
|
28
|
+
import {
|
|
29
|
+
buildControlTableBootstrapQueries,
|
|
30
|
+
buildSignMarkerBootstrapQueries,
|
|
31
|
+
} from '@prisma-next/target-sqlite/contract-free';
|
|
32
|
+
import type { SqliteDdlNode } from '@prisma-next/target-sqlite/ddl';
|
|
24
33
|
import { parseSqliteDefault } from '@prisma-next/target-sqlite/default-normalizer';
|
|
25
34
|
import { normalizeSqliteNativeType } from '@prisma-next/target-sqlite/native-type-normalizer';
|
|
35
|
+
import { blindCast } from '@prisma-next/utils/casts';
|
|
26
36
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
27
37
|
import { renderLoweredSql } from './adapter';
|
|
38
|
+
import { renderLoweredDdl } from './ddl-renderer';
|
|
39
|
+
import { coerceLedgerAppliedAt, operationCountFromStored } from './ledger-decode';
|
|
40
|
+
import {
|
|
41
|
+
decodeSqliteMarkerRow,
|
|
42
|
+
execute,
|
|
43
|
+
ledger,
|
|
44
|
+
ledgerReadShape,
|
|
45
|
+
marker,
|
|
46
|
+
mergeInvariants,
|
|
47
|
+
NOW,
|
|
48
|
+
sqliteCatalog,
|
|
49
|
+
} from './marker-ledger';
|
|
28
50
|
import type { SqliteContract } from './types';
|
|
29
51
|
|
|
30
52
|
const SQLITE_MARKER_TABLE = '_prisma_marker';
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
const record = row as { invariants: unknown };
|
|
44
|
-
if (typeof record.invariants !== 'string') return row;
|
|
45
|
-
let parsed: unknown;
|
|
46
|
-
try {
|
|
47
|
-
parsed = JSON.parse(record.invariants);
|
|
48
|
-
} catch (err) {
|
|
49
|
-
const detail = err instanceof Error ? err.message : String(err);
|
|
50
|
-
throw new Error(`Invalid contract marker row: invariants is not valid JSON: ${detail}`);
|
|
51
|
-
}
|
|
52
|
-
return { ...record, invariants: parsed };
|
|
53
|
-
}
|
|
53
|
+
const SQLITE_LEDGER_TABLE = '_prisma_ledger';
|
|
54
|
+
|
|
55
|
+
type SqliteLedgerRow = {
|
|
56
|
+
readonly space: string;
|
|
57
|
+
readonly migration_name: string;
|
|
58
|
+
readonly migration_hash: string;
|
|
59
|
+
readonly origin_core_hash: string | null;
|
|
60
|
+
readonly destination_core_hash: string;
|
|
61
|
+
readonly operations: unknown;
|
|
62
|
+
readonly created_at: Date | string;
|
|
63
|
+
};
|
|
54
64
|
|
|
55
65
|
// PRAGMA result row types
|
|
56
66
|
type PragmaTableInfoRow = {
|
|
@@ -101,6 +111,14 @@ export class SqliteControlAdapter implements SqlControlAdapter<'sqlite'> {
|
|
|
101
111
|
readonly normalizeDefault = parseSqliteDefault;
|
|
102
112
|
readonly normalizeNativeType = normalizeSqliteNativeType;
|
|
103
113
|
|
|
114
|
+
bootstrapControlTableQueries(): readonly DdlNode[] {
|
|
115
|
+
return buildControlTableBootstrapQueries();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
bootstrapSignMarkerQueries(): readonly DdlNode[] {
|
|
119
|
+
return buildSignMarkerBootstrapQueries();
|
|
120
|
+
}
|
|
121
|
+
|
|
104
122
|
/**
|
|
105
123
|
* Lower a SQL query AST into a SQLite-flavored `{ sql, params }` payload.
|
|
106
124
|
*
|
|
@@ -109,7 +127,10 @@ export class SqliteControlAdapter implements SqlControlAdapter<'sqlite'> {
|
|
|
109
127
|
* and contract. Used at migration plan/emit time (e.g. by `dataTransform`)
|
|
110
128
|
* without instantiating the runtime adapter.
|
|
111
129
|
*/
|
|
112
|
-
lower(ast: AnyQueryAst, context: LowererContext<unknown>): LoweredStatement {
|
|
130
|
+
lower(ast: AnyQueryAst | SqliteDdlNode, context: LowererContext<unknown>): LoweredStatement {
|
|
131
|
+
if (isDdlNode(ast)) {
|
|
132
|
+
return renderLoweredDdl(ast);
|
|
133
|
+
}
|
|
113
134
|
return renderLoweredSql(ast, context.contract as SqliteContract);
|
|
114
135
|
}
|
|
115
136
|
|
|
@@ -122,53 +143,16 @@ export class SqliteControlAdapter implements SqlControlAdapter<'sqlite'> {
|
|
|
122
143
|
driver: ControlDriverInstance<'sql', 'sqlite'>,
|
|
123
144
|
space: string,
|
|
124
145
|
): Promise<ContractMarkerRecord | null> {
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
driver.query(`SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?`, [
|
|
129
|
-
'_prisma_marker',
|
|
130
|
-
]),
|
|
131
|
-
markerContext,
|
|
132
|
-
);
|
|
133
|
-
if (exists.rows.length === 0) {
|
|
134
|
-
return null;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const result = await withMarkerReadErrorHandling(
|
|
138
|
-
() =>
|
|
139
|
-
driver.query<{
|
|
140
|
-
core_hash: string;
|
|
141
|
-
profile_hash: string;
|
|
142
|
-
contract_json: unknown | null;
|
|
143
|
-
canonical_version: number | null;
|
|
144
|
-
updated_at: Date | string;
|
|
145
|
-
app_tag: string | null;
|
|
146
|
-
meta: unknown | null;
|
|
147
|
-
invariants: unknown;
|
|
148
|
-
}>(
|
|
149
|
-
`SELECT
|
|
150
|
-
core_hash,
|
|
151
|
-
profile_hash,
|
|
152
|
-
contract_json,
|
|
153
|
-
canonical_version,
|
|
154
|
-
updated_at,
|
|
155
|
-
app_tag,
|
|
156
|
-
meta,
|
|
157
|
-
invariants
|
|
158
|
-
FROM _prisma_marker
|
|
159
|
-
WHERE space = ?`,
|
|
160
|
-
[space],
|
|
161
|
-
),
|
|
162
|
-
markerContext,
|
|
163
|
-
);
|
|
146
|
+
const result = await this.readMarkerDiscriminated(driver, space);
|
|
147
|
+
return result.kind === 'present' ? result.record : null;
|
|
148
|
+
}
|
|
164
149
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
);
|
|
150
|
+
async readMarkerDiscriminated(
|
|
151
|
+
driver: ControlDriverInstance<'sql', 'sqlite'>,
|
|
152
|
+
space: string,
|
|
153
|
+
): Promise<MarkerReadResult> {
|
|
154
|
+
const markerContext = { space, markerLocation: SQLITE_MARKER_TABLE };
|
|
155
|
+
return withMarkerReadErrorHandling(() => this.readMarkerResult(driver, space), markerContext);
|
|
172
156
|
}
|
|
173
157
|
|
|
174
158
|
/**
|
|
@@ -180,48 +164,44 @@ export class SqliteControlAdapter implements SqlControlAdapter<'sqlite'> {
|
|
|
180
164
|
driver: ControlDriverInstance<'sql', 'sqlite'>,
|
|
181
165
|
): Promise<ReadonlyMap<string, ContractMarkerRecord>> {
|
|
182
166
|
const markerContext = { space: APP_SPACE_ID, markerLocation: SQLITE_MARKER_TABLE };
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
);
|
|
190
|
-
|
|
167
|
+
return withMarkerReadErrorHandling(() => this.readAllMarkersResult(driver), markerContext);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
private async readAllMarkersResult(
|
|
171
|
+
driver: ControlDriverInstance<'sql', 'sqlite'>,
|
|
172
|
+
): Promise<ReadonlyMap<string, ContractMarkerRecord>> {
|
|
173
|
+
const lower = (query: AnyQueryAst) => this.lower(query, { contract: undefined });
|
|
174
|
+
const probe = sqliteCatalog
|
|
175
|
+
.select(sqliteCatalog.name)
|
|
176
|
+
.where(sqliteCatalog.type.eq('table').and(sqliteCatalog.name.eq('_prisma_marker')))
|
|
177
|
+
.build();
|
|
178
|
+
const exists = await execute(lower, driver, probe);
|
|
179
|
+
if (exists.length === 0) {
|
|
191
180
|
return new Map();
|
|
192
181
|
}
|
|
193
182
|
|
|
194
|
-
const
|
|
195
|
-
(
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
invariants
|
|
217
|
-
FROM _prisma_marker`,
|
|
218
|
-
),
|
|
219
|
-
markerContext,
|
|
220
|
-
);
|
|
221
|
-
|
|
222
|
-
const rows = new Map<string, ContractMarkerRecord>();
|
|
223
|
-
for (const row of result.rows) {
|
|
224
|
-
rows.set(
|
|
183
|
+
const fetch = marker
|
|
184
|
+
.select(
|
|
185
|
+
marker.space,
|
|
186
|
+
marker.core_hash,
|
|
187
|
+
marker.profile_hash,
|
|
188
|
+
marker.contract_json,
|
|
189
|
+
marker.canonical_version,
|
|
190
|
+
marker.updated_at,
|
|
191
|
+
marker.app_tag,
|
|
192
|
+
marker.meta,
|
|
193
|
+
marker.invariants,
|
|
194
|
+
)
|
|
195
|
+
.build();
|
|
196
|
+
const rawRows = await execute(lower, driver, fetch);
|
|
197
|
+
const rows = blindCast<
|
|
198
|
+
ReadonlyArray<{ space: string } & Record<string, unknown>>,
|
|
199
|
+
'Driver returns rows shaped by SELECT'
|
|
200
|
+
>(rawRows);
|
|
201
|
+
|
|
202
|
+
const out = new Map<string, ContractMarkerRecord>();
|
|
203
|
+
for (const row of rows) {
|
|
204
|
+
out.set(
|
|
225
205
|
row.space,
|
|
226
206
|
parseMarkerRowSafely(row, (raw) => parseContractMarkerRow(decodeSqliteMarkerRow(raw)), {
|
|
227
207
|
space: row.space,
|
|
@@ -229,7 +209,234 @@ export class SqliteControlAdapter implements SqlControlAdapter<'sqlite'> {
|
|
|
229
209
|
}),
|
|
230
210
|
);
|
|
231
211
|
}
|
|
232
|
-
return
|
|
212
|
+
return out;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Reads per-migration ledger rows from `_prisma_ledger` in apply order.
|
|
217
|
+
* Probes `sqlite_master` first so a fresh database without the ledger
|
|
218
|
+
* table returns `[]` instead of raising "no such table".
|
|
219
|
+
*/
|
|
220
|
+
async readLedger(
|
|
221
|
+
driver: ControlDriverInstance<'sql', 'sqlite'>,
|
|
222
|
+
space?: string,
|
|
223
|
+
): Promise<readonly LedgerEntryRecord[]> {
|
|
224
|
+
const ledgerContext = { space: space ?? '*', markerLocation: SQLITE_LEDGER_TABLE };
|
|
225
|
+
return withMarkerReadErrorHandling(() => this.readLedgerResult(driver, space), ledgerContext);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
private async readLedgerResult(
|
|
229
|
+
driver: ControlDriverInstance<'sql', 'sqlite'>,
|
|
230
|
+
space: string | undefined,
|
|
231
|
+
): Promise<readonly LedgerEntryRecord[]> {
|
|
232
|
+
const lower = (query: AnyQueryAst) => this.lower(query, { contract: undefined });
|
|
233
|
+
const probe = sqliteCatalog
|
|
234
|
+
.select(sqliteCatalog.name)
|
|
235
|
+
.where(sqliteCatalog.type.eq('table').and(sqliteCatalog.name.eq('_prisma_ledger')))
|
|
236
|
+
.build();
|
|
237
|
+
const exists = await execute(lower, driver, probe);
|
|
238
|
+
if (exists.length === 0) {
|
|
239
|
+
return [];
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const base = ledgerReadShape.select(
|
|
243
|
+
ledgerReadShape.space,
|
|
244
|
+
ledgerReadShape.migration_name,
|
|
245
|
+
ledgerReadShape.migration_hash,
|
|
246
|
+
ledgerReadShape.origin_core_hash,
|
|
247
|
+
ledgerReadShape.destination_core_hash,
|
|
248
|
+
ledgerReadShape.operations,
|
|
249
|
+
ledgerReadShape.created_at,
|
|
250
|
+
);
|
|
251
|
+
const filtered = space !== undefined ? base.where(ledgerReadShape.space.eq(space)) : base;
|
|
252
|
+
const rawRows = await execute(lower, driver, filtered.orderBy(ledgerReadShape.id).build());
|
|
253
|
+
const rows = blindCast<readonly SqliteLedgerRow[], 'Driver returns rows shaped by SELECT'>(
|
|
254
|
+
rawRows,
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
return rows.map((row) => ({
|
|
258
|
+
space: row.space,
|
|
259
|
+
migrationName: row.migration_name,
|
|
260
|
+
migrationHash: row.migration_hash,
|
|
261
|
+
from: ledgerOriginFromStored(row.origin_core_hash),
|
|
262
|
+
to: row.destination_core_hash,
|
|
263
|
+
appliedAt: coerceLedgerAppliedAt(row.created_at),
|
|
264
|
+
operationCount: operationCountFromStored(row.operations),
|
|
265
|
+
}));
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Stamps the initial marker row for `space` via the shared contract-free DML
|
|
270
|
+
* builder, lowered through {@link lower} and executed on the driver. See the
|
|
271
|
+
* `SqlControlAdapter.initMarker` contract.
|
|
272
|
+
*/
|
|
273
|
+
async insertMarker(
|
|
274
|
+
driver: ControlDriverInstance<'sql', 'sqlite'>,
|
|
275
|
+
space: string,
|
|
276
|
+
destination: {
|
|
277
|
+
readonly storageHash: string;
|
|
278
|
+
readonly profileHash: string;
|
|
279
|
+
readonly invariants?: readonly string[];
|
|
280
|
+
},
|
|
281
|
+
): Promise<void> {
|
|
282
|
+
await execute(
|
|
283
|
+
(query) => this.lower(query, { contract: undefined }),
|
|
284
|
+
driver,
|
|
285
|
+
marker
|
|
286
|
+
.insert({
|
|
287
|
+
space,
|
|
288
|
+
core_hash: destination.storageHash,
|
|
289
|
+
profile_hash: destination.profileHash,
|
|
290
|
+
contract_json: null,
|
|
291
|
+
canonical_version: null,
|
|
292
|
+
updated_at: NOW,
|
|
293
|
+
app_tag: null,
|
|
294
|
+
meta: {},
|
|
295
|
+
invariants: destination.invariants ?? [],
|
|
296
|
+
})
|
|
297
|
+
.build(),
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
async initMarker(
|
|
302
|
+
driver: ControlDriverInstance<'sql', 'sqlite'>,
|
|
303
|
+
space: string,
|
|
304
|
+
destination: {
|
|
305
|
+
readonly storageHash: string;
|
|
306
|
+
readonly profileHash: string;
|
|
307
|
+
readonly invariants?: readonly string[];
|
|
308
|
+
},
|
|
309
|
+
): Promise<void> {
|
|
310
|
+
await execute(
|
|
311
|
+
(query) => this.lower(query, { contract: undefined }),
|
|
312
|
+
driver,
|
|
313
|
+
marker
|
|
314
|
+
.upsert({
|
|
315
|
+
space,
|
|
316
|
+
core_hash: destination.storageHash,
|
|
317
|
+
profile_hash: destination.profileHash,
|
|
318
|
+
contract_json: null,
|
|
319
|
+
canonical_version: null,
|
|
320
|
+
updated_at: NOW,
|
|
321
|
+
app_tag: null,
|
|
322
|
+
meta: {},
|
|
323
|
+
invariants: destination.invariants ?? [],
|
|
324
|
+
})
|
|
325
|
+
.onConflict(marker.space)
|
|
326
|
+
.doUpdate((excluded) => ({
|
|
327
|
+
core_hash: excluded.core_hash,
|
|
328
|
+
profile_hash: excluded.profile_hash,
|
|
329
|
+
contract_json: excluded.contract_json,
|
|
330
|
+
canonical_version: excluded.canonical_version,
|
|
331
|
+
updated_at: NOW,
|
|
332
|
+
app_tag: excluded.app_tag,
|
|
333
|
+
meta: excluded.meta,
|
|
334
|
+
invariants: excluded.invariants,
|
|
335
|
+
}))
|
|
336
|
+
.build(),
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Compare-and-swap advance of the marker row for `space`. See the
|
|
342
|
+
* `SqlControlAdapter.updateMarker` contract.
|
|
343
|
+
*/
|
|
344
|
+
async updateMarker(
|
|
345
|
+
driver: ControlDriverInstance<'sql', 'sqlite'>,
|
|
346
|
+
space: string,
|
|
347
|
+
expectedFrom: string,
|
|
348
|
+
destination: {
|
|
349
|
+
readonly storageHash: string;
|
|
350
|
+
readonly profileHash: string;
|
|
351
|
+
readonly invariants?: readonly string[];
|
|
352
|
+
},
|
|
353
|
+
): Promise<boolean> {
|
|
354
|
+
const currentInvariants =
|
|
355
|
+
destination.invariants === undefined
|
|
356
|
+
? []
|
|
357
|
+
: ((await this.readMarker(driver, space))?.invariants ?? []);
|
|
358
|
+
const mergedInvariants =
|
|
359
|
+
destination.invariants === undefined
|
|
360
|
+
? undefined
|
|
361
|
+
: mergeInvariants(currentInvariants, destination.invariants);
|
|
362
|
+
|
|
363
|
+
const query = marker
|
|
364
|
+
.update()
|
|
365
|
+
.set({
|
|
366
|
+
core_hash: destination.storageHash,
|
|
367
|
+
profile_hash: destination.profileHash,
|
|
368
|
+
updated_at: NOW,
|
|
369
|
+
...(mergedInvariants !== undefined ? { invariants: mergedInvariants } : {}),
|
|
370
|
+
})
|
|
371
|
+
.where(marker.space.eq(space).and(marker.core_hash.eq(expectedFrom)))
|
|
372
|
+
.returning(marker.space)
|
|
373
|
+
.build();
|
|
374
|
+
|
|
375
|
+
const rows = await execute((q) => this.lower(q, { contract: undefined }), driver, query);
|
|
376
|
+
return rows.length > 0;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Appends a ledger entry for `space`. See the
|
|
381
|
+
* `SqlControlAdapter.writeLedgerEntry` contract.
|
|
382
|
+
*/
|
|
383
|
+
async writeLedgerEntry(
|
|
384
|
+
driver: ControlDriverInstance<'sql', 'sqlite'>,
|
|
385
|
+
space: string,
|
|
386
|
+
entry: {
|
|
387
|
+
readonly edgeId: string;
|
|
388
|
+
readonly from: string;
|
|
389
|
+
readonly to: string;
|
|
390
|
+
readonly migrationName: string;
|
|
391
|
+
readonly migrationHash: string;
|
|
392
|
+
readonly operations: readonly unknown[];
|
|
393
|
+
},
|
|
394
|
+
): Promise<void> {
|
|
395
|
+
await execute(
|
|
396
|
+
(query) => this.lower(query, { contract: undefined }),
|
|
397
|
+
driver,
|
|
398
|
+
ledger
|
|
399
|
+
.insert({
|
|
400
|
+
space,
|
|
401
|
+
migration_name: entry.migrationName,
|
|
402
|
+
migration_hash: entry.migrationHash,
|
|
403
|
+
origin_core_hash: entry.from,
|
|
404
|
+
destination_core_hash: entry.to,
|
|
405
|
+
operations: entry.operations,
|
|
406
|
+
})
|
|
407
|
+
.build(),
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
private async readMarkerResult(driver: ControlDriverInstance<'sql', 'sqlite'>, space: string) {
|
|
412
|
+
const lower = (query: AnyQueryAst) => this.lower(query, { contract: undefined });
|
|
413
|
+
const probe = sqliteCatalog
|
|
414
|
+
.select(sqliteCatalog.name)
|
|
415
|
+
.where(sqliteCatalog.type.eq('table').and(sqliteCatalog.name.eq('_prisma_marker')))
|
|
416
|
+
.build();
|
|
417
|
+
const exists = await execute(lower, driver, probe);
|
|
418
|
+
if (exists.length === 0) return { kind: 'no-table' as const };
|
|
419
|
+
|
|
420
|
+
const fetch = marker
|
|
421
|
+
.select(
|
|
422
|
+
marker.core_hash,
|
|
423
|
+
marker.profile_hash,
|
|
424
|
+
marker.contract_json,
|
|
425
|
+
marker.canonical_version,
|
|
426
|
+
marker.updated_at,
|
|
427
|
+
marker.app_tag,
|
|
428
|
+
marker.meta,
|
|
429
|
+
marker.invariants,
|
|
430
|
+
)
|
|
431
|
+
.where(marker.space.eq(space))
|
|
432
|
+
.build();
|
|
433
|
+
const result = await execute(lower, driver, fetch);
|
|
434
|
+
const row = result[0];
|
|
435
|
+
if (!row) return { kind: 'absent' as const };
|
|
436
|
+
return {
|
|
437
|
+
kind: 'present' as const,
|
|
438
|
+
record: parseContractMarkerRow(decodeSqliteMarkerRow(row)),
|
|
439
|
+
};
|
|
233
440
|
}
|
|
234
441
|
|
|
235
442
|
async introspect(
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
DdlColumn,
|
|
3
|
+
DdlColumnDefaultVisitor,
|
|
4
|
+
FunctionColumnDefault,
|
|
5
|
+
LiteralColumnDefault,
|
|
6
|
+
} from '@prisma-next/sql-relational-core/ast';
|
|
7
|
+
import type {
|
|
8
|
+
SqliteCreateTable,
|
|
9
|
+
SqliteDdlNode,
|
|
10
|
+
SqliteDdlVisitor,
|
|
11
|
+
} from '@prisma-next/target-sqlite/ddl';
|
|
12
|
+
import { escapeLiteral } from '@prisma-next/target-sqlite/sql-utils';
|
|
13
|
+
import type { SqliteLoweredStatement } from './types';
|
|
14
|
+
|
|
15
|
+
class SqliteDdlVisitorImpl implements SqliteDdlVisitor<string> {
|
|
16
|
+
createTable(node: SqliteCreateTable): string {
|
|
17
|
+
const ifNotExists = node.ifNotExists ? 'IF NOT EXISTS ' : '';
|
|
18
|
+
const tableRef = node.table;
|
|
19
|
+
const columnDefs = node.columns.map((column) => renderColumn(column)).join(',\n ');
|
|
20
|
+
return `CREATE TABLE ${ifNotExists}${tableRef} (\n ${columnDefs}\n )`;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const defaultVisitor: DdlColumnDefaultVisitor<string> = {
|
|
25
|
+
literal(node: LiteralColumnDefault): string {
|
|
26
|
+
const { value } = node;
|
|
27
|
+
if (typeof value === 'string') {
|
|
28
|
+
return `DEFAULT '${escapeLiteral(value)}'`;
|
|
29
|
+
}
|
|
30
|
+
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
31
|
+
return `DEFAULT ${String(value)}`;
|
|
32
|
+
}
|
|
33
|
+
if (value === null) {
|
|
34
|
+
return 'DEFAULT NULL';
|
|
35
|
+
}
|
|
36
|
+
return `DEFAULT '${JSON.stringify(value)}'`;
|
|
37
|
+
},
|
|
38
|
+
function(node: FunctionColumnDefault): string {
|
|
39
|
+
if (node.expression === 'autoincrement()') {
|
|
40
|
+
return '';
|
|
41
|
+
}
|
|
42
|
+
return `DEFAULT (${node.expression})`;
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
function renderColumn(column: DdlColumn): string {
|
|
47
|
+
if (column.type.includes('AUTOINCREMENT')) {
|
|
48
|
+
return `${column.name} ${column.type}`;
|
|
49
|
+
}
|
|
50
|
+
const parts = [column.name, column.type];
|
|
51
|
+
if (column.notNull) {
|
|
52
|
+
parts.push('NOT NULL');
|
|
53
|
+
}
|
|
54
|
+
if (column.primaryKey) {
|
|
55
|
+
parts.push('PRIMARY KEY');
|
|
56
|
+
}
|
|
57
|
+
const defaultClause = column.default ? column.default.accept(defaultVisitor) : '';
|
|
58
|
+
if (defaultClause.length > 0) {
|
|
59
|
+
parts.push(defaultClause);
|
|
60
|
+
}
|
|
61
|
+
return parts.join(' ');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function renderLoweredDdl(ast: SqliteDdlNode): SqliteLoweredStatement {
|
|
65
|
+
const sql = ast.accept(new SqliteDdlVisitorImpl());
|
|
66
|
+
return Object.freeze({ sql, params: Object.freeze([]) });
|
|
67
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const DESIGNATOR_LESS_UTC_DATETIME = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(\.\d+)?$/;
|
|
2
|
+
|
|
3
|
+
export function coerceLedgerAppliedAt(value: Date | string): Date {
|
|
4
|
+
if (value instanceof Date) {
|
|
5
|
+
return value;
|
|
6
|
+
}
|
|
7
|
+
if (DESIGNATOR_LESS_UTC_DATETIME.test(value)) {
|
|
8
|
+
return new Date(`${value.replace(' ', 'T')}Z`);
|
|
9
|
+
}
|
|
10
|
+
return new Date(value);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function operationCountFromStored(operations: unknown): number {
|
|
14
|
+
if (Array.isArray(operations)) {
|
|
15
|
+
return operations.length;
|
|
16
|
+
}
|
|
17
|
+
if (typeof operations === 'string') {
|
|
18
|
+
try {
|
|
19
|
+
const parsed: unknown = JSON.parse(operations);
|
|
20
|
+
return Array.isArray(parsed) ? parsed.length : 0;
|
|
21
|
+
} catch {
|
|
22
|
+
return 0;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return 0;
|
|
26
|
+
}
|