import type { CheckConstraint, ColumnInfo, TableInfo } from "../types.js"; /** * Generating a row that Postgres will actually accept is the hardest mechanical * part of this whole tool. A real schema will reject anything naive with NOT * NULL, CHECK, enum, length, and uniqueness violations — and every table we fail * to seed is a table we cannot test. * * The strategy that makes this tractable: write as few columns as possible. * Anything with a default is left to the default, so most constraints are * satisfied by the schema's own intent rather than by our guessing. We only * generate values for columns that are required and have no default, plus the * ones that carry meaning for us (owner, org, foreign keys, and one canary). * * Where the schema *does* force us to write a constrained column, the CHECK is * read rather than guessed at. The line we hold is that a value must be * *provably* legal: a shape we can decompose to "this column is restricted to * these literals" or "this number lies between these bounds". Anything else is * declined and the table is reported as unseeded. A row that slips past a * constraint by luck is worse than an honest skip, because the value it * invented — a `signal_type`, a `role`, a redemption code — may mean something * in the application that wrote the constraint. */ export interface CheckHints { /** Literal values the column is restricted to. */ allowed?: string[]; min?: number; max?: number; exclusiveMin?: number; exclusiveMax?: number; /** Bounds on `length(col)` / `char_length(col)`, which are not bounds on col. */ minChars?: number; maxChars?: number; /** Elements an array column is restricted to, from `col <@ ARRAY[...]`. */ arrayAllowed?: string[]; /** Keys a json object must carry, from `col ? 'key'`. */ jsonKeys?: string[]; /** Required `jsonb_typeof(col)`. */ jsonShape?: string; } /** * Extract what a set of CHECK constraints permits, column by column. * * Postgres hands us the expression back normalised — `IN` becomes `= ANY * (ARRAY[...])`, every operand is parenthesised and cast — so this works on a * predictable shape rather than on arbitrary SQL. It is deliberately not an * expression evaluator: `parseExpression` decomposes conjunctions, recognises a * fixed set of leaf comparisons, and drops everything else on the floor. * * A disjunction is dropped whole. `(kind = 'a' AND ref IS NULL) OR (kind = 'b' * AND ref IS NOT NULL)` offers no clause that has to hold, and taking `kind = * 'a'` out of one branch without the rest of that branch produces a row the * constraint still rejects — or, worse, one it accepts for a reason we did not * intend. */ export declare function parseCheckHints(checks: CheckConstraint[]): Map; /** Build a canary that survives length limits and is greppable in any response. */ export declare function makeCanary(maxLength: number | null): string; /** * The longest string this column will accept, taking both the declared type and * any `length(col) <= n` CHECK into account. */ export declare function textLimit(column: ColumnInfo | null, hints: CheckHints | undefined): number | null; /** A canary sized for the column it is going into, wherever that column came from. */ export declare function makeCanaryFor(column: ColumnInfo | null, hints: Map): string; /** True for `text[]`, `escalation_level[]`, and anything else Postgres spells with brackets. */ export declare function isArrayType(column: ColumnInfo): boolean; /** * Can this column hold a plain string? * * The bracket check is load-bearing: `format_type` spells a text array `text[]`, * so a prefix match for "text" says yes and the canary goes in as a bare string * — `malformed array literal: "CROSSLINE_0ab047bf0297"`, and the table is lost. */ export declare function isTextualType(column: ColumnInfo): boolean; /** * Pick the column to plant the canary in: a plain text column that is not * unique, not a key, and not constrained to an enumerated set — and long enough * to hold something worth grepping for. */ export declare function pickCanaryColumn(table: TableInfo, hints: Map): ColumnInfo | null; export interface GenerateContext { column: ColumnInfo; hints: CheckHints | undefined; canary: string; /** * Distinguishes one generated row from the next. * * Alice and Bob are seeded one after the other, so a column that is part of a * key has to get a different value each time or the second insert collides * and the table goes unchecked — a `seat_no integer primary key` or an enum * key would otherwise cost us the table entirely. Only applied where a * collision is actually possible, so ordinary payload columns keep their * simple, predictable values. */ variant?: number; /** True when the column takes part in a primary key or unique constraint. */ mustBeUnique?: boolean; } /** * Produce a value Postgres will accept for a column we must write. * Returns `undefined` when we cannot construct one, which the planner records * as a skipped table rather than silently omitting a check. */ export declare function generateValue(ctx: GenerateContext): unknown | undefined;