@happyvertical/smrt-core 0.51.10 → 0.51.14
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/agents/change-feed.md +82 -0
- package/agents/generators.md +3 -1
- package/agents/schema-paths.md +23 -6
- package/dist/change-feed-sensitivity.d.ts +155 -0
- package/dist/change-feed-sensitivity.d.ts.map +1 -0
- package/dist/change-feed-sensitivity.js +139 -0
- package/dist/change-feed-sensitivity.js.map +1 -0
- package/dist/change-feed.d.ts +37 -10
- package/dist/change-feed.d.ts.map +1 -1
- package/dist/change-feed.js +128 -12
- package/dist/change-feed.js.map +1 -1
- package/dist/change-signals.d.ts.map +1 -1
- package/dist/change-signals.js +3 -0
- package/dist/change-signals.js.map +1 -1
- package/dist/class.d.ts +1 -1
- package/dist/class.d.ts.map +1 -1
- package/dist/class.js +4 -2
- package/dist/class.js.map +1 -1
- package/dist/decorators/index.d.ts +19 -0
- package/dist/decorators/index.d.ts.map +1 -1
- package/dist/decorators/index.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/manifest/static-manifest.js +1 -1
- package/dist/manifest/static-manifest.js.map +1 -1
- package/dist/manifest/store.js +1 -1
- package/dist/manifest.json +1 -1
- package/dist/migrations/differ.d.ts +64 -5
- package/dist/migrations/differ.d.ts.map +1 -1
- package/dist/migrations/differ.js +274 -37
- package/dist/migrations/differ.js.map +1 -1
- package/dist/migrations/index.d.ts +1 -1
- package/dist/migrations/index.d.ts.map +1 -1
- package/dist/migrations/index.js +2 -2
- package/dist/migrations/sqlite-rebuild.d.ts +41 -0
- package/dist/migrations/sqlite-rebuild.d.ts.map +1 -1
- package/dist/migrations/sqlite-rebuild.js +68 -13
- package/dist/migrations/sqlite-rebuild.js.map +1 -1
- package/dist/migrations.js +2 -2
- package/dist/node_modules/.pnpm/{minimatch@10.2.5 → minimatch@10.2.6}/node_modules/minimatch/dist/esm/assert-valid-pattern.js +1 -1
- package/dist/node_modules/.pnpm/{minimatch@10.2.5 → minimatch@10.2.6}/node_modules/minimatch/dist/esm/assert-valid-pattern.js.map +1 -1
- package/dist/node_modules/.pnpm/{minimatch@10.2.5 → minimatch@10.2.6}/node_modules/minimatch/dist/esm/ast.js +1 -1
- package/dist/node_modules/.pnpm/{minimatch@10.2.5 → minimatch@10.2.6}/node_modules/minimatch/dist/esm/ast.js.map +1 -1
- package/dist/node_modules/.pnpm/{minimatch@10.2.5 → minimatch@10.2.6}/node_modules/minimatch/dist/esm/brace-expressions.js +1 -1
- package/dist/node_modules/.pnpm/{minimatch@10.2.5 → minimatch@10.2.6}/node_modules/minimatch/dist/esm/brace-expressions.js.map +1 -1
- package/dist/node_modules/.pnpm/{minimatch@10.2.5 → minimatch@10.2.6}/node_modules/minimatch/dist/esm/escape.js +1 -1
- package/dist/node_modules/.pnpm/{minimatch@10.2.5 → minimatch@10.2.6}/node_modules/minimatch/dist/esm/escape.js.map +1 -1
- package/dist/node_modules/.pnpm/{minimatch@10.2.5 → minimatch@10.2.6}/node_modules/minimatch/dist/esm/index.js +1 -1
- package/dist/node_modules/.pnpm/{minimatch@10.2.5 → minimatch@10.2.6}/node_modules/minimatch/dist/esm/index.js.map +1 -1
- package/dist/node_modules/.pnpm/{minimatch@10.2.5 → minimatch@10.2.6}/node_modules/minimatch/dist/esm/unescape.js +1 -1
- package/dist/node_modules/.pnpm/{minimatch@10.2.5 → minimatch@10.2.6}/node_modules/minimatch/dist/esm/unescape.js.map +1 -1
- package/dist/registry/class-registration.d.ts +1 -1
- package/dist/registry/class-registration.d.ts.map +1 -1
- package/dist/registry/class-registration.js +50 -4
- package/dist/registry/class-registration.js.map +1 -1
- package/dist/registry/db-cache-key.d.ts +47 -0
- package/dist/registry/db-cache-key.d.ts.map +1 -0
- package/dist/registry/db-cache-key.js +201 -0
- package/dist/registry/db-cache-key.js.map +1 -0
- package/dist/registry/schema-builder.d.ts.map +1 -1
- package/dist/registry/schema-builder.js +6 -1
- package/dist/registry/schema-builder.js.map +1 -1
- package/dist/registry/types.d.ts +43 -0
- package/dist/registry/types.d.ts.map +1 -1
- package/dist/registry.d.ts +17 -1
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +20 -8
- package/dist/registry.js.map +1 -1
- package/dist/scanner/types.d.ts +2 -0
- package/dist/scanner/types.d.ts.map +1 -1
- package/dist/scanner/types.js.map +1 -1
- package/dist/schema/backfill.d.ts +16 -0
- package/dist/schema/backfill.d.ts.map +1 -0
- package/dist/schema/backfill.js +26 -0
- package/dist/schema/backfill.js.map +1 -0
- package/dist/schema/generator.d.ts.map +1 -1
- package/dist/schema/generator.js +3 -0
- package/dist/schema/generator.js.map +1 -1
- package/dist/schema/types.d.ts +20 -1
- package/dist/schema/types.d.ts.map +1 -1
- package/dist/smrt-knowledge.json +21 -21
- package/dist/vite-plugin/dev-plane-route.js +1 -1
- package/dist/vite-plugin/dev-plane-route.js.map +1 -1
- package/dist/vite-plugin/index.js +1 -1
- package/dist/vite-plugin/sveltekit-generator.d.ts.map +1 -1
- package/dist/vite-plugin/sveltekit-generator.js +54 -7
- package/dist/vite-plugin/sveltekit-generator.js.map +1 -1
- package/dist/vite-plugin/sync-apply-route.d.ts +7 -0
- package/dist/vite-plugin/sync-apply-route.d.ts.map +1 -1
- package/dist/vite-plugin/sync-apply-route.js +50 -3
- package/dist/vite-plugin/sync-apply-route.js.map +1 -1
- package/package.json +23 -23
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
export { type BackfillRecord, BackfillTableUnavailableError, BackfillTracker, type BackfillTrackerOptions, } from './backfill-tracker.js';
|
|
12
12
|
export { computeChecksum, normalizeSQL, shortChecksum, verifyChecksum, } from './checksum.js';
|
|
13
13
|
export type { DiffOptions } from './differ.js';
|
|
14
|
-
export { canonicalizeDefault, generateSchemaDiff, getSQLFromDiff, hasActionableChanges, isAdvisoryOnlyChange, isInfoOnlyChange, isManualOrAdvisoryChange, SchemaComparer, uniqueColumnIndexName, } from './differ.js';
|
|
14
|
+
export { canonicalizeDefault, generateSchemaDiff, getSQLFromDiff, hasActionableChanges, isAdvisoryOnlyChange, isInfoOnlyChange, isManualOrAdvisoryChange, REQUIRED_COLUMN_NOT_ADDED, SchemaComparer, uniqueColumnIndexName, } from './differ.js';
|
|
15
15
|
export { type DropFrameworkBaseTablesOptions, type DropFrameworkBaseTablesResult, dropFrameworkBaseTables, FRAMEWORK_BASE_TABLE_NAMES, type FrameworkBaseTableName, type FrameworkBaseTableRefusal, type FrameworkBaseTableReport, type FrameworkBaseTablesPlan, type PlanFrameworkBaseTableDropOptions, planFrameworkBaseTableDrop, } from './framework-base-tables.js';
|
|
16
16
|
export type { GeneratedMigration, GenerateMigrationOptions, } from './generator.js';
|
|
17
17
|
export { createMigrationDefinition, generateMigrationSequence, generateMigrationTimestamp, MigrationGenerator, } from './generator.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/migrations/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/migrations/index.ts"],"names":[],"mappings":"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"}
|
package/dist/migrations/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { parsePostgresTimeoutMs } from "../postgres-timeouts.js";
|
|
2
2
|
import { SQLITE_REBUILD_TABLE_PREFIX, buildSqliteRebuildStatements, isSqliteRebuildPlaceholder, planSqliteTableRebuilds, rewriteSqliteCreateTable, sqliteRebuildPlaceholderSql } from "./sqlite-rebuild.js";
|
|
3
|
-
import { SchemaComparer, canonicalizeDefault, generateSchemaDiff, getSQLFromDiff, hasActionableChanges, isAdvisoryOnlyChange, isInfoOnlyChange, isManualOrAdvisoryChange, uniqueColumnIndexName } from "./differ.js";
|
|
3
|
+
import { REQUIRED_COLUMN_NOT_ADDED, SchemaComparer, canonicalizeDefault, generateSchemaDiff, getSQLFromDiff, hasActionableChanges, isAdvisoryOnlyChange, isInfoOnlyChange, isManualOrAdvisoryChange, uniqueColumnIndexName } from "./differ.js";
|
|
4
4
|
import { BackfillTableUnavailableError, BackfillTracker } from "./backfill-tracker.js";
|
|
5
5
|
import { buildIntegerWidthStatements, buildIntegerWidthTableStatements, collectIntegerWidthTargets, preflightIntegerWidthWidening, widenIntegerColumnsToBigInt } from "./integer-width.js";
|
|
6
6
|
import { computeChecksum, normalizeSQL, shortChecksum, verifyChecksum } from "./checksum.js";
|
|
@@ -10,4 +10,4 @@ import { MinorUnitsPreflightError, buildMinorUnitsStatements, preflightMinorUnit
|
|
|
10
10
|
import { collectNullEqualIndexTargets, migrateNullEqualIndexes, nullEqualDuplicateDetector, nullEqualIndexStatements, preflightNullEqualIndexes } from "./null-equal-indexes.js";
|
|
11
11
|
import { MigrationTracker, buildConcurrentIndexPlan, extractCreatedIndexName, planPostgresStatements } from "./tracker.js";
|
|
12
12
|
import { getPendingSchemaStatements, migrateSmrtSchemas } from "./orchestrate.js";
|
|
13
|
-
export { BackfillTableUnavailableError, BackfillTracker, FRAMEWORK_BASE_TABLE_NAMES, MigrationGenerator, MigrationTracker, MinorUnitsPreflightError, SQLITE_REBUILD_TABLE_PREFIX, SchemaComparer, buildConcurrentIndexPlan, buildIntegerWidthStatements, buildIntegerWidthTableStatements, buildMinorUnitsStatements, buildSqliteRebuildStatements, canonicalizeDefault, collectIntegerWidthTargets, collectNullEqualIndexTargets, computeChecksum, createMigrationDefinition, dropFrameworkBaseTables, extractCreatedIndexName, generateMigrationSequence, generateMigrationTimestamp, generateSchemaDiff, getPendingSchemaStatements, getSQLFromDiff, hasActionableChanges, isAdvisoryOnlyChange, isInfoOnlyChange, isManualOrAdvisoryChange, isSqliteRebuildPlaceholder, migrateNullEqualIndexes, migrateSmrtSchemas, normalizeSQL, nullEqualDuplicateDetector, nullEqualIndexStatements, parsePostgresTimeoutMs, planFrameworkBaseTableDrop, planPostgresStatements, planSqliteTableRebuilds, preflightIntegerWidthWidening, preflightMinorUnitsRescale, preflightNullEqualIndexes, rescaleMoneyColumnsToMinorUnits, rewriteSqliteCreateTable, shortChecksum, sqliteRebuildPlaceholderSql, uniqueColumnIndexName, verifyChecksum, widenIntegerColumnsToBigInt };
|
|
13
|
+
export { BackfillTableUnavailableError, BackfillTracker, FRAMEWORK_BASE_TABLE_NAMES, MigrationGenerator, MigrationTracker, MinorUnitsPreflightError, REQUIRED_COLUMN_NOT_ADDED, SQLITE_REBUILD_TABLE_PREFIX, SchemaComparer, buildConcurrentIndexPlan, buildIntegerWidthStatements, buildIntegerWidthTableStatements, buildMinorUnitsStatements, buildSqliteRebuildStatements, canonicalizeDefault, collectIntegerWidthTargets, collectNullEqualIndexTargets, computeChecksum, createMigrationDefinition, dropFrameworkBaseTables, extractCreatedIndexName, generateMigrationSequence, generateMigrationTimestamp, generateSchemaDiff, getPendingSchemaStatements, getSQLFromDiff, hasActionableChanges, isAdvisoryOnlyChange, isInfoOnlyChange, isManualOrAdvisoryChange, isSqliteRebuildPlaceholder, migrateNullEqualIndexes, migrateSmrtSchemas, normalizeSQL, nullEqualDuplicateDetector, nullEqualIndexStatements, parsePostgresTimeoutMs, planFrameworkBaseTableDrop, planPostgresStatements, planSqliteTableRebuilds, preflightIntegerWidthWidening, preflightMinorUnitsRescale, preflightNullEqualIndexes, rescaleMoneyColumnsToMinorUnits, rewriteSqliteCreateTable, shortChecksum, sqliteRebuildPlaceholderSql, uniqueColumnIndexName, verifyChecksum, widenIntegerColumnsToBigInt };
|
|
@@ -19,6 +19,18 @@ export declare function sqliteRebuildPlaceholderSql(columnName: string): string;
|
|
|
19
19
|
* i.e. a type upgrade waiting for this module to turn it into a rebuild.
|
|
20
20
|
*/
|
|
21
21
|
export declare function isSqliteRebuildPlaceholder(sql: string | undefined): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* #3008: the placeholder the differ emits for a required column whose
|
|
24
|
+
* existing rows are filled from a declared per-row backfill. SQLite cannot
|
|
25
|
+
* add a NOT NULL column without a default to a populated table, nor
|
|
26
|
+
* `SET NOT NULL` afterwards, so {@link planSqliteTableRebuilds} turns it into
|
|
27
|
+
* a rebuild whose copy step evaluates the backfill for every row. An
|
|
28
|
+
* `add_column` placeholder adds the column; an `alter_column` placeholder
|
|
29
|
+
* tightens an existing nullable column (`COALESCE(col, backfill)`).
|
|
30
|
+
*/
|
|
31
|
+
export declare function sqliteBackfillPlaceholderSql(columnName: string): string;
|
|
32
|
+
/** True when `sql` is the #3008 backfill-rebuild placeholder. */
|
|
33
|
+
export declare function isSqliteBackfillPlaceholder(sql: string | undefined): boolean;
|
|
22
34
|
/** A rebuild plan: the ordered statements plus what they touch. */
|
|
23
35
|
export interface SqliteTableRebuildPlan {
|
|
24
36
|
/** Ordered, executable statements. Run them in one transaction. */
|
|
@@ -35,6 +47,16 @@ export interface PlanSqliteTableRebuildsOptions {
|
|
|
35
47
|
changes: SchemaChange[];
|
|
36
48
|
/** Abstract → SQLite type mapping (the engine's DDL strategy `mapType`). */
|
|
37
49
|
mapType: (type: SQLDataType) => string;
|
|
50
|
+
/**
|
|
51
|
+
* Statements to run after the rebuild for a column the rebuild adds
|
|
52
|
+
* (#3008), e.g. its `CREATE UNIQUE INDEX <table>_<col>_key`.
|
|
53
|
+
*/
|
|
54
|
+
addedColumnFollowUps?: (change: SchemaChange) => string[];
|
|
55
|
+
/**
|
|
56
|
+
* Full column definition for a column the rebuild adds (#3008). Defaults
|
|
57
|
+
* to `"<name>" <type> NOT NULL [CHECK (...)]`.
|
|
58
|
+
*/
|
|
59
|
+
addedColumnDefinition?: (change: SchemaChange) => string;
|
|
38
60
|
}
|
|
39
61
|
/**
|
|
40
62
|
* Replace every "requires table recreation" placeholder for `tableName` with
|
|
@@ -73,6 +95,21 @@ export interface BuildSqliteRebuildStatementsInput {
|
|
|
73
95
|
}[];
|
|
74
96
|
/** Current `PRAGMA legacy_alter_table` value, restored after the rename. */
|
|
75
97
|
legacyAlterTable: boolean;
|
|
98
|
+
/** #3008: new columns the rebuild adds, filled by their copy expression. */
|
|
99
|
+
appendColumns?: SqliteAppendedColumn[];
|
|
100
|
+
/**
|
|
101
|
+
* #3008: existing columns the rebuild tightens to NOT NULL, mapped to the
|
|
102
|
+
* copy expression that fills their NULLs.
|
|
103
|
+
*/
|
|
104
|
+
tightenColumns?: Map<string, string>;
|
|
105
|
+
}
|
|
106
|
+
/** A column a rebuild appends to the table (#3008). */
|
|
107
|
+
export interface SqliteAppendedColumn {
|
|
108
|
+
name: string;
|
|
109
|
+
/** Full column definition, e.g. `"key" TEXT NOT NULL`. */
|
|
110
|
+
definition: string;
|
|
111
|
+
/** Per-row value copied into it, evaluated against the old table. */
|
|
112
|
+
copyExpression: string;
|
|
76
113
|
}
|
|
77
114
|
/**
|
|
78
115
|
* Build the ordered statement list for one table rebuild.
|
|
@@ -108,6 +145,10 @@ export declare function buildSqliteRebuildStatements(input: BuildSqliteRebuildSt
|
|
|
108
145
|
export declare function rewriteSqliteCreateTable(createTableSql: string, target: {
|
|
109
146
|
tableName: string;
|
|
110
147
|
columnTypes: Map<string, string>;
|
|
148
|
+
/** #3008: columns whose definition gains `NOT NULL`. */
|
|
149
|
+
notNullColumns?: Set<string>;
|
|
150
|
+
/** #3008: column definitions appended after the existing columns. */
|
|
151
|
+
appendDefinitions?: string[];
|
|
111
152
|
}): string | null;
|
|
112
153
|
interface ParsedCreateTable {
|
|
113
154
|
/** Top-level, comma-separated column and constraint definitions. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sqlite-rebuild.d.ts","sourceRoot":"","sources":["../../src/migrations/sqlite-rebuild.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgEG;AAIH,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACpE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAIpD;;;;;GAKG;AACH,eAAO,MAAM,2BAA2B,mBAAmB,CAAC;AAE5D;;;;;GAKG;AACH,wBAAgB,2BAA2B,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAEtE;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAE3E;AAcD,mEAAmE;AACnE,MAAM,WAAW,sBAAsB;IACrC,mEAAmE;IACnE,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,0EAA0E;IAC1E,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,kEAAkE;IAClE,gBAAgB,EAAE,MAAM,EAAE,CAAC;CAC5B;AAsBD,MAAM,WAAW,8BAA8B;IAC7C,EAAE,EAAE,iBAAiB,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,0EAA0E;IAC1E,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,4EAA4E;IAC5E,OAAO,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"sqlite-rebuild.d.ts","sourceRoot":"","sources":["../../src/migrations/sqlite-rebuild.ts"],"names":[],"mappings":"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"}
|
|
@@ -90,6 +90,22 @@ function sqliteRebuildPlaceholderSql(columnName) {
|
|
|
90
90
|
function isSqliteRebuildPlaceholder(sql) {
|
|
91
91
|
return typeof sql === "string" && sql.includes("requires table recreation");
|
|
92
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* #3008: the placeholder the differ emits for a required column whose
|
|
95
|
+
* existing rows are filled from a declared per-row backfill. SQLite cannot
|
|
96
|
+
* add a NOT NULL column without a default to a populated table, nor
|
|
97
|
+
* `SET NOT NULL` afterwards, so {@link planSqliteTableRebuilds} turns it into
|
|
98
|
+
* a rebuild whose copy step evaluates the backfill for every row. An
|
|
99
|
+
* `add_column` placeholder adds the column; an `alter_column` placeholder
|
|
100
|
+
* tightens an existing nullable column (`COALESCE(col, backfill)`).
|
|
101
|
+
*/
|
|
102
|
+
function sqliteBackfillPlaceholderSql(columnName) {
|
|
103
|
+
return `-- SQLite: required column ${quoteIdentifier(columnName)} is backfilled by table recreation`;
|
|
104
|
+
}
|
|
105
|
+
/** True when `sql` is the #3008 backfill-rebuild placeholder. */
|
|
106
|
+
function isSqliteBackfillPlaceholder(sql) {
|
|
107
|
+
return typeof sql === "string" && sql.includes("is backfilled by table recreation");
|
|
108
|
+
}
|
|
93
109
|
function blockedSql(columnName, reason) {
|
|
94
110
|
return `-- SQLite: ${quoteIdentifier(columnName)} requires a table rebuild, blocked: ${reason}`;
|
|
95
111
|
}
|
|
@@ -119,7 +135,9 @@ function coveredSql(columnName, rebuiltWith) {
|
|
|
119
135
|
*/
|
|
120
136
|
async function planSqliteTableRebuilds(options) {
|
|
121
137
|
const { db, tableName, changes, mapType } = options;
|
|
122
|
-
const
|
|
138
|
+
const retypes = changes.filter((change) => change.type === "type_upgrade" && change.name && change.column && isSqliteRebuildPlaceholder(change.sql));
|
|
139
|
+
const backfills = changes.filter((change) => (change.type === "add_column" || change.type === "alter_column") && change.name && typeof change.column?.backfill === "string" && isSqliteBackfillPlaceholder(change.sql));
|
|
140
|
+
const pending = [...retypes, ...backfills];
|
|
123
141
|
if (pending.length === 0) return changes;
|
|
124
142
|
const block = (reason) => {
|
|
125
143
|
logger.debug(`[SQLiteRebuild] Cannot rebuild ${tableName}: ${reason}; leaving ${pending.length} type upgrade(s) as manual drift`);
|
|
@@ -138,23 +156,44 @@ async function planSqliteTableRebuilds(options) {
|
|
|
138
156
|
if (context.columns.length === 0) return block("PRAGMA table_info returned no columns");
|
|
139
157
|
if (context.foreignKeysEnabled && context.inboundReferences.length > 0) return block(`PRAGMA foreign_keys is ON and ${context.inboundReferences.map((name) => `"${name}"`).join(", ")} declare a foreign key onto this table, so DROP TABLE would fire their ON DELETE actions`);
|
|
140
158
|
const columnTypes = /* @__PURE__ */ new Map();
|
|
141
|
-
for (const change of
|
|
159
|
+
for (const change of retypes) columnTypes.set(change.name, mapType(change.column.type));
|
|
160
|
+
const appendColumns = [];
|
|
161
|
+
const tightenColumns = /* @__PURE__ */ new Map();
|
|
162
|
+
const followUps = [];
|
|
163
|
+
for (const change of backfills) {
|
|
164
|
+
const name = change.name;
|
|
165
|
+
const column = change.column;
|
|
166
|
+
const backfill = column.backfill;
|
|
167
|
+
if (change.type === "add_column") {
|
|
168
|
+
const check = column.check ? ` CHECK (${column.check})` : "";
|
|
169
|
+
appendColumns.push({
|
|
170
|
+
name,
|
|
171
|
+
definition: options.addedColumnDefinition?.(change) ?? `${quoteIdentifier(name)} ${mapType(column.type)} NOT NULL${check}`,
|
|
172
|
+
copyExpression: `(${backfill})`
|
|
173
|
+
});
|
|
174
|
+
followUps.push(...options.addedColumnFollowUps?.(change) ?? []);
|
|
175
|
+
} else tightenColumns.set(name, `COALESCE(${quoteIdentifier(name)}, (${backfill}))`);
|
|
176
|
+
}
|
|
142
177
|
const plan = buildSqliteRebuildStatements({
|
|
143
178
|
tableName,
|
|
144
179
|
createTableSql: context.createTableSql,
|
|
145
180
|
columnTypes,
|
|
146
181
|
columns: context.columns,
|
|
147
182
|
objects: context.objects,
|
|
148
|
-
legacyAlterTable: context.legacyAlterTable
|
|
183
|
+
legacyAlterTable: context.legacyAlterTable,
|
|
184
|
+
appendColumns,
|
|
185
|
+
tightenColumns
|
|
149
186
|
});
|
|
150
187
|
if (!plan) return block("the live CREATE TABLE statement could not be rewritten for the target column types");
|
|
151
188
|
const [primary, ...covered] = pending;
|
|
189
|
+
const statements = [...plan.statements, ...followUps];
|
|
152
190
|
const rebuilt = changes.map((change) => {
|
|
153
191
|
if (change === primary) return {
|
|
154
192
|
...change,
|
|
155
|
-
sql:
|
|
156
|
-
sqlStatements:
|
|
193
|
+
sql: statements[0],
|
|
194
|
+
sqlStatements: statements
|
|
157
195
|
};
|
|
196
|
+
if (covered.includes(change) && change.type === "add_column") return null;
|
|
158
197
|
if (covered.includes(change)) {
|
|
159
198
|
const { sqlStatements: _dropped, ...rest } = change;
|
|
160
199
|
return {
|
|
@@ -165,7 +204,7 @@ async function planSqliteTableRebuilds(options) {
|
|
|
165
204
|
return change;
|
|
166
205
|
});
|
|
167
206
|
const rebuildPositions = new Set(pending.map((change) => changes.indexOf(change)));
|
|
168
|
-
return [...rebuilt.filter((_, index) => rebuildPositions.has(index)), ...rebuilt.filter((_, index) => !rebuildPositions.has(index))];
|
|
207
|
+
return [...rebuilt.filter((_, index) => rebuildPositions.has(index)), ...rebuilt.filter((_, index) => !rebuildPositions.has(index))].filter((change) => change !== null);
|
|
169
208
|
}
|
|
170
209
|
/**
|
|
171
210
|
* Read everything the rebuild needs from the live database.
|
|
@@ -250,22 +289,28 @@ function readPragmaFlag(result) {
|
|
|
250
289
|
* documented in `packages/core/agents/schema-paths.md`.
|
|
251
290
|
*/
|
|
252
291
|
function buildSqliteRebuildStatements(input) {
|
|
253
|
-
const { tableName, createTableSql, columnTypes, columns, objects, legacyAlterTable } = input;
|
|
292
|
+
const { tableName, createTableSql, columnTypes, columns, objects, legacyAlterTable, appendColumns = [], tightenColumns = /* @__PURE__ */ new Map() } = input;
|
|
254
293
|
const stagingTable = `${SQLITE_REBUILD_TABLE_PREFIX}${tableName}`;
|
|
255
294
|
const createStaging = rewriteSqliteCreateTable(createTableSql, {
|
|
256
295
|
tableName: stagingTable,
|
|
257
|
-
columnTypes
|
|
296
|
+
columnTypes,
|
|
297
|
+
notNullColumns: new Set(tightenColumns.keys()),
|
|
298
|
+
appendDefinitions: appendColumns.map((column) => column.definition)
|
|
258
299
|
});
|
|
259
300
|
if (!createStaging) return null;
|
|
301
|
+
const liveColumns = new Set(columns.map((name) => name.toLowerCase()));
|
|
302
|
+
for (const name of tightenColumns.keys()) if (!liveColumns.has(name.toLowerCase())) return null;
|
|
303
|
+
for (const column of appendColumns) if (liveColumns.has(column.name.toLowerCase())) return null;
|
|
260
304
|
const quotedTable = quoteIdentifier(tableName);
|
|
261
305
|
const quotedStaging = quoteIdentifier(stagingTable);
|
|
262
|
-
const
|
|
306
|
+
const targetList = [...columns, ...appendColumns.map((c) => c.name)].map((name) => quoteIdentifier(name)).join(", ");
|
|
307
|
+
const sourceList = [...columns.map((name) => tightenColumns.get(name) ?? quoteIdentifier(name)), ...appendColumns.map((column) => column.copyExpression)].join(", ");
|
|
263
308
|
return {
|
|
264
309
|
statements: [
|
|
265
310
|
"PRAGMA defer_foreign_keys = ON",
|
|
266
311
|
`DROP TABLE IF EXISTS ${quotedStaging}`,
|
|
267
312
|
createStaging,
|
|
268
|
-
`INSERT INTO ${quotedStaging} (${
|
|
313
|
+
`INSERT INTO ${quotedStaging} (${targetList}) SELECT ${sourceList} FROM ${quotedTable}`,
|
|
269
314
|
`DROP TABLE ${quotedTable}`,
|
|
270
315
|
"PRAGMA legacy_alter_table = ON",
|
|
271
316
|
`ALTER TABLE ${quotedStaging} RENAME TO ${quotedTable}`,
|
|
@@ -293,19 +338,29 @@ function rewriteSqliteCreateTable(createTableSql, target) {
|
|
|
293
338
|
if (!parsed) return null;
|
|
294
339
|
const wanted = /* @__PURE__ */ new Map();
|
|
295
340
|
for (const [name, type] of target.columnTypes) wanted.set(name.toLowerCase(), type);
|
|
341
|
+
const tighten = new Set([...target.notNullColumns ?? []].map((name) => name.toLowerCase()));
|
|
296
342
|
const rewritten = [];
|
|
343
|
+
let lastColumnIndex = -1;
|
|
297
344
|
for (const item of parsed.items) {
|
|
298
345
|
const columnName = readColumnDefinitionName(item);
|
|
346
|
+
if (columnName) lastColumnIndex = rewritten.length;
|
|
347
|
+
let current = item;
|
|
348
|
+
if (columnName && tighten.has(columnName.toLowerCase())) {
|
|
349
|
+
tighten.delete(columnName.toLowerCase());
|
|
350
|
+
if (!/\bnot\s+null\b/i.test(current)) current = `${current} NOT NULL`;
|
|
351
|
+
}
|
|
299
352
|
const newType = columnName ? wanted.get(columnName.toLowerCase()) : null;
|
|
300
353
|
if (!columnName || !newType) {
|
|
301
|
-
rewritten.push(
|
|
354
|
+
rewritten.push(current);
|
|
302
355
|
continue;
|
|
303
356
|
}
|
|
304
|
-
const replaced = replaceColumnType(
|
|
357
|
+
const replaced = replaceColumnType(current, newType);
|
|
305
358
|
if (!replaced) return null;
|
|
306
359
|
rewritten.push(replaced);
|
|
307
360
|
wanted.delete(columnName.toLowerCase());
|
|
308
361
|
}
|
|
362
|
+
if (tighten.size > 0) return null;
|
|
363
|
+
rewritten.splice(lastColumnIndex + 1, 0, ...target.appendDefinitions ?? []);
|
|
309
364
|
if (wanted.size > 0) return null;
|
|
310
365
|
const tail = parsed.tail ? ` ${parsed.tail}` : "";
|
|
311
366
|
return `CREATE TABLE ${quoteIdentifier(target.tableName)} (\n ${rewritten.join(",\n ")}\n)${tail}`;
|
|
@@ -509,6 +564,6 @@ function replaceColumnType(item, newType) {
|
|
|
509
564
|
return `${head} ${newType}${rest.length === 0 || /^\s/.test(rest) ? "" : " "}${rest}`;
|
|
510
565
|
}
|
|
511
566
|
//#endregion
|
|
512
|
-
export { SQLITE_REBUILD_TABLE_PREFIX, buildSqliteRebuildStatements, isSqliteRebuildPlaceholder, parseCreateTableBody, planSqliteTableRebuilds, readColumnDefinitionName, replaceColumnType, rewriteSqliteCreateTable, sqliteRebuildPlaceholderSql };
|
|
567
|
+
export { SQLITE_REBUILD_TABLE_PREFIX, buildSqliteRebuildStatements, isSqliteBackfillPlaceholder, isSqliteRebuildPlaceholder, parseCreateTableBody, planSqliteTableRebuilds, readColumnDefinitionName, replaceColumnType, rewriteSqliteCreateTable, sqliteBackfillPlaceholderSql, sqliteRebuildPlaceholderSql };
|
|
513
568
|
|
|
514
569
|
//# sourceMappingURL=sqlite-rebuild.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sqlite-rebuild.js","names":[],"sources":["../../src/migrations/sqlite-rebuild.ts"],"sourcesContent":["/**\n * SQLite table-rebuild migrations (issue #2370)\n *\n * SQLite has no `ALTER TABLE ... ALTER COLUMN ... TYPE`. Before this module\n * every type-bucket change on SQLite (the canonical one being a numeric\n * default edited from `0` to `0.0`, i.e. INTEGER → REAL) turned into an\n * advisory differ comment, which `db:migrate` classifies as\n * *manual intervention* and exits 1 for — forever, because nothing in the\n * toolchain could ever perform the change.\n *\n * This module turns that comment into the executable rebuild SQLite's own\n * documentation prescribes (\"Making Other Kinds Of Table Schema Changes\",\n * lang_altertable.html):\n *\n * 1. `PRAGMA defer_foreign_keys = ON` — defers foreign-key enforcement to\n * COMMIT so the window where the table does not exist cannot raise. It is\n * honored *inside* a transaction (unlike `PRAGMA foreign_keys`, which is a\n * silent no-op there) and resets itself at COMMIT/ROLLBACK.\n * 2. Create a new table under a reserved `_smrt_rebuild_` name carrying the\n * target shape.\n * 3. Copy every row across.\n * 4. Drop the original table.\n * 5. Rename the new table into its place, with `PRAGMA legacy_alter_table`\n * ON for the rename itself (see {@link buildSqliteRebuildStatements}).\n * 6. Recreate the indexes and triggers that died with the original table.\n *\n * The whole plan is a plain statement list, so it is executed by the\n * `MigrationTracker` inside the same transaction as the rest of the batch:\n * a failure anywhere rolls the table back to its original shape and contents.\n *\n * ## The target shape comes from the live DDL, not from the manifest\n *\n * The new table is the *original* `CREATE TABLE` text with only the drifted\n * columns' type tokens replaced. That deliberately preserves everything the\n * manifest does not model or does not own here: columns the manifest has\n * dropped (SMRT is additive — a rebuild must not become an implicit\n * `DROP COLUMN`), table-level constraints, `CHECK`s, collations, and table\n * options such as `WITHOUT ROWID`. The `add_column` changes in the same batch\n * still run their own `ALTER TABLE ... ADD COLUMN`, against the rebuilt table\n * — {@link planSqliteTableRebuilds} hoists the rebuild ahead of them for\n * exactly that reason.\n *\n * ## Why the copy carries no CAST\n *\n * The copy is a plain `INSERT INTO new SELECT ... FROM old`; SQLite applies\n * the destination column's *affinity* on insert, which is exactly the\n * conversion a fresh table would have performed (integer `1` becomes real\n * `1.0` in a REAL column). An explicit `CAST` would be strictly worse: casting\n * a non-numeric TEXT value to REAL/INTEGER silently yields `0`, and casting an\n * ISO timestamp string to SQLite's NUMERIC-affinity `DATETIME` silently yields\n * the leading year. Affinity conversion leaves values it cannot convert\n * untouched instead of destroying them.\n *\n * ## When the rebuild refuses\n *\n * With `PRAGMA foreign_keys` enabled — which the SMRT SQLite adapter is by\n * default — `DROP TABLE` performs an implicit `DELETE FROM` that fires\n * `ON DELETE` actions on children. A child with `ON DELETE CASCADE` therefore\n * loses its rows, and `defer_foreign_keys` does not help: it defers\n * constraint *checks*, not FK *actions*. So when any table declares a foreign\n * key onto the rebuild target and enforcement is on, the plan is refused and\n * the change stays a manual intervention. That includes the target's own\n * self-references, because the staging table copies them and so becomes a\n * child of the table being dropped.\n */\n\nimport { createLogger } from '@happyvertical/logger';\nimport { quoteIdentifier } from '../schema/sql-identifiers.js';\nimport type { SchemaChange, SQLDataType } from '../schema/types.js';\nimport type { DatabaseInterface } from './types.js';\n\nconst logger = createLogger({ level: 'info' });\n\n/**\n * Prefix of the reserved staging table a rebuild creates. `_smrt_`-prefixed\n * names are SMRT system tables, which the differ's dropped-table sweep skips,\n * so a staging table left behind by a non-transactional adapter cannot be\n * mistaken for user data.\n */\nexport const SQLITE_REBUILD_TABLE_PREFIX = '_smrt_rebuild_';\n\n/**\n * The advisory SQL the differ emits for a SQLite type upgrade it cannot\n * perform with an `ALTER TABLE`. `planSqliteTableRebuilds` replaces it with an\n * executable plan; when it cannot, the comment survives and `db:migrate`\n * reports the column as manual drift exactly as it did before #2370.\n */\nexport function sqliteRebuildPlaceholderSql(columnName: string): string {\n return `-- SQLite: Type upgrade for ${quoteIdentifier(columnName)} requires table recreation`;\n}\n\n/**\n * True when `sql` is the differ's \"requires table recreation\" placeholder —\n * i.e. a type upgrade waiting for this module to turn it into a rebuild.\n */\nexport function isSqliteRebuildPlaceholder(sql: string | undefined): boolean {\n return typeof sql === 'string' && sql.includes('requires table recreation');\n}\n\nfunction blockedSql(columnName: string, reason: string): string {\n // Must stay a single line, and must not read as a no-op: the CLI classifies\n // any comment that says \"no change needed\" as noop and drops it silently.\n return `-- SQLite: ${quoteIdentifier(columnName)} requires a table rebuild, blocked: ${reason}`;\n}\n\nfunction coveredSql(columnName: string, rebuiltWith: string): string {\n // Classified `noop` by the CLI (`no change needed`): the sibling change\n // carrying the plan rebuilds the whole table, this column included.\n return `-- SQLite: ${quoteIdentifier(columnName)} is rebuilt with ${quoteIdentifier(rebuiltWith)} in one table rebuild (no change needed)`;\n}\n\n/** A rebuild plan: the ordered statements plus what they touch. */\nexport interface SqliteTableRebuildPlan {\n /** Ordered, executable statements. Run them in one transaction. */\n statements: string[];\n /** Columns copied from the old table into the new one, in table order. */\n copiedColumns: string[];\n /** Names of the indexes and triggers recreated after the swap. */\n recreatedObjects: string[];\n}\n\n/** Live SQLite state a rebuild needs before it can plan anything. */\ninterface SqliteRebuildContext {\n /** `sqlite_master.sql` for the table (SQLite normalizes IF NOT EXISTS away). */\n createTableSql: string;\n /**\n * Column names in `PRAGMA table_info` order. That pragma omits generated\n * columns, which is what the copy wants: they are not insertable and the\n * preserved DDL recomputes them in the new table.\n */\n columns: string[];\n /** Indexes and triggers that will die with the dropped table. */\n objects: { name: string; sql: string }[];\n /** Whether `PRAGMA foreign_keys` enforcement is on for this connection. */\n foreignKeysEnabled: boolean;\n /** Current `PRAGMA legacy_alter_table` value, restored after the rename. */\n legacyAlterTable: boolean;\n /** Other tables declaring a foreign key onto this table. */\n inboundReferences: string[];\n}\n\nexport interface PlanSqliteTableRebuildsOptions {\n db: DatabaseInterface;\n tableName: string;\n /** The table's changes, as produced by the differ's column comparison. */\n changes: SchemaChange[];\n /** Abstract → SQLite type mapping (the engine's DDL strategy `mapType`). */\n mapType: (type: SQLDataType) => string;\n}\n\n/**\n * Replace every \"requires table recreation\" placeholder for `tableName` with\n * one executable rebuild.\n *\n * All of the table's drifted columns are retyped by a *single* rebuild, so the\n * first placeholder carries the plan and the rest become no-ops that reference\n * it. Returns the changes unchanged when there is nothing to rebuild, and\n * leaves the placeholders in place (annotated with the reason) when the\n * rebuild cannot be planned safely — the pre-#2370 manual-intervention\n * behavior.\n *\n * When a plan is produced, the rebuild is hoisted to the front of the table's\n * changes. The plan's staging DDL and copy list are captured from the live\n * schema *at diff time*, so a same-batch `ALTER TABLE ... ADD COLUMN` that ran\n * first would be silently undone by the rebuild — both statements succeed, the\n * transaction commits, and the new column is simply gone. The differ emits\n * changes in manifest field order, which puts an `add_column` first whenever\n * the new field is declared above the retyped one, so the order has to be\n * imposed here rather than assumed. Rebuild first, then add columns to the\n * rebuilt table.\n */\nexport async function planSqliteTableRebuilds(\n options: PlanSqliteTableRebuildsOptions,\n): Promise<SchemaChange[]> {\n const { db, tableName, changes, mapType } = options;\n\n const pending = changes.filter(\n (change) =>\n change.type === 'type_upgrade' &&\n change.name &&\n change.column &&\n isSqliteRebuildPlaceholder(change.sql),\n );\n if (pending.length === 0) {\n return changes;\n }\n\n const block = (reason: string): SchemaChange[] => {\n logger.debug(\n `[SQLiteRebuild] Cannot rebuild ${tableName}: ${reason}; leaving ${pending.length} type upgrade(s) as manual drift`,\n );\n return changes.map((change) =>\n pending.includes(change)\n ? { ...change, sql: blockedSql(change.name as string, reason) }\n : change,\n );\n };\n\n let context: SqliteRebuildContext;\n try {\n context = await readSqliteRebuildContext(db, tableName);\n } catch (error) {\n return block(\n `live schema introspection failed (${error instanceof Error ? error.message : String(error)})`,\n );\n }\n\n if (!context.createTableSql) {\n return block('no CREATE TABLE statement found in sqlite_master');\n }\n if (context.columns.length === 0) {\n return block('PRAGMA table_info returned no columns');\n }\n if (context.foreignKeysEnabled && context.inboundReferences.length > 0) {\n return block(\n `PRAGMA foreign_keys is ON and ${context.inboundReferences\n .map((name) => `\"${name}\"`)\n .join(\n ', ',\n )} declare a foreign key onto this table, so DROP TABLE would fire their ON DELETE actions`,\n );\n }\n\n const columnTypes = new Map<string, string>();\n for (const change of pending) {\n columnTypes.set(\n change.name as string,\n mapType((change.column as { type: SQLDataType }).type),\n );\n }\n\n const plan = buildSqliteRebuildStatements({\n tableName,\n createTableSql: context.createTableSql,\n columnTypes,\n columns: context.columns,\n objects: context.objects,\n legacyAlterTable: context.legacyAlterTable,\n });\n\n if (!plan) {\n return block(\n 'the live CREATE TABLE statement could not be rewritten for the target column types',\n );\n }\n\n const [primary, ...covered] = pending;\n const rebuilt = changes.map((change) => {\n if (change === primary) {\n return {\n ...change,\n // `sql` stays a real statement so the CLI classifies this upgrade as\n // executable; every consumer that actually runs the change prefers\n // `sqlStatements`.\n sql: plan.statements[0],\n sqlStatements: plan.statements,\n };\n }\n if (covered.includes(change)) {\n const { sqlStatements: _dropped, ...rest } = change;\n return {\n ...rest,\n sql: coveredSql(change.name as string, primary.name as string),\n };\n }\n return change;\n });\n\n // Hoist the rebuild ahead of the table's other column changes, keeping both\n // groups in their original relative order. Consumers execute `diff.changes`\n // in order, and the plan was built from the pre-batch table shape.\n const rebuildPositions = new Set(\n pending.map((change) => changes.indexOf(change)),\n );\n return [\n ...rebuilt.filter((_, index) => rebuildPositions.has(index)),\n ...rebuilt.filter((_, index) => !rebuildPositions.has(index)),\n ];\n}\n\n/**\n * Read everything the rebuild needs from the live database.\n *\n * Every query is SQLite-only (`sqlite_master`, `PRAGMA`), and the caller only\n * reaches this for an engine already detected as SQLite.\n */\nasync function readSqliteRebuildContext(\n db: DatabaseInterface,\n tableName: string,\n): Promise<SqliteRebuildContext> {\n const tableRows = await db.query(\n `SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ?`,\n tableName,\n );\n const createTableSql = readString(\n (tableRows.rows[0] as { sql?: unknown } | undefined)?.sql,\n );\n\n const columnRows = await db.query(\n `PRAGMA table_info(${quoteIdentifier(tableName)})`,\n );\n const columns = columnRows.rows\n .map((row) => readString((row as { name?: unknown }).name))\n .filter((name): name is string => Boolean(name));\n\n // `sql IS NULL` marks the implicit indexes SQLite creates for inline\n // PRIMARY KEY / UNIQUE constraints; those are carried by the CREATE TABLE\n // text and must not be replayed separately.\n const objectRows = await db.query(\n `SELECT name, sql FROM sqlite_master\n WHERE tbl_name = ? AND sql IS NOT NULL\n AND type IN ('index', 'trigger') AND name NOT LIKE 'sqlite_%'`,\n tableName,\n );\n const objects: { name: string; sql: string }[] = [];\n for (const row of objectRows.rows as { name?: unknown; sql?: unknown }[]) {\n const name = readString(row.name);\n const sql = readString(row.sql);\n if (name && sql) {\n objects.push({ name, sql });\n }\n }\n\n const foreignKeysEnabled = readPragmaFlag(\n await db.query('PRAGMA foreign_keys'),\n );\n const legacyAlterTable = readPragmaFlag(\n await db.query('PRAGMA legacy_alter_table'),\n );\n\n const inboundReferences = foreignKeysEnabled\n ? await readInboundForeignKeys(db, tableName)\n : [];\n\n return {\n createTableSql,\n columns,\n objects,\n foreignKeysEnabled,\n legacyAlterTable,\n inboundReferences,\n };\n}\n\n/**\n * Names of tables that declare a foreign key onto `tableName`.\n *\n * The table itself counts. A self-reference is copied verbatim into the\n * staging table, which therefore becomes a real child of the live table, and\n * `DROP TABLE` then cascades the *staged* rows away — verified: a two-row\n * self-referencing table finishes the rebuild holding one row.\n */\nasync function readInboundForeignKeys(\n db: DatabaseInterface,\n tableName: string,\n): Promise<string[]> {\n const tables = await db.query(\n `SELECT name FROM sqlite_master\n WHERE type = 'table' AND name NOT LIKE 'sqlite_%'`,\n );\n\n const referencing: string[] = [];\n const target = tableName.toLowerCase();\n for (const row of tables.rows as { name?: unknown }[]) {\n const name = readString(row.name);\n if (!name) continue;\n const fks = await db.query(\n `PRAGMA foreign_key_list(${quoteIdentifier(name)})`,\n );\n const referencesTarget = (fks.rows as { table?: unknown }[]).some(\n (fk) => readString(fk.table).toLowerCase() === target,\n );\n if (referencesTarget) {\n referencing.push(name);\n }\n }\n\n return referencing;\n}\n\nfunction readString(value: unknown): string {\n return typeof value === 'string' ? value : '';\n}\n\n/** Read a single-row/single-column PRAGMA result as a boolean. */\nfunction readPragmaFlag(result: { rows: Record<string, unknown>[] }): boolean {\n const row = result.rows[0];\n if (!row) return false;\n const value = Object.values(row)[0];\n if (typeof value === 'number') return value !== 0;\n if (typeof value === 'boolean') return value;\n if (typeof value === 'string') return value !== '0' && value !== '';\n return false;\n}\n\nexport interface BuildSqliteRebuildStatementsInput {\n tableName: string;\n /** The live `sqlite_master.sql` CREATE TABLE text. */\n createTableSql: string;\n /** Column name → target SQLite type, for the columns being retyped. */\n columnTypes: Map<string, string>;\n /** Every column of the live table, in `PRAGMA table_info` order. */\n columns: string[];\n /** Indexes and triggers to replay after the swap. */\n objects: { name: string; sql: string }[];\n /** Current `PRAGMA legacy_alter_table` value, restored after the rename. */\n legacyAlterTable: boolean;\n}\n\n/**\n * Build the ordered statement list for one table rebuild.\n *\n * Returns `null` when the live `CREATE TABLE` text cannot be rewritten (an\n * unparsable statement, or a requested column that is not in it) — the caller\n * then leaves the change as manual drift rather than emitting SQL that would\n * \"succeed\" without fixing the column, which would re-diff forever.\n *\n * `PRAGMA legacy_alter_table` brackets the rename because SQLite ≥ 3.25\n * re-parses the whole schema on `ALTER TABLE ... RENAME`: a view left\n * referencing the just-dropped table makes the rename fail outright\n * (\"error in view … no such table\"). Legacy mode renames the table without\n * that reference rewriting, which is exactly what a rebuild wants — the name\n * the views refer to is about to exist again. It is restored to its previous\n * value immediately afterwards; a rolled-back batch leaves it set, which is\n * inert for SMRT (nothing else in the toolchain renames a table) but is\n * documented in `packages/core/agents/schema-paths.md`.\n */\nexport function buildSqliteRebuildStatements(\n input: BuildSqliteRebuildStatementsInput,\n): SqliteTableRebuildPlan | null {\n const {\n tableName,\n createTableSql,\n columnTypes,\n columns,\n objects,\n legacyAlterTable,\n } = input;\n\n const stagingTable = `${SQLITE_REBUILD_TABLE_PREFIX}${tableName}`;\n const createStaging = rewriteSqliteCreateTable(createTableSql, {\n tableName: stagingTable,\n columnTypes,\n });\n if (!createStaging) {\n return null;\n }\n\n const quotedTable = quoteIdentifier(tableName);\n const quotedStaging = quoteIdentifier(stagingTable);\n const columnList = columns.map((name) => quoteIdentifier(name)).join(', ');\n\n const statements = [\n // Deferred FK enforcement survives the window where the table is absent.\n // Unlike `PRAGMA foreign_keys`, this one is honored inside a transaction\n // and resets itself at COMMIT/ROLLBACK.\n 'PRAGMA defer_foreign_keys = ON',\n // Idempotent for a retry after a partially applied non-transactional run.\n `DROP TABLE IF EXISTS ${quotedStaging}`,\n createStaging,\n `INSERT INTO ${quotedStaging} (${columnList}) SELECT ${columnList} FROM ${quotedTable}`,\n `DROP TABLE ${quotedTable}`,\n 'PRAGMA legacy_alter_table = ON',\n `ALTER TABLE ${quotedStaging} RENAME TO ${quotedTable}`,\n `PRAGMA legacy_alter_table = ${legacyAlterTable ? 'ON' : 'OFF'}`,\n // The indexes and triggers went down with the dropped table. Their\n // original text still names the table correctly (it was renamed back), so\n // replaying it verbatim preserves partial predicates, expression indexes,\n // and trigger bodies that a manifest-driven regeneration would flatten.\n ...objects.map((object) => object.sql),\n ];\n\n return {\n statements,\n copiedColumns: [...columns],\n recreatedObjects: objects.map((object) => object.name),\n };\n}\n\n/**\n * Rewrite a live `CREATE TABLE` statement into the rebuild's target shape:\n * the same body under a new table name, with the declared type of each column\n * in `columnTypes` replaced.\n *\n * Everything else is preserved verbatim — other columns, their defaults,\n * `NOT NULL`/`PRIMARY KEY`/`CHECK`/`COLLATE` clauses, table-level\n * constraints, and trailing table options (`WITHOUT ROWID`, `STRICT`).\n *\n * Returns `null` if the statement cannot be parsed or if any requested column\n * is not present in it.\n */\nexport function rewriteSqliteCreateTable(\n createTableSql: string,\n target: { tableName: string; columnTypes: Map<string, string> },\n): string | null {\n const parsed = parseCreateTableBody(createTableSql);\n if (!parsed) {\n return null;\n }\n\n const wanted = new Map<string, string>();\n for (const [name, type] of target.columnTypes) {\n wanted.set(name.toLowerCase(), type);\n }\n\n const rewritten: string[] = [];\n for (const item of parsed.items) {\n const columnName = readColumnDefinitionName(item);\n const newType = columnName ? wanted.get(columnName.toLowerCase()) : null;\n if (!columnName || !newType) {\n rewritten.push(item);\n continue;\n }\n const replaced = replaceColumnType(item, newType);\n if (!replaced) {\n return null;\n }\n rewritten.push(replaced);\n wanted.delete(columnName.toLowerCase());\n }\n\n if (wanted.size > 0) {\n // A column the differ wants retyped is not in the live DDL. Rebuilding\n // would not fix the drift, so refuse instead of emitting a no-op plan.\n return null;\n }\n\n const tail = parsed.tail ? ` ${parsed.tail}` : '';\n return `CREATE TABLE ${quoteIdentifier(target.tableName)} (\\n ${rewritten.join(\n ',\\n ',\n )}\\n)${tail}`;\n}\n\ninterface ParsedCreateTable {\n /** Top-level, comma-separated column and constraint definitions. */\n items: string[];\n /** Table options after the closing paren (`WITHOUT ROWID`, `STRICT`). */\n tail: string;\n}\n\n/**\n * Split a `CREATE TABLE` statement into its top-level definition items plus\n * any trailing table options, honoring SQLite's quoting (`'…'`, `\"…\"`,\n * `` `…` ``, `[…]`) and comments so a quoted comma or paren cannot split the\n * body.\n *\n * @internal exported for testing\n */\nexport function parseCreateTableBody(sql: string): ParsedCreateTable | null {\n let index = 0;\n let open = -1;\n while (index < sql.length) {\n const skipped = skipQuotedOrComment(sql, index);\n if (skipped !== null) {\n index = skipped;\n continue;\n }\n if (sql[index] === '(') {\n open = index;\n break;\n }\n index++;\n }\n if (open === -1) {\n return null;\n }\n\n const items: string[] = [];\n let depth = 1;\n let itemStart = open + 1;\n let close = -1;\n index = open + 1;\n while (index < sql.length) {\n const skipped = skipQuotedOrComment(sql, index);\n if (skipped !== null) {\n index = skipped;\n continue;\n }\n const char = sql[index];\n if (char === '(') {\n depth++;\n } else if (char === ')') {\n depth--;\n if (depth === 0) {\n close = index;\n items.push(sql.slice(itemStart, index));\n break;\n }\n } else if (char === ',' && depth === 1) {\n items.push(sql.slice(itemStart, index));\n itemStart = index + 1;\n }\n index++;\n }\n\n if (close === -1) {\n return null;\n }\n\n const trimmed = items.map((item) => item.trim()).filter(Boolean);\n if (trimmed.length === 0) {\n return null;\n }\n\n return {\n items: trimmed,\n tail: sql\n .slice(close + 1)\n .trim()\n .replace(/;+$/, '')\n .trim(),\n };\n}\n\n/**\n * If `sql[index]` opens a quoted identifier, string literal, or comment,\n * return the index just past its end. Otherwise return `null`.\n */\nfunction skipQuotedOrComment(sql: string, index: number): number | null {\n const char = sql[index];\n\n if (char === \"'\" || char === '\"' || char === '`') {\n let cursor = index + 1;\n while (cursor < sql.length) {\n if (sql[cursor] === char) {\n // A doubled delimiter is an escaped one, not the end of the token.\n if (sql[cursor + 1] === char) {\n cursor += 2;\n continue;\n }\n return cursor + 1;\n }\n cursor++;\n }\n return sql.length;\n }\n\n if (char === '[') {\n const end = sql.indexOf(']', index + 1);\n return end === -1 ? sql.length : end + 1;\n }\n\n if (char === '-' && sql[index + 1] === '-') {\n const end = sql.indexOf('\\n', index);\n return end === -1 ? sql.length : end + 1;\n }\n\n if (char === '/' && sql[index + 1] === '*') {\n const end = sql.indexOf('*/', index + 2);\n return end === -1 ? sql.length : end + 2;\n }\n\n return null;\n}\n\n/**\n * Keywords that start a *table*-level constraint, i.e. a body item that is not\n * a column definition.\n */\nconst TABLE_CONSTRAINT_KEYWORDS = new Set([\n 'constraint',\n 'primary',\n 'unique',\n 'check',\n 'foreign',\n]);\n\n/**\n * Keywords that end a column's type name and start its constraint list.\n */\nconst COLUMN_CONSTRAINT_KEYWORDS = new Set([\n 'constraint',\n 'primary',\n 'not',\n 'null',\n 'unique',\n 'check',\n 'default',\n 'collate',\n 'references',\n 'generated',\n 'as',\n]);\n\ninterface Identifier {\n name: string;\n end: number;\n}\n\n/** Read the leading identifier of a body item (quoted or bare). */\nfunction readLeadingIdentifier(item: string): Identifier | null {\n let index = 0;\n while (index < item.length && /\\s/.test(item[index])) index++;\n if (index >= item.length) return null;\n\n const char = item[index];\n if (char === '\"' || char === '`' || char === '[') {\n const end = skipQuotedOrComment(item, index);\n if (end === null) return null;\n const raw = item.slice(index + 1, end - 1);\n // `[…]` has no escape form; `\"…\"` / `` `…` `` double the delimiter.\n return {\n name: char === '[' ? raw : raw.split(char + char).join(char),\n end,\n };\n }\n\n const match = /^[A-Za-z_][\\w$]*/.exec(item.slice(index));\n if (!match) return null;\n return { name: match[0], end: index + match[0].length };\n}\n\n/**\n * The column name of a body item, or `null` when the item is a table-level\n * constraint rather than a column definition.\n *\n * @internal exported for testing\n */\nexport function readColumnDefinitionName(item: string): string | null {\n const identifier = readLeadingIdentifier(item);\n if (!identifier) return null;\n\n const isQuoted = /^\\s*[\"`[]/.test(item);\n if (\n !isQuoted &&\n TABLE_CONSTRAINT_KEYWORDS.has(identifier.name.toLowerCase())\n ) {\n return null;\n }\n return identifier.name;\n}\n\n/**\n * Replace the declared type of a column definition, keeping its name and every\n * constraint that follows. A column declared without a type gets one inserted.\n *\n * @internal exported for testing\n */\nexport function replaceColumnType(\n item: string,\n newType: string,\n): string | null {\n const identifier = readLeadingIdentifier(item);\n if (!identifier) return null;\n\n const head = item.slice(0, identifier.end);\n let index = identifier.end;\n\n // The type name is one or more bare words followed by an optional\n // `(size[, size])` — everything before the first column-constraint keyword.\n let typeEnd = index;\n while (index < item.length) {\n while (index < item.length && /\\s/.test(item[index])) index++;\n const match = /^[A-Za-z_][\\w$]*/.exec(item.slice(index));\n if (!match || COLUMN_CONSTRAINT_KEYWORDS.has(match[0].toLowerCase())) {\n break;\n }\n index += match[0].length;\n typeEnd = index;\n }\n\n // Optional parenthesized type argument, e.g. VARCHAR(255).\n if (typeEnd > identifier.end) {\n let cursor = typeEnd;\n while (cursor < item.length && /\\s/.test(item[cursor])) cursor++;\n if (item[cursor] === '(') {\n let depth = 0;\n while (cursor < item.length) {\n const skipped = skipQuotedOrComment(item, cursor);\n if (skipped !== null) {\n cursor = skipped;\n continue;\n }\n if (item[cursor] === '(') depth++;\n else if (item[cursor] === ')') {\n depth--;\n if (depth === 0) {\n typeEnd = cursor + 1;\n break;\n }\n }\n cursor++;\n }\n }\n }\n\n const rest = item.slice(typeEnd);\n const separator = rest.length === 0 || /^\\s/.test(rest) ? '' : ' ';\n return `${head} ${newType}${separator}${rest}`;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"sqlite-rebuild.js","names":[],"sources":["../../src/migrations/sqlite-rebuild.ts"],"sourcesContent":["/**\n * SQLite table-rebuild migrations (issue #2370)\n *\n * SQLite has no `ALTER TABLE ... ALTER COLUMN ... TYPE`. Before this module\n * every type-bucket change on SQLite (the canonical one being a numeric\n * default edited from `0` to `0.0`, i.e. INTEGER → REAL) turned into an\n * advisory differ comment, which `db:migrate` classifies as\n * *manual intervention* and exits 1 for — forever, because nothing in the\n * toolchain could ever perform the change.\n *\n * This module turns that comment into the executable rebuild SQLite's own\n * documentation prescribes (\"Making Other Kinds Of Table Schema Changes\",\n * lang_altertable.html):\n *\n * 1. `PRAGMA defer_foreign_keys = ON` — defers foreign-key enforcement to\n * COMMIT so the window where the table does not exist cannot raise. It is\n * honored *inside* a transaction (unlike `PRAGMA foreign_keys`, which is a\n * silent no-op there) and resets itself at COMMIT/ROLLBACK.\n * 2. Create a new table under a reserved `_smrt_rebuild_` name carrying the\n * target shape.\n * 3. Copy every row across.\n * 4. Drop the original table.\n * 5. Rename the new table into its place, with `PRAGMA legacy_alter_table`\n * ON for the rename itself (see {@link buildSqliteRebuildStatements}).\n * 6. Recreate the indexes and triggers that died with the original table.\n *\n * The whole plan is a plain statement list, so it is executed by the\n * `MigrationTracker` inside the same transaction as the rest of the batch:\n * a failure anywhere rolls the table back to its original shape and contents.\n *\n * ## The target shape comes from the live DDL, not from the manifest\n *\n * The new table is the *original* `CREATE TABLE` text with only the drifted\n * columns' type tokens replaced. That deliberately preserves everything the\n * manifest does not model or does not own here: columns the manifest has\n * dropped (SMRT is additive — a rebuild must not become an implicit\n * `DROP COLUMN`), table-level constraints, `CHECK`s, collations, and table\n * options such as `WITHOUT ROWID`. The `add_column` changes in the same batch\n * still run their own `ALTER TABLE ... ADD COLUMN`, against the rebuilt table\n * — {@link planSqliteTableRebuilds} hoists the rebuild ahead of them for\n * exactly that reason.\n *\n * ## Why the copy carries no CAST\n *\n * The copy is a plain `INSERT INTO new SELECT ... FROM old`; SQLite applies\n * the destination column's *affinity* on insert, which is exactly the\n * conversion a fresh table would have performed (integer `1` becomes real\n * `1.0` in a REAL column). An explicit `CAST` would be strictly worse: casting\n * a non-numeric TEXT value to REAL/INTEGER silently yields `0`, and casting an\n * ISO timestamp string to SQLite's NUMERIC-affinity `DATETIME` silently yields\n * the leading year. Affinity conversion leaves values it cannot convert\n * untouched instead of destroying them.\n *\n * ## When the rebuild refuses\n *\n * With `PRAGMA foreign_keys` enabled — which the SMRT SQLite adapter is by\n * default — `DROP TABLE` performs an implicit `DELETE FROM` that fires\n * `ON DELETE` actions on children. A child with `ON DELETE CASCADE` therefore\n * loses its rows, and `defer_foreign_keys` does not help: it defers\n * constraint *checks*, not FK *actions*. So when any table declares a foreign\n * key onto the rebuild target and enforcement is on, the plan is refused and\n * the change stays a manual intervention. That includes the target's own\n * self-references, because the staging table copies them and so becomes a\n * child of the table being dropped.\n */\n\nimport { createLogger } from '@happyvertical/logger';\nimport { quoteIdentifier } from '../schema/sql-identifiers.js';\nimport type { SchemaChange, SQLDataType } from '../schema/types.js';\nimport type { DatabaseInterface } from './types.js';\n\nconst logger = createLogger({ level: 'info' });\n\n/**\n * Prefix of the reserved staging table a rebuild creates. `_smrt_`-prefixed\n * names are SMRT system tables, which the differ's dropped-table sweep skips,\n * so a staging table left behind by a non-transactional adapter cannot be\n * mistaken for user data.\n */\nexport const SQLITE_REBUILD_TABLE_PREFIX = '_smrt_rebuild_';\n\n/**\n * The advisory SQL the differ emits for a SQLite type upgrade it cannot\n * perform with an `ALTER TABLE`. `planSqliteTableRebuilds` replaces it with an\n * executable plan; when it cannot, the comment survives and `db:migrate`\n * reports the column as manual drift exactly as it did before #2370.\n */\nexport function sqliteRebuildPlaceholderSql(columnName: string): string {\n return `-- SQLite: Type upgrade for ${quoteIdentifier(columnName)} requires table recreation`;\n}\n\n/**\n * True when `sql` is the differ's \"requires table recreation\" placeholder —\n * i.e. a type upgrade waiting for this module to turn it into a rebuild.\n */\nexport function isSqliteRebuildPlaceholder(sql: string | undefined): boolean {\n return typeof sql === 'string' && sql.includes('requires table recreation');\n}\n\n/**\n * #3008: the placeholder the differ emits for a required column whose\n * existing rows are filled from a declared per-row backfill. SQLite cannot\n * add a NOT NULL column without a default to a populated table, nor\n * `SET NOT NULL` afterwards, so {@link planSqliteTableRebuilds} turns it into\n * a rebuild whose copy step evaluates the backfill for every row. An\n * `add_column` placeholder adds the column; an `alter_column` placeholder\n * tightens an existing nullable column (`COALESCE(col, backfill)`).\n */\nexport function sqliteBackfillPlaceholderSql(columnName: string): string {\n return `-- SQLite: required column ${quoteIdentifier(columnName)} is backfilled by table recreation`;\n}\n\n/** True when `sql` is the #3008 backfill-rebuild placeholder. */\nexport function isSqliteBackfillPlaceholder(sql: string | undefined): boolean {\n return (\n typeof sql === 'string' && sql.includes('is backfilled by table recreation')\n );\n}\n\nfunction blockedSql(columnName: string, reason: string): string {\n // Must stay a single line, and must not read as a no-op: the CLI classifies\n // any comment that says \"no change needed\" as noop and drops it silently.\n return `-- SQLite: ${quoteIdentifier(columnName)} requires a table rebuild, blocked: ${reason}`;\n}\n\nfunction coveredSql(columnName: string, rebuiltWith: string): string {\n // Classified `noop` by the CLI (`no change needed`): the sibling change\n // carrying the plan rebuilds the whole table, this column included.\n return `-- SQLite: ${quoteIdentifier(columnName)} is rebuilt with ${quoteIdentifier(rebuiltWith)} in one table rebuild (no change needed)`;\n}\n\n/** A rebuild plan: the ordered statements plus what they touch. */\nexport interface SqliteTableRebuildPlan {\n /** Ordered, executable statements. Run them in one transaction. */\n statements: string[];\n /** Columns copied from the old table into the new one, in table order. */\n copiedColumns: string[];\n /** Names of the indexes and triggers recreated after the swap. */\n recreatedObjects: string[];\n}\n\n/** Live SQLite state a rebuild needs before it can plan anything. */\ninterface SqliteRebuildContext {\n /** `sqlite_master.sql` for the table (SQLite normalizes IF NOT EXISTS away). */\n createTableSql: string;\n /**\n * Column names in `PRAGMA table_info` order. That pragma omits generated\n * columns, which is what the copy wants: they are not insertable and the\n * preserved DDL recomputes them in the new table.\n */\n columns: string[];\n /** Indexes and triggers that will die with the dropped table. */\n objects: { name: string; sql: string }[];\n /** Whether `PRAGMA foreign_keys` enforcement is on for this connection. */\n foreignKeysEnabled: boolean;\n /** Current `PRAGMA legacy_alter_table` value, restored after the rename. */\n legacyAlterTable: boolean;\n /** Other tables declaring a foreign key onto this table. */\n inboundReferences: string[];\n}\n\nexport interface PlanSqliteTableRebuildsOptions {\n db: DatabaseInterface;\n tableName: string;\n /** The table's changes, as produced by the differ's column comparison. */\n changes: SchemaChange[];\n /** Abstract → SQLite type mapping (the engine's DDL strategy `mapType`). */\n mapType: (type: SQLDataType) => string;\n /**\n * Statements to run after the rebuild for a column the rebuild adds\n * (#3008), e.g. its `CREATE UNIQUE INDEX <table>_<col>_key`.\n */\n addedColumnFollowUps?: (change: SchemaChange) => string[];\n /**\n * Full column definition for a column the rebuild adds (#3008). Defaults\n * to `\"<name>\" <type> NOT NULL [CHECK (...)]`.\n */\n addedColumnDefinition?: (change: SchemaChange) => string;\n}\n\n/**\n * Replace every \"requires table recreation\" placeholder for `tableName` with\n * one executable rebuild.\n *\n * All of the table's drifted columns are retyped by a *single* rebuild, so the\n * first placeholder carries the plan and the rest become no-ops that reference\n * it. Returns the changes unchanged when there is nothing to rebuild, and\n * leaves the placeholders in place (annotated with the reason) when the\n * rebuild cannot be planned safely — the pre-#2370 manual-intervention\n * behavior.\n *\n * When a plan is produced, the rebuild is hoisted to the front of the table's\n * changes. The plan's staging DDL and copy list are captured from the live\n * schema *at diff time*, so a same-batch `ALTER TABLE ... ADD COLUMN` that ran\n * first would be silently undone by the rebuild — both statements succeed, the\n * transaction commits, and the new column is simply gone. The differ emits\n * changes in manifest field order, which puts an `add_column` first whenever\n * the new field is declared above the retyped one, so the order has to be\n * imposed here rather than assumed. Rebuild first, then add columns to the\n * rebuilt table.\n */\nexport async function planSqliteTableRebuilds(\n options: PlanSqliteTableRebuildsOptions,\n): Promise<SchemaChange[]> {\n const { db, tableName, changes, mapType } = options;\n\n const retypes = changes.filter(\n (change) =>\n change.type === 'type_upgrade' &&\n change.name &&\n change.column &&\n isSqliteRebuildPlaceholder(change.sql),\n );\n const backfills = changes.filter(\n (change) =>\n (change.type === 'add_column' || change.type === 'alter_column') &&\n change.name &&\n typeof change.column?.backfill === 'string' &&\n isSqliteBackfillPlaceholder(change.sql),\n );\n const pending = [...retypes, ...backfills];\n if (pending.length === 0) {\n return changes;\n }\n\n const block = (reason: string): SchemaChange[] => {\n logger.debug(\n `[SQLiteRebuild] Cannot rebuild ${tableName}: ${reason}; leaving ${pending.length} type upgrade(s) as manual drift`,\n );\n return changes.map((change) =>\n pending.includes(change)\n ? { ...change, sql: blockedSql(change.name as string, reason) }\n : change,\n );\n };\n\n let context: SqliteRebuildContext;\n try {\n context = await readSqliteRebuildContext(db, tableName);\n } catch (error) {\n return block(\n `live schema introspection failed (${error instanceof Error ? error.message : String(error)})`,\n );\n }\n\n if (!context.createTableSql) {\n return block('no CREATE TABLE statement found in sqlite_master');\n }\n if (context.columns.length === 0) {\n return block('PRAGMA table_info returned no columns');\n }\n if (context.foreignKeysEnabled && context.inboundReferences.length > 0) {\n return block(\n `PRAGMA foreign_keys is ON and ${context.inboundReferences\n .map((name) => `\"${name}\"`)\n .join(\n ', ',\n )} declare a foreign key onto this table, so DROP TABLE would fire their ON DELETE actions`,\n );\n }\n\n const columnTypes = new Map<string, string>();\n for (const change of retypes) {\n columnTypes.set(\n change.name as string,\n mapType((change.column as { type: SQLDataType }).type),\n );\n }\n\n const appendColumns: SqliteAppendedColumn[] = [];\n const tightenColumns = new Map<string, string>();\n const followUps: string[] = [];\n for (const change of backfills) {\n const name = change.name as string;\n const column = change.column as NonNullable<SchemaChange['column']>;\n const backfill = column.backfill as string;\n if (change.type === 'add_column') {\n const check = column.check ? ` CHECK (${column.check})` : '';\n appendColumns.push({\n name,\n definition:\n options.addedColumnDefinition?.(change) ??\n `${quoteIdentifier(name)} ${mapType(column.type)} NOT NULL${check}`,\n copyExpression: `(${backfill})`,\n });\n followUps.push(...(options.addedColumnFollowUps?.(change) ?? []));\n } else {\n tightenColumns.set(\n name,\n `COALESCE(${quoteIdentifier(name)}, (${backfill}))`,\n );\n }\n }\n\n const plan = buildSqliteRebuildStatements({\n tableName,\n createTableSql: context.createTableSql,\n columnTypes,\n columns: context.columns,\n objects: context.objects,\n legacyAlterTable: context.legacyAlterTable,\n appendColumns,\n tightenColumns,\n });\n\n if (!plan) {\n return block(\n 'the live CREATE TABLE statement could not be rewritten for the target column types',\n );\n }\n\n const [primary, ...covered] = pending;\n const statements = [...plan.statements, ...followUps];\n const rebuilt = changes.map((change) => {\n if (change === primary) {\n return {\n ...change,\n // `sql` stays a real statement so the CLI classifies this upgrade as\n // executable; every consumer that actually runs the change prefers\n // `sqlStatements`.\n sql: statements[0],\n sqlStatements: statements,\n };\n }\n if (covered.includes(change) && change.type === 'add_column') {\n // Nothing left to run: the primary's rebuild adds this column. An\n // `add_column` is always executed, so it cannot stay as a no-op\n // comment the way a covered type upgrade does — drop it.\n return null;\n }\n if (covered.includes(change)) {\n const { sqlStatements: _dropped, ...rest } = change;\n return {\n ...rest,\n sql: coveredSql(change.name as string, primary.name as string),\n };\n }\n return change;\n });\n\n // Hoist the rebuild ahead of the table's other column changes, keeping both\n // groups in their original relative order. Consumers execute `diff.changes`\n // in order, and the plan was built from the pre-batch table shape.\n const rebuildPositions = new Set(\n pending.map((change) => changes.indexOf(change)),\n );\n return [\n ...rebuilt.filter((_, index) => rebuildPositions.has(index)),\n ...rebuilt.filter((_, index) => !rebuildPositions.has(index)),\n ].filter((change): change is SchemaChange => change !== null);\n}\n\n/**\n * Read everything the rebuild needs from the live database.\n *\n * Every query is SQLite-only (`sqlite_master`, `PRAGMA`), and the caller only\n * reaches this for an engine already detected as SQLite.\n */\nasync function readSqliteRebuildContext(\n db: DatabaseInterface,\n tableName: string,\n): Promise<SqliteRebuildContext> {\n const tableRows = await db.query(\n `SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ?`,\n tableName,\n );\n const createTableSql = readString(\n (tableRows.rows[0] as { sql?: unknown } | undefined)?.sql,\n );\n\n const columnRows = await db.query(\n `PRAGMA table_info(${quoteIdentifier(tableName)})`,\n );\n const columns = columnRows.rows\n .map((row) => readString((row as { name?: unknown }).name))\n .filter((name): name is string => Boolean(name));\n\n // `sql IS NULL` marks the implicit indexes SQLite creates for inline\n // PRIMARY KEY / UNIQUE constraints; those are carried by the CREATE TABLE\n // text and must not be replayed separately.\n const objectRows = await db.query(\n `SELECT name, sql FROM sqlite_master\n WHERE tbl_name = ? AND sql IS NOT NULL\n AND type IN ('index', 'trigger') AND name NOT LIKE 'sqlite_%'`,\n tableName,\n );\n const objects: { name: string; sql: string }[] = [];\n for (const row of objectRows.rows as { name?: unknown; sql?: unknown }[]) {\n const name = readString(row.name);\n const sql = readString(row.sql);\n if (name && sql) {\n objects.push({ name, sql });\n }\n }\n\n const foreignKeysEnabled = readPragmaFlag(\n await db.query('PRAGMA foreign_keys'),\n );\n const legacyAlterTable = readPragmaFlag(\n await db.query('PRAGMA legacy_alter_table'),\n );\n\n const inboundReferences = foreignKeysEnabled\n ? await readInboundForeignKeys(db, tableName)\n : [];\n\n return {\n createTableSql,\n columns,\n objects,\n foreignKeysEnabled,\n legacyAlterTable,\n inboundReferences,\n };\n}\n\n/**\n * Names of tables that declare a foreign key onto `tableName`.\n *\n * The table itself counts. A self-reference is copied verbatim into the\n * staging table, which therefore becomes a real child of the live table, and\n * `DROP TABLE` then cascades the *staged* rows away — verified: a two-row\n * self-referencing table finishes the rebuild holding one row.\n */\nasync function readInboundForeignKeys(\n db: DatabaseInterface,\n tableName: string,\n): Promise<string[]> {\n const tables = await db.query(\n `SELECT name FROM sqlite_master\n WHERE type = 'table' AND name NOT LIKE 'sqlite_%'`,\n );\n\n const referencing: string[] = [];\n const target = tableName.toLowerCase();\n for (const row of tables.rows as { name?: unknown }[]) {\n const name = readString(row.name);\n if (!name) continue;\n const fks = await db.query(\n `PRAGMA foreign_key_list(${quoteIdentifier(name)})`,\n );\n const referencesTarget = (fks.rows as { table?: unknown }[]).some(\n (fk) => readString(fk.table).toLowerCase() === target,\n );\n if (referencesTarget) {\n referencing.push(name);\n }\n }\n\n return referencing;\n}\n\nfunction readString(value: unknown): string {\n return typeof value === 'string' ? value : '';\n}\n\n/** Read a single-row/single-column PRAGMA result as a boolean. */\nfunction readPragmaFlag(result: { rows: Record<string, unknown>[] }): boolean {\n const row = result.rows[0];\n if (!row) return false;\n const value = Object.values(row)[0];\n if (typeof value === 'number') return value !== 0;\n if (typeof value === 'boolean') return value;\n if (typeof value === 'string') return value !== '0' && value !== '';\n return false;\n}\n\nexport interface BuildSqliteRebuildStatementsInput {\n tableName: string;\n /** The live `sqlite_master.sql` CREATE TABLE text. */\n createTableSql: string;\n /** Column name → target SQLite type, for the columns being retyped. */\n columnTypes: Map<string, string>;\n /** Every column of the live table, in `PRAGMA table_info` order. */\n columns: string[];\n /** Indexes and triggers to replay after the swap. */\n objects: { name: string; sql: string }[];\n /** Current `PRAGMA legacy_alter_table` value, restored after the rename. */\n legacyAlterTable: boolean;\n /** #3008: new columns the rebuild adds, filled by their copy expression. */\n appendColumns?: SqliteAppendedColumn[];\n /**\n * #3008: existing columns the rebuild tightens to NOT NULL, mapped to the\n * copy expression that fills their NULLs.\n */\n tightenColumns?: Map<string, string>;\n}\n\n/** A column a rebuild appends to the table (#3008). */\nexport interface SqliteAppendedColumn {\n name: string;\n /** Full column definition, e.g. `\"key\" TEXT NOT NULL`. */\n definition: string;\n /** Per-row value copied into it, evaluated against the old table. */\n copyExpression: string;\n}\n\n/**\n * Build the ordered statement list for one table rebuild.\n *\n * Returns `null` when the live `CREATE TABLE` text cannot be rewritten (an\n * unparsable statement, or a requested column that is not in it) — the caller\n * then leaves the change as manual drift rather than emitting SQL that would\n * \"succeed\" without fixing the column, which would re-diff forever.\n *\n * `PRAGMA legacy_alter_table` brackets the rename because SQLite ≥ 3.25\n * re-parses the whole schema on `ALTER TABLE ... RENAME`: a view left\n * referencing the just-dropped table makes the rename fail outright\n * (\"error in view … no such table\"). Legacy mode renames the table without\n * that reference rewriting, which is exactly what a rebuild wants — the name\n * the views refer to is about to exist again. It is restored to its previous\n * value immediately afterwards; a rolled-back batch leaves it set, which is\n * inert for SMRT (nothing else in the toolchain renames a table) but is\n * documented in `packages/core/agents/schema-paths.md`.\n */\nexport function buildSqliteRebuildStatements(\n input: BuildSqliteRebuildStatementsInput,\n): SqliteTableRebuildPlan | null {\n const {\n tableName,\n createTableSql,\n columnTypes,\n columns,\n objects,\n legacyAlterTable,\n appendColumns = [],\n tightenColumns = new Map<string, string>(),\n } = input;\n\n const stagingTable = `${SQLITE_REBUILD_TABLE_PREFIX}${tableName}`;\n const createStaging = rewriteSqliteCreateTable(createTableSql, {\n tableName: stagingTable,\n columnTypes,\n notNullColumns: new Set(tightenColumns.keys()),\n appendDefinitions: appendColumns.map((column) => column.definition),\n });\n if (!createStaging) {\n return null;\n }\n const liveColumns = new Set(columns.map((name) => name.toLowerCase()));\n for (const name of tightenColumns.keys()) {\n if (!liveColumns.has(name.toLowerCase())) return null;\n }\n for (const column of appendColumns) {\n if (liveColumns.has(column.name.toLowerCase())) return null;\n }\n\n const quotedTable = quoteIdentifier(tableName);\n const quotedStaging = quoteIdentifier(stagingTable);\n const targetList = [...columns, ...appendColumns.map((c) => c.name)]\n .map((name) => quoteIdentifier(name))\n .join(', ');\n const sourceList = [\n ...columns.map((name) => tightenColumns.get(name) ?? quoteIdentifier(name)),\n ...appendColumns.map((column) => column.copyExpression),\n ].join(', ');\n\n const statements = [\n // Deferred FK enforcement survives the window where the table is absent.\n // Unlike `PRAGMA foreign_keys`, this one is honored inside a transaction\n // and resets itself at COMMIT/ROLLBACK.\n 'PRAGMA defer_foreign_keys = ON',\n // Idempotent for a retry after a partially applied non-transactional run.\n `DROP TABLE IF EXISTS ${quotedStaging}`,\n createStaging,\n `INSERT INTO ${quotedStaging} (${targetList}) SELECT ${sourceList} FROM ${quotedTable}`,\n `DROP TABLE ${quotedTable}`,\n 'PRAGMA legacy_alter_table = ON',\n `ALTER TABLE ${quotedStaging} RENAME TO ${quotedTable}`,\n `PRAGMA legacy_alter_table = ${legacyAlterTable ? 'ON' : 'OFF'}`,\n // The indexes and triggers went down with the dropped table. Their\n // original text still names the table correctly (it was renamed back), so\n // replaying it verbatim preserves partial predicates, expression indexes,\n // and trigger bodies that a manifest-driven regeneration would flatten.\n ...objects.map((object) => object.sql),\n ];\n\n return {\n statements,\n copiedColumns: [...columns],\n recreatedObjects: objects.map((object) => object.name),\n };\n}\n\n/**\n * Rewrite a live `CREATE TABLE` statement into the rebuild's target shape:\n * the same body under a new table name, with the declared type of each column\n * in `columnTypes` replaced.\n *\n * Everything else is preserved verbatim — other columns, their defaults,\n * `NOT NULL`/`PRIMARY KEY`/`CHECK`/`COLLATE` clauses, table-level\n * constraints, and trailing table options (`WITHOUT ROWID`, `STRICT`).\n *\n * Returns `null` if the statement cannot be parsed or if any requested column\n * is not present in it.\n */\nexport function rewriteSqliteCreateTable(\n createTableSql: string,\n target: {\n tableName: string;\n columnTypes: Map<string, string>;\n /** #3008: columns whose definition gains `NOT NULL`. */\n notNullColumns?: Set<string>;\n /** #3008: column definitions appended after the existing columns. */\n appendDefinitions?: string[];\n },\n): string | null {\n const parsed = parseCreateTableBody(createTableSql);\n if (!parsed) {\n return null;\n }\n\n const wanted = new Map<string, string>();\n for (const [name, type] of target.columnTypes) {\n wanted.set(name.toLowerCase(), type);\n }\n\n const tighten = new Set(\n [...(target.notNullColumns ?? [])].map((name) => name.toLowerCase()),\n );\n\n const rewritten: string[] = [];\n let lastColumnIndex = -1;\n for (const item of parsed.items) {\n const columnName = readColumnDefinitionName(item);\n if (columnName) lastColumnIndex = rewritten.length;\n let current = item;\n if (columnName && tighten.has(columnName.toLowerCase())) {\n tighten.delete(columnName.toLowerCase());\n if (!/\\bnot\\s+null\\b/i.test(current)) {\n current = `${current} NOT NULL`;\n }\n }\n const newType = columnName ? wanted.get(columnName.toLowerCase()) : null;\n if (!columnName || !newType) {\n rewritten.push(current);\n continue;\n }\n const replaced = replaceColumnType(current, newType);\n if (!replaced) {\n return null;\n }\n rewritten.push(replaced);\n wanted.delete(columnName.toLowerCase());\n }\n\n if (tighten.size > 0) {\n return null;\n }\n // Column definitions must precede table-level constraints.\n rewritten.splice(lastColumnIndex + 1, 0, ...(target.appendDefinitions ?? []));\n\n if (wanted.size > 0) {\n // A column the differ wants retyped is not in the live DDL. Rebuilding\n // would not fix the drift, so refuse instead of emitting a no-op plan.\n return null;\n }\n\n const tail = parsed.tail ? ` ${parsed.tail}` : '';\n return `CREATE TABLE ${quoteIdentifier(target.tableName)} (\\n ${rewritten.join(\n ',\\n ',\n )}\\n)${tail}`;\n}\n\ninterface ParsedCreateTable {\n /** Top-level, comma-separated column and constraint definitions. */\n items: string[];\n /** Table options after the closing paren (`WITHOUT ROWID`, `STRICT`). */\n tail: string;\n}\n\n/**\n * Split a `CREATE TABLE` statement into its top-level definition items plus\n * any trailing table options, honoring SQLite's quoting (`'…'`, `\"…\"`,\n * `` `…` ``, `[…]`) and comments so a quoted comma or paren cannot split the\n * body.\n *\n * @internal exported for testing\n */\nexport function parseCreateTableBody(sql: string): ParsedCreateTable | null {\n let index = 0;\n let open = -1;\n while (index < sql.length) {\n const skipped = skipQuotedOrComment(sql, index);\n if (skipped !== null) {\n index = skipped;\n continue;\n }\n if (sql[index] === '(') {\n open = index;\n break;\n }\n index++;\n }\n if (open === -1) {\n return null;\n }\n\n const items: string[] = [];\n let depth = 1;\n let itemStart = open + 1;\n let close = -1;\n index = open + 1;\n while (index < sql.length) {\n const skipped = skipQuotedOrComment(sql, index);\n if (skipped !== null) {\n index = skipped;\n continue;\n }\n const char = sql[index];\n if (char === '(') {\n depth++;\n } else if (char === ')') {\n depth--;\n if (depth === 0) {\n close = index;\n items.push(sql.slice(itemStart, index));\n break;\n }\n } else if (char === ',' && depth === 1) {\n items.push(sql.slice(itemStart, index));\n itemStart = index + 1;\n }\n index++;\n }\n\n if (close === -1) {\n return null;\n }\n\n const trimmed = items.map((item) => item.trim()).filter(Boolean);\n if (trimmed.length === 0) {\n return null;\n }\n\n return {\n items: trimmed,\n tail: sql\n .slice(close + 1)\n .trim()\n .replace(/;+$/, '')\n .trim(),\n };\n}\n\n/**\n * If `sql[index]` opens a quoted identifier, string literal, or comment,\n * return the index just past its end. Otherwise return `null`.\n */\nfunction skipQuotedOrComment(sql: string, index: number): number | null {\n const char = sql[index];\n\n if (char === \"'\" || char === '\"' || char === '`') {\n let cursor = index + 1;\n while (cursor < sql.length) {\n if (sql[cursor] === char) {\n // A doubled delimiter is an escaped one, not the end of the token.\n if (sql[cursor + 1] === char) {\n cursor += 2;\n continue;\n }\n return cursor + 1;\n }\n cursor++;\n }\n return sql.length;\n }\n\n if (char === '[') {\n const end = sql.indexOf(']', index + 1);\n return end === -1 ? sql.length : end + 1;\n }\n\n if (char === '-' && sql[index + 1] === '-') {\n const end = sql.indexOf('\\n', index);\n return end === -1 ? sql.length : end + 1;\n }\n\n if (char === '/' && sql[index + 1] === '*') {\n const end = sql.indexOf('*/', index + 2);\n return end === -1 ? sql.length : end + 2;\n }\n\n return null;\n}\n\n/**\n * Keywords that start a *table*-level constraint, i.e. a body item that is not\n * a column definition.\n */\nconst TABLE_CONSTRAINT_KEYWORDS = new Set([\n 'constraint',\n 'primary',\n 'unique',\n 'check',\n 'foreign',\n]);\n\n/**\n * Keywords that end a column's type name and start its constraint list.\n */\nconst COLUMN_CONSTRAINT_KEYWORDS = new Set([\n 'constraint',\n 'primary',\n 'not',\n 'null',\n 'unique',\n 'check',\n 'default',\n 'collate',\n 'references',\n 'generated',\n 'as',\n]);\n\ninterface Identifier {\n name: string;\n end: number;\n}\n\n/** Read the leading identifier of a body item (quoted or bare). */\nfunction readLeadingIdentifier(item: string): Identifier | null {\n let index = 0;\n while (index < item.length && /\\s/.test(item[index])) index++;\n if (index >= item.length) return null;\n\n const char = item[index];\n if (char === '\"' || char === '`' || char === '[') {\n const end = skipQuotedOrComment(item, index);\n if (end === null) return null;\n const raw = item.slice(index + 1, end - 1);\n // `[…]` has no escape form; `\"…\"` / `` `…` `` double the delimiter.\n return {\n name: char === '[' ? raw : raw.split(char + char).join(char),\n end,\n };\n }\n\n const match = /^[A-Za-z_][\\w$]*/.exec(item.slice(index));\n if (!match) return null;\n return { name: match[0], end: index + match[0].length };\n}\n\n/**\n * The column name of a body item, or `null` when the item is a table-level\n * constraint rather than a column definition.\n *\n * @internal exported for testing\n */\nexport function readColumnDefinitionName(item: string): string | null {\n const identifier = readLeadingIdentifier(item);\n if (!identifier) return null;\n\n const isQuoted = /^\\s*[\"`[]/.test(item);\n if (\n !isQuoted &&\n TABLE_CONSTRAINT_KEYWORDS.has(identifier.name.toLowerCase())\n ) {\n return null;\n }\n return identifier.name;\n}\n\n/**\n * Replace the declared type of a column definition, keeping its name and every\n * constraint that follows. A column declared without a type gets one inserted.\n *\n * @internal exported for testing\n */\nexport function replaceColumnType(\n item: string,\n newType: string,\n): string | null {\n const identifier = readLeadingIdentifier(item);\n if (!identifier) return null;\n\n const head = item.slice(0, identifier.end);\n let index = identifier.end;\n\n // The type name is one or more bare words followed by an optional\n // `(size[, size])` — everything before the first column-constraint keyword.\n let typeEnd = index;\n while (index < item.length) {\n while (index < item.length && /\\s/.test(item[index])) index++;\n const match = /^[A-Za-z_][\\w$]*/.exec(item.slice(index));\n if (!match || COLUMN_CONSTRAINT_KEYWORDS.has(match[0].toLowerCase())) {\n break;\n }\n index += match[0].length;\n typeEnd = index;\n }\n\n // Optional parenthesized type argument, e.g. VARCHAR(255).\n if (typeEnd > identifier.end) {\n let cursor = typeEnd;\n while (cursor < item.length && /\\s/.test(item[cursor])) cursor++;\n if (item[cursor] === '(') {\n let depth = 0;\n while (cursor < item.length) {\n const skipped = skipQuotedOrComment(item, cursor);\n if (skipped !== null) {\n cursor = skipped;\n continue;\n }\n if (item[cursor] === '(') depth++;\n else if (item[cursor] === ')') {\n depth--;\n if (depth === 0) {\n typeEnd = cursor + 1;\n break;\n }\n }\n cursor++;\n }\n }\n }\n\n const rest = item.slice(typeEnd);\n const separator = rest.length === 0 || /^\\s/.test(rest) ? '' : ' ';\n return `${head} ${newType}${separator}${rest}`;\n}\n"],"mappings":"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"}
|
package/dist/migrations.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { parsePostgresTimeoutMs } from "./postgres-timeouts.js";
|
|
2
2
|
import { SQLITE_REBUILD_TABLE_PREFIX, buildSqliteRebuildStatements, isSqliteRebuildPlaceholder, planSqliteTableRebuilds, rewriteSqliteCreateTable, sqliteRebuildPlaceholderSql } from "./migrations/sqlite-rebuild.js";
|
|
3
|
-
import { SchemaComparer, canonicalizeDefault, generateSchemaDiff, getSQLFromDiff, hasActionableChanges, isAdvisoryOnlyChange, isInfoOnlyChange, isManualOrAdvisoryChange, uniqueColumnIndexName } from "./migrations/differ.js";
|
|
3
|
+
import { REQUIRED_COLUMN_NOT_ADDED, SchemaComparer, canonicalizeDefault, generateSchemaDiff, getSQLFromDiff, hasActionableChanges, isAdvisoryOnlyChange, isInfoOnlyChange, isManualOrAdvisoryChange, uniqueColumnIndexName } from "./migrations/differ.js";
|
|
4
4
|
import { BackfillTableUnavailableError, BackfillTracker } from "./migrations/backfill-tracker.js";
|
|
5
5
|
import { buildIntegerWidthStatements, buildIntegerWidthTableStatements, collectIntegerWidthTargets, preflightIntegerWidthWidening, widenIntegerColumnsToBigInt } from "./migrations/integer-width.js";
|
|
6
6
|
import { computeChecksum, normalizeSQL, shortChecksum, verifyChecksum } from "./migrations/checksum.js";
|
|
@@ -11,4 +11,4 @@ import { collectNullEqualIndexTargets, migrateNullEqualIndexes, nullEqualDuplica
|
|
|
11
11
|
import { MigrationTracker, buildConcurrentIndexPlan, extractCreatedIndexName, planPostgresStatements } from "./migrations/tracker.js";
|
|
12
12
|
import { getPendingSchemaStatements, migrateSmrtSchemas } from "./migrations/orchestrate.js";
|
|
13
13
|
import "./migrations/index.js";
|
|
14
|
-
export { BackfillTableUnavailableError, BackfillTracker, FRAMEWORK_BASE_TABLE_NAMES, MigrationGenerator, MigrationTracker, MinorUnitsPreflightError, SQLITE_REBUILD_TABLE_PREFIX, SchemaComparer, buildConcurrentIndexPlan, buildIntegerWidthStatements, buildIntegerWidthTableStatements, buildMinorUnitsStatements, buildSqliteRebuildStatements, canonicalizeDefault, collectIntegerWidthTargets, collectNullEqualIndexTargets, computeChecksum, createMigrationDefinition, dropFrameworkBaseTables, extractCreatedIndexName, generateMigrationSequence, generateMigrationTimestamp, generateSchemaDiff, getPendingSchemaStatements, getSQLFromDiff, hasActionableChanges, isAdvisoryOnlyChange, isInfoOnlyChange, isManualOrAdvisoryChange, isSqliteRebuildPlaceholder, migrateNullEqualIndexes, migrateSmrtSchemas, normalizeSQL, nullEqualDuplicateDetector, nullEqualIndexStatements, parsePostgresTimeoutMs, planFrameworkBaseTableDrop, planPostgresStatements, planSqliteTableRebuilds, preflightIntegerWidthWidening, preflightMinorUnitsRescale, preflightNullEqualIndexes, rescaleMoneyColumnsToMinorUnits, rewriteSqliteCreateTable, shortChecksum, sqliteRebuildPlaceholderSql, uniqueColumnIndexName, verifyChecksum, widenIntegerColumnsToBigInt };
|
|
14
|
+
export { BackfillTableUnavailableError, BackfillTracker, FRAMEWORK_BASE_TABLE_NAMES, MigrationGenerator, MigrationTracker, MinorUnitsPreflightError, REQUIRED_COLUMN_NOT_ADDED, SQLITE_REBUILD_TABLE_PREFIX, SchemaComparer, buildConcurrentIndexPlan, buildIntegerWidthStatements, buildIntegerWidthTableStatements, buildMinorUnitsStatements, buildSqliteRebuildStatements, canonicalizeDefault, collectIntegerWidthTargets, collectNullEqualIndexTargets, computeChecksum, createMigrationDefinition, dropFrameworkBaseTables, extractCreatedIndexName, generateMigrationSequence, generateMigrationTimestamp, generateSchemaDiff, getPendingSchemaStatements, getSQLFromDiff, hasActionableChanges, isAdvisoryOnlyChange, isInfoOnlyChange, isManualOrAdvisoryChange, isSqliteRebuildPlaceholder, migrateNullEqualIndexes, migrateSmrtSchemas, normalizeSQL, nullEqualDuplicateDetector, nullEqualIndexStatements, parsePostgresTimeoutMs, planFrameworkBaseTableDrop, planPostgresStatements, planSqliteTableRebuilds, preflightIntegerWidthWidening, preflightMinorUnitsRescale, preflightNullEqualIndexes, rescaleMoneyColumnsToMinorUnits, rewriteSqliteCreateTable, shortChecksum, sqliteRebuildPlaceholderSql, uniqueColumnIndexName, verifyChecksum, widenIntegerColumnsToBigInt };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//#region ../../node_modules/.pnpm/minimatch@10.2.
|
|
1
|
+
//#region ../../node_modules/.pnpm/minimatch@10.2.6/node_modules/minimatch/dist/esm/assert-valid-pattern.js
|
|
2
2
|
var MAX_PATTERN_LENGTH = 1024 * 64;
|
|
3
3
|
var assertValidPattern = (pattern) => {
|
|
4
4
|
if (typeof pattern !== "string") throw new TypeError("invalid pattern");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assert-valid-pattern.js","names":[],"sources":["../../../../../../../../../../node_modules/.pnpm/minimatch@10.2.
|
|
1
|
+
{"version":3,"file":"assert-valid-pattern.js","names":[],"sources":["../../../../../../../../../../node_modules/.pnpm/minimatch@10.2.6/node_modules/minimatch/dist/esm/assert-valid-pattern.js"],"sourcesContent":["const MAX_PATTERN_LENGTH = 1024 * 64;\nexport const assertValidPattern = (pattern) => {\n if (typeof pattern !== 'string') {\n throw new TypeError('invalid pattern');\n }\n if (pattern.length > MAX_PATTERN_LENGTH) {\n throw new TypeError('pattern is too long');\n }\n};\n//# sourceMappingURL=assert-valid-pattern.js.map"],"x_google_ignoreList":[0],"mappings":";AAAA,IAAM,qBAAqB,OAAO;AAClC,IAAa,sBAAsB,YAAY;CAC3C,IAAI,OAAO,YAAY,UACnB,MAAM,IAAI,UAAU,iBAAiB;CAEzC,IAAI,QAAQ,SAAS,oBACjB,MAAM,IAAI,UAAU,qBAAqB;AAEjD"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { parseClass } from "./brace-expressions.js";
|
|
2
2
|
import { unescape } from "./unescape.js";
|
|
3
|
-
//#region ../../node_modules/.pnpm/minimatch@10.2.
|
|
3
|
+
//#region ../../node_modules/.pnpm/minimatch@10.2.6/node_modules/minimatch/dist/esm/ast.js
|
|
4
4
|
var _a;
|
|
5
5
|
var types = /* @__PURE__ */ new Set([
|
|
6
6
|
"!",
|