@prisma/orm-target-sqlite 8.0.0-rc.17-dev.36 → 8.0.0-rc.17-dev.38

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.
Files changed (69) hide show
  1. package/dist/{adapter-Cfp6xn4m-BtODbERC.mjs → adapter-Cfp6xn4m-CqnRsbnV.mjs} +2 -2
  2. package/dist/{adapter-Cfp6xn4m-BtODbERC.mjs.map → adapter-Cfp6xn4m-CqnRsbnV.mjs.map} +1 -1
  3. package/dist/adapter.mjs +2 -2
  4. package/dist/adapter__adapter.mjs +1 -1
  5. package/dist/adapter__control.mjs +3 -3
  6. package/dist/adapter__runtime.mjs +1 -1
  7. package/dist/adapter__sql-renderer.mjs +1 -1
  8. package/dist/{control-DJq7MF_s.mjs → control-CVmnzYUI.mjs} +113 -18
  9. package/dist/control-CVmnzYUI.mjs.map +1 -0
  10. package/dist/{control-4njwExvT.mjs → control-DKT_rL3s.mjs} +11 -3
  11. package/dist/control-DKT_rL3s.mjs.map +1 -0
  12. package/dist/driver.mjs +1 -1
  13. package/dist/driver__control.mjs +1 -1
  14. package/dist/{migration-CIKnw9my.mjs → migration-DOkUBr8w.mjs} +2 -2
  15. package/dist/{migration-CIKnw9my.mjs.map → migration-DOkUBr8w.mjs.map} +1 -1
  16. package/dist/{migration-BsYgVFeM.d.mts → migration-wH0Hw2VD.d.mts} +3 -3
  17. package/dist/{migration-BsYgVFeM.d.mts.map → migration-wH0Hw2VD.d.mts.map} +1 -1
  18. package/dist/{op-factory-call-C-6Lxtlm.d.mts → op-factory-call-DAI1NqNz.d.mts} +16 -10
  19. package/dist/op-factory-call-DAI1NqNz.d.mts.map +1 -0
  20. package/dist/{op-factory-call-gwVgzGbc-caCULmpj.mjs → op-factory-call-hxIbAMSY-CinxsQHg.mjs} +234 -25
  21. package/dist/op-factory-call-hxIbAMSY-CinxsQHg.mjs.map +1 -0
  22. package/dist/{planner-Ddpm7j-w-BzyQ-raY.mjs → planner-BENMBCnG-CW6-1do-.mjs} +65 -23
  23. package/dist/planner-BENMBCnG-CW6-1do-.mjs.map +1 -0
  24. package/dist/planner-produced-sqlite-migration-CB9UDEdR.d.mts +1 -0
  25. package/dist/{planner-produced-sqlite-migration-D50U99OT--9-v0jj6.d.mts → planner-produced-sqlite-migration-CkHb8T4n-SM_tztzo.d.mts} +3 -3
  26. package/dist/planner-produced-sqlite-migration-CkHb8T4n-SM_tztzo.d.mts.map +1 -0
  27. package/dist/{planner-produced-sqlite-migration-Cj_QCROn-0eatEGzt.mjs → planner-produced-sqlite-migration-DjEZTiif-DpC6rk_q.mjs} +4 -4
  28. package/dist/{planner-produced-sqlite-migration-Cj_QCROn-0eatEGzt.mjs.map → planner-produced-sqlite-migration-DjEZTiif-DpC6rk_q.mjs.map} +1 -1
  29. package/dist/{planner-DRDXnFF-.d.mts → planner-tv5z1cn8.d.mts} +5 -4
  30. package/dist/planner-tv5z1cn8.d.mts.map +1 -0
  31. package/dist/{render-ops-CBkWzmKj-6vfxyU3K.mjs → render-ops-B889CY71-JvEeW9B4.mjs} +3 -3
  32. package/dist/{render-ops-CBkWzmKj-6vfxyU3K.mjs.map → render-ops-B889CY71-JvEeW9B4.mjs.map} +1 -1
  33. package/dist/{sql-utils-C2O4zVkP-Dz3Hb85I.mjs → sql-utils-XGT5Esi2-5k9pvJeF.mjs} +30 -3
  34. package/dist/sql-utils-XGT5Esi2-5k9pvJeF.mjs.map +1 -0
  35. package/dist/{sqlite-contract-view-BrXalcTm-DYDH4LQC.mjs → sqlite-contract-view-TELBWEt4-CVsdZnpX.mjs} +3 -3
  36. package/dist/{sqlite-contract-view-BrXalcTm-DYDH4LQC.mjs.map → sqlite-contract-view-TELBWEt4-CVsdZnpX.mjs.map} +1 -1
  37. package/dist/{sqlite-migration-omhBdJEh-CizoFjMg.d.mts → sqlite-migration-BqbQmUY1-CTmurARU.d.mts} +9 -3
  38. package/dist/{sqlite-migration-omhBdJEh-CizoFjMg.d.mts.map → sqlite-migration-BqbQmUY1-CTmurARU.d.mts.map} +1 -1
  39. package/dist/{sqlite-migration-D7DWt0JL-D_5jFkOx.mjs → sqlite-migration-CTNkjYJ1-eRdmsbpK.mjs} +235 -66
  40. package/dist/sqlite-migration-CTNkjYJ1-eRdmsbpK.mjs.map +1 -0
  41. package/dist/{tables-CvLOKCiJ-DYQkJ90L.d.mts → tables-DD_EmImp-B8ZaVBMr.d.mts} +16 -7
  42. package/dist/tables-DD_EmImp-B8ZaVBMr.d.mts.map +1 -0
  43. package/dist/target.d.mts +6 -6
  44. package/dist/target.mjs +9 -9
  45. package/dist/target__control.mjs +2 -2
  46. package/dist/target__migration.d.mts +2 -2
  47. package/dist/target__migration.mjs +2 -2
  48. package/dist/target__op-factory-call.d.mts +1 -1
  49. package/dist/target__op-factory-call.mjs +1 -1
  50. package/dist/target__planner-produced-sqlite-migration.d.mts +2 -2
  51. package/dist/target__planner-produced-sqlite-migration.mjs +1 -1
  52. package/dist/target__planner.d.mts +1 -1
  53. package/dist/target__planner.mjs +1 -1
  54. package/dist/target__render-ops.mjs +1 -1
  55. package/dist/target__runtime.mjs +1 -1
  56. package/dist/target__runtime.mjs.map +1 -1
  57. package/dist/target__sql-utils.mjs +1 -1
  58. package/package.json +9 -9
  59. package/dist/control-4njwExvT.mjs.map +0 -1
  60. package/dist/control-DJq7MF_s.mjs.map +0 -1
  61. package/dist/op-factory-call-C-6Lxtlm.d.mts.map +0 -1
  62. package/dist/op-factory-call-gwVgzGbc-caCULmpj.mjs.map +0 -1
  63. package/dist/planner-DRDXnFF-.d.mts.map +0 -1
  64. package/dist/planner-Ddpm7j-w-BzyQ-raY.mjs.map +0 -1
  65. package/dist/planner-produced-sqlite-migration-CWUahMOX.d.mts +0 -1
  66. package/dist/planner-produced-sqlite-migration-D50U99OT--9-v0jj6.d.mts.map +0 -1
  67. package/dist/sql-utils-C2O4zVkP-Dz3Hb85I.mjs.map +0 -1
  68. package/dist/sqlite-migration-D7DWt0JL-D_5jFkOx.mjs.map +0 -1
  69. package/dist/tables-CvLOKCiJ-DYQkJ90L.d.mts.map +0 -1
