@query-doctor/core 0.2.4 → 0.3.0

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.js CHANGED
@@ -780,6 +780,9 @@ var PostgresQueryBuilder = class _PostgresQueryBuilder {
780
780
  __publicField(this, "isIntrospection", false);
781
781
  __publicField(this, "explainFlags", []);
782
782
  __publicField(this, "_preamble", 0);
783
+ __publicField(this, "parameters", {});
784
+ // substitution for `limit $1` -> `limit 5`
785
+ __publicField(this, "limitSubstitution");
783
786
  }
784
787
  get preamble() {
785
788
  return this._preamble;
@@ -813,6 +816,14 @@ var PostgresQueryBuilder = class _PostgresQueryBuilder {
813
816
  this.explainFlags = flags;
814
817
  return this;
815
818
  }
819
+ parameterize(parameters) {
820
+ Object.assign(this.parameters, parameters);
821
+ return this;
822
+ }
823
+ replaceLimit(limit) {
824
+ this.limitSubstitution = limit;
825
+ return this;
826
+ }
816
827
  build() {
817
828
  let commands = this.generateSetCommands();
818
829
  commands += this.generateExplain().query;
@@ -841,14 +852,28 @@ var PostgresQueryBuilder = class _PostgresQueryBuilder {
841
852
  return commands;
842
853
  }
843
854
  generateExplain() {
844
- let query = "";
855
+ let finalQuery = "";
845
856
  if (this.explainFlags.length > 0) {
846
- query += `explain (${this.explainFlags.join(", ")}) `;
857
+ finalQuery += `explain (${this.explainFlags.join(", ")}) `;
858
+ }
859
+ const query = this.substituteQuery();
860
+ const semicolon = query.endsWith(";") ? "" : ";";
861
+ const preamble = finalQuery.length;
862
+ finalQuery += `${query}${semicolon}`;
863
+ return { query: finalQuery, preamble };
864
+ }
865
+ substituteQuery() {
866
+ let query = this.query;
867
+ if (this.limitSubstitution !== void 0) {
868
+ query = query.replace(
869
+ /limit\s+\$\d+/g,
870
+ `limit ${this.limitSubstitution}`
871
+ );
847
872
  }
848
- const semicolon = this.query.endsWith(";") ? "" : ";";
849
- const preamble = query.length;
850
- query += `${this.query}${semicolon}`;
851
- return { query, preamble };
873
+ for (const [key, value] of Object.entries(this.parameters)) {
874
+ query = query.replaceAll(`\\$${key}`, value.toString());
875
+ }
876
+ return query;
852
877
  }
853
878
  };
854
879
 
@@ -866,7 +891,9 @@ var _PgIdentifier = class _PgIdentifier {
866
891
  const identifierRegex = /^[a-z_][a-zA-Z0-9_]*$/;
867
892
  const match = identifier.match(/^"(.+)"$/);
868
893
  if (match) {
869
- return new _PgIdentifier(match[1], true);
894
+ const value = match[1];
895
+ const quoted2 = !identifierRegex.test(value) || this.reservedKeywords.has(value.toLowerCase());
896
+ return new _PgIdentifier(value, quoted2);
870
897
  }
871
898
  const quoted = !identifierRegex.test(identifier) || this.reservedKeywords.has(identifier.toLowerCase());
872
899
  return new _PgIdentifier(identifier, quoted);
@@ -1071,6 +1098,24 @@ var PgIdentifier = _PgIdentifier;
1071
1098
 
1072
1099
  // src/optimizer/genalgo.ts
1073
1100
  import { blue as blue2, gray, green, magenta, red, yellow } from "colorette";
1101
+
1102
+ // src/sql/permutations.ts
1103
+ function permutationsWithDescendingLength(arr) {
1104
+ const collected = [];
1105
+ function collect(path, rest) {
1106
+ for (let i = 0; i < rest.length; i++) {
1107
+ const nextRest = [...rest.slice(0, i), ...rest.slice(i + 1)];
1108
+ const nextPath = [...path, rest[i]];
1109
+ collected.push(nextPath);
1110
+ collect(nextPath, nextRest);
1111
+ }
1112
+ }
1113
+ collect([], arr);
1114
+ collected.sort((a, b) => b.length - a.length);
1115
+ return collected;
1116
+ }
1117
+
1118
+ // src/optimizer/genalgo.ts
1074
1119
  var _IndexOptimizer = class _IndexOptimizer {
1075
1120
  constructor(db, statistics, existingIndexes, config = {}) {
1076
1121
  this.db = db;
@@ -1203,14 +1248,12 @@ var _IndexOptimizer = class _IndexOptimizer {
1203
1248
  * Derive the list of indexes [tableA(X, Y, Z), tableB(H, I, J)]
1204
1249
  **/
1205
1250
  indexesToCreate(rootCandidates) {
1206
- const permutedIndexes = this.tableColumnIndexCandidates(rootCandidates);
1251
+ const permutedIndexes = this.groupPotentialIndexColumnsByTable(rootCandidates);
1207
1252
  const nextStage = [];
1208
1253
  for (const permutation of permutedIndexes.values()) {
1209
1254
  const { table: rawTable, schema: rawSchema, columns } = permutation;
1210
- const permutations = permuteWithFeedback(columns);
1211
- let iter = permutations.next(PROCEED);
1212
- while (!iter.done) {
1213
- const columns2 = iter.value;
1255
+ const permutations = permutationsWithDescendingLength(columns);
1256
+ for (const columns2 of permutations) {
1214
1257
  const schema = PgIdentifier.fromString(rawSchema);
1215
1258
  const table = PgIdentifier.fromString(rawTable);
1216
1259
  const existingIndex = this.indexAlreadyExists(
@@ -1218,12 +1261,10 @@ var _IndexOptimizer = class _IndexOptimizer {
1218
1261
  columns2
1219
1262
  );
1220
1263
  if (existingIndex) {
1221
- iter = permutations.next(PROCEED);
1222
1264
  continue;
1223
1265
  }
1224
1266
  const indexName = this.indexName();
1225
1267
  const definition = this.toDefinition({ table, schema, columns: columns2 }).raw;
1226
- iter = permutations.next(PROCEED);
1227
1268
  nextStage.push({
1228
1269
  name: indexName,
1229
1270
  schema: schema.toString(),
@@ -1355,7 +1396,7 @@ var _IndexOptimizer = class _IndexOptimizer {
1355
1396
  }
1356
1397
  throw new Error("Unreachable");
1357
1398
  }
1358
- tableColumnIndexCandidates(indexes) {
1399
+ groupPotentialIndexColumnsByTable(indexes) {
1359
1400
  const tableColumns = /* @__PURE__ */ new Map();
1360
1401
  for (const index of indexes) {
1361
1402
  const existing = tableColumns.get(`${index.schema}.${index.table}`);
@@ -1425,21 +1466,6 @@ var RollbackError = class {
1425
1466
  };
1426
1467
  var PROCEED = Symbol("PROCEED");
1427
1468
  var SKIP = Symbol("SKIP");
1428
- function* permuteWithFeedback(arr) {
1429
- function* helper(path, rest) {
1430
- let i = 0;
1431
- while (i < rest.length) {
1432
- const nextPath = [...path, rest[i]];
1433
- const nextRest = [...rest.slice(0, i), ...rest.slice(i + 1)];
1434
- const input = yield nextPath;
1435
- if (input === PROCEED) {
1436
- yield* helper(nextPath, nextRest);
1437
- }
1438
- i++;
1439
- }
1440
- }
1441
- yield* helper([], arr);
1442
- }
1443
1469
 
1444
1470
  // src/optimizer/statistics.ts
1445
1471
  import { gray as gray2 } from "colorette";
@@ -2155,7 +2181,6 @@ export {
2155
2181
  ignoredIdentifier,
2156
2182
  isIndexProbablyDroppable,
2157
2183
  isIndexSupported,
2158
- parseNudges,
2159
- permuteWithFeedback
2184
+ parseNudges
2160
2185
  };
2161
2186
  //# sourceMappingURL=index.js.map