import { ident } from 'pg-format';
import { stringify, } from "../../../../../shared/src/bigint-json.js";
import { stringCompare } from "../../../../../shared/src/string-compare.js";
import { normalizedKeyOrder } from "../../../types/row-key.js";
import { cvrSchema } from "../../../types/shards.js";
import { versionFromString, versionString, } from "./types.js";
// For readability in the sql statements.
function schema(shard) {
    return ident(cvrSchema(shard));
}
function createSchema(shard) {
    return `CREATE SCHEMA IF NOT EXISTS ${schema(shard)};`;
}
function createInstancesTable(shard) {
    return `
CREATE TABLE ${schema(shard)}.instances (
  "clientGroupID"  TEXT PRIMARY KEY,
  "version"        TEXT NOT NULL,             -- Sortable representation of CVRVersion, e.g. "5nbqa2w:09"
  "lastActive"     TIMESTAMPTZ NOT NULL,      -- For garbage collection
  "ttlClock"       DOUBLE PRECISION NOT NULL, -- The ttl clock gets "paused" when disconnected.
  "replicaVersion" TEXT,                      -- Identifies the replica (i.e. initial-sync point) from which the CVR data comes.
  "owner"          TEXT,                      -- The ID of the task / server that has been granted ownership of the CVR.
  "grantedAt"      TIMESTAMPTZ,               -- The time at which the current owner was last granted ownership (most recent connection time).
  "clientSchema"   JSONB                      -- ClientSchema of the client group
);

-- For garbage collection.
CREATE INDEX instances_last_active
  ON ${schema(shard)}.instances ("lastActive");
`;
}
export function compareInstancesRows(a, b) {
    return stringCompare(a.clientGroupID, b.clientGroupID);
}
function createClientsTable(shard) {
    return `
CREATE TABLE ${schema(shard)}.clients (
  "clientGroupID"      TEXT,
  "clientID"           TEXT,

  PRIMARY KEY ("clientGroupID", "clientID"),

  CONSTRAINT fk_clients_client_group
    FOREIGN KEY("clientGroupID")
    REFERENCES ${schema(shard)}.instances("clientGroupID")
    ON DELETE CASCADE
);

`;
}
export function compareClientsRows(a, b) {
    const clientGroupIDComp = stringCompare(a.clientGroupID, b.clientGroupID);
    if (clientGroupIDComp !== 0) {
        return clientGroupIDComp;
    }
    return stringCompare(a.clientID, b.clientID);
}
function createQueriesTable(shard) {
    return `
CREATE TABLE ${schema(shard)}.queries (
  "clientGroupID"         TEXT,
  "queryHash"             TEXT, -- this is the hash of the client query AST
  "clientAST"             JSONB, -- this is nullable as custom queries will not persist an AST
  "queryName"             TEXT, -- the name of the query if it is a custom query
  "queryArgs"             JSON, -- the arguments of the query if it is a custom query
  "patchVersion"          TEXT,  -- NULL if only desired but not yet "got"
  "transformationHash"    TEXT,
  "transformationVersion" TEXT,
  "internal"              BOOL,  -- If true, no need to track / send patches
  "deleted"               BOOL,  -- put vs del "got" query

  PRIMARY KEY ("clientGroupID", "queryHash"),

  CONSTRAINT fk_queries_client_group
    FOREIGN KEY("clientGroupID")
    REFERENCES ${schema(shard)}.instances("clientGroupID")
    ON DELETE CASCADE
);

-- For catchup patches.
CREATE INDEX queries_patch_version 
  ON ${schema(shard)}.queries ("patchVersion" NULLS FIRST);
`;
}
export function compareQueriesRows(a, b) {
    const clientGroupIDComp = stringCompare(a.clientGroupID, b.clientGroupID);
    if (clientGroupIDComp !== 0) {
        return clientGroupIDComp;
    }
    return stringCompare(a.queryHash, b.queryHash);
}
function createDesiresTable(shard) {
    return `
CREATE TABLE ${schema(shard)}.desires (
  "clientGroupID"      TEXT,
  "clientID"           TEXT,
  "queryHash"          TEXT,
  "patchVersion"       TEXT NOT NULL,
  "deleted"            BOOL,  -- put vs del "desired" query
  "ttl"                INTERVAL,  -- Time to live for this client
  "inactivatedAt"      TIMESTAMPTZ,  -- Time at which this row was inactivated

  PRIMARY KEY ("clientGroupID", "clientID", "queryHash"),

  CONSTRAINT fk_desires_query
    FOREIGN KEY("clientGroupID", "queryHash")
    REFERENCES ${ident(cvrSchema(shard))}.queries("clientGroupID", "queryHash")
    ON DELETE CASCADE
);

-- For catchup patches.
CREATE INDEX desires_patch_version
  ON ${schema(shard)}.desires ("patchVersion");

CREATE INDEX desires_inactivated_at
  ON ${schema(shard)}.desires ("inactivatedAt");
`;
}
export function compareDesiresRows(a, b) {
    const clientGroupIDComp = stringCompare(a.clientGroupID, b.clientGroupID);
    if (clientGroupIDComp !== 0) {
        return clientGroupIDComp;
    }
    const clientIDComp = stringCompare(a.clientID, b.clientID);
    if (clientIDComp !== 0) {
        return clientIDComp;
    }
    return stringCompare(a.queryHash, b.queryHash);
}
export function rowsRowToRowID(rowsRow) {
    return {
        schema: rowsRow.schema,
        table: rowsRow.table,
        rowKey: rowsRow.rowKey,
    };
}
export function rowsRowToRowRecord(rowsRow) {
    return {
        id: rowsRowToRowID(rowsRow),
        rowVersion: rowsRow.rowVersion,
        patchVersion: versionFromString(rowsRow.patchVersion),
        refCounts: rowsRow.refCounts,
    };
}
export function rowRecordToRowsRow(clientGroupID, rowRecord) {
    return {
        clientGroupID,
        schema: rowRecord.id.schema,
        table: rowRecord.id.table,
        rowKey: rowRecord.id.rowKey,
        rowVersion: rowRecord.rowVersion,
        patchVersion: versionString(rowRecord.patchVersion),
        refCounts: rowRecord.refCounts,
    };
}
export function compareRowsRows(a, b) {
    const clientGroupIDComp = stringCompare(a.clientGroupID, b.clientGroupID);
    if (clientGroupIDComp !== 0) {
        return clientGroupIDComp;
    }
    const schemaComp = stringCompare(a.schema, b.schema);
    if (schemaComp !== 0) {
        return schemaComp;
    }
    const tableComp = stringCompare(b.table, b.table);
    if (tableComp !== 0) {
        return tableComp;
    }
    return stringCompare(stringifySorted(a.rowKey), stringifySorted(b.rowKey));
}
/**
 * The version of the data in the `cvr.rows` table. This may lag
 * `version` in `cvr.instances` but eventually catches up, modulo
 * exceptional circumstances like a server crash.
 *
 * The `rowsVersion` is tracked in a separate table (as opposed to
 * a column in the `cvr.instances` table) so that general `cvr` updates
 * and `row` updates can be executed independently without serialization
 * conflicts.
 *
 * Note: Although `clientGroupID` logically references the same column in
 * `cvr.instances`, a FOREIGN KEY constraint must not be declared as the
 * `cvr.rows` TABLE needs to be updated without affecting the
 * `SELECT ... FOR UPDATE` lock when `cvr.instances` is updated.
 */
