@query-doctor/core 0.4.1-rc.3 → 0.4.2
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.cjs +6 -550
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -23
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +3 -23
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +7 -549
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -42,70 +42,6 @@ function isANode$1(node) {
|
|
|
42
42
|
const keys = Object.keys(node);
|
|
43
43
|
return keys.length === 1 && /^[A-Z]/.test(keys[0]);
|
|
44
44
|
}
|
|
45
|
-
/** Operators that require a GiST index for efficient execution */
|
|
46
|
-
const GIST_OPERATORS = new Set(["&&", "-|-"]);
|
|
47
|
-
/** PostGIS / spatial functions that require a GiST index */
|
|
48
|
-
const SPATIAL_FUNCTIONS = new Set([
|
|
49
|
-
"st_intersects",
|
|
50
|
-
"st_contains",
|
|
51
|
-
"st_within",
|
|
52
|
-
"st_dwithin",
|
|
53
|
-
"st_covers",
|
|
54
|
-
"st_coveredby",
|
|
55
|
-
"st_crosses",
|
|
56
|
-
"st_overlaps",
|
|
57
|
-
"st_touches"
|
|
58
|
-
]);
|
|
59
|
-
const JSONB_EXTRACTION_OPS = new Set([
|
|
60
|
-
"->>",
|
|
61
|
-
"->",
|
|
62
|
-
"#>>",
|
|
63
|
-
"#>"
|
|
64
|
-
]);
|
|
65
|
-
function getAExprOpName(node) {
|
|
66
|
-
const nameNode = node.A_Expr.name?.[0];
|
|
67
|
-
if (nameNode && is$1(nameNode, "String") && nameNode.String.sval) return nameNode.String.sval;
|
|
68
|
-
return null;
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* Check if a node is or contains a JSONB extraction expression,
|
|
72
|
-
* unwrapping through TypeCast. Returns the extraction A_Expr if found.
|
|
73
|
-
*/
|
|
74
|
-
function findExtractionExpr(node) {
|
|
75
|
-
if (is$1(node, "A_Expr")) {
|
|
76
|
-
const op = getAExprOpName(node);
|
|
77
|
-
if (op && JSONB_EXTRACTION_OPS.has(op)) {
|
|
78
|
-
if (node.A_Expr.lexpr && hasColumnRefInNode(node.A_Expr.lexpr)) return node;
|
|
79
|
-
return null;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
if (is$1(node, "TypeCast") && node.TypeCast.arg) return findExtractionExpr(node.TypeCast.arg);
|
|
83
|
-
return null;
|
|
84
|
-
}
|
|
85
|
-
const ARITHMETIC_OPERATORS = new Set([
|
|
86
|
-
"+",
|
|
87
|
-
"-",
|
|
88
|
-
"*",
|
|
89
|
-
"/",
|
|
90
|
-
"%"
|
|
91
|
-
]);
|
|
92
|
-
const isArithmeticExpr = (node) => {
|
|
93
|
-
if (!node || typeof node !== "object") return false;
|
|
94
|
-
if (!isANode$1(node) || !is$1(node, "A_Expr")) return false;
|
|
95
|
-
if (node.A_Expr.kind !== "AEXPR_OP" || !node.A_Expr.name?.length) return false;
|
|
96
|
-
const opNode = node.A_Expr.name[0];
|
|
97
|
-
if (!is$1(opNode, "String") || !opNode.String.sval) return false;
|
|
98
|
-
return ARITHMETIC_OPERATORS.has(opNode.String.sval);
|
|
99
|
-
};
|
|
100
|
-
const COMPARISON_OPERATORS = new Set([
|
|
101
|
-
"=",
|
|
102
|
-
"<",
|
|
103
|
-
">",
|
|
104
|
-
"<=",
|
|
105
|
-
">=",
|
|
106
|
-
"<>",
|
|
107
|
-
"!="
|
|
108
|
-
]);
|
|
109
45
|
const findFuncCallsOnColumns = (whereClause) => {
|
|
110
46
|
const nudges = [];
|
|
111
47
|
Walker.shallowMatch(whereClause, "FuncCall", (node) => {
|
|
@@ -116,77 +52,6 @@ const findFuncCallsOnColumns = (whereClause) => {
|
|
|
116
52
|
location: node.FuncCall.location
|
|
117
53
|
});
|
|
118
54
|
});
|
|
119
|
-
Walker.shallowMatch(whereClause, "CoalesceExpr", (node) => {
|
|
120
|
-
if (node.CoalesceExpr.args && containsColumnRef(node.CoalesceExpr.args)) nudges.push({
|
|
121
|
-
kind: "AVOID_FUNCTIONS_ON_COLUMNS_IN_WHERE",
|
|
122
|
-
severity: "WARNING",
|
|
123
|
-
message: "Avoid using functions on columns in WHERE clause",
|
|
124
|
-
location: node.CoalesceExpr.location
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
return nudges;
|
|
128
|
-
};
|
|
129
|
-
const findJsonbExtractionInWhere = (whereClause) => {
|
|
130
|
-
const nudges = [];
|
|
131
|
-
const seen = /* @__PURE__ */ new Set();
|
|
132
|
-
function emit(node) {
|
|
133
|
-
const extraction = findExtractionExpr(node);
|
|
134
|
-
if (extraction && extraction.A_Expr.location !== void 0 && !seen.has(extraction.A_Expr.location)) {
|
|
135
|
-
seen.add(extraction.A_Expr.location);
|
|
136
|
-
nudges.push({
|
|
137
|
-
kind: "CONSIDER_EXPRESSION_INDEX_FOR_JSONB_EXTRACTION",
|
|
138
|
-
severity: "INFO",
|
|
139
|
-
message: "Consider an expression B-tree index for JSONB path extraction",
|
|
140
|
-
location: extraction.A_Expr.location
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
function walk(node) {
|
|
145
|
-
if (is$1(node, "A_Expr")) {
|
|
146
|
-
const op = getAExprOpName(node);
|
|
147
|
-
if (op && JSONB_EXTRACTION_OPS.has(op)) return;
|
|
148
|
-
if (node.A_Expr.lexpr) emit(node.A_Expr.lexpr);
|
|
149
|
-
if (node.A_Expr.rexpr) emit(node.A_Expr.rexpr);
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
|
-
if (is$1(node, "BoolExpr") && node.BoolExpr.args) {
|
|
153
|
-
for (const arg of node.BoolExpr.args) walk(arg);
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
156
|
-
if (is$1(node, "NullTest") && node.NullTest.arg) {
|
|
157
|
-
emit(node.NullTest.arg);
|
|
158
|
-
return;
|
|
159
|
-
}
|
|
160
|
-
if (is$1(node, "BooleanTest") && node.BooleanTest.arg) {
|
|
161
|
-
emit(node.BooleanTest.arg);
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
if (is$1(node, "SubLink") && node.SubLink.testexpr) {
|
|
165
|
-
emit(node.SubLink.testexpr);
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
walk(whereClause);
|
|
170
|
-
return nudges;
|
|
171
|
-
};
|
|
172
|
-
const findArithmeticExprsOnColumns = (whereClause) => {
|
|
173
|
-
const nudges = [];
|
|
174
|
-
Walker.shallowMatch(whereClause, "A_Expr", (node) => {
|
|
175
|
-
if (node.A_Expr.kind !== "AEXPR_OP" || !node.A_Expr.name?.length) return;
|
|
176
|
-
const opNode = node.A_Expr.name[0];
|
|
177
|
-
if (!is$1(opNode, "String") || !opNode.String.sval) return;
|
|
178
|
-
if (!COMPARISON_OPERATORS.has(opNode.String.sval)) return;
|
|
179
|
-
const sides = [node.A_Expr.lexpr, node.A_Expr.rexpr];
|
|
180
|
-
for (const side of sides) if (side && isArithmeticExpr(side) && hasColumnRefInNode(side)) {
|
|
181
|
-
const expr = side;
|
|
182
|
-
nudges.push({
|
|
183
|
-
kind: "AVOID_EXPRESSIONS_ON_COLUMNS_IN_WHERE",
|
|
184
|
-
severity: "WARNING",
|
|
185
|
-
message: "Avoid using arithmetic expressions on columns in WHERE clause",
|
|
186
|
-
location: expr.A_Expr.location
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
});
|
|
190
55
|
return nudges;
|
|
191
56
|
};
|
|
192
57
|
/**
|
|
@@ -196,11 +61,7 @@ const findArithmeticExprsOnColumns = (whereClause) => {
|
|
|
196
61
|
function parseNudges(node, stack) {
|
|
197
62
|
const nudges = [];
|
|
198
63
|
if (is$1(node, "SelectStmt")) {
|
|
199
|
-
if (node.SelectStmt.whereClause)
|
|
200
|
-
nudges.push(...findFuncCallsOnColumns(node.SelectStmt.whereClause));
|
|
201
|
-
nudges.push(...findJsonbExtractionInWhere(node.SelectStmt.whereClause));
|
|
202
|
-
nudges.push(...findArithmeticExprsOnColumns(node.SelectStmt.whereClause));
|
|
203
|
-
}
|
|
64
|
+
if (node.SelectStmt.whereClause) nudges.push(...findFuncCallsOnColumns(node.SelectStmt.whereClause));
|
|
204
65
|
const star = node.SelectStmt.targetList?.find((target) => {
|
|
205
66
|
if (!(is$1(target, "ResTarget") && target.ResTarget.val && is$1(target.ResTarget.val, "ColumnRef"))) return false;
|
|
206
67
|
const fields = target.ResTarget.val.ColumnRef.fields;
|
|
@@ -217,12 +78,6 @@ function parseNudges(node, stack) {
|
|
|
217
78
|
location: star.ResTarget.location
|
|
218
79
|
});
|
|
219
80
|
}
|
|
220
|
-
for (const target of node.SelectStmt.targetList ?? []) if (is$1(target, "ResTarget") && target.ResTarget.val && is$1(target.ResTarget.val, "SubLink") && target.ResTarget.val.SubLink.subLinkType === "EXPR_SUBLINK") nudges.push({
|
|
221
|
-
kind: "AVOID_SCALAR_SUBQUERY_IN_SELECT",
|
|
222
|
-
severity: "WARNING",
|
|
223
|
-
message: "Avoid correlated scalar subqueries in SELECT; consider rewriting as a JOIN",
|
|
224
|
-
location: target.ResTarget.val.SubLink.location
|
|
225
|
-
});
|
|
226
81
|
}
|
|
227
82
|
if (is$1(node, "SelectStmt")) {
|
|
228
83
|
if (!stack.some((item) => item === "RangeSubselect" || item === "SubLink" || item === "CommonTableExpr")) {
|
|
@@ -247,44 +102,8 @@ function parseNudges(node, stack) {
|
|
|
247
102
|
}
|
|
248
103
|
}
|
|
249
104
|
}
|
|
250
|
-
if (is$1(node, "SelectStmt")) {
|
|
251
|
-
if (!stack.some((item) => item === "RangeSubselect" || item === "SubLink" || item === "CommonTableExpr") && node.SelectStmt.sortClause && node.SelectStmt.whereClause) {
|
|
252
|
-
const equalityColumns = collectEqualityConstrainedColumns(node.SelectStmt.whereClause);
|
|
253
|
-
for (const sortItem of node.SelectStmt.sortClause) {
|
|
254
|
-
if (!is$1(sortItem, "SortBy") || !sortItem.SortBy.node) continue;
|
|
255
|
-
const sortNode = sortItem.SortBy.node;
|
|
256
|
-
if (!is$1(sortNode, "ColumnRef") || !sortNode.ColumnRef.fields) continue;
|
|
257
|
-
const lastField = sortNode.ColumnRef.fields[sortNode.ColumnRef.fields.length - 1];
|
|
258
|
-
if (!is$1(lastField, "String")) continue;
|
|
259
|
-
const sortCol = lastField.String.sval;
|
|
260
|
-
if (sortCol && equalityColumns.has(sortCol)) nudges.push({
|
|
261
|
-
kind: "NOOP_ORDER_BY",
|
|
262
|
-
severity: "INFO",
|
|
263
|
-
message: "ORDER BY column is constrained to a single value by WHERE clause",
|
|
264
|
-
location: sortNode.ColumnRef.location
|
|
265
|
-
});
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
if (is$1(node, "SelectStmt") && node.SelectStmt.sortClause) for (const sortItem of node.SelectStmt.sortClause) {
|
|
270
|
-
if (!is$1(sortItem, "SortBy")) continue;
|
|
271
|
-
const sortDir = sortItem.SortBy.sortby_dir ?? "SORTBY_DEFAULT";
|
|
272
|
-
const sortNulls = sortItem.SortBy.sortby_nulls ?? "SORTBY_NULLS_DEFAULT";
|
|
273
|
-
if (sortDir === "SORTBY_DESC" && sortNulls === "SORTBY_NULLS_DEFAULT") {
|
|
274
|
-
if (sortItem.SortBy.node && is$1(sortItem.SortBy.node, "ColumnRef")) {
|
|
275
|
-
const sortColumnName = getLastColumnRefField(sortItem.SortBy.node);
|
|
276
|
-
if (!(sortColumnName !== null && whereHasIsNotNull(node.SelectStmt.whereClause, sortColumnName))) nudges.push({
|
|
277
|
-
kind: "NULLS_FIRST_IN_DESC_ORDER",
|
|
278
|
-
severity: "INFO",
|
|
279
|
-
message: "ORDER BY … DESC sorts NULLs first — add NULLS LAST to push them to the end",
|
|
280
|
-
location: sortItem.SortBy.node.ColumnRef.location
|
|
281
|
-
});
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
105
|
if (is$1(node, "A_Expr")) {
|
|
286
|
-
|
|
287
|
-
if (isEqualityOp) {
|
|
106
|
+
if (node.A_Expr.kind === "AEXPR_OP" && node.A_Expr.name && node.A_Expr.name.length > 0 && is$1(node.A_Expr.name[0], "String") && (node.A_Expr.name[0].String.sval === "=" || node.A_Expr.name[0].String.sval === "!=" || node.A_Expr.name[0].String.sval === "<>")) {
|
|
288
107
|
const leftIsNull = isNullConstant(node.A_Expr.lexpr);
|
|
289
108
|
const rightIsNull = isNullConstant(node.A_Expr.rexpr);
|
|
290
109
|
if (leftIsNull || rightIsNull) nudges.push({
|
|
@@ -294,8 +113,7 @@ function parseNudges(node, stack) {
|
|
|
294
113
|
location: node.A_Expr.location
|
|
295
114
|
});
|
|
296
115
|
}
|
|
297
|
-
|
|
298
|
-
if (isLikeOp && node.A_Expr.rexpr) {
|
|
116
|
+
if ((node.A_Expr.kind === "AEXPR_LIKE" || node.A_Expr.kind === "AEXPR_ILIKE") && node.A_Expr.rexpr) {
|
|
299
117
|
const patternString = getStringConstantValue(node.A_Expr.rexpr);
|
|
300
118
|
if (patternString && patternString.startsWith("%")) {
|
|
301
119
|
let stringNode;
|
|
@@ -303,44 +121,17 @@ function parseNudges(node, stack) {
|
|
|
303
121
|
nudges.push({
|
|
304
122
|
kind: "AVOID_LEADING_WILDCARD_LIKE",
|
|
305
123
|
severity: "WARNING",
|
|
306
|
-
message: "
|
|
124
|
+
message: "Avoid using LIKE with leading wildcards",
|
|
307
125
|
location: stringNode?.location
|
|
308
126
|
});
|
|
309
127
|
}
|
|
310
128
|
}
|
|
311
|
-
if (isEqualityOp === false && isLikeOp === false) {
|
|
312
|
-
const gistOpName = node.A_Expr.kind === "AEXPR_OP" && node.A_Expr.name?.[0] && is$1(node.A_Expr.name[0], "String") && node.A_Expr.name[0].String.sval;
|
|
313
|
-
if (gistOpName && GIST_OPERATORS.has(gistOpName)) nudges.push({
|
|
314
|
-
kind: "CONSIDER_GIST_INDEX",
|
|
315
|
-
severity: "INFO",
|
|
316
|
-
message: `Operator "${gistOpName}" benefits from a GiST index for efficient execution`,
|
|
317
|
-
location: node.A_Expr.location
|
|
318
|
-
});
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
if (is$1(node, "SelectStmt") && node.SelectStmt.sortClause) {
|
|
322
|
-
for (const sortItem of node.SelectStmt.sortClause) if (is$1(sortItem, "SortBy") && sortItem.SortBy.node && is$1(sortItem.SortBy.node, "FuncCall") && sortItem.SortBy.node.FuncCall.funcname?.some((name) => is$1(name, "String") && name.String.sval === "random")) nudges.push({
|
|
323
|
-
kind: "AVOID_ORDER_BY_RANDOM",
|
|
324
|
-
severity: "WARNING",
|
|
325
|
-
message: "Avoid using ORDER BY random()",
|
|
326
|
-
location: sortItem.SortBy.node.FuncCall.location
|
|
327
|
-
});
|
|
328
129
|
}
|
|
329
130
|
if (is$1(node, "SelectStmt") && node.SelectStmt.distinctClause) nudges.push({
|
|
330
131
|
kind: "AVOID_DISTINCT_WITHOUT_REASON",
|
|
331
132
|
severity: "WARNING",
|
|
332
133
|
message: "Avoid using DISTINCT without a reason"
|
|
333
134
|
});
|
|
334
|
-
if (is$1(node, "SelectStmt") && node.SelectStmt.limitOffset) {
|
|
335
|
-
const offsetNode = node.SelectStmt.limitOffset;
|
|
336
|
-
const location = isANode$1(offsetNode) && is$1(offsetNode, "A_Const") ? offsetNode.A_Const.location : void 0;
|
|
337
|
-
nudges.push({
|
|
338
|
-
kind: "AVOID_OFFSET_FOR_PAGINATION",
|
|
339
|
-
severity: "INFO",
|
|
340
|
-
message: "Avoid using OFFSET for pagination",
|
|
341
|
-
location
|
|
342
|
-
});
|
|
343
|
-
}
|
|
344
135
|
if (is$1(node, "JoinExpr")) {
|
|
345
136
|
if (!node.JoinExpr.quals) nudges.push({
|
|
346
137
|
kind: "MISSING_JOIN_CONDITION",
|
|
@@ -377,93 +168,6 @@ function parseNudges(node, stack) {
|
|
|
377
168
|
});
|
|
378
169
|
}
|
|
379
170
|
}
|
|
380
|
-
if (is$1(node, "SelectStmt") && node.SelectStmt.havingClause) {
|
|
381
|
-
if (!containsAggregate(node.SelectStmt.havingClause)) {
|
|
382
|
-
const having = node.SelectStmt.havingClause;
|
|
383
|
-
let location;
|
|
384
|
-
if (is$1(having, "A_Expr")) location = having.A_Expr.location;
|
|
385
|
-
else if (is$1(having, "BoolExpr")) location = having.BoolExpr.location;
|
|
386
|
-
nudges.push({
|
|
387
|
-
kind: "PREFER_WHERE_OVER_HAVING_FOR_NON_AGGREGATES",
|
|
388
|
-
severity: "INFO",
|
|
389
|
-
message: "Non-aggregate condition in HAVING should be in WHERE",
|
|
390
|
-
location
|
|
391
|
-
});
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
if (is$1(node, "FuncCall")) {
|
|
395
|
-
const funcName = node.FuncCall.funcname;
|
|
396
|
-
if (funcName && funcName.length === 1 && is$1(funcName[0], "String") && funcName[0].String.sval === "count" && node.FuncCall.args && !node.FuncCall.agg_star && !node.FuncCall.agg_distinct) nudges.push({
|
|
397
|
-
kind: "PREFER_COUNT_STAR_OVER_COUNT_COLUMN",
|
|
398
|
-
severity: "INFO",
|
|
399
|
-
message: "Prefer COUNT(*) over COUNT(column) or COUNT(1) — COUNT(*) counts rows without checking for NULLs. If you need to count non-NULL values, COUNT(column) is correct.",
|
|
400
|
-
location: node.FuncCall.location
|
|
401
|
-
});
|
|
402
|
-
}
|
|
403
|
-
if (is$1(node, "A_Expr") && node.A_Expr.kind === "AEXPR_OP" && node.A_Expr.name && node.A_Expr.name.length > 0) {
|
|
404
|
-
const opNode = node.A_Expr.name[0];
|
|
405
|
-
const op = is$1(opNode, "String") ? opNode.String.sval : null;
|
|
406
|
-
if (op && isExistenceCheckPattern(node.A_Expr.lexpr, node.A_Expr.rexpr, op)) nudges.push({
|
|
407
|
-
kind: "USE_EXISTS_NOT_COUNT_FOR_EXISTENCE_CHECK",
|
|
408
|
-
severity: "INFO",
|
|
409
|
-
message: "Use EXISTS instead of COUNT for existence checks",
|
|
410
|
-
location: node.A_Expr.location
|
|
411
|
-
});
|
|
412
|
-
}
|
|
413
|
-
if (is$1(node, "FuncCall")) {
|
|
414
|
-
const funcname = node.FuncCall.funcname?.[0] && is$1(node.FuncCall.funcname[0], "String") && node.FuncCall.funcname[0].String.sval;
|
|
415
|
-
if (funcname && [
|
|
416
|
-
"sum",
|
|
417
|
-
"count",
|
|
418
|
-
"avg",
|
|
419
|
-
"min",
|
|
420
|
-
"max"
|
|
421
|
-
].includes(funcname.toLowerCase())) {
|
|
422
|
-
const firstArg = node.FuncCall.args?.[0];
|
|
423
|
-
if (firstArg && isANode$1(firstArg) && is$1(firstArg, "CaseExpr")) {
|
|
424
|
-
const caseExpr = firstArg.CaseExpr;
|
|
425
|
-
if (caseExpr.args && caseExpr.args.length === 1) {
|
|
426
|
-
const defresult = caseExpr.defresult;
|
|
427
|
-
if (!defresult || isANode$1(defresult) && is$1(defresult, "A_Const") && (defresult.A_Const.isnull !== void 0 || defresult.A_Const.ival !== void 0 && (defresult.A_Const.ival.ival === 0 || defresult.A_Const.ival.ival === void 0))) nudges.push({
|
|
428
|
-
kind: "PREFER_FILTER_OVER_CASE_IN_AGGREGATE",
|
|
429
|
-
severity: "INFO",
|
|
430
|
-
message: "Use FILTER (WHERE ...) instead of CASE inside aggregate functions",
|
|
431
|
-
location: node.FuncCall.location
|
|
432
|
-
});
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
if (is$1(node, "FuncCall") && node.FuncCall.funcname) {
|
|
438
|
-
const lastNameNode = node.FuncCall.funcname[node.FuncCall.funcname.length - 1];
|
|
439
|
-
if (lastNameNode && is$1(lastNameNode, "String") && lastNameNode.String.sval) {
|
|
440
|
-
const funcName = lastNameNode.String.sval.toLowerCase();
|
|
441
|
-
if (SPATIAL_FUNCTIONS.has(funcName)) nudges.push({
|
|
442
|
-
kind: "CONSIDER_GIST_INDEX",
|
|
443
|
-
severity: "INFO",
|
|
444
|
-
message: `Spatial function "${lastNameNode.String.sval}" benefits from a GiST index for efficient execution`,
|
|
445
|
-
location: node.FuncCall.location
|
|
446
|
-
});
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
if (is$1(node, "BoolExpr") && node.BoolExpr.boolop === "NOT_EXPR") {
|
|
450
|
-
const args = node.BoolExpr.args;
|
|
451
|
-
if (args && args.length === 1) {
|
|
452
|
-
const arg = args[0];
|
|
453
|
-
if (isANode$1(arg) && is$1(arg, "SubLink") && arg.SubLink.subLinkType === "EXISTS_SUBLINK" && arg.SubLink.subselect) {
|
|
454
|
-
const subselect = arg.SubLink.subselect;
|
|
455
|
-
if (isANode$1(subselect) && is$1(subselect, "SelectStmt") && subselect.SelectStmt.whereClause) {
|
|
456
|
-
const where = subselect.SelectStmt.whereClause;
|
|
457
|
-
if (isANode$1(where) && is$1(where, "BoolExpr") && where.BoolExpr.boolop === "OR_EXPR") nudges.push({
|
|
458
|
-
kind: "FLATTEN_NOT_EXISTS_OR",
|
|
459
|
-
severity: "INFO",
|
|
460
|
-
message: "Consider splitting NOT EXISTS with OR into separate NOT EXISTS joined by AND",
|
|
461
|
-
location: node.BoolExpr.location
|
|
462
|
-
});
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
171
|
if (is$1(node, "A_Expr")) {
|
|
468
172
|
if (node.A_Expr.kind === "AEXPR_IN") {
|
|
469
173
|
let list;
|
|
@@ -477,36 +181,6 @@ function parseNudges(node, stack) {
|
|
|
477
181
|
});
|
|
478
182
|
}
|
|
479
183
|
}
|
|
480
|
-
if (is$1(node, "SelectStmt") && node.SelectStmt.op === "SETOP_UNION" && !node.SelectStmt.all) nudges.push({
|
|
481
|
-
kind: "PREFER_UNION_ALL_OVER_UNION",
|
|
482
|
-
severity: "INFO",
|
|
483
|
-
message: "UNION removes duplicates with an implicit sort — use UNION ALL if deduplication is not needed"
|
|
484
|
-
});
|
|
485
|
-
if (is$1(node, "SelectStmt")) {
|
|
486
|
-
const subqueryAliases = /* @__PURE__ */ new Set();
|
|
487
|
-
if (node.SelectStmt.targetList) {
|
|
488
|
-
for (const target of node.SelectStmt.targetList) if (is$1(target, "ResTarget")) {
|
|
489
|
-
if (target.ResTarget.val && is$1(target.ResTarget.val, "SubLink")) {
|
|
490
|
-
if (target.ResTarget.name) subqueryAliases.add(target.ResTarget.name.toLowerCase());
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
if (subqueryAliases.size > 0 && node.SelectStmt.sortClause) {
|
|
495
|
-
for (const sortBy of node.SelectStmt.sortClause) if (is$1(sortBy, "SortBy") && sortBy.SortBy.node) {
|
|
496
|
-
if (is$1(sortBy.SortBy.node, "ColumnRef")) {
|
|
497
|
-
const columnName = extractColumnName(sortBy.SortBy.node);
|
|
498
|
-
if (columnName && subqueryAliases.has(columnName.toLowerCase())) {
|
|
499
|
-
nudges.push({
|
|
500
|
-
kind: "AVOID_SORTING_ON_GENERATED_VALUES",
|
|
501
|
-
severity: "WARNING",
|
|
502
|
-
message: "Be careful sorting on values generated dynamically during query execution - these cannot use indexes and contribute to slower query performance."
|
|
503
|
-
});
|
|
504
|
-
break;
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
184
|
return nudges;
|
|
511
185
|
}
|
|
512
186
|
function containsColumnRef(args) {
|
|
@@ -542,142 +216,6 @@ function getStringConstantValue(node) {
|
|
|
542
216
|
if (isANode$1(node) && is$1(node, "A_Const") && node.A_Const.sval) return node.A_Const.sval.sval || null;
|
|
543
217
|
return null;
|
|
544
218
|
}
|
|
545
|
-
const AGGREGATE_FUNCTIONS = new Set([
|
|
546
|
-
"count",
|
|
547
|
-
"sum",
|
|
548
|
-
"avg",
|
|
549
|
-
"min",
|
|
550
|
-
"max",
|
|
551
|
-
"array_agg",
|
|
552
|
-
"string_agg",
|
|
553
|
-
"bool_and",
|
|
554
|
-
"bool_or",
|
|
555
|
-
"every"
|
|
556
|
-
]);
|
|
557
|
-
function containsAggregate(node) {
|
|
558
|
-
if (!node || typeof node !== "object") return false;
|
|
559
|
-
if (Array.isArray(node)) return node.some(containsAggregate);
|
|
560
|
-
if (isANode$1(node) && is$1(node, "FuncCall")) {
|
|
561
|
-
const funcname = node.FuncCall.funcname;
|
|
562
|
-
if (funcname) {
|
|
563
|
-
for (const f of funcname) if (isANode$1(f) && is$1(f, "String") && AGGREGATE_FUNCTIONS.has(f.String.sval?.toLowerCase() ?? "")) return true;
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
if (isANode$1(node)) return containsAggregate(node[Object.keys(node)[0]]);
|
|
567
|
-
for (const child of Object.values(node)) if (containsAggregate(child)) return true;
|
|
568
|
-
return false;
|
|
569
|
-
}
|
|
570
|
-
function getLastColumnRefField(columnRef) {
|
|
571
|
-
const fields = columnRef.ColumnRef.fields;
|
|
572
|
-
if (!fields || fields.length === 0) return null;
|
|
573
|
-
const lastField = fields[fields.length - 1];
|
|
574
|
-
if (isANode$1(lastField) && is$1(lastField, "String")) return lastField.String.sval || null;
|
|
575
|
-
return null;
|
|
576
|
-
}
|
|
577
|
-
function whereHasIsNotNull(whereClause, columnName) {
|
|
578
|
-
if (!whereClause) return false;
|
|
579
|
-
let found = false;
|
|
580
|
-
Walker.shallowMatch(whereClause, "NullTest", (node) => {
|
|
581
|
-
if (node.NullTest.nulltesttype === "IS_NOT_NULL" && node.NullTest.arg && is$1(node.NullTest.arg, "ColumnRef")) {
|
|
582
|
-
if (getLastColumnRefField(node.NullTest.arg) === columnName) found = true;
|
|
583
|
-
}
|
|
584
|
-
});
|
|
585
|
-
return found;
|
|
586
|
-
}
|
|
587
|
-
function isCountFuncCall(node) {
|
|
588
|
-
if (!node || typeof node !== "object") return false;
|
|
589
|
-
if (!isANode$1(node) || !is$1(node, "FuncCall")) return false;
|
|
590
|
-
const fc = node.FuncCall;
|
|
591
|
-
if (!(fc.funcname?.some((n) => is$1(n, "String") && n.String.sval === "count") ?? false)) return false;
|
|
592
|
-
if (fc.agg_star) return true;
|
|
593
|
-
if (fc.args && fc.args.length === 1 && isANode$1(fc.args[0]) && is$1(fc.args[0], "A_Const")) return true;
|
|
594
|
-
return false;
|
|
595
|
-
}
|
|
596
|
-
function isSubLinkWithCount(node) {
|
|
597
|
-
if (!node || typeof node !== "object") return false;
|
|
598
|
-
if (!isANode$1(node) || !is$1(node, "SubLink")) return false;
|
|
599
|
-
const subselect = node.SubLink.subselect;
|
|
600
|
-
if (!subselect || !isANode$1(subselect) || !is$1(subselect, "SelectStmt")) return false;
|
|
601
|
-
const targets = subselect.SelectStmt.targetList;
|
|
602
|
-
if (!targets || targets.length !== 1) return false;
|
|
603
|
-
const target = targets[0];
|
|
604
|
-
if (!isANode$1(target) || !is$1(target, "ResTarget") || !target.ResTarget.val) return false;
|
|
605
|
-
return isCountFuncCall(target.ResTarget.val);
|
|
606
|
-
}
|
|
607
|
-
function isCountExpression(node) {
|
|
608
|
-
return isCountFuncCall(node) || isSubLinkWithCount(node);
|
|
609
|
-
}
|
|
610
|
-
function getIntegerConstantValue(node) {
|
|
611
|
-
if (!node || typeof node !== "object") return null;
|
|
612
|
-
if (!isANode$1(node) || !is$1(node, "A_Const")) return null;
|
|
613
|
-
if (node.A_Const.ival === void 0) return null;
|
|
614
|
-
return node.A_Const.ival.ival ?? 0;
|
|
615
|
-
}
|
|
616
|
-
function isExistenceCheckPattern(lexpr, rexpr, op) {
|
|
617
|
-
if (isCountExpression(lexpr)) {
|
|
618
|
-
const val = getIntegerConstantValue(rexpr);
|
|
619
|
-
if (val !== null) {
|
|
620
|
-
if (op === ">" && val === 0) return true;
|
|
621
|
-
if (op === ">=" && val === 1) return true;
|
|
622
|
-
if ((op === "!=" || op === "<>") && val === 0) return true;
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
if (isCountExpression(rexpr)) {
|
|
626
|
-
const val = getIntegerConstantValue(lexpr);
|
|
627
|
-
if (val !== null) {
|
|
628
|
-
if (op === "<" && val === 0) return true;
|
|
629
|
-
if (op === "<=" && val === 1) return true;
|
|
630
|
-
if ((op === "!=" || op === "<>") && val === 0) return true;
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
return false;
|
|
634
|
-
}
|
|
635
|
-
function collectEqualityConstrainedColumns(whereClause) {
|
|
636
|
-
const columns = /* @__PURE__ */ new Set();
|
|
637
|
-
function walk(node) {
|
|
638
|
-
if (!node || typeof node !== "object") return;
|
|
639
|
-
if (!isANode$1(node)) return;
|
|
640
|
-
if (is$1(node, "BoolExpr") && node.BoolExpr.boolop === "AND_EXPR" && node.BoolExpr.args) {
|
|
641
|
-
for (const arg of node.BoolExpr.args) walk(arg);
|
|
642
|
-
return;
|
|
643
|
-
}
|
|
644
|
-
if (is$1(node, "A_Expr") && node.A_Expr.kind === "AEXPR_OP") {
|
|
645
|
-
const opName = node.A_Expr.name?.[0];
|
|
646
|
-
if (opName && is$1(opName, "String") && opName.String.sval === "=") {
|
|
647
|
-
addColumnIfConstant(node.A_Expr.lexpr, node.A_Expr.rexpr, columns);
|
|
648
|
-
addColumnIfConstant(node.A_Expr.rexpr, node.A_Expr.lexpr, columns);
|
|
649
|
-
}
|
|
650
|
-
return;
|
|
651
|
-
}
|
|
652
|
-
if (is$1(node, "A_Expr") && node.A_Expr.kind === "AEXPR_IN") {
|
|
653
|
-
const listNode = node.A_Expr.rexpr;
|
|
654
|
-
if (listNode && isANode$1(listNode) && is$1(listNode, "List") && listNode.List.items && listNode.List.items.length === 1) {
|
|
655
|
-
const colNode = node.A_Expr.lexpr;
|
|
656
|
-
if (colNode && isANode$1(colNode) && is$1(colNode, "ColumnRef")) {
|
|
657
|
-
const lastField = colNode.ColumnRef.fields?.[colNode.ColumnRef.fields.length - 1];
|
|
658
|
-
if (lastField && is$1(lastField, "String") && lastField.String.sval) columns.add(lastField.String.sval);
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
walk(whereClause);
|
|
664
|
-
return columns;
|
|
665
|
-
}
|
|
666
|
-
function addColumnIfConstant(colSide, constSide, columns) {
|
|
667
|
-
if (!colSide || !constSide || typeof colSide !== "object" || typeof constSide !== "object") return;
|
|
668
|
-
if (!isANode$1(colSide) || !is$1(colSide, "ColumnRef")) return;
|
|
669
|
-
if (!isANode$1(constSide) || !is$1(constSide, "A_Const") && !is$1(constSide, "ParamRef")) return;
|
|
670
|
-
const fields = colSide.ColumnRef.fields;
|
|
671
|
-
if (!fields || fields.length === 0) return;
|
|
672
|
-
const lastField = fields[fields.length - 1];
|
|
673
|
-
if (is$1(lastField, "String") && lastField.String.sval) columns.add(lastField.String.sval);
|
|
674
|
-
}
|
|
675
|
-
function extractColumnName(node) {
|
|
676
|
-
if (!node.ColumnRef.fields || node.ColumnRef.fields.length === 0) return null;
|
|
677
|
-
const lastField = node.ColumnRef.fields[node.ColumnRef.fields.length - 1];
|
|
678
|
-
if (is$1(lastField, "String") && lastField.String.sval) return lastField.String.sval;
|
|
679
|
-
return null;
|
|
680
|
-
}
|
|
681
219
|
function countBoolOrConditions(node) {
|
|
682
220
|
if (node.BoolExpr.boolop !== "OR_EXPR" || !node.BoolExpr.args) return 1;
|
|
683
221
|
let count = 0;
|
|
@@ -685,28 +223,6 @@ function countBoolOrConditions(node) {
|
|
|
685
223
|
else count += 1;
|
|
686
224
|
return count;
|
|
687
225
|
}
|
|
688
|
-
/**
|
|
689
|
-
* Extract the major version number from a PostgreSQL version string.
|
|
690
|
-
* PostgreSQL encodes versions as XXYYZZ where XX is major, YY is minor, ZZ is patch.
|
|
691
|
-
* e.g. "170000" → 17, "160004" → 16, "120000" → 12
|
|
692
|
-
*/
|
|
693
|
-
function parseMajorVersion(versionNum) {
|
|
694
|
-
return Math.floor(parseInt(versionNum, 10) / 1e4);
|
|
695
|
-
}
|
|
696
|
-
/**
|
|
697
|
-
* Produce version-aware nudges by checking existing nudges against the
|
|
698
|
-
* user's PostgreSQL major version. Returns upgrade recommendations
|
|
699
|
-
* when a query pattern would benefit from a newer version.
|
|
700
|
-
*/
|
|
701
|
-
function parseVersionNudges(nudges, majorVersion) {
|
|
702
|
-
const versionNudges = [];
|
|
703
|
-
if (majorVersion < 17 && nudges.some((n) => n.kind === "AVOID_DISTINCT_WITHOUT_REASON")) versionNudges.push({
|
|
704
|
-
kind: "UPGRADE_DISTINCT_PG17",
|
|
705
|
-
severity: "INFO",
|
|
706
|
-
message: "PostgreSQL 17 introduces hash-based DISTINCT, which can significantly speed up queries using DISTINCT"
|
|
707
|
-
});
|
|
708
|
-
return versionNudges;
|
|
709
|
-
}
|
|
710
226
|
|
|
711
227
|
//#endregion
|
|
712
228
|
//#region \0@oxc-project+runtime@0.112.0/helpers/typeof.js
|
|
@@ -837,11 +353,6 @@ var Walker = class Walker {
|
|
|
837
353
|
if (node.A_Expr.lexpr && is(node.A_Expr.lexpr, "ColumnRef")) this.add(node.A_Expr.lexpr, { jsonbOperator });
|
|
838
354
|
if (node.A_Expr.rexpr && is(node.A_Expr.rexpr, "ColumnRef")) this.add(node.A_Expr.rexpr, { jsonbOperator });
|
|
839
355
|
}
|
|
840
|
-
if (opName && (opName === "&&" || opName === "-|-")) {
|
|
841
|
-
const gistOperator = opName;
|
|
842
|
-
if (node.A_Expr.lexpr && is(node.A_Expr.lexpr, "ColumnRef")) this.add(node.A_Expr.lexpr, { gistOperator });
|
|
843
|
-
if (node.A_Expr.rexpr && is(node.A_Expr.rexpr, "ColumnRef")) this.add(node.A_Expr.rexpr, { gistOperator });
|
|
844
|
-
}
|
|
845
356
|
}
|
|
846
357
|
if (is(node, "ColumnRef")) {
|
|
847
358
|
for (let i = 0; i < stack.length; i++) {
|
|
@@ -925,7 +436,6 @@ var Walker = class Walker {
|
|
|
925
436
|
if (options?.sort) ref.sort = options.sort;
|
|
926
437
|
if (options?.where) ref.where = options.where;
|
|
927
438
|
if (options?.jsonbOperator) ref.jsonbOperator = options.jsonbOperator;
|
|
928
|
-
if (options?.gistOperator) ref.gistOperator = options.gistOperator;
|
|
929
439
|
this.highlights.push(ref);
|
|
930
440
|
}
|
|
931
441
|
/**
|
|
@@ -1094,7 +604,6 @@ var Analyzer = class {
|
|
|
1094
604
|
if (colReference.sort) index.sort = colReference.sort;
|
|
1095
605
|
if (colReference.where) index.where = colReference.where;
|
|
1096
606
|
if (colReference.jsonbOperator) index.jsonbOperator = colReference.jsonbOperator;
|
|
1097
|
-
if (colReference.gistOperator) index.gistOperator = colReference.gistOperator;
|
|
1098
607
|
addIndex(index);
|
|
1099
608
|
}
|
|
1100
609
|
} else if (tableReference) {
|
|
@@ -1111,7 +620,6 @@ var Analyzer = class {
|
|
|
1111
620
|
if (colReference.sort) index.sort = colReference.sort;
|
|
1112
621
|
if (colReference.where) index.where = colReference.where;
|
|
1113
622
|
if (colReference.jsonbOperator) index.jsonbOperator = colReference.jsonbOperator;
|
|
1114
|
-
if (colReference.gistOperator) index.gistOperator = colReference.gistOperator;
|
|
1115
623
|
addIndex(index);
|
|
1116
624
|
}
|
|
1117
625
|
} else if (fullReference) {
|
|
@@ -1124,7 +632,6 @@ var Analyzer = class {
|
|
|
1124
632
|
if (colReference.sort) index.sort = colReference.sort;
|
|
1125
633
|
if (colReference.where) index.where = colReference.where;
|
|
1126
634
|
if (colReference.jsonbOperator) index.jsonbOperator = colReference.jsonbOperator;
|
|
1127
|
-
if (colReference.gistOperator) index.gistOperator = colReference.gistOperator;
|
|
1128
635
|
addIndex(index);
|
|
1129
636
|
} else {
|
|
1130
637
|
console.error("Column reference has too many parts. The query is malformed", colReference);
|
|
@@ -1236,7 +743,7 @@ async function dropIndex(tx, index) {
|
|
|
1236
743
|
//#endregion
|
|
1237
744
|
//#region src/sql/indexes.ts
|
|
1238
745
|
function isIndexSupported(index) {
|
|
1239
|
-
return index.index_type === "btree" || index.index_type === "gin"
|
|
746
|
+
return index.index_type === "btree" || index.index_type === "gin";
|
|
1240
747
|
}
|
|
1241
748
|
/**
|
|
1242
749
|
* Doesn't necessarily decide whether the index can be dropped but can be
|
|
@@ -1670,9 +1177,8 @@ var IndexOptimizer = class IndexOptimizer {
|
|
|
1670
1177
|
* Derive the list of indexes [tableA(X, Y, Z), tableB(H, I, J)]
|
|
1671
1178
|
**/
|
|
1672
1179
|
indexesToCreate(rootCandidates) {
|
|
1673
|
-
const btreeCandidates = rootCandidates.filter((c) => !c.jsonbOperator
|
|
1180
|
+
const btreeCandidates = rootCandidates.filter((c) => !c.jsonbOperator);
|
|
1674
1181
|
const ginCandidates = rootCandidates.filter((c) => c.jsonbOperator);
|
|
1675
|
-
const gistCandidates = rootCandidates.filter((c) => c.gistOperator);
|
|
1676
1182
|
const nextStage = [];
|
|
1677
1183
|
const permutedIndexes = this.groupPotentialIndexColumnsByTable(btreeCandidates);
|
|
1678
1184
|
for (const permutation of permutedIndexes.values()) {
|
|
@@ -1726,32 +1232,6 @@ var IndexOptimizer = class IndexOptimizer {
|
|
|
1726
1232
|
opclass
|
|
1727
1233
|
});
|
|
1728
1234
|
}
|
|
1729
|
-
const gistGroups = this.groupGistCandidatesByColumn(gistCandidates);
|
|
1730
|
-
for (const group of gistGroups.values()) {
|
|
1731
|
-
const { schema: rawSchema, table: rawTable, column } = group;
|
|
1732
|
-
const schema = PgIdentifier.fromString(rawSchema);
|
|
1733
|
-
const table = PgIdentifier.fromString(rawTable);
|
|
1734
|
-
if (this.gistIndexAlreadyExists(table.toString(), column)) continue;
|
|
1735
|
-
const indexName = this.indexName();
|
|
1736
|
-
const candidate = {
|
|
1737
|
-
schema: rawSchema,
|
|
1738
|
-
table: rawTable,
|
|
1739
|
-
column
|
|
1740
|
-
};
|
|
1741
|
-
const definition = this.toGistDefinition({
|
|
1742
|
-
table,
|
|
1743
|
-
schema,
|
|
1744
|
-
column: PgIdentifier.fromString(column)
|
|
1745
|
-
});
|
|
1746
|
-
nextStage.push({
|
|
1747
|
-
name: indexName,
|
|
1748
|
-
schema: schema.toString(),
|
|
1749
|
-
table: table.toString(),
|
|
1750
|
-
columns: [candidate],
|
|
1751
|
-
definition,
|
|
1752
|
-
indexMethod: "gist"
|
|
1753
|
-
});
|
|
1754
|
-
}
|
|
1755
1235
|
return nextStage;
|
|
1756
1236
|
}
|
|
1757
1237
|
toDefinition({ schema, table, columns }) {
|
|
@@ -1782,12 +1262,6 @@ var IndexOptimizer = class IndexOptimizer {
|
|
|
1782
1262
|
const opclassSuffix = opclass ? ` ${opclass}` : "";
|
|
1783
1263
|
return `${fullyQualifiedTable} using gin (${column}${opclassSuffix})`;
|
|
1784
1264
|
}
|
|
1785
|
-
toGistDefinition({ schema, table, column }) {
|
|
1786
|
-
let fullyQualifiedTable;
|
|
1787
|
-
if (schema.toString() === "public") fullyQualifiedTable = table;
|
|
1788
|
-
else fullyQualifiedTable = PgIdentifier.fromParts(schema, table);
|
|
1789
|
-
return `${fullyQualifiedTable} using gist (${column})`;
|
|
1790
|
-
}
|
|
1791
1265
|
groupGinCandidatesByColumn(candidates) {
|
|
1792
1266
|
const groups = /* @__PURE__ */ new Map();
|
|
1793
1267
|
for (const c of candidates) {
|
|
@@ -1808,22 +1282,6 @@ var IndexOptimizer = class IndexOptimizer {
|
|
|
1808
1282
|
ginIndexAlreadyExists(table, column) {
|
|
1809
1283
|
return this.existingIndexes.find((index) => index.index_type === "gin" && index.table_name === table && index.index_columns.some((c) => c.name === column));
|
|
1810
1284
|
}
|
|
1811
|
-
groupGistCandidatesByColumn(candidates) {
|
|
1812
|
-
const groups = /* @__PURE__ */ new Map();
|
|
1813
|
-
for (const c of candidates) {
|
|
1814
|
-
if (!c.gistOperator) continue;
|
|
1815
|
-
const key = `${c.schema}.${c.table}.${c.column}`;
|
|
1816
|
-
if (!groups.has(key)) groups.set(key, {
|
|
1817
|
-
schema: c.schema,
|
|
1818
|
-
table: c.table,
|
|
1819
|
-
column: c.column
|
|
1820
|
-
});
|
|
1821
|
-
}
|
|
1822
|
-
return groups;
|
|
1823
|
-
}
|
|
1824
|
-
gistIndexAlreadyExists(table, column) {
|
|
1825
|
-
return this.existingIndexes.find((index) => index.index_type === "gist" && index.table_name === table && index.index_columns.some((c) => c.name === column));
|
|
1826
|
-
}
|
|
1827
1285
|
/**
|
|
1828
1286
|
* Drop indexes that can be dropped. Ignore the ones that can't
|
|
1829
1287
|
*/
|
|
@@ -2618,7 +2076,5 @@ exports.dropIndex = dropIndex;
|
|
|
2618
2076
|
exports.ignoredIdentifier = ignoredIdentifier;
|
|
2619
2077
|
exports.isIndexProbablyDroppable = isIndexProbablyDroppable;
|
|
2620
2078
|
exports.isIndexSupported = isIndexSupported;
|
|
2621
|
-
exports.parseMajorVersion = parseMajorVersion;
|
|
2622
2079
|
exports.parseNudges = parseNudges;
|
|
2623
|
-
exports.parseVersionNudges = parseVersionNudges;
|
|
2624
2080
|
//# sourceMappingURL=index.cjs.map
|