@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.
Files changed (39) hide show
  1. package/dist/explain/rewriter.d.ts +4 -0
  2. package/dist/explain/rewriter.d.ts.map +1 -0
  3. package/dist/explain/traverse.d.ts +3 -0
  4. package/dist/explain/traverse.d.ts.map +1 -0
  5. package/dist/explain/tree.d.ts +73 -0
  6. package/dist/explain/tree.d.ts.map +1 -0
  7. package/dist/index.cjs +58 -22
  8. package/dist/index.cjs.map +1 -1
  9. package/dist/index.js +58 -22
  10. package/dist/index.js.map +1 -1
  11. package/dist/index.mjs +25333 -0
  12. package/dist/index.mjs.map +1 -0
  13. package/dist/optimizer/genalgo.test.d.ts +2 -0
  14. package/dist/optimizer/genalgo.test.d.ts.map +1 -0
  15. package/dist/optimizer/index-candidate.d.ts +23 -0
  16. package/dist/optimizer/index-candidate.d.ts.map +1 -0
  17. package/dist/optimizer/index-shrinker.d.ts +10 -0
  18. package/dist/optimizer/index-shrinker.d.ts.map +1 -0
  19. package/dist/optimizer/index-shrinker.test.d.ts +2 -0
  20. package/dist/optimizer/index-shrinker.test.d.ts.map +1 -0
  21. package/dist/optimizer/index-tester.d.ts +2 -0
  22. package/dist/optimizer/index-tester.d.ts.map +1 -0
  23. package/dist/optimizer/pss-rewriter.test.d.ts +2 -0
  24. package/dist/optimizer/pss-rewriter.test.d.ts.map +1 -0
  25. package/dist/sql/analyzer.test.d.ts +2 -0
  26. package/dist/sql/analyzer.test.d.ts.map +1 -0
  27. package/dist/sql/builder.test.d.ts +2 -0
  28. package/dist/sql/builder.test.d.ts.map +1 -0
  29. package/dist/sql/nudges.d.ts +1 -0
  30. package/dist/sql/nudges.d.ts.map +1 -1
  31. package/dist/sql/permutations.test.d.ts +2 -0
  32. package/dist/sql/permutations.test.d.ts.map +1 -0
  33. package/dist/sql/pg-identifier.test.d.ts +2 -0
  34. package/dist/sql/pg-identifier.test.d.ts.map +1 -0
  35. package/dist/sql/walker.d.ts +2 -1
  36. package/dist/sql/walker.d.ts.map +1 -1
  37. package/dist/sql/walker.test.d.ts +2 -0
  38. package/dist/sql/walker.test.d.ts.map +1 -0
  39. 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, "A_Star")) {
31
- nudges.push({
32
- kind: "AVOID_SELECT_STAR",
33
- severity: "INFO",
34
- message: "Avoid using SELECT *"
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 tableCount = node.SelectStmt.fromClause.filter(
147
+ const tables = node.SelectStmt.fromClause.filter(
122
148
  (item) => is(item, "RangeVar")
123
- ).length;
124
- if (tableCount > 1) {
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, [], (node, stack) => {
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, stack, callback) {
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.traverse(item, stack, callback);
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.traverse(node[keys[0]], [...stack, getNodeKind(node)], callback);
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.traverse(child, [...stack, key], callback);
511
+ _Walker.doTraverse(
512
+ child,
513
+ [...stack, key],
514
+ callback
515
+ );
480
516
  }
481
517
  }
482
518
  }