export function createRowsVersionTable(shard) {
    return `
CREATE TABLE ${schema(shard)}."rowsVersion" (
  "clientGroupID" TEXT PRIMARY KEY,
  "version"       TEXT NOT NULL
);
`;
}
/**
 * CVR `rows` are updated asynchronously from the CVR metadata
 * (i.e. `instances`). The `rowsVersion` table is updated atomically with
 * updates to the `rows` data.
 */
function createRowsTable(shard) {
    return `
CREATE TABLE ${schema(shard)}.rows (
  "clientGroupID"    TEXT,
  "schema"           TEXT,
  "table"            TEXT,
  "rowKey"           JSONB,
  "rowVersion"       TEXT NOT NULL,
  "patchVersion"     TEXT NOT NULL,
  "refCounts"        JSONB,  -- {[queryHash: string]: number}, NULL for tombstone

  PRIMARY KEY ("clientGroupID", "schema", "table", "rowKey"),

  CONSTRAINT fk_rows_client_group
    FOREIGN KEY("clientGroupID")
    REFERENCES ${schema(shard)}."rowsVersion" ("clientGroupID")
    ON DELETE CASCADE
);

-- For catchup patches.
CREATE INDEX row_patch_version 
  ON ${schema(shard)}.rows ("patchVersion");

-- For listing rows returned by one or more query hashes. e.g.
-- SELECT * FROM cvr_shard.rows WHERE "refCounts" ?| array[...queryHashes...];
CREATE INDEX row_ref_counts ON ${schema(shard)}.rows 
  USING GIN ("refCounts");
`;
}
function createTables(shard) {
    return (createSchema(shard) +
        createInstancesTable(shard) +
        createClientsTable(shard) +
        createQueriesTable(shard) +
        createDesiresTable(shard) +
        createRowsVersionTable(shard) +
        createRowsTable(shard));
}
export async function setupCVRTables(lc, db, shard) {
    lc.info?.(`Setting up CVR tables`);
    await db.unsafe(createTables(shard));
}
function stringifySorted(r) {
    return stringify(normalizedKeyOrder(r));
}
//# sourceMappingURL=cvr.js.map