@happyvertical/smrt-core 0.41.0 → 0.42.0

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 (96) hide show
  1. package/AGENTS.md +1 -1
  2. package/agents/schema-paths.md +79 -8
  3. package/dist/change-feed.d.ts.map +1 -1
  4. package/dist/change-feed.js +2 -2
  5. package/dist/change-feed.js.map +1 -1
  6. package/dist/change-signals.d.ts.map +1 -1
  7. package/dist/change-signals.js +8 -1
  8. package/dist/change-signals.js.map +1 -1
  9. package/dist/class.d.ts.map +1 -1
  10. package/dist/class.js +10 -9
  11. package/dist/class.js.map +1 -1
  12. package/dist/collection.d.ts.map +1 -1
  13. package/dist/collection.js +2 -2
  14. package/dist/collection.js.map +1 -1
  15. package/dist/dispatch/collections/Dispatches.d.ts.map +1 -1
  16. package/dist/dispatch/collections/Dispatches.js +5 -5
  17. package/dist/dispatch/collections/Dispatches.js.map +1 -1
  18. package/dist/dispatch/models/Dispatch.d.ts +1 -1
  19. package/dist/dispatch/models/Dispatch.d.ts.map +1 -1
  20. package/dist/dispatch/models/Dispatch.js +3 -2
  21. package/dist/dispatch/models/Dispatch.js.map +1 -1
  22. package/dist/dispatch/models/DispatchSubscription.d.ts +1 -1
  23. package/dist/dispatch/models/DispatchSubscription.d.ts.map +1 -1
  24. package/dist/dispatch/models/DispatchSubscription.js +2 -1
  25. package/dist/dispatch/models/DispatchSubscription.js.map +1 -1
  26. package/dist/embeddings/storage.d.ts.map +1 -1
  27. package/dist/embeddings/storage.js +2 -1
  28. package/dist/embeddings/storage.js.map +1 -1
  29. package/dist/learning/memory.d.ts.map +1 -1
  30. package/dist/learning/memory.js +5 -4
  31. package/dist/learning/memory.js.map +1 -1
  32. package/dist/manifest/static-manifest.js +1 -1
  33. package/dist/manifest/static-manifest.js.map +1 -1
  34. package/dist/manifest/store.js +1 -1
  35. package/dist/manifest.json +1 -1
  36. package/dist/migrations/differ.js +2 -2
  37. package/dist/migrations/differ.js.map +1 -1
  38. package/dist/migrations/index.d.ts +1 -0
  39. package/dist/migrations/index.d.ts.map +1 -1
  40. package/dist/migrations/index.js +2 -1
  41. package/dist/migrations/integer-width.d.ts +99 -0
  42. package/dist/migrations/integer-width.d.ts.map +1 -0
  43. package/dist/migrations/integer-width.js +237 -0
  44. package/dist/migrations/integer-width.js.map +1 -0
  45. package/dist/migrations/minor-units.d.ts +2 -2
  46. package/dist/migrations/minor-units.d.ts.map +1 -1
  47. package/dist/migrations/minor-units.js +16 -14
  48. package/dist/migrations/minor-units.js.map +1 -1
  49. package/dist/migrations/tracker.d.ts.map +1 -1
  50. package/dist/migrations/tracker.js +28 -20
  51. package/dist/migrations/tracker.js.map +1 -1
  52. package/dist/migrations.js +2 -1
  53. package/dist/schema/ddl/duckdb-strategy.d.ts.map +1 -1
  54. package/dist/schema/ddl/duckdb-strategy.js +1 -0
  55. package/dist/schema/ddl/duckdb-strategy.js.map +1 -1
  56. package/dist/schema/ddl/postgres-strategy.d.ts.map +1 -1
  57. package/dist/schema/ddl/postgres-strategy.js +2 -0
  58. package/dist/schema/ddl/postgres-strategy.js.map +1 -1
  59. package/dist/schema/generator.d.ts +1 -33
  60. package/dist/schema/generator.d.ts.map +1 -1
  61. package/dist/schema/generator.js +1 -150
  62. package/dist/schema/generator.js.map +1 -1
  63. package/dist/schema/index.d.ts +1 -2
  64. package/dist/schema/index.d.ts.map +1 -1
  65. package/dist/schema/index.js +1 -2
  66. package/dist/schema/live-parity.d.ts +3 -1
  67. package/dist/schema/live-parity.d.ts.map +1 -1
  68. package/dist/schema/live-parity.js +19 -0
  69. package/dist/schema/live-parity.js.map +1 -1
  70. package/dist/schema/types.d.ts +17 -10
  71. package/dist/schema/types.d.ts.map +1 -1
  72. package/dist/schema.js +1 -2
  73. package/dist/smrt-knowledge.json +7 -7
  74. package/dist/system/retention.d.ts.map +1 -1
  75. package/dist/system/retention.js +3 -3
  76. package/dist/system/retention.js.map +1 -1
  77. package/dist/system/schema.d.ts +2 -1
  78. package/dist/system/schema.d.ts.map +1 -1
  79. package/dist/system/schema.js +4 -2
  80. package/dist/system/schema.js.map +1 -1
  81. package/dist/utils/safe-integer.d.ts +19 -0
  82. package/dist/utils/safe-integer.d.ts.map +1 -0
  83. package/dist/utils/safe-integer.js +31 -0
  84. package/dist/utils/safe-integer.js.map +1 -0
  85. package/dist/utils.d.ts +2 -1
  86. package/dist/utils.d.ts.map +1 -1
  87. package/dist/utils.js +6 -3
  88. package/dist/utils.js.map +1 -1
  89. package/dist/vite-plugin/index.d.ts.map +1 -1
  90. package/dist/vite-plugin/index.js +0 -53
  91. package/dist/vite-plugin/index.js.map +1 -1
  92. package/package.json +4 -4
  93. package/dist/schema/override-system.d.ts +0 -43
  94. package/dist/schema/override-system.d.ts.map +0 -1
  95. package/dist/schema/override-system.js +0 -205
  96. package/dist/schema/override-system.js.map +0 -1
@@ -2,9 +2,10 @@ import { parsePostgresTimeoutMs } from "../postgres-timeouts.js";
2
2
  import { SQLITE_REBUILD_TABLE_PREFIX, buildSqliteRebuildStatements, isSqliteRebuildPlaceholder, planSqliteTableRebuilds, rewriteSqliteCreateTable, sqliteRebuildPlaceholderSql } from "./sqlite-rebuild.js";
