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