@@ -2,21 +2,22 @@ import { o as sqliteInteger } from "./data-types-pFQMl37_-B-3aK_wd.mjs";
2
2
  import { n as sqliteError } from "./errors-DnR-sMHS-KkMLIP2U.mjs";
3
3
  import { n as decodeSqliteDatetime, r as encodeSqliteDatetime } from "./codecs-BctEWIFt-DgMd3dUl.mjs";
4
4
  import { a as tableExistsAst, i as indexExistsAst, n as columnNameTakenAst, o as tableNameTakenAst, r as createTable, t as columnExistsAst } from "./ddl-CDoa1Ehx-BOT670XL.mjs";
5
- import { n as quoteIdentifier, t as escapeLiteral } from "./sql-utils-C2O4zVkP-Dz3Hb85I.mjs";
5
+ import { n as escapeLiteral, r as quoteIdentifier, t as SQLITE_QUOTING } from "./sql-utils-XGT5Esi2-5k9pvJeF.mjs";
6
6
  import { r as stripOuterParens } from "./default-normalizer-Ch3VFnEH-C-h1XtlJ.mjs";
7
7
  import { t as buildTargetDetails } from "./planner-target-details-CIQ789Gc-C5U4yVY9.mjs";
8
8
  import { DdlColumn, FunctionColumnDefault, LiteralColumnDefault, opaqueSql } from "@prisma/orm-family-sql/relational-core/ast";
