@intentius/chant-lexicon-sql 0.124.48 → 0.124.50
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/clickhouse/plan/statements.d.ts +29 -0
- package/dist/clickhouse/plan/statements.d.ts.map +1 -0
- package/dist/clickhouse/plan/statements.js +334 -0
- package/dist/codegen/classifier-page.d.ts.map +1 -1
- package/dist/codegen/classifier-page.js +23 -0
- package/dist/codegen/postgres-classifier-page.js +2 -2
- package/dist/codegen/registry.d.ts.map +1 -1
- package/dist/codegen/registry.js +5 -0
- package/dist/core/statement-class.d.ts +55 -0
- package/dist/core/statement-class.d.ts.map +1 -0
- package/dist/core/statement-class.js +43 -0
- package/dist/coverage/postgres.json +6 -6
- package/dist/drizzle.d.ts.map +1 -1
- package/dist/drizzle.js +5 -0
- package/dist/files/postgres.d.ts.map +1 -1
- package/dist/files/postgres.js +23 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/integrity.json +5 -5
- package/dist/lint/post-synth/postgres-names.d.ts.map +1 -1
- package/dist/lint/post-synth/postgres-names.js +3 -0
- package/dist/lint/rules/postgres-templates.js +2 -2
- package/dist/lsp/postgres-template.d.ts.map +1 -1
- package/dist/lsp/postgres-template.js +10 -0
- package/dist/manifest.json +1 -1
- package/dist/mcp/postgres.js +1 -1
- package/dist/meta.json +25 -0
- package/dist/migration-classify.d.ts +23 -0
- package/dist/migration-classify.d.ts.map +1 -0
- package/dist/migration-classify.js +44 -0
- package/dist/okf/index.md +5 -0
- package/dist/okf/types/PostgresForeignTable.md +9 -0
- package/dist/okf/types/PostgresOperatorClass.md +9 -0
- package/dist/okf/types/PostgresOperatorFamily.md +9 -0
- package/dist/okf/types/PostgresTextSearchConfiguration.md +9 -0
- package/dist/okf/types/PostgresTextSearchDictionary.md +9 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +13 -0
- package/dist/postgres/access/acl.js +1 -1
- package/dist/postgres/access/import.js +1 -1
- package/dist/postgres/apply/apply.js +5 -0
- package/dist/postgres/apply/statements.d.ts.map +1 -1
- package/dist/postgres/apply/statements.js +156 -2
- package/dist/postgres/entities.d.ts +120 -2
- package/dist/postgres/entities.d.ts.map +1 -1
- package/dist/postgres/entities.js +180 -5
- package/dist/postgres/entity-types.d.ts +5 -0
- package/dist/postgres/entity-types.d.ts.map +1 -1
- package/dist/postgres/entity-types.js +5 -0
- package/dist/postgres/import/generator.d.ts.map +1 -1
- package/dist/postgres/import/generator.js +16 -1
- package/dist/postgres/live/catalog-kinds.d.ts +6 -0
- package/dist/postgres/live/catalog-kinds.d.ts.map +1 -1
- package/dist/postgres/live/catalog-kinds.js +251 -3
- package/dist/postgres/live/describe-resources.d.ts.map +1 -1
- package/dist/postgres/live/describe-resources.js +2 -0
- package/dist/postgres/migrate/observe.d.ts.map +1 -1
- package/dist/postgres/migrate/observe.js +4 -1
- package/dist/postgres/migrate/replication.d.ts +42 -0
- package/dist/postgres/migrate/replication.d.ts.map +1 -1
- package/dist/postgres/migrate/replication.js +53 -0
- package/dist/postgres/parser.d.ts +73 -1
- package/dist/postgres/parser.d.ts.map +1 -1
- package/dist/postgres/parser.js +181 -7
- package/dist/postgres/plan/deep.d.ts.map +1 -1
- package/dist/postgres/plan/deep.js +15 -4
- package/dist/postgres/plan/diff.d.ts.map +1 -1
- package/dist/postgres/plan/diff.js +120 -5
- package/dist/postgres/plan/normalize.d.ts +12 -1
- package/dist/postgres/plan/normalize.d.ts.map +1 -1
- package/dist/postgres/plan/normalize.js +151 -0
- package/dist/postgres/plan/rules.d.ts +3 -0
- package/dist/postgres/plan/rules.d.ts.map +1 -1
- package/dist/postgres/plan/rules.js +5 -2
- package/dist/postgres/plan/schema.js +1 -1
- package/dist/postgres/plan/statements.d.ts +26 -0
- package/dist/postgres/plan/statements.d.ts.map +1 -0
- package/dist/postgres/plan/statements.js +364 -0
- package/dist/postgres/testing/roundtrip-schema.d.ts +3 -2
- package/dist/postgres/testing/roundtrip-schema.d.ts.map +1 -1
- package/dist/postgres/testing/roundtrip-schema.js +12 -1
- package/dist/postgres.d.ts +1 -1
- package/dist/postgres.d.ts.map +1 -1
- package/dist/postgres.js +1 -1
- package/dist/rules/postgres-names.ts +2 -0
- package/dist/rules/postgres-templates.ts +2 -2
- package/dist/validate.d.ts.map +1 -1
- package/dist/validate.js +5 -0
- package/package.json +2 -2
- package/src/clickhouse/plan/statements.test.ts +61 -0
- package/src/clickhouse/plan/statements.ts +306 -0
- package/src/codegen/classifier-page.ts +23 -0
- package/src/codegen/postgres-classifier-page.ts +2 -2
- package/src/codegen/registry.ts +5 -0
- package/src/core/statement-class.ts +88 -0
- package/src/coverage/postgres.json +6 -6
- package/src/drizzle.ts +5 -0
- package/src/files/postgres.ts +22 -1
- package/src/generated/lexicon-sql.json +25 -0
- package/src/index.ts +1 -1
- package/src/lint/post-synth/postgres-checks.test.ts +8 -1
- package/src/lint/post-synth/postgres-names.ts +2 -0
- package/src/lint/rules/postgres-templates.ts +2 -2
- package/src/lsp/postgres-template.ts +9 -0
- package/src/mcp/postgres.ts +1 -1
- package/src/migration-classify.test.ts +150 -0
- package/src/migration-classify.ts +45 -0
- package/src/op/watch-pipeline.test.ts +8 -1
- package/src/plugin.ts +14 -0
- package/src/postgres/access/acl.ts +1 -1
- package/src/postgres/access/import.ts +1 -1
- package/src/postgres/apply/apply.ts +5 -0
- package/src/postgres/apply/statements.ts +151 -3
- package/src/postgres/database-kinds.test.ts +498 -17
- package/src/postgres/entities.ts +276 -5
- package/src/postgres/entity-types.ts +5 -0
- package/src/postgres/import/generator.ts +13 -1
- package/src/postgres/live/catalog-kinds.ts +275 -3
- package/src/postgres/live/describe-resources.ts +2 -0
- package/src/postgres/migrate/observe.ts +3 -1
- package/src/postgres/migrate/replication.ts +66 -0
- package/src/postgres/migrate/subscriber.e2e.test.ts +141 -0
- package/src/postgres/migrate/subscriber.test.ts +59 -0
- package/src/postgres/parser.ts +225 -6
- package/src/postgres/plan/deep.ts +15 -4
- package/src/postgres/plan/diff.ts +107 -6
- package/src/postgres/plan/normalize.ts +155 -0
- package/src/postgres/plan/rules.ts +25 -4
- package/src/postgres/plan/schema.ts +1 -1
- package/src/postgres/plan/statements.test.ts +78 -0
- package/src/postgres/plan/statements.ts +299 -0
- package/src/postgres/testing/roundtrip-schema.ts +12 -1
- package/src/postgres.ts +17 -0
- package/src/skills/chant-sql-postgres-migration.md +1 -0
- package/src/skills/chant-sql-postgres.md +5 -0
- package/src/sql-files.test.ts +33 -0
- package/src/validate.ts +5 -0
|
@@ -38,6 +38,11 @@ const TAG = {
|
|
|
38
38
|
[POSTGRES_ENTITY_TYPES.cast]: tags.cast,
|
|
39
39
|
[POSTGRES_ENTITY_TYPES.eventTrigger]: tags.eventTrigger,
|
|
40
40
|
[POSTGRES_ENTITY_TYPES.operator]: tags.operator,
|
|
41
|
+
[POSTGRES_ENTITY_TYPES.operatorClass]: tags.operatorClass,
|
|
42
|
+
[POSTGRES_ENTITY_TYPES.operatorFamily]: tags.operatorFamily,
|
|
43
|
+
[POSTGRES_ENTITY_TYPES.foreignTable]: tags.foreignTable,
|
|
44
|
+
[POSTGRES_ENTITY_TYPES.textSearchConfiguration]: tags.textSearchConfiguration,
|
|
45
|
+
[POSTGRES_ENTITY_TYPES.textSearchDictionary]: tags.textSearchDictionary,
|
|
41
46
|
[POSTGRES_ENTITY_TYPES.statistics]: tags.statistics,
|
|
42
47
|
[POSTGRES_ENTITY_TYPES.foreignDataWrapper]: tags.foreignDataWrapper,
|
|
43
48
|
[POSTGRES_ENTITY_TYPES.foreignServer]: tags.foreignServer,
|
|
@@ -140,6 +145,12 @@ const PROP_OF_FIELD = {
|
|
|
140
145
|
validator: ["validator", "validatorName", "noValidator"],
|
|
141
146
|
serverType: ["serverType"],
|
|
142
147
|
wrapper: ["wrapper", "wrapperName"],
|
|
148
|
+
server: ["serverName"],
|
|
149
|
+
family: ["family"],
|
|
150
|
+
items: ["items"],
|
|
151
|
+
template: ["template"],
|
|
152
|
+
parser: ["parser"],
|
|
153
|
+
mappings: ["mappings"],
|
|
143
154
|
allTables: ["allTables"],
|
|
144
155
|
tables: ["tables"],
|
|
145
156
|
schemas: ["schemas"],
|
|
@@ -194,7 +205,7 @@ export function inDeclaredVocabulary(declared, live, d, l) {
|
|
|
194
205
|
if (f !== "schema" || d.kind === "extension")
|
|
195
206
|
adopt(key);
|
|
196
207
|
}
|
|
197
|
-
if (d.kind === "table") {
|
|
208
|
+
if (d.kind === "table" || d.kind === "foreignTable") {
|
|
198
209
|
const declaredColumns = declared.columns ?? [];
|
|
199
210
|
const liveColumns = live.columns ?? [];
|
|
200
211
|
// Columns are matched by name and listed in the declaration's order, the
|
|
@@ -219,7 +230,7 @@ export function inDeclaredVocabulary(declared, live, d, l) {
|
|
|
219
230
|
? ["definition"]
|
|
220
231
|
: d.kind === "collation"
|
|
221
232
|
? ["provider", "lcCollate", "lcCtype", "locale", "nondeterministic", "rules"]
|
|
222
|
-
: d.kind === "foreignDataWrapper" || d.kind === "foreignServer"
|
|
233
|
+
: d.kind === "foreignDataWrapper" || d.kind === "foreignServer" || d.kind === "foreignTable" || d.kind === "textSearchDictionary"
|
|
223
234
|
? ["options"]
|
|
224
235
|
: d.kind === "publication"
|
|
225
236
|
? ["publish", "viaRoot", "generatedColumns"]
|
|
@@ -235,9 +246,9 @@ export function inDeclaredVocabulary(declared, live, d, l) {
|
|
|
235
246
|
out.method = live.method;
|
|
236
247
|
out.functionArgs = live.functionArgs;
|
|
237
248
|
}
|
|
238
|
-
if (d.kind === "table" || d.kind === "domain") {
|
|
249
|
+
if (d.kind === "table" || d.kind === "domain" || d.kind === "foreignTable") {
|
|
239
250
|
// A constraint the declaration says the same as the server is the declaration's (its name included or left out).
|
|
240
|
-
const kinds = d.kind === "
|
|
251
|
+
const kinds = d.kind === "table" ? Object.keys(CONSTRAINT_PROPS) : ["CHECK"];
|
|
241
252
|
for (const kind of kinds) {
|
|
242
253
|
const key = CONSTRAINT_PROPS[kind];
|
|
243
254
|
const dl = d.constraints.filter((c) => c.kind === kind);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../../../src/postgres/plan/diff.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAoB,KAAK,SAAS,EAAE,KAAK,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAChG,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAuB,KAAK,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAC3F,OAAO,EAAkE,KAAK,mBAAmB,EAAE,MAAM,aAAa,CAAC;AACvH,OAAO,EAAmE,KAAK,YAAY,EAAE,MAAM,eAAe,CAAC;AACnH,OAAO,EAAe,KAAK,YAAY,EAAE,KAAK,cAAc,EAAE,MAAM,UAAU,CAAC;AAE/E,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,oBAAoB,CAAC;AAExE,4HAA4H;AAC5H,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,MAAM,QAAQ,GAAG,gBAAgB,CAAC,kBAAkB,EAAE,aAAa,CAAC,CAAC;AAE3E,MAAM,WAAW,YAAa,SAAQ,SAAS,CAAC,QAAQ,CAAC;IACvD,6EAA6E;IAC7E,OAAO,EAAE,QAAQ,EAAE,CAAC;IACpB,8FAA8F;IAC9F,YAAY,CAAC,EAAE,6BAA6B,EAAE,CAAC;IAC/C,sHAAsH;IACtH,MAAM,CAAC,EAAE,YAAY,EAAE,CAAC;CACzB;AAqHD,gDAAgD;AAChD,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,mBAAmB,GAAG,kBAAkB,CAa5E;
|
|
1
|
+
{"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../../../src/postgres/plan/diff.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAoB,KAAK,SAAS,EAAE,KAAK,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAChG,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAuB,KAAK,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAC3F,OAAO,EAAkE,KAAK,mBAAmB,EAAE,MAAM,aAAa,CAAC;AACvH,OAAO,EAAmE,KAAK,YAAY,EAAE,MAAM,eAAe,CAAC;AACnH,OAAO,EAAe,KAAK,YAAY,EAAE,KAAK,cAAc,EAAE,MAAM,UAAU,CAAC;AAE/E,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,oBAAoB,CAAC;AAExE,4HAA4H;AAC5H,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,MAAM,QAAQ,GAAG,gBAAgB,CAAC,kBAAkB,EAAE,aAAa,CAAC,CAAC;AAE3E,MAAM,WAAW,YAAa,SAAQ,SAAS,CAAC,QAAQ,CAAC;IACvD,6EAA6E;IAC7E,OAAO,EAAE,QAAQ,EAAE,CAAC;IACpB,8FAA8F;IAC9F,YAAY,CAAC,EAAE,6BAA6B,EAAE,CAAC;IAC/C,sHAAsH;IACtH,MAAM,CAAC,EAAE,YAAY,EAAE,CAAC;CACzB;AAqHD,gDAAgD;AAChD,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,mBAAmB,GAAG,kBAAkB,CAa5E;AAoXD;;;GAGG;AACH;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,SAAS,cAAc,EAAE,EAAE,KAAK,EAAE,SAAS,cAAc,EAAE,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,YAAY,CAAC,CAAC,CAUpJ;AAED,wBAAgB,aAAa,CAAC,SAAS,EAAE,SAAS,cAAc,EAAE,EAAE,QAAQ,EAAE,SAAS,cAAc,EAAE,EAAE,OAAO,GAAE,aAAkB,GAAG,YAAY,CAqClJ"}
|
|
@@ -201,7 +201,7 @@ function diffUnparsed(key, before, after, out, hints) {
|
|
|
201
201
|
}
|
|
202
202
|
}
|
|
203
203
|
/** The kinds (#3911) whose definition has no ALTER: a change is a drop and a create, or (an aggregate's) CREATE OR REPLACE. */
|
|
204
|
-
const REPLACED_KINDS = new Set(["composite", "range", "aggregate", "collation", "cast", "eventTrigger", "operator", "statistics"]);
|
|
204
|
+
const REPLACED_KINDS = new Set(["composite", "range", "aggregate", "collation", "cast", "eventTrigger", "operator", "operatorClass", "operatorFamily", "statistics"]);
|
|
205
205
|
/** What each of {@link REPLACED_KINDS} compares besides its name and comment. */
|
|
206
206
|
const DEFINITION = {
|
|
207
207
|
composite: ["attributeDefs"],
|
|
@@ -211,6 +211,9 @@ const DEFINITION = {
|
|
|
211
211
|
cast: ["castMethod", "function", "context"],
|
|
212
212
|
eventTrigger: ["event", "tags", "function"],
|
|
213
213
|
operator: ["definition"],
|
|
214
|
+
// A class's members, type, family and default flag have no ALTER; a family has no definition besides its name and method (its identity).
|
|
215
|
+
operatorClass: ["default", "dataType", "family", "items"],
|
|
216
|
+
operatorFamily: [],
|
|
214
217
|
statistics: ["table", "kinds", "targets"],
|
|
215
218
|
};
|
|
216
219
|
/** The kinds (#3911) the server alters in place, with the fields that cannot be altered (the object is dropped and created) and those that can. */
|
|
@@ -218,6 +221,9 @@ const ALTERED_KINDS = {
|
|
|
218
221
|
foreignDataWrapper: { recreate: [], inPlace: ["handler", "validator", "options"] },
|
|
219
222
|
foreignServer: { recreate: ["serverType", "wrapper"], inPlace: ["version", "options"] },
|
|
220
223
|
publication: { recreate: ["allTables"], inPlace: ["tables", "schemas", "publish", "viaRoot", "generatedColumns"] },
|
|
224
|
+
// ALTER TEXT SEARCH DICTIONARY sets options and cannot change the template; ALTER TEXT SEARCH CONFIGURATION maps token types and cannot change the parser (SQLPG305).
|
|
225
|
+
textSearchDictionary: { recreate: ["template"], inPlace: ["options"], rule: "SQLPG305" },
|
|
226
|
+
textSearchConfiguration: { recreate: ["parser"], inPlace: ["mappings"], rule: "SQLPG305" },
|
|
221
227
|
};
|
|
222
228
|
/**
|
|
223
229
|
* A foreign data wrapper's, foreign server's or publication's change: the
|
|
@@ -229,11 +235,15 @@ const ALTERED_KINDS = {
|
|
|
229
235
|
function diffAltered(key, before, after, out) {
|
|
230
236
|
if (!same(before.fields.comment, after.fields.comment))
|
|
231
237
|
out.push(change(key, "comment", "SQLPG216", show(before.fields.comment), show(after.fields.comment)));
|
|
232
|
-
const { recreate, inPlace } = ALTERED_KINDS[after.kind];
|
|
238
|
+
const { recreate, inPlace, rule = "SQLPG303" } = ALTERED_KINDS[after.kind];
|
|
233
239
|
const replaced = recreate.filter((k) => !same(before.fields[k], after.fields[k]));
|
|
240
|
+
// An object that lives in a schema is another object under another name: a rename between builds is a drop and a create.
|
|
241
|
+
if (after.schema !== undefined && (before.schema !== after.schema || before.name !== after.name))
|
|
242
|
+
replaced.unshift("name");
|
|
234
243
|
if (replaced.length > 0) {
|
|
235
|
-
const
|
|
236
|
-
const
|
|
244
|
+
const value = (o, k) => (k === "name" ? qualified(o) : (show(o.fields[k]) ?? "-"));
|
|
245
|
+
const was = replaced.map((k) => `${k} ${value(before, k)}`).join("; ");
|
|
246
|
+
const now = replaced.map((k) => `${k} ${value(after, k)}`).join("; ");
|
|
237
247
|
if (before.dependents && before.dependents.length > 0) {
|
|
238
248
|
out.push(change(key, replaced.join(", "), "SQLPG301", was, now, { note: `${before.dependents.join(", ")} depend${before.dependents.length === 1 ? "s" : ""} on it` }));
|
|
239
249
|
}
|
|
@@ -243,7 +253,98 @@ function diffAltered(key, before, after, out) {
|
|
|
243
253
|
}
|
|
244
254
|
for (const k of inPlace)
|
|
245
255
|
if (!same(before.fields[k], after.fields[k]))
|
|
246
|
-
out.push(change(key, k,
|
|
256
|
+
out.push(change(key, k, rule, show(before.fields[k]), show(after.fields[k])));
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Which of an operator's attributes `ALTER OPERATOR ... SET` can change from
|
|
260
|
+
* what the server holds (the Postgres 18 page, with the supported majors):
|
|
261
|
+
* RESTRICT and JOIN on every major, set or removed; from 17 COMMUTATOR and
|
|
262
|
+
* NEGATOR when the operator has none, and HASHES and MERGES when off, never
|
|
263
|
+
* back. Anything else about the definition is dropped and created.
|
|
264
|
+
*/
|
|
265
|
+
function operatorAlterable(key, before, after, major) {
|
|
266
|
+
if (key === "restrict" || key === "join")
|
|
267
|
+
return true;
|
|
268
|
+
if (major < 17)
|
|
269
|
+
return false;
|
|
270
|
+
if (key === "commutator" || key === "negator")
|
|
271
|
+
return before[key] === undefined && after[key] !== undefined;
|
|
272
|
+
if (key === "hashes" || key === "merges")
|
|
273
|
+
return before[key] === undefined && after[key] === "true";
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
/** An operator's change: in place (SQLPG304) when every attribute that changed is one ALTER OPERATOR reaches, else dropped and created like the other kinds without an ALTER. */
|
|
277
|
+
function diffOperator(key, before, after, out, major) {
|
|
278
|
+
const bo = (before.fields.definition ?? {});
|
|
279
|
+
const ao = (after.fields.definition ?? {});
|
|
280
|
+
const renamed = before.schema !== after.schema || before.name !== after.name || before.signature !== after.signature;
|
|
281
|
+
// In the order the page lists them, so the statement reads the same whatever the order the options were written in.
|
|
282
|
+
const ORDER = ["restrict", "join", "commutator", "negator", "hashes", "merges"];
|
|
283
|
+
const rank = (k) => (ORDER.includes(k) ? ORDER.indexOf(k) : ORDER.length);
|
|
284
|
+
const changed = [...new Set([...Object.keys(bo), ...Object.keys(ao)])].filter((k) => !same(bo[k], ao[k])).sort((a, b) => rank(a) - rank(b) || (a < b ? -1 : 1));
|
|
285
|
+
if (renamed || !changed.every((k) => operatorAlterable(k, bo, ao, major))) {
|
|
286
|
+
diffReplaced(key, before, after, out);
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
if (!same(before.fields.comment, after.fields.comment))
|
|
290
|
+
out.push(change(key, "comment", "SQLPG216", show(before.fields.comment), show(after.fields.comment)));
|
|
291
|
+
const note = major < 17 ? undefined : "from 17 ALTER OPERATOR also sets a commutator's or negator's own commutator or negator";
|
|
292
|
+
for (const k of changed)
|
|
293
|
+
out.push(change(key, `definition.${k}`, "SQLPG304", show(bo[k]), show(ao[k]), note && (k === "commutator" || k === "negator") ? { note } : {}));
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* A foreign table's columns, as ALTER FOREIGN TABLE changes them (SQLPG306):
|
|
297
|
+
* all catalog changes, none reading the remote side. A column is its name
|
|
298
|
+
* (a rename is a drop and an add); its comment is the generic comment rule.
|
|
299
|
+
*/
|
|
300
|
+
function diffForeignColumns(key, before, after, out) {
|
|
301
|
+
const byName = new Map(before.columns.map((c) => [c.name, c]));
|
|
302
|
+
const afterNames = new Set(after.columns.map((c) => c.name));
|
|
303
|
+
for (const b of before.columns)
|
|
304
|
+
if (!afterNames.has(b.name))
|
|
305
|
+
out.push(change(key, `columns.${b.name}`, "SQLPG306", `${b.name} ${b.type ?? ""}`.trim(), undefined));
|
|
306
|
+
for (const a of after.columns) {
|
|
307
|
+
const b = byName.get(a.name);
|
|
308
|
+
if (!b) {
|
|
309
|
+
out.push(change(key, `columns.${a.name}`, "SQLPG306", undefined, `${a.name} ${a.type ?? ""}`.trim()));
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
const at = `columns.${a.name}`;
|
|
313
|
+
if (b.type !== a.type)
|
|
314
|
+
out.push(change(key, `${at}.type`, "SQLPG306", b.type, a.type));
|
|
315
|
+
if (b.collate !== a.collate)
|
|
316
|
+
out.push(change(key, `${at}.collate`, "SQLPG306", b.collate, a.collate));
|
|
317
|
+
if (b.default !== a.default)
|
|
318
|
+
out.push(change(key, `${at}.default`, "SQLPG306", b.default, a.default));
|
|
319
|
+
if (b.notNull !== a.notNull)
|
|
320
|
+
out.push(change(key, `${at}.notNull`, "SQLPG306", b.notNull ? "NOT NULL" : "NULL", a.notNull ? "NOT NULL" : "NULL"));
|
|
321
|
+
if (!same(b.options, a.options))
|
|
322
|
+
out.push(change(key, `${at}.options`, "SQLPG306", show(b.options), show(a.options)));
|
|
323
|
+
if (b.comment !== a.comment)
|
|
324
|
+
out.push(change(key, `${at}.comment`, "SQLPG216", b.comment, a.comment));
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
/** A foreign table's CHECK constraints: a dropped one first, so a constraint replaced under its name is dropped before it is added. */
|
|
328
|
+
function diffForeignConstraints(key, before, after, out) {
|
|
329
|
+
const label = (c) => (c.name ? `${c.name}: ${c.body}` : c.body);
|
|
330
|
+
const remaining = [...before.constraints];
|
|
331
|
+
const added = [];
|
|
332
|
+
const comments = [];
|
|
333
|
+
for (const a of after.constraints) {
|
|
334
|
+
const i = remaining.findIndex((b) => sameConstraint(a, b));
|
|
335
|
+
if (i < 0) {
|
|
336
|
+
added.push(a);
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
const b = remaining.splice(i, 1)[0];
|
|
340
|
+
if (b.comment !== a.comment)
|
|
341
|
+
comments.push(change(key, `constraints.${a.name ?? b.name}.comment`, "SQLPG216", b.comment, a.comment));
|
|
342
|
+
}
|
|
343
|
+
for (const b of remaining)
|
|
344
|
+
out.push(change(key, `constraints.${b.name ?? b.kind.toLowerCase()}`, "SQLPG306", label(b), undefined));
|
|
345
|
+
for (const a of added)
|
|
346
|
+
out.push(change(key, `constraints.${a.name ?? a.kind.toLowerCase()}`, "SQLPG306", undefined, label(a)));
|
|
347
|
+
out.push(...comments);
|
|
247
348
|
}
|
|
248
349
|
/**
|
|
249
350
|
* A type's, aggregate's, collation's, cast's or event trigger's change: its
|
|
@@ -285,6 +386,15 @@ function diffObject(key, before, after, out, hints, major) {
|
|
|
285
386
|
if (!same(b, a))
|
|
286
387
|
out.push(change(key, name, rule, show(b), show(a), extra));
|
|
287
388
|
};
|
|
389
|
+
if (after.kind === "operator") {
|
|
390
|
+
diffOperator(key, before, after, out, major);
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
// A foreign table moved to another server is another table: ALTER FOREIGN TABLE has no SERVER action.
|
|
394
|
+
if (after.kind === "foreignTable" && before.schema === after.schema && before.name === after.name && !same(before.fields.server, after.fields.server)) {
|
|
395
|
+
out.push(change(key, "server", before.dependents && before.dependents.length > 0 ? "SQLPG301" : "SQLPG300", show(before.fields.server), show(after.fields.server), before.dependents && before.dependents.length > 0 ? { note: `${before.dependents.join(", ")} depend${before.dependents.length === 1 ? "s" : ""} on it` } : {}));
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
288
398
|
if (REPLACED_KINDS.has(after.kind)) {
|
|
289
399
|
// A cast is named by its types, and an aggregate's argument types are its identity: either changed, between two builds, is another object.
|
|
290
400
|
if (after.kind === "cast" && before.name !== after.name) {
|
|
@@ -425,6 +535,11 @@ function diffObject(key, before, after, out, hints, major) {
|
|
|
425
535
|
field("attributes", "SQLPG294");
|
|
426
536
|
field("connectionLimit", "SQLPG294");
|
|
427
537
|
break;
|
|
538
|
+
case "foreignTable":
|
|
539
|
+
diffForeignColumns(key, before, after, out);
|
|
540
|
+
diffForeignConstraints(key, before, after, out);
|
|
541
|
+
field("options", "SQLPG306");
|
|
542
|
+
break;
|
|
428
543
|
case "table":
|
|
429
544
|
if (!same(before.fields.rowSecurity, after.fields.rowSecurity) || !same(before.fields.forceRowSecurity, after.fields.forceRowSecurity)) {
|
|
430
545
|
const state = (o) => (o.fields.rowSecurity ? (o.fields.forceRowSecurity ? "enabled, forced" : "enabled") : o.fields.forceRowSecurity ? "disabled, forced" : "disabled");
|
|
@@ -73,6 +73,8 @@ export interface CanonicalColumn {
|
|
|
73
73
|
collate?: string;
|
|
74
74
|
compression?: string;
|
|
75
75
|
storage?: string;
|
|
76
|
+
/** A foreign table column's `OPTIONS`, sorted by name. */
|
|
77
|
+
options?: Record<string, string>;
|
|
76
78
|
comment?: string;
|
|
77
79
|
}
|
|
78
80
|
export interface CanonicalConstraint {
|
|
@@ -85,7 +87,7 @@ export interface CanonicalConstraint {
|
|
|
85
87
|
notValid?: boolean;
|
|
86
88
|
comment?: string;
|
|
87
89
|
}
|
|
88
|
-
export type CanonicalKind = "schema" | "table" | "index" | "view" | "materializedView" | "sequence" | "enum" | "composite" | "range" | "domain" | "extension" | "aggregate" | "collation" | "cast" | "eventTrigger" | "operator" | "statistics" | "foreignDataWrapper" | "foreignServer" | "publication" | "function" | "procedure" | "trigger" | "policy" | "role" | "grant" | "defaultPrivileges";
|
|
90
|
+
export type CanonicalKind = "schema" | "table" | "index" | "view" | "materializedView" | "sequence" | "enum" | "composite" | "range" | "domain" | "extension" | "aggregate" | "collation" | "cast" | "eventTrigger" | "operator" | "operatorClass" | "operatorFamily" | "foreignTable" | "textSearchConfiguration" | "textSearchDictionary" | "statistics" | "foreignDataWrapper" | "foreignServer" | "publication" | "function" | "procedure" | "trigger" | "policy" | "role" | "grant" | "defaultPrivileges";
|
|
89
91
|
/** The kinds whose declarations are privileges, compared as the access they add up to (`../access/`), not one by one. */
|
|
90
92
|
export declare const ACCESS_KINDS: ReadonlySet<CanonicalKind>;
|
|
91
93
|
/** The kinds a profile manages only when it says so (`sql.profiles.<env>.access`). */
|
|
@@ -143,6 +145,15 @@ export declare function operatorSignature(p: Pick<OperatorProps, "leftArg" | "ri
|
|
|
143
145
|
* in the new operator's own schema, as `schema.symbol`.
|
|
144
146
|
*/
|
|
145
147
|
export declare function canonicalOperatorRef(text: string, ownSchema: string): string;
|
|
148
|
+
/**
|
|
149
|
+
* A name where an object is named (a text search parser, template or
|
|
150
|
+
* dictionary, an operator family): the schema left off when it is
|
|
151
|
+
* `pg_catalog` or the default schema, since a bare name is looked for in both.
|
|
152
|
+
* The catalog prints every schema, so both sides are brought to this form.
|
|
153
|
+
*/
|
|
154
|
+
export declare function canonicalRef(text: string, defaultSchema: string): string;
|
|
155
|
+
/** An operator named in an operator class: `OPERATOR(app.<<)`, `app.<<` or a bare symbol, by `canonicalRef`'s rule for the schema. */
|
|
156
|
+
export declare function canonicalOperatorName(text: string, defaultSchema: string): string;
|
|
146
157
|
/**
|
|
147
158
|
* The kinds of statistics gathered, sorted. Left out, the server builds all
|
|
148
159
|
* three, except for a lone expression, which gets none of them (only the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"normalize.d.ts","sourceRoot":"","sources":["../../../src/postgres/plan/normalize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAQH,OAAO,KAAK,EAEV,aAAa,
|
|
1
|
+
{"version":3,"file":"normalize.d.ts","sourceRoot":"","sources":["../../../src/postgres/plan/normalize.ts"],"names":[],"mappings":"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"}
|
|
@@ -304,6 +304,11 @@ const kindOf = {
|
|
|
304
304
|
"Postgres::Cast": "cast",
|
|
305
305
|
"Postgres::EventTrigger": "eventTrigger",
|
|
306
306
|
"Postgres::Operator": "operator",
|
|
307
|
+
"Postgres::OperatorClass": "operatorClass",
|
|
308
|
+
"Postgres::OperatorFamily": "operatorFamily",
|
|
309
|
+
"Postgres::ForeignTable": "foreignTable",
|
|
310
|
+
"Postgres::TextSearchConfiguration": "textSearchConfiguration",
|
|
311
|
+
"Postgres::TextSearchDictionary": "textSearchDictionary",
|
|
307
312
|
"Postgres::Statistics": "statistics",
|
|
308
313
|
"Postgres::ForeignDataWrapper": "foreignDataWrapper",
|
|
309
314
|
"Postgres::ForeignServer": "foreignServer",
|
|
@@ -653,6 +658,112 @@ function operatorOptions(p, ownSchema, defaultSchema) {
|
|
|
653
658
|
}
|
|
654
659
|
return Object.fromEntries(Object.entries(out).sort(([a], [b]) => (a < b ? -1 : 1)));
|
|
655
660
|
}
|
|
661
|
+
/**
|
|
662
|
+
* A name where an object is named (a text search parser, template or
|
|
663
|
+
* dictionary, an operator family): the schema left off when it is
|
|
664
|
+
* `pg_catalog` or the default schema, since a bare name is looked for in both.
|
|
665
|
+
* The catalog prints every schema, so both sides are brought to this form.
|
|
666
|
+
*/
|
|
667
|
+
export function canonicalRef(text, defaultSchema) {
|
|
668
|
+
const pieces = canonicalName(text).split(/\.(?=(?:[^"]*"[^"]*")*[^"]*$)/);
|
|
669
|
+
if (pieces.length >= 2) {
|
|
670
|
+
const schema = identValue(pieces[0]);
|
|
671
|
+
return schema === "pg_catalog" || schema === defaultSchema ? pieces.slice(1).join(".") : pieces.join(".");
|
|
672
|
+
}
|
|
673
|
+
return pieces[0];
|
|
674
|
+
}
|
|
675
|
+
/** An operator named in an operator class: `OPERATOR(app.<<)`, `app.<<` or a bare symbol, by `canonicalRef`'s rule for the schema. */
|
|
676
|
+
export function canonicalOperatorName(text, defaultSchema) {
|
|
677
|
+
const inner = text.trim().replace(/^OPERATOR\s*\(\s*(.*?)\s*\)$/is, "$1").trim();
|
|
678
|
+
const m = /^("(?:[^"]|"")+"|[A-Za-z_][A-Za-z0-9_$]*)\s*\.\s*(\S.*)$/s.exec(inner);
|
|
679
|
+
if (!m)
|
|
680
|
+
return inner;
|
|
681
|
+
const schema = identValue(m[1]);
|
|
682
|
+
return schema === "pg_catalog" || schema === defaultSchema ? m[2].trim() : `${quoteIdent(schema)}.${m[2].trim()}`;
|
|
683
|
+
}
|
|
684
|
+
/** A comma list split at its top level: `numeric(12,2), text` is two items. */
|
|
685
|
+
function splitTopLevel(text) {
|
|
686
|
+
const out = [];
|
|
687
|
+
let depth = 0;
|
|
688
|
+
let current = "";
|
|
689
|
+
for (const ch of text) {
|
|
690
|
+
if (ch === "(" || ch === "[")
|
|
691
|
+
depth++;
|
|
692
|
+
else if (ch === ")" || ch === "]")
|
|
693
|
+
depth--;
|
|
694
|
+
if (ch === "," && depth === 0) {
|
|
695
|
+
out.push(current.trim());
|
|
696
|
+
current = "";
|
|
697
|
+
}
|
|
698
|
+
else
|
|
699
|
+
current += ch;
|
|
700
|
+
}
|
|
701
|
+
if (current.trim() !== "")
|
|
702
|
+
out.push(current.trim());
|
|
703
|
+
return out;
|
|
704
|
+
}
|
|
705
|
+
/**
|
|
706
|
+
* The operand types a support function of an operator class is registered
|
|
707
|
+
* for when `FUNCTION n (left, right)` does not say (sql-createopclass): a
|
|
708
|
+
* B-tree comparison function's (1) and a hash function's (1, 2) are the
|
|
709
|
+
* function's own input types, a B-tree `in_range` function's (3) its first
|
|
710
|
+
* and third, and every other support function's the class's type. The catalog
|
|
711
|
+
* always stores both, so a declaration is brought to them.
|
|
712
|
+
*/
|
|
713
|
+
function supportTypes(method, number, classType, args) {
|
|
714
|
+
if (method === "btree" && number === 1)
|
|
715
|
+
return [args[0] ?? classType, args[1] ?? args[0] ?? classType];
|
|
716
|
+
if (method === "btree" && number === 3)
|
|
717
|
+
return [args[0] ?? classType, args[2] ?? classType];
|
|
718
|
+
if (method === "hash" && (number === 1 || number === 2))
|
|
719
|
+
return [args[0] ?? classType, args[0] ?? classType];
|
|
720
|
+
return [classType, classType];
|
|
721
|
+
}
|
|
722
|
+
/** An operator class as compared: its type, family, default flag and members, each member canonical and the list sorted. */
|
|
723
|
+
function operatorClassFields(p, schema, defaultSchema, fields) {
|
|
724
|
+
const type = canonicalType(p.dataType, defaultSchema);
|
|
725
|
+
fields.default = p.default === true;
|
|
726
|
+
fields.dataType = type;
|
|
727
|
+
// A class with no FAMILY is in a family of its own name; so is one that names that family.
|
|
728
|
+
const own = canonicalRef(`${quoteIdent(schema)}.${quoteIdent(p.name)}`, defaultSchema);
|
|
729
|
+
const named = p.family === undefined ? own : /^\s*("(?:[^"]|"")+"|[A-Za-z_][A-Za-z0-9_$]*)\s*$/.test(p.family) && identValue(p.family.trim()) === p.name ? own : canonicalRef(p.family, defaultSchema);
|
|
730
|
+
fields.family = named === own ? undefined : named;
|
|
731
|
+
const side = (t) => (t.trim().toUpperCase() === "NONE" ? "NONE" : canonicalArgType(t, defaultSchema));
|
|
732
|
+
const members = p.items.map((i) => {
|
|
733
|
+
if (i.kind === "storage")
|
|
734
|
+
return { rank: 2, number: 0, text: `storage ${canonicalType(i.type, defaultSchema)}` };
|
|
735
|
+
const n = Number(i.number);
|
|
736
|
+
if (i.kind === "operator") {
|
|
737
|
+
const [l, r] = i.types !== undefined ? splitTopLevel(i.types) : [type, type];
|
|
738
|
+
const sorted = i.orderBy !== undefined ? ` for order by ${canonicalRef(i.orderBy, defaultSchema)}` : "";
|
|
739
|
+
return { rank: 0, number: n, text: `operator ${n} ${canonicalOperatorName(i.operator, defaultSchema)} (${side(l ?? type)},${side(r ?? l ?? type)})${sorted}` };
|
|
740
|
+
}
|
|
741
|
+
const args = splitTopLevel(i.args).map((a) => canonicalArgType(a, defaultSchema));
|
|
742
|
+
const given = i.types !== undefined ? splitTopLevel(i.types).map(side) : undefined;
|
|
743
|
+
const [l, r] = given ? [given[0], given[1] ?? given[0]] : supportTypes(p.method, n, type, args);
|
|
744
|
+
return { rank: 1, number: n, text: `function ${n} (${l},${r}) ${canonicalFunctionName(i.function, defaultSchema)}(${args.join(",")})` };
|
|
745
|
+
});
|
|
746
|
+
members.sort((a, b) => a.rank - b.rank || a.number - b.number || (a.text < b.text ? -1 : 1));
|
|
747
|
+
fields.items = members.map((m) => m.text);
|
|
748
|
+
}
|
|
749
|
+
/** A dictionary option's value as compared: a string constant's value, a quoted name's, a bare word folded, a number as written. */
|
|
750
|
+
function dictionaryValue(v) {
|
|
751
|
+
const t = v.trim();
|
|
752
|
+
if (/^([Ee]?'|\$)/.test(t))
|
|
753
|
+
return stringValue(t) ?? t;
|
|
754
|
+
if (/^"/.test(t))
|
|
755
|
+
return identValue(t);
|
|
756
|
+
return t.toLowerCase();
|
|
757
|
+
}
|
|
758
|
+
/** A text search configuration's mappings as compared: each token type to its dictionaries in order, sorted by token type. */
|
|
759
|
+
function mappingFields(p, defaultSchema) {
|
|
760
|
+
const out = {};
|
|
761
|
+
for (const m of p.mappings)
|
|
762
|
+
for (const t of m.tokens)
|
|
763
|
+
out[t.toLowerCase()] = m.dictionaries.map((d) => canonicalRef(d, defaultSchema));
|
|
764
|
+
const entries = Object.entries(out).sort(([a], [b]) => (a < b ? -1 : 1));
|
|
765
|
+
return entries.length > 0 ? Object.fromEntries(entries) : undefined;
|
|
766
|
+
}
|
|
656
767
|
/** An `OPTIONS (...)` list as compared: by name, none as nothing. */
|
|
657
768
|
function sortedOptions(options) {
|
|
658
769
|
const entries = Object.entries(options ?? {}).sort(([a], [b]) => (a < b ? -1 : 1));
|
|
@@ -770,6 +881,46 @@ export function canonicalPgObject(entityType, props, defaultSchema = "public") {
|
|
|
770
881
|
fields.definition = operatorOptions(p, base.schema, defaultSchema);
|
|
771
882
|
break;
|
|
772
883
|
}
|
|
884
|
+
case "operatorClass":
|
|
885
|
+
operatorClassFields(props, base.schema, defaultSchema, fields);
|
|
886
|
+
base.signature = ` USING ${props.method}`;
|
|
887
|
+
break;
|
|
888
|
+
case "operatorFamily":
|
|
889
|
+
base.signature = ` USING ${props.method}`;
|
|
890
|
+
break;
|
|
891
|
+
case "textSearchDictionary": {
|
|
892
|
+
const p = props;
|
|
893
|
+
fields.template = canonicalRef(p.template, defaultSchema);
|
|
894
|
+
fields.options = sortedOptions(Object.fromEntries(Object.entries(p.options).map(([k, v]) => [k.toLowerCase(), dictionaryValue(v)])));
|
|
895
|
+
break;
|
|
896
|
+
}
|
|
897
|
+
case "textSearchConfiguration": {
|
|
898
|
+
const p = props;
|
|
899
|
+
fields.parser = canonicalRef(p.parser, defaultSchema);
|
|
900
|
+
fields.mappings = mappingFields(p, defaultSchema);
|
|
901
|
+
break;
|
|
902
|
+
}
|
|
903
|
+
case "foreignTable": {
|
|
904
|
+
const p = props;
|
|
905
|
+
p.columns.forEach((c, position) => {
|
|
906
|
+
const options = sortedOptions(c.options);
|
|
907
|
+
columns.push({
|
|
908
|
+
name: c.name,
|
|
909
|
+
position,
|
|
910
|
+
type: canonicalType(c.type, defaultSchema),
|
|
911
|
+
notNull: c.notNull === true,
|
|
912
|
+
default: canonicalExpr(c.default),
|
|
913
|
+
collate: c.collate ? canonicalName(c.collate) : undefined,
|
|
914
|
+
comment: comment(c.comment),
|
|
915
|
+
...(options ? { options } : {}),
|
|
916
|
+
});
|
|
917
|
+
});
|
|
918
|
+
for (const c of p.checks)
|
|
919
|
+
constraints.push({ kind: "CHECK", name: c.name, body: ["check", canonicalExpr(c.expr), c.noInherit ? "no inherit" : ""].filter(Boolean).join(" "), ...(c.notValid ? { notValid: true } : {}), comment: comment(c.comment) });
|
|
920
|
+
fields.server = quoteIdent(p.serverName);
|
|
921
|
+
fields.options = sortedOptions(p.options);
|
|
922
|
+
break;
|
|
923
|
+
}
|
|
773
924
|
case "statistics": {
|
|
774
925
|
const p = props;
|
|
775
926
|
fields.table = canonicalName(p.tableName, defaultSchema);
|
|
@@ -97,6 +97,9 @@ export declare const PG_CLASSIFIER_RULES: {
|
|
|
97
97
|
readonly SQLPG301: PgClassifierRule;
|
|
98
98
|
readonly SQLPG302: PgClassifierRule;
|
|
99
99
|
readonly SQLPG303: PgClassifierRule;
|
|
100
|
+
readonly SQLPG304: PgClassifierRule;
|
|
101
|
+
readonly SQLPG305: PgClassifierRule;
|
|
102
|
+
readonly SQLPG306: PgClassifierRule;
|
|
100
103
|
readonly SQLPG270: PgClassifierRule;
|
|
101
104
|
};
|
|
102
105
|
export type PgClassifierRuleId = keyof typeof PG_CLASSIFIER_RULES;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../../../src/postgres/plan/rules.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,EAAkB,KAAK,aAAa,EAAE,KAAK,cAAc,IAAI,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAErH,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,UAAU,GAAG,cAAc,GAAG,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAAC;AAEhH,MAAM,MAAM,gBAAgB,GAAG,iBAAiB,CAAC,aAAa,CAAC,CAAC;AAEhE,eAAO,MAAM,iBAAiB,EAAE,aAAa,CAAC,aAAa,CAY1D,CAAC;AASF,eAAO,MAAM,mBAAmB
|
|
1
|
+
{"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../../../src/postgres/plan/rules.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,EAAkB,KAAK,aAAa,EAAE,KAAK,cAAc,IAAI,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAErH,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,UAAU,GAAG,cAAc,GAAG,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAAC;AAEhH,MAAM,MAAM,gBAAgB,GAAG,iBAAiB,CAAC,aAAa,CAAC,CAAC;AAEhE,eAAO,MAAM,iBAAiB,EAAE,aAAa,CAAC,aAAa,CAY1D,CAAC;AASF,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiWqB,CAAC;AAEtD,MAAM,MAAM,kBAAkB,GAAG,MAAM,OAAO,mBAAmB,CAAC"}
|
|
@@ -108,9 +108,12 @@ export const PG_CLASSIFIER_RULES = {
|
|
|
108
108
|
SQLPG297: rule("SQLPG297", "metadata", "Revoke a privilege", "REVOKE removes from the object's privileges in the catalog. The role loses it at once: its next statement that needs it fails, so move every reader off it first.", `${DOCS}/sql-revoke.html`),
|
|
109
109
|
SQLPG298: rule("SQLPG298", "metadata", "Change default privileges", "ALTER DEFAULT PRIVILEGES changes what objects created afterwards are given; objects that exist keep their privileges.", `${DOCS}/sql-alterdefaultprivileges.html`),
|
|
110
110
|
SQLPG299: rule("SQLPG299", "expand", "Change a clause chant does not read", "chant's parser does not know this clause, so it cannot tell which lock the change takes or whether an in-place statement makes it; the plan refuses it rather than guess. Review it: make it by hand with the ALTER the server documents for the clause, or as expand and contract.", `${DOCS}/sql-altertable.html`),
|
|
111
|
-
SQLPG300: rule("SQLPG300", "metadata", "Drop and create a type, aggregate, collation, cast, event trigger, operator, statistics object, foreign server or publication", "A composite or range type's definition, a collation's, a cast's, an event trigger's, an operator's function and operands and extended statistics' have no ALTER, an aggregate's argument types, state type and final function cannot be replaced, and neither can a foreign server's type or wrapper or a publication's FOR ALL TABLES: the object is dropped and created in one transaction. Nothing on the server depends on it (a column, a view, a function or a cast would make the DROP fail); the locks are on its catalog rows only. Dropped statistics are empty until the next ANALYZE, and a publication that is dropped stops its subscribers until it is created again.", `${DOCS}/sql-createtype.html`),
|
|
112
|
-
SQLPG301: rule("SQLPG301", "expand", "Change a type, aggregate, collation, cast, foreign server or publication that other objects depend on", "No ALTER makes the change, and DROP refuses while a column, a view, a function or another object depends on the object. Create the new definition under another name, move what depends on it, then drop the old.", `${DOCS}/sql-droptype.html`),
|
|
111
|
+
SQLPG300: rule("SQLPG300", "metadata", "Drop and create a type, aggregate, collation, cast, event trigger, operator, operator class or family, foreign table's server, text search object, statistics object, foreign server or publication", "A composite or range type's definition, a collation's, a cast's, an event trigger's, an operator's function and operands, and any attribute of it that SQLPG304 does not alter, an operator class's members, type, family and default flag, an operator family's index method, and extended statistics' have no ALTER (ALTER OPERATOR CLASS and ALTER OPERATOR FAMILY change a name, an owner or a schema only), an aggregate's argument types, state type and final function cannot be replaced, and neither can a foreign server's type or wrapper, a foreign table's server, a text search configuration's parser, a text search dictionary's template or a publication's FOR ALL TABLES: the object is dropped and created in one transaction. Nothing on the server depends on it (a column, a view, an index, a function or a cast would make the DROP fail); the locks are on its catalog rows only. Dropped statistics are empty until the next ANALYZE, and a publication that is dropped stops its subscribers until it is created again.", `${DOCS}/sql-createtype.html`),
|
|
112
|
+
SQLPG301: rule("SQLPG301", "expand", "Change a type, aggregate, collation, cast, operator, operator class, text search object, foreign table's server, foreign server or publication that other objects depend on", "No ALTER makes the change, and DROP refuses while a column, a view, an index, a function or another object depends on the object. Create the new definition under another name, move what depends on it, then drop the old.", `${DOCS}/sql-droptype.html`),
|
|
113
113
|
SQLPG302: rule("SQLPG302", "metadata", "Replace an aggregate's definition", "CREATE OR REPLACE AGGREGATE (14 and later) replaces the aggregate's functions and options in the catalog; no table is locked. Its argument types, its result and its kind (plain, ordered-set, hypothetical-set) have to stay the same, so a change of the state type or the final function is dropped and created instead (SQLPG300).", `${DOCS}/sql-createaggregate.html`),
|
|
114
114
|
SQLPG303: rule("SQLPG303", "metadata", "Alter a foreign data wrapper, foreign server or publication in place", "ALTER FOREIGN DATA WRAPPER (handler, validator, options), ALTER SERVER (version, options) and ALTER PUBLICATION (SET options, ADD and DROP TABLE and TABLES IN SCHEMA) change the object's catalog rows under a brief lock; no table is rewritten. A publication's change reaches its subscribers as the walsender next reads it: a table added is not copied to a subscription until the subscriber runs REFRESH PUBLICATION.", `${DOCS}/sql-alterpublication.html`),
|
|
115
|
+
SQLPG304: rule("SQLPG304", "metadata", "Alter an operator in place", "ALTER OPERATOR ... SET changes the operator's catalog row under a brief lock; no index or table is read. RESTRICT and JOIN can be set or removed (NONE) on every supported major. From 17, COMMUTATOR and NEGATOR can be set when the operator has none, and HASHES and MERGES turned on, never off (the Postgres 18 page: \"Can only be changed if the operator does not have an existing commutator\", \"Can only be enabled and not disabled\"); setting a commutator or negator also sets the other operator's. On 14 to 16 only RESTRICT and JOIN are alterable, so any other change drops and creates the operator (SQLPG300), as does a change of function or operands on any major.", `${DOCS}/sql-alteroperator.html`),
|
|
116
|
+
SQLPG305: rule("SQLPG305", "metadata", "Alter a text search configuration's mappings or a dictionary's options in place", "ALTER TEXT SEARCH CONFIGURATION ADD MAPPING, ALTER MAPPING and DROP MAPPING change the token types mapped to dictionaries; ALTER TEXT SEARCH DICTIONARY sets a template's options, and an option named with no value is removed. Both change catalog rows under a brief lock and leave the parser or template as it is, so a parser or template change is dropped and created (SQLPG300). A tsvector already stored keeps the tokens it was built with: rebuild the columns and indexes that hold one when the mappings or options change what a word becomes. The same applies on 14 to 18.", `${DOCS}/sql-altertsconfig.html`),
|
|
117
|
+
SQLPG306: rule("SQLPG306", "metadata", "Alter a foreign table in place", "ALTER FOREIGN TABLE adds and drops columns, changes a column's type, default, NOT NULL and OPTIONS, adds and drops CHECK and NOT NULL constraints, and sets the table's OPTIONS, all in the catalog. Nothing is checked against the remote side and no row is rewritten or scanned (\"It is the user's responsibility to ensure that the table definition matches the remote side\"), so a CHECK is added with no validation pass. A foreign table's server cannot be altered, so a change of server is dropped and created (SQLPG300).", `${DOCS}/sql-alterforeigntable.html`),
|
|
115
118
|
SQLPG270: rule("SQLPG270", "drop", "Drop an object", "The object and, for a table, a materialized view or a sequence, its data are gone.", `${DOCS}/sql-droptable.html`),
|
|
116
119
|
};
|
|
@@ -73,7 +73,7 @@ export function pgSchemaFromBuildFile(path, defaultSchema = "public") {
|
|
|
73
73
|
export function pgNamespace(kind) {
|
|
74
74
|
if (kind === "schema" || kind === "extension" || kind === "trigger" || kind === "policy" || kind === "role")
|
|
75
75
|
return kind;
|
|
76
|
-
if (kind === "collation" || kind === "cast" || kind === "eventTrigger" || kind === "operator" || kind === "statistics" || kind === "foreignDataWrapper" || kind === "foreignServer" || kind === "publication")
|
|
76
|
+
if (kind === "collation" || kind === "cast" || kind === "eventTrigger" || kind === "operator" || kind === "operatorClass" || kind === "operatorFamily" || kind === "textSearchConfiguration" || kind === "textSearchDictionary" || kind === "statistics" || kind === "foreignDataWrapper" || kind === "foreignServer" || kind === "publication")
|
|
77
77
|
return kind;
|
|
78
78
|
if (kind === "grant" || kind === "defaultPrivileges")
|
|
79
79
|
return "grant";
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Postgres statement classifier (#4407): a migration file's statements,
|
|
3
|
+
* each given the class and rule the schema-diff classifier (`./rules.ts`)
|
|
4
|
+
* gives the same change, with the lock the rule's restriction names. It reads
|
|
5
|
+
* text and contacts no server.
|
|
6
|
+
*
|
|
7
|
+
* `CREATE`, `GRANT`, `REVOKE`, `COMMENT ON` and `ALTER TABLE ... ROW LEVEL
|
|
8
|
+
* SECURITY` go through the declaration parser (`../parser.ts`). The parser
|
|
9
|
+
* has no `ALTER` grammar, so the other `ALTER` and `DROP` statements are read
|
|
10
|
+
* word by word from the same tokens.
|
|
11
|
+
*
|
|
12
|
+
* A statement alone cannot decide some changes. `ALTER COLUMN ... TYPE` is a
|
|
13
|
+
* catalog change or a table rewrite by the column's old type, which the file
|
|
14
|
+
* does not state: it is `undecided`, naming SQLPG206 and SQLPG207 (and
|
|
15
|
+
* SQLPG208 with a `USING` expression). `CREATE OR REPLACE VIEW` is SQLPG250
|
|
16
|
+
* or SQLPG251 by whether the columns stay. A statement no rule covers is
|
|
17
|
+
* `unclassified` with a note.
|
|
18
|
+
*/
|
|
19
|
+
import { type StatementVerdict } from "../../core/statement-class.js";
|
|
20
|
+
/**
|
|
21
|
+
* Classify a migration's up statements, in order. The statements are the
|
|
22
|
+
* file's own text, one per entry (a Flyway file after placeholder
|
|
23
|
+
* substitution).
|
|
24
|
+
*/
|
|
25
|
+
export declare function classifyPgStatements(statements: readonly string[]): StatementVerdict[];
|
|
26
|
+
//# sourceMappingURL=statements.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"statements.d.ts","sourceRoot":"","sources":["../../../src/postgres/plan/statements.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH,OAAO,EAA0E,KAAK,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAuP3I;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,GAAG,gBAAgB,EAAE,CAyBtF"}
|