@quereus/quereus 4.20.0 → 4.20.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/common/types.d.ts +4 -4
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +3 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/schema/catalog-rendering.js +13 -1
- package/dist/src/schema/catalog-rendering.js.map +1 -1
- package/dist/src/schema/catalog.d.ts +35 -2
- package/dist/src/schema/catalog.d.ts.map +1 -1
- package/dist/src/schema/catalog.js +42 -33
- package/dist/src/schema/catalog.js.map +1 -1
- package/dist/src/schema/schema-differ.d.ts +1 -1
- package/dist/src/schema/schema-differ.d.ts.map +1 -1
- package/dist/src/schema/schema-differ.js +195 -33
- package/dist/src/schema/schema-differ.js.map +1 -1
- package/dist/src/schema/unique-enforcement.d.ts +23 -1
- package/dist/src/schema/unique-enforcement.d.ts.map +1 -1
- package/dist/src/schema/unique-enforcement.js +43 -1
- package/dist/src/schema/unique-enforcement.js.map +1 -1
- package/dist/src/vtab/memory/layer/manager.d.ts +15 -12
- package/dist/src/vtab/memory/layer/manager.d.ts.map +1 -1
- package/dist/src/vtab/memory/layer/manager.js +87 -100
- package/dist/src/vtab/memory/layer/manager.js.map +1 -1
- package/package.json +3 -3
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { implicitIndexNameForColumns } from './catalog.js';
|
|
1
2
|
import { createTableToString, createViewToString, createMaterializedViewToString, createIndexToString, createAssertionToString, columnDefToString, quoteIdentifier, expressionToString, tagsBodyToString, tableConstraintsToString, constraintBodyToCanonicalString, createIndexBodyToCanonicalString, indexedColumnBareName, viewDefinitionToCanonicalString, astToString } from '../emit/ast-stringify.js';
|
|
2
3
|
import { computeBodyHash, normalizeBackingModuleName, canonicalBackingModuleArgs } from './view.js';
|
|
3
4
|
import { QuereusError } from '../common/errors.js';
|
|
@@ -10,7 +11,7 @@ import { Parser } from '../parser/parser.js';
|
|
|
10
11
|
import { cloneExpr, cloneQueryExpr } from '../planner/mutation/scope-transform.js';
|
|
11
12
|
import { normalizeCollationName } from '../util/comparison.js';
|
|
12
13
|
import { inferType } from '../types/registry.js';
|
|
13
|
-
import { resolveDefaultCollation } from './table.js';
|
|
14
|
+
import { resolveDefaultCollation, disambiguateAutoConstraintName } from './table.js';
|
|
14
15
|
const log = createLogger('schema:differ');
|
|
15
16
|
const warnLog = log.extend('warn');
|
|
16
17
|
/** Reserved tag namespace prefix used for differ-recognized hints. */
|
|
@@ -1399,11 +1400,12 @@ columnRenamesByTable, schemaName) {
|
|
|
1399
1400
|
return { ...stmt, columns, where };
|
|
1400
1401
|
}
|
|
1401
1402
|
/**
|
|
1402
|
-
* Converts a column-level constraint
|
|
1403
|
-
*
|
|
1404
|
-
*
|
|
1405
|
-
* Returns undefined for constraint kinds that are not
|
|
1406
|
-
* constraints (NOT NULL / NULL / DEFAULT / COLLATE /
|
|
1403
|
+
* Converts a column-level constraint into the equivalent table-level
|
|
1404
|
+
* {@link AST.TableConstraint}, so it can be stringified into an `ADD CONSTRAINT`
|
|
1405
|
+
* fragment and diffed (by name, or by body when unnamed) alongside table-level
|
|
1406
|
+
* constraints. Returns undefined for constraint kinds that are not
|
|
1407
|
+
* lifecycle-managed table constraints (NOT NULL / NULL / DEFAULT / COLLATE /
|
|
1408
|
+
* GENERATED / PRIMARY KEY).
|
|
1407
1409
|
*/
|
|
1408
1410
|
function columnConstraintToTableConstraint(columnName, cc) {
|
|
1409
1411
|
switch (cc.type) {
|
|
@@ -1422,13 +1424,18 @@ function columnConstraintToTableConstraint(columnName, cc) {
|
|
|
1422
1424
|
}
|
|
1423
1425
|
}
|
|
1424
1426
|
/**
|
|
1425
|
-
* Gathers declared
|
|
1426
|
-
*
|
|
1427
|
-
*
|
|
1428
|
-
*
|
|
1429
|
-
*
|
|
1427
|
+
* Gathers a declared table's CHECK / UNIQUE / FOREIGN KEY constraints — both
|
|
1428
|
+
* table-level and column-level — split the way the catalog splits them:
|
|
1429
|
+
* *user-named* ones keyed by lowercased name, and the rest (no name, or a
|
|
1430
|
+
* reserved `_`-prefixed one) as a list in declaration order. PRIMARY KEY is
|
|
1431
|
+
* excluded (handled by `primaryKeyChange`). On a user-name collision the first
|
|
1430
1432
|
* wins (a duplicate user constraint name is a separate validation concern).
|
|
1431
1433
|
*
|
|
1434
|
+
* A column-level clause on a column `isAddedColumn` reports is skipped: `ADD
|
|
1435
|
+
* COLUMN` renders the column's clauses inline (`columnDefToString`), so collecting
|
|
1436
|
+
* it here would ADD the constraint a second time. A TABLE-level constraint over a
|
|
1437
|
+
* new column is not carried by `ADD COLUMN` and is collected as usual.
|
|
1438
|
+
*
|
|
1432
1439
|
* `schemaName` is the schema this table is being diffed under (the differ runs
|
|
1433
1440
|
* per schema — it is the CHILD schema for any FK declared here). It is threaded
|
|
1434
1441
|
* into the canonical-body render so an FK's explicit own-schema qualifier folds
|
|
@@ -1436,33 +1443,34 @@ function columnConstraintToTableConstraint(columnName, cc) {
|
|
|
1436
1443
|
* {@link constraintBodyToCanonicalString}); a genuine cross-schema parent stays a
|
|
1437
1444
|
* body-change channel.
|
|
1438
1445
|
*/
|
|
1439
|
-
function
|
|
1440
|
-
const
|
|
1441
|
-
const
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
+
function collectDeclaredConstraints(declaredTable, schemaName, isAddedColumn) {
|
|
1447
|
+
const named = new Map();
|
|
1448
|
+
const unnamed = [];
|
|
1449
|
+
const add = (tc, column) => {
|
|
1450
|
+
const definition = constraintBodyToCanonicalString(tc, schemaName);
|
|
1451
|
+
if (!tc.name || tc.name.startsWith('_')) {
|
|
1452
|
+
unnamed.push({ name: tc.name, column, definition, bodyAst: tc });
|
|
1446
1453
|
return;
|
|
1447
|
-
|
|
1454
|
+
}
|
|
1455
|
+
const lower = tc.name.toLowerCase();
|
|
1456
|
+
if (named.has(lower))
|
|
1448
1457
|
return;
|
|
1449
|
-
|
|
1458
|
+
named.set(lower, { name: tc.name, tags: tc.tags, ddl: tableConstraintsToString([tc]), definition, bodyAst: tc });
|
|
1450
1459
|
};
|
|
1451
1460
|
for (const c of declaredTable.tableStmt.constraints ?? []) {
|
|
1452
|
-
if (c.type
|
|
1453
|
-
|
|
1454
|
-
add(c.name, c.tags, c);
|
|
1461
|
+
if (c.type !== 'primaryKey')
|
|
1462
|
+
add(c);
|
|
1455
1463
|
}
|
|
1456
1464
|
for (const col of declaredTable.tableStmt.columns) {
|
|
1465
|
+
if (isAddedColumn(col.name.toLowerCase()))
|
|
1466
|
+
continue;
|
|
1457
1467
|
for (const cc of col.constraints ?? []) {
|
|
1458
|
-
if (!cc.name)
|
|
1459
|
-
continue;
|
|
1460
1468
|
const tc = columnConstraintToTableConstraint(col.name, cc);
|
|
1461
1469
|
if (tc)
|
|
1462
|
-
add(
|
|
1470
|
+
add(tc, col.name);
|
|
1463
1471
|
}
|
|
1464
1472
|
}
|
|
1465
|
-
return
|
|
1473
|
+
return { named, unnamed };
|
|
1466
1474
|
}
|
|
1467
1475
|
/**
|
|
1468
1476
|
* Inverse-applies the in-diff column renames to a constraint's column list: maps
|
|
@@ -1655,6 +1663,141 @@ resolveDeclaredColumn) {
|
|
|
1655
1663
|
return d.definition;
|
|
1656
1664
|
}
|
|
1657
1665
|
}
|
|
1666
|
+
/**
|
|
1667
|
+
* Constraint lifecycle for CHECK / UNIQUE / FOREIGN KEY constraints with no
|
|
1668
|
+
* user-addressable name (see `CatalogTable.unnamedConstraints`). With no name to
|
|
1669
|
+
* key on, identity is the canonical body: declared and actual are matched as a
|
|
1670
|
+
* multiset — each declared constraint consumes one actual with the same
|
|
1671
|
+
* (rename-reconciled) body, so two identical unnamed CHECKs are two entries.
|
|
1672
|
+
*
|
|
1673
|
+
* - declared, unmatched → ADD under a minted reserved name (see
|
|
1674
|
+
* {@link mintUnnamedConstraintName}), so the result is unnamed-class on the
|
|
1675
|
+
* next diff and droppable by name;
|
|
1676
|
+
* - actual, unmatched → DROP by its stored auto-name. One stored with no name
|
|
1677
|
+
* (a CREATE-time table-level CHECK, any UNIQUE added unnamed) has no statement
|
|
1678
|
+
* that removes it: skipped when a dropped column takes it along (DROP COLUMN
|
|
1679
|
+
* prunes a UNIQUE / FK over the column), otherwise refused here — the
|
|
1680
|
+
* alternative is the silent no-op that leaves an upgraded database enforcing a
|
|
1681
|
+
* rule a fresh one does not.
|
|
1682
|
+
*
|
|
1683
|
+
* NOTE: tags on an unnamed constraint are not diffed — with no name there is
|
|
1684
|
+
* nothing for `ALTER CONSTRAINT … SET TAGS` to address. A tag-only edit to an
|
|
1685
|
+
* unnamed constraint is a no-op; name the constraint to manage its tags.
|
|
1686
|
+
*/
|
|
1687
|
+
function diffUnnamedConstraints(declared, actualTable, ctx) {
|
|
1688
|
+
const unmatchedActual = [...(actualTable.unnamedConstraints ?? [])];
|
|
1689
|
+
const unmatchedDeclared = [];
|
|
1690
|
+
for (const d of declared) {
|
|
1691
|
+
const body = ctx.reconcile(d);
|
|
1692
|
+
// Among same-body actuals, keep the one with no stored name: of a pair, it is
|
|
1693
|
+
// the one no DROP can reach, so leaving the other unmatched stays droppable.
|
|
1694
|
+
let at = unmatchedActual.findIndex(a => a.definition === body && a.name === undefined);
|
|
1695
|
+
if (at < 0)
|
|
1696
|
+
at = unmatchedActual.findIndex(a => a.definition === body);
|
|
1697
|
+
if (at >= 0)
|
|
1698
|
+
unmatchedActual.splice(at, 1);
|
|
1699
|
+
else
|
|
1700
|
+
unmatchedDeclared.push(d);
|
|
1701
|
+
}
|
|
1702
|
+
const drops = [];
|
|
1703
|
+
const undroppable = [];
|
|
1704
|
+
for (const a of unmatchedActual) {
|
|
1705
|
+
if (a.name !== undefined)
|
|
1706
|
+
drops.push(a.name);
|
|
1707
|
+
else if (!prunedByColumnDrop(a, ctx.droppedColumns))
|
|
1708
|
+
undroppable.push(a);
|
|
1709
|
+
}
|
|
1710
|
+
if (undroppable.length > 0 && ctx.refuseUndroppable) {
|
|
1711
|
+
throw new QuereusError(`Cannot drop unnamed constraint ${undroppable.map(a => `'${a.definition}'`).join(', ')} from table '${ctx.schemaName}.${actualTable.name}': `
|
|
1712
|
+
+ `it is no longer declared, but it was created without a name, so no statement can drop it. `
|
|
1713
|
+
+ `Keep declaring it, or rebuild the table; name constraints (constraint <name> …) to keep them droppable.`, StatusCode.ERROR);
|
|
1714
|
+
}
|
|
1715
|
+
const taken = constraintNamesAtAddTime(actualTable, ctx, drops);
|
|
1716
|
+
const checkOrdinal = countActualChecks(actualTable);
|
|
1717
|
+
const adds = unmatchedDeclared.map(d => tableConstraintsToString([{ ...d.bodyAst, name: mintUnnamedConstraintName(d, ctx.tableName, checkOrdinal, taken) }]));
|
|
1718
|
+
return { adds, drops };
|
|
1719
|
+
}
|
|
1720
|
+
/** True when DROP COLUMN removes `a` on its own: it prunes a UNIQUE / FK over a dropped column (a CHECK instead blocks the drop). */
|
|
1721
|
+
function prunedByColumnDrop(a, droppedColumns) {
|
|
1722
|
+
return a.kind !== 'check' && (a.bodyAst?.columns ?? []).some(c => droppedColumns.has(c.name.toLowerCase()));
|
|
1723
|
+
}
|
|
1724
|
+
/** CHECKs on the actual table — where an unnamed table-level CHECK's `_check_<n>` ordinal starts. */
|
|
1725
|
+
function countActualChecks(actualTable) {
|
|
1726
|
+
return (actualTable.namedConstraints ?? []).filter(c => c.bodyAst?.type === 'check').length
|
|
1727
|
+
+ (actualTable.unnamedConstraints ?? []).filter(c => c.kind === 'check').length;
|
|
1728
|
+
}
|
|
1729
|
+
/**
|
|
1730
|
+
* The lowercased constraint names a minted name must avoid at the point the
|
|
1731
|
+
* `ADD CONSTRAINT` steps run (after every DROP / RENAME CONSTRAINT and ADD COLUMN
|
|
1732
|
+
* of the table — see the phase order in `generateMigrationPlan`): the live names
|
|
1733
|
+
* minus the dropped ones, plus every declared user name (each exists by then),
|
|
1734
|
+
* plus — over-approximated — what `ADD COLUMN` mints for an inline clause on a new
|
|
1735
|
+
* column. An unnamed UNIQUE's backing-structure name (`_uc_<cols>`) is included:
|
|
1736
|
+
* a UNIQUE added under the same name would collide with that structure.
|
|
1737
|
+
*
|
|
1738
|
+
* NOTE: that backing name is derived from the PRE-rename column names, but a RENAME
|
|
1739
|
+
* COLUMN in the same diff moves it to `_uc_<new cols>` before the ADDs run. A mint can
|
|
1740
|
+
* only hit the moved name through a `_`-joined spelling clash (`unique (a_b)` beside a
|
|
1741
|
+
* renamed `unique (a, b)`), and the engine refuses that ADD loudly; if it ever bites,
|
|
1742
|
+
* derive the name over the post-rename columns.
|
|
1743
|
+
*/
|
|
1744
|
+
function constraintNamesAtAddTime(actualTable, ctx, unnamedDrops) {
|
|
1745
|
+
const taken = new Set();
|
|
1746
|
+
for (const c of actualTable.namedConstraints ?? [])
|
|
1747
|
+
taken.add(c.name.toLowerCase());
|
|
1748
|
+
for (const c of actualTable.unnamedConstraints ?? []) {
|
|
1749
|
+
if (c.name !== undefined)
|
|
1750
|
+
taken.add(c.name.toLowerCase());
|
|
1751
|
+
else if (c.kind === 'unique')
|
|
1752
|
+
taken.add(uniqueBackingName(c.bodyAst?.columns ?? []).toLowerCase());
|
|
1753
|
+
}
|
|
1754
|
+
for (const name of [...ctx.namedDrops, ...unnamedDrops])
|
|
1755
|
+
taken.delete(name.toLowerCase());
|
|
1756
|
+
for (const name of ctx.declaredNames)
|
|
1757
|
+
taken.add(name);
|
|
1758
|
+
for (const col of ctx.addedColumns) {
|
|
1759
|
+
taken.add(`_check_${col}`.toLowerCase());
|
|
1760
|
+
taken.add(`_fk_${ctx.tableName}_${col}`.toLowerCase());
|
|
1761
|
+
taken.add(uniqueBackingName([{ name: col }]).toLowerCase());
|
|
1762
|
+
}
|
|
1763
|
+
return taken;
|
|
1764
|
+
}
|
|
1765
|
+
/** The structure name an unnamed UNIQUE over `columns` is backed by. */
|
|
1766
|
+
function uniqueBackingName(columns) {
|
|
1767
|
+
return implicitIndexNameForColumns(undefined, columns.map(c => c.name));
|
|
1768
|
+
}
|
|
1769
|
+
/**
|
|
1770
|
+
* The reserved `_`-prefixed name an unnamed constraint is ADDed under — reserved so
|
|
1771
|
+
* the result stays in the unnamed class (body-matched, stable on re-diff), named so
|
|
1772
|
+
* it can be dropped and its ADD undone. Each spells what the engine itself names
|
|
1773
|
+
* the constraint where it names one:
|
|
1774
|
+
* - the `_`-name the declaration wrote, if any;
|
|
1775
|
+
* - column-level CHECK → `_check_<column>` (CREATE TABLE's mint);
|
|
1776
|
+
* - table-level CHECK → `_check_<n>`, `n` from the table's CHECK count, bumped
|
|
1777
|
+
* until free (the `_check_<index>` label an unnamed CHECK's violation reports;
|
|
1778
|
+
* each mint registers in `taken`, so the next one bumps past it).
|
|
1779
|
+
* NOT left unnamed: `ALTER … ADD` would mint a user-class `check_<n>`, which
|
|
1780
|
+
* the next diff would drop and re-add forever;
|
|
1781
|
+
* - FOREIGN KEY → `_fk_<table>_<cols>` (both engine paths' mint);
|
|
1782
|
+
* - UNIQUE → `_uc_<cols>` (the backing-structure name both paths use).
|
|
1783
|
+
* Then disambiguated (`_<N>` suffix) against — and registered into — `taken`.
|
|
1784
|
+
*/
|
|
1785
|
+
function mintUnnamedConstraintName(d, tableName, checkOrdinal, taken) {
|
|
1786
|
+
const tc = d.bodyAst;
|
|
1787
|
+
if (d.name === undefined && tc.type === 'check' && d.column === undefined) {
|
|
1788
|
+
let n = checkOrdinal;
|
|
1789
|
+
while (taken.has(`_check_${n}`))
|
|
1790
|
+
n++;
|
|
1791
|
+
taken.add(`_check_${n}`);
|
|
1792
|
+
return `_check_${n}`;
|
|
1793
|
+
}
|
|
1794
|
+
const columns = tc.columns ?? [];
|
|
1795
|
+
const base = d.name
|
|
1796
|
+
?? (tc.type === 'check' ? `_check_${d.column}`
|
|
1797
|
+
: tc.type === 'foreignKey' ? `_fk_${tableName}_${columns.map(c => c.name).join('_')}`
|
|
1798
|
+
: uniqueBackingName(columns));
|
|
1799
|
+
return disambiguateAutoConstraintName(base, taken);
|
|
1800
|
+
}
|
|
1658
1801
|
/**
|
|
1659
1802
|
* Resolves a declared table's column renames against its matched actual catalog
|
|
1660
1803
|
* table — the map-building + {@link resolveRenames} step shared by both the
|
|
@@ -1785,10 +1928,11 @@ defaultCollation) {
|
|
|
1785
1928
|
// `constraints` list AND column-level constraints carrying an explicit name
|
|
1786
1929
|
// (e.g. `qty int constraint chk_qty check (qty > 0)`) — the actual catalog's
|
|
1787
1930
|
// `namedConstraints` already merges both. PRIMARY KEY constraints are excluded
|
|
1788
|
-
// (PK changes flow through `primaryKeyChange`)
|
|
1789
|
-
//
|
|
1790
|
-
//
|
|
1791
|
-
const
|
|
1931
|
+
// (PK changes flow through `primaryKeyChange`). Unnamed and auto-prefixed (`_`)
|
|
1932
|
+
// ones are matched by body instead, after the named lifecycle below (see
|
|
1933
|
+
// `diffUnnamedConstraints`), mirroring the catalog's named / unnamed split.
|
|
1934
|
+
const declaredConstraints = collectDeclaredConstraints(declaredTable, schemaName, lower => !colRenames.pairs.has(lower));
|
|
1935
|
+
const declaredNamedConstraints = declaredConstraints.named;
|
|
1792
1936
|
const actualNamedConstraints = new Map();
|
|
1793
1937
|
for (const c of actualTable.namedConstraints ?? []) {
|
|
1794
1938
|
actualNamedConstraints.set(c.name.toLowerCase(), c);
|
|
@@ -1863,6 +2007,21 @@ defaultCollation) {
|
|
|
1863
2007
|
pureDropCount++;
|
|
1864
2008
|
}
|
|
1865
2009
|
}
|
|
2010
|
+
// Unnamed constraints are not rename candidates, so they stay out of the
|
|
2011
|
+
// require-hint counts.
|
|
2012
|
+
const unnamed = diffUnnamedConstraints(declaredConstraints.unnamed, actualTable, {
|
|
2013
|
+
tableName: declaredTable.tableStmt.table.name,
|
|
2014
|
+
schemaName,
|
|
2015
|
+
reconcile: d => reconciledDeclaredBody(d, diff.columnsToRename, tableRenames, actualTable.name, schemaName, columnRenamesByTable, resolveDeclaredColumn),
|
|
2016
|
+
declaredNames: [...declaredNamedConstraints.keys()],
|
|
2017
|
+
namedDrops: constraintsToDrop,
|
|
2018
|
+
addedColumns: declaredTable.tableStmt.columns.filter(c => !colRenames.pairs.has(c.name.toLowerCase())).map(c => c.name),
|
|
2019
|
+
droppedColumns: new Set(diff.columnsToDrop.map(c => c.toLowerCase())),
|
|
2020
|
+
// A backing-module move recreates the table, so its alter diff is discarded.
|
|
2021
|
+
refuseUndroppable: !diff.maintainedModuleMigration,
|
|
2022
|
+
});
|
|
2023
|
+
constraintsToDrop.push(...unnamed.drops);
|
|
2024
|
+
constraintsToAdd.push(...unnamed.adds);
|
|
1866
2025
|
if (constraintsToAdd.length > 0)
|
|
1867
2026
|
diff.constraintsToAdd = constraintsToAdd;
|
|
1868
2027
|
if (constraintsToDrop.length > 0)
|
|
@@ -2824,8 +2983,11 @@ class UndoRenderer {
|
|
|
2824
2983
|
}
|
|
2825
2984
|
readdConstraint(table, name, quotedTable) {
|
|
2826
2985
|
// `constraintsToDrop` carries pre-apply names (a renamed constraint is never
|
|
2827
|
-
// also dropped), so the lookup is direct rather than through the renames.
|
|
2828
|
-
|
|
2986
|
+
// also dropped), so the lookup is direct rather than through the renames. An
|
|
2987
|
+
// unnamed constraint is dropped by its stored auto-name, and re-added under it.
|
|
2988
|
+
const actual = this.tableNow(table);
|
|
2989
|
+
const constraint = actual?.namedConstraints?.find(c => sameName(c.name, name))
|
|
2990
|
+
?? actual?.unnamedConstraints?.find(c => c.name !== undefined && sameName(c.name, name));
|
|
2829
2991
|
if (!constraint)
|
|
2830
2992
|
return NOTHING_TO_UNDO;
|
|
2831
2993
|
if (!constraint.bodyAst)
|