@query-doctor/core 0.4.0 → 0.4.1
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/explain/rewriter.d.ts +4 -0
- package/dist/explain/rewriter.d.ts.map +1 -0
- package/dist/explain/traverse.d.ts +3 -0
- package/dist/explain/traverse.d.ts.map +1 -0
- package/dist/explain/tree.d.ts +73 -0
- package/dist/explain/tree.d.ts.map +1 -0
- package/dist/index.cjs +58 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +58 -22
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +25333 -0
- package/dist/index.mjs.map +1 -0
- package/dist/optimizer/genalgo.test.d.ts +2 -0
- package/dist/optimizer/genalgo.test.d.ts.map +1 -0
- package/dist/optimizer/index-candidate.d.ts +23 -0
- package/dist/optimizer/index-candidate.d.ts.map +1 -0
- package/dist/optimizer/index-shrinker.d.ts +10 -0
- package/dist/optimizer/index-shrinker.d.ts.map +1 -0
- package/dist/optimizer/index-shrinker.test.d.ts +2 -0
- package/dist/optimizer/index-shrinker.test.d.ts.map +1 -0
- package/dist/optimizer/index-tester.d.ts +2 -0
- package/dist/optimizer/index-tester.d.ts.map +1 -0
- package/dist/optimizer/pss-rewriter.test.d.ts +2 -0
- package/dist/optimizer/pss-rewriter.test.d.ts.map +1 -0
- package/dist/sql/analyzer.test.d.ts +2 -0
- package/dist/sql/analyzer.test.d.ts.map +1 -0
- package/dist/sql/builder.test.d.ts +2 -0
- package/dist/sql/builder.test.d.ts.map +1 -0
- package/dist/sql/nudges.d.ts +1 -0
- package/dist/sql/nudges.d.ts.map +1 -1
- package/dist/sql/permutations.test.d.ts +2 -0
- package/dist/sql/permutations.test.d.ts.map +1 -0
- package/dist/sql/pg-identifier.test.d.ts +2 -0
- package/dist/sql/pg-identifier.test.d.ts.map +1 -0
- package/dist/sql/walker.d.ts +2 -1
- package/dist/sql/walker.d.ts.map +1 -1
- package/dist/sql/walker.test.d.ts +2 -0
- package/dist/sql/walker.test.d.ts.map +1 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -27,12 +27,25 @@ function isANode(node) {
|
|
|
27
27
|
}
|
|
28
28
|
function parseNudges(node, stack) {
|
|
29
29
|
const nudges = [];
|
|
30
|
-
if (is(node, "
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
30
|
+
if (is(node, "SelectStmt")) {
|
|
31
|
+
const star = node.SelectStmt.targetList?.find(
|
|
32
|
+
(target) => {
|
|
33
|
+
if (!(is(target, "ResTarget") && target.ResTarget.val && is(target.ResTarget.val, "ColumnRef"))) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
return target.ResTarget.val.ColumnRef.fields?.some(
|
|
37
|
+
(field) => is(field, "A_Star")
|
|
38
|
+
) ?? false;
|
|
39
|
+
}
|
|
40
|
+
);
|
|
41
|
+
if (star) {
|
|
42
|
+
nudges.push({
|
|
43
|
+
kind: "AVOID_SELECT_STAR",
|
|
44
|
+
severity: "INFO",
|
|
45
|
+
message: "Avoid using SELECT *",
|
|
46
|
+
location: star.ResTarget.location
|
|
47
|
+
});
|
|
48
|
+
}
|
|
36
49
|
}
|
|
37
50
|
if (is(node, "FuncCall")) {
|
|
38
51
|
const inWhereClause = stack.some((item) => item === "whereClause");
|
|
@@ -42,7 +55,8 @@ function parseNudges(node, stack) {
|
|
|
42
55
|
nudges.push({
|
|
43
56
|
kind: "AVOID_FUNCTIONS_ON_COLUMNS_IN_WHERE",
|
|
44
57
|
severity: "WARNING",
|
|
45
|
-
message: "Avoid using functions on columns in WHERE clause"
|
|
58
|
+
message: "Avoid using functions on columns in WHERE clause",
|
|
59
|
+
location: node.FuncCall.location
|
|
46
60
|
});
|
|
47
61
|
}
|
|
48
62
|
}
|
|
@@ -58,18 +72,24 @@ function parseNudges(node, stack) {
|
|
|
58
72
|
return is(fromItem, "RangeVar") || is(fromItem, "JoinExpr") && hasActualTablesInJoin(fromItem);
|
|
59
73
|
});
|
|
60
74
|
if (hasActualTables) {
|
|
75
|
+
const firstTable = node.SelectStmt.fromClause.find(
|
|
76
|
+
(item) => is(item, "RangeVar")
|
|
77
|
+
);
|
|
78
|
+
const fromLocation = firstTable?.RangeVar.location;
|
|
61
79
|
if (!node.SelectStmt.whereClause) {
|
|
62
80
|
nudges.push({
|
|
63
81
|
kind: "MISSING_WHERE_CLAUSE",
|
|
64
82
|
severity: "INFO",
|
|
65
|
-
message: "Missing WHERE clause"
|
|
83
|
+
message: "Missing WHERE clause",
|
|
84
|
+
location: fromLocation
|
|
66
85
|
});
|
|
67
86
|
}
|
|
68
87
|
if (!node.SelectStmt.limitCount) {
|
|
69
88
|
nudges.push({
|
|
70
89
|
kind: "MISSING_LIMIT_CLAUSE",
|
|
71
90
|
severity: "INFO",
|
|
72
|
-
message: "Missing LIMIT clause"
|
|
91
|
+
message: "Missing LIMIT clause",
|
|
92
|
+
location: fromLocation
|
|
73
93
|
});
|
|
74
94
|
}
|
|
75
95
|
}
|
|
@@ -85,7 +105,8 @@ function parseNudges(node, stack) {
|
|
|
85
105
|
nudges.push({
|
|
86
106
|
kind: "USE_IS_NULL_NOT_EQUALS",
|
|
87
107
|
severity: "WARNING",
|
|
88
|
-
message: "Use IS NULL instead of = or != or <> for NULL comparisons"
|
|
108
|
+
message: "Use IS NULL instead of = or != or <> for NULL comparisons",
|
|
109
|
+
location: node.A_Expr.location
|
|
89
110
|
});
|
|
90
111
|
}
|
|
91
112
|
}
|
|
@@ -93,10 +114,15 @@ function parseNudges(node, stack) {
|
|
|
93
114
|
if (isLikeOp && node.A_Expr.rexpr) {
|
|
94
115
|
const patternString = getStringConstantValue(node.A_Expr.rexpr);
|
|
95
116
|
if (patternString && patternString.startsWith("%")) {
|
|
117
|
+
let stringNode;
|
|
118
|
+
if (is(node.A_Expr.rexpr, "A_Const")) {
|
|
119
|
+
stringNode = node.A_Expr.rexpr.A_Const;
|
|
120
|
+
}
|
|
96
121
|
nudges.push({
|
|
97
122
|
kind: "AVOID_LEADING_WILDCARD_LIKE",
|
|
98
123
|
severity: "WARNING",
|
|
99
|
-
message: "Avoid using LIKE with leading wildcards"
|
|
124
|
+
message: "Avoid using LIKE with leading wildcards",
|
|
125
|
+
location: stringNode?.location
|
|
100
126
|
});
|
|
101
127
|
}
|
|
102
128
|
}
|
|
@@ -118,14 +144,15 @@ function parseNudges(node, stack) {
|
|
|
118
144
|
}
|
|
119
145
|
}
|
|
120
146
|
if (is(node, "SelectStmt") && node.SelectStmt.fromClause && node.SelectStmt.fromClause.length > 1) {
|
|
121
|
-
const
|
|
147
|
+
const tables = node.SelectStmt.fromClause.filter(
|
|
122
148
|
(item) => is(item, "RangeVar")
|
|
123
|
-
)
|
|
124
|
-
if (
|
|
149
|
+
);
|
|
150
|
+
if (tables.length > 1) {
|
|
125
151
|
nudges.push({
|
|
126
152
|
kind: "MISSING_JOIN_CONDITION",
|
|
127
153
|
severity: "WARNING",
|
|
128
|
-
message: "Missing JOIN condition"
|
|
154
|
+
message: "Missing JOIN condition",
|
|
155
|
+
location: tables[1].RangeVar.location
|
|
129
156
|
});
|
|
130
157
|
}
|
|
131
158
|
}
|
|
@@ -135,7 +162,8 @@ function parseNudges(node, stack) {
|
|
|
135
162
|
nudges.push({
|
|
136
163
|
kind: "CONSIDER_IN_INSTEAD_OF_MANY_ORS",
|
|
137
164
|
severity: "WARNING",
|
|
138
|
-
message: "Consider using IN instead of many ORs"
|
|
165
|
+
message: "Consider using IN instead of many ORs",
|
|
166
|
+
location: node.BoolExpr.location
|
|
139
167
|
});
|
|
140
168
|
}
|
|
141
169
|
}
|
|
@@ -151,7 +179,8 @@ function parseNudges(node, stack) {
|
|
|
151
179
|
nudges.push({
|
|
152
180
|
kind: "REPLACE_LARGE_IN_TUPLE_WITH_ANY_ARRAY",
|
|
153
181
|
message: "`in (...)` queries with large tuples can often be replaced with `= ANY($1)` using a single parameter",
|
|
154
|
-
severity: "INFO"
|
|
182
|
+
severity: "INFO",
|
|
183
|
+
location: node.A_Expr.location
|
|
155
184
|
});
|
|
156
185
|
}
|
|
157
186
|
}
|
|
@@ -265,7 +294,7 @@ var Walker = class _Walker {
|
|
|
265
294
|
this.seenReferences = /* @__PURE__ */ new Map();
|
|
266
295
|
this.shadowedAliases = [];
|
|
267
296
|
this.nudges = [];
|
|
268
|
-
_Walker.traverse(root,
|
|
297
|
+
_Walker.traverse(root, (node, stack) => {
|
|
269
298
|
const nodeNudges = parseNudges(node, stack);
|
|
270
299
|
this.nudges = [...this.nudges, ...nodeNudges];
|
|
271
300
|
if (is2(node, "CommonTableExpr")) {
|
|
@@ -458,7 +487,10 @@ var Walker = class _Walker {
|
|
|
458
487
|
}
|
|
459
488
|
this.highlights.push(ref);
|
|
460
489
|
}
|
|
461
|
-
static traverse(node,
|
|
490
|
+
static traverse(node, callback) {
|
|
491
|
+
_Walker.doTraverse(node, [], callback);
|
|
492
|
+
}
|
|
493
|
+
static doTraverse(node, stack, callback) {
|
|
462
494
|
if (isANode2(node)) {
|
|
463
495
|
callback(node, [...stack, getNodeKind(node)]);
|
|
464
496
|
}
|
|
@@ -468,15 +500,19 @@ var Walker = class _Walker {
|
|
|
468
500
|
if (Array.isArray(node)) {
|
|
469
501
|
for (const item of node) {
|
|
470
502
|
if (isANode2(item)) {
|
|
471
|
-
_Walker.
|
|
503
|
+
_Walker.doTraverse(item, stack, callback);
|
|
472
504
|
}
|
|
473
505
|
}
|
|
474
506
|
} else if (isANode2(node)) {
|
|
475
507
|
const keys = Object.keys(node);
|
|
476
|
-
_Walker.
|
|
508
|
+
_Walker.doTraverse(node[keys[0]], [...stack, getNodeKind(node)], callback);
|
|
477
509
|
} else {
|
|
478
510
|
for (const [key, child] of Object.entries(node)) {
|
|
479
|
-
_Walker.
|
|
511
|
+
_Walker.doTraverse(
|
|
512
|
+
child,
|
|
513
|
+
[...stack, key],
|
|
514
|
+
callback
|
|
515
|
+
);
|
|
480
516
|
}
|
|
481
517
|
}
|
|
482
518
|
}
|