drizzle-orm 1.0.0-beta.22-41a7d21 → 1.0.0-beta.22-bf0fa46
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/bun-sql/sqlite/migrator.cjs +1 -1
- package/bun-sql/sqlite/migrator.cjs.map +1 -1
- package/bun-sql/sqlite/migrator.js +1 -1
- package/bun-sql/sqlite/migrator.js.map +1 -1
- package/d1/migrator.cjs +1 -1
- package/d1/migrator.cjs.map +1 -1
- package/d1/migrator.js +1 -1
- package/d1/migrator.js.map +1 -1
- package/durable-sqlite/migrator.cjs +1 -1
- package/durable-sqlite/migrator.js +1 -1
- package/libsql/migrator.cjs +1 -1
- package/libsql/migrator.cjs.map +1 -1
- package/libsql/migrator.js +1 -1
- package/libsql/migrator.js.map +1 -1
- package/mysql-proxy/migrator.cjs +2 -2
- package/mysql-proxy/migrator.cjs.map +1 -1
- package/mysql-proxy/migrator.js +2 -2
- package/mysql-proxy/migrator.js.map +1 -1
- package/op-sqlite/migrator.cjs +1 -1
- package/op-sqlite/migrator.cjs.map +1 -1
- package/op-sqlite/migrator.js +1 -1
- package/op-sqlite/migrator.js.map +1 -1
- package/package.json +67 -13
- package/pg-proxy/migrator.cjs +2 -2
- package/pg-proxy/migrator.cjs.map +1 -1
- package/pg-proxy/migrator.js +2 -2
- package/pg-proxy/migrator.js.map +1 -1
- package/singlestore-proxy/migrator.cjs +2 -2
- package/singlestore-proxy/migrator.cjs.map +1 -1
- package/singlestore-proxy/migrator.js +2 -2
- package/singlestore-proxy/migrator.js.map +1 -1
- package/sqlite-cloud/migrator.cjs +1 -1
- package/sqlite-cloud/migrator.cjs.map +1 -1
- package/sqlite-cloud/migrator.js +1 -1
- package/sqlite-cloud/migrator.js.map +1 -1
- package/sqlite-core/dialect.cjs +7 -7
- package/sqlite-core/dialect.cjs.map +1 -1
- package/sqlite-core/dialect.d.cts +2 -1
- package/sqlite-core/dialect.d.ts +2 -1
- package/sqlite-core/dialect.js +7 -7
- package/sqlite-core/dialect.js.map +1 -1
- package/sqlite-proxy/migrator.cjs +40 -2
- package/sqlite-proxy/migrator.cjs.map +1 -1
- package/sqlite-proxy/migrator.js +39 -2
- package/sqlite-proxy/migrator.js.map +1 -1
- package/tursodatabase/migrator.cjs +1 -1
- package/tursodatabase/migrator.cjs.map +1 -1
- package/tursodatabase/migrator.js +1 -1
- package/tursodatabase/migrator.js.map +1 -1
- package/up-migrations/cockroach.cjs +6 -12
- package/up-migrations/cockroach.cjs.map +1 -1
- package/up-migrations/cockroach.d.cts +1 -5
- package/up-migrations/cockroach.d.ts +1 -5
- package/up-migrations/cockroach.js +6 -12
- package/up-migrations/cockroach.js.map +1 -1
- package/up-migrations/effect-pg.cjs +6 -12
- package/up-migrations/effect-pg.cjs.map +1 -1
- package/up-migrations/effect-pg.d.cts +1 -5
- package/up-migrations/effect-pg.d.ts +1 -5
- package/up-migrations/effect-pg.js +6 -12
- package/up-migrations/effect-pg.js.map +1 -1
- package/up-migrations/mssql.cjs +6 -12
- package/up-migrations/mssql.cjs.map +1 -1
- package/up-migrations/mssql.d.cts +1 -5
- package/up-migrations/mssql.d.ts +1 -5
- package/up-migrations/mssql.js +6 -12
- package/up-migrations/mssql.js.map +1 -1
- package/up-migrations/mysql-proxy.cjs +73 -0
- package/up-migrations/mysql-proxy.cjs.map +1 -0
- package/up-migrations/mysql-proxy.d.cts +16 -0
- package/up-migrations/mysql-proxy.d.ts +16 -0
- package/up-migrations/mysql-proxy.js +71 -0
- package/up-migrations/mysql-proxy.js.map +1 -0
- package/up-migrations/mysql.cjs +12 -28
- package/up-migrations/mysql.cjs.map +1 -1
- package/up-migrations/mysql.d.cts +1 -5
- package/up-migrations/mysql.d.ts +1 -5
- package/up-migrations/mysql.js +12 -28
- package/up-migrations/mysql.js.map +1 -1
- package/up-migrations/pg-proxy.cjs +79 -0
- package/up-migrations/pg-proxy.cjs.map +1 -0
- package/up-migrations/pg-proxy.d.cts +16 -0
- package/up-migrations/pg-proxy.d.ts +16 -0
- package/up-migrations/pg-proxy.js +77 -0
- package/up-migrations/pg-proxy.js.map +1 -0
- package/up-migrations/pg.cjs +9 -13
- package/up-migrations/pg.cjs.map +1 -1
- package/up-migrations/pg.d.cts +2 -6
- package/up-migrations/pg.d.ts +2 -6
- package/up-migrations/pg.js +9 -13
- package/up-migrations/pg.js.map +1 -1
- package/up-migrations/singlestore-proxy.cjs +73 -0
- package/up-migrations/singlestore-proxy.cjs.map +1 -0
- package/up-migrations/singlestore-proxy.d.cts +16 -0
- package/up-migrations/singlestore-proxy.d.ts +16 -0
- package/up-migrations/singlestore-proxy.js +71 -0
- package/up-migrations/singlestore-proxy.js.map +1 -0
- package/up-migrations/singlestore.cjs +9 -25
- package/up-migrations/singlestore.cjs.map +1 -1
- package/up-migrations/singlestore.d.cts +1 -5
- package/up-migrations/singlestore.d.ts +1 -5
- package/up-migrations/singlestore.js +9 -25
- package/up-migrations/singlestore.js.map +1 -1
- package/up-migrations/sqlite-proxy.cjs +80 -0
- package/up-migrations/sqlite-proxy.cjs.map +1 -0
- package/up-migrations/sqlite-proxy.d.cts +16 -0
- package/up-migrations/sqlite-proxy.d.ts +16 -0
- package/up-migrations/sqlite-proxy.js +78 -0
- package/up-migrations/sqlite-proxy.js.map +1 -0
- package/up-migrations/sqlite.cjs +17 -48
- package/up-migrations/sqlite.cjs.map +1 -1
- package/up-migrations/sqlite.d.cts +3 -10
- package/up-migrations/sqlite.d.ts +5 -12
- package/up-migrations/sqlite.js +17 -48
- package/up-migrations/sqlite.js.map +1 -1
- package/up-migrations/utils.cjs +47 -0
- package/up-migrations/utils.cjs.map +1 -0
- package/up-migrations/utils.d.cts +25 -0
- package/up-migrations/utils.d.ts +25 -0
- package/up-migrations/utils.js +44 -0
- package/up-migrations/utils.js.map +1 -0
- package/sqlite-proxy/migrator.internal.cjs +0 -61
- package/sqlite-proxy/migrator.internal.cjs.map +0 -1
- package/sqlite-proxy/migrator.internal.d.cts +0 -26
- package/sqlite-proxy/migrator.internal.d.ts +0 -26
- package/sqlite-proxy/migrator.internal.js +0 -59
- package/sqlite-proxy/migrator.internal.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pg-proxy.js","names":[],"sources":["../../src/up-migrations/pg-proxy.ts"],"sourcesContent":["import type { MigrationMeta } from '~/migrator.ts';\nimport type { PgRemoteDatabase } from '~/pg-proxy/index.ts';\nimport type { ProxyMigrator } from '~/pg-proxy/migrator.ts';\nimport { sql } from '~/sql/sql.ts';\nimport { GET_VERSION_FOR, MIGRATIONS_TABLE_VERSIONS, type UpgradeResult } from './utils.ts';\n\n/**\n * Map of upgrade functions. Each key is the version being upgraded FROM,\n * and the function upgrades the table to the next version.\n */\nconst upgradeFunctions: Record<\n\tnumber,\n\t(\n\t\tmigrationsSchema: string,\n\t\tmigrationsTable: string,\n\t\tdb: PgRemoteDatabase<Record<string, unknown>>,\n\t\tcallback: ProxyMigrator,\n\t\tlocalMigrations: MigrationMeta[],\n\t) => Promise<void>\n> = {\n\t/**\n\t * Upgrade from version 0 to version 1:\n\t * 1. Read all existing DB migrations\n\t * 2. Sort localMigrations ASC by millis and if the same - sort by name\n\t * 3. Match each DB row to a local migration\n\t * If multiple migrations share the same second, use hash matching as a tiebreaker\n\t * Not implemented for now -> If hash matching fails, fall back to serial id ordering\n\t * 5. Create extra column and backfill names for matched migrations\n\t */\n\t0: async (migrationsSchema, migrationsTable, db, callback, localMigrations) => {\n\t\tconst table = sql`${sql.identifier(migrationsSchema)}.${sql.identifier(migrationsTable)}`;\n\n\t\t// 1. Read all existing DB migrations\n\t\t// Sort them by ids asc (order how they were applied)\n\t\tconst dbRows = await db.session.execute<{ id: number; hash: string; created_at: string }[]>(\n\t\t\tsql`SELECT id, hash, created_at FROM ${table} ORDER BY id ASC`,\n\t\t);\n\n\t\t// 2. Sort ASC by millis and if the same - sort by name\n\t\tlocalMigrations.sort((a, b) =>\n\t\t\ta.folderMillis !== b.folderMillis ? a.folderMillis - b.folderMillis : (a.name ?? '').localeCompare(b.name ?? '')\n\t\t);\n\n\t\tconst byMillis = new Map<number, MigrationMeta[]>();\n\t\tconst byHash = new Map<string, MigrationMeta>();\n\t\tfor (const lm of localMigrations) {\n\t\t\tif (!byMillis.has(lm.folderMillis)) {\n\t\t\t\tbyMillis.set(lm.folderMillis, []);\n\t\t\t}\n\t\t\tbyMillis.get(lm.folderMillis)!.push(lm);\n\t\t\tbyHash.set(lm.hash, lm);\n\t\t}\n\n\t\t// \t3. Match each DB row to a local migration\n\t\t// \tPriority: millis -> hash\n\t\tconst toApply: { id: number; name: string }[] = [];\n\t\tlet unmatchedIds: number[] = [];\n\n\t\tfor (const dbRow of dbRows) {\n\t\t\tconst stringified = String(dbRow.created_at);\n\t\t\tconst millis = Number(stringified.substring(0, stringified.length - 3) + '000');\n\t\t\tconst candidates = byMillis.get(millis);\n\n\t\t\tlet matched: MigrationMeta | undefined;\n\n\t\t\tif (candidates && candidates.length === 1) {\n\t\t\t\tmatched = candidates[0];\n\t\t\t} else if (candidates && candidates.length > 1) {\n\t\t\t\tmatched = candidates.find((c) => c.hash === dbRow.hash);\n\t\t\t} else {\n\t\t\t\tmatched = byHash.get(dbRow.hash);\n\t\t\t}\n\n\t\t\tif (matched) toApply.push({ id: dbRow.id, name: matched.name });\n\t\t\telse unmatchedIds.push(dbRow.id);\n\t\t}\n\n\t\t// 4. Check for unmatched\n\t\t// Our assumption on this migration flow is that all DB entries should be matched to a local migration\n\t\t// (if same seconds - fallback to hash, if hash fails - corner case)\n\t\t// If there are unmatched entries, it means that the local environment is missing migrations that have been applied to the DB,\n\t\t// which can lead to inconsistencies and potential issues when running future migrations\n\t\tif (unmatchedIds.length > 0) {\n\t\t\tthrow Error(\n\t\t\t\t`While upgrading your database migrations table we found ${unmatchedIds.length} migrations (ids: ${\n\t\t\t\t\tunmatchedIds.join(', ')\n\t\t\t\t}) in the database that do not match any local migration. This means that some migrations were applied to the database but are missing from the local environment`,\n\t\t\t);\n\t\t}\n\n\t\t// 5. Create extra column and backfill names for matched migrations\n\t\tconst sqls: string[] = [\n\t\t\tdb.dialect.sqlToQuery(\n\t\t\t\tsql`ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS ${sql.identifier('name')} text`.inlineParams(),\n\t\t\t).sql,\n\t\t\tdb.dialect.sqlToQuery(\n\t\t\t\tsql`ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS ${\n\t\t\t\t\tsql.identifier('applied_at')\n\t\t\t\t} timestamp with time zone DEFAULT now()`.inlineParams(),\n\t\t\t).sql,\n\t\t];\n\t\tfor (const { id, name } of toApply) {\n\t\t\tsqls.push(\n\t\t\t\tdb.dialect.sqlToQuery(\n\t\t\t\t\tsql`UPDATE ${table} SET ${sql.identifier('name')} = ${name}, ${sql.identifier('applied_at')} = NULL WHERE ${\n\t\t\t\t\t\tsql.identifier('id')\n\t\t\t\t\t} = ${id}`.inlineParams(),\n\t\t\t\t).sql,\n\t\t\t);\n\t\t}\n\n\t\tawait callback(sqls);\n\t\treturn;\n\t},\n};\n\n/**\n * Detects the current version of the migrations table schema and upgrades it if needed.\n *\n * Version 0: Original schema (id, hash, created_at)\n * Version 1: Extended schema (id, hash, created_at, name, applied_at)\n */\nexport async function upgradeIfNeeded(\n\tmigrationsSchema: string,\n\tmigrationsTable: string,\n\tdb: PgRemoteDatabase<Record<string, unknown>>,\n\tcallback: ProxyMigrator,\n\tlocalMigrations: MigrationMeta[],\n): Promise<UpgradeResult> {\n\t// Check if the table exists at all\n\tconst result = await db.session.execute<{ '1': 1 }[]>(\n\t\tsql`SELECT 1 FROM information_schema.tables\n\t\t\tWHERE table_schema = ${migrationsSchema}\n\t\t\tAND table_name = ${migrationsTable}`,\n\t);\n\n\tif (result.length === 0) {\n\t\treturn { newDb: true };\n\t}\n\n\t// Table exists, check table shape\n\tconst rows = await db.session.execute<\n\t\t{ schema: string; table_name: string; column_name: string; type: string }[]\n\t>(\n\t\tsql`SELECT\n\t\t\tn.nspname AS \"schema\",\n\t\t\tc.relname AS \"table_name\",\n\t\t\ta.attname AS \"column_name\",\n\t\t\tpg_catalog.format_type(a.atttypid, a.atttypmod) AS \"type\"\n\t\tFROM\n\t\t\tpg_catalog.pg_attribute a\n\t\t\tJOIN pg_catalog.pg_class c ON c.oid = a.attrelid\n\t\t\tJOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n\t\tWHERE\n\t\t\ta.attnum > 0\n\t\t\tAND NOT a.attisdropped\n\t\t\tAND n.nspname = ${migrationsSchema}\n\t\t\tAND c.relname = ${migrationsTable}\n\t\tORDER BY a.attnum;`,\n\t);\n\n\tlet version = GET_VERSION_FOR.pg(rows.map((r) => r.column_name));\n\n\tfor (let v = version; v < MIGRATIONS_TABLE_VERSIONS.pg; v++) {\n\t\tconst upgradeFn = upgradeFunctions[v];\n\t\tif (!upgradeFn) {\n\t\t\tthrow new Error(`No upgrade path from migration table version ${v} to ${v + 1}`);\n\t\t}\n\t\tawait upgradeFn(migrationsSchema, migrationsTable, db, callback, localMigrations);\n\t}\n\n\treturn { newDb: false };\n}\n"],"mappings":";;;;;;;;AAUA,MAAM,mBASF,EAUH,GAAG,OAAO,kBAAkB,iBAAiB,IAAI,UAAU,oBAAoB;CAC9E,MAAM,QAAQ,GAAG,GAAG,IAAI,WAAW,iBAAiB,CAAC,GAAG,IAAI,WAAW,gBAAgB;CAIvF,MAAM,SAAS,MAAM,GAAG,QAAQ,QAC/B,GAAG,oCAAoC,MAAM,kBAC7C;AAGD,iBAAgB,MAAM,GAAG,MACxB,EAAE,iBAAiB,EAAE,eAAe,EAAE,eAAe,EAAE,gBAAgB,EAAE,QAAQ,IAAI,cAAc,EAAE,QAAQ,GAAG,CAChH;CAED,MAAM,2BAAW,IAAI,KAA8B;CACnD,MAAM,yBAAS,IAAI,KAA4B;AAC/C,MAAK,MAAM,MAAM,iBAAiB;AACjC,MAAI,CAAC,SAAS,IAAI,GAAG,aAAa,CACjC,UAAS,IAAI,GAAG,cAAc,EAAE,CAAC;AAElC,WAAS,IAAI,GAAG,aAAa,CAAE,KAAK,GAAG;AACvC,SAAO,IAAI,GAAG,MAAM,GAAG;;CAKxB,MAAM,UAA0C,EAAE;CAClD,IAAI,eAAyB,EAAE;AAE/B,MAAK,MAAM,SAAS,QAAQ;EAC3B,MAAM,cAAc,OAAO,MAAM,WAAW;EAC5C,MAAM,SAAS,OAAO,YAAY,UAAU,GAAG,YAAY,SAAS,EAAE,GAAG,MAAM;EAC/E,MAAM,aAAa,SAAS,IAAI,OAAO;EAEvC,IAAI;AAEJ,MAAI,cAAc,WAAW,WAAW,EACvC,WAAU,WAAW;WACX,cAAc,WAAW,SAAS,EAC5C,WAAU,WAAW,MAAM,MAAM,EAAE,SAAS,MAAM,KAAK;MAEvD,WAAU,OAAO,IAAI,MAAM,KAAK;AAGjC,MAAI,QAAS,SAAQ,KAAK;GAAE,IAAI,MAAM;GAAI,MAAM,QAAQ;GAAM,CAAC;MAC1D,cAAa,KAAK,MAAM,GAAG;;AAQjC,KAAI,aAAa,SAAS,EACzB,OAAM,MACL,2DAA2D,aAAa,OAAO,oBAC9E,aAAa,KAAK,KAAK,CACvB,kKACD;CAIF,MAAM,OAAiB,CACtB,GAAG,QAAQ,WACV,GAAG,eAAe,MAAM,4BAA4B,IAAI,WAAW,OAAO,CAAC,OAAO,cAAc,CAChG,CAAC,KACF,GAAG,QAAQ,WACV,GAAG,eAAe,MAAM,4BACvB,IAAI,WAAW,aAAa,CAC5B,yCAAyC,cAAc,CACxD,CAAC,IACF;AACD,MAAK,MAAM,EAAE,IAAI,UAAU,QAC1B,MAAK,KACJ,GAAG,QAAQ,WACV,GAAG,UAAU,MAAM,OAAO,IAAI,WAAW,OAAO,CAAC,KAAK,KAAK,IAAI,IAAI,WAAW,aAAa,CAAC,gBAC3F,IAAI,WAAW,KAAK,CACpB,KAAK,KAAK,cAAc,CACzB,CAAC,IACF;AAGF,OAAM,SAAS,KAAK;GAGrB;;;;;;;AAQD,eAAsB,gBACrB,kBACA,iBACA,IACA,UACA,iBACyB;AAQzB,MANe,MAAM,GAAG,QAAQ,QAC/B,GAAG;0BACqB,iBAAiB;sBACrB,kBACpB,EAEU,WAAW,EACrB,QAAO,EAAE,OAAO,MAAM;CAIvB,MAAM,OAAO,MAAM,GAAG,QAAQ,QAG7B,GAAG;;;;;;;;;;;;qBAYgB,iBAAiB;qBACjB,gBAAgB;sBAEnC;CAED,IAAI,UAAU,gBAAgB,GAAG,KAAK,KAAK,MAAM,EAAE,YAAY,CAAC;AAEhE,MAAK,IAAI,IAAI,SAAS,IAAI,0BAA0B,IAAI,KAAK;EAC5D,MAAM,YAAY,iBAAiB;AACnC,MAAI,CAAC,UACJ,OAAM,IAAI,MAAM,gDAAgD,EAAE,MAAM,IAAI,IAAI;AAEjF,QAAM,UAAU,kBAAkB,iBAAiB,IAAI,UAAU,gBAAgB;;AAGlF,QAAO,EAAE,OAAO,OAAO"}
|
package/up-migrations/pg.cjs
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
2
|
const require_runtime = require('../_virtual/_rolldown/runtime.cjs');
|
|
3
|
+
const require_up_migrations_utils = require('./utils.cjs');
|
|
4
|
+
let __utils_ts = require("../utils.cjs");
|
|
3
5
|
let __sql_sql_ts = require("../sql/sql.cjs");
|
|
4
6
|
|
|
5
7
|
//#region src/up-migrations/pg.ts
|
|
6
|
-
const CURRENT_MIGRATION_TABLE_VERSION = 1;
|
|
7
|
-
function getVersion(columns) {
|
|
8
|
-
if (columns.includes("name")) return 1;
|
|
9
|
-
return 0;
|
|
10
|
-
}
|
|
11
8
|
async function execute(session, sql) {
|
|
12
9
|
const result = await session.execute(sql);
|
|
13
10
|
if ("rows" in result) return result.rows;
|
|
@@ -53,7 +50,8 @@ const upgradeFunctions = { 0: async (migrationsSchema, migrationsTable, db, loca
|
|
|
53
50
|
else if (mode === "batch") {
|
|
54
51
|
const database = db;
|
|
55
52
|
await database.batch(sqls.map((s) => database.execute(s)));
|
|
56
|
-
} else for (const sql of sqls) await db.execute(sql);
|
|
53
|
+
} else if (mode === "execute") for (const sql of sqls) await db.session.execute(sql);
|
|
54
|
+
else (0, __utils_ts.assertUnreachable)(mode);
|
|
57
55
|
} };
|
|
58
56
|
/**
|
|
59
57
|
* Detects the current version of the migrations table schema and upgrades it if needed.
|
|
@@ -65,7 +63,7 @@ async function upgradeIfNeeded(migrationsSchema, migrationsTable, db, localMigra
|
|
|
65
63
|
if ((await execute(db.session, __sql_sql_ts.sql`SELECT 1 FROM information_schema.tables
|
|
66
64
|
WHERE table_schema = ${migrationsSchema}
|
|
67
65
|
AND table_name = ${migrationsTable}`)).length === 0) return { newDb: true };
|
|
68
|
-
|
|
66
|
+
const rows = await execute(db.session, __sql_sql_ts.sql`SELECT
|
|
69
67
|
n.nspname AS "schema",
|
|
70
68
|
c.relname AS "table_name",
|
|
71
69
|
a.attname AS "column_name",
|
|
@@ -79,16 +77,14 @@ async function upgradeIfNeeded(migrationsSchema, migrationsTable, db, localMigra
|
|
|
79
77
|
AND NOT a.attisdropped
|
|
80
78
|
AND n.nspname = ${migrationsSchema}
|
|
81
79
|
AND c.relname = ${migrationsTable}
|
|
82
|
-
ORDER BY a.attnum;`)
|
|
83
|
-
|
|
80
|
+
ORDER BY a.attnum;`);
|
|
81
|
+
let version = require_up_migrations_utils.GET_VERSION_FOR.pg(rows.map((r) => r.column_name));
|
|
82
|
+
for (let v = version; v < require_up_migrations_utils.MIGRATIONS_TABLE_VERSIONS.pg; v++) {
|
|
84
83
|
const upgradeFn = upgradeFunctions[v];
|
|
85
84
|
if (!upgradeFn) throw new Error(`No upgrade path from migration table version ${v} to ${v + 1}`);
|
|
86
85
|
await upgradeFn(migrationsSchema, migrationsTable, db, localMigrations, mode);
|
|
87
86
|
}
|
|
88
|
-
return {
|
|
89
|
-
prevVersion: version,
|
|
90
|
-
currentVersion: CURRENT_MIGRATION_TABLE_VERSION
|
|
91
|
-
};
|
|
87
|
+
return { newDb: false };
|
|
92
88
|
}
|
|
93
89
|
|
|
94
90
|
//#endregion
|
package/up-migrations/pg.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pg.cjs","names":["sql"],"sources":["../../src/up-migrations/pg.ts"],"sourcesContent":["import type { TablesRelationalConfig } from '~/_relations.ts';\nimport type { BatchItem } from '~/batch.ts';\nimport type { MigrationMeta } from '~/migrator.ts';\nimport type { NeonHttpDatabase } from '~/neon-http/driver.ts';\nimport type { NeonHttpSession } from '~/neon-http/session.ts';\nimport type { PgAsyncDatabase } from '~/pg-core/async/db.ts';\nimport type { PgAsyncSession, PgAsyncTransaction } from '~/pg-core/async/session.ts';\nimport type { PgQueryResultHKT } from '~/pg-core/session.ts';\nimport type { AnyRelations } from '~/relations.ts';\nimport { type SQL, sql } from '~/sql/sql.ts';\nimport type { XataHttpSession } from '~/xata-http/session.ts';\n\nconst CURRENT_MIGRATION_TABLE_VERSION = 1;\n\ninterface UpgradeResult {\n\tnewDb?: boolean;\n\tprevVersion?: number;\n\tcurrentVersion?: number;\n}\n\nfunction getVersion(columns: string[]) {\n\tif (columns.includes('name')) return 1;\n\treturn 0;\n}\n\n// postgres.js returns array of objects\n// pg-proxy returns arrays of objects\n// node-postgres returns { rows: array of objects }\nasync function execute<T extends any[]>(\n\tsession:\n\t\t| PgAsyncSession\n\t\t| NeonHttpSession<Record<string, unknown>, AnyRelations, TablesRelationalConfig>\n\t\t| XataHttpSession<Record<string, unknown>, AnyRelations, TablesRelationalConfig>,\n\tsql: SQL,\n): Promise<T> {\n\tconst result: { rows: T } | T = await session.execute(sql);\n\tif ('rows' in result) return result.rows;\n\treturn result;\n}\n\n/**\n * Map of upgrade functions. Each key is the version being upgraded FROM,\n * and the function upgrades the table to the next version.\n */\nconst upgradeFunctions: Record<\n\tnumber,\n\t(\n\t\tmigrationsSchema: string,\n\t\tmigrationsTable: string,\n\t\tdb: PgAsyncDatabase<PgQueryResultHKT, any, any, any>,\n\t\tlocalMigrations: MigrationMeta[],\n\t\tmode: 'transaction' | 'execute' | 'batch',\n\t) => Promise<void>\n> = {\n\t/**\n\t * Upgrade from version 0 to version 1:\n\t * 1. Read all existing DB migrations\n\t * 2. Sort localMigrations ASC by millis and if the same - sort by name\n\t * 3. Match each DB row to a local migration\n\t * If multiple migrations share the same second, use hash matching as a tiebreaker\n\t * Not implemented for now -> If hash matching fails, fall back to serial id ordering\n\t * 5. Create extra column and backfill names for matched migrations\n\t */\n\t0: async (migrationsSchema, migrationsTable, db, localMigrations, mode) => {\n\t\tconst table = sql`${sql.identifier(migrationsSchema)}.${sql.identifier(migrationsTable)}`;\n\n\t\t// 1. Read all existing DB migrations\n\t\t// Sort them by ids asc (order how they were applied)\n\t\tconst dbRows = await execute<{ id: number; hash: string; created_at: string }[]>(\n\t\t\tdb.session,\n\t\t\tsql`SELECT id, hash, created_at FROM ${table} ORDER BY id ASC`,\n\t\t);\n\n\t\t// 2. Sort ASC by millis and if the same - sort by name\n\t\tlocalMigrations.sort((a, b) =>\n\t\t\ta.folderMillis !== b.folderMillis ? a.folderMillis - b.folderMillis : (a.name ?? '').localeCompare(b.name ?? '')\n\t\t);\n\n\t\tconst byMillis = new Map<number, MigrationMeta[]>();\n\t\tconst byHash = new Map<string, MigrationMeta>();\n\t\tfor (const lm of localMigrations) {\n\t\t\tif (!byMillis.has(lm.folderMillis)) {\n\t\t\t\tbyMillis.set(lm.folderMillis, []);\n\t\t\t}\n\t\t\tbyMillis.get(lm.folderMillis)!.push(lm);\n\t\t\tbyHash.set(lm.hash, lm);\n\t\t}\n\n\t\t// \t3. Match each DB row to a local migration\n\t\t// \tPriority: millis -> hash\n\t\tconst toApply: { id: number; name: string }[] = [];\n\t\tlet unmatchedIds: number[] = [];\n\n\t\tfor (const dbRow of dbRows) {\n\t\t\tconst stringified = String(dbRow.created_at);\n\t\t\tconst millis = Number(stringified.substring(0, stringified.length - 3) + '000');\n\t\t\tconst candidates = byMillis.get(millis);\n\n\t\t\tlet matched: MigrationMeta | undefined;\n\n\t\t\tif (candidates && candidates.length === 1) {\n\t\t\t\tmatched = candidates[0];\n\t\t\t} else if (candidates && candidates.length > 1) {\n\t\t\t\tmatched = candidates.find((c) => c.hash === dbRow.hash);\n\t\t\t} else {\n\t\t\t\tmatched = byHash.get(dbRow.hash);\n\t\t\t}\n\n\t\t\tif (matched) toApply.push({ id: dbRow.id, name: matched.name });\n\t\t\telse unmatchedIds.push(dbRow.id);\n\t\t}\n\n\t\t// 4. Check for unmatched\n\t\t// Our assumption on this migration flow is that all DB entries should be matched to a local migration\n\t\t// (if same seconds - fallback to hash, if hash fails - corner case)\n\t\t// If there are unmatched entries, it means that the local environment is missing migrations that have been applied to the DB,\n\t\t// which can lead to inconsistencies and potential issues when running future migrations\n\t\tif (unmatchedIds.length > 0) {\n\t\t\tthrow Error(\n\t\t\t\t`While upgrading your database migrations table we found ${unmatchedIds.length} migrations (ids: ${\n\t\t\t\t\tunmatchedIds.join(', ')\n\t\t\t\t}) in the database that do not match any local migration. This means that some migrations were applied to the database but are missing from the local environment`,\n\t\t\t);\n\t\t}\n\n\t\t// 5. Create extra column and backfill names for matched migrations\n\t\tconst sqls: SQL[] = [\n\t\t\tsql`ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS ${sql.identifier('name')} text`,\n\t\t\tsql`ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS ${\n\t\t\t\tsql.identifier('applied_at')\n\t\t\t} timestamp with time zone DEFAULT now()`,\n\t\t];\n\t\tfor (const { id, name } of toApply) {\n\t\t\tsqls.push(\n\t\t\t\tsql`UPDATE ${table} SET ${sql.identifier('name')} = ${name}, ${sql.identifier('applied_at')} = NULL WHERE ${\n\t\t\t\t\tsql.identifier('id')\n\t\t\t\t} = ${id}`,\n\t\t\t);\n\t\t}\n\n\t\t// check if http\n\t\t// execute -> proxy, http drivers\n\t\t// transaction -> other\n\t\tif (mode === 'transaction') {\n\t\t\tawait db.transaction(async (tx: PgAsyncTransaction<any, any, any>) => {\n\t\t\t\tfor (const sql of sqls) {\n\t\t\t\t\tawait tx.execute(sql);\n\t\t\t\t}\n\t\t\t});\n\t\t} else if (mode === 'batch') {\n\t\t\tconst database = db as NeonHttpDatabase;\n\n\t\t\tawait database.batch(\n\t\t\t\tsqls.map((s) => database.execute(s)) as unknown as [BatchItem<'pg'>, ...BatchItem<'pg'>[]],\n\t\t\t);\n\t\t} else {\n\t\t\tfor (const sql of sqls) {\n\t\t\t\tawait db.execute(sql);\n\t\t\t}\n\t\t}\n\t},\n};\n\n/**\n * Detects the current version of the migrations table schema and upgrades it if needed.\n *\n * Version 0: Original schema (id, hash, created_at)\n * Version 1: Extended schema (id, hash, created_at, name, applied_at)\n */\nexport async function upgradeIfNeeded(\n\tmigrationsSchema: string,\n\tmigrationsTable: string,\n\tdb: PgAsyncDatabase<PgQueryResultHKT, any, any, any>,\n\tlocalMigrations: MigrationMeta[],\n\tmode: 'transaction' | 'execute' | 'batch' = 'transaction',\n): Promise<UpgradeResult> {\n\t// Check if the table exists at all\n\tconst result = await execute<{ '1': 1 }[]>(\n\t\tdb.session,\n\t\tsql`SELECT 1 FROM information_schema.tables\n\t\t\tWHERE table_schema = ${migrationsSchema}\n\t\t\tAND table_name = ${migrationsTable}`,\n\t);\n\n\tif (result.length === 0) {\n\t\treturn { newDb: true };\n\t}\n\n\t// Table exists, check table shape\n\tconst rows = await execute<\n\t\t{ schema: string; table_name: string; column_name: string; type: string }[]\n\t>(\n\t\tdb.session,\n\t\tsql`SELECT\n\t\t\tn.nspname AS \"schema\",\n\t\t\tc.relname AS \"table_name\",\n\t\t\ta.attname AS \"column_name\",\n\t\t\tpg_catalog.format_type(a.atttypid, a.atttypmod) AS \"type\"\n\t\tFROM\n\t\t\tpg_catalog.pg_attribute a\n\t\t\tJOIN pg_catalog.pg_class c ON c.oid = a.attrelid\n\t\t\tJOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n\t\tWHERE\n\t\t\ta.attnum > 0\n\t\t\tAND NOT a.attisdropped\n\t\t\tAND n.nspname = ${migrationsSchema}\n\t\t\tAND c.relname = ${migrationsTable}\n\t\tORDER BY a.attnum;`,\n\t);\n\n\tlet version = getVersion(rows.map((r) => r.column_name));\n\n\tfor (let v = version; v < CURRENT_MIGRATION_TABLE_VERSION; v++) {\n\t\tconst upgradeFn = upgradeFunctions[v];\n\t\tif (!upgradeFn) {\n\t\t\tthrow new Error(`No upgrade path from migration table version ${v} to ${v + 1}`);\n\t\t}\n\t\tawait upgradeFn(migrationsSchema, migrationsTable, db, localMigrations, mode);\n\t}\n\n\treturn { prevVersion: version, currentVersion: CURRENT_MIGRATION_TABLE_VERSION };\n}\n"],"mappings":";;;;;AAYA,MAAM,kCAAkC;AAQxC,SAAS,WAAW,SAAmB;AACtC,KAAI,QAAQ,SAAS,OAAO,CAAE,QAAO;AACrC,QAAO;;AAMR,eAAe,QACd,SAIA,KACa;CACb,MAAM,SAA0B,MAAM,QAAQ,QAAQ,IAAI;AAC1D,KAAI,UAAU,OAAQ,QAAO,OAAO;AACpC,QAAO;;;;;;AAOR,MAAM,mBASF,EAUH,GAAG,OAAO,kBAAkB,iBAAiB,IAAI,iBAAiB,SAAS;CAC1E,MAAM,QAAQ,gBAAG,GAAGA,iBAAI,WAAW,iBAAiB,CAAC,GAAGA,iBAAI,WAAW,gBAAgB;CAIvF,MAAM,SAAS,MAAM,QACpB,GAAG,SACH,gBAAG,oCAAoC,MAAM,kBAC7C;AAGD,iBAAgB,MAAM,GAAG,MACxB,EAAE,iBAAiB,EAAE,eAAe,EAAE,eAAe,EAAE,gBAAgB,EAAE,QAAQ,IAAI,cAAc,EAAE,QAAQ,GAAG,CAChH;CAED,MAAM,2BAAW,IAAI,KAA8B;CACnD,MAAM,yBAAS,IAAI,KAA4B;AAC/C,MAAK,MAAM,MAAM,iBAAiB;AACjC,MAAI,CAAC,SAAS,IAAI,GAAG,aAAa,CACjC,UAAS,IAAI,GAAG,cAAc,EAAE,CAAC;AAElC,WAAS,IAAI,GAAG,aAAa,CAAE,KAAK,GAAG;AACvC,SAAO,IAAI,GAAG,MAAM,GAAG;;CAKxB,MAAM,UAA0C,EAAE;CAClD,IAAI,eAAyB,EAAE;AAE/B,MAAK,MAAM,SAAS,QAAQ;EAC3B,MAAM,cAAc,OAAO,MAAM,WAAW;EAC5C,MAAM,SAAS,OAAO,YAAY,UAAU,GAAG,YAAY,SAAS,EAAE,GAAG,MAAM;EAC/E,MAAM,aAAa,SAAS,IAAI,OAAO;EAEvC,IAAI;AAEJ,MAAI,cAAc,WAAW,WAAW,EACvC,WAAU,WAAW;WACX,cAAc,WAAW,SAAS,EAC5C,WAAU,WAAW,MAAM,MAAM,EAAE,SAAS,MAAM,KAAK;MAEvD,WAAU,OAAO,IAAI,MAAM,KAAK;AAGjC,MAAI,QAAS,SAAQ,KAAK;GAAE,IAAI,MAAM;GAAI,MAAM,QAAQ;GAAM,CAAC;MAC1D,cAAa,KAAK,MAAM,GAAG;;AAQjC,KAAI,aAAa,SAAS,EACzB,OAAM,MACL,2DAA2D,aAAa,OAAO,oBAC9E,aAAa,KAAK,KAAK,CACvB,kKACD;CAIF,MAAM,OAAc,CACnB,gBAAG,eAAe,MAAM,4BAA4BA,iBAAI,WAAW,OAAO,CAAC,QAC3E,gBAAG,eAAe,MAAM,4BACvBA,iBAAI,WAAW,aAAa,CAC5B,yCACD;AACD,MAAK,MAAM,EAAE,IAAI,UAAU,QAC1B,MAAK,KACJ,gBAAG,UAAU,MAAM,OAAOA,iBAAI,WAAW,OAAO,CAAC,KAAK,KAAK,IAAIA,iBAAI,WAAW,aAAa,CAAC,gBAC3FA,iBAAI,WAAW,KAAK,CACpB,KAAK,KACN;AAMF,KAAI,SAAS,cACZ,OAAM,GAAG,YAAY,OAAO,OAA0C;AACrE,OAAK,MAAM,OAAO,KACjB,OAAM,GAAG,QAAQ,IAAI;GAErB;UACQ,SAAS,SAAS;EAC5B,MAAM,WAAW;AAEjB,QAAM,SAAS,MACd,KAAK,KAAK,MAAM,SAAS,QAAQ,EAAE,CAAC,CACpC;OAED,MAAK,MAAM,OAAO,KACjB,OAAM,GAAG,QAAQ,IAAI;GAIxB;;;;;;;AAQD,eAAsB,gBACrB,kBACA,iBACA,IACA,iBACA,OAA4C,eACnB;AASzB,MAPe,MAAM,QACpB,GAAG,SACH,gBAAG;0BACqB,iBAAiB;sBACrB,kBACpB,EAEU,WAAW,EACrB,QAAO,EAAE,OAAO,MAAM;CAyBvB,IAAI,UAAU,YArBD,MAAM,QAGlB,GAAG,SACH,gBAAG;;;;;;;;;;;;qBAYgB,iBAAiB;qBACjB,gBAAgB;sBAEnC,EAE6B,KAAK,MAAM,EAAE,YAAY,CAAC;AAExD,MAAK,IAAI,IAAI,SAAS,IAAI,iCAAiC,KAAK;EAC/D,MAAM,YAAY,iBAAiB;AACnC,MAAI,CAAC,UACJ,OAAM,IAAI,MAAM,gDAAgD,EAAE,MAAM,IAAI,IAAI;AAEjF,QAAM,UAAU,kBAAkB,iBAAiB,IAAI,iBAAiB,KAAK;;AAG9E,QAAO;EAAE,aAAa;EAAS,gBAAgB;EAAiC"}
|
|
1
|
+
{"version":3,"file":"pg.cjs","names":["sql","GET_VERSION_FOR","MIGRATIONS_TABLE_VERSIONS"],"sources":["../../src/up-migrations/pg.ts"],"sourcesContent":["import type { TablesRelationalConfig } from '~/_relations.ts';\nimport type { BatchItem } from '~/batch.ts';\nimport type { MigrationMeta } from '~/migrator.ts';\nimport type { NeonHttpDatabase } from '~/neon-http/driver.ts';\nimport type { NeonHttpSession } from '~/neon-http/session.ts';\nimport type { PgAsyncDatabase } from '~/pg-core/async/db.ts';\nimport type { PgAsyncSession, PgAsyncTransaction } from '~/pg-core/async/session.ts';\nimport type { PgQueryResultHKT } from '~/pg-core/session.ts';\nimport type { AnyRelations } from '~/relations.ts';\nimport { type SQL, sql } from '~/sql/sql.ts';\nimport { assertUnreachable } from '~/utils.ts';\nimport type { XataHttpSession } from '~/xata-http/session.ts';\nimport { GET_VERSION_FOR, MIGRATIONS_TABLE_VERSIONS, type UpgradeResult } from './utils.ts';\n\n// postgres.js returns array of objects\n// node-postgres returns { rows: array of objects }\nasync function execute<T extends any[]>(\n\tsession:\n\t\t| PgAsyncSession\n\t\t| NeonHttpSession<Record<string, unknown>, AnyRelations, TablesRelationalConfig>\n\t\t| XataHttpSession<Record<string, unknown>, AnyRelations, TablesRelationalConfig>,\n\tsql: SQL,\n): Promise<T> {\n\tconst result: { rows: T } | T = await session.execute(sql);\n\tif ('rows' in result) return result.rows;\n\treturn result;\n}\n\n/**\n * Map of upgrade functions. Each key is the version being upgraded FROM,\n * and the function upgrades the table to the next version.\n */\nconst upgradeFunctions: Record<\n\tnumber,\n\t(\n\t\tmigrationsSchema: string,\n\t\tmigrationsTable: string,\n\t\tdb: PgAsyncDatabase<PgQueryResultHKT, any, any, any>,\n\t\tlocalMigrations: MigrationMeta[],\n\t\tmode: 'transaction' | 'batch' | 'execute',\n\t) => Promise<void>\n> = {\n\t/**\n\t * Upgrade from version 0 to version 1:\n\t * 1. Read all existing DB migrations\n\t * 2. Sort localMigrations ASC by millis and if the same - sort by name\n\t * 3. Match each DB row to a local migration\n\t * If multiple migrations share the same second, use hash matching as a tiebreaker\n\t * Not implemented for now -> If hash matching fails, fall back to serial id ordering\n\t * 5. Create extra column and backfill names for matched migrations\n\t */\n\t0: async (migrationsSchema, migrationsTable, db, localMigrations, mode) => {\n\t\tconst table = sql`${sql.identifier(migrationsSchema)}.${sql.identifier(migrationsTable)}`;\n\n\t\t// 1. Read all existing DB migrations\n\t\t// Sort them by ids asc (order how they were applied)\n\t\tconst dbRows = await execute<{ id: number; hash: string; created_at: string }[]>(\n\t\t\tdb.session,\n\t\t\tsql`SELECT id, hash, created_at FROM ${table} ORDER BY id ASC`,\n\t\t);\n\n\t\t// 2. Sort ASC by millis and if the same - sort by name\n\t\tlocalMigrations.sort((a, b) =>\n\t\t\ta.folderMillis !== b.folderMillis ? a.folderMillis - b.folderMillis : (a.name ?? '').localeCompare(b.name ?? '')\n\t\t);\n\n\t\tconst byMillis = new Map<number, MigrationMeta[]>();\n\t\tconst byHash = new Map<string, MigrationMeta>();\n\t\tfor (const lm of localMigrations) {\n\t\t\tif (!byMillis.has(lm.folderMillis)) {\n\t\t\t\tbyMillis.set(lm.folderMillis, []);\n\t\t\t}\n\t\t\tbyMillis.get(lm.folderMillis)!.push(lm);\n\t\t\tbyHash.set(lm.hash, lm);\n\t\t}\n\n\t\t// \t3. Match each DB row to a local migration\n\t\t// \tPriority: millis -> hash\n\t\tconst toApply: { id: number; name: string }[] = [];\n\t\tlet unmatchedIds: number[] = [];\n\n\t\tfor (const dbRow of dbRows) {\n\t\t\tconst stringified = String(dbRow.created_at);\n\t\t\tconst millis = Number(stringified.substring(0, stringified.length - 3) + '000');\n\t\t\tconst candidates = byMillis.get(millis);\n\n\t\t\tlet matched: MigrationMeta | undefined;\n\n\t\t\tif (candidates && candidates.length === 1) {\n\t\t\t\tmatched = candidates[0];\n\t\t\t} else if (candidates && candidates.length > 1) {\n\t\t\t\tmatched = candidates.find((c) => c.hash === dbRow.hash);\n\t\t\t} else {\n\t\t\t\tmatched = byHash.get(dbRow.hash);\n\t\t\t}\n\n\t\t\tif (matched) toApply.push({ id: dbRow.id, name: matched.name });\n\t\t\telse unmatchedIds.push(dbRow.id);\n\t\t}\n\n\t\t// 4. Check for unmatched\n\t\t// Our assumption on this migration flow is that all DB entries should be matched to a local migration\n\t\t// (if same seconds - fallback to hash, if hash fails - corner case)\n\t\t// If there are unmatched entries, it means that the local environment is missing migrations that have been applied to the DB,\n\t\t// which can lead to inconsistencies and potential issues when running future migrations\n\t\tif (unmatchedIds.length > 0) {\n\t\t\tthrow Error(\n\t\t\t\t`While upgrading your database migrations table we found ${unmatchedIds.length} migrations (ids: ${\n\t\t\t\t\tunmatchedIds.join(', ')\n\t\t\t\t}) in the database that do not match any local migration. This means that some migrations were applied to the database but are missing from the local environment`,\n\t\t\t);\n\t\t}\n\n\t\t// 5. Create extra column and backfill names for matched migrations\n\t\tconst sqls: SQL[] = [\n\t\t\tsql`ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS ${sql.identifier('name')} text`,\n\t\t\tsql`ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS ${\n\t\t\t\tsql.identifier('applied_at')\n\t\t\t} timestamp with time zone DEFAULT now()`,\n\t\t];\n\t\tfor (const { id, name } of toApply) {\n\t\t\tsqls.push(\n\t\t\t\tsql`UPDATE ${table} SET ${sql.identifier('name')} = ${name}, ${sql.identifier('applied_at')} = NULL WHERE ${\n\t\t\t\t\tsql.identifier('id')\n\t\t\t\t} = ${id}`,\n\t\t\t);\n\t\t}\n\n\t\t// batch - neon http\n\t\t// execute - xata http\n\t\t// transaction -> other\n\t\tif (mode === 'transaction') {\n\t\t\tawait db.transaction(async (tx: PgAsyncTransaction<any, any, any>) => {\n\t\t\t\tfor (const sql of sqls) {\n\t\t\t\t\tawait tx.execute(sql);\n\t\t\t\t}\n\t\t\t});\n\t\t} else if (mode === 'batch') {\n\t\t\tconst database = db as NeonHttpDatabase;\n\n\t\t\tawait database.batch(\n\t\t\t\tsqls.map((s) => database.execute(s)) as unknown as [BatchItem<'pg'>, ...BatchItem<'pg'>[]],\n\t\t\t);\n\t\t} else if (mode === 'execute') {\n\t\t\tfor (const sql of sqls) {\n\t\t\t\tawait db.session.execute(sql);\n\t\t\t}\n\t\t} else {\n\t\t\tassertUnreachable(mode);\n\t\t}\n\t},\n};\n\n/**\n * Detects the current version of the migrations table schema and upgrades it if needed.\n *\n * Version 0: Original schema (id, hash, created_at)\n * Version 1: Extended schema (id, hash, created_at, name, applied_at)\n */\nexport async function upgradeIfNeeded(\n\tmigrationsSchema: string,\n\tmigrationsTable: string,\n\tdb: PgAsyncDatabase<PgQueryResultHKT, any, any, any>,\n\tlocalMigrations: MigrationMeta[],\n\t// batch - neon http\n\t// execute - xata http\n\t// transaction - all others\n\tmode: 'transaction' | 'batch' | 'execute' = 'transaction',\n): Promise<UpgradeResult> {\n\t// Check if the table exists at all\n\tconst result = await execute<{ '1': 1 }[]>(\n\t\tdb.session,\n\t\tsql`SELECT 1 FROM information_schema.tables\n\t\t\tWHERE table_schema = ${migrationsSchema}\n\t\t\tAND table_name = ${migrationsTable}`,\n\t);\n\n\tif (result.length === 0) {\n\t\treturn { newDb: true };\n\t}\n\n\t// Table exists, check table shape\n\tconst rows = await execute<\n\t\t{ schema: string; table_name: string; column_name: string; type: string }[]\n\t>(\n\t\tdb.session,\n\t\tsql`SELECT\n\t\t\tn.nspname AS \"schema\",\n\t\t\tc.relname AS \"table_name\",\n\t\t\ta.attname AS \"column_name\",\n\t\t\tpg_catalog.format_type(a.atttypid, a.atttypmod) AS \"type\"\n\t\tFROM\n\t\t\tpg_catalog.pg_attribute a\n\t\t\tJOIN pg_catalog.pg_class c ON c.oid = a.attrelid\n\t\t\tJOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n\t\tWHERE\n\t\t\ta.attnum > 0\n\t\t\tAND NOT a.attisdropped\n\t\t\tAND n.nspname = ${migrationsSchema}\n\t\t\tAND c.relname = ${migrationsTable}\n\t\tORDER BY a.attnum;`,\n\t);\n\n\tlet version = GET_VERSION_FOR.pg(rows.map((r) => r.column_name));\n\n\tfor (let v = version; v < MIGRATIONS_TABLE_VERSIONS.pg; v++) {\n\t\tconst upgradeFn = upgradeFunctions[v];\n\t\tif (!upgradeFn) {\n\t\t\tthrow new Error(`No upgrade path from migration table version ${v} to ${v + 1}`);\n\t\t}\n\t\tawait upgradeFn(migrationsSchema, migrationsTable, db, localMigrations, mode);\n\t}\n\n\treturn { newDb: false };\n}\n"],"mappings":"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"}
|
package/up-migrations/pg.d.cts
CHANGED
|
@@ -1,20 +1,16 @@
|
|
|
1
|
+
import { UpgradeResult } from "./utils.cjs";
|
|
1
2
|
import { MigrationMeta } from "../migrator.cjs";
|
|
2
3
|
import { PgQueryResultHKT } from "../pg-core/session.cjs";
|
|
3
4
|
import { PgAsyncDatabase } from "../pg-core/async/db.cjs";
|
|
4
5
|
|
|
5
6
|
//#region src/up-migrations/pg.d.ts
|
|
6
|
-
interface UpgradeResult {
|
|
7
|
-
newDb?: boolean;
|
|
8
|
-
prevVersion?: number;
|
|
9
|
-
currentVersion?: number;
|
|
10
|
-
}
|
|
11
7
|
/**
|
|
12
8
|
* Detects the current version of the migrations table schema and upgrades it if needed.
|
|
13
9
|
*
|
|
14
10
|
* Version 0: Original schema (id, hash, created_at)
|
|
15
11
|
* Version 1: Extended schema (id, hash, created_at, name, applied_at)
|
|
16
12
|
*/
|
|
17
|
-
declare function upgradeIfNeeded(migrationsSchema: string, migrationsTable: string, db: PgAsyncDatabase<PgQueryResultHKT, any, any, any>, localMigrations: MigrationMeta[], mode?: 'transaction' | '
|
|
13
|
+
declare function upgradeIfNeeded(migrationsSchema: string, migrationsTable: string, db: PgAsyncDatabase<PgQueryResultHKT, any, any, any>, localMigrations: MigrationMeta[], mode?: 'transaction' | 'batch' | 'execute'): Promise<UpgradeResult>;
|
|
18
14
|
//#endregion
|
|
19
15
|
export { upgradeIfNeeded };
|
|
20
16
|
//# sourceMappingURL=pg.d.cts.map
|
package/up-migrations/pg.d.ts
CHANGED
|
@@ -1,20 +1,16 @@
|
|
|
1
|
+
import { UpgradeResult } from "./utils.js";
|
|
1
2
|
import { PgAsyncDatabase } from "../pg-core/async/db.js";
|
|
2
3
|
import { MigrationMeta } from "../migrator.js";
|
|
3
4
|
import { PgQueryResultHKT } from "../pg-core/session.js";
|
|
4
5
|
|
|
5
6
|
//#region src/up-migrations/pg.d.ts
|
|
6
|
-
interface UpgradeResult {
|
|
7
|
-
newDb?: boolean;
|
|
8
|
-
prevVersion?: number;
|
|
9
|
-
currentVersion?: number;
|
|
10
|
-
}
|
|
11
7
|
/**
|
|
12
8
|
* Detects the current version of the migrations table schema and upgrades it if needed.
|
|
13
9
|
*
|
|
14
10
|
* Version 0: Original schema (id, hash, created_at)
|
|
15
11
|
* Version 1: Extended schema (id, hash, created_at, name, applied_at)
|
|
16
12
|
*/
|
|
17
|
-
declare function upgradeIfNeeded(migrationsSchema: string, migrationsTable: string, db: PgAsyncDatabase<PgQueryResultHKT, any, any, any>, localMigrations: MigrationMeta[], mode?: 'transaction' | '
|
|
13
|
+
declare function upgradeIfNeeded(migrationsSchema: string, migrationsTable: string, db: PgAsyncDatabase<PgQueryResultHKT, any, any, any>, localMigrations: MigrationMeta[], mode?: 'transaction' | 'batch' | 'execute'): Promise<UpgradeResult>;
|
|
18
14
|
//#endregion
|
|
19
15
|
export { upgradeIfNeeded };
|
|
20
16
|
//# sourceMappingURL=pg.d.ts.map
|
package/up-migrations/pg.js
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
|
+
import { GET_VERSION_FOR, MIGRATIONS_TABLE_VERSIONS } from "./utils.js";
|
|
2
|
+
import { assertUnreachable } from "../utils.js";
|
|
1
3
|
import { sql } from "../sql/sql.js";
|
|
2
4
|
|
|
3
5
|
//#region src/up-migrations/pg.ts
|
|
4
|
-
const CURRENT_MIGRATION_TABLE_VERSION = 1;
|
|
5
|
-
function getVersion(columns) {
|
|
6
|
-
if (columns.includes("name")) return 1;
|
|
7
|
-
return 0;
|
|
8
|
-
}
|
|
9
6
|
async function execute(session, sql) {
|
|
10
7
|
const result = await session.execute(sql);
|
|
11
8
|
if ("rows" in result) return result.rows;
|
|
@@ -51,7 +48,8 @@ const upgradeFunctions = { 0: async (migrationsSchema, migrationsTable, db, loca
|
|
|
51
48
|
else if (mode === "batch") {
|
|
52
49
|
const database = db;
|
|
53
50
|
await database.batch(sqls.map((s) => database.execute(s)));
|
|
54
|
-
} else for (const sql of sqls) await db.execute(sql);
|
|
51
|
+
} else if (mode === "execute") for (const sql of sqls) await db.session.execute(sql);
|
|
52
|
+
else assertUnreachable(mode);
|
|
55
53
|
} };
|
|
56
54
|
/**
|
|
57
55
|
* Detects the current version of the migrations table schema and upgrades it if needed.
|
|
@@ -63,7 +61,7 @@ async function upgradeIfNeeded(migrationsSchema, migrationsTable, db, localMigra
|
|
|
63
61
|
if ((await execute(db.session, sql`SELECT 1 FROM information_schema.tables
|
|
64
62
|
WHERE table_schema = ${migrationsSchema}
|
|
65
63
|
AND table_name = ${migrationsTable}`)).length === 0) return { newDb: true };
|
|
66
|
-
|
|
64
|
+
const rows = await execute(db.session, sql`SELECT
|
|
67
65
|
n.nspname AS "schema",
|
|
68
66
|
c.relname AS "table_name",
|
|
69
67
|
a.attname AS "column_name",
|
|
@@ -77,16 +75,14 @@ async function upgradeIfNeeded(migrationsSchema, migrationsTable, db, localMigra
|
|
|
77
75
|
AND NOT a.attisdropped
|
|
78
76
|
AND n.nspname = ${migrationsSchema}
|
|
79
77
|
AND c.relname = ${migrationsTable}
|
|
80
|
-
ORDER BY a.attnum;`)
|
|
81
|
-
|
|
78
|
+
ORDER BY a.attnum;`);
|
|
79
|
+
let version = GET_VERSION_FOR.pg(rows.map((r) => r.column_name));
|
|
80
|
+
for (let v = version; v < MIGRATIONS_TABLE_VERSIONS.pg; v++) {
|
|
82
81
|
const upgradeFn = upgradeFunctions[v];
|
|
83
82
|
if (!upgradeFn) throw new Error(`No upgrade path from migration table version ${v} to ${v + 1}`);
|
|
84
83
|
await upgradeFn(migrationsSchema, migrationsTable, db, localMigrations, mode);
|
|
85
84
|
}
|
|
86
|
-
return {
|
|
87
|
-
prevVersion: version,
|
|
88
|
-
currentVersion: CURRENT_MIGRATION_TABLE_VERSION
|
|
89
|
-
};
|
|
85
|
+
return { newDb: false };
|
|
90
86
|
}
|
|
91
87
|
|
|
92
88
|
//#endregion
|
package/up-migrations/pg.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pg.js","names":[],"sources":["../../src/up-migrations/pg.ts"],"sourcesContent":["import type { TablesRelationalConfig } from '~/_relations.ts';\nimport type { BatchItem } from '~/batch.ts';\nimport type { MigrationMeta } from '~/migrator.ts';\nimport type { NeonHttpDatabase } from '~/neon-http/driver.ts';\nimport type { NeonHttpSession } from '~/neon-http/session.ts';\nimport type { PgAsyncDatabase } from '~/pg-core/async/db.ts';\nimport type { PgAsyncSession, PgAsyncTransaction } from '~/pg-core/async/session.ts';\nimport type { PgQueryResultHKT } from '~/pg-core/session.ts';\nimport type { AnyRelations } from '~/relations.ts';\nimport { type SQL, sql } from '~/sql/sql.ts';\nimport type { XataHttpSession } from '~/xata-http/session.ts';\n\nconst CURRENT_MIGRATION_TABLE_VERSION = 1;\n\ninterface UpgradeResult {\n\tnewDb?: boolean;\n\tprevVersion?: number;\n\tcurrentVersion?: number;\n}\n\nfunction getVersion(columns: string[]) {\n\tif (columns.includes('name')) return 1;\n\treturn 0;\n}\n\n// postgres.js returns array of objects\n// pg-proxy returns arrays of objects\n// node-postgres returns { rows: array of objects }\nasync function execute<T extends any[]>(\n\tsession:\n\t\t| PgAsyncSession\n\t\t| NeonHttpSession<Record<string, unknown>, AnyRelations, TablesRelationalConfig>\n\t\t| XataHttpSession<Record<string, unknown>, AnyRelations, TablesRelationalConfig>,\n\tsql: SQL,\n): Promise<T> {\n\tconst result: { rows: T } | T = await session.execute(sql);\n\tif ('rows' in result) return result.rows;\n\treturn result;\n}\n\n/**\n * Map of upgrade functions. Each key is the version being upgraded FROM,\n * and the function upgrades the table to the next version.\n */\nconst upgradeFunctions: Record<\n\tnumber,\n\t(\n\t\tmigrationsSchema: string,\n\t\tmigrationsTable: string,\n\t\tdb: PgAsyncDatabase<PgQueryResultHKT, any, any, any>,\n\t\tlocalMigrations: MigrationMeta[],\n\t\tmode: 'transaction' | 'execute' | 'batch',\n\t) => Promise<void>\n> = {\n\t/**\n\t * Upgrade from version 0 to version 1:\n\t * 1. Read all existing DB migrations\n\t * 2. Sort localMigrations ASC by millis and if the same - sort by name\n\t * 3. Match each DB row to a local migration\n\t * If multiple migrations share the same second, use hash matching as a tiebreaker\n\t * Not implemented for now -> If hash matching fails, fall back to serial id ordering\n\t * 5. Create extra column and backfill names for matched migrations\n\t */\n\t0: async (migrationsSchema, migrationsTable, db, localMigrations, mode) => {\n\t\tconst table = sql`${sql.identifier(migrationsSchema)}.${sql.identifier(migrationsTable)}`;\n\n\t\t// 1. Read all existing DB migrations\n\t\t// Sort them by ids asc (order how they were applied)\n\t\tconst dbRows = await execute<{ id: number; hash: string; created_at: string }[]>(\n\t\t\tdb.session,\n\t\t\tsql`SELECT id, hash, created_at FROM ${table} ORDER BY id ASC`,\n\t\t);\n\n\t\t// 2. Sort ASC by millis and if the same - sort by name\n\t\tlocalMigrations.sort((a, b) =>\n\t\t\ta.folderMillis !== b.folderMillis ? a.folderMillis - b.folderMillis : (a.name ?? '').localeCompare(b.name ?? '')\n\t\t);\n\n\t\tconst byMillis = new Map<number, MigrationMeta[]>();\n\t\tconst byHash = new Map<string, MigrationMeta>();\n\t\tfor (const lm of localMigrations) {\n\t\t\tif (!byMillis.has(lm.folderMillis)) {\n\t\t\t\tbyMillis.set(lm.folderMillis, []);\n\t\t\t}\n\t\t\tbyMillis.get(lm.folderMillis)!.push(lm);\n\t\t\tbyHash.set(lm.hash, lm);\n\t\t}\n\n\t\t// \t3. Match each DB row to a local migration\n\t\t// \tPriority: millis -> hash\n\t\tconst toApply: { id: number; name: string }[] = [];\n\t\tlet unmatchedIds: number[] = [];\n\n\t\tfor (const dbRow of dbRows) {\n\t\t\tconst stringified = String(dbRow.created_at);\n\t\t\tconst millis = Number(stringified.substring(0, stringified.length - 3) + '000');\n\t\t\tconst candidates = byMillis.get(millis);\n\n\t\t\tlet matched: MigrationMeta | undefined;\n\n\t\t\tif (candidates && candidates.length === 1) {\n\t\t\t\tmatched = candidates[0];\n\t\t\t} else if (candidates && candidates.length > 1) {\n\t\t\t\tmatched = candidates.find((c) => c.hash === dbRow.hash);\n\t\t\t} else {\n\t\t\t\tmatched = byHash.get(dbRow.hash);\n\t\t\t}\n\n\t\t\tif (matched) toApply.push({ id: dbRow.id, name: matched.name });\n\t\t\telse unmatchedIds.push(dbRow.id);\n\t\t}\n\n\t\t// 4. Check for unmatched\n\t\t// Our assumption on this migration flow is that all DB entries should be matched to a local migration\n\t\t// (if same seconds - fallback to hash, if hash fails - corner case)\n\t\t// If there are unmatched entries, it means that the local environment is missing migrations that have been applied to the DB,\n\t\t// which can lead to inconsistencies and potential issues when running future migrations\n\t\tif (unmatchedIds.length > 0) {\n\t\t\tthrow Error(\n\t\t\t\t`While upgrading your database migrations table we found ${unmatchedIds.length} migrations (ids: ${\n\t\t\t\t\tunmatchedIds.join(', ')\n\t\t\t\t}) in the database that do not match any local migration. This means that some migrations were applied to the database but are missing from the local environment`,\n\t\t\t);\n\t\t}\n\n\t\t// 5. Create extra column and backfill names for matched migrations\n\t\tconst sqls: SQL[] = [\n\t\t\tsql`ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS ${sql.identifier('name')} text`,\n\t\t\tsql`ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS ${\n\t\t\t\tsql.identifier('applied_at')\n\t\t\t} timestamp with time zone DEFAULT now()`,\n\t\t];\n\t\tfor (const { id, name } of toApply) {\n\t\t\tsqls.push(\n\t\t\t\tsql`UPDATE ${table} SET ${sql.identifier('name')} = ${name}, ${sql.identifier('applied_at')} = NULL WHERE ${\n\t\t\t\t\tsql.identifier('id')\n\t\t\t\t} = ${id}`,\n\t\t\t);\n\t\t}\n\n\t\t// check if http\n\t\t// execute -> proxy, http drivers\n\t\t// transaction -> other\n\t\tif (mode === 'transaction') {\n\t\t\tawait db.transaction(async (tx: PgAsyncTransaction<any, any, any>) => {\n\t\t\t\tfor (const sql of sqls) {\n\t\t\t\t\tawait tx.execute(sql);\n\t\t\t\t}\n\t\t\t});\n\t\t} else if (mode === 'batch') {\n\t\t\tconst database = db as NeonHttpDatabase;\n\n\t\t\tawait database.batch(\n\t\t\t\tsqls.map((s) => database.execute(s)) as unknown as [BatchItem<'pg'>, ...BatchItem<'pg'>[]],\n\t\t\t);\n\t\t} else {\n\t\t\tfor (const sql of sqls) {\n\t\t\t\tawait db.execute(sql);\n\t\t\t}\n\t\t}\n\t},\n};\n\n/**\n * Detects the current version of the migrations table schema and upgrades it if needed.\n *\n * Version 0: Original schema (id, hash, created_at)\n * Version 1: Extended schema (id, hash, created_at, name, applied_at)\n */\nexport async function upgradeIfNeeded(\n\tmigrationsSchema: string,\n\tmigrationsTable: string,\n\tdb: PgAsyncDatabase<PgQueryResultHKT, any, any, any>,\n\tlocalMigrations: MigrationMeta[],\n\tmode: 'transaction' | 'execute' | 'batch' = 'transaction',\n): Promise<UpgradeResult> {\n\t// Check if the table exists at all\n\tconst result = await execute<{ '1': 1 }[]>(\n\t\tdb.session,\n\t\tsql`SELECT 1 FROM information_schema.tables\n\t\t\tWHERE table_schema = ${migrationsSchema}\n\t\t\tAND table_name = ${migrationsTable}`,\n\t);\n\n\tif (result.length === 0) {\n\t\treturn { newDb: true };\n\t}\n\n\t// Table exists, check table shape\n\tconst rows = await execute<\n\t\t{ schema: string; table_name: string; column_name: string; type: string }[]\n\t>(\n\t\tdb.session,\n\t\tsql`SELECT\n\t\t\tn.nspname AS \"schema\",\n\t\t\tc.relname AS \"table_name\",\n\t\t\ta.attname AS \"column_name\",\n\t\t\tpg_catalog.format_type(a.atttypid, a.atttypmod) AS \"type\"\n\t\tFROM\n\t\t\tpg_catalog.pg_attribute a\n\t\t\tJOIN pg_catalog.pg_class c ON c.oid = a.attrelid\n\t\t\tJOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n\t\tWHERE\n\t\t\ta.attnum > 0\n\t\t\tAND NOT a.attisdropped\n\t\t\tAND n.nspname = ${migrationsSchema}\n\t\t\tAND c.relname = ${migrationsTable}\n\t\tORDER BY a.attnum;`,\n\t);\n\n\tlet version = getVersion(rows.map((r) => r.column_name));\n\n\tfor (let v = version; v < CURRENT_MIGRATION_TABLE_VERSION; v++) {\n\t\tconst upgradeFn = upgradeFunctions[v];\n\t\tif (!upgradeFn) {\n\t\t\tthrow new Error(`No upgrade path from migration table version ${v} to ${v + 1}`);\n\t\t}\n\t\tawait upgradeFn(migrationsSchema, migrationsTable, db, localMigrations, mode);\n\t}\n\n\treturn { prevVersion: version, currentVersion: CURRENT_MIGRATION_TABLE_VERSION };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"pg.js","names":[],"sources":["../../src/up-migrations/pg.ts"],"sourcesContent":["import type { TablesRelationalConfig } from '~/_relations.ts';\nimport type { BatchItem } from '~/batch.ts';\nimport type { MigrationMeta } from '~/migrator.ts';\nimport type { NeonHttpDatabase } from '~/neon-http/driver.ts';\nimport type { NeonHttpSession } from '~/neon-http/session.ts';\nimport type { PgAsyncDatabase } from '~/pg-core/async/db.ts';\nimport type { PgAsyncSession, PgAsyncTransaction } from '~/pg-core/async/session.ts';\nimport type { PgQueryResultHKT } from '~/pg-core/session.ts';\nimport type { AnyRelations } from '~/relations.ts';\nimport { type SQL, sql } from '~/sql/sql.ts';\nimport { assertUnreachable } from '~/utils.ts';\nimport type { XataHttpSession } from '~/xata-http/session.ts';\nimport { GET_VERSION_FOR, MIGRATIONS_TABLE_VERSIONS, type UpgradeResult } from './utils.ts';\n\n// postgres.js returns array of objects\n// node-postgres returns { rows: array of objects }\nasync function execute<T extends any[]>(\n\tsession:\n\t\t| PgAsyncSession\n\t\t| NeonHttpSession<Record<string, unknown>, AnyRelations, TablesRelationalConfig>\n\t\t| XataHttpSession<Record<string, unknown>, AnyRelations, TablesRelationalConfig>,\n\tsql: SQL,\n): Promise<T> {\n\tconst result: { rows: T } | T = await session.execute(sql);\n\tif ('rows' in result) return result.rows;\n\treturn result;\n}\n\n/**\n * Map of upgrade functions. Each key is the version being upgraded FROM,\n * and the function upgrades the table to the next version.\n */\nconst upgradeFunctions: Record<\n\tnumber,\n\t(\n\t\tmigrationsSchema: string,\n\t\tmigrationsTable: string,\n\t\tdb: PgAsyncDatabase<PgQueryResultHKT, any, any, any>,\n\t\tlocalMigrations: MigrationMeta[],\n\t\tmode: 'transaction' | 'batch' | 'execute',\n\t) => Promise<void>\n> = {\n\t/**\n\t * Upgrade from version 0 to version 1:\n\t * 1. Read all existing DB migrations\n\t * 2. Sort localMigrations ASC by millis and if the same - sort by name\n\t * 3. Match each DB row to a local migration\n\t * If multiple migrations share the same second, use hash matching as a tiebreaker\n\t * Not implemented for now -> If hash matching fails, fall back to serial id ordering\n\t * 5. Create extra column and backfill names for matched migrations\n\t */\n\t0: async (migrationsSchema, migrationsTable, db, localMigrations, mode) => {\n\t\tconst table = sql`${sql.identifier(migrationsSchema)}.${sql.identifier(migrationsTable)}`;\n\n\t\t// 1. Read all existing DB migrations\n\t\t// Sort them by ids asc (order how they were applied)\n\t\tconst dbRows = await execute<{ id: number; hash: string; created_at: string }[]>(\n\t\t\tdb.session,\n\t\t\tsql`SELECT id, hash, created_at FROM ${table} ORDER BY id ASC`,\n\t\t);\n\n\t\t// 2. Sort ASC by millis and if the same - sort by name\n\t\tlocalMigrations.sort((a, b) =>\n\t\t\ta.folderMillis !== b.folderMillis ? a.folderMillis - b.folderMillis : (a.name ?? '').localeCompare(b.name ?? '')\n\t\t);\n\n\t\tconst byMillis = new Map<number, MigrationMeta[]>();\n\t\tconst byHash = new Map<string, MigrationMeta>();\n\t\tfor (const lm of localMigrations) {\n\t\t\tif (!byMillis.has(lm.folderMillis)) {\n\t\t\t\tbyMillis.set(lm.folderMillis, []);\n\t\t\t}\n\t\t\tbyMillis.get(lm.folderMillis)!.push(lm);\n\t\t\tbyHash.set(lm.hash, lm);\n\t\t}\n\n\t\t// \t3. Match each DB row to a local migration\n\t\t// \tPriority: millis -> hash\n\t\tconst toApply: { id: number; name: string }[] = [];\n\t\tlet unmatchedIds: number[] = [];\n\n\t\tfor (const dbRow of dbRows) {\n\t\t\tconst stringified = String(dbRow.created_at);\n\t\t\tconst millis = Number(stringified.substring(0, stringified.length - 3) + '000');\n\t\t\tconst candidates = byMillis.get(millis);\n\n\t\t\tlet matched: MigrationMeta | undefined;\n\n\t\t\tif (candidates && candidates.length === 1) {\n\t\t\t\tmatched = candidates[0];\n\t\t\t} else if (candidates && candidates.length > 1) {\n\t\t\t\tmatched = candidates.find((c) => c.hash === dbRow.hash);\n\t\t\t} else {\n\t\t\t\tmatched = byHash.get(dbRow.hash);\n\t\t\t}\n\n\t\t\tif (matched) toApply.push({ id: dbRow.id, name: matched.name });\n\t\t\telse unmatchedIds.push(dbRow.id);\n\t\t}\n\n\t\t// 4. Check for unmatched\n\t\t// Our assumption on this migration flow is that all DB entries should be matched to a local migration\n\t\t// (if same seconds - fallback to hash, if hash fails - corner case)\n\t\t// If there are unmatched entries, it means that the local environment is missing migrations that have been applied to the DB,\n\t\t// which can lead to inconsistencies and potential issues when running future migrations\n\t\tif (unmatchedIds.length > 0) {\n\t\t\tthrow Error(\n\t\t\t\t`While upgrading your database migrations table we found ${unmatchedIds.length} migrations (ids: ${\n\t\t\t\t\tunmatchedIds.join(', ')\n\t\t\t\t}) in the database that do not match any local migration. This means that some migrations were applied to the database but are missing from the local environment`,\n\t\t\t);\n\t\t}\n\n\t\t// 5. Create extra column and backfill names for matched migrations\n\t\tconst sqls: SQL[] = [\n\t\t\tsql`ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS ${sql.identifier('name')} text`,\n\t\t\tsql`ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS ${\n\t\t\t\tsql.identifier('applied_at')\n\t\t\t} timestamp with time zone DEFAULT now()`,\n\t\t];\n\t\tfor (const { id, name } of toApply) {\n\t\t\tsqls.push(\n\t\t\t\tsql`UPDATE ${table} SET ${sql.identifier('name')} = ${name}, ${sql.identifier('applied_at')} = NULL WHERE ${\n\t\t\t\t\tsql.identifier('id')\n\t\t\t\t} = ${id}`,\n\t\t\t);\n\t\t}\n\n\t\t// batch - neon http\n\t\t// execute - xata http\n\t\t// transaction -> other\n\t\tif (mode === 'transaction') {\n\t\t\tawait db.transaction(async (tx: PgAsyncTransaction<any, any, any>) => {\n\t\t\t\tfor (const sql of sqls) {\n\t\t\t\t\tawait tx.execute(sql);\n\t\t\t\t}\n\t\t\t});\n\t\t} else if (mode === 'batch') {\n\t\t\tconst database = db as NeonHttpDatabase;\n\n\t\t\tawait database.batch(\n\t\t\t\tsqls.map((s) => database.execute(s)) as unknown as [BatchItem<'pg'>, ...BatchItem<'pg'>[]],\n\t\t\t);\n\t\t} else if (mode === 'execute') {\n\t\t\tfor (const sql of sqls) {\n\t\t\t\tawait db.session.execute(sql);\n\t\t\t}\n\t\t} else {\n\t\t\tassertUnreachable(mode);\n\t\t}\n\t},\n};\n\n/**\n * Detects the current version of the migrations table schema and upgrades it if needed.\n *\n * Version 0: Original schema (id, hash, created_at)\n * Version 1: Extended schema (id, hash, created_at, name, applied_at)\n */\nexport async function upgradeIfNeeded(\n\tmigrationsSchema: string,\n\tmigrationsTable: string,\n\tdb: PgAsyncDatabase<PgQueryResultHKT, any, any, any>,\n\tlocalMigrations: MigrationMeta[],\n\t// batch - neon http\n\t// execute - xata http\n\t// transaction - all others\n\tmode: 'transaction' | 'batch' | 'execute' = 'transaction',\n): Promise<UpgradeResult> {\n\t// Check if the table exists at all\n\tconst result = await execute<{ '1': 1 }[]>(\n\t\tdb.session,\n\t\tsql`SELECT 1 FROM information_schema.tables\n\t\t\tWHERE table_schema = ${migrationsSchema}\n\t\t\tAND table_name = ${migrationsTable}`,\n\t);\n\n\tif (result.length === 0) {\n\t\treturn { newDb: true };\n\t}\n\n\t// Table exists, check table shape\n\tconst rows = await execute<\n\t\t{ schema: string; table_name: string; column_name: string; type: string }[]\n\t>(\n\t\tdb.session,\n\t\tsql`SELECT\n\t\t\tn.nspname AS \"schema\",\n\t\t\tc.relname AS \"table_name\",\n\t\t\ta.attname AS \"column_name\",\n\t\t\tpg_catalog.format_type(a.atttypid, a.atttypmod) AS \"type\"\n\t\tFROM\n\t\t\tpg_catalog.pg_attribute a\n\t\t\tJOIN pg_catalog.pg_class c ON c.oid = a.attrelid\n\t\t\tJOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n\t\tWHERE\n\t\t\ta.attnum > 0\n\t\t\tAND NOT a.attisdropped\n\t\t\tAND n.nspname = ${migrationsSchema}\n\t\t\tAND c.relname = ${migrationsTable}\n\t\tORDER BY a.attnum;`,\n\t);\n\n\tlet version = GET_VERSION_FOR.pg(rows.map((r) => r.column_name));\n\n\tfor (let v = version; v < MIGRATIONS_TABLE_VERSIONS.pg; v++) {\n\t\tconst upgradeFn = upgradeFunctions[v];\n\t\tif (!upgradeFn) {\n\t\t\tthrow new Error(`No upgrade path from migration table version ${v} to ${v + 1}`);\n\t\t}\n\t\tawait upgradeFn(migrationsSchema, migrationsTable, db, localMigrations, mode);\n\t}\n\n\treturn { newDb: false };\n}\n"],"mappings":";;;;;AAgBA,eAAe,QACd,SAIA,KACa;CACb,MAAM,SAA0B,MAAM,QAAQ,QAAQ,IAAI;AAC1D,KAAI,UAAU,OAAQ,QAAO,OAAO;AACpC,QAAO;;;;;;AAOR,MAAM,mBASF,EAUH,GAAG,OAAO,kBAAkB,iBAAiB,IAAI,iBAAiB,SAAS;CAC1E,MAAM,QAAQ,GAAG,GAAG,IAAI,WAAW,iBAAiB,CAAC,GAAG,IAAI,WAAW,gBAAgB;CAIvF,MAAM,SAAS,MAAM,QACpB,GAAG,SACH,GAAG,oCAAoC,MAAM,kBAC7C;AAGD,iBAAgB,MAAM,GAAG,MACxB,EAAE,iBAAiB,EAAE,eAAe,EAAE,eAAe,EAAE,gBAAgB,EAAE,QAAQ,IAAI,cAAc,EAAE,QAAQ,GAAG,CAChH;CAED,MAAM,2BAAW,IAAI,KAA8B;CACnD,MAAM,yBAAS,IAAI,KAA4B;AAC/C,MAAK,MAAM,MAAM,iBAAiB;AACjC,MAAI,CAAC,SAAS,IAAI,GAAG,aAAa,CACjC,UAAS,IAAI,GAAG,cAAc,EAAE,CAAC;AAElC,WAAS,IAAI,GAAG,aAAa,CAAE,KAAK,GAAG;AACvC,SAAO,IAAI,GAAG,MAAM,GAAG;;CAKxB,MAAM,UAA0C,EAAE;CAClD,IAAI,eAAyB,EAAE;AAE/B,MAAK,MAAM,SAAS,QAAQ;EAC3B,MAAM,cAAc,OAAO,MAAM,WAAW;EAC5C,MAAM,SAAS,OAAO,YAAY,UAAU,GAAG,YAAY,SAAS,EAAE,GAAG,MAAM;EAC/E,MAAM,aAAa,SAAS,IAAI,OAAO;EAEvC,IAAI;AAEJ,MAAI,cAAc,WAAW,WAAW,EACvC,WAAU,WAAW;WACX,cAAc,WAAW,SAAS,EAC5C,WAAU,WAAW,MAAM,MAAM,EAAE,SAAS,MAAM,KAAK;MAEvD,WAAU,OAAO,IAAI,MAAM,KAAK;AAGjC,MAAI,QAAS,SAAQ,KAAK;GAAE,IAAI,MAAM;GAAI,MAAM,QAAQ;GAAM,CAAC;MAC1D,cAAa,KAAK,MAAM,GAAG;;AAQjC,KAAI,aAAa,SAAS,EACzB,OAAM,MACL,2DAA2D,aAAa,OAAO,oBAC9E,aAAa,KAAK,KAAK,CACvB,kKACD;CAIF,MAAM,OAAc,CACnB,GAAG,eAAe,MAAM,4BAA4B,IAAI,WAAW,OAAO,CAAC,QAC3E,GAAG,eAAe,MAAM,4BACvB,IAAI,WAAW,aAAa,CAC5B,yCACD;AACD,MAAK,MAAM,EAAE,IAAI,UAAU,QAC1B,MAAK,KACJ,GAAG,UAAU,MAAM,OAAO,IAAI,WAAW,OAAO,CAAC,KAAK,KAAK,IAAI,IAAI,WAAW,aAAa,CAAC,gBAC3F,IAAI,WAAW,KAAK,CACpB,KAAK,KACN;AAMF,KAAI,SAAS,cACZ,OAAM,GAAG,YAAY,OAAO,OAA0C;AACrE,OAAK,MAAM,OAAO,KACjB,OAAM,GAAG,QAAQ,IAAI;GAErB;UACQ,SAAS,SAAS;EAC5B,MAAM,WAAW;AAEjB,QAAM,SAAS,MACd,KAAK,KAAK,MAAM,SAAS,QAAQ,EAAE,CAAC,CACpC;YACS,SAAS,UACnB,MAAK,MAAM,OAAO,KACjB,OAAM,GAAG,QAAQ,QAAQ,IAAI;KAG9B,mBAAkB,KAAK;GAGzB;;;;;;;AAQD,eAAsB,gBACrB,kBACA,iBACA,IACA,iBAIA,OAA4C,eACnB;AASzB,MAPe,MAAM,QACpB,GAAG,SACH,GAAG;0BACqB,iBAAiB;sBACrB,kBACpB,EAEU,WAAW,EACrB,QAAO,EAAE,OAAO,MAAM;CAIvB,MAAM,OAAO,MAAM,QAGlB,GAAG,SACH,GAAG;;;;;;;;;;;;qBAYgB,iBAAiB;qBACjB,gBAAgB;sBAEnC;CAED,IAAI,UAAU,gBAAgB,GAAG,KAAK,KAAK,MAAM,EAAE,YAAY,CAAC;AAEhE,MAAK,IAAI,IAAI,SAAS,IAAI,0BAA0B,IAAI,KAAK;EAC5D,MAAM,YAAY,iBAAiB;AACnC,MAAI,CAAC,UACJ,OAAM,IAAI,MAAM,gDAAgD,EAAE,MAAM,IAAI,IAAI;AAEjF,QAAM,UAAU,kBAAkB,iBAAiB,IAAI,iBAAiB,KAAK;;AAG9E,QAAO,EAAE,OAAO,OAAO"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
const require_runtime = require('../_virtual/_rolldown/runtime.cjs');
|
|
3
|
+
const require_up_migrations_utils = require('./utils.cjs');
|
|
4
|
+
let __sql_sql_ts = require("../sql/sql.cjs");
|
|
5
|
+
|
|
6
|
+
//#region src/up-migrations/singlestore-proxy.ts
|
|
7
|
+
/**
|
|
8
|
+
* Map of upgrade functions. Each key is the version being upgraded FROM,
|
|
9
|
+
* and the function upgrades the table to the next version.
|
|
10
|
+
*/
|
|
11
|
+
const upgradeFunctions = { 0: async (migrationsTable, db, callback, localMigrations) => {
|
|
12
|
+
const table = __sql_sql_ts.sql`${__sql_sql_ts.sql.identifier(migrationsTable)}`;
|
|
13
|
+
const dbRows = (await db.session.all(__sql_sql_ts.sql`SELECT id, hash, created_at FROM ${table} ORDER BY id ASC`)).map((it) => ({
|
|
14
|
+
id: it[0],
|
|
15
|
+
hash: it[1],
|
|
16
|
+
created_at: it[2]
|
|
17
|
+
}));
|
|
18
|
+
localMigrations.sort((a, b) => a.folderMillis !== b.folderMillis ? a.folderMillis - b.folderMillis : (a.name ?? "").localeCompare(b.name ?? ""));
|
|
19
|
+
const byMillis = /* @__PURE__ */ new Map();
|
|
20
|
+
const byHash = /* @__PURE__ */ new Map();
|
|
21
|
+
for (const lm of localMigrations) {
|
|
22
|
+
if (!byMillis.has(lm.folderMillis)) byMillis.set(lm.folderMillis, []);
|
|
23
|
+
byMillis.get(lm.folderMillis).push(lm);
|
|
24
|
+
byHash.set(lm.hash, lm);
|
|
25
|
+
}
|
|
26
|
+
const toApply = [];
|
|
27
|
+
let unmatchedIds = [];
|
|
28
|
+
for (const dbRow of dbRows) {
|
|
29
|
+
const stringified = String(dbRow.created_at);
|
|
30
|
+
const millis = Number(stringified.substring(0, stringified.length - 3) + "000");
|
|
31
|
+
const candidates = byMillis.get(millis);
|
|
32
|
+
let matched;
|
|
33
|
+
if (candidates && candidates.length === 1) matched = candidates[0];
|
|
34
|
+
else if (candidates && candidates.length > 1) matched = candidates.find((c) => c.hash === dbRow.hash);
|
|
35
|
+
else matched = byHash.get(dbRow.hash);
|
|
36
|
+
if (matched) toApply.push({
|
|
37
|
+
id: dbRow.id,
|
|
38
|
+
name: matched.name
|
|
39
|
+
});
|
|
40
|
+
else unmatchedIds.push(dbRow.id);
|
|
41
|
+
}
|
|
42
|
+
if (unmatchedIds.length > 0) throw Error(`While upgrading your database migrations table we found ${unmatchedIds.length} migrations (ids: ${unmatchedIds.join(", ")}) in the database that do not match any local migration. This means that some migrations were applied to the database but are missing from the local environment`);
|
|
43
|
+
const statements = [db.dialect.sqlToQuery(__sql_sql_ts.sql`ALTER TABLE ${table} ADD ${__sql_sql_ts.sql.identifier("name")} text`.inlineParams()).sql, db.dialect.sqlToQuery(__sql_sql_ts.sql`ALTER TABLE ${table} ADD ${__sql_sql_ts.sql.identifier("applied_at")} TIMESTAMP DEFAULT CURRENT_TIMESTAMP`.inlineParams()).sql];
|
|
44
|
+
for (const backfillEntry of toApply) statements.push(db.dialect.sqlToQuery(__sql_sql_ts.sql`UPDATE ${table} SET ${__sql_sql_ts.sql.identifier("name")} = ${backfillEntry.name}, ${__sql_sql_ts.sql.identifier("applied_at")} = NULL WHERE ${__sql_sql_ts.sql.identifier("id")} = ${backfillEntry.id}`.inlineParams()).sql);
|
|
45
|
+
await callback(statements);
|
|
46
|
+
} };
|
|
47
|
+
/**
|
|
48
|
+
* Detects the current version of the migrations table schema and upgrades it if needed.
|
|
49
|
+
*
|
|
50
|
+
* Version 0: Original schema (id, hash, created_at)
|
|
51
|
+
* Version 1: Extended schema (id, hash, created_at, name, applied_at)
|
|
52
|
+
*/
|
|
53
|
+
async function upgradeIfNeeded(migrationsTable, db, callback, localMigrations) {
|
|
54
|
+
if ((await db.session.all(__sql_sql_ts.sql`SELECT 1 FROM information_schema.tables
|
|
55
|
+
WHERE table_name = ${migrationsTable}
|
|
56
|
+
AND table_schema = DATABASE()`)).length === 0) return { newDb: true };
|
|
57
|
+
const rows = await db.session.all(__sql_sql_ts.sql`SELECT column_name as \`column_name\`
|
|
58
|
+
FROM information_schema.columns
|
|
59
|
+
WHERE table_name = ${migrationsTable}
|
|
60
|
+
AND table_schema = DATABASE()
|
|
61
|
+
ORDER BY ordinal_position`);
|
|
62
|
+
const version = require_up_migrations_utils.GET_VERSION_FOR.singlestore(rows.map((r) => r[0]));
|
|
63
|
+
for (let v = version; v < require_up_migrations_utils.MIGRATIONS_TABLE_VERSIONS.singlestore; v++) {
|
|
64
|
+
const upgradeFn = upgradeFunctions[v];
|
|
65
|
+
if (!upgradeFn) throw new Error(`No upgrade path from migration table version ${v} to ${v + 1}`);
|
|
66
|
+
await upgradeFn(migrationsTable, db, callback, localMigrations);
|
|
67
|
+
}
|
|
68
|
+
return { newDb: false };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
//#endregion
|
|
72
|
+
exports.upgradeIfNeeded = upgradeIfNeeded;
|
|
73
|
+
//# sourceMappingURL=singlestore-proxy.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"singlestore-proxy.cjs","names":["sql","GET_VERSION_FOR","MIGRATIONS_TABLE_VERSIONS"],"sources":["../../src/up-migrations/singlestore-proxy.ts"],"sourcesContent":["import type { MigrationMeta } from '~/migrator.ts';\nimport type { SingleStoreRemoteDatabase } from '~/singlestore-proxy/index.ts';\nimport type { ProxyMigrator } from '~/singlestore-proxy/migrator.ts';\nimport { sql } from '~/sql/sql.ts';\nimport { GET_VERSION_FOR, MIGRATIONS_TABLE_VERSIONS, type UpgradeResult } from './utils.ts';\n\n/**\n * Map of upgrade functions. Each key is the version being upgraded FROM,\n * and the function upgrades the table to the next version.\n */\nconst upgradeFunctions: Record<\n\tnumber,\n\t(\n\t\tmigrationsTable: string,\n\t\tdb: SingleStoreRemoteDatabase<Record<string, unknown>>,\n\t\tcallback: ProxyMigrator,\n\t\tlocalMigrations: MigrationMeta[],\n\t) => Promise<void>\n> = {\n\t/**\n\t * Upgrade from version 0 to version 1:\n\t * 1. Read all existing DB migrations\n\t * 2. Sort localMigrations ASC by millis and if the same - sort by name\n\t * 3. Match each DB row to a local migration\n\t * If multiple migrations share the same second, use hash matching as a tiebreaker\n\t * Not implemented for now -> If hash matching fails, fall back to serial id ordering\n\t * 5. Create extra column and backfill names for matched migrations\n\t */\n\t0: async (migrationsTable, db, callback, localMigrations) => {\n\t\tconst table = sql`${sql.identifier(migrationsTable)}`;\n\n\t\t// 1. Read all existing DB migrations\n\t\t// Sort them by ids asc (order how they were applied)\n\t\tconst dbRows = (await db.session.all<[number, string, string]>(\n\t\t\tsql`SELECT id, hash, created_at FROM ${table} ORDER BY id ASC`,\n\t\t)).map((it) => ({\n\t\t\tid: it[0],\n\t\t\thash: it[1],\n\t\t\tcreated_at: it[2],\n\t\t}));\n\n\t\t// 2. Sort ASC by millis and if the same - sort by name\n\t\tlocalMigrations.sort((a, b) =>\n\t\t\ta.folderMillis !== b.folderMillis ? a.folderMillis - b.folderMillis : (a.name ?? '').localeCompare(b.name ?? '')\n\t\t);\n\n\t\tconst byMillis = new Map<number, MigrationMeta[]>();\n\t\tconst byHash = new Map<string, MigrationMeta>();\n\t\tfor (const lm of localMigrations) {\n\t\t\tif (!byMillis.has(lm.folderMillis)) {\n\t\t\t\tbyMillis.set(lm.folderMillis, []);\n\t\t\t}\n\t\t\tbyMillis.get(lm.folderMillis)!.push(lm);\n\t\t\tbyHash.set(lm.hash, lm);\n\t\t}\n\n\t\t// \t3. Match each DB row to a local migration\n\t\t// \tPriority: millis -> hash\n\t\tconst toApply: { id: number; name: string }[] = [];\n\t\tlet unmatchedIds: number[] = [];\n\n\t\tfor (const dbRow of dbRows) {\n\t\t\tconst stringified = String(dbRow.created_at);\n\t\t\tconst millis = Number(stringified.substring(0, stringified.length - 3) + '000');\n\t\t\tconst candidates = byMillis.get(millis);\n\n\t\t\tlet matched: MigrationMeta | undefined;\n\n\t\t\tif (candidates && candidates.length === 1) {\n\t\t\t\tmatched = candidates[0];\n\t\t\t} else if (candidates && candidates.length > 1) {\n\t\t\t\tmatched = candidates.find((c) => c.hash === dbRow.hash);\n\t\t\t} else {\n\t\t\t\tmatched = byHash.get(dbRow.hash);\n\t\t\t}\n\n\t\t\tif (matched) toApply.push({ id: dbRow.id, name: matched.name });\n\t\t\telse unmatchedIds.push(dbRow.id);\n\t\t}\n\n\t\t// 4. Check for unmatched\n\t\t// Our assumption on this migration flow is that all DB entries should be matched to a local migration\n\t\t// (if same seconds - fallback to hash, if hash fails - corner case)\n\t\t// If there are unmatched entries, it means that the local environment is missing migrations that have been applied to the DB,\n\t\t// which can lead to inconsistencies and potential issues when running future migrations\n\t\tif (unmatchedIds.length > 0) {\n\t\t\tthrow Error(\n\t\t\t\t`While upgrading your database migrations table we found ${unmatchedIds.length} migrations (ids: ${\n\t\t\t\t\tunmatchedIds.join(', ')\n\t\t\t\t}) in the database that do not match any local migration. This means that some migrations were applied to the database but are missing from the local environment`,\n\t\t\t);\n\t\t}\n\n\t\t// 5. Create extra column and backfill names for matched migrations\n\t\tconst statements: string[] = [\n\t\t\tdb.dialect.sqlToQuery(sql`ALTER TABLE ${table} ADD ${sql.identifier('name')} text`.inlineParams()).sql,\n\t\t\tdb.dialect.sqlToQuery(\n\t\t\t\tsql`ALTER TABLE ${table} ADD ${sql.identifier('applied_at')} TIMESTAMP DEFAULT CURRENT_TIMESTAMP`\n\t\t\t\t\t.inlineParams(),\n\t\t\t).sql,\n\t\t];\n\n\t\tfor (const backfillEntry of toApply) {\n\t\t\tstatements.push(\n\t\t\t\tdb.dialect.sqlToQuery(\n\t\t\t\t\tsql`UPDATE ${table} SET ${sql.identifier('name')} = ${backfillEntry.name}, ${\n\t\t\t\t\t\tsql.identifier('applied_at')\n\t\t\t\t\t} = NULL WHERE ${sql.identifier('id')} = ${backfillEntry.id}`.inlineParams(),\n\t\t\t\t).sql,\n\t\t\t);\n\t\t}\n\n\t\tawait callback(statements);\n\t\treturn;\n\t},\n};\n\n/**\n * Detects the current version of the migrations table schema and upgrades it if needed.\n *\n * Version 0: Original schema (id, hash, created_at)\n * Version 1: Extended schema (id, hash, created_at, name, applied_at)\n */\nexport async function upgradeIfNeeded(\n\tmigrationsTable: string,\n\tdb: SingleStoreRemoteDatabase<Record<string, unknown>>,\n\tcallback: ProxyMigrator,\n\tlocalMigrations: MigrationMeta[],\n): Promise<UpgradeResult> {\n\t// Check if the table exists at all\n\tconst result = await db.session.all<[1]>(\n\t\tsql`SELECT 1 FROM information_schema.tables \n\t\t\tWHERE table_name = ${migrationsTable}\n\t\t\tAND table_schema = DATABASE()`,\n\t);\n\n\tif (result.length === 0) {\n\t\treturn { newDb: true };\n\t}\n\n\t// Table exists, check table shape\n\tconst rows = await db.session.all<[string]>(\n\t\tsql`SELECT column_name as \\`column_name\\`\n\t\tFROM information_schema.columns\n\t\tWHERE table_name = ${migrationsTable}\n\t\tAND table_schema = DATABASE()\n\t\tORDER BY ordinal_position`,\n\t);\n\n\tconst version = GET_VERSION_FOR.singlestore(rows.map((r) => r[0]));\n\n\tfor (let v = version; v < MIGRATIONS_TABLE_VERSIONS.singlestore; v++) {\n\t\tconst upgradeFn = upgradeFunctions[v];\n\t\tif (!upgradeFn) {\n\t\t\tthrow new Error(`No upgrade path from migration table version ${v} to ${v + 1}`);\n\t\t}\n\t\tawait upgradeFn(migrationsTable, db, callback, localMigrations);\n\t}\n\n\treturn { newDb: false };\n}\n"],"mappings":";;;;;;;;;;AAUA,MAAM,mBAQF,EAUH,GAAG,OAAO,iBAAiB,IAAI,UAAU,oBAAoB;CAC5D,MAAM,QAAQ,gBAAG,GAAGA,iBAAI,WAAW,gBAAgB;CAInD,MAAM,UAAU,MAAM,GAAG,QAAQ,IAChC,gBAAG,oCAAoC,MAAM,kBAC7C,EAAE,KAAK,QAAQ;EACf,IAAI,GAAG;EACP,MAAM,GAAG;EACT,YAAY,GAAG;EACf,EAAE;AAGH,iBAAgB,MAAM,GAAG,MACxB,EAAE,iBAAiB,EAAE,eAAe,EAAE,eAAe,EAAE,gBAAgB,EAAE,QAAQ,IAAI,cAAc,EAAE,QAAQ,GAAG,CAChH;CAED,MAAM,2BAAW,IAAI,KAA8B;CACnD,MAAM,yBAAS,IAAI,KAA4B;AAC/C,MAAK,MAAM,MAAM,iBAAiB;AACjC,MAAI,CAAC,SAAS,IAAI,GAAG,aAAa,CACjC,UAAS,IAAI,GAAG,cAAc,EAAE,CAAC;AAElC,WAAS,IAAI,GAAG,aAAa,CAAE,KAAK,GAAG;AACvC,SAAO,IAAI,GAAG,MAAM,GAAG;;CAKxB,MAAM,UAA0C,EAAE;CAClD,IAAI,eAAyB,EAAE;AAE/B,MAAK,MAAM,SAAS,QAAQ;EAC3B,MAAM,cAAc,OAAO,MAAM,WAAW;EAC5C,MAAM,SAAS,OAAO,YAAY,UAAU,GAAG,YAAY,SAAS,EAAE,GAAG,MAAM;EAC/E,MAAM,aAAa,SAAS,IAAI,OAAO;EAEvC,IAAI;AAEJ,MAAI,cAAc,WAAW,WAAW,EACvC,WAAU,WAAW;WACX,cAAc,WAAW,SAAS,EAC5C,WAAU,WAAW,MAAM,MAAM,EAAE,SAAS,MAAM,KAAK;MAEvD,WAAU,OAAO,IAAI,MAAM,KAAK;AAGjC,MAAI,QAAS,SAAQ,KAAK;GAAE,IAAI,MAAM;GAAI,MAAM,QAAQ;GAAM,CAAC;MAC1D,cAAa,KAAK,MAAM,GAAG;;AAQjC,KAAI,aAAa,SAAS,EACzB,OAAM,MACL,2DAA2D,aAAa,OAAO,oBAC9E,aAAa,KAAK,KAAK,CACvB,kKACD;CAIF,MAAM,aAAuB,CAC5B,GAAG,QAAQ,WAAW,gBAAG,eAAe,MAAM,OAAOA,iBAAI,WAAW,OAAO,CAAC,OAAO,cAAc,CAAC,CAAC,KACnG,GAAG,QAAQ,WACV,gBAAG,eAAe,MAAM,OAAOA,iBAAI,WAAW,aAAa,CAAC,sCAC1D,cAAc,CAChB,CAAC,IACF;AAED,MAAK,MAAM,iBAAiB,QAC3B,YAAW,KACV,GAAG,QAAQ,WACV,gBAAG,UAAU,MAAM,OAAOA,iBAAI,WAAW,OAAO,CAAC,KAAK,cAAc,KAAK,IACxEA,iBAAI,WAAW,aAAa,CAC5B,gBAAgBA,iBAAI,WAAW,KAAK,CAAC,KAAK,cAAc,KAAK,cAAc,CAC5E,CAAC,IACF;AAGF,OAAM,SAAS,WAAW;GAG3B;;;;;;;AAQD,eAAsB,gBACrB,iBACA,IACA,UACA,iBACyB;AAQzB,MANe,MAAM,GAAG,QAAQ,IAC/B,gBAAG;wBACmB,gBAAgB;kCAEtC,EAEU,WAAW,EACrB,QAAO,EAAE,OAAO,MAAM;CAIvB,MAAM,OAAO,MAAM,GAAG,QAAQ,IAC7B,gBAAG;;uBAEkB,gBAAgB;;6BAGrC;CAED,MAAM,UAAUC,4CAAgB,YAAY,KAAK,KAAK,MAAM,EAAE,GAAG,CAAC;AAElE,MAAK,IAAI,IAAI,SAAS,IAAIC,sDAA0B,aAAa,KAAK;EACrE,MAAM,YAAY,iBAAiB;AACnC,MAAI,CAAC,UACJ,OAAM,IAAI,MAAM,gDAAgD,EAAE,MAAM,IAAI,IAAI;AAEjF,QAAM,UAAU,iBAAiB,IAAI,UAAU,gBAAgB;;AAGhE,QAAO,EAAE,OAAO,OAAO"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { UpgradeResult } from "./utils.cjs";
|
|
2
|
+
import { MigrationMeta } from "../migrator.cjs";
|
|
3
|
+
import { SingleStoreRemoteDatabase } from "../singlestore-proxy/index.cjs";
|
|
4
|
+
import { ProxyMigrator } from "../singlestore-proxy/migrator.cjs";
|
|
5
|
+
|
|
6
|
+
//#region src/up-migrations/singlestore-proxy.d.ts
|
|
7
|
+
/**
|
|
8
|
+
* Detects the current version of the migrations table schema and upgrades it if needed.
|
|
9
|
+
*
|
|
10
|
+
* Version 0: Original schema (id, hash, created_at)
|
|
11
|
+
* Version 1: Extended schema (id, hash, created_at, name, applied_at)
|
|
12
|
+
*/
|
|
13
|
+
declare function upgradeIfNeeded(migrationsTable: string, db: SingleStoreRemoteDatabase<Record<string, unknown>>, callback: ProxyMigrator, localMigrations: MigrationMeta[]): Promise<UpgradeResult>;
|
|
14
|
+
//#endregion
|
|
15
|
+
export { upgradeIfNeeded };
|
|
16
|
+
//# sourceMappingURL=singlestore-proxy.d.cts.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { UpgradeResult } from "./utils.js";
|
|
2
|
+
import { MigrationMeta } from "../migrator.js";
|
|
3
|
+
import { SingleStoreRemoteDatabase } from "../singlestore-proxy/index.js";
|
|
4
|
+
import { ProxyMigrator } from "../singlestore-proxy/migrator.js";
|
|
5
|
+
|
|
6
|
+
//#region src/up-migrations/singlestore-proxy.d.ts
|
|
7
|
+
/**
|
|
8
|
+
* Detects the current version of the migrations table schema and upgrades it if needed.
|
|
9
|
+
*
|
|
10
|
+
* Version 0: Original schema (id, hash, created_at)
|
|
11
|
+
* Version 1: Extended schema (id, hash, created_at, name, applied_at)
|
|
12
|
+
*/
|
|
13
|
+
declare function upgradeIfNeeded(migrationsTable: string, db: SingleStoreRemoteDatabase<Record<string, unknown>>, callback: ProxyMigrator, localMigrations: MigrationMeta[]): Promise<UpgradeResult>;
|
|
14
|
+
//#endregion
|
|
15
|
+
export { upgradeIfNeeded };
|
|
16
|
+
//# sourceMappingURL=singlestore-proxy.d.ts.map
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { GET_VERSION_FOR, MIGRATIONS_TABLE_VERSIONS } from "./utils.js";
|
|
2
|
+
import { sql } from "../sql/sql.js";
|
|
3
|
+
|
|
4
|
+
//#region src/up-migrations/singlestore-proxy.ts
|
|
5
|
+
/**
|
|
6
|
+
* Map of upgrade functions. Each key is the version being upgraded FROM,
|
|
7
|
+
* and the function upgrades the table to the next version.
|
|
8
|
+
*/
|
|
9
|
+
const upgradeFunctions = { 0: async (migrationsTable, db, callback, localMigrations) => {
|
|
10
|
+
const table = sql`${sql.identifier(migrationsTable)}`;
|
|
11
|
+
const dbRows = (await db.session.all(sql`SELECT id, hash, created_at FROM ${table} ORDER BY id ASC`)).map((it) => ({
|
|
12
|
+
id: it[0],
|
|
13
|
+
hash: it[1],
|
|
14
|
+
created_at: it[2]
|
|
15
|
+
}));
|
|
16
|
+
localMigrations.sort((a, b) => a.folderMillis !== b.folderMillis ? a.folderMillis - b.folderMillis : (a.name ?? "").localeCompare(b.name ?? ""));
|
|
17
|
+
const byMillis = /* @__PURE__ */ new Map();
|
|
18
|
+
const byHash = /* @__PURE__ */ new Map();
|
|
19
|
+
for (const lm of localMigrations) {
|
|
20
|
+
if (!byMillis.has(lm.folderMillis)) byMillis.set(lm.folderMillis, []);
|
|
21
|
+
byMillis.get(lm.folderMillis).push(lm);
|
|
22
|
+
byHash.set(lm.hash, lm);
|
|
23
|
+
}
|
|
24
|
+
const toApply = [];
|
|
25
|
+
let unmatchedIds = [];
|
|
26
|
+
for (const dbRow of dbRows) {
|
|
27
|
+
const stringified = String(dbRow.created_at);
|
|
28
|
+
const millis = Number(stringified.substring(0, stringified.length - 3) + "000");
|
|
29
|
+
const candidates = byMillis.get(millis);
|
|
30
|
+
let matched;
|
|
31
|
+
if (candidates && candidates.length === 1) matched = candidates[0];
|
|
32
|
+
else if (candidates && candidates.length > 1) matched = candidates.find((c) => c.hash === dbRow.hash);
|
|
33
|
+
else matched = byHash.get(dbRow.hash);
|
|
34
|
+
if (matched) toApply.push({
|
|
35
|
+
id: dbRow.id,
|
|
36
|
+
name: matched.name
|
|
37
|
+
});
|
|
38
|
+
else unmatchedIds.push(dbRow.id);
|
|
39
|
+
}
|
|
40
|
+
if (unmatchedIds.length > 0) throw Error(`While upgrading your database migrations table we found ${unmatchedIds.length} migrations (ids: ${unmatchedIds.join(", ")}) in the database that do not match any local migration. This means that some migrations were applied to the database but are missing from the local environment`);
|
|
41
|
+
const statements = [db.dialect.sqlToQuery(sql`ALTER TABLE ${table} ADD ${sql.identifier("name")} text`.inlineParams()).sql, db.dialect.sqlToQuery(sql`ALTER TABLE ${table} ADD ${sql.identifier("applied_at")} TIMESTAMP DEFAULT CURRENT_TIMESTAMP`.inlineParams()).sql];
|
|
42
|
+
for (const backfillEntry of toApply) statements.push(db.dialect.sqlToQuery(sql`UPDATE ${table} SET ${sql.identifier("name")} = ${backfillEntry.name}, ${sql.identifier("applied_at")} = NULL WHERE ${sql.identifier("id")} = ${backfillEntry.id}`.inlineParams()).sql);
|
|
43
|
+
await callback(statements);
|
|
44
|
+
} };
|
|
45
|
+
/**
|
|
46
|
+
* Detects the current version of the migrations table schema and upgrades it if needed.
|
|
47
|
+
*
|
|
48
|
+
* Version 0: Original schema (id, hash, created_at)
|
|
49
|
+
* Version 1: Extended schema (id, hash, created_at, name, applied_at)
|
|
50
|
+
*/
|
|
51
|
+
async function upgradeIfNeeded(migrationsTable, db, callback, localMigrations) {
|
|
52
|
+
if ((await db.session.all(sql`SELECT 1 FROM information_schema.tables
|
|
53
|
+
WHERE table_name = ${migrationsTable}
|
|
54
|
+
AND table_schema = DATABASE()`)).length === 0) return { newDb: true };
|
|
55
|
+
const rows = await db.session.all(sql`SELECT column_name as \`column_name\`
|
|
56
|
+
FROM information_schema.columns
|
|
57
|
+
WHERE table_name = ${migrationsTable}
|
|
58
|
+
AND table_schema = DATABASE()
|
|
59
|
+
ORDER BY ordinal_position`);
|
|
60
|
+
const version = GET_VERSION_FOR.singlestore(rows.map((r) => r[0]));
|
|
61
|
+
for (let v = version; v < MIGRATIONS_TABLE_VERSIONS.singlestore; v++) {
|
|
62
|
+
const upgradeFn = upgradeFunctions[v];
|
|
63
|
+
if (!upgradeFn) throw new Error(`No upgrade path from migration table version ${v} to ${v + 1}`);
|
|
64
|
+
await upgradeFn(migrationsTable, db, callback, localMigrations);
|
|
65
|
+
}
|
|
66
|
+
return { newDb: false };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
//#endregion
|
|
70
|
+
export { upgradeIfNeeded };
|
|
71
|
+
//# sourceMappingURL=singlestore-proxy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"singlestore-proxy.js","names":[],"sources":["../../src/up-migrations/singlestore-proxy.ts"],"sourcesContent":["import type { MigrationMeta } from '~/migrator.ts';\nimport type { SingleStoreRemoteDatabase } from '~/singlestore-proxy/index.ts';\nimport type { ProxyMigrator } from '~/singlestore-proxy/migrator.ts';\nimport { sql } from '~/sql/sql.ts';\nimport { GET_VERSION_FOR, MIGRATIONS_TABLE_VERSIONS, type UpgradeResult } from './utils.ts';\n\n/**\n * Map of upgrade functions. Each key is the version being upgraded FROM,\n * and the function upgrades the table to the next version.\n */\nconst upgradeFunctions: Record<\n\tnumber,\n\t(\n\t\tmigrationsTable: string,\n\t\tdb: SingleStoreRemoteDatabase<Record<string, unknown>>,\n\t\tcallback: ProxyMigrator,\n\t\tlocalMigrations: MigrationMeta[],\n\t) => Promise<void>\n> = {\n\t/**\n\t * Upgrade from version 0 to version 1:\n\t * 1. Read all existing DB migrations\n\t * 2. Sort localMigrations ASC by millis and if the same - sort by name\n\t * 3. Match each DB row to a local migration\n\t * If multiple migrations share the same second, use hash matching as a tiebreaker\n\t * Not implemented for now -> If hash matching fails, fall back to serial id ordering\n\t * 5. Create extra column and backfill names for matched migrations\n\t */\n\t0: async (migrationsTable, db, callback, localMigrations) => {\n\t\tconst table = sql`${sql.identifier(migrationsTable)}`;\n\n\t\t// 1. Read all existing DB migrations\n\t\t// Sort them by ids asc (order how they were applied)\n\t\tconst dbRows = (await db.session.all<[number, string, string]>(\n\t\t\tsql`SELECT id, hash, created_at FROM ${table} ORDER BY id ASC`,\n\t\t)).map((it) => ({\n\t\t\tid: it[0],\n\t\t\thash: it[1],\n\t\t\tcreated_at: it[2],\n\t\t}));\n\n\t\t// 2. Sort ASC by millis and if the same - sort by name\n\t\tlocalMigrations.sort((a, b) =>\n\t\t\ta.folderMillis !== b.folderMillis ? a.folderMillis - b.folderMillis : (a.name ?? '').localeCompare(b.name ?? '')\n\t\t);\n\n\t\tconst byMillis = new Map<number, MigrationMeta[]>();\n\t\tconst byHash = new Map<string, MigrationMeta>();\n\t\tfor (const lm of localMigrations) {\n\t\t\tif (!byMillis.has(lm.folderMillis)) {\n\t\t\t\tbyMillis.set(lm.folderMillis, []);\n\t\t\t}\n\t\t\tbyMillis.get(lm.folderMillis)!.push(lm);\n\t\t\tbyHash.set(lm.hash, lm);\n\t\t}\n\n\t\t// \t3. Match each DB row to a local migration\n\t\t// \tPriority: millis -> hash\n\t\tconst toApply: { id: number; name: string }[] = [];\n\t\tlet unmatchedIds: number[] = [];\n\n\t\tfor (const dbRow of dbRows) {\n\t\t\tconst stringified = String(dbRow.created_at);\n\t\t\tconst millis = Number(stringified.substring(0, stringified.length - 3) + '000');\n\t\t\tconst candidates = byMillis.get(millis);\n\n\t\t\tlet matched: MigrationMeta | undefined;\n\n\t\t\tif (candidates && candidates.length === 1) {\n\t\t\t\tmatched = candidates[0];\n\t\t\t} else if (candidates && candidates.length > 1) {\n\t\t\t\tmatched = candidates.find((c) => c.hash === dbRow.hash);\n\t\t\t} else {\n\t\t\t\tmatched = byHash.get(dbRow.hash);\n\t\t\t}\n\n\t\t\tif (matched) toApply.push({ id: dbRow.id, name: matched.name });\n\t\t\telse unmatchedIds.push(dbRow.id);\n\t\t}\n\n\t\t// 4. Check for unmatched\n\t\t// Our assumption on this migration flow is that all DB entries should be matched to a local migration\n\t\t// (if same seconds - fallback to hash, if hash fails - corner case)\n\t\t// If there are unmatched entries, it means that the local environment is missing migrations that have been applied to the DB,\n\t\t// which can lead to inconsistencies and potential issues when running future migrations\n\t\tif (unmatchedIds.length > 0) {\n\t\t\tthrow Error(\n\t\t\t\t`While upgrading your database migrations table we found ${unmatchedIds.length} migrations (ids: ${\n\t\t\t\t\tunmatchedIds.join(', ')\n\t\t\t\t}) in the database that do not match any local migration. This means that some migrations were applied to the database but are missing from the local environment`,\n\t\t\t);\n\t\t}\n\n\t\t// 5. Create extra column and backfill names for matched migrations\n\t\tconst statements: string[] = [\n\t\t\tdb.dialect.sqlToQuery(sql`ALTER TABLE ${table} ADD ${sql.identifier('name')} text`.inlineParams()).sql,\n\t\t\tdb.dialect.sqlToQuery(\n\t\t\t\tsql`ALTER TABLE ${table} ADD ${sql.identifier('applied_at')} TIMESTAMP DEFAULT CURRENT_TIMESTAMP`\n\t\t\t\t\t.inlineParams(),\n\t\t\t).sql,\n\t\t];\n\n\t\tfor (const backfillEntry of toApply) {\n\t\t\tstatements.push(\n\t\t\t\tdb.dialect.sqlToQuery(\n\t\t\t\t\tsql`UPDATE ${table} SET ${sql.identifier('name')} = ${backfillEntry.name}, ${\n\t\t\t\t\t\tsql.identifier('applied_at')\n\t\t\t\t\t} = NULL WHERE ${sql.identifier('id')} = ${backfillEntry.id}`.inlineParams(),\n\t\t\t\t).sql,\n\t\t\t);\n\t\t}\n\n\t\tawait callback(statements);\n\t\treturn;\n\t},\n};\n\n/**\n * Detects the current version of the migrations table schema and upgrades it if needed.\n *\n * Version 0: Original schema (id, hash, created_at)\n * Version 1: Extended schema (id, hash, created_at, name, applied_at)\n */\nexport async function upgradeIfNeeded(\n\tmigrationsTable: string,\n\tdb: SingleStoreRemoteDatabase<Record<string, unknown>>,\n\tcallback: ProxyMigrator,\n\tlocalMigrations: MigrationMeta[],\n): Promise<UpgradeResult> {\n\t// Check if the table exists at all\n\tconst result = await db.session.all<[1]>(\n\t\tsql`SELECT 1 FROM information_schema.tables \n\t\t\tWHERE table_name = ${migrationsTable}\n\t\t\tAND table_schema = DATABASE()`,\n\t);\n\n\tif (result.length === 0) {\n\t\treturn { newDb: true };\n\t}\n\n\t// Table exists, check table shape\n\tconst rows = await db.session.all<[string]>(\n\t\tsql`SELECT column_name as \\`column_name\\`\n\t\tFROM information_schema.columns\n\t\tWHERE table_name = ${migrationsTable}\n\t\tAND table_schema = DATABASE()\n\t\tORDER BY ordinal_position`,\n\t);\n\n\tconst version = GET_VERSION_FOR.singlestore(rows.map((r) => r[0]));\n\n\tfor (let v = version; v < MIGRATIONS_TABLE_VERSIONS.singlestore; v++) {\n\t\tconst upgradeFn = upgradeFunctions[v];\n\t\tif (!upgradeFn) {\n\t\t\tthrow new Error(`No upgrade path from migration table version ${v} to ${v + 1}`);\n\t\t}\n\t\tawait upgradeFn(migrationsTable, db, callback, localMigrations);\n\t}\n\n\treturn { newDb: false };\n}\n"],"mappings":";;;;;;;;AAUA,MAAM,mBAQF,EAUH,GAAG,OAAO,iBAAiB,IAAI,UAAU,oBAAoB;CAC5D,MAAM,QAAQ,GAAG,GAAG,IAAI,WAAW,gBAAgB;CAInD,MAAM,UAAU,MAAM,GAAG,QAAQ,IAChC,GAAG,oCAAoC,MAAM,kBAC7C,EAAE,KAAK,QAAQ;EACf,IAAI,GAAG;EACP,MAAM,GAAG;EACT,YAAY,GAAG;EACf,EAAE;AAGH,iBAAgB,MAAM,GAAG,MACxB,EAAE,iBAAiB,EAAE,eAAe,EAAE,eAAe,EAAE,gBAAgB,EAAE,QAAQ,IAAI,cAAc,EAAE,QAAQ,GAAG,CAChH;CAED,MAAM,2BAAW,IAAI,KAA8B;CACnD,MAAM,yBAAS,IAAI,KAA4B;AAC/C,MAAK,MAAM,MAAM,iBAAiB;AACjC,MAAI,CAAC,SAAS,IAAI,GAAG,aAAa,CACjC,UAAS,IAAI,GAAG,cAAc,EAAE,CAAC;AAElC,WAAS,IAAI,GAAG,aAAa,CAAE,KAAK,GAAG;AACvC,SAAO,IAAI,GAAG,MAAM,GAAG;;CAKxB,MAAM,UAA0C,EAAE;CAClD,IAAI,eAAyB,EAAE;AAE/B,MAAK,MAAM,SAAS,QAAQ;EAC3B,MAAM,cAAc,OAAO,MAAM,WAAW;EAC5C,MAAM,SAAS,OAAO,YAAY,UAAU,GAAG,YAAY,SAAS,EAAE,GAAG,MAAM;EAC/E,MAAM,aAAa,SAAS,IAAI,OAAO;EAEvC,IAAI;AAEJ,MAAI,cAAc,WAAW,WAAW,EACvC,WAAU,WAAW;WACX,cAAc,WAAW,SAAS,EAC5C,WAAU,WAAW,MAAM,MAAM,EAAE,SAAS,MAAM,KAAK;MAEvD,WAAU,OAAO,IAAI,MAAM,KAAK;AAGjC,MAAI,QAAS,SAAQ,KAAK;GAAE,IAAI,MAAM;GAAI,MAAM,QAAQ;GAAM,CAAC;MAC1D,cAAa,KAAK,MAAM,GAAG;;AAQjC,KAAI,aAAa,SAAS,EACzB,OAAM,MACL,2DAA2D,aAAa,OAAO,oBAC9E,aAAa,KAAK,KAAK,CACvB,kKACD;CAIF,MAAM,aAAuB,CAC5B,GAAG,QAAQ,WAAW,GAAG,eAAe,MAAM,OAAO,IAAI,WAAW,OAAO,CAAC,OAAO,cAAc,CAAC,CAAC,KACnG,GAAG,QAAQ,WACV,GAAG,eAAe,MAAM,OAAO,IAAI,WAAW,aAAa,CAAC,sCAC1D,cAAc,CAChB,CAAC,IACF;AAED,MAAK,MAAM,iBAAiB,QAC3B,YAAW,KACV,GAAG,QAAQ,WACV,GAAG,UAAU,MAAM,OAAO,IAAI,WAAW,OAAO,CAAC,KAAK,cAAc,KAAK,IACxE,IAAI,WAAW,aAAa,CAC5B,gBAAgB,IAAI,WAAW,KAAK,CAAC,KAAK,cAAc,KAAK,cAAc,CAC5E,CAAC,IACF;AAGF,OAAM,SAAS,WAAW;GAG3B;;;;;;;AAQD,eAAsB,gBACrB,iBACA,IACA,UACA,iBACyB;AAQzB,MANe,MAAM,GAAG,QAAQ,IAC/B,GAAG;wBACmB,gBAAgB;kCAEtC,EAEU,WAAW,EACrB,QAAO,EAAE,OAAO,MAAM;CAIvB,MAAM,OAAO,MAAM,GAAG,QAAQ,IAC7B,GAAG;;uBAEkB,gBAAgB;;6BAGrC;CAED,MAAM,UAAU,gBAAgB,YAAY,KAAK,KAAK,MAAM,EAAE,GAAG,CAAC;AAElE,MAAK,IAAI,IAAI,SAAS,IAAI,0BAA0B,aAAa,KAAK;EACrE,MAAM,YAAY,iBAAiB;AACnC,MAAI,CAAC,UACJ,OAAM,IAAI,MAAM,gDAAgD,EAAE,MAAM,IAAI,IAAI;AAEjF,QAAM,UAAU,iBAAiB,IAAI,UAAU,gBAAgB;;AAGhE,QAAO,EAAE,OAAO,OAAO"}
|
|
@@ -1,30 +1,16 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
2
|
const require_runtime = require('../_virtual/_rolldown/runtime.cjs');
|
|
3
|
+
const require_up_migrations_utils = require('./utils.cjs');
|
|
3
4
|
let __sql_sql_ts = require("../sql/sql.cjs");
|
|
4
5
|
|
|
5
6
|
//#region src/up-migrations/singlestore.ts
|
|
6
|
-
const CURRENT_MIGRATION_TABLE_VERSION = 1;
|
|
7
|
-
function getVersion(columns) {
|
|
8
|
-
if (columns.includes("name")) return 1;
|
|
9
|
-
return 0;
|
|
10
|
-
}
|
|
11
|
-
async function all(session, sqlQuery, resultMapper = () => []) {
|
|
12
|
-
const result = await session.all(sqlQuery);
|
|
13
|
-
if (result.length === 0) return [];
|
|
14
|
-
if (Array.isArray(result[0])) return result.map((row) => resultMapper(row));
|
|
15
|
-
return result;
|
|
16
|
-
}
|
|
17
7
|
/**
|
|
18
8
|
* Map of upgrade functions. Each key is the version being upgraded FROM,
|
|
19
9
|
* and the function upgrades the table to the next version.
|
|
20
10
|
*/
|
|
21
11
|
const upgradeFunctions = { 0: async (migrationsTable, session, localMigrations) => {
|
|
22
12
|
const table = __sql_sql_ts.sql`${__sql_sql_ts.sql.identifier(migrationsTable)}`;
|
|
23
|
-
const dbRows = await all(
|
|
24
|
-
id: row[0],
|
|
25
|
-
hash: row[1],
|
|
26
|
-
created_at: row[2]
|
|
27
|
-
}));
|
|
13
|
+
const dbRows = await session.all(__sql_sql_ts.sql`SELECT id, hash, created_at FROM ${table} ORDER BY id ASC`);
|
|
28
14
|
localMigrations.sort((a, b) => a.folderMillis !== b.folderMillis ? a.folderMillis - b.folderMillis : (a.name ?? "").localeCompare(b.name ?? ""));
|
|
29
15
|
const byMillis = /* @__PURE__ */ new Map();
|
|
30
16
|
const byHash = /* @__PURE__ */ new Map();
|
|
@@ -61,23 +47,21 @@ const upgradeFunctions = { 0: async (migrationsTable, session, localMigrations)
|
|
|
61
47
|
* Version 1: Extended schema (id, hash, created_at, name, applied_at)
|
|
62
48
|
*/
|
|
63
49
|
async function upgradeIfNeeded(migrationsTable, session, localMigrations) {
|
|
64
|
-
if ((await all(
|
|
50
|
+
if ((await session.all(__sql_sql_ts.sql`SELECT 1 FROM information_schema.tables
|
|
65
51
|
WHERE table_name = ${migrationsTable}
|
|
66
|
-
AND table_schema = DATABASE()
|
|
67
|
-
const
|
|
52
|
+
AND table_schema = DATABASE()`)).length === 0) return { newDb: true };
|
|
53
|
+
const rows = await session.all(__sql_sql_ts.sql`SELECT column_name as \`column_name\`
|
|
68
54
|
FROM information_schema.columns
|
|
69
55
|
WHERE table_name = ${migrationsTable}
|
|
70
56
|
AND table_schema = DATABASE()
|
|
71
|
-
ORDER BY ordinal_position
|
|
72
|
-
|
|
57
|
+
ORDER BY ordinal_position`);
|
|
58
|
+
const version = require_up_migrations_utils.GET_VERSION_FOR.singlestore(rows.map((r) => r.column_name));
|
|
59
|
+
for (let v = version; v < require_up_migrations_utils.MIGRATIONS_TABLE_VERSIONS.singlestore; v++) {
|
|
73
60
|
const upgradeFn = upgradeFunctions[v];
|
|
74
61
|
if (!upgradeFn) throw new Error(`No upgrade path from migration table version ${v} to ${v + 1}`);
|
|
75
62
|
await upgradeFn(migrationsTable, session, localMigrations);
|
|
76
63
|
}
|
|
77
|
-
return {
|
|
78
|
-
prevVersion: version,
|
|
79
|
-
currentVersion: CURRENT_MIGRATION_TABLE_VERSION
|
|
80
|
-
};
|
|
64
|
+
return { newDb: false };
|
|
81
65
|
}
|
|
82
66
|
|
|
83
67
|
//#endregion
|