3
3
  import { SchemaComparer, canonicalizeDefault, generateSchemaDiff, getSQLFromDiff, hasActionableChanges, isAdvisoryOnlyChange, isInfoOnlyChange, isManualOrAdvisoryChange, uniqueColumnIndexName } from "./differ.js";
4
4
  import { BackfillTableUnavailableError, BackfillTracker } from "./backfill-tracker.js";
5
+ import { buildIntegerWidthStatements, buildIntegerWidthTableStatements, collectIntegerWidthTargets, preflightIntegerWidthWidening, widenIntegerColumnsToBigInt } from "./integer-width.js";
5
6
  import { computeChecksum, normalizeSQL, shortChecksum, verifyChecksum } from "./checksum.js";
6
7
  import { MigrationGenerator, createMigrationDefinition, generateMigrationSequence, generateMigrationTimestamp } from "./generator.js";
7
8
  import { MinorUnitsPreflightError, buildMinorUnitsStatements, preflightMinorUnitsRescale, rescaleMoneyColumnsToMinorUnits } from "./minor-units.js";
8
9
  import { MigrationTracker, buildConcurrentIndexPlan, extractCreatedIndexName, planPostgresStatements } from "./tracker.js";
9
10
  import { getPendingSchemaStatements, migrateSmrtSchemas } from "./orchestrate.js";
