import { IndexMethod } from '../schema/table-builder'; /** * Shared index SQL generation + definition comparison. * * Centralizing the CREATE/DROP statement builders here guarantees that the live * auto-migrate path (`DbSchemaManager`) and the file scaffold (`MigrationScaffold`) * emit byte-for-byte identical SQL — which in turn keeps the signature comparison * below stable: an index this module creates always normalizes back to the same * signature this module derives from the model, so it is never recreated twice. */ /** Minimal index shape needed to render SQL — a subset of `IndexDefinition`. */ export interface IndexSqlSpec { name: string; columns: string[]; isUnique?: boolean; using?: IndexMethod; operatorClass?: string; expressions?: string[]; where?: string; /** * Opt-in `NULLS NOT DISTINCT` for a UNIQUE index (PostgreSQL 15+): treat NULLs * as equal so at most one row may have NULL in the indexed column(s). Only * meaningful on unique indexes — PostgreSQL rejects it on non-unique ones, so * the SQL builder emits the clause only when `isUnique` is also set. */ nullsNotDistinct?: boolean; } /** * Build the parenthesized column/expression list, applying the per-column * operator-class suffix. Mirrors the logic the ORM has always used so the * generated SQL — and therefore PostgreSQL's stored definition — is unchanged. */ export declare function buildIndexColumnList(spec: IndexSqlSpec): string; /** * Build a `CREATE INDEX` statement. `qualifiedTable` must already be quoted / * schema-qualified by the caller (e.g. `"public"."t"`). */ export declare function buildCreateIndexStatement(spec: IndexSqlSpec, qualifiedTable: string, opts?: { concurrent?: boolean; ifNotExists?: boolean; }): string; /** * Build a `DROP INDEX` statement. `qualifiedIndex` must already be quoted / * schema-qualified by the caller. */ export declare function buildDropIndexStatement(qualifiedIndex: string, opts?: { concurrent?: boolean; ifExists?: boolean; }): string; /** The comparable signature of an index, normalized for equality testing. */ export interface IndexSignature { isUnique: boolean; /** Access method, lower-cased. `undefined` model `using` is treated as btree. */ method: string; /** Normalized column / expression list. */ columns: string; /** Normalized partial-index predicate (empty string when none). */ where: string; /** * Whether the (unique) index treats NULLs as equal (`NULLS NOT DISTINCT`). * Always `false` for a non-unique index, mirroring what PostgreSQL can store. */ nullsNotDistinct: boolean; } /** * Normalize an index column-list or predicate fragment so the model's intended * SQL and PostgreSQL's canonical `pg_get_indexdef()` form compare equal. * * The transforms, derived from observed `pg_get_indexdef(oid, 0, true)` output: * - lower-case (folds keyword/identifier case: `USING`/`using`, `DESC`, ...); * - strip double quotes (`"email"` -> `email`); * - strip the `public.` schema prefix the ORM emits for `search_normalize`, * which PostgreSQL drops because `public` is on the search_path; * - strip `::type` casts that PostgreSQL injects for argument coercion * (`search_normalize(email::text)` -> `search_normalize(email)`), the key to * leaving `ixNormalized` indexes on varchar columns untouched; * - collapse whitespace and tighten spacing around commas/parens. * * Known limitations (documented): lower-casing also folds string literals inside * a partial-index `WHERE`; an explicitly-specified *default* operator class is * hidden by PostgreSQL and would read as a difference. Both are rare and noted * in the migration guide. */ export declare function normalizeIndexFragment(fragment: string | undefined): string; /** * Normalize a partial-index `WHERE` predicate. Builds on * {@link normalizeIndexFragment} and additionally folds the rewrites PostgreSQL * applies to a predicate at parse-analysis time, so the model's raw predicate * string compares equal to the form `pg_get_indexdef` reports: * * - `!=` → `<>`; * - `LIKE`/`ILIKE`/`NOT LIKE`/`NOT ILIKE` → the `~~` / `~~*` / `!~~` / `!~~*` * operators PostgreSQL stores; * - `col = ANY (ARRAY[...])` ↔ `col IN (...)` (PostgreSQL rewrites `IN` lists to * the `= ANY (ARRAY[...])` form — normalize both directions to `IN`); * - `col BETWEEN a AND b` → `col >= a AND col <= b` (PostgreSQL's stored form). * * Not recoverable by text normalization, and therefore documented as * limitations (the index may be recreated once per migration): PostgreSQL * expands bare date/time literals to a fully-qualified `timestamptz` with the * server's offset, and re-parenthesizes sub-expressions by operator precedence. * Write such predicates in PostgreSQL's canonical form, or disable * `recreateChangedIndexes`, to avoid churn. */ export declare function normalizeIndexPredicate(where: string | undefined): string; /** Build the normalized signature of a model `IndexDefinition`-like object. */ export declare function modelIndexSignature(spec: IndexSqlSpec): IndexSignature; /** * Parse PostgreSQL's canonical index definition * (`pg_get_indexdef(oid, 0, true)`) into a normalized signature. * * Returns `null` when the definition cannot be parsed or carries a clause the * model cannot express (e.g. `INCLUDE`), in which case the caller treats the * index as unchanged to avoid pointless rebuilds. */ export declare function parseDbIndexSignature(canonicalDef: string): IndexSignature | null; export interface IndexComparison { changed: boolean; /** Human-readable summary of what differs (for logs / scaffold comments). */ reason?: string; modelSignature: IndexSignature; dbSignature: IndexSignature | null; } /** * Decide whether the model's index definition differs from what PostgreSQL * currently stores. Conservative by design: if the database definition can't be * parsed/compared, it reports `changed: false` so a same-named index is never * needlessly rebuilt. */ export declare function compareIndexDefinition(canonicalDbDef: string, spec: IndexSqlSpec): IndexComparison; /** * Reduce a canonical `pg_get_indexdef()` string to the parts that define the * index's shape: its uniqueness and everything from `USING ` onward * (access method, column/expression list with operator classes, and the partial * predicate). The index name and table reference — which differ between the real * index and its temp-table rebuild — are deliberately excluded. */ export declare function indexCanonicalSignature(canonicalDef: string): { isUnique: boolean; body: string; }; /** * Whether two canonical `pg_get_indexdef()` strings describe the same index * shape (ignoring index name and table). Because both strings come from * PostgreSQL's own deparser in the same session, equivalent definitions are * byte-identical here — making this an exact, false-positive-free check. */ export declare function canonicalDefsEquivalent(defA: string, defB: string): boolean; //# sourceMappingURL=index-sql.d.ts.map