pgterra 0.2.12 → 0.2.14
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/index.js +137 -84
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -21475,6 +21475,7 @@ function parseCreateIndex(stmt) {
|
|
|
21475
21475
|
const schema = stmt.relation?.schemaname || undefined;
|
|
21476
21476
|
const indexParams = stmt.indexParams || [];
|
|
21477
21477
|
const columns = [];
|
|
21478
|
+
let opclasses;
|
|
21478
21479
|
let expression;
|
|
21479
21480
|
if (indexParams.length === 1 && indexParams[0].IndexElem?.expr) {
|
|
21480
21481
|
expression = import_pgsql_parser3.deparseSync([indexParams[0].IndexElem.expr]).trim();
|
|
@@ -21484,6 +21485,14 @@ function parseCreateIndex(stmt) {
|
|
|
21484
21485
|
const colName = param.IndexElem.name;
|
|
21485
21486
|
if (colName) {
|
|
21486
21487
|
columns.push(colName);
|
|
21488
|
+
if (param.IndexElem.opclass && param.IndexElem.opclass.length > 0) {
|
|
21489
|
+
const opclassName = param.IndexElem.opclass.map((node) => node.String?.sval).filter(Boolean).join(".");
|
|
21490
|
+
if (opclassName) {
|
|
21491
|
+
if (!opclasses)
|
|
21492
|
+
opclasses = {};
|
|
21493
|
+
opclasses[colName] = opclassName;
|
|
21494
|
+
}
|
|
21495
|
+
}
|
|
21487
21496
|
} else if (param.IndexElem.expr) {
|
|
21488
21497
|
expression = import_pgsql_parser3.deparseSync([param.IndexElem.expr]).trim();
|
|
21489
21498
|
break;
|
|
@@ -21553,6 +21562,7 @@ function parseCreateIndex(stmt) {
|
|
|
21553
21562
|
tableName,
|
|
21554
21563
|
schema,
|
|
21555
21564
|
columns,
|
|
21565
|
+
opclasses,
|
|
21556
21566
|
type,
|
|
21557
21567
|
unique,
|
|
21558
21568
|
concurrent,
|
|
@@ -22846,6 +22856,22 @@ class DatabaseInspector {
|
|
|
22846
22856
|
-- Expression index - no simple column names
|
|
22847
22857
|
ARRAY[]::text[]
|
|
22848
22858
|
END as column_names,
|
|
22859
|
+
-- Get operator class names for each column (non-default only)
|
|
22860
|
+
CASE
|
|
22861
|
+
WHEN ix.indexprs IS NULL THEN
|
|
22862
|
+
ARRAY(
|
|
22863
|
+
SELECT
|
|
22864
|
+
CASE
|
|
22865
|
+
WHEN opc.opcdefault THEN NULL
|
|
22866
|
+
ELSE opc.opcname
|
|
22867
|
+
END
|
|
22868
|
+
FROM unnest(ix.indclass) WITH ORDINALITY AS u(opcoid, ord)
|
|
22869
|
+
JOIN pg_opclass opc ON opc.oid = u.opcoid
|
|
22870
|
+
ORDER BY u.ord
|
|
22871
|
+
)
|
|
22872
|
+
ELSE
|
|
22873
|
+
ARRAY[]::text[]
|
|
22874
|
+
END as opclass_names,
|
|
22849
22875
|
CASE
|
|
22850
22876
|
WHEN ix.indpred IS NOT NULL THEN
|
|
22851
22877
|
regexp_replace(
|
|
@@ -22874,19 +22900,32 @@ class DatabaseInspector {
|
|
|
22874
22900
|
)
|
|
22875
22901
|
ORDER BY i.indexname
|
|
22876
22902
|
`, [tableName, tableSchema]);
|
|
22877
|
-
return result.rows.map((row) =>
|
|
22878
|
-
|
|
22879
|
-
|
|
22880
|
-
|
|
22881
|
-
columns
|
|
22882
|
-
|
|
22883
|
-
|
|
22884
|
-
|
|
22885
|
-
|
|
22886
|
-
|
|
22887
|
-
|
|
22888
|
-
|
|
22889
|
-
|
|
22903
|
+
return result.rows.map((row) => {
|
|
22904
|
+
const columns = row.column_names || [];
|
|
22905
|
+
const opclassNames = row.opclass_names || [];
|
|
22906
|
+
let opclasses;
|
|
22907
|
+
for (let i = 0;i < columns.length; i++) {
|
|
22908
|
+
if (opclassNames[i]) {
|
|
22909
|
+
if (!opclasses)
|
|
22910
|
+
opclasses = {};
|
|
22911
|
+
opclasses[columns[i]] = opclassNames[i];
|
|
22912
|
+
}
|
|
22913
|
+
}
|
|
22914
|
+
return {
|
|
22915
|
+
name: row.index_name,
|
|
22916
|
+
tableName: row.table_name,
|
|
22917
|
+
schema: row.table_schema,
|
|
22918
|
+
columns,
|
|
22919
|
+
opclasses,
|
|
22920
|
+
type: this.mapPostgreSQLIndexType(row.access_method),
|
|
22921
|
+
unique: row.is_unique,
|
|
22922
|
+
concurrent: false,
|
|
22923
|
+
where: row.where_clause || undefined,
|
|
22924
|
+
expression: row.has_expressions ? row.expression_def : undefined,
|
|
22925
|
+
storageParameters: this.parseStorageOptions(row.storage_options),
|
|
22926
|
+
tablespace: row.tablespace_name || undefined
|
|
22927
|
+
};
|
|
22928
|
+
});
|
|
22890
22929
|
}
|
|
22891
22930
|
parseStorageOptions(reloptions) {
|
|
22892
22931
|
if (!reloptions || !Array.isArray(reloptions) || reloptions.length === 0) {
|
|
@@ -23688,6 +23727,30 @@ function normalizeDefault(value) {
|
|
|
23688
23727
|
normalized = normalized.replace(/::[a-z_]+(\s+[a-z_]+)*(\([^)]*\))?$/i, "");
|
|
23689
23728
|
return normalized.trim();
|
|
23690
23729
|
}
|
|
23730
|
+
function normalizeExpression(expr) {
|
|
23731
|
+
let normalized = expr.replace(/\s+/g, " ").trim().replace(/::"?[a-z_]+"?(\([^)]*\))?/gi, "").replace(/\(([a-z_][a-z0-9_]*)\)/gi, "$1");
|
|
23732
|
+
while (/^\(.*\)$/.test(normalized)) {
|
|
23733
|
+
const inner = normalized.slice(1, -1);
|
|
23734
|
+
let depth = 0;
|
|
23735
|
+
let balanced = true;
|
|
23736
|
+
for (const char of inner) {
|
|
23737
|
+
if (char === "(")
|
|
23738
|
+
depth++;
|
|
23739
|
+
if (char === ")")
|
|
23740
|
+
depth--;
|
|
23741
|
+
if (depth < 0) {
|
|
23742
|
+
balanced = false;
|
|
23743
|
+
break;
|
|
23744
|
+
}
|
|
23745
|
+
}
|
|
23746
|
+
if (balanced && depth === 0) {
|
|
23747
|
+
normalized = inner.trim();
|
|
23748
|
+
} else {
|
|
23749
|
+
break;
|
|
23750
|
+
}
|
|
23751
|
+
}
|
|
23752
|
+
return normalized.replace(/\s+/g, " ").trim();
|
|
23753
|
+
}
|
|
23691
23754
|
function columnsAreDifferent(desired, current) {
|
|
23692
23755
|
const normalizedDesiredType = normalizeType(desired.type);
|
|
23693
23756
|
const normalizedCurrentType = normalizeType(current.type);
|
|
@@ -23718,7 +23781,7 @@ function columnsAreDifferent(desired, current) {
|
|
|
23718
23781
|
if (!desired.generated || !current.generated) {
|
|
23719
23782
|
return true;
|
|
23720
23783
|
}
|
|
23721
|
-
if (desired.generated.always !== current.generated.always || desired.generated.stored !== current.generated.stored || desired.generated.expression !== current.generated.expression) {
|
|
23784
|
+
if (desired.generated.always !== current.generated.always || desired.generated.stored !== current.generated.stored || normalizeExpression(desired.generated.expression) !== normalizeExpression(current.generated.expression)) {
|
|
23722
23785
|
return true;
|
|
23723
23786
|
}
|
|
23724
23787
|
}
|
|
@@ -24419,7 +24482,7 @@ class SchemaDiffer {
|
|
|
24419
24482
|
}
|
|
24420
24483
|
generateColumnModificationStatements(tableName, desiredColumn, currentColumn) {
|
|
24421
24484
|
const statements = [];
|
|
24422
|
-
const generatedChanging = (desiredColumn.generated || currentColumn.generated) && (!desiredColumn.generated || !currentColumn.generated || desiredColumn.generated.expression !== currentColumn.generated.expression || desiredColumn.generated.always !== currentColumn.generated.always || desiredColumn.generated.stored !== currentColumn.generated.stored);
|
|
24485
|
+
const generatedChanging = (desiredColumn.generated || currentColumn.generated) && (!desiredColumn.generated || !currentColumn.generated || normalizeExpression(desiredColumn.generated.expression) !== normalizeExpression(currentColumn.generated.expression) || desiredColumn.generated.always !== currentColumn.generated.always || desiredColumn.generated.stored !== currentColumn.generated.stored);
|
|
24423
24486
|
if (generatedChanging) {
|
|
24424
24487
|
const dropSql = new SQLBuilder().p("ALTER TABLE").p(tableName).p("DROP COLUMN").ident(desiredColumn.name).p(";").build();
|
|
24425
24488
|
statements.push(dropSql);
|
|
@@ -24624,6 +24687,16 @@ class SchemaDiffer {
|
|
|
24624
24687
|
return false;
|
|
24625
24688
|
if (index1.tablespace !== index2.tablespace)
|
|
24626
24689
|
return false;
|
|
24690
|
+
const opclasses1 = index1.opclasses || {};
|
|
24691
|
+
const opclasses2 = index2.opclasses || {};
|
|
24692
|
+
const opKeys1 = Object.keys(opclasses1);
|
|
24693
|
+
const opKeys2 = Object.keys(opclasses2);
|
|
24694
|
+
if (opKeys1.length !== opKeys2.length)
|
|
24695
|
+
return false;
|
|
24696
|
+
for (const key of opKeys1) {
|
|
24697
|
+
if (opclasses1[key] !== opclasses2[key])
|
|
24698
|
+
return false;
|
|
24699
|
+
}
|
|
24627
24700
|
const params1 = index1.storageParameters || {};
|
|
24628
24701
|
const params2 = index2.storageParameters || {};
|
|
24629
24702
|
const keys1 = Object.keys(params1);
|
|
@@ -24669,7 +24742,11 @@ class SchemaDiffer {
|
|
|
24669
24742
|
if (index.expression) {
|
|
24670
24743
|
builder.p(`(${index.expression})`);
|
|
24671
24744
|
} else {
|
|
24672
|
-
const quotedColumns = index.columns.map((col) =>
|
|
24745
|
+
const quotedColumns = index.columns.map((col) => {
|
|
24746
|
+
const quoted = `"${col.replace(/"/g, '""')}"`;
|
|
24747
|
+
const opclass = index.opclasses?.[col];
|
|
24748
|
+
return opclass ? `${quoted} ${opclass}` : quoted;
|
|
24749
|
+
}).join(", ");
|
|
24673
24750
|
builder.p(`(${quotedColumns})`);
|
|
24674
24751
|
}
|
|
24675
24752
|
if (index.where) {
|
|
@@ -24699,9 +24776,9 @@ class SchemaDiffer {
|
|
|
24699
24776
|
}
|
|
24700
24777
|
generateCheckConstraintStatements(tableName, desiredConstraints, currentConstraints) {
|
|
24701
24778
|
const statements = [];
|
|
24702
|
-
const
|
|
24703
|
-
const currentMap = new Map(currentConstraints.map((c) => [
|
|
24704
|
-
const desiredMap = new Map(desiredConstraints.map((c) => [
|
|
24779
|
+
const normalizeExpression2 = (expr) => expr.replace(/\s+/g, " ").trim();
|
|
24780
|
+
const currentMap = new Map(currentConstraints.map((c) => [normalizeExpression2(c.expression), c]));
|
|
24781
|
+
const desiredMap = new Map(desiredConstraints.map((c) => [normalizeExpression2(c.expression), c]));
|
|
24705
24782
|
for (const [key, constraint] of currentMap) {
|
|
24706
24783
|
if (!desiredMap.has(key)) {
|
|
24707
24784
|
if (constraint.name) {
|
|
@@ -24822,7 +24899,7 @@ class SchemaDiffer {
|
|
|
24822
24899
|
return alterations;
|
|
24823
24900
|
}
|
|
24824
24901
|
collectColumnModificationAlterations(desiredColumn, currentColumn, alterations) {
|
|
24825
|
-
const generatedChanging = (desiredColumn.generated || currentColumn.generated) && (!desiredColumn.generated || !currentColumn.generated || desiredColumn.generated.expression !== currentColumn.generated.expression || desiredColumn.generated.always !== currentColumn.generated.always || desiredColumn.generated.stored !== currentColumn.generated.stored);
|
|
24902
|
+
const generatedChanging = (desiredColumn.generated || currentColumn.generated) && (!desiredColumn.generated || !currentColumn.generated || normalizeExpression(desiredColumn.generated.expression) !== normalizeExpression(currentColumn.generated.expression) || desiredColumn.generated.always !== currentColumn.generated.always || desiredColumn.generated.stored !== currentColumn.generated.stored);
|
|
24826
24903
|
if (generatedChanging) {
|
|
24827
24904
|
alterations.push({ type: "drop_column", columnName: desiredColumn.name });
|
|
24828
24905
|
alterations.push({ type: "add_column", column: desiredColumn });
|
|
@@ -24880,9 +24957,9 @@ class SchemaDiffer {
|
|
|
24880
24957
|
}
|
|
24881
24958
|
}
|
|
24882
24959
|
collectCheckConstraintAlterations(desiredConstraints, currentConstraints, alterations) {
|
|
24883
|
-
const
|
|
24884
|
-
const currentMap = new Map(currentConstraints.map((c) => [
|
|
24885
|
-
const desiredMap = new Map(desiredConstraints.map((c) => [
|
|
24960
|
+
const normalizeExpression2 = (expr) => expr.replace(/\s+/g, " ").trim();
|
|
24961
|
+
const currentMap = new Map(currentConstraints.map((c) => [normalizeExpression2(c.expression), c]));
|
|
24962
|
+
const desiredMap = new Map(desiredConstraints.map((c) => [normalizeExpression2(c.expression), c]));
|
|
24886
24963
|
for (const [key, constraint] of currentMap) {
|
|
24887
24964
|
if (!desiredMap.has(key) && constraint.name) {
|
|
24888
24965
|
alterations.push({
|
|
@@ -25119,27 +25196,32 @@ class MigrationExecutor {
|
|
|
25119
25196
|
spinner.stopAndPersist({ symbol: "✔", text: `Applied concurrent change (${elapsed}s)` });
|
|
25120
25197
|
} catch (error2) {
|
|
25121
25198
|
spinner.stopAndPersist({ symbol: "✗", text: "Failed to apply concurrent change" });
|
|
25122
|
-
|
|
25199
|
+
if (error2 && typeof error2 === "object" && "code" in error2) {
|
|
25200
|
+
const pgError = error2;
|
|
25201
|
+
throw new MigrationError(pgError.message || "Concurrent statement failed", statement, {
|
|
25202
|
+
code: pgError.code,
|
|
25203
|
+
detail: pgError.detail,
|
|
25204
|
+
hint: pgError.hint,
|
|
25205
|
+
position: pgError.position
|
|
25206
|
+
});
|
|
25207
|
+
}
|
|
25208
|
+
throw new MigrationError(error2 instanceof Error ? error2.message : String(error2), statement);
|
|
25123
25209
|
}
|
|
25124
25210
|
}
|
|
25125
25211
|
}
|
|
25126
25212
|
} catch (error2) {
|
|
25213
|
+
if (error2 instanceof MigrationError) {
|
|
25214
|
+
throw error2;
|
|
25215
|
+
}
|
|
25127
25216
|
if (error2 && typeof error2 === "object" && "code" in error2) {
|
|
25128
25217
|
const pgError = error2;
|
|
25129
|
-
|
|
25130
|
-
if (pgError.message) {
|
|
25131
|
-
failedStatement = undefined;
|
|
25132
|
-
}
|
|
25133
|
-
throw new MigrationError(pgError.message || "Database migration failed", failedStatement, {
|
|
25218
|
+
throw new MigrationError(pgError.message || "Database migration failed", undefined, {
|
|
25134
25219
|
code: pgError.code,
|
|
25135
25220
|
detail: pgError.detail,
|
|
25136
25221
|
hint: pgError.hint,
|
|
25137
25222
|
position: pgError.position
|
|
25138
25223
|
});
|
|
25139
25224
|
}
|
|
25140
|
-
if (error2 instanceof MigrationError) {
|
|
25141
|
-
throw error2;
|
|
25142
|
-
}
|
|
25143
25225
|
throw new MigrationError(error2 instanceof Error ? error2.message : String(error2));
|
|
25144
25226
|
}
|
|
25145
25227
|
}
|
|
@@ -25209,18 +25291,13 @@ class EnumHandler {
|
|
|
25209
25291
|
}
|
|
25210
25292
|
return { transactional, concurrent };
|
|
25211
25293
|
}
|
|
25212
|
-
|
|
25294
|
+
generateRemovalStatements(desiredEnums, currentEnums) {
|
|
25213
25295
|
const statements = [];
|
|
25214
25296
|
const desiredEnumNames = new Set(desiredEnums.map((e) => e.name));
|
|
25215
25297
|
for (const currentEnum of currentEnums) {
|
|
25216
25298
|
if (!desiredEnumNames.has(currentEnum.name)) {
|
|
25217
|
-
|
|
25218
|
-
|
|
25219
|
-
statements.push(generateDropTypeSQL(currentEnum.name, currentEnum.schema));
|
|
25220
|
-
Logger.info(`Dropping unused ENUM type '${currentEnum.name}'`);
|
|
25221
|
-
} else {
|
|
25222
|
-
Logger.warning(`ENUM type '${currentEnum.name}' is not in schema but is still referenced by table columns. ` + `Cannot auto-drop. Remove column references first.`);
|
|
25223
|
-
}
|
|
25299
|
+
statements.push(generateDropTypeSQL(currentEnum.name, currentEnum.schema));
|
|
25300
|
+
Logger.info(`Dropping ENUM type '${currentEnum.name}'`);
|
|
25224
25301
|
}
|
|
25225
25302
|
}
|
|
25226
25303
|
return statements;
|
|
@@ -25258,14 +25335,6 @@ class EnumHandler {
|
|
|
25258
25335
|
}
|
|
25259
25336
|
return statements;
|
|
25260
25337
|
}
|
|
25261
|
-
async isTypeUsed(enumName, client, schemas) {
|
|
25262
|
-
const result = await client.query(`
|
|
25263
|
-
SELECT COUNT(*) as usage_count
|
|
25264
|
-
FROM information_schema.columns
|
|
25265
|
-
WHERE udt_name = $1 AND table_schema = ANY($2::text[])
|
|
25266
|
-
`, [enumName, schemas]);
|
|
25267
|
-
return parseInt(result.rows[0].usage_count) > 0;
|
|
25268
|
-
}
|
|
25269
25338
|
}
|
|
25270
25339
|
// src/core/schema/handlers/extension-handler.ts
|
|
25271
25340
|
class ExtensionHandler {
|
|
@@ -25604,15 +25673,25 @@ class SchemaService {
|
|
|
25604
25673
|
return;
|
|
25605
25674
|
}
|
|
25606
25675
|
}
|
|
25607
|
-
|
|
25608
|
-
|
|
25609
|
-
|
|
25610
|
-
|
|
25611
|
-
|
|
25612
|
-
|
|
25613
|
-
|
|
25614
|
-
|
|
25615
|
-
|
|
25676
|
+
const enumRemovalStatements = this.enumHandler.generateRemovalStatements(desiredEnums, currentEnums);
|
|
25677
|
+
const combinedPlan = {
|
|
25678
|
+
transactional: [
|
|
25679
|
+
...sequenceStatements,
|
|
25680
|
+
...plan.transactional,
|
|
25681
|
+
...plan.deferred,
|
|
25682
|
+
...enumRemovalStatements,
|
|
25683
|
+
...functionStatements,
|
|
25684
|
+
...procedureStatements,
|
|
25685
|
+
...viewStatements,
|
|
25686
|
+
...triggerStatements,
|
|
25687
|
+
...commentStatements,
|
|
25688
|
+
...extensionDropStatements
|
|
25689
|
+
],
|
|
25690
|
+
concurrent: plan.concurrent,
|
|
25691
|
+
deferred: [],
|
|
25692
|
+
hasChanges: true
|
|
25693
|
+
};
|
|
25694
|
+
await this.executor.executePlan(client, combinedPlan, autoApprove);
|
|
25616
25695
|
} finally {
|
|
25617
25696
|
if (lockOptions && !dryRun) {
|
|
25618
25697
|
await this.databaseService.releaseAdvisoryLock(client, lockOptions.lockName);
|
|
@@ -25645,32 +25724,6 @@ class SchemaService {
|
|
|
25645
25724
|
return await this.parser.parseSchemaFile(input);
|
|
25646
25725
|
}
|
|
25647
25726
|
}
|
|
25648
|
-
async executeStatements(client, statements, autoApprove) {
|
|
25649
|
-
if (statements.length === 0)
|
|
25650
|
-
return;
|
|
25651
|
-
await this.executor.executePlan(client, {
|
|
25652
|
-
transactional: statements,
|
|
25653
|
-
concurrent: [],
|
|
25654
|
-
deferred: [],
|
|
25655
|
-
hasChanges: true
|
|
25656
|
-
}, autoApprove);
|
|
25657
|
-
}
|
|
25658
|
-
async executeEnumRemovals(client, desiredEnums, currentEnums, schemas) {
|
|
25659
|
-
const statements = await this.enumHandler.generateRemovalStatements(desiredEnums, currentEnums, client, schemas);
|
|
25660
|
-
for (const statement of statements) {
|
|
25661
|
-
try {
|
|
25662
|
-
await client.query(statement);
|
|
25663
|
-
} catch (error2) {
|
|
25664
|
-
if (error2.code === "2BP01") {
|
|
25665
|
-
const match = statement.match(/DROP TYPE\s+(?:"?(\w+)"?\.)?"?(\w+)"?/i);
|
|
25666
|
-
const typeName = match ? match[1] ? `${match[1]}.${match[2]}` : match[2] : "unknown";
|
|
25667
|
-
Logger.warning(`Could not drop ENUM '${typeName}': now in use (concurrent change). Will retry next migration.`);
|
|
25668
|
-
} else {
|
|
25669
|
-
throw error2;
|
|
25670
|
-
}
|
|
25671
|
-
}
|
|
25672
|
-
}
|
|
25673
|
-
}
|
|
25674
25727
|
validateSchemaReferences(managedSchemas, tables, enums, views, functions, procedures, triggers, sequences) {
|
|
25675
25728
|
const errors = [];
|
|
25676
25729
|
const checkSchema = (objType, objName, objSchema) => {
|
|
@@ -25811,7 +25864,7 @@ async function applyCommand(options, config6) {
|
|
|
25811
25864
|
// package.json
|
|
25812
25865
|
var package_default = {
|
|
25813
25866
|
name: "pgterra",
|
|
25814
|
-
version: "0.2.
|
|
25867
|
+
version: "0.2.14",
|
|
25815
25868
|
description: "Declarative schema management for Postgres",
|
|
25816
25869
|
keywords: [
|
|
25817
25870
|
"postgres",
|