diff --git a/src/remote/remote.test.ts b/src/remote/remote.test.ts index 7071de3..c0c6fce 100644 --- a/src/remote/remote.test.ts +++ b/src/remote/remote.test.ts @@ -73,6 +73,7 @@ Deno.test({ ); const result = await remote.syncFrom(source); + await remote.optimizer.finish; const optimizedQueries = remote.optimizer.getQueries(); const queries = optimizedQueries.map((f) => f.query); @@ -129,6 +130,66 @@ Deno.test({ }, }); +// Users who upgraded from Postgres 13/14 may have a leftover bit_xor aggregate. +// It became built-in in Postgres 15, but custom versions from older installs remain. +// This test ensures sync handles this gracefully. +Deno.test({ + name: "syncs database with custom bit_xor aggregate", + sanitizeOps: false, + sanitizeResources: false, + fn: async () => { + const [sourceDb, targetDb] = await Promise.all([ + new PostgreSqlContainer("postgres:17") + .withCopyContentToContainer([ + { + content: ` + create extension pg_stat_statements; + CREATE AGGREGATE public.bit_xor(v bigint) ( + SFUNC = int8xor, + STYPE = bigint + ); + create table testing(a bigint); + insert into testing values (1); + create index "testing_idx" on testing(a); + `, + target: "/docker-entrypoint-initdb.d/init.sql", + }, + ]) + .withCommand(["-c", "shared_preload_libraries=pg_stat_statements"]) + .start(), + testSpawnTarget(), + ]); + + try { + const target = Connectable.fromString(targetDb.getConnectionUri()); + const source = Connectable.fromString(sourceDb.getConnectionUri()); + + const remote = new Remote( + target, + ConnectionManager.forLocalDatabase(), + ); + + const result = await remote.syncFrom(source); + await remote.optimizer.finish; + + // Assert sync completed successfully (aggregate excluded gracefully) + assertOk(result.schema); + + const tableNames = result.schema.value.tables.map((table) => + table.tableName + ); + assertArrayIncludes(tableNames, ["testing"]); + + const indexNames = result.schema.value.indexes.map((index) => + index.indexName + ); + assertArrayIncludes(indexNames, ["testing_idx"]); + } finally { + await Promise.all([sourceDb.stop(), targetDb.stop()]); + } + }, +}); + Deno.test({ name: "raw timescaledb syncs correctly", sanitizeOps: false, @@ -243,6 +304,7 @@ Deno.test({ targetConn.withDatabaseName(PgIdentifier.fromString("optimizing_db")), ); await remote.syncFrom(sourceConn); + await remote.optimizer.finish; const queries = remote.optimizer.getQueries(); const queryStrings = queries.map((q) => q.query); diff --git a/src/sync/schema_dump.sql b/src/sync/schema_dump.sql index 228da22..07fc274 100644 --- a/src/sync/schema_dump.sql +++ b/src/sync/schema_dump.sql @@ -220,6 +220,8 @@ all_functions as ( n.nspname not like 'pg_%' AND n.nspname <> 'information_schema' AND d.objid IS NULL + -- Exclude bit_xor aggregate as pg_get_functiondef doesn't work on it + AND NOT (pro.proname = 'bit_xor' AND pro.prokind = 'a') ORDER BY n.nspname, pro.proname, pg_get_function_identity_arguments(pro.oid) ),