9
9
  import { ifDefined } from "@prisma/orm-framework/utils/defined";
10
10
  import { blindCast } from "@prisma/orm-framework/utils/casts";
11
11
  import { dataTypeParams, renderSqlTypeName, sqlDataTypeOfCodec } from "@prisma/orm-family-sql/contract/data-type";
12
+ import { type } from "arktype";
12
13
  import { issueOutcome } from "@prisma/orm-framework/components/control";
13
- import { createSqlTextSources } from "@prisma/orm-family-sql/family/control";
14
+ import { checkSqlExpressionText, convertedColumnMeta, createSqlTextSources, isUnfilledSlot, requiredColumnMeta, slotSource, sqlOfSlot } from "@prisma/orm-family-sql/family/control";
14
15
  import { RelationalSchemaNodeKind } from "@prisma/orm-family-sql/schema-ir/types";
15
16
  import { assertNever } from "@prisma/orm-framework/utils/internal-error";
16
17
  import { errorMigrationOperationOptionRemoved, unfilledPlaceholderOperation } from "@prisma/orm-framework/errors/migration";
17
18
  import { REFERENTIAL_ACTION_SQL } from "@prisma/orm-family-sql/contract/referential-action-sql";
18
19
  import { TsExpression, jsonToTsSource, tsArraySource, tsObjectSource, tsQuotedTextSource } from "@prisma/orm-framework/ts-render";
19
- //#region ../../../3-targets/3-targets/sqlite/dist/op-factory-call-gwVgzGbc.mjs
20
+ //#region ../../../3-targets/3-targets/sqlite/dist/op-factory-call-hxIbAMSY.mjs
20
21
  /**
21
22
  * Low-level DDL fragment builders for SQLite migrations.
22
23
  *
@@ -260,18 +261,35 @@ function renameTableSteps(fromName, toName) {
260
261
  const via = quoteIdentifier(viaName);
261
262
  return [step(`rename table "${fromName}" to "${viaName}" (SQLite table names are case-insensitive)`, `ALTER TABLE ${from} RENAME TO ${via}`), step(`rename table "${viaName}" to "${toName}"`, `ALTER TABLE ${via} RENAME TO ${to}`)];
262
263
  }
264
+ /** The table a rebuild of `tableName` creates, copies into, and renames to `tableName`. */
265
+ function rebuiltTableName(tableName) {
266
+ return `_prisma_new_${tableName}`;
267
+ }
263
268
  async function recreateTable(args, lowerer) {
264
- const { tableName, contractTable, schemaColumnNames, indexes, summary, postchecks, operationClass } = args;
269
+ const { tableName, contractTable, schemaColumnNames, indexes, summary, postchecks, operationClass, copyExpressions = {}, temporaryValues = {}, retypedColumns = [] } = args;
265
270
  refuseEarlierColumnSpecs("recreateTable", tableName, contractTable.columns);
266
- const tempName = `_prisma_new_${tableName}`;
271
+ const tempName = rebuiltTableName(tableName);
267
272
  const liveSet = new Set(schemaColumnNames);
268
- const sharedColumns = contractTable.columns.filter((c) => liveSet.has(c.name)).map((c) => c.name);
273
+ const convertedBy = (column) => Object.hasOwn(copyExpressions, column) ? copyExpressions[column] : void 0;
274
+ const copiedFrom = (column) => convertedBy(column) ?? (Object.hasOwn(temporaryValues, column) ? temporaryValues[column] : void 0);
275
+ const sharedColumns = contractTable.columns.filter((c) => liveSet.has(c.name) || copiedFrom(c.name) !== void 0).map((c) => c.name);
269
276
  const columnList = sharedColumns.map(quoteIdentifier).join(", ");
277
+ const selectList = sharedColumns.map((column) => copiedFrom(column) ?? quoteIdentifier(column)).join(", ");
270
278
  const indexStatements = indexes.map((idx) => ({
271
279
  description: `recreate index "${idx.name}" on "${tableName}"`,
272
280
  sql: buildCreateIndexSql(tableName, idx.name, idx.columns)
273
281
  }));
274
- const copyStep = sharedColumns.length > 0 ? [step(`copy data from "${tableName}" to "${tempName}"`, `INSERT INTO ${quoteIdentifier(tempName)} (${columnList}) SELECT ${columnList} FROM ${quoteIdentifier(tableName)}`)] : [];
282
+ const conversions = Object.fromEntries(sharedColumns.flatMap((column) => {
283
+ const expression = convertedBy(column);
284
+ return expression === void 0 ? [] : [[column, expression]];
285
+ }));
286
+ const copyStep = sharedColumns.length > 0 ? [{
287
+ ...step(`copy data from "${tableName}" to "${tempName}"`, `INSERT INTO ${quoteIdentifier(tempName)} (${columnList}) SELECT ${selectList} FROM ${quoteIdentifier(tableName)}`),
288
+ ...Object.keys(conversions).length === 0 ? {} : { meta: { conversionCopy: {
289
+ table: tableName,
290
+ expressions: conversions
291
+ } } }
292
+ }] : [];
275
293
  const defaultClauses = await renderSpecDefaults(contractTable, tableName, lowerer);
276
294
  const clauseByColumn = new Map(contractTable.columns.map((column, index) => [column.name, defaultClauses[index] ?? ""]));
277
295
  const tableSteps = await tableExistsSteps(lowerer, tableName);
@@ -296,10 +314,70 @@ async function recreateTable(args, lowerer) {
296
314
  postcheck: [
297
315
  step(`verify table "${tableName}" exists`, tableSteps.present.sql, tableSteps.present.params),
298
316
  step(`verify temp table "${tempName}" is gone`, tempSteps.absent.sql, tempSteps.absent.params),
299
- ...postchecks.flatMap((check) => "sql" in check ? [check] : defaultPostcheck(tableName, check, clauseByColumn.get(check.columnDefault)))
317
+ ...postchecks.flatMap((check) => "sql" in check ? [markedStep(tableName, check)] : defaultPostcheck(tableName, check, clauseByColumn.get(check.columnDefault))),
318
+ ...contractTable.columns.flatMap((column) => {
319
+ const by = Object.hasOwn(conversions, column.name) ? "conversion" : retypedColumns.includes(column.name) ? "cast" : void 0;
320
+ return by === void 0 ? [] : [{
321
+ ...storageClassCheck(tableName, column),
322
+ meta: convertedColumnMeta(tableName, column.name, by)
323
+ }];
324
+ })
300
325
  ]
301
326
  };
302
327
  }
