import { LogContext } from '@rocicorp/logger';
import { type JSONValue } from '../../../../shared/src/bigint-json.ts';
import type { LiteAndZqlSpec, LiteTableSpecWithKeys } from '../../db/specs.ts';
import { StatementRunner } from '../../db/statements.ts';
import { type RowKey, type RowValue } from '../../types/row-key.ts';
import { type SchemaVersions } from '../../types/schema-versions.ts';
import type { AppID } from '../../types/shards.ts';
/**
 * A `Snapshotter` manages the progression of database snapshots for a
 * ViewSyncer.
 *
 * The Replicator and ViewSyncers operate on the same SQLite file, with the
 * Replicator being the sole writer to the database. The IVM logic in
 * ViewSyncers, however, rely on incrementally applying changes to the DB to
 * update the state of its pipelines.
 *
 * To avoid coupling the progress of the Replicator and all IVM pipelines on
 * each other, ViewSyncers operate on ephemeral forks of the database by holding
 * [concurrent](https://sqlite.org/src/doc/begin-concurrent/doc/begin_concurrent.md)
 * snapshots of the database and simulating (but ultimately rolling back)
 * mutations on these snapshots.
 *
 * Example:
 * 1. ViewSyncer takes `snapshot_a` at version `t1` of the database and
 *    hydrates its pipeline(s).
 * 2. Replicator applies a new transaction to the database and notifies
 *    subscribers.
 * 3. ViewSyncer takes `snapshot_b` at `t2`, and queries the `ChangeLog` at
 *    that snapshot for changes since `t1`.
 * 4. ViewSyncer applies those changes to `snapshot_a` for IVM, but does not
 *    commit them. (Recall that the Replicator is the sole writer to the db, so
 *    the ViewSyncer never commits any writes.)
 * 5. Replicator applies the next transaction and advances the database to `t3`.
 * 6. ViewSyncer rolls back `snapshot_a` and opens `snapshot_c` at `t3`, using
 *    `snapshot_b` to simulate changes from `t2` to `t3`.
 *
 * ```
 * Replicator:  t1 --------------> t2 --------------> t3 --------------->
 * ViewSyncer:       [snapshot_a] ----> [snapshot_b] ----> [snapshot_c]
 * ```
 *
 * Note that the Replicator (and ViewSyncers) do not wait on the progress of
 * other ViewSyncers. If a ViewSyncer is busy hydrating at `t1`, the Replicator
 * and other ViewSyncers can progress through `t2`, `t3`, etc. independently,
 * as the busy ViewSyncer simply takes its own snapshot when it is ready.
 *
 * ```
 * Replicator:   t1 --------------> t2 --------------> t3 --------------->
 * ViewSyncer1:       [snapshot_a] ----> [snapshot_b] ----> [snapshot_c]
 * ViewSyncer2:       [.......... snapshot_a ..........] ----> [snapshot_b]
 * ```
 *
 * To minimize Database connections (and statement preparation, etc.), the
 * Snapshotter reuses the connection from the previous (rolled back)
 * snapshot when opening the new one.
 *
 * ```
 * Replicator:  t1 --------------> t2 --------------> t3 --------------->
 * ViewSyncer:       [snapshot_a] ----> [snapshot_b] ----> [snapshot_c]
 *                     (conn_1)           (conn_2)           (conn_1)
 * ```
 *
 * In this manner, each ViewSyncer uses two connections that continually
 * "leapfrog" each other to replay the timeline of changes in isolation from
 * the Replicator and other ViewSyncers.
 */
