import { ident } from "pg-format";
import "postgres";
import { AbortError } from "../../../../../shared/src/abort-error.js";
import { equals } from "../../../../../shared/src/set-utils.js";
import { runTx } from "../../../db/run-transaction.js";
import "../../../types/pg.js";
import { cdcSchema } from "../../../types/shards.js";
function schema(shard) {
  return ident(cdcSchema(shard));
}
function createSchema(shard) {
  return (
    /*sql*/
    `CREATE SCHEMA IF NOT EXISTS ${schema(shard)};`
  );
}
function createChangeLogTable(shard) {
  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)
  );
`
  );
}
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)
  );
`
  );
}
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 createBackfillTables(shard) {
  return (
    /*sql*/
    `
  CREATE TABLE ${schema(shard)}."tableMetadata" (
    "schema" TEXT NOT NULL,
    "table" TEXT NOT NULL,
    "metadata" JSONB NOT NULL,
    PRIMARY KEY("schema", "table")
  );

  CREATE TABLE ${schema(shard)}."backfilling" (
    "schema" TEXT NOT NULL,
    "table" TEXT NOT NULL,
    "column" TEXT NOT NULL,
    "backfill" JSONB NOT NULL,
    PRIMARY KEY("schema", "table", "column")
  );
  `
  );
}
function createTables(shard) {
  return createSchema(shard) + createChangeLogTable(shard) + createReplicationStateTable(shard) + createReplicationConfigTable(shard) + createBackfillTables(shard);
}
async function setupCDCTables(lc, db, shard) {
  lc.info?.(`Setting up CDC tables`);
  await db.unsafe(createTables(shard));
}
async function markResetRequired(sql, shard) {
  const schema2 = cdcSchema(shard);
  await sql`
  UPDATE ${sql(schema2)}."replicationConfig"
    SET "resetRequired" = true`;
}
async function ensureReplicationConfig(lc, db, subscriptionState, shard, autoReset) {
  const { publications, replicaVersion, watermark } = subscriptionState;
  const replicaConfig = { publications, replicaVersion };
  const replicationState = {
    lastWatermark: replicaVersion,
    owner: null,
    ownerAddress: null
  };
  const schema2 = cdcSchema(shard);
  await runTx(db, async (sql) => {
    const stmts = [];
    const results = await sql`
    SELECT "replicaVersion", "publications", "resetRequired" 
      FROM ${sql(schema2)}."replicationConfig"`;
    if (results.length) {
      const { replicaVersion: replicaVersion2, publications: publications2 } = results[0];
      if (replicaVersion2 !== replicaConfig.replicaVersion || !equals(new Set(publications2), new Set(replicaConfig.publications))) {
        if (replicaConfig.replicaVersion !== watermark) {
          throw new AutoResetSignal(
            `Cannot reset change db@${replicaVersion2} to service replica@${replicaConfig.replicaVersion} from watermark ${watermark}`
          );
        }
        lc.info?.(
          `Data in cdc tables @${replicaVersion2} is incompatible with replica @${replicaConfig.replicaVersion}. Clearing tables.`
        );
        stmts.push(
          sql`TRUNCATE TABLE ${sql(schema2)}."changeLog"`,
          sql`TRUNCATE TABLE ${sql(schema2)}."replicationConfig"`,
          sql`TRUNCATE TABLE ${sql(schema2)}."tableMetadata"`,
          sql`TRUNCATE TABLE ${sql(schema2)}."backfilling"`
        );
      }
    }
    if (results.length === 0 || stmts.length > 0) {
      const watermark2 = replicaConfig.replicaVersion;
      const initialTx = [
        { watermark: watermark2, pos: 0, change: { tag: "begin" } },
        { watermark: watermark2, pos: 1, change: { tag: "commit" } }
      ];
      stmts.push(
        sql`INSERT INTO ${sql(schema2)}."replicationConfig" ${sql(replicaConfig)}`,
        sql`INSERT INTO ${sql(schema2)}."replicationState"  ${sql(replicationState)} 
              ON CONFLICT (lock) DO UPDATE SET ${sql(replicationState)}`,
        ...initialTx.map(
          (change) => sql`INSERT INTO ${sql(schema2)}."changeLog" ${sql(change)}`
        )
      );
      return Promise.all(stmts);
    }
    const { resetRequired } = results[0];
    if (resetRequired) {
      if (autoReset) {
        throw new AutoResetSignal("reset required by replication stream");
      }
      lc.error?.(
        "\n\n\nReset required but --auto-reset is not enabled.\nThis can happen for upstream databases that do not support event triggers.\nTo correct this, see https://zero.rocicorp.dev/docs/connecting-to-postgres#schema-changes\n\n\n"
      );
    }
    return [];
  });
}
class AutoResetSignal extends AbortError {
  name = "AutoResetSignal";
}
export {
  AutoResetSignal,
  createBackfillTables,
  createReplicationStateTable,
  discoverChangeStreamerAddress,
  ensureReplicationConfig,
  markResetRequired,
  setupCDCTables
};
//# sourceMappingURL=tables.js.map
