linkgress-orm 0.4.59 → 0.4.61
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/entity/entity-base.d.ts +16 -0
- package/dist/entity/entity-base.d.ts.map +1 -1
- package/dist/entity/entity-base.js +1 -0
- package/dist/entity/entity-base.js.map +1 -1
- package/dist/entity/entity-builder.d.ts +23 -0
- package/dist/entity/entity-builder.d.ts.map +1 -1
- package/dist/entity/entity-builder.js +34 -0
- package/dist/entity/entity-builder.js.map +1 -1
- package/dist/entity/model-config.d.ts.map +1 -1
- package/dist/entity/model-config.js +4 -0
- package/dist/entity/model-config.js.map +1 -1
- package/dist/migration/check-constraint-sql.d.ts +45 -0
- package/dist/migration/check-constraint-sql.d.ts.map +1 -0
- package/dist/migration/check-constraint-sql.js +41 -0
- package/dist/migration/check-constraint-sql.js.map +1 -0
- package/dist/migration/db-schema-manager.d.ts +27 -0
- package/dist/migration/db-schema-manager.d.ts.map +1 -1
- package/dist/migration/db-schema-manager.js +213 -117
- package/dist/migration/db-schema-manager.js.map +1 -1
- package/dist/migration/migration-scaffold.d.ts.map +1 -1
- package/dist/migration/migration-scaffold.js +19 -0
- package/dist/migration/migration-scaffold.js.map +1 -1
- package/dist/query/conditions.d.ts +8 -0
- package/dist/query/conditions.d.ts.map +1 -1
- package/dist/query/conditions.js.map +1 -1
- package/dist/query/query-builder.d.ts +14 -0
- package/dist/query/query-builder.d.ts.map +1 -1
- package/dist/query/query-builder.js +53 -3
- package/dist/query/query-builder.js.map +1 -1
- package/dist/query/union-builder.d.ts +7 -0
- package/dist/query/union-builder.d.ts.map +1 -1
- package/dist/query/union-builder.js +66 -0
- package/dist/query/union-builder.js.map +1 -1
- package/dist/schema/table-builder.d.ts +23 -0
- package/dist/schema/table-builder.d.ts.map +1 -1
- package/dist/schema/table-builder.js +10 -0
- package/dist/schema/table-builder.js.map +1 -1
- package/package.json +1 -1
|
@@ -40,6 +40,7 @@ const collation_builder_1 = require("../types/collation-builder");
|
|
|
40
40
|
const conditions_1 = require("../query/conditions");
|
|
41
41
|
const index_sql_1 = require("./index-sql");
|
|
42
42
|
const statistics_sql_1 = require("./statistics-sql");
|
|
43
|
+
const check_constraint_sql_1 = require("./check-constraint-sql");
|
|
43
44
|
const dbsetting_sql_1 = require("./dbsetting-sql");
|
|
44
45
|
const partition_sql_1 = require("./partition-sql");
|
|
45
46
|
/**
|
|
@@ -128,10 +129,10 @@ class DbSchemaManager {
|
|
|
128
129
|
}
|
|
129
130
|
for (const [enumName, enumDef] of enums.entries()) {
|
|
130
131
|
// Check if enum already exists
|
|
131
|
-
const checkSQL = `
|
|
132
|
-
SELECT EXISTS (
|
|
133
|
-
SELECT 1 FROM pg_type WHERE typname = $1
|
|
134
|
-
) as exists
|
|
132
|
+
const checkSQL = `
|
|
133
|
+
SELECT EXISTS (
|
|
134
|
+
SELECT 1 FROM pg_type WHERE typname = $1
|
|
135
|
+
) as exists
|
|
135
136
|
`;
|
|
136
137
|
const result = await this.client.query(checkSQL, [enumName]);
|
|
137
138
|
const exists = result.rows[0]?.exists;
|
|
@@ -162,10 +163,10 @@ class DbSchemaManager {
|
|
|
162
163
|
this.logger('Creating collations...\n');
|
|
163
164
|
}
|
|
164
165
|
for (const [collationName, collationDef] of collations.entries()) {
|
|
165
|
-
const checkSQL = `
|
|
166
|
-
SELECT EXISTS (
|
|
167
|
-
SELECT 1 FROM pg_collation WHERE collname = $1
|
|
168
|
-
) as exists
|
|
166
|
+
const checkSQL = `
|
|
167
|
+
SELECT EXISTS (
|
|
168
|
+
SELECT 1 FROM pg_collation WHERE collname = $1
|
|
169
|
+
) as exists
|
|
169
170
|
`;
|
|
170
171
|
const result = await this.client.query(checkSQL, [collationName]);
|
|
171
172
|
const exists = result.rows[0]?.exists;
|
|
@@ -200,13 +201,13 @@ class DbSchemaManager {
|
|
|
200
201
|
: `"${config.name}"`;
|
|
201
202
|
// Check if sequence already exists
|
|
202
203
|
const checkSQL = config.schema
|
|
203
|
-
? `SELECT EXISTS (
|
|
204
|
-
SELECT 1 FROM information_schema.sequences
|
|
205
|
-
WHERE sequence_schema = $1 AND sequence_name = $2
|
|
204
|
+
? `SELECT EXISTS (
|
|
205
|
+
SELECT 1 FROM information_schema.sequences
|
|
206
|
+
WHERE sequence_schema = $1 AND sequence_name = $2
|
|
206
207
|
) as exists`
|
|
207
|
-
: `SELECT EXISTS (
|
|
208
|
-
SELECT 1 FROM information_schema.sequences
|
|
209
|
-
WHERE sequence_name = $1 AND sequence_schema = 'public'
|
|
208
|
+
: `SELECT EXISTS (
|
|
209
|
+
SELECT 1 FROM information_schema.sequences
|
|
210
|
+
WHERE sequence_name = $1 AND sequence_schema = 'public'
|
|
210
211
|
) as exists`;
|
|
211
212
|
const checkParams = config.schema ? [config.schema, config.name] : [config.name];
|
|
212
213
|
const result = await this.client.query(checkSQL, checkParams);
|
|
@@ -347,10 +348,10 @@ class DbSchemaManager {
|
|
|
347
348
|
(0, partition_sql_1.validatePartitioningPrimaryKey)(tableSchema.partitioning, pkColumnNames, tableName);
|
|
348
349
|
partitionByClause = ` ${(0, partition_sql_1.buildPartitionByClause)(tableSchema.partitioning)}`;
|
|
349
350
|
}
|
|
350
|
-
const createTableSQL = `
|
|
351
|
-
CREATE TABLE IF NOT EXISTS ${qualifiedTableName} (
|
|
352
|
-
${columnDefs.join(',\n ')}
|
|
353
|
-
)${partitionByClause}
|
|
351
|
+
const createTableSQL = `
|
|
352
|
+
CREATE TABLE IF NOT EXISTS ${qualifiedTableName} (
|
|
353
|
+
${columnDefs.join(',\n ')}
|
|
354
|
+
)${partitionByClause}
|
|
354
355
|
`;
|
|
355
356
|
if (this.logQueries) {
|
|
356
357
|
this.logger(` Creating table ${qualifiedTableName}...`);
|
|
@@ -568,6 +569,11 @@ class DbSchemaManager {
|
|
|
568
569
|
for (const [tableName, tableSchema] of sortedTables) {
|
|
569
570
|
await this.createIndexes(tableName, tableSchema);
|
|
570
571
|
}
|
|
572
|
+
// Create CHECK constraints (existence-guarded — no ADD CONSTRAINT IF NOT
|
|
573
|
+
// EXISTS in PostgreSQL, and ensureCreated must stay idempotent)
|
|
574
|
+
for (const [tableName, tableSchema] of sortedTables) {
|
|
575
|
+
await this.createCheckConstraints(tableName, tableSchema);
|
|
576
|
+
}
|
|
571
577
|
// Create extended-statistics objects (after indexes so the ANALYZE they
|
|
572
578
|
// trigger also refreshes freshly indexed expression columns)
|
|
573
579
|
for (const [tableName, tableSchema] of sortedTables) {
|
|
@@ -608,6 +614,23 @@ class DbSchemaManager {
|
|
|
608
614
|
await this.executeCreateStatistics(tableName, spec, tableSchema.schema);
|
|
609
615
|
}
|
|
610
616
|
}
|
|
617
|
+
/**
|
|
618
|
+
* Create the declared CHECK constraints of a table that are missing on it.
|
|
619
|
+
* Reconciled by NAME (see check-constraint-sql.ts) — existing same-named
|
|
620
|
+
* constraints are left untouched, so the pass is idempotent.
|
|
621
|
+
*/
|
|
622
|
+
async createCheckConstraints(tableName, tableSchema) {
|
|
623
|
+
const checkConstraints = tableSchema.checkConstraints || [];
|
|
624
|
+
if (checkConstraints.length === 0) {
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
const existing = await this.getExistingCheckConstraints(tableName, tableSchema.schema);
|
|
628
|
+
for (const spec of checkConstraints) {
|
|
629
|
+
if (!existing.includes(spec.name)) {
|
|
630
|
+
await this.executeCreateCheckConstraint(tableName, spec, tableSchema.schema);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
611
634
|
/**
|
|
612
635
|
* Converge declared database-level settings (`model.hasDbSetting`) — apply
|
|
613
636
|
* every declaration that is missing from `pg_db_role_setting` or whose
|
|
@@ -640,11 +663,11 @@ class DbSchemaManager {
|
|
|
640
663
|
* keyed by lower-cased parameter name (GUC names are case-insensitive).
|
|
641
664
|
*/
|
|
642
665
|
async getExistingDatabaseSettings() {
|
|
643
|
-
const result = await this.client.query(`
|
|
644
|
-
SELECT entry
|
|
645
|
-
FROM pg_db_role_setting, LATERAL unnest(setconfig) AS entry
|
|
646
|
-
WHERE setrole = 0
|
|
647
|
-
AND setdatabase = (SELECT oid FROM pg_database WHERE datname = current_database())
|
|
666
|
+
const result = await this.client.query(`
|
|
667
|
+
SELECT entry
|
|
668
|
+
FROM pg_db_role_setting, LATERAL unnest(setconfig) AS entry
|
|
669
|
+
WHERE setrole = 0
|
|
670
|
+
AND setdatabase = (SELECT oid FROM pg_database WHERE datname = current_database())
|
|
648
671
|
`);
|
|
649
672
|
const settings = new Map();
|
|
650
673
|
for (const row of result.rows) {
|
|
@@ -750,10 +773,10 @@ class DbSchemaManager {
|
|
|
750
773
|
}
|
|
751
774
|
// Add schema creation operations if needed
|
|
752
775
|
for (const schemaName of modelSchemas) {
|
|
753
|
-
const result = await this.client.query(`
|
|
754
|
-
SELECT EXISTS (
|
|
755
|
-
SELECT 1 FROM information_schema.schemata WHERE schema_name = $1
|
|
756
|
-
) as exists
|
|
776
|
+
const result = await this.client.query(`
|
|
777
|
+
SELECT EXISTS (
|
|
778
|
+
SELECT 1 FROM information_schema.schemata WHERE schema_name = $1
|
|
779
|
+
) as exists
|
|
757
780
|
`, [schemaName]);
|
|
758
781
|
if (!result.rows[0]?.exists) {
|
|
759
782
|
operations.push({ type: 'create_schema', schemaName });
|
|
@@ -762,10 +785,10 @@ class DbSchemaManager {
|
|
|
762
785
|
// Check collations
|
|
763
786
|
const modelCollations = collation_builder_1.CollationRegistry.getAll();
|
|
764
787
|
for (const [collationName, collationDef] of modelCollations.entries()) {
|
|
765
|
-
const result = await this.client.query(`
|
|
766
|
-
SELECT EXISTS (
|
|
767
|
-
SELECT 1 FROM pg_collation WHERE collname = $1
|
|
768
|
-
) as exists
|
|
788
|
+
const result = await this.client.query(`
|
|
789
|
+
SELECT EXISTS (
|
|
790
|
+
SELECT 1 FROM pg_collation WHERE collname = $1
|
|
791
|
+
) as exists
|
|
769
792
|
`, [collationName]);
|
|
770
793
|
if (!result.rows[0]?.exists) {
|
|
771
794
|
operations.push({ type: 'create_collation', collation: collationDef });
|
|
@@ -774,11 +797,11 @@ class DbSchemaManager {
|
|
|
774
797
|
// Check enums
|
|
775
798
|
const modelEnums = enum_builder_1.EnumTypeRegistry.getAll();
|
|
776
799
|
for (const [enumName, enumDef] of modelEnums.entries()) {
|
|
777
|
-
const existing = await this.client.query(`
|
|
778
|
-
SELECT e.enumlabel
|
|
779
|
-
FROM pg_type t
|
|
780
|
-
JOIN pg_enum e ON e.enumtypid = t.oid
|
|
781
|
-
WHERE t.typname = $1
|
|
800
|
+
const existing = await this.client.query(`
|
|
801
|
+
SELECT e.enumlabel
|
|
802
|
+
FROM pg_type t
|
|
803
|
+
JOIN pg_enum e ON e.enumtypid = t.oid
|
|
804
|
+
WHERE t.typname = $1
|
|
782
805
|
`, [enumName]);
|
|
783
806
|
if (existing.rows.length === 0) {
|
|
784
807
|
// Enum type doesn't exist yet — create it with the full value set.
|
|
@@ -799,13 +822,13 @@ class DbSchemaManager {
|
|
|
799
822
|
// Check sequences
|
|
800
823
|
for (const [_, config] of this.sequenceRegistry.entries()) {
|
|
801
824
|
const checkSQL = config.schema
|
|
802
|
-
? `SELECT EXISTS (
|
|
803
|
-
SELECT 1 FROM information_schema.sequences
|
|
804
|
-
WHERE sequence_schema = $1 AND sequence_name = $2
|
|
825
|
+
? `SELECT EXISTS (
|
|
826
|
+
SELECT 1 FROM information_schema.sequences
|
|
827
|
+
WHERE sequence_schema = $1 AND sequence_name = $2
|
|
805
828
|
) as exists`
|
|
806
|
-
: `SELECT EXISTS (
|
|
807
|
-
SELECT 1 FROM information_schema.sequences
|
|
808
|
-
WHERE sequence_name = $1 AND sequence_schema = 'public'
|
|
829
|
+
: `SELECT EXISTS (
|
|
830
|
+
SELECT 1 FROM information_schema.sequences
|
|
831
|
+
WHERE sequence_name = $1 AND sequence_schema = 'public'
|
|
809
832
|
) as exists`;
|
|
810
833
|
const checkParams = config.schema ? [config.schema, config.name] : [config.name];
|
|
811
834
|
const result = await this.client.query(checkSQL, checkParams);
|
|
@@ -921,6 +944,22 @@ class DbSchemaManager {
|
|
|
921
944
|
});
|
|
922
945
|
}
|
|
923
946
|
}
|
|
947
|
+
// Compare CHECK constraints — by NAME only (create missing; never
|
|
948
|
+
// drop or rebuild — rename a constraint to change its definition).
|
|
949
|
+
if ((schema.checkConstraints || []).length > 0) {
|
|
950
|
+
const existingCheckConstraints = await this.getExistingCheckConstraints(tableName, schema.schema);
|
|
951
|
+
for (const modelCheck of schema.checkConstraints || []) {
|
|
952
|
+
if (!existingCheckConstraints.includes(modelCheck.name)) {
|
|
953
|
+
operations.push({
|
|
954
|
+
type: 'create_check_constraint',
|
|
955
|
+
tableName,
|
|
956
|
+
schema: schema.schema,
|
|
957
|
+
constraintName: modelCheck.name,
|
|
958
|
+
expression: modelCheck.expression,
|
|
959
|
+
});
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
}
|
|
924
963
|
// Compare foreign key constraints
|
|
925
964
|
const existingForeignKeys = await this.getExistingForeignKeys(tableName, schema.schema);
|
|
926
965
|
const modelForeignKeys = schema.foreignKeys || [];
|
|
@@ -1046,6 +1085,16 @@ class DbSchemaManager {
|
|
|
1046
1085
|
nullsNotDistinct: index.nullsNotDistinct,
|
|
1047
1086
|
});
|
|
1048
1087
|
}
|
|
1088
|
+
// Add CHECK constraints for newly created tables to phase 3
|
|
1089
|
+
for (const check of schema.checkConstraints || []) {
|
|
1090
|
+
phase3Ops.push({
|
|
1091
|
+
type: 'create_check_constraint',
|
|
1092
|
+
tableName,
|
|
1093
|
+
schema: schema.schema,
|
|
1094
|
+
constraintName: check.name,
|
|
1095
|
+
expression: check.expression,
|
|
1096
|
+
});
|
|
1097
|
+
}
|
|
1049
1098
|
}
|
|
1050
1099
|
}
|
|
1051
1100
|
const totalOps = phase1Ops.length + phase2Ops.length + phase3Ops.length;
|
|
@@ -1169,6 +1218,12 @@ class DbSchemaManager {
|
|
|
1169
1218
|
kinds: operation.kinds,
|
|
1170
1219
|
}, operation.schema);
|
|
1171
1220
|
break;
|
|
1221
|
+
case 'create_check_constraint':
|
|
1222
|
+
await this.executeCreateCheckConstraint(operation.tableName, {
|
|
1223
|
+
name: operation.constraintName,
|
|
1224
|
+
expression: operation.expression,
|
|
1225
|
+
}, operation.schema);
|
|
1226
|
+
break;
|
|
1172
1227
|
case 'set_database_setting':
|
|
1173
1228
|
await this.executeSetDatabaseSetting(operation);
|
|
1174
1229
|
break;
|
|
@@ -1447,16 +1502,55 @@ class DbSchemaManager {
|
|
|
1447
1502
|
* the table's namespace so the lookup is independent of search_path.
|
|
1448
1503
|
*/
|
|
1449
1504
|
async getExistingStatistics(tableName, schemaName) {
|
|
1450
|
-
const result = await this.client.query(`
|
|
1451
|
-
SELECT s.stxname
|
|
1452
|
-
FROM pg_statistic_ext s
|
|
1453
|
-
JOIN pg_class c ON c.oid = s.stxrelid
|
|
1454
|
-
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
1455
|
-
WHERE n.nspname = $1
|
|
1456
|
-
AND c.relname = $2
|
|
1505
|
+
const result = await this.client.query(`
|
|
1506
|
+
SELECT s.stxname
|
|
1507
|
+
FROM pg_statistic_ext s
|
|
1508
|
+
JOIN pg_class c ON c.oid = s.stxrelid
|
|
1509
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
1510
|
+
WHERE n.nspname = $1
|
|
1511
|
+
AND c.relname = $2
|
|
1457
1512
|
`, [schemaName || 'public', tableName]);
|
|
1458
1513
|
return result.rows.map((row) => row.stxname);
|
|
1459
1514
|
}
|
|
1515
|
+
/**
|
|
1516
|
+
* Add a CHECK constraint, guarded by a fresh existence lookup (PostgreSQL
|
|
1517
|
+
* has no ADD CONSTRAINT IF NOT EXISTS) so replays stay idempotent.
|
|
1518
|
+
* PostgreSQL validates existing rows as part of the ALTER — a table holding
|
|
1519
|
+
* violating rows fails the statement loudly; backfill data first.
|
|
1520
|
+
*/
|
|
1521
|
+
async executeCreateCheckConstraint(tableName, spec, schema) {
|
|
1522
|
+
if (!spec.expression || spec.expression.trim().length === 0) {
|
|
1523
|
+
throw new Error(`Check constraint "${spec.name}" has no expression. Pass the raw SQL boolean expression as the second argument of hasCheckConstraint().`);
|
|
1524
|
+
}
|
|
1525
|
+
const existing = await this.getExistingCheckConstraints(tableName, schema);
|
|
1526
|
+
if (existing.includes(spec.name)) {
|
|
1527
|
+
return;
|
|
1528
|
+
}
|
|
1529
|
+
const qualifiedTableName = this.getQualifiedTableName(tableName, schema);
|
|
1530
|
+
this.logger(` Adding check constraint "${spec.name}" to ${qualifiedTableName}...`);
|
|
1531
|
+
await this.client.query((0, check_constraint_sql_1.buildAddCheckConstraintStatement)(spec, qualifiedTableName));
|
|
1532
|
+
this.logger(` ✓ Check constraint "${spec.name}" added
|
|
1533
|
+
`);
|
|
1534
|
+
}
|
|
1535
|
+
/**
|
|
1536
|
+
* Names of the CHECK constraints (`contype = 'c'`) defined on a table,
|
|
1537
|
+
* resolved via the table's namespace so the lookup is independent of
|
|
1538
|
+
* search_path. Includes column NOT NULL-style checks PostgreSQL stores as
|
|
1539
|
+
* table constraints, which is fine — reconciliation only tests declared
|
|
1540
|
+
* names for membership.
|
|
1541
|
+
*/
|
|
1542
|
+
async getExistingCheckConstraints(tableName, schemaName) {
|
|
1543
|
+
const result = await this.client.query(`
|
|
1544
|
+
SELECT con.conname
|
|
1545
|
+
FROM pg_constraint con
|
|
1546
|
+
JOIN pg_class c ON c.oid = con.conrelid
|
|
1547
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
1548
|
+
WHERE n.nspname = $1
|
|
1549
|
+
AND c.relname = $2
|
|
1550
|
+
AND con.contype = 'c'
|
|
1551
|
+
`, [schemaName || 'public', tableName]);
|
|
1552
|
+
return result.rows.map((row) => row.conname);
|
|
1553
|
+
}
|
|
1460
1554
|
/**
|
|
1461
1555
|
* Execute create foreign key
|
|
1462
1556
|
*/
|
|
@@ -1499,10 +1593,10 @@ class DbSchemaManager {
|
|
|
1499
1593
|
}
|
|
1500
1594
|
const tables = new Map();
|
|
1501
1595
|
for (const schemaName of schemas) {
|
|
1502
|
-
const result = await this.client.query(`
|
|
1503
|
-
SELECT table_name
|
|
1504
|
-
FROM information_schema.tables
|
|
1505
|
-
WHERE table_schema = $1 AND table_type = 'BASE TABLE'
|
|
1596
|
+
const result = await this.client.query(`
|
|
1597
|
+
SELECT table_name
|
|
1598
|
+
FROM information_schema.tables
|
|
1599
|
+
WHERE table_schema = $1 AND table_type = 'BASE TABLE'
|
|
1506
1600
|
`, [schemaName]);
|
|
1507
1601
|
for (const row of result.rows) {
|
|
1508
1602
|
// Use composite key for non-public schemas to avoid collisions
|
|
@@ -1516,20 +1610,20 @@ class DbSchemaManager {
|
|
|
1516
1610
|
* Get all columns for a table
|
|
1517
1611
|
*/
|
|
1518
1612
|
async getExistingColumns(tableName, schemaName) {
|
|
1519
|
-
const result = await this.client.query(`
|
|
1520
|
-
SELECT
|
|
1521
|
-
column_name,
|
|
1522
|
-
data_type,
|
|
1523
|
-
udt_name,
|
|
1524
|
-
character_maximum_length,
|
|
1525
|
-
numeric_precision,
|
|
1526
|
-
numeric_scale,
|
|
1527
|
-
is_nullable,
|
|
1528
|
-
column_default,
|
|
1529
|
-
collation_name
|
|
1530
|
-
FROM information_schema.columns
|
|
1531
|
-
WHERE table_schema = $1 AND table_name = $2
|
|
1532
|
-
ORDER BY ordinal_position
|
|
1613
|
+
const result = await this.client.query(`
|
|
1614
|
+
SELECT
|
|
1615
|
+
column_name,
|
|
1616
|
+
data_type,
|
|
1617
|
+
udt_name,
|
|
1618
|
+
character_maximum_length,
|
|
1619
|
+
numeric_precision,
|
|
1620
|
+
numeric_scale,
|
|
1621
|
+
is_nullable,
|
|
1622
|
+
column_default,
|
|
1623
|
+
collation_name
|
|
1624
|
+
FROM information_schema.columns
|
|
1625
|
+
WHERE table_schema = $1 AND table_name = $2
|
|
1626
|
+
ORDER BY ordinal_position
|
|
1533
1627
|
`, [schemaName || 'public', tableName]);
|
|
1534
1628
|
const columns = new Map();
|
|
1535
1629
|
for (const row of result.rows) {
|
|
@@ -1544,24 +1638,24 @@ class DbSchemaManager {
|
|
|
1544
1638
|
// `pg_get_indexdef(oid, 0, true)` yields the canonical, pretty-printed
|
|
1545
1639
|
// CREATE INDEX statement (no CONCURRENTLY / IF NOT EXISTS, redundant parens
|
|
1546
1640
|
// stripped), which the signature comparison parses to detect changes.
|
|
1547
|
-
const result = await this.client.query(`
|
|
1548
|
-
SELECT
|
|
1549
|
-
i.relname as index_name,
|
|
1550
|
-
pg_get_indexdef(i.oid, 0, true) as canonical_def,
|
|
1551
|
-
ARRAY(
|
|
1552
|
-
SELECT a.attname
|
|
1553
|
-
FROM unnest(ix.indkey) WITH ORDINALITY k(attnum, ord)
|
|
1554
|
-
JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = k.attnum
|
|
1555
|
-
ORDER BY k.ord
|
|
1556
|
-
) as column_names
|
|
1557
|
-
FROM pg_index ix
|
|
1558
|
-
JOIN pg_class t ON t.oid = ix.indrelid
|
|
1559
|
-
JOIN pg_class i ON i.oid = ix.indexrelid
|
|
1560
|
-
JOIN pg_namespace n ON n.oid = t.relnamespace
|
|
1561
|
-
WHERE
|
|
1562
|
-
n.nspname = $1
|
|
1563
|
-
AND t.relname = $2
|
|
1564
|
-
AND NOT ix.indisprimary
|
|
1641
|
+
const result = await this.client.query(`
|
|
1642
|
+
SELECT
|
|
1643
|
+
i.relname as index_name,
|
|
1644
|
+
pg_get_indexdef(i.oid, 0, true) as canonical_def,
|
|
1645
|
+
ARRAY(
|
|
1646
|
+
SELECT a.attname
|
|
1647
|
+
FROM unnest(ix.indkey) WITH ORDINALITY k(attnum, ord)
|
|
1648
|
+
JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = k.attnum
|
|
1649
|
+
ORDER BY k.ord
|
|
1650
|
+
) as column_names
|
|
1651
|
+
FROM pg_index ix
|
|
1652
|
+
JOIN pg_class t ON t.oid = ix.indrelid
|
|
1653
|
+
JOIN pg_class i ON i.oid = ix.indexrelid
|
|
1654
|
+
JOIN pg_namespace n ON n.oid = t.relnamespace
|
|
1655
|
+
WHERE
|
|
1656
|
+
n.nspname = $1
|
|
1657
|
+
AND t.relname = $2
|
|
1658
|
+
AND NOT ix.indisprimary
|
|
1565
1659
|
`, [schemaName || 'public', tableName]);
|
|
1566
1660
|
return result.rows.map(row => ({
|
|
1567
1661
|
index_name: row.index_name,
|
|
@@ -1641,29 +1735,29 @@ class DbSchemaManager {
|
|
|
1641
1735
|
* Get all foreign key constraints for a table
|
|
1642
1736
|
*/
|
|
1643
1737
|
async getExistingForeignKeys(tableName, schemaName) {
|
|
1644
|
-
const result = await this.client.query(`
|
|
1645
|
-
SELECT
|
|
1646
|
-
tc.constraint_name,
|
|
1647
|
-
array_agg(kcu.column_name ORDER BY kcu.ordinal_position) as column_names,
|
|
1648
|
-
ccu.table_name AS referenced_table,
|
|
1649
|
-
array_agg(ccu.column_name ORDER BY kcu.ordinal_position) as referenced_column_names,
|
|
1650
|
-
rc.delete_rule as on_delete,
|
|
1651
|
-
rc.update_rule as on_update
|
|
1652
|
-
FROM information_schema.table_constraints AS tc
|
|
1653
|
-
JOIN information_schema.key_column_usage AS kcu
|
|
1654
|
-
ON tc.constraint_name = kcu.constraint_name
|
|
1655
|
-
AND tc.table_schema = kcu.table_schema
|
|
1656
|
-
JOIN information_schema.constraint_column_usage AS ccu
|
|
1657
|
-
ON ccu.constraint_name = tc.constraint_name
|
|
1658
|
-
AND ccu.table_schema = tc.table_schema
|
|
1659
|
-
JOIN information_schema.referential_constraints AS rc
|
|
1660
|
-
ON rc.constraint_name = tc.constraint_name
|
|
1661
|
-
AND rc.constraint_schema = tc.table_schema
|
|
1662
|
-
WHERE
|
|
1663
|
-
tc.table_schema = $1
|
|
1664
|
-
AND tc.table_name = $2
|
|
1665
|
-
AND tc.constraint_type = 'FOREIGN KEY'
|
|
1666
|
-
GROUP BY tc.constraint_name, ccu.table_name, rc.delete_rule, rc.update_rule
|
|
1738
|
+
const result = await this.client.query(`
|
|
1739
|
+
SELECT
|
|
1740
|
+
tc.constraint_name,
|
|
1741
|
+
array_agg(kcu.column_name ORDER BY kcu.ordinal_position) as column_names,
|
|
1742
|
+
ccu.table_name AS referenced_table,
|
|
1743
|
+
array_agg(ccu.column_name ORDER BY kcu.ordinal_position) as referenced_column_names,
|
|
1744
|
+
rc.delete_rule as on_delete,
|
|
1745
|
+
rc.update_rule as on_update
|
|
1746
|
+
FROM information_schema.table_constraints AS tc
|
|
1747
|
+
JOIN information_schema.key_column_usage AS kcu
|
|
1748
|
+
ON tc.constraint_name = kcu.constraint_name
|
|
1749
|
+
AND tc.table_schema = kcu.table_schema
|
|
1750
|
+
JOIN information_schema.constraint_column_usage AS ccu
|
|
1751
|
+
ON ccu.constraint_name = tc.constraint_name
|
|
1752
|
+
AND ccu.table_schema = tc.table_schema
|
|
1753
|
+
JOIN information_schema.referential_constraints AS rc
|
|
1754
|
+
ON rc.constraint_name = tc.constraint_name
|
|
1755
|
+
AND rc.constraint_schema = tc.table_schema
|
|
1756
|
+
WHERE
|
|
1757
|
+
tc.table_schema = $1
|
|
1758
|
+
AND tc.table_name = $2
|
|
1759
|
+
AND tc.constraint_type = 'FOREIGN KEY'
|
|
1760
|
+
GROUP BY tc.constraint_name, ccu.table_name, rc.delete_rule, rc.update_rule
|
|
1667
1761
|
`, [schemaName || 'public', tableName]);
|
|
1668
1762
|
return result.rows.map(row => ({
|
|
1669
1763
|
constraint_name: row.constraint_name,
|
|
@@ -1894,6 +1988,8 @@ class DbSchemaManager {
|
|
|
1894
1988
|
const kindsDesc = operation.kinds && operation.kinds.length > 0 ? ` (${operation.kinds.join(', ')})` : '';
|
|
1895
1989
|
return `Create statistics "${operation.statisticsName}"${kindsDesc} on "${operation.tableName}" (${operation.expressions.join(', ')})`;
|
|
1896
1990
|
}
|
|
1991
|
+
case 'create_check_constraint':
|
|
1992
|
+
return `Add check constraint "${operation.constraintName}" on "${operation.tableName}" CHECK (${operation.expression})`;
|
|
1897
1993
|
case 'set_database_setting':
|
|
1898
1994
|
return `Set database parameter ${operation.name} = ${operation.value} (ALTER DATABASE ... SET, persists for new sessions)`;
|
|
1899
1995
|
case 'create_foreign_key':
|
|
@@ -1969,15 +2065,15 @@ exports.DbSchemaManager = DbSchemaManager;
|
|
|
1969
2065
|
* `search_path`, so an unqualified `'unaccent'::regdictionary` would fail with
|
|
1970
2066
|
* "text search dictionary unaccent does not exist" when the index is built.
|
|
1971
2067
|
*/
|
|
1972
|
-
DbSchemaManager.SEARCH_NORMALIZE_FUNCTION_SQL = `
|
|
1973
|
-
CREATE OR REPLACE FUNCTION public.search_normalize(value text)
|
|
1974
|
-
RETURNS text
|
|
1975
|
-
LANGUAGE sql
|
|
1976
|
-
IMMUTABLE
|
|
1977
|
-
PARALLEL SAFE
|
|
1978
|
-
RETURNS NULL ON NULL INPUT
|
|
1979
|
-
AS $$
|
|
1980
|
-
SELECT lower(public.unaccent('public.unaccent', value))
|
|
2068
|
+
DbSchemaManager.SEARCH_NORMALIZE_FUNCTION_SQL = `
|
|
2069
|
+
CREATE OR REPLACE FUNCTION public.search_normalize(value text)
|
|
2070
|
+
RETURNS text
|
|
2071
|
+
LANGUAGE sql
|
|
2072
|
+
IMMUTABLE
|
|
2073
|
+
PARALLEL SAFE
|
|
2074
|
+
RETURNS NULL ON NULL INPUT
|
|
2075
|
+
AS $$
|
|
2076
|
+
SELECT lower(public.unaccent('public.unaccent', value))
|
|
1981
2077
|
$$`;
|
|
1982
2078
|
/**
|
|
1983
2079
|
* Execute create index
|