10
- export { BackfillTableUnavailableError, BackfillTracker, MigrationGenerator, MigrationTracker, MinorUnitsPreflightError, SQLITE_REBUILD_TABLE_PREFIX, SchemaComparer, buildConcurrentIndexPlan, buildMinorUnitsStatements, buildSqliteRebuildStatements, canonicalizeDefault, computeChecksum, createMigrationDefinition, extractCreatedIndexName, generateMigrationSequence, generateMigrationTimestamp, generateSchemaDiff, getPendingSchemaStatements, getSQLFromDiff, hasActionableChanges, isAdvisoryOnlyChange, isInfoOnlyChange, isManualOrAdvisoryChange, isSqliteRebuildPlaceholder, migrateSmrtSchemas, normalizeSQL, parsePostgresTimeoutMs, planPostgresStatements, planSqliteTableRebuilds, preflightMinorUnitsRescale, rescaleMoneyColumnsToMinorUnits, rewriteSqliteCreateTable, shortChecksum, sqliteRebuildPlaceholderSql, uniqueColumnIndexName, verifyChecksum };
11
+ export { BackfillTableUnavailableError, BackfillTracker, MigrationGenerator, MigrationTracker, MinorUnitsPreflightError, SQLITE_REBUILD_TABLE_PREFIX, SchemaComparer, buildConcurrentIndexPlan, buildIntegerWidthStatements, buildIntegerWidthTableStatements, buildMinorUnitsStatements, buildSqliteRebuildStatements, canonicalizeDefault, collectIntegerWidthTargets, computeChecksum, createMigrationDefinition, extractCreatedIndexName, generateMigrationSequence, generateMigrationTimestamp, generateSchemaDiff, getPendingSchemaStatements, getSQLFromDiff, hasActionableChanges, isAdvisoryOnlyChange, isInfoOnlyChange, isManualOrAdvisoryChange, isSqliteRebuildPlaceholder, migrateSmrtSchemas, normalizeSQL, parsePostgresTimeoutMs, planPostgresStatements, planSqliteTableRebuilds, preflightIntegerWidthWidening, preflightMinorUnitsRescale, rescaleMoneyColumnsToMinorUnits, rewriteSqliteCreateTable, shortChecksum, sqliteRebuildPlaceholderSql, uniqueColumnIndexName, verifyChecksum, widenIntegerColumnsToBigInt };
@@ -0,0 +1,99 @@
1
+ import { DatabaseInterface } from '@happyvertical/sql';
2
+ import { SchemaDefinition } from '../schema/types.js';
3
+ /** A table and its SMRT-owned integer columns. */
4
+ export interface IntegerWidthTarget {
5
+ table: string;
6
+ columns: string[];
7
+ }
8
+ /** The width state of one declared integer column. */
9
+ export type IntegerWidthColumnState = 'pending' | 'current' | 'missing' | 'unexpected';
10
+ /** Preflight finding for one declared integer column. */
11
+ export interface IntegerWidthColumnReport {
12
+ table: string;
13
+ column: string;
14
+ state: IntegerWidthColumnState;
15
+ /** Live `information_schema.columns.data_type`, when the column exists. */
16
+ declaredType: string | null;
17
+ }
18
+ /** Preflight findings for a single SMRT-owned table. */
19
+ export interface IntegerWidthTableReport {
20
+ table: string;
21
+ /** Number of rows when at least one column still needs widening. */
22
+ rowCount: number | null;
23
+ columns: IntegerWidthColumnReport[];
24
+ }
25
+ /** Read-only assessment of a deployment's legacy int4 columns. */
26
+ export interface IntegerWidthPreflightResult {
27
+ engine: string;
28
+ /** False for SQLite and adapters without a native 32-bit integer width. */
29
+ supported: boolean;
30
+ tables: IntegerWidthTableReport[];
31
+ pendingColumns: number;
32
+ currentColumns: number;
33
+ missingColumns: number;
34
+ unexpectedColumns: number;
35
+ /** Tables that need a maintenance-window table rewrite. */
36
+ pendingTables: number;
37
+ /** Human-readable output suitable for an operator change record. */
38
+ summary: string;
39
+ }
40
+ /** Shared options for the read-only preflight and mutating widening pass. */
41
+ export interface IntegerWidthOptions {
42
+ /** Adapter hint when its URL does not identify the engine. */
43
+ engineHint?: string;
44
+ }
45
+ /** Options that make the widening pass safely repeatable. */
46
+ export interface IntegerWidthWidenOptions extends IntegerWidthOptions {
47
+ /** Stable `_smrt_backfills` audit marker, unique to this migration owner. */
48
+ backfillName: string;
49
+ /** Package recorded alongside the marker. */
50
+ packageName?: string;
51
+ /** PostgreSQL lock timeout in milliseconds (defaults to 30 seconds). */
52
+ lockTimeout?: number;
53
+ /** PostgreSQL statement timeout in milliseconds (defaults to 60 seconds). */
54
+ statementTimeout?: number;
55
+ }
56
+ /** Result of {@link widenIntegerColumnsToBigInt}. */
57
+ export interface IntegerWidthWidenResult {
58
+ /** False when no legacy columns remain or the engine is unsupported. */
59
+ ran: boolean;
60
+ preflight: IntegerWidthPreflightResult;
61
+ widenedColumns: Array<{
62
+ table: string;
63
+ column: string;
64
+ }>;
65
+ statements: string[];
66
+ }
67
+ /**
68
+ * Collect every integer column owned by loaded application schemas and, when
69
+ * requested, by hand-written core system-table DDL.
70
+ */
71
+ export declare function collectIntegerWidthTargets(schemas: Record<string, SchemaDefinition>, options?: {
72
+ includeSystemTables?: boolean;
73
+ }): IntegerWidthTarget[];
74
+ /** Build the exact, lossless ALTER statement for one target column. */
75
+ export declare function buildIntegerWidthStatements(engine: string, table: string, column: string): string[];
76
+ /**
77
+ * Build lossless widening statements for every pending column in one table.
78
+ *
79
+ * PostgreSQL performs a table rewrite for an integer-width change, so all of
80
+ * one table's pending columns must share one ALTER TABLE and one lock window.
81
+ * DuckDB keeps its one-column form because its ALTER COLUMN grammar is not
82
+ * equivalently composable across supported adapter versions.
83
+ */
84
+ export declare function buildIntegerWidthTableStatements(engine: string, table: string, columns: string[]): string[];
85
+ /**
86
+ * Inspect explicit SMRT-owned integer targets without changing the database.
87
+ *
88
+ * PostgreSQL and DuckDB expose their live widths through
89
+ * `information_schema.columns`. SQLite is deliberately a no-op because its
90
+ * INTEGER storage class already holds signed 64-bit values.
91
+ */
92
+ export declare function preflightIntegerWidthWidening(db: DatabaseInterface, targets: IntegerWidthTarget[], options?: IntegerWidthOptions): Promise<IntegerWidthPreflightResult>;
93
+ /**
94
+ * Widen legacy int4 columns to BIGINT and record a marker after every ALTER
95
+ * succeeds. This is intentionally never called by normal schema migration or
96
+ * framework bootstrap: PostgreSQL rewrites each altered table.
97
+ */
98
+ export declare function widenIntegerColumnsToBigInt(db: DatabaseInterface, targets: IntegerWidthTarget[], options: IntegerWidthWidenOptions): Promise<IntegerWidthWidenResult>;
99
+ //# sourceMappingURL=integer-width.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"integer-width.d.ts","sourceRoot":"","sources":["../../src/migrations/integer-width.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,237 @@
1
+ import { toSafeInteger } from "../utils/safe-integer.js";
2
+ import { detectEngine } from "../schema/ddl/index.js";
3
+ import { getSystemTableShapes } from "../schema/system-table-shapes.js";
4
+ import { parsePostgresTimeoutMs } from "../postgres-timeouts.js";
5
+ import { BackfillTracker } from "./backfill-tracker.js";
6
+ //#region src/migrations/integer-width.ts
7
+ var DEFAULT_POSTGRES_LOCK_TIMEOUT_MS = 3e4;
8
+ var DEFAULT_POSTGRES_STATEMENT_TIMEOUT_MS = 6e4;
9
+ /**
10
+ * Collect every integer column owned by loaded application schemas and, when
11
+ * requested, by hand-written core system-table DDL.
12
+ */
13
+ function collectIntegerWidthTargets(schemas, options = {}) {
14
+ const byTable = /* @__PURE__ */ new Map();
15
+ const add = (table, column) => {
16
+ if (!byTable.has(table)) byTable.set(table, /* @__PURE__ */ new Set());
17
+ byTable.get(table)?.add(column);
18
+ };
19
+ for (const [key, schema] of Object.entries(schemas)) {
20
+ const table = schema?.tableName || key;
21
+ for (const [column, definition] of Object.entries(schema?.columns ?? {})) if (definition.type === "INTEGER") add(table, column);
22
+ }
23
+ if (options.includeSystemTables ?? true) {
24
+ for (const shape of getSystemTableShapes("postgres").values()) for (const column of shape.columns) if (isBigIntType(column.type)) add(shape.tableName, column.name);
25
+ }
26
+ return [...byTable].map(([table, columns]) => ({
27
+ table,
28
+ columns: [...columns].sort()
29
+ })).sort((left, right) => left.table.localeCompare(right.table));
30
+ }
31
+ /** Build the exact, lossless ALTER statement for one target column. */
32
+ function buildIntegerWidthStatements(engine, table, column) {
33
+ return buildIntegerWidthTableStatements(engine, table, [column]);
34
+ }
35
+ /**
36
+ * Build lossless widening statements for every pending column in one table.
37
+ *
38
+ * PostgreSQL performs a table rewrite for an integer-width change, so all of
39
+ * one table's pending columns must share one ALTER TABLE and one lock window.
40
+ * DuckDB keeps its one-column form because its ALTER COLUMN grammar is not
41
+ * equivalently composable across supported adapter versions.
42
+ */
43
+ function buildIntegerWidthTableStatements(engine, table, columns) {
44
+ const quotedTable = quoteIdentifier(table);
45
+ const quotedColumns = columns.map(quoteIdentifier);
46
+ if (quotedColumns.length === 0) return [];
47
+ if (engine !== "postgres" && engine !== "duckdb") return [];
48
+ if (engine === "postgres") return [`ALTER TABLE ${quotedTable} ${quotedColumns.map((column) => `ALTER COLUMN ${column} TYPE BIGINT`).join(", ")}`];
49
+ return quotedColumns.map((column) => `ALTER TABLE ${quotedTable} ALTER COLUMN ${column} TYPE BIGINT`);
50
+ }
51
+ /**
52
+ * Inspect explicit SMRT-owned integer targets without changing the database.
53
+ *
54
+ * PostgreSQL and DuckDB expose their live widths through
55
+ * `information_schema.columns`. SQLite is deliberately a no-op because its
56
+ * INTEGER storage class already holds signed 64-bit values.
57
+ */
58
+ async function preflightIntegerWidthWidening(db, targets, options = {}) {
59
+ const engine = resolveEngine(db, options.engineHint);
60
+ const normalizedTargets = normalizeTargets(targets);
61
+ if (engine !== "postgres" && engine !== "duckdb") return {
62
+ engine,
63
+ supported: false,
64
+ tables: [],
65
+ pendingColumns: 0,
66
+ currentColumns: 0,
67
+ missingColumns: 0,
68
+ unexpectedColumns: 0,
69
+ pendingTables: 0,
70
+ summary: `Integer-width widening preflight (engine=${engine}): not applicable; this engine does not expose a distinct native int4 storage type.`
71
+ };
72
+ const tables = [];
73
+ for (const target of normalizedTargets) {
74
+ const types = await readColumnTypes(db, engine, target.table);
75
+ const columns = target.columns.map((column) => {
76
+ const declaredType = types.get(column) ?? null;
77
+ return {
78
+ table: target.table,
79
+ column,
80
+ declaredType,
81
+ state: classifyIntegerWidth(declaredType)
82
+ };
83
+ });
84
+ const needsWidening = columns.some((column) => column.state === "pending");
85
+ tables.push({
86
+ table: target.table,
87
+ rowCount: needsWidening ? await countRows(db, target.table) : null,
88
+ columns
89
+ });
90
+ }
91
+ const allColumns = tables.flatMap((table) => table.columns);
92
+ const result = {
93
+ engine,
94
+ supported: true,
95
+ tables,
96
+ pendingColumns: allColumns.filter((column) => column.state === "pending").length,
97
+ currentColumns: allColumns.filter((column) => column.state === "current").length,
98
+ missingColumns: allColumns.filter((column) => column.state === "missing").length,
99
+ unexpectedColumns: allColumns.filter((column) => column.state === "unexpected").length,
100
+ pendingTables: tables.filter((table) => table.columns.some((column) => column.state === "pending")).length
101
+ };
102
+ return {
103
+ ...result,
104
+ summary: renderSummary(result)
105
+ };
106
+ }
107
+ /**
108
+ * Widen legacy int4 columns to BIGINT and record a marker after every ALTER
109
+ * succeeds. This is intentionally never called by normal schema migration or
110
+ * framework bootstrap: PostgreSQL rewrites each altered table.
111
+ */
112
+ async function widenIntegerColumnsToBigInt(db, targets, options) {
113
+ const preflight = await preflightIntegerWidthWidening(db, targets, options);
114
+ if (!preflight.supported) return {
115
+ ran: false,
116
+ preflight,
117
+ widenedColumns: [],
118
+ statements: []
119
+ };
120
+ if (preflight.unexpectedColumns > 0) throw new Error("Integer-width widening refused because declared integer columns have unexpected live types. Resolve ordinary schema drift first.\n" + preflight.summary);
121
+ if (preflight.pendingColumns === 0) return {
122
+ ran: false,
123
+ preflight,
124
+ widenedColumns: [],
125
+ statements: []
126
+ };
127
+ const statements = [];
128
+ const widenedColumns = [];
129
+ await new BackfillTracker({ db }).initialize();
130
+ const applyWidening = async (writeDb) => {
131
+ for (const table of preflight.tables) {
132
+ const pendingColumns = table.columns.filter((column) => column.state === "pending").map((column) => column.column);
133
+ if (pendingColumns.length === 0) continue;
134
+ for (const sql of buildIntegerWidthTableStatements(preflight.engine, table.table, pendingColumns)) {
135
+ await writeDb.query(sql);
136
+ statements.push(sql);
137
+ }
138
+ widenedColumns.push(...pendingColumns.map((column) => ({
139
+ table: table.table,
140
+ column
141
+ })));
142
+ }
143
+ await new BackfillTracker({ db: writeDb }).recordApplied(options.backfillName, {
144
+ description: "Widened legacy int4 SMRT columns to BIGINT after #2373 changed fresh-schema defaults.",
145
+ packageName: options.packageName
146
+ });
147
+ };
148
+ if (preflight.engine === "postgres") {
149
+ if (!db.transaction) throw new Error("Integer-width widening on PostgreSQL requires transaction support.");
150
+ const lockTimeout = parsePostgresTimeoutMs(options.lockTimeout, DEFAULT_POSTGRES_LOCK_TIMEOUT_MS);
151
+ const statementTimeout = parsePostgresTimeoutMs(options.statementTimeout, DEFAULT_POSTGRES_STATEMENT_TIMEOUT_MS);
152
+ await db.transaction(async (tx) => {
153
+ const txDb = {
154
+ ...tx,
155
+ transaction: async (callback) => callback(tx)
156
+ };
157
+ BackfillTracker.inheritInitialization(txDb, db);
158
+ await txDb.query(`SET LOCAL lock_timeout = '${formatPostgresTimeout(lockTimeout)}'`);
159
+ await txDb.query(`SET LOCAL statement_timeout = '${formatPostgresTimeout(statementTimeout)}'`);
160
+ await applyWidening(txDb);
161
+ });
162
+ } else await applyWidening(db);
163
+ return {
164
+ ran: true,
165
+ preflight,
166
+ widenedColumns,
167
+ statements
168
+ };
169
+ }
170
+ function resolveEngine(db, engineHint) {
171
+ const dbWithConfig = db;
172
+ return detectEngine(db.url || dbWithConfig.config?.url || "", engineHint);
173
+ }
174
+ function normalizeTargets(targets) {
175
+ const byTable = /* @__PURE__ */ new Map();
176
+ for (const target of targets) {
177
+ quoteIdentifier(target.table);
178
+ if (!byTable.has(target.table)) byTable.set(target.table, /* @__PURE__ */ new Set());
179
+ for (const column of target.columns) {
180
+ quoteIdentifier(column);
181
+ byTable.get(target.table)?.add(column);
182
+ }
183
+ }
184
+ return [...byTable].map(([table, columns]) => ({
185
+ table,
186
+ columns: [...columns].sort()
187
+ })).sort((left, right) => left.table.localeCompare(right.table));
188
+ }
189
+ function quoteIdentifier(name) {
190
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) throw new Error(`Integer-width widening: '${name}' is not a plain SQL identifier.`);
191
+ return `"${name}"`;
192
+ }
193
+ function formatPostgresTimeout(milliseconds) {
194
+ return `${milliseconds}ms`;
195
+ }
196
+ function isLegacyInt4Type(type) {
197
+ return /^(INTEGER|INT|INT4)$/i.test(type.trim());
198
+ }
199
+ function isBigIntType(type) {
200
+ return /^(BIGINT|INT8)$/i.test(type.trim());
201
+ }
202
+ function classifyIntegerWidth(type) {
203
+ if (type === null) return "missing";
204
+ if (isLegacyInt4Type(type)) return "pending";
205
+ if (isBigIntType(type)) return "current";
206
+ return "unexpected";
207
+ }
208
+ async function readColumnTypes(db, engine, table) {
209
+ const result = await db.query(engine === "postgres" ? `SELECT column_name, data_type
210
+ FROM information_schema.columns
211
+ WHERE table_schema = current_schema() AND table_name = ?` : `SELECT column_name, data_type
212
+ FROM information_schema.columns
213
+ WHERE table_name = ?`, table);
214
+ const types = /* @__PURE__ */ new Map();
215
+ for (const row of result.rows) if (typeof row.column_name === "string") types.set(row.column_name, String(row.data_type ?? ""));
216
+ return types;
217
+ }
218
+ async function countRows(db, table) {
219
+ return toSafeInteger((await db.query(`SELECT COUNT(*) AS row_count FROM ${quoteIdentifier(table)}`)).rows?.[0]?.row_count ?? 0, `Integer-width row count for ${table}`);
220
+ }
221
+ function renderSummary(result) {
222
+ const lines = [
223
+ `Integer-width widening preflight (engine=${result.engine})`,
224
+ ` tables: ${result.pendingTables} pending`,
225
+ ` columns: ${result.pendingColumns} pending, ${result.currentColumns} already BIGINT, ${result.missingColumns} missing, ${result.unexpectedColumns} unexpected`
226
+ ];
227
+ for (const table of result.tables) {
228
+ const pending = table.columns.filter((column) => column.state === "pending");
229
+ if (pending.length === 0) continue;
230
+ lines.push(` - ${table.table}: ${table.rowCount ?? 0} row(s); ${pending.map((column) => `${column.column} (${column.declaredType})`).join(", ")}`);
231
+ }
232
+ return lines.join("\n");
233
+ }
234
+ //#endregion
235
+ export { buildIntegerWidthStatements, buildIntegerWidthTableStatements, collectIntegerWidthTargets, preflightIntegerWidthWidening, widenIntegerColumnsToBigInt };
236
+
237
+ //# sourceMappingURL=integer-width.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"integer-width.js","names":[],"sources":["../../src/migrations/integer-width.ts"],"sourcesContent":["/**\n * Explicit one-time widening for deployments created before #2373.\n *\n * Fresh PostgreSQL and DuckDB schemas now materialize SMRT's abstract\n * `INTEGER` as `BIGINT`. The migration differ deliberately treats int4 and\n * int8 as equivalent, so existing deployments need this separate, operator\n * initiated maintenance-window migration rather than an automatic repair.\n */\n\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport { parsePostgresTimeoutMs } from '../postgres-timeouts.js';\nimport { detectEngine } from '../schema/ddl/index.js';\nimport { getSystemTableShapes } from '../schema/system-table-shapes.js';\nimport type { SchemaDefinition } from '../schema/types.js';\nimport { toSafeInteger } from '../utils/safe-integer.js';\nimport { BackfillTracker } from './backfill-tracker.js';\n\nconst DEFAULT_POSTGRES_LOCK_TIMEOUT_MS = 30_000;\nconst DEFAULT_POSTGRES_STATEMENT_TIMEOUT_MS = 60_000;\n\n/** A table and its SMRT-owned integer columns. */\nexport interface IntegerWidthTarget {\n table: string;\n columns: string[];\n}\n\n/** The width state of one declared integer column. */\nexport type IntegerWidthColumnState =\n | 'pending'\n | 'current'\n | 'missing'\n | 'unexpected';\n\n/** Preflight finding for one declared integer column. */\nexport interface IntegerWidthColumnReport {\n table: string;\n column: string;\n state: IntegerWidthColumnState;\n /** Live `information_schema.columns.data_type`, when the column exists. */\n declaredType: string | null;\n}\n\n/** Preflight findings for a single SMRT-owned table. */\nexport interface IntegerWidthTableReport {\n table: string;\n /** Number of rows when at least one column still needs widening. */\n rowCount: number | null;\n columns: IntegerWidthColumnReport[];\n}\n\n/** Read-only assessment of a deployment's legacy int4 columns. */\nexport interface IntegerWidthPreflightResult {\n engine: string;\n /** False for SQLite and adapters without a native 32-bit integer width. */\n supported: boolean;\n tables: IntegerWidthTableReport[];\n pendingColumns: number;\n currentColumns: number;\n missingColumns: number;\n unexpectedColumns: number;\n /** Tables that need a maintenance-window table rewrite. */\n pendingTables: number;\n /** Human-readable output suitable for an operator change record. */\n summary: string;\n}\n\n/** Shared options for the read-only preflight and mutating widening pass. */\nexport interface IntegerWidthOptions {\n /** Adapter hint when its URL does not identify the engine. */\n engineHint?: string;\n}\n\n/** Options that make the widening pass safely repeatable. */\nexport interface IntegerWidthWidenOptions extends IntegerWidthOptions {\n /** Stable `_smrt_backfills` audit marker, unique to this migration owner. */\n backfillName: string;\n /** Package recorded alongside the marker. */\n packageName?: string;\n /** PostgreSQL lock timeout in milliseconds (defaults to 30 seconds). */\n lockTimeout?: number;\n /** PostgreSQL statement timeout in milliseconds (defaults to 60 seconds). */\n statementTimeout?: number;\n}\n\n/** Result of {@link widenIntegerColumnsToBigInt}. */\nexport interface IntegerWidthWidenResult {\n /** False when no legacy columns remain or the engine is unsupported. */\n ran: boolean;\n preflight: IntegerWidthPreflightResult;\n widenedColumns: Array<{ table: string; column: string }>;\n statements: string[];\n}\n\n/**\n * Collect every integer column owned by loaded application schemas and, when\n * requested, by hand-written core system-table DDL.\n */\nexport function collectIntegerWidthTargets(\n schemas: Record<string, SchemaDefinition>,\n options: { includeSystemTables?: boolean } = {},\n): IntegerWidthTarget[] {\n const byTable = new Map<string, Set<string>>();\n const add = (table: string, column: string) => {\n if (!byTable.has(table)) byTable.set(table, new Set());\n byTable.get(table)?.add(column);\n };\n\n for (const [key, schema] of Object.entries(schemas)) {\n const table = schema?.tableName || key;\n for (const [column, definition] of Object.entries(schema?.columns ?? {})) {\n if (definition.type === 'INTEGER') add(table, column);\n }\n }\n\n if (options.includeSystemTables ?? true) {\n for (const shape of getSystemTableShapes('postgres').values()) {\n for (const column of shape.columns) {\n if (isBigIntType(column.type)) add(shape.tableName, column.name);\n }\n }\n }\n\n return [...byTable]\n .map(([table, columns]) => ({ table, columns: [...columns].sort() }))\n .sort((left, right) => left.table.localeCompare(right.table));\n}\n\n/** Build the exact, lossless ALTER statement for one target column. */\nexport function buildIntegerWidthStatements(\n engine: string,\n table: string,\n column: string,\n): string[] {\n return buildIntegerWidthTableStatements(engine, table, [column]);\n}\n\n/**\n * Build lossless widening statements for every pending column in one table.\n *\n * PostgreSQL performs a table rewrite for an integer-width change, so all of\n * one table's pending columns must share one ALTER TABLE and one lock window.\n * DuckDB keeps its one-column form because its ALTER COLUMN grammar is not\n * equivalently composable across supported adapter versions.\n */\nexport function buildIntegerWidthTableStatements(\n engine: string,\n table: string,\n columns: string[],\n): string[] {\n const quotedTable = quoteIdentifier(table);\n const quotedColumns = columns.map(quoteIdentifier);\n if (quotedColumns.length === 0) return [];\n if (engine !== 'postgres' && engine !== 'duckdb') return [];\n if (engine === 'postgres') {\n return [\n `ALTER TABLE ${quotedTable} ${quotedColumns\n .map((column) => `ALTER COLUMN ${column} TYPE BIGINT`)\n .join(', ')}`,\n ];\n }\n return quotedColumns.map(\n (column) => `ALTER TABLE ${quotedTable} ALTER COLUMN ${column} TYPE BIGINT`,\n );\n}\n\n/**\n * Inspect explicit SMRT-owned integer targets without changing the database.\n *\n * PostgreSQL and DuckDB expose their live widths through\n * `information_schema.columns`. SQLite is deliberately a no-op because its\n * INTEGER storage class already holds signed 64-bit values.\n */\nexport async function preflightIntegerWidthWidening(\n db: DatabaseInterface,\n targets: IntegerWidthTarget[],\n options: IntegerWidthOptions = {},\n): Promise<IntegerWidthPreflightResult> {\n const engine = resolveEngine(db, options.engineHint);\n const normalizedTargets = normalizeTargets(targets);\n if (engine !== 'postgres' && engine !== 'duckdb') {\n const result = {\n engine,\n supported: false,\n tables: [],\n pendingColumns: 0,\n currentColumns: 0,\n missingColumns: 0,\n unexpectedColumns: 0,\n pendingTables: 0,\n };\n return {\n ...result,\n summary:\n `Integer-width widening preflight (engine=${engine}): not applicable; ` +\n 'this engine does not expose a distinct native int4 storage type.',\n };\n }\n\n const tables: IntegerWidthTableReport[] = [];\n for (const target of normalizedTargets) {\n const types = await readColumnTypes(db, engine, target.table);\n const columns = target.columns.map((column) => {\n const declaredType = types.get(column) ?? null;\n return {\n table: target.table,\n column,\n declaredType,\n state: classifyIntegerWidth(declaredType),\n };\n });\n const needsWidening = columns.some((column) => column.state === 'pending');\n tables.push({\n table: target.table,\n rowCount: needsWidening ? await countRows(db, target.table) : null,\n columns,\n });\n }\n\n const allColumns = tables.flatMap((table) => table.columns);\n const result = {\n engine,\n supported: true,\n tables,\n pendingColumns: allColumns.filter((column) => column.state === 'pending')\n .length,\n currentColumns: allColumns.filter((column) => column.state === 'current')\n .length,\n missingColumns: allColumns.filter((column) => column.state === 'missing')\n .length,\n unexpectedColumns: allColumns.filter(\n (column) => column.state === 'unexpected',\n ).length,\n pendingTables: tables.filter((table) =>\n table.columns.some((column) => column.state === 'pending'),\n ).length,\n };\n return { ...result, summary: renderSummary(result) };\n}\n\n/**\n * Widen legacy int4 columns to BIGINT and record a marker after every ALTER\n * succeeds. This is intentionally never called by normal schema migration or\n * framework bootstrap: PostgreSQL rewrites each altered table.\n */\nexport async function widenIntegerColumnsToBigInt(\n db: DatabaseInterface,\n targets: IntegerWidthTarget[],\n options: IntegerWidthWidenOptions,\n): Promise<IntegerWidthWidenResult> {\n const preflight = await preflightIntegerWidthWidening(db, targets, options);\n if (!preflight.supported) {\n return { ran: false, preflight, widenedColumns: [], statements: [] };\n }\n if (preflight.unexpectedColumns > 0) {\n throw new Error(\n 'Integer-width widening refused because declared integer columns have unexpected live types. Resolve ordinary schema drift first.\\n' +\n preflight.summary,\n );\n }\n\n // Current declared widths are the idempotency guard. A fixed marker cannot\n // safely be the guard because a later successful discovery may reveal a\n // package/table absent from the earlier target set.\n if (preflight.pendingColumns === 0) {\n return { ran: false, preflight, widenedColumns: [], statements: [] };\n }\n\n const statements: string[] = [];\n const widenedColumns: Array<{ table: string; column: string }> = [];\n await new BackfillTracker({ db }).initialize();\n\n const applyWidening = async (writeDb: DatabaseInterface) => {\n for (const table of preflight.tables) {\n const pendingColumns = table.columns\n .filter((column) => column.state === 'pending')\n .map((column) => column.column);\n if (pendingColumns.length === 0) continue;\n for (const sql of buildIntegerWidthTableStatements(\n preflight.engine,\n table.table,\n pendingColumns,\n )) {\n await writeDb.query(sql);\n statements.push(sql);\n }\n widenedColumns.push(\n ...pendingColumns.map((column) => ({ table: table.table, column })),\n );\n }\n\n await new BackfillTracker({ db: writeDb }).recordApplied(\n options.backfillName,\n {\n description:\n 'Widened legacy int4 SMRT columns to BIGINT after #2373 changed fresh-schema defaults.',\n packageName: options.packageName,\n },\n );\n };\n\n if (preflight.engine === 'postgres') {\n if (!db.transaction) {\n throw new Error(\n 'Integer-width widening on PostgreSQL requires transaction support.',\n );\n }\n const lockTimeout = parsePostgresTimeoutMs(\n options.lockTimeout,\n DEFAULT_POSTGRES_LOCK_TIMEOUT_MS,\n );\n const statementTimeout = parsePostgresTimeoutMs(\n options.statementTimeout,\n DEFAULT_POSTGRES_STATEMENT_TIMEOUT_MS,\n );\n await db.transaction(async (tx) => {\n const txDb = {\n ...tx,\n transaction: async <T>(\n callback: (transactionDb: DatabaseInterface) => Promise<T>,\n ) => callback(tx as DatabaseInterface),\n } as DatabaseInterface;\n BackfillTracker.inheritInitialization(txDb, db);\n await txDb.query(\n `SET LOCAL lock_timeout = '${formatPostgresTimeout(lockTimeout)}'`,\n );\n await txDb.query(\n `SET LOCAL statement_timeout = '${formatPostgresTimeout(statementTimeout)}'`,\n );\n await applyWidening(txDb);\n });\n } else {\n await applyWidening(db);\n }\n\n return { ran: true, preflight, widenedColumns, statements };\n}\n\nfunction resolveEngine(db: DatabaseInterface, engineHint?: string): string {\n const dbWithConfig = db as DatabaseInterface & { config?: { url?: string } };\n return detectEngine(db.url || dbWithConfig.config?.url || '', engineHint);\n}\n\nfunction normalizeTargets(targets: IntegerWidthTarget[]): IntegerWidthTarget[] {\n const byTable = new Map<string, Set<string>>();\n for (const target of targets) {\n quoteIdentifier(target.table);\n if (!byTable.has(target.table)) byTable.set(target.table, new Set());\n for (const column of target.columns) {\n quoteIdentifier(column);\n byTable.get(target.table)?.add(column);\n }\n }\n return [...byTable]\n .map(([table, columns]) => ({ table, columns: [...columns].sort() }))\n .sort((left, right) => left.table.localeCompare(right.table));\n}\n\nfunction quoteIdentifier(name: string): string {\n if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {\n throw new Error(\n `Integer-width widening: '${name}' is not a plain SQL identifier.`,\n );\n }\n return `\"${name}\"`;\n}\n\nfunction formatPostgresTimeout(milliseconds: number): string {\n return `${milliseconds}ms`;\n}\n\nfunction isLegacyInt4Type(type: string): boolean {\n return /^(INTEGER|INT|INT4)$/i.test(type.trim());\n}\n\nfunction isBigIntType(type: string): boolean {\n return /^(BIGINT|INT8)$/i.test(type.trim());\n}\n\nfunction classifyIntegerWidth(type: string | null): IntegerWidthColumnState {\n if (type === null) return 'missing';\n if (isLegacyInt4Type(type)) return 'pending';\n if (isBigIntType(type)) return 'current';\n return 'unexpected';\n}\n\nasync function readColumnTypes(\n db: DatabaseInterface,\n engine: string,\n table: string,\n): Promise<Map<string, string>> {\n const result = await db.query(\n engine === 'postgres'\n ? `SELECT column_name, data_type\n FROM information_schema.columns\n WHERE table_schema = current_schema() AND table_name = ?`\n : `SELECT column_name, data_type\n FROM information_schema.columns\n WHERE table_name = ?`,\n table,\n );\n const types = new Map<string, string>();\n for (const row of result.rows as {\n column_name?: unknown;\n data_type?: unknown;\n }[]) {\n if (typeof row.column_name === 'string') {\n types.set(row.column_name, String(row.data_type ?? ''));\n }\n }\n return types;\n}\n\nasync function countRows(\n db: DatabaseInterface,\n table: string,\n): Promise<number> {\n const result = await db.query(\n `SELECT COUNT(*) AS row_count FROM ${quoteIdentifier(table)}`,\n );\n return toSafeInteger(\n result.rows?.[0]?.row_count ?? 0,\n `Integer-width row count for ${table}`,\n );\n}\n\nfunction renderSummary(\n result: Omit<IntegerWidthPreflightResult, 'summary'>,\n): string {\n const lines = [\n `Integer-width widening preflight (engine=${result.engine})`,\n ` tables: ${result.pendingTables} pending`,\n ` columns: ${result.pendingColumns} pending, ${result.currentColumns} already BIGINT, ${result.missingColumns} missing, ${result.unexpectedColumns} unexpected`,\n ];\n for (const table of result.tables) {\n const pending = table.columns.filter(\n (column) => column.state === 'pending',\n );\n if (pending.length === 0) continue;\n lines.push(\n ` - ${table.table}: ${table.rowCount ?? 0} row(s); ${pending\n .map((column) => `${column.column} (${column.declaredType})`)\n .join(', ')}`,\n );\n }\n return lines.join('\\n');\n}\n"],"mappings":"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"}
@@ -34,7 +34,7 @@ export interface MinorUnitsColumnReport {
34
34
  inspectedRows: number;
35
35
  /** Values whose scaled form is not a whole number — rounding loses money. */
36
36
  nonIntegral: MinorUnitsProblemRow[];
37
- /** Values whose scaled form falls outside PostgreSQL `int4`. */
37
+ /** Values whose scaled form exceeds JavaScript's safe integer range. */
38
38
  overflow: MinorUnitsProblemRow[];
39
39
  }