export declare class Snapshotter {
    #private;
    constructor(lc: LogContext, dbFile: string, { appID }: AppID);
    /**
     * Initializes the snapshot to the current head of the database. This must be
     * only be called once. The state of whether a Snapshotter has been initialized
     * can be determined by calling {@link initialized()}.
     */
    init(): this;
    initialized(): boolean;
    /** Returns the current snapshot. Asserts if {@link initialized()} is false. */
    current(): Snapshot;
    /**
     * Advances to the head of the Database, returning a diff between the
     * previously current Snapshot and a new Snapshot at head. This is called
     * in response to a notification from a Replicator subscription. Subsequent
     * calls to {@link current()} return the new Snapshot. Note that the Snapshotter
     * must be initialized before advancing.
     *
     * The returned {@link SnapshotDiff} contains snapshots at the endpoints
     * of the database timeline. Iterating over the diff generates a sequence
     * of {@link Change}s between the two snapshots.
     *
     * Note that this sequence is not chronological; rather, the sequence is
     * ordered by `<table, row-key>`, such that a row can appear at most once
     * in the common case, or twice if its table is `TRUNCATE`'d and a new value
     * is subsequently `INSERT`'ed. This results in dropping most intermediate
     * changes to a row and bounds the amount of work needed to catch up;
     * however, as a consequence, a consistent database state is only guaranteed
     * when the sequence has been fully consumed.
     *
     * Note that Change generation relies on the state of the underlying
     * database connections, and because the connection for the previous snapshot
     * is reused to produce the next snapshot, the diff object is only valid
     * until the next call to `advance()`.
     *
     * It is okay for the caller to apply `Change`s to the `prev` snapshot
     * during the iteration (e.g. this is necessary for IVM); the remainder
     * of the iteration is not affected because a given row can appear at most
     * once in the sequence (with the exception being TRUNCATE, after which the
     * deleted rows can be re-inserted, but this will also behave correctly if
     * the changes are applied).
     *
     * Once the changes have been applied, however, a _subsequent_ iteration
     * will not produce the correct results. In order to perform multiple
     * change-applying iterations, the caller must (1) create a save point
     * on `prev` before each iteration, and (2) rollback to the save point after
     * the iteration.
     */
    advance(tables: Map<string, LiteAndZqlSpec>): SnapshotDiff;
    advanceWithoutDiff(): {
        prev: Snapshot;
        curr: Snapshot;
    };
    /**
     * Call this to close the database connections when the Snapshotter is
     * no longer needed.
     */
    destroy(): void;
}
export type Change = {
    readonly table: string;
    readonly prevValue: Readonly<RowValue> | null;
    readonly nextValue: Readonly<RowValue> | null;
    readonly rowKey: RowKey;
};
/**
 * Represents the difference between two database Snapshots.
 * Iterating over the object will produce a sequence of {@link Change}s
 * between the two snapshots.
 *
 * See {@link Snapshotter.advance()} for semantics and usage.
 */
export interface SnapshotDiff extends Iterable<Change> {
    readonly prev: {
        readonly db: StatementRunner;
        readonly version: string;
    };
    readonly curr: {
        readonly db: StatementRunner;
        readonly version: string;
    };
    /**
     * The number of ChangeLog entries between the snapshots. Note that this
     * may not necessarily equal the number of `Change` objects that the iteration
     * will produce, as `TRUNCATE` entries are counted as a single log entry which
     * may be expanded into many changes (i.e. row deletes).
     *
     * TODO: Determine if it is worth changing the definition to count the
     *       truncated rows. This would make diff computation more expensive
     *       (requiring the count to be aggregated by operation type), which
     *       may not be worth it for a presumable rare operation.
     */
    readonly changes: number;
}
/**
 * Thrown during an iteration of a {@link SnapshotDiff} when a schema
 * change or truncate is encountered, which result in aborting the
 * advancement and resetting / rehydrating the pipelines.
 */
export declare class ResetPipelinesSignal extends Error {
    readonly name = "ResetPipelinesSignal";
    constructor(msg: string);
}
declare class Snapshot {
    #private;
    static create(lc: LogContext, dbFile: string, appID: string): Snapshot;
    readonly db: StatementRunner;
    readonly version: string;
    readonly schemaVersions: SchemaVersions;
    constructor(db: StatementRunner, appID: string);
    numChangesSince(prevVersion: string): any;
    changesSince(prevVersion: string): {
        changes: IterableIterator<unknown>;
        cleanup: () => void;
    };
    getRow(table: LiteTableSpecWithKeys, rowKey: JSONValue): any;
    getRows(table: LiteTableSpecWithKeys): {
        rows: IterableIterator<unknown>;
        cleanup: () => void;
    };
    resetToHead(): Snapshot;
}
export declare class InvalidDiffError extends Error {
    constructor(msg: string);
}
export {};
//# sourceMappingURL=snapshotter.d.ts.map