328
+ function markedStep(tableName, check) {
329
+ const { requiredColumn, ...step } = check;
330
+ const meta = { ...requiredColumn === void 0 ? {} : requiredColumnMeta(tableName, requiredColumn.column, requiredColumn.reason) };
331
+ return Object.keys(meta).length === 0 ? step : {
332
+ ...step,
333
+ meta
334
+ };
335
+ }
336
+ /**
337
+ * The storage classes SQLite keeps a value in once a column of `typeSql` has taken it: the column's
338
+ * type affinity converts what it can, so a value of another class is one it could not convert.
339
+ * See https://www.sqlite.org/datatype3.html#determination_of_column_affinity.
340
+ */
341
+ function affinityStorageClasses(typeSql) {
342
+ const declared = typeSql.toUpperCase();
343
+ if (declared.includes("INT")) return ["integer"];
344
+ if ([
345
+ "CHAR",
346
+ "CLOB",
347
+ "TEXT"
348
+ ].some((part) => declared.includes(part))) return ["text"];
349
+ if (declared === "" || declared.includes("BLOB")) return ["blob"];
350
+ if ([
351
+ "REAL",
352
+ "FLOA",
353
+ "DOUB"
354
+ ].some((part) => declared.includes(part))) return ["real"];
355
+ return ["integer", "real"];
356
+ }
357
+ /**
358
+ * The check that the copy into a rebuilt table converted every value of a retyped column to the
359
+ * column's new type. SQLite keeps a value it cannot convert in its old storage class, so a value of
360
+ * any other class than the column's affinity keeps fails the check.
361
+ */
362
+ function storageClassCheck(tableName, column) {
363
+ const classes = ["null", ...affinityStorageClasses(column.typeSql)].map((storageClass) => `'${storageClass}'`).join(", ");
364
+ return {
365
+ description: `verify every value of "${column.name}" on "${tableName}" is stored as ${column.typeSql}`,
366
+ sql: `SELECT NOT EXISTS (SELECT 1 FROM ${quoteIdentifier(tableName)} WHERE typeof(${quoteIdentifier(column.name)}) NOT IN (${classes}))`
367
+ };
368
+ }
369
+ const ConversionCopyMeta = type({ conversionCopy: {
370
+ table: "string",
371
+ expressions: "Record<string, string>"
372
+ } });
373
+ /**
374
+ * The table a rebuild's copy step reads and the copy expression of each column its conversions
375
+ * convert, when the step converts any: an error of the step may be an error of one of them.
376
+ */
377
+ function conversionCopyOf(step) {
378
+ const meta = ConversionCopyMeta(step.meta ?? {});
379
+ return meta instanceof type.errors ? void 0 : meta.conversionCopy;
380
+ }
303
381
  function renderSpecDefaults(spec, tableName, lowerer) {
304
382
  return Promise.all(spec.columns.map((column) => renderSpecDefault$1(column, tableName, lowerer)));
305
383
  }