40
40
  /** Result of {@link preflightMinorUnitsRescale}. */
@@ -50,7 +50,7 @@ export interface MinorUnitsPreflightResult {
50
50
  missingColumns: number;
51
51
  /** Total rows that would be rounded. */
52
52
  nonIntegralRows: number;
53
- /** Total rows that would overflow `int4`. */
53
+ /** Total rows that would exceed JavaScript's safe integer range. */
54
54
  overflowRows: number;
55
55
  /** No row would lose information. */
56
56
  ok: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"minor-units.d.ts","sourceRoot":"","sources":["../../src/migrations/minor-units.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"minor-units.d.ts","sourceRoot":"","sources":["../../src/migrations/minor-units.ts"],"names":[],"mappings":"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"}
@@ -6,20 +6,22 @@ import { createLogger } from "@happyvertical/logger";
6
6
  * Money-column rescale: floating-point major units → integer minor units (#2401).
7
7
  *
8
8
  * SMRT's rule is that money is exact and is stored as **integer minor units**
9
- * (cents, satoshis) — `$19.99` is `1999`. Packages that predate the rule
9
+ * at an application-defined scale — `$19.99` is `1999` at a cents scale.
10
+ * Packages that predate the rule
10
11
  * declared their money fields `= 0.0`, which compiles to a floating-point
11
12
  * column holding *major* units. Flipping the declaration to `= 0` changes the
12
13
  * column type, and REAL→INTEGER is deliberately **not** one of the differ's
13
- * whitelisted upgrades: an automatic `ALTER … TYPE integer` would truncate
14
- * `19.99` to `19`, silently destroying 99 cents on every row.
14
+ * whitelisted upgrades: an automatic `ALTER … TYPE BIGINT` would truncate
15
+ * `19.99` to `19`, silently destroying minor units on every row.
15
16
  *
16
17
  * So the conversion is an explicit, opt-in migration with two halves:
17
18
  *
18
19
  * 1. {@link preflightMinorUnitsRescale} — read-only. Reports, per column,
19
20
  * whether it still needs converting, and lists the rows that would lose
20
21
  * information: values whose scaled form is not a whole number (a half-cent
21
- * that has to be rounded away) and values that overflow PostgreSQL's `int4`
22
- * once multiplied by the scale. Run it against a production snapshot and
22
+ * that has to be rounded away) and values that exceed JavaScript's safe
23
+ * integer range once multiplied by the scale. Run it against a production
24
+ * snapshot and
23
25
  * read the summary before converting anything.
24
26
  * 2. {@link rescaleMoneyColumnsToMinorUnits} — the conversion itself, guarded
25
27
  * by a {@link BackfillTracker} marker so re-running it can never multiply a
@@ -29,8 +31,8 @@ import { createLogger } from "@happyvertical/logger";
29
31
  *
30
32
  * | Engine | Column type | Values |
31
33
  * | --- | --- | --- |
32
- * | PostgreSQL | `ALTER COLUMN … TYPE integer USING round(col * scale)` | converted by the cast |
33
- * | DuckDB | `ALTER COLUMN … TYPE INTEGER USING round(col * scale)` | converted by the cast |
34
+ * | PostgreSQL | `ALTER COLUMN … TYPE BIGINT USING round(col * scale)` | converted by the cast |
35
+ * | DuckDB | `ALTER COLUMN … TYPE BIGINT USING round(col * scale)` | converted by the cast |
34
36
  * | SQLite | **not changed here** — SQLite cannot alter a column's declared type in place | rescaled with an `UPDATE`, values become exact integers |
35
37
  *
36
38
  * SQLite's declared type only sets an affinity, so a rescaled column already
@@ -40,9 +42,9 @@ import { createLogger } from "@happyvertical/logger";
40
42
  * pretending the column type moved.
41
43
  */
42
44
  var logger = createLogger({ level: "info" });
43
- /** PostgreSQL `int4` bounds the ceiling this conversion has to respect. */
44
- var INT4_MIN = -2147483648;
45
- var INT4_MAX = 2147483647;
45
+ /** Hydrated integers must remain exactly representable in JavaScript. */
46
+ var MIN_SAFE_INTEGER = -Number.MAX_SAFE_INTEGER;
47
+ var MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER;
46
48
  /**
47
49
  * How far a scaled value may sit from a whole number and still count as
48
50
  * integral.
@@ -78,7 +80,7 @@ function roundHalfAwayFromZero(value) {
78
80
  var MinorUnitsPreflightError = class extends Error {
79
81
  preflight;
80
82
  constructor(preflight) {
81
- super(`Money minor-units rescale refused: ${preflight.nonIntegralRows} row(s) would be rounded and ${preflight.overflowRows} row(s) would overflow int4. Fix the data or pass \`force: true\` to accept the rounding.
83
+ super(`Money minor-units rescale refused: ${preflight.nonIntegralRows} row(s) would be rounded and ${preflight.overflowRows} row(s) would overflow JavaScript's safe integer range. Fix the data or pass \`force: true\` to accept the rounding.
82
84
  ` + preflight.summary);
83
85
  this.preflight = preflight;
84
86
  this.name = "MinorUnitsPreflightError";
@@ -172,7 +174,7 @@ async function inspectColumn(db, table, column, scale, maxProblemRows, withId) {
172
174
  scaled
173
175
  });
174
176
  }
175
- if (rounded > INT4_MAX || rounded < INT4_MIN) {
177
+ if (rounded > MAX_SAFE_INTEGER || rounded < MIN_SAFE_INTEGER) {
176
178
  if (overflow.length < maxProblemRows) overflow.push({
177
179
  id,
178
180
  value,
@@ -191,7 +193,7 @@ function renderSummary(result) {
191
193
  `Money minor-units preflight (engine=${result.engine}, scale=${result.scale})`,
192
194
  ` columns: ${result.pendingColumns} pending, ${result.convertedColumns} already integer, ${result.missingColumns} missing`,
193
195
  ` rows that would be rounded: ${result.nonIntegralRows}`,
194
- ` rows that would overflow int4: ${result.overflowRows}`
196
+ ` rows that would exceed JavaScript's safe integer range: ${result.overflowRows}`
195
197
  ];
196
198
  for (const column of result.columns) {
197
199
  const label = `${column.table}.${column.column}`;
@@ -229,7 +231,7 @@ function buildMinorUnitsStatements(engine, table, column, scale) {
229
231
  const scaled = engine === "postgres" ? `round((${quotedColumn})::numeric * ${scale})` : `round(${quotedColumn} * ${scale})`;
230
232
  return [
231
233
  `ALTER TABLE ${quotedTable} ALTER COLUMN ${quotedColumn} DROP DEFAULT`,
232
- `ALTER TABLE ${quotedTable} ALTER COLUMN ${quotedColumn} TYPE INTEGER USING ${scaled}`,
234
+ `ALTER TABLE ${quotedTable} ALTER COLUMN ${quotedColumn} TYPE BIGINT USING ${scaled}`,
233
235
  `ALTER TABLE ${quotedTable} ALTER COLUMN ${quotedColumn} SET DEFAULT 0`
234
236
  ];
235
237
  }