@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.mjs
CHANGED
|
@@ -13,70 +13,6 @@ function isANode$1(node) {
|
|
|
13
13
|
const keys = Object.keys(node);
|
|
14
14
|
return keys.length === 1 && /^[A-Z]/.test(keys[0]);
|
|
15
15
|
}
|
|
16
|
-
/** Operators that require a GiST index for efficient execution */
|
|
17
|
-
const GIST_OPERATORS = new Set(["&&", "-|-"]);
|
|
18
|
-
/** PostGIS / spatial functions that require a GiST index */
|
|
19
|
-
const SPATIAL_FUNCTIONS = new Set([
|
|
20
|
-
"st_intersects",
|
|
21
|
-
"st_contains",
|
|
22
|
-
"st_within",
|
|
23
|
-
"st_dwithin",
|
|
24
|
-
"st_covers",
|
|
25
|
-
"st_coveredby",
|
|
26
|
-
"st_crosses",
|
|
27
|
-
"st_overlaps",
|
|
28
|
-
"st_touches"
|
|
29
|
-
]);
|
|
30
|
-
const JSONB_EXTRACTION_OPS = new Set([
|
|
31
|
-
"->>",
|
|
32
|
-
"->",
|
|
33
|
-
"#>>",
|
|
34
|
-
"#>"
|
|
35
|
-
]);
|
|
36
|
-
function getAExprOpName(node) {
|
|
37
|
-
const nameNode = node.A_Expr.name?.[0];
|
|
38
|
-
if (nameNode && is$1(nameNode, "String") && nameNode.String.sval) return nameNode.String.sval;
|
|
39
|
-
return null;
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* Check if a node is or contains a JSONB extraction expression,
|
|
43
|
-
* unwrapping through TypeCast. Returns the extraction A_Expr if found.
|
|
44
|
-
*/
|
|
45
|
-
function findExtractionExpr(node) {
|
|
46
|
-
if (is$1(node, "A_Expr")) {
|
|
47
|
-
const op = getAExprOpName(node);
|
|
48
|
-
if (op && JSONB_EXTRACTION_OPS.has(op)) {
|
|
49
|
-
if (node.A_Expr.lexpr && hasColumnRefInNode(node.A_Expr.lexpr)) return node;
|
|
50
|
-
return null;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
if (is$1(node, "TypeCast") && node.TypeCast.arg) return findExtractionExpr(node.TypeCast.arg);
|
|
54
|
-
return null;
|
|
55
|
-
}
|
|
56
|
-
const ARITHMETIC_OPERATORS = new Set([
|
|
57
|
-
"+",
|
|
58
|
-
"-",
|
|
59
|
-
"*",
|
|
60
|
-
"/",
|
|
61
|
-
"%"
|
|
62
|
-
]);
|
|
63
|
-
const isArithmeticExpr = (node) => {
|
|
64
|
-
if (!node || typeof node !== "object") return false;
|
|
65
|
-
if (!isANode$1(node) || !is$1(node, "A_Expr")) return false;
|
|
66
|
-
if (node.A_Expr.kind !== "AEXPR_OP" || !node.A_Expr.name?.length) return false;
|
|
67
|
-
const opNode = node.A_Expr.name[0];
|
|
68
|
-
if (!is$1(opNode, "String") || !opNode.String.sval) return false;
|
|
69
|
-
return ARITHMETIC_OPERATORS.has(opNode.String.sval);
|
|
70
|
-
};
|
|
71
|
-
const COMPARISON_OPERATORS = new Set([
|
|
72
|
-
"=",
|
|
73
|
-
"<",
|
|
74
|
-
">",
|
|
75
|
-
"<=",
|
|
76
|
-
">=",
|
|
77
|
-
"<>",
|
|
78
|
-
"!="
|
|
79
|
-
]);
|
|
80
16
|
const findFuncCallsOnColumns = (whereClause) => {
|
|
81
17
|
const nudges = [];
|
|
82
18
|
Walker.shallowMatch(whereClause, "FuncCall", (node) => {
|
|
@@ -87,77 +23,6 @@ const findFuncCallsOnColumns = (whereClause) => {
|
|
|
87
23
|
location: node.FuncCall.location
|
|
88
24
|
});
|
|
89
25
|
});
|
|
90
|
-
Walker.shallowMatch(whereClause, "CoalesceExpr", (node) => {
|
|
91
|
-
if (node.CoalesceExpr.args && containsColumnRef(node.CoalesceExpr.args)) nudges.push({
|
|
92
|
-
kind: "AVOID_FUNCTIONS_ON_COLUMNS_IN_WHERE",
|
|
93
|
-
severity: "WARNING",
|
|
94
|
-
message: "Avoid using functions on columns in WHERE clause",
|
|
95
|
-
location: node.CoalesceExpr.location
|
|
96
|
-
});
|
|
97
|
-
});
|
|
98
|
-
return nudges;
|
|
99
|
-
};
|
|
100
|
-
const findJsonbExtractionInWhere = (whereClause) => {
|
|
101
|
-
const nudges = [];
|
|
102
|
-
const seen = /* @__PURE__ */ new Set();
|
|
103
|
-
function emit(node) {
|
|
104
|
-
const extraction = findExtractionExpr(node);
|
|
105
|
-
if (extraction && extraction.A_Expr.location !== void 0 && !seen.has(extraction.A_Expr.location)) {
|
|
106
|
-
seen.add(extraction.A_Expr.location);
|
|
107
|
-
nudges.push({
|
|
108
|
-
kind: "CONSIDER_EXPRESSION_INDEX_FOR_JSONB_EXTRACTION",
|
|
109
|
-
severity: "INFO",
|
|
110
|
-
message: "Consider an expression B-tree index for JSONB path extraction",
|
|
111
|
-
location: extraction.A_Expr.location
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
function walk(node) {
|
|
116
|
-
if (is$1(node, "A_Expr")) {
|
|
117
|
-
const op = getAExprOpName(node);
|
|
118
|
-
if (op && JSONB_EXTRACTION_OPS.has(op)) return;
|
|
119
|
-
if (node.A_Expr.lexpr) emit(node.A_Expr.lexpr);
|
|
120
|
-
if (node.A_Expr.rexpr) emit(node.A_Expr.rexpr);
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
if (is$1(node, "BoolExpr") && node.BoolExpr.args) {
|
|
124
|
-
for (const arg of node.BoolExpr.args) walk(arg);
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
if (is$1(node, "NullTest") && node.NullTest.arg) {
|
|
128
|
-
emit(node.NullTest.arg);
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
if (is$1(node, "BooleanTest") && node.BooleanTest.arg) {
|
|
132
|
-
emit(node.BooleanTest.arg);
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
if (is$1(node, "SubLink") && node.SubLink.testexpr) {
|
|
136
|
-
emit(node.SubLink.testexpr);
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
walk(whereClause);
|
|
141
|
-
return nudges;
|
|
142
|
-
};
|
|
143
|
-
const findArithmeticExprsOnColumns = (whereClause) => {
|
|
144
|
-
const nudges = [];
|
|
145
|
-
Walker.shallowMatch(whereClause, "A_Expr", (node) => {
|
|
146
|
-
if (node.A_Expr.kind !== "AEXPR_OP" || !node.A_Expr.name?.length) return;
|
|
147
|
-
const opNode = node.A_Expr.name[0];
|
|
148
|
-
if (!is$1(opNode, "String") || !opNode.String.sval) return;
|
|
149
|
-
if (!COMPARISON_OPERATORS.has(opNode.String.sval)) return;
|
|
150
|
-
const sides = [node.A_Expr.lexpr, node.A_Expr.rexpr];
|
|
151
|
-
for (const side of sides) if (side && isArithmeticExpr(side) && hasColumnRefInNode(side)) {
|
|
152
|
-
const expr = side;
|
|
153
|
-
nudges.push({
|
|
154
|
-
kind: "AVOID_EXPRESSIONS_ON_COLUMNS_IN_WHERE",
|
|
155
|
-
severity: "WARNING",
|
|
156
|
-
message: "Avoid using arithmetic expressions on columns in WHERE clause",
|
|
157
|
-
location: expr.A_Expr.location
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
|
-
});
|
|
161
26
|
return nudges;
|
|
162
27
|
};
|
|
163
28
|
/**
|
|
@@ -167,11 +32,7 @@ const findArithmeticExprsOnColumns = (whereClause) => {
|
|
|
167
32
|
function parseNudges(node, stack) {
|
|
168
33
|
const nudges = [];
|
|
169
34
|
if (is$1(node, "SelectStmt")) {
|
|
170
|
-
if (node.SelectStmt.whereClause)
|
|
171
|
-
nudges.push(...findFuncCallsOnColumns(node.SelectStmt.whereClause));
|
|
172
|
-
nudges.push(...findJsonbExtractionInWhere(node.SelectStmt.whereClause));
|
|
173
|
-
nudges.push(...findArithmeticExprsOnColumns(node.SelectStmt.whereClause));
|
|
174
|
-
}
|
|
35
|
+
if (node.SelectStmt.whereClause) nudges.push(...findFuncCallsOnColumns(node.SelectStmt.whereClause));
|
|
175
36
|
const star = node.SelectStmt.targetList?.find((target) => {
|
|
176
37
|
if (!(is$1(target, "ResTarget") && target.ResTarget.val && is$1(target.ResTarget.val, "ColumnRef"))) return false;
|
|
177
38
|
const fields = target.ResTarget.val.ColumnRef.fields;
|
|
@@ -188,12 +49,6 @@ function parseNudges(node, stack) {
|
|
|
188
49
|
location: star.ResTarget.location
|
|
189
50
|
});
|
|
190
51
|
}
|
|
191
|
-
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({
|
|
192
|
-
kind: "AVOID_SCALAR_SUBQUERY_IN_SELECT",
|
|
193
|
-
severity: "WARNING",
|
|
194
|
-
message: "Avoid correlated scalar subqueries in SELECT; consider rewriting as a JOIN",
|
|
195
|
-
location: target.ResTarget.val.SubLink.location
|
|
196
|
-
});
|
|
197
52
|
}
|
|
198
53
|
if (is$1(node, "SelectStmt")) {
|
|
199
54
|
if (!stack.some((item) => item === "RangeSubselect" || item === "SubLink" || item === "CommonTableExpr")) {
|
|
@@ -218,44 +73,8 @@ function parseNudges(node, stack) {
|
|
|
218
73
|
}
|
|
219
74
|
}
|
|
220
75
|
}
|
|
221
|
-
if (is$1(node, "SelectStmt")) {
|
|
222
|
-
if (!stack.some((item) => item === "RangeSubselect" || item === "SubLink" || item === "CommonTableExpr") && node.SelectStmt.sortClause && node.SelectStmt.whereClause) {
|
|
223
|
-
const equalityColumns = collectEqualityConstrainedColumns(node.SelectStmt.whereClause);
|
|
224
|
-
for (const sortItem of node.SelectStmt.sortClause) {
|
|
225
|
-
if (!is$1(sortItem, "SortBy") || !sortItem.SortBy.node) continue;
|
|
226
|
-
const sortNode = sortItem.SortBy.node;
|
|
227
|
-
if (!is$1(sortNode, "ColumnRef") || !sortNode.ColumnRef.fields) continue;
|
|
228
|
-
const lastField = sortNode.ColumnRef.fields[sortNode.ColumnRef.fields.length - 1];
|
|
229
|
-
if (!is$1(lastField, "String")) continue;
|
|
230
|
-
const sortCol = lastField.String.sval;
|
|
231
|
-
if (sortCol && equalityColumns.has(sortCol)) nudges.push({
|
|
232
|
-
kind: "NOOP_ORDER_BY",
|
|
233
|
-
severity: "INFO",
|
|
234
|
-
message: "ORDER BY column is constrained to a single value by WHERE clause",
|
|
235
|
-
location: sortNode.ColumnRef.location
|
|
236
|
-
});
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
if (is$1(node, "SelectStmt") && node.SelectStmt.sortClause) for (const sortItem of node.SelectStmt.sortClause) {
|
|
241
|
-
if (!is$1(sortItem, "SortBy")) continue;
|
|
242
|
-
const sortDir = sortItem.SortBy.sortby_dir ?? "SORTBY_DEFAULT";
|
|
243
|
-
const sortNulls = sortItem.SortBy.sortby_nulls ?? "SORTBY_NULLS_DEFAULT";
|
|
244
|
-
if (sortDir === "SORTBY_DESC" && sortNulls === "SORTBY_NULLS_DEFAULT") {
|
|
245
|
-
if (sortItem.SortBy.node && is$1(sortItem.SortBy.node, "ColumnRef")) {
|
|
246
|
-
const sortColumnName = getLastColumnRefField(sortItem.SortBy.node);
|
|
247
|
-
if (!(sortColumnName !== null && whereHasIsNotNull(node.SelectStmt.whereClause, sortColumnName))) nudges.push({
|
|
248
|
-
kind: "NULLS_FIRST_IN_DESC_ORDER",
|
|
249
|
-
severity: "INFO",
|
|
250
|
-
message: "ORDER BY … DESC sorts NULLs first — add NULLS LAST to push them to the end",
|
|
251
|
-
location: sortItem.SortBy.node.ColumnRef.location
|
|
252
|
-
});
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
76
|
if (is$1(node, "A_Expr")) {
|
|
257
|
-
|
|
258
|
-
if (isEqualityOp) {
|
|
77
|
+
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 === "<>")) {
|
|
259
78
|
const leftIsNull = isNullConstant(node.A_Expr.lexpr);
|
|
260
79
|
const rightIsNull = isNullConstant(node.A_Expr.rexpr);
|
|
261
80
|
if (leftIsNull || rightIsNull) nudges.push({
|
|
@@ -265,8 +84,7 @@ function parseNudges(node, stack) {
|
|
|
265
84
|
location: node.A_Expr.location
|
|
266
85
|
});
|
|
267
86
|
}
|
|
268
|
-
|
|
269
|
-
if (isLikeOp && node.A_Expr.rexpr) {
|
|
87
|
+
if ((node.A_Expr.kind === "AEXPR_LIKE" || node.A_Expr.kind === "AEXPR_ILIKE") && node.A_Expr.rexpr) {
|
|
270
88
|
const patternString = getStringConstantValue(node.A_Expr.rexpr);
|
|
271
89
|
if (patternString && patternString.startsWith("%")) {
|
|
272
90
|
let stringNode;
|
|
@@ -274,44 +92,17 @@ function parseNudges(node, stack) {
|
|
|
274
92
|
nudges.push({
|
|
275
93
|
kind: "AVOID_LEADING_WILDCARD_LIKE",
|
|
276
94
|
severity: "WARNING",
|
|
277
|
-
message: "
|
|
95
|
+
message: "Avoid using LIKE with leading wildcards",
|
|
278
96
|
location: stringNode?.location
|
|
279
97
|
});
|
|
280
98
|
}
|
|
281
99
|
}
|
|
282
|
-
if (isEqualityOp === false && isLikeOp === false) {
|
|
283
|
-
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;
|
|
284
|
-
if (gistOpName && GIST_OPERATORS.has(gistOpName)) nudges.push({
|
|
285
|
-
kind: "CONSIDER_GIST_INDEX",
|
|
286
|
-
severity: "INFO",
|
|
287
|
-
message: `Operator "${gistOpName}" benefits from a GiST index for efficient execution`,
|
|
288
|
-
location: node.A_Expr.location
|
|
289
|
-
});
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
if (is$1(node, "SelectStmt") && node.SelectStmt.sortClause) {
|
|
293
|
-
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({
|
|
294
|
-
kind: "AVOID_ORDER_BY_RANDOM",
|
|
295
|
-
severity: "WARNING",
|
|
296
|
-
message: "Avoid using ORDER BY random()",
|
|
297
|
-
location: sortItem.SortBy.node.FuncCall.location
|
|
298
|
-
});
|
|
299
100
|
}
|
|
300
101
|
if (is$1(node, "SelectStmt") && node.SelectStmt.distinctClause) nudges.push({
|
|
301
102
|
kind: "AVOID_DISTINCT_WITHOUT_REASON",
|
|
302
103
|
severity: "WARNING",
|
|
303
104
|
message: "Avoid using DISTINCT without a reason"
|
|
304
105
|
});
|
|
305
|
-
if (is$1(node, "SelectStmt") && node.SelectStmt.limitOffset) {
|
|
306
|
-
const offsetNode = node.SelectStmt.limitOffset;
|
|
307
|
-
const location = isANode$1(offsetNode) && is$1(offsetNode, "A_Const") ? offsetNode.A_Const.location : void 0;
|
|
308
|
-
nudges.push({
|
|
309
|
-
kind: "AVOID_OFFSET_FOR_PAGINATION",
|
|
310
|
-
severity: "INFO",
|
|
311
|
-
message: "Avoid using OFFSET for pagination",
|
|
312
|
-
location
|
|
313
|
-
});
|
|
314
|
-
}
|
|
315
106
|
if (is$1(node, "JoinExpr")) {
|
|
316
107
|
if (!node.JoinExpr.quals) nudges.push({
|
|
317
108
|
kind: "MISSING_JOIN_CONDITION",
|
|
@@ -348,93 +139,6 @@ function parseNudges(node, stack) {
|
|
|
348
139
|
});
|
|
349
140
|
}
|
|
350
141
|
}
|
|
351
|
-
if (is$1(node, "SelectStmt") && node.SelectStmt.havingClause) {
|
|
352
|
-
if (!containsAggregate(node.SelectStmt.havingClause)) {
|
|
353
|
-
const having = node.SelectStmt.havingClause;
|
|
354
|
-
let location;
|
|
355
|
-
if (is$1(having, "A_Expr")) location = having.A_Expr.location;
|
|
356
|
-
else if (is$1(having, "BoolExpr")) location = having.BoolExpr.location;
|
|
357
|
-
nudges.push({
|
|
358
|
-
kind: "PREFER_WHERE_OVER_HAVING_FOR_NON_AGGREGATES",
|
|
359
|
-
severity: "INFO",
|
|
360
|
-
message: "Non-aggregate condition in HAVING should be in WHERE",
|
|
361
|
-
location
|
|
362
|
-
});
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
if (is$1(node, "FuncCall")) {
|
|
366
|
-
const funcName = node.FuncCall.funcname;
|
|
367
|
-
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({
|
|
368
|
-
kind: "PREFER_COUNT_STAR_OVER_COUNT_COLUMN",
|
|
369
|
-
severity: "INFO",
|
|
370
|
-
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.",
|
|
371
|
-
location: node.FuncCall.location
|
|
372
|
-
});
|
|
373
|
-
}
|
|
374
|
-
if (is$1(node, "A_Expr") && node.A_Expr.kind === "AEXPR_OP" && node.A_Expr.name && node.A_Expr.name.length > 0) {
|
|
375
|
-
const opNode = node.A_Expr.name[0];
|
|
376
|
-
const op = is$1(opNode, "String") ? opNode.String.sval : null;
|
|
377
|
-
if (op && isExistenceCheckPattern(node.A_Expr.lexpr, node.A_Expr.rexpr, op)) nudges.push({
|
|
378
|
-
kind: "USE_EXISTS_NOT_COUNT_FOR_EXISTENCE_CHECK",
|
|
379
|
-
severity: "INFO",
|
|
380
|
-
message: "Use EXISTS instead of COUNT for existence checks",
|
|
381
|
-
location: node.A_Expr.location
|
|
382
|
-
});
|
|
383
|
-
}
|
|
384
|
-
if (is$1(node, "FuncCall")) {
|
|
385
|
-
const funcname = node.FuncCall.funcname?.[0] && is$1(node.FuncCall.funcname[0], "String") && node.FuncCall.funcname[0].String.sval;
|
|
386
|
-
if (funcname && [
|
|
387
|
-
"sum",
|
|
388
|
-
"count",
|
|
389
|
-
"avg",
|
|
390
|
-
"min",
|
|
391
|
-
"max"
|
|
392
|
-
].includes(funcname.toLowerCase())) {
|
|
393
|
-
const firstArg = node.FuncCall.args?.[0];
|
|
394
|
-
if (firstArg && isANode$1(firstArg) && is$1(firstArg, "CaseExpr")) {
|
|
395
|
-
const caseExpr = firstArg.CaseExpr;
|
|
396
|
-
if (caseExpr.args && caseExpr.args.length === 1) {
|
|
397
|
-
const defresult = caseExpr.defresult;
|
|
398
|
-
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({
|
|
399
|
-
kind: "PREFER_FILTER_OVER_CASE_IN_AGGREGATE",
|
|
400
|
-
severity: "INFO",
|
|
401
|
-
message: "Use FILTER (WHERE ...) instead of CASE inside aggregate functions",
|
|
402
|
-
location: node.FuncCall.location
|
|
403
|
-
});
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
if (is$1(node, "FuncCall") && node.FuncCall.funcname) {
|
|
409
|
-
const lastNameNode = node.FuncCall.funcname[node.FuncCall.funcname.length - 1];
|
|
410
|
-
if (lastNameNode && is$1(lastNameNode, "String") && lastNameNode.String.sval) {
|
|
411
|
-
const funcName = lastNameNode.String.sval.toLowerCase();
|
|
412
|
-
if (SPATIAL_FUNCTIONS.has(funcName)) nudges.push({
|
|
413
|
-
kind: "CONSIDER_GIST_INDEX",
|
|
414
|
-
severity: "INFO",
|
|
415
|
-
message: `Spatial function "${lastNameNode.String.sval}" benefits from a GiST index for efficient execution`,
|
|
416
|
-
location: node.FuncCall.location
|
|
417
|
-
});
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
if (is$1(node, "BoolExpr") && node.BoolExpr.boolop === "NOT_EXPR") {
|
|
421
|
-
const args = node.BoolExpr.args;
|
|
422
|
-
if (args && args.length === 1) {
|
|
423
|
-
const arg = args[0];
|
|
424
|
-
if (isANode$1(arg) && is$1(arg, "SubLink") && arg.SubLink.subLinkType === "EXISTS_SUBLINK" && arg.SubLink.subselect) {
|
|
425
|
-
const subselect = arg.SubLink.subselect;
|
|
426
|
-
if (isANode$1(subselect) && is$1(subselect, "SelectStmt") && subselect.SelectStmt.whereClause) {
|
|
427
|
-
const where = subselect.SelectStmt.whereClause;
|
|
428
|
-
if (isANode$1(where) && is$1(where, "BoolExpr") && where.BoolExpr.boolop === "OR_EXPR") nudges.push({
|
|
429
|
-
kind: "FLATTEN_NOT_EXISTS_OR",
|
|
430
|
-
severity: "INFO",
|
|
431
|
-
message: "Consider splitting NOT EXISTS with OR into separate NOT EXISTS joined by AND",
|
|
432
|
-
location: node.BoolExpr.location
|
|
433
|
-
});
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
142
|
if (is$1(node, "A_Expr")) {
|
|
439
143
|
if (node.A_Expr.kind === "AEXPR_IN") {
|
|
440
144
|
let list;
|
|
@@ -448,36 +152,6 @@ function parseNudges(node, stack) {
|
|
|
448
152
|
});
|
|
449
153
|
}
|
|
450
154
|
}
|
|
451
|
-
if (is$1(node, "SelectStmt") && node.SelectStmt.op === "SETOP_UNION" && !node.SelectStmt.all) nudges.push({
|
|
452
|
-
kind: "PREFER_UNION_ALL_OVER_UNION",
|
|
453
|
-
severity: "INFO",
|
|
454
|
-
message: "UNION removes duplicates with an implicit sort — use UNION ALL if deduplication is not needed"
|
|
455
|
-
});
|
|
456
|
-
if (is$1(node, "SelectStmt")) {
|
|
457
|
-
const subqueryAliases = /* @__PURE__ */ new Set();
|
|
458
|
-
if (node.SelectStmt.targetList) {
|
|
459
|
-
for (const target of node.SelectStmt.targetList) if (is$1(target, "ResTarget")) {
|
|
460
|
-
if (target.ResTarget.val && is$1(target.ResTarget.val, "SubLink")) {
|
|
461
|
-
if (target.ResTarget.name) subqueryAliases.add(target.ResTarget.name.toLowerCase());
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
if (subqueryAliases.size > 0 && node.SelectStmt.sortClause) {
|
|
466
|
-
for (const sortBy of node.SelectStmt.sortClause) if (is$1(sortBy, "SortBy") && sortBy.SortBy.node) {
|
|
467
|
-
if (is$1(sortBy.SortBy.node, "ColumnRef")) {
|
|
468
|
-
const columnName = extractColumnName(sortBy.SortBy.node);
|
|
469
|
-
if (columnName && subqueryAliases.has(columnName.toLowerCase())) {
|
|
470
|
-
nudges.push({
|
|
471
|
-
kind: "AVOID_SORTING_ON_GENERATED_VALUES",
|
|
472
|
-
severity: "WARNING",
|
|
473
|
-
message: "Be careful sorting on values generated dynamically during query execution - these cannot use indexes and contribute to slower query performance."
|
|
474
|
-
});
|
|
475
|
-
break;
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
155
|
return nudges;
|
|
482
156
|
}
|
|
483
157
|
function containsColumnRef(args) {
|
|
@@ -513,142 +187,6 @@ function getStringConstantValue(node) {
|
|
|
513
187
|
if (isANode$1(node) && is$1(node, "A_Const") && node.A_Const.sval) return node.A_Const.sval.sval || null;
|
|
514
188
|
return null;
|
|
515
189
|
}
|
|
516
|
-
const AGGREGATE_FUNCTIONS = new Set([
|
|
517
|
-
"count",
|
|
518
|
-
"sum",
|
|
519
|
-
"avg",
|
|
520
|
-
"min",
|
|
521
|
-
"max",
|
|
522
|
-
"array_agg",
|
|
523
|
-
"string_agg",
|
|
524
|
-
"bool_and",
|
|
525
|
-
"bool_or",
|
|
526
|
-
"every"
|
|
527
|
-
]);
|
|
528
|
-
function containsAggregate(node) {
|
|
529
|
-
if (!node || typeof node !== "object") return false;
|
|
530
|
-
if (Array.isArray(node)) return node.some(containsAggregate);
|
|
531
|
-
if (isANode$1(node) && is$1(node, "FuncCall")) {
|
|
532
|
-
const funcname = node.FuncCall.funcname;
|
|
533
|
-
if (funcname) {
|
|
534
|
-
for (const f of funcname) if (isANode$1(f) && is$1(f, "String") && AGGREGATE_FUNCTIONS.has(f.String.sval?.toLowerCase() ?? "")) return true;
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
if (isANode$1(node)) return containsAggregate(node[Object.keys(node)[0]]);
|
|
538
|
-
for (const child of Object.values(node)) if (containsAggregate(child)) return true;
|
|
539
|
-
return false;
|
|
540
|
-
}
|
|
541
|
-
function getLastColumnRefField(columnRef) {
|
|
542
|
-
const fields = columnRef.ColumnRef.fields;
|
|
543
|
-
if (!fields || fields.length === 0) return null;
|
|
544
|
-
const lastField = fields[fields.length - 1];
|
|
545
|
-
if (isANode$1(lastField) && is$1(lastField, "String")) return lastField.String.sval || null;
|
|
546
|
-
return null;
|
|
547
|
-
}
|
|
548
|
-
function whereHasIsNotNull(whereClause, columnName) {
|
|
549
|
-
if (!whereClause) return false;
|
|
550
|
-
let found = false;
|
|
551
|
-
Walker.shallowMatch(whereClause, "NullTest", (node) => {
|
|
552
|
-
if (node.NullTest.nulltesttype === "IS_NOT_NULL" && node.NullTest.arg && is$1(node.NullTest.arg, "ColumnRef")) {
|
|
553
|
-
if (getLastColumnRefField(node.NullTest.arg) === columnName) found = true;
|
|
554
|
-
}
|
|
555
|
-
});
|
|
556
|
-
return found;
|
|
557
|
-
}
|
|
558
|
-
function isCountFuncCall(node) {
|
|
559
|
-
if (!node || typeof node !== "object") return false;
|
|
560
|
-
if (!isANode$1(node) || !is$1(node, "FuncCall")) return false;
|
|
561
|
-
const fc = node.FuncCall;
|
|
562
|
-
if (!(fc.funcname?.some((n) => is$1(n, "String") && n.String.sval === "count") ?? false)) return false;
|
|
563
|
-
if (fc.agg_star) return true;
|
|
564
|
-
if (fc.args && fc.args.length === 1 && isANode$1(fc.args[0]) && is$1(fc.args[0], "A_Const")) return true;
|
|
565
|
-
return false;
|
|
566
|
-
}
|
|
567
|
-
function isSubLinkWithCount(node) {
|
|
568
|
-
if (!node || typeof node !== "object") return false;
|
|
569
|
-
if (!isANode$1(node) || !is$1(node, "SubLink")) return false;
|
|
570
|
-
const subselect = node.SubLink.subselect;
|
|
571
|
-
if (!subselect || !isANode$1(subselect) || !is$1(subselect, "SelectStmt")) return false;
|
|
572
|
-
const targets = subselect.SelectStmt.targetList;
|
|
573
|
-
if (!targets || targets.length !== 1) return false;
|
|
574
|
-
const target = targets[0];
|
|
575
|
-
if (!isANode$1(target) || !is$1(target, "ResTarget") || !target.ResTarget.val) return false;
|
|
576
|
-
return isCountFuncCall(target.ResTarget.val);
|
|
577
|
-
}
|
|
578
|
-
function isCountExpression(node) {
|
|
579
|
-
return isCountFuncCall(node) || isSubLinkWithCount(node);
|
|
580
|
-
}
|
|
581
|
-
function getIntegerConstantValue(node) {
|
|
582
|
-
if (!node || typeof node !== "object") return null;
|
|
583
|
-
if (!isANode$1(node) || !is$1(node, "A_Const")) return null;
|
|
584
|
-
if (node.A_Const.ival === void 0) return null;
|
|
585
|
-
return node.A_Const.ival.ival ?? 0;
|
|
586
|
-
}
|
|
587
|
-
function isExistenceCheckPattern(lexpr, rexpr, op) {
|
|
588
|
-
if (isCountExpression(lexpr)) {
|
|
589
|
-
const val = getIntegerConstantValue(rexpr);
|
|
590
|
-
if (val !== null) {
|
|
591
|
-
if (op === ">" && val === 0) return true;
|
|
592
|
-
if (op === ">=" && val === 1) return true;
|
|
593
|
-
if ((op === "!=" || op === "<>") && val === 0) return true;
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
if (isCountExpression(rexpr)) {
|
|
597
|
-
const val = getIntegerConstantValue(lexpr);
|
|
598
|
-
if (val !== null) {
|
|
599
|
-
if (op === "<" && val === 0) return true;
|
|
600
|
-
if (op === "<=" && val === 1) return true;
|
|
601
|
-
if ((op === "!=" || op === "<>") && val === 0) return true;
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
return false;
|
|
605
|
-
}
|
|
606
|
-
function collectEqualityConstrainedColumns(whereClause) {
|
|
607
|
-
const columns = /* @__PURE__ */ new Set();
|
|
608
|
-
function walk(node) {
|
|
609
|
-
if (!node || typeof node !== "object") return;
|
|
610
|
-
if (!isANode$1(node)) return;
|
|
611
|
-
if (is$1(node, "BoolExpr") && node.BoolExpr.boolop === "AND_EXPR" && node.BoolExpr.args) {
|
|
612
|
-
for (const arg of node.BoolExpr.args) walk(arg);
|
|
613
|
-
return;
|
|
614
|
-
}
|
|
615
|
-
if (is$1(node, "A_Expr") && node.A_Expr.kind === "AEXPR_OP") {
|
|
616
|
-
const opName = node.A_Expr.name?.[0];
|
|
617
|
-
if (opName && is$1(opName, "String") && opName.String.sval === "=") {
|
|
618
|
-
addColumnIfConstant(node.A_Expr.lexpr, node.A_Expr.rexpr, columns);
|
|
619
|
-
addColumnIfConstant(node.A_Expr.rexpr, node.A_Expr.lexpr, columns);
|
|
620
|
-
}
|
|
621
|
-
return;
|
|
622
|
-
}
|
|
623
|
-
if (is$1(node, "A_Expr") && node.A_Expr.kind === "AEXPR_IN") {
|
|
624
|
-
const listNode = node.A_Expr.rexpr;
|
|
625
|
-
if (listNode && isANode$1(listNode) && is$1(listNode, "List") && listNode.List.items && listNode.List.items.length === 1) {
|
|
626
|
-
const colNode = node.A_Expr.lexpr;
|
|
627
|
-
if (colNode && isANode$1(colNode) && is$1(colNode, "ColumnRef")) {
|
|
628
|
-
const lastField = colNode.ColumnRef.fields?.[colNode.ColumnRef.fields.length - 1];
|
|
629
|
-
if (lastField && is$1(lastField, "String") && lastField.String.sval) columns.add(lastField.String.sval);
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
walk(whereClause);
|
|
635
|
-
return columns;
|
|
636
|
-
}
|
|
637
|
-
function addColumnIfConstant(colSide, constSide, columns) {
|
|
638
|
-
if (!colSide || !constSide || typeof colSide !== "object" || typeof constSide !== "object") return;
|
|
639
|
-
if (!isANode$1(colSide) || !is$1(colSide, "ColumnRef")) return;
|
|
640
|
-
if (!isANode$1(constSide) || !is$1(constSide, "A_Const") && !is$1(constSide, "ParamRef")) return;
|
|
641
|
-
const fields = colSide.ColumnRef.fields;
|
|
642
|
-
if (!fields || fields.length === 0) return;
|
|
643
|
-
const lastField = fields[fields.length - 1];
|
|
644
|
-
if (is$1(lastField, "String") && lastField.String.sval) columns.add(lastField.String.sval);
|
|
645
|
-
}
|
|
646
|
-
function extractColumnName(node) {
|
|
647
|
-
if (!node.ColumnRef.fields || node.ColumnRef.fields.length === 0) return null;
|
|
648
|
-
const lastField = node.ColumnRef.fields[node.ColumnRef.fields.length - 1];
|
|
649
|
-
if (is$1(lastField, "String") && lastField.String.sval) return lastField.String.sval;
|
|
650
|
-
return null;
|
|
651
|
-
}
|
|
652
190
|
function countBoolOrConditions(node) {
|
|
653
191
|
if (node.BoolExpr.boolop !== "OR_EXPR" || !node.BoolExpr.args) return 1;
|
|
654
192
|
let count = 0;
|
|
@@ -656,28 +194,6 @@ function countBoolOrConditions(node) {
|
|
|
656
194
|
else count += 1;
|
|
657
195
|
return count;
|
|
658
196
|
}
|
|
659
|
-
/**
|
|
660
|
-
* Extract the major version number from a PostgreSQL version string.
|
|
661
|
-
* PostgreSQL encodes versions as XXYYZZ where XX is major, YY is minor, ZZ is patch.
|
|
662
|
-
* e.g. "170000" → 17, "160004" → 16, "120000" → 12
|
|
663
|
-
*/
|
|
664
|
-
function parseMajorVersion(versionNum) {
|
|
665
|
-
return Math.floor(parseInt(versionNum, 10) / 1e4);
|
|
666
|
-
}
|
|
667
|
-
/**
|
|
668
|
-
* Produce version-aware nudges by checking existing nudges against the
|
|
669
|
-
* user's PostgreSQL major version. Returns upgrade recommendations
|
|
670
|
-
* when a query pattern would benefit from a newer version.
|
|
671
|
-
*/
|
|
672
|
-
function parseVersionNudges(nudges, majorVersion) {
|
|
673
|
-
const versionNudges = [];
|
|
674
|
-
if (majorVersion < 17 && nudges.some((n) => n.kind === "AVOID_DISTINCT_WITHOUT_REASON")) versionNudges.push({
|
|
675
|
-
kind: "UPGRADE_DISTINCT_PG17",
|
|
676
|
-
severity: "INFO",
|
|
677
|
-
message: "PostgreSQL 17 introduces hash-based DISTINCT, which can significantly speed up queries using DISTINCT"
|
|
678
|
-
});
|
|
679
|
-
return versionNudges;
|
|
680
|
-
}
|
|
681
197
|
|
|
682
198
|
//#endregion
|
|
683
199
|
//#region \0@oxc-project+runtime@0.112.0/helpers/typeof.js
|
|
@@ -808,11 +324,6 @@ var Walker = class Walker {
|
|
|
808
324
|
if (node.A_Expr.lexpr && is(node.A_Expr.lexpr, "ColumnRef")) this.add(node.A_Expr.lexpr, { jsonbOperator });
|
|
809
325
|
if (node.A_Expr.rexpr && is(node.A_Expr.rexpr, "ColumnRef")) this.add(node.A_Expr.rexpr, { jsonbOperator });
|
|
810
326
|
}
|
|
811
|
-
if (opName && (opName === "&&" || opName === "-|-")) {
|
|
812
|
-
const gistOperator = opName;
|
|
813
|
-
if (node.A_Expr.lexpr && is(node.A_Expr.lexpr, "ColumnRef")) this.add(node.A_Expr.lexpr, { gistOperator });
|
|
814
|
-
if (node.A_Expr.rexpr && is(node.A_Expr.rexpr, "ColumnRef")) this.add(node.A_Expr.rexpr, { gistOperator });
|
|
815
|
-
}
|
|
816
327
|
}
|
|
817
328
|
if (is(node, "ColumnRef")) {
|
|
818
329
|
for (let i = 0; i < stack.length; i++) {
|
|
@@ -896,7 +407,6 @@ var Walker = class Walker {
|
|
|
896
407
|
if (options?.sort) ref.sort = options.sort;
|
|
897
408
|
if (options?.where) ref.where = options.where;
|
|
898
409
|
if (options?.jsonbOperator) ref.jsonbOperator = options.jsonbOperator;
|
|
899
|
-
if (options?.gistOperator) ref.gistOperator = options.gistOperator;
|
|
900
410
|
this.highlights.push(ref);
|
|
901
411
|
}
|
|
902
412
|
/**
|
|
@@ -1065,7 +575,6 @@ var Analyzer = class {
|
|
|
1065
575
|
if (colReference.sort) index.sort = colReference.sort;
|
|
1066
576
|
if (colReference.where) index.where = colReference.where;
|
|
1067
577
|
if (colReference.jsonbOperator) index.jsonbOperator = colReference.jsonbOperator;
|
|
1068
|
-
if (colReference.gistOperator) index.gistOperator = colReference.gistOperator;
|
|
1069
578
|
addIndex(index);
|
|
1070
579
|
}
|
|
1071
580
|
} else if (tableReference) {
|
|
@@ -1082,7 +591,6 @@ var Analyzer = class {
|
|
|
1082
591
|
if (colReference.sort) index.sort = colReference.sort;
|
|
1083
592
|
if (colReference.where) index.where = colReference.where;
|
|
1084
593
|
if (colReference.jsonbOperator) index.jsonbOperator = colReference.jsonbOperator;
|
|
1085
|
-
if (colReference.gistOperator) index.gistOperator = colReference.gistOperator;
|
|
1086
594
|
addIndex(index);
|
|
1087
595
|
}
|
|
1088
596
|
} else if (fullReference) {
|
|
@@ -1095,7 +603,6 @@ var Analyzer = class {
|
|
|
1095
603
|
if (colReference.sort) index.sort = colReference.sort;
|
|
1096
604
|
if (colReference.where) index.where = colReference.where;
|
|
1097
605
|
if (colReference.jsonbOperator) index.jsonbOperator = colReference.jsonbOperator;
|
|
1098
|
-
if (colReference.gistOperator) index.gistOperator = colReference.gistOperator;
|
|
1099
606
|
addIndex(index);
|
|
1100
607
|
} else {
|
|
1101
608
|
console.error("Column reference has too many parts. The query is malformed", colReference);
|
|
@@ -1207,7 +714,7 @@ async function dropIndex(tx, index) {
|
|
|
1207
714
|
//#endregion
|
|
1208
715
|
//#region src/sql/indexes.ts
|
|
1209
716
|
function isIndexSupported(index) {
|
|
1210
|
-
return index.index_type === "btree" || index.index_type === "gin"
|
|
717
|
+
return index.index_type === "btree" || index.index_type === "gin";
|
|
1211
718
|
}
|
|
1212
719
|
/**
|
|
1213
720
|
* Doesn't necessarily decide whether the index can be dropped but can be
|
|
@@ -1641,9 +1148,8 @@ var IndexOptimizer = class IndexOptimizer {
|
|
|
1641
1148
|
* Derive the list of indexes [tableA(X, Y, Z), tableB(H, I, J)]
|
|
1642
1149
|
**/
|
|
1643
1150
|
indexesToCreate(rootCandidates) {
|
|
1644
|
-
const btreeCandidates = rootCandidates.filter((c) => !c.jsonbOperator
|
|
1151
|
+
const btreeCandidates = rootCandidates.filter((c) => !c.jsonbOperator);
|
|
1645
1152
|
const ginCandidates = rootCandidates.filter((c) => c.jsonbOperator);
|
|
1646
|
-
const gistCandidates = rootCandidates.filter((c) => c.gistOperator);
|
|
1647
1153
|
const nextStage = [];
|
|
1648
1154
|
const permutedIndexes = this.groupPotentialIndexColumnsByTable(btreeCandidates);
|
|
1649
1155
|
for (const permutation of permutedIndexes.values()) {
|
|
@@ -1697,32 +1203,6 @@ var IndexOptimizer = class IndexOptimizer {
|
|
|
1697
1203
|
opclass
|
|
1698
1204
|
});
|
|
1699
1205
|
}
|
|
1700
|
-
const gistGroups = this.groupGistCandidatesByColumn(gistCandidates);
|
|
1701
|
-
for (const group of gistGroups.values()) {
|
|
1702
|
-
const { schema: rawSchema, table: rawTable, column } = group;
|
|
1703
|
-
const schema = PgIdentifier.fromString(rawSchema);
|
|
1704
|
-
const table = PgIdentifier.fromString(rawTable);
|
|
1705
|
-
if (this.gistIndexAlreadyExists(table.toString(), column)) continue;
|
|
1706
|
-
const indexName = this.indexName();
|
|
1707
|
-
const candidate = {
|
|
1708
|
-
schema: rawSchema,
|
|
1709
|
-
table: rawTable,
|
|
1710
|
-
column
|
|
1711
|
-
};
|
|
1712
|
-
const definition = this.toGistDefinition({
|
|
1713
|
-
table,
|
|
1714
|
-
schema,
|
|
1715
|
-
column: PgIdentifier.fromString(column)
|
|
1716
|
-
});
|
|
1717
|
-
nextStage.push({
|
|
1718
|
-
name: indexName,
|
|
1719
|
-
schema: schema.toString(),
|
|
1720
|
-
table: table.toString(),
|
|
1721
|
-
columns: [candidate],
|
|
1722
|
-
definition,
|
|
1723
|
-
indexMethod: "gist"
|
|
1724
|
-
});
|
|
1725
|
-
}
|
|
1726
1206
|
return nextStage;
|
|
1727
1207
|
}
|
|
1728
1208
|
toDefinition({ schema, table, columns }) {
|
|
@@ -1753,12 +1233,6 @@ var IndexOptimizer = class IndexOptimizer {
|
|
|
1753
1233
|
const opclassSuffix = opclass ? ` ${opclass}` : "";
|
|
1754
1234
|
return `${fullyQualifiedTable} using gin (${column}${opclassSuffix})`;
|
|
1755
1235
|
}
|
|
1756
|
-
toGistDefinition({ schema, table, column }) {
|
|
1757
|
-
let fullyQualifiedTable;
|
|
1758
|
-
if (schema.toString() === "public") fullyQualifiedTable = table;
|
|
1759
|
-
else fullyQualifiedTable = PgIdentifier.fromParts(schema, table);
|
|
1760
|
-
return `${fullyQualifiedTable} using gist (${column})`;
|
|
1761
|
-
}
|
|
1762
1236
|
groupGinCandidatesByColumn(candidates) {
|
|
1763
1237
|
const groups = /* @__PURE__ */ new Map();
|
|
1764
1238
|
for (const c of candidates) {
|
|
@@ -1779,22 +1253,6 @@ var IndexOptimizer = class IndexOptimizer {
|
|
|
1779
1253
|
ginIndexAlreadyExists(table, column) {
|
|
1780
1254
|
return this.existingIndexes.find((index) => index.index_type === "gin" && index.table_name === table && index.index_columns.some((c) => c.name === column));
|
|
1781
1255
|
}
|
|
1782
|
-
groupGistCandidatesByColumn(candidates) {
|
|
1783
|
-
const groups = /* @__PURE__ */ new Map();
|
|
1784
|
-
for (const c of candidates) {
|
|
1785
|
-
if (!c.gistOperator) continue;
|
|
1786
|
-
const key = `${c.schema}.${c.table}.${c.column}`;
|
|
1787
|
-
if (!groups.has(key)) groups.set(key, {
|
|
1788
|
-
schema: c.schema,
|
|
1789
|
-
table: c.table,
|
|
1790
|
-
column: c.column
|
|
1791
|
-
});
|
|
1792
|
-
}
|
|
1793
|
-
return groups;
|
|
1794
|
-
}
|
|
1795
|
-
gistIndexAlreadyExists(table, column) {
|
|
1796
|
-
return this.existingIndexes.find((index) => index.index_type === "gist" && index.table_name === table && index.index_columns.some((c) => c.name === column));
|
|
1797
|
-
}
|
|
1798
1256
|
/**
|
|
1799
1257
|
* Drop indexes that can be dropped. Ignore the ones that can't
|
|
1800
1258
|
*/
|
|
@@ -2569,5 +2027,5 @@ var PssRewriter = class {
|
|
|
2569
2027
|
};
|
|
2570
2028
|
|
|
2571
2029
|
//#endregion
|
|
2572
|
-
export { Analyzer, ExportedStats, ExportedStatsColumns, ExportedStatsIndex, ExportedStatsStatistics, ExportedStatsV1, IndexOptimizer, PROCEED, PgIdentifier, PostgresQueryBuilder, PostgresVersion, PssRewriter, SKIP, Statistics, StatisticsMode, StatisticsSource, dropIndex, ignoredIdentifier, isIndexProbablyDroppable, isIndexSupported,
|
|
2030
|
+
export { Analyzer, ExportedStats, ExportedStatsColumns, ExportedStatsIndex, ExportedStatsStatistics, ExportedStatsV1, IndexOptimizer, PROCEED, PgIdentifier, PostgresQueryBuilder, PostgresVersion, PssRewriter, SKIP, Statistics, StatisticsMode, StatisticsSource, dropIndex, ignoredIdentifier, isIndexProbablyDroppable, isIndexSupported, parseNudges };
|
|
2573
2031
|
//# sourceMappingURL=index.mjs.map
|