import { LogContext } from '@rocicorp/logger';
import { ident } from 'pg-format';
import postgres, {} from 'postgres';
import { AbortError } from "../../../../../shared/src/abort-error.js";
import { equals } from "../../../../../shared/src/set-utils.js";
import { disableStatementTimeout } from "../../../types/pg.js";
import { cdcSchema } from "../../../types/shards.js";
// For readability in the sql statements.
function schema(shard) {
    return ident(cdcSchema(shard));
}
export const PG_SCHEMA = 'cdc';
function createSchema(shard) {
    return /*sql*/ `CREATE SCHEMA IF NOT EXISTS ${schema(shard)};`;
}
function createChangeLogTable(shard) {
    // Note: The "change" column used to be JSONB, but that was problematic in that
    // it does not handle the NULL unicode character.
    // https://vladimir.varank.in/notes/2021/01/you-dont-insert-unicode-null-character-as-postgres-jsonb/
    return /*sql*/ `
  CREATE TABLE ${schema(shard)}."changeLog" (
    watermark  TEXT,
    pos        INT8,
    change     JSON NOT NULL,
    precommit  TEXT,  -- Only exists on commit entries. Purely for debugging.
    PRIMARY KEY (watermark, pos)
  );
`;
}
export function createReplicationStateTable(shard) {
    return /*sql*/ `
  CREATE TABLE ${schema(shard)}."replicationState" (
    "lastWatermark" TEXT NOT NULL,
    "owner" TEXT,
    "ownerAddress" TEXT,
    "lock" INTEGER PRIMARY KEY DEFAULT 1 CHECK (lock=1)
  );
`;
}
export async function discoverChangeStreamerAddress(shard, sql) {
    const result = await sql `
    SELECT "ownerAddress" FROM ${sql(cdcSchema(shard))}."replicationState"`;
    return result[0].ownerAddress;
}
function createReplicationConfigTable(shard) {
    return /*sql*/ `
  CREATE TABLE ${schema(shard)}."replicationConfig" (
    "replicaVersion" TEXT NOT NULL,
    "publications" TEXT[] NOT NULL,
    "resetRequired" BOOL,
    "lock" INTEGER PRIMARY KEY DEFAULT 1 CHECK (lock=1)
  );
`;
}
function createTables(shard) {
    return (createSchema(shard) +
        createChangeLogTable(shard) +
        createReplicationStateTable(shard) +
        createReplicationConfigTable(shard));
}
export async function setupCDCTables(lc, db, shard) {
    lc.info?.(`Setting up CDC tables`);
    await db.unsafe(createTables(shard));
}
export async function markResetRequired(sql, shard) {
    const schema = cdcSchema(shard);
    await sql `
  UPDATE ${sql(schema)}."replicationConfig"
    SET "resetRequired" = true`;
}
export async function ensureReplicationConfig(lc, db, subscriptionState, shard, autoReset) {
    const { publications, replicaVersion, watermark } = subscriptionState;
    const replicaConfig = { publications, replicaVersion };
    const replicationState = {
        lastWatermark: replicaVersion,
    };
    const schema = cdcSchema(shard);
    await db.begin(async (sql) => {
        disableStatementTimeout(sql);
        const stmts = [];
        const results = await sql `
    SELECT "replicaVersion", "publications", "resetRequired" 
      FROM ${sql(schema)}."replicationConfig"`;
        if (results.length) {
            const { replicaVersion, publications } = results[0];
            if (replicaVersion !== replicaConfig.replicaVersion ||
                !equals(new Set(publications), new Set(replicaConfig.publications))) {
                if (replicaConfig.replicaVersion !== watermark) {
                    throw new AutoResetSignal(`Cannot reset change db@${replicaVersion} to ` +
                        `service replica@${replicaConfig.replicaVersion} ` +
                        `from watermark ${watermark}`);
                }
                lc.info?.(`Data in cdc tables @${replicaVersion} is incompatible ` +
                    `with replica @${replicaConfig.replicaVersion}. Clearing tables.`);
                stmts.push(sql `TRUNCATE TABLE ${sql(schema)}."changeLog"`, sql `TRUNCATE TABLE ${sql(schema)}."replicationConfig"`, sql `TRUNCATE TABLE ${sql(schema)}."replicationState"`);
            }
        }
        // Initialize (or re-initialize TRUNCATED) tables
        if (results.length === 0 || stmts.length > 0) {
            stmts.push(sql `INSERT INTO ${sql(schema)}."replicationConfig" ${sql(replicaConfig)}`, sql `INSERT INTO ${sql(schema)}."replicationState" 
           ${sql(replicationState)}`);
            return Promise.all(stmts);
        }
        const { resetRequired } = results[0];
        if (resetRequired) {
            if (autoReset) {
                throw new AutoResetSignal('reset required by replication stream');
            }
            lc.error?.('\n\n\n' +
                'Reset required but --auto-reset is not enabled.\n' +
                'This can happen for upstream databases that do not support event triggers.\n' +
                'To correct this, see https://zero.rocicorp.dev/docs/connecting-to-postgres#schema-changes' +
                '\n\n\n');
        }
        return [];
    });
}
export class AutoResetSignal extends AbortError {
    name = 'AutoResetSignal';
}
//# sourceMappingURL=tables.js.map