@@ -386,7 +464,7 @@ function columnNameFromDefaultIssuePath(issue) {
386
464
  * `RecreateTableCall` doesn't have to carry `SchemaDiffIssue` objects through
387
465
  * to render time.
388
466
  */
389
- function buildRecreatePostchecks(tableName, issues, spec) {
467
+ function buildRecreatePostchecks(tableName, issues, spec, copiedColumns = /* @__PURE__ */ new Set(), backfilledColumns = /* @__PURE__ */ new Set()) {
390
468
  const checks = [];
391
469
  const t = escapeLiteral(tableName);
392
470
  const byName = new Map(spec.columns.map((c) => [c.name, c]));
@@ -395,6 +473,24 @@ function buildRecreatePostchecks(tableName, issues, spec) {
395
473
  let hasFkIssue = false;
396
474
  for (const issue of issues) {
397
475
  const nodeKind = nodeKindOf(issue);
476
+ if (nodeKind === RelationalSchemaNodeKind.column && issueOutcome(issue) === "not-found") {
477
+ const columnName = columnNameFromNode(issue);
478
+ const column = columnName === void 0 ? void 0 : byName.get(columnName);
479
+ if (columnName === void 0 || column === void 0) continue;
480
+ const takesNoValue = !column.nullable && column.default === void 0 && !copiedColumns.has(columnName);
481
+ checks.push({
482
+ description: `verify "${columnName}" nullability on "${tableName}"`,
483
+ sql: `SELECT COUNT(*) > 0 FROM pragma_table_info('${t}') WHERE name = '${escapeLiteral(columnName)}' AND "notnull" = ${column.nullable ? 0 : 1}`,
484
+ ...backfilledColumns.has(columnName) ? { requiredColumn: {
485
+ column: columnName,
486
+ reason: "backfillMissedRows"
487
+ } } : takesNoValue ? { requiredColumn: {
488
+ column: columnName,
489
+ reason: "newField"
490
+ } } : {}
491
+ });
492
+ continue;
493
+ }
398
494
  if (nodeKind === RelationalSchemaNodeKind.column && issueOutcome(issue) === "not-equal") {
399
495
  const columnName = columnNameFromNode(issue);
400
496
  if (columnName === void 0) continue;
@@ -403,7 +499,11 @@ function buildRecreatePostchecks(tableName, issues, spec) {
403
499
  const actual = blindCast(issue.actual);
404
500
  if (expected.nullable !== actual.nullable) checks.push({
405
501
  description: `verify "${columnName}" nullability on "${tableName}"`,
406
- sql: `SELECT COUNT(*) > 0 FROM pragma_table_info('${t}') WHERE name = '${c}' AND "notnull" = ${expected.nullable ? 0 : 1}`
502
+ sql: `SELECT COUNT(*) > 0 FROM pragma_table_info('${t}') WHERE name = '${c}' AND "notnull" = ${expected.nullable ? 0 : 1}`,
503
+ ...expected.nullable ? {} : { requiredColumn: {
504
+ column: columnName,
505
+ reason: backfilledColumns.has(columnName) ? "backfillMissedRows" : "nullValues"
506
+ } }
407
507
  });
408
508
  if (columnTypeChanged(expected, actual)) {
409
509
  const colSpec = byName.get(columnName);
@@ -791,6 +891,86 @@ var RenameColumnCall = class extends SqliteOpFactoryCallNode {
791
891
  return [];
792
892
  }
793
893
  };
894
+ function refuseUnsafeCopyExpressions(tableName, option, expressions) {
895
+ for (const [column, expression] of Object.entries(expressions)) {
896
+ if (expression.kind !== "sql") continue;
897
+ const refused = checkSqlExpressionText(expression.sql, SQLITE_QUOTING);
898
+ if (refused === void 0) continue;
899
+ const call = `recreateTable({ tableName: ${jsonToTsSource(tableName)} })`;
900
+ throw sqliteError("MIGRATION.SQL_EXPRESSION_INVALID", `${call}: ${option}.${column} ${JSON.stringify(expression.sql)} holds ${refused}, which could end or extend the INSERT … SELECT statement it is written into`, {
901
+ why: "A copy expression or a temporary value is written into the SELECT that copies the rows into the rebuilt table. The check refuses a semicolon, a SQL comment, dollar quoting, and a comma or a statement keyword outside parentheses, since each could end or extend that statement; it does not check that the text is valid SQL.",
902
+ fix: `Write ${option}.${column} as one SQL expression over the old table's columns, such as CAST("${column}" AS INTEGER). If it holds a comma or a keyword such as FROM outside parentheses, wrap it in parentheses.`,
903
+ meta: {
904
+ call,
905
+ field: `${option}.${column}`,
906
+ expression: expression.sql,
907
+ refused
908
+ }
909
+ });
910
+ }
911
+ }
912
+ function refuseUnknownCopyExpressionColumns(tableName, contractTable, named) {
913
+ const columns = contractTable.columns.map((column) => column.name);
914
+ const unknown = [
915
+ ...Object.keys(named.copyExpressions).map((key) => ({
916
+ option: `copyExpressions.${key}`,
917
+ key
918
+ })),
919
+ ...Object.keys(named.temporaryValues).map((key) => ({
920
+ option: `temporaryValues.${key}`,
921
+ key
922
+ })),
923
+ ...named.retypedColumns.map((key) => ({
924
+ option: `retypedColumns entry ${JSON.stringify(key)}`,
925
+ key
926
+ }))
927
+ ].find(({ key }) => !columns.includes(key));
928
+ if (unknown === void 0) return;
929
+ const call = `recreateTable({ tableName: ${jsonToTsSource(tableName)} })`;
930
+ throw sqliteError("MIGRATION.COPY_EXPRESSION_COLUMN_UNKNOWN", `${call}: ${unknown.option} names no column of the new table. Its columns are ${columns.map((column) => JSON.stringify(column)).join(", ")}.`, {
931
+ why: "Each key of copyExpressions and temporaryValues, and each entry of retypedColumns, names a column of the new table. One that names no column applies to nothing, so the column meant would be copied unchanged or left without a value, or its values would go unchecked.",
932
+ fix: "Name a column in contractTable.columns, with the same letters and case.",
933
+ meta: {
934
+ call,
935
+ key: unknown.key,
936
+ columns
937
+ }
938
+ });
939
+ }
940
+ /**
941
+ * A temporary value fills a column the old table does not have, and only that: for a column the
942
+ * old table has it would replace every existing value, and a column with a copy expression is
943
+ * already filled by its conversion.
944
+ */
945
+ function refuseMisplacedTemporaryValues(tableName, schemaColumnNames, copyExpressions, temporaryValues) {
946
+ const call = `recreateTable({ tableName: ${jsonToTsSource(tableName)} })`;
947
+ for (const column of Object.keys(temporaryValues)) {
948
+ if (schemaColumnNames.includes(column)) throw sqliteError("MIGRATION.TEMPORARY_VALUE_INVALID", `${call}: temporaryValues.${column} names a column the old table already has, so the copy would replace every value of it.`, {
949
+ why: "A temporary value fills a new column in the existing rows. A column the old table has keeps its values through the copy, or is converted by a copy expression.",
950
+ fix: `Remove temporaryValues.${column}. To change the values of "${column}", write a conversion in copyExpressions.${column} instead.`,
951
+ meta: {
952
+ call,
953
+ column,
954
+ reason: "existingColumn"
955
+ }
956
+ });
957
+ if (Object.hasOwn(copyExpressions, column)) throw sqliteError("MIGRATION.TEMPORARY_VALUE_INVALID", `${call}: "${column}" is named in both copyExpressions and temporaryValues, so it would be filled twice.`, {
958
+ why: "A column is filled either by the conversion in copyExpressions or by a temporary value, not both.",
959
+ fix: `Keep one of copyExpressions.${column} and temporaryValues.${column}.`,
960
+ meta: {
961
+ call,
962
+ column,
963
+ reason: "alsoConverted"
964
+ }
965
+ });
966
+ }
967
+ }
968
+ function sqlOfSlots(slots) {
969
+ return Object.fromEntries(Object.entries(slots).flatMap(([column, slot]) => {
970
+ const sql = sqlOfSlot(slot);
971
+ return sql === void 0 ? [] : [[column, sql]];
972
+ }));
973
+ }
794
974
  var RecreateTableCall = class extends SqliteOpFactoryCallNode {
795
975
  factoryName = "recreateTable";
796
976
  operationClass;
@@ -801,14 +981,25 @@ var RecreateTableCall = class extends SqliteOpFactoryCallNode {
801
981
  summary;
802
982
  postchecks;
803
983
  label;
804
- /**
805
- * The columns whose values the copy can change, because their type changes. The planner reads
806
- * them to name what the recreate loses; they never reach the operation or `migration.ts`.
807
- */
808
- lossyColumns;
809
- constructor(args, lossyColumns = []) {
984
+ /** The conversion each named column of the new table is copied through, in place of the live column. */
985
+ copyExpressions;
986
+ /** The value each named new column takes in the existing rows. */
987
+ temporaryValues;
988
+ /** The columns whose type changes; the rebuild checks each after its copy, as a conversion when it has a copy expression and as SQLite's cast otherwise. */
989
+ retypedColumns;
990
+ constructor(args) {
810
991
  super();
811
- this.lossyColumns = Object.freeze([...lossyColumns]);
992
+ refuseUnsafeCopyExpressions(args.tableName, "copyExpressions", args.copyExpressions ?? {});
993
+ refuseUnsafeCopyExpressions(args.tableName, "temporaryValues", args.temporaryValues ?? {});
994
+ refuseUnknownCopyExpressionColumns(args.tableName, args.contractTable, {
995
+ copyExpressions: args.copyExpressions ?? {},
996
+ temporaryValues: args.temporaryValues ?? {},
997
+ retypedColumns: args.retypedColumns ?? []
998
+ });
999
+ refuseMisplacedTemporaryValues(args.tableName, args.schemaColumnNames, args.copyExpressions ?? {}, args.temporaryValues ?? {});
1000
+ this.copyExpressions = Object.freeze({ ...args.copyExpressions });
1001
+ this.temporaryValues = Object.freeze({ ...args.temporaryValues });
1002
+ this.retypedColumns = Object.freeze([...args.retypedColumns ?? []]);
812
1003
  this.tableName = args.tableName;
813
1004
  this.contractTable = args.contractTable;
814
1005
  this.schemaColumnNames = args.schemaColumnNames;
@@ -824,6 +1015,8 @@ var RecreateTableCall = class extends SqliteOpFactoryCallNode {
824
1015
  factory: this.factoryName,
825
1016
  tableName: this.tableName
826
1017
  } });
1018
+ const copies = Object.entries(this.copyExpressions);
1019
+ if (copies.some(([, expression]) => isUnfilledSlot(expression))) return unfilledPlaceholderOperation(this.label);
827
1020
  return recreateTable({
828
1021
  tableName: this.tableName,
829
1022
  contractTable: this.contractTable,
@@ -831,14 +1024,23 @@ var RecreateTableCall = class extends SqliteOpFactoryCallNode {
831
1024
  indexes: this.indexes,
832
1025
  summary: this.summary,
833
1026
  postchecks: this.postchecks,
834
- operationClass: this.operationClass
1027
+ operationClass: this.operationClass,
1028
+ retypedColumns: this.retypedColumns,
1029
+ ...copies.length === 0 ? {} : { copyExpressions: sqlOfSlots(this.copyExpressions) },
1030
+ ...Object.keys(this.temporaryValues).length === 0 ? {} : { temporaryValues: sqlOfSlots(this.temporaryValues) }
835
1031
  }, lowerer);
836
1032
  }
1033
+ #slotsSource(slots, sql) {
1034
+ return `{ ${Object.entries(slots).map(([column, expression]) => `${jsonToTsSource(column)}: ${slotSource(expression, sql)}`).join(", ")} }`;
1035
+ }
837
1036
  renderTypeScript() {
838
1037
  return this.#written().source;
839
1038
  }
840
1039
  importRequirements() {
841
- return this.#written().imports;
1040
+ return [...Object.values(this.copyExpressions).some(isUnfilledSlot) ? [{
1041
+ moduleSpecifier: TARGET_MIGRATION_MODULE,
1042
+ symbol: "placeholder"
1043
+ }] : [], ...this.#written().imports];
842
1044
  }
843
1045
  #written() {
844
1046
  const sql = createSqlTextSources(TARGET_MIGRATION_MODULE);
@@ -850,7 +1052,10 @@ var RecreateTableCall = class extends SqliteOpFactoryCallNode {
850
1052
  ["indexes", jsonToTsSource(this.indexes)],
851
1053
  ["summary", jsonToTsSource(this.summary)],
852
1054
  ["postchecks", tsArraySource(this.postchecks.map(renderPostcheck))],
853
- ["operationClass", jsonToTsSource(this.operationClass)]
1055
+ ["operationClass", jsonToTsSource(this.operationClass)],
1056
+ ...Object.keys(this.copyExpressions).length === 0 ? [] : [["copyExpressions", this.#slotsSource(this.copyExpressions, sql)]],
1057
+ ...Object.keys(this.temporaryValues).length === 0 ? [] : [["temporaryValues", this.#slotsSource(this.temporaryValues, sql)]],
1058
+ ...this.retypedColumns.length === 0 ? [] : [["retypedColumns", jsonToTsSource(this.retypedColumns)]]
854
1059
  ])})`,
855
1060
  imports: sql.imports()
856
1061
  };
@@ -1051,19 +1256,22 @@ var DataTransformCall = class extends SqliteOpFactoryCallNode {
1051
1256
  label;
1052
1257
  tableName;
1053
1258
  columnName;
1054
- constructor(id, label, tableName, columnName) {
1259
+ /** The slot the user fills with the statement that fills the column: `<Model.field>:run`. */
1260
+ slot;
1261
+ constructor(id, label, tableName, columnName, slot) {
1055
1262
  super();
1056
1263
  this.id = id;
1057
1264
  this.label = label;
1058
1265
  this.tableName = tableName;
1059
1266
  this.columnName = columnName;
1267
+ this.slot = slot;
1060
1268
  this.freeze();
1061
1269
  }
1062
1270
  toOp(_lowerer) {
1063
1271
  return unfilledPlaceholderOperation(this.label);
1064
1272
  }
1065
1273
  renderTypeScript() {
1066
- const slot = `${this.tableName}-${this.columnName}-backfill-sql`;
1274
+ const slot = this.slot;
1067
1275
  return [
1068
1276
  "dataTransform({",
1069
1277
  ` id: ${jsonToTsSource(this.id)},`,
@@ -1146,7 +1354,8 @@ function renderSpecDefault(columnDefault, sql) {
1146
1354
  function renderPostcheck(postcheck) {
1147
1355
  if ("sql" in postcheck) return tsObjectSource(definedSourceEntries({
1148
1356
  description: jsonToTsSource(postcheck.description),
1149
- sql: tsQuotedTextSource(postcheck.sql)
1357
+ sql: tsQuotedTextSource(postcheck.sql),
1358
+ requiredColumn: postcheck.requiredColumn === void 0 ? void 0 : jsonToTsSource(postcheck.requiredColumn)
1150
1359
  }));
1151
1360
  return tsObjectSource(definedSourceEntries({
1152
1361
  description: jsonToTsSource(postcheck.description),
@@ -1154,6 +1363,6 @@ function renderPostcheck(postcheck) {
1154
1363
  }));
1155
1364
  }
1156
1365
  //#endregion
1157
- export { renameTableSteps as _, DropColumnCall as a, sqliteIdentifiersCollide as b, RawSqlCall as c, RenameTableCall as d, buildColumnTypeSql as f, isSqliteOpFactoryCall as g, indexReplacementCalls as h, DataTransformCall as i, RecreateTableCall as l, buildRecreateSummary as m, CreateIndexCall as n, DropIndexCall as o, buildRecreatePostchecks as p, CreateTableCall as r, DropTableCall as s, AddColumnCall as t, RenameColumnCall as u, renderDefaultLiteral as v, step as x, sqliteDefaultToDdlColumnDefault as y };
1366
+ export { sqliteIdentifiersCollide as C, sqliteDefaultToDdlColumnDefault as S, indexReplacementCalls as _, DropColumnCall as a, renameTableSteps as b, RawSqlCall as c, RenameTableCall as d, affinityStorageClasses as f, conversionCopyOf as g, buildRecreateSummary as h, DataTransformCall as i, RecreateTableCall as l, buildRecreatePostchecks as m, CreateIndexCall as n, DropIndexCall as o, buildColumnTypeSql as p, CreateTableCall as r, DropTableCall as s, AddColumnCall as t, RenameColumnCall as u, isSqliteOpFactoryCall as v, step as w, renderDefaultLiteral as x, rebuiltTableName as y };
1158
1367
 
1159
- //# sourceMappingURL=op-factory-call-gwVgzGbc-caCULmpj.mjs.map
1368
+ //# sourceMappingURL=op-factory-call-hxIbAMSY-CinxsQHg.mjs.map