@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.cjs CHANGED
@@ -53,8 +53,7 @@ __export(index_exports, {
53
53
  ignoredIdentifier: () => ignoredIdentifier,
54
54
  isIndexProbablyDroppable: () => isIndexProbablyDroppable,
55
55
  isIndexSupported: () => isIndexSupported,
56
- parseNudges: () => parseNudges,
57
- permuteWithFeedback: () => permuteWithFeedback
56
+ parseNudges: () => parseNudges
58
57
  });
59
58
  module.exports = __toCommonJS(index_exports);
60
59
 
@@ -830,6 +829,9 @@ var PostgresQueryBuilder = class _PostgresQueryBuilder {
830
829
  __publicField(this, "isIntrospection", false);
831
830
  __publicField(this, "explainFlags", []);
832
831
  __publicField(this, "_preamble", 0);
832
+ __publicField(this, "parameters", {});
833
+ // substitution for `limit $1` -> `limit 5`
834
+ __publicField(this, "limitSubstitution");
833
835
  }
834
836
  get preamble() {
835
837
  return this._preamble;
@@ -863,6 +865,14 @@ var PostgresQueryBuilder = class _PostgresQueryBuilder {
863
865
  this.explainFlags = flags;
864
866
  return this;
865
867
  }
868
+ parameterize(parameters) {
869
+ Object.assign(this.parameters, parameters);
870
+ return this;
871
+ }
872
+ replaceLimit(limit) {
873
+ this.limitSubstitution = limit;
874
+ return this;
875
+ }
866
876
  build() {
867
877
  let commands = this.generateSetCommands();
868
878
  commands += this.generateExplain().query;
@@ -891,14 +901,28 @@ var PostgresQueryBuilder = class _PostgresQueryBuilder {
891
901
  return commands;
892
902
  }
893
903
  generateExplain() {
894
- let query = "";
904
+ let finalQuery = "";
895
905
  if (this.explainFlags.length > 0) {
896
- query += `explain (${this.explainFlags.join(", ")}) `;
906
+ finalQuery += `explain (${this.explainFlags.join(", ")}) `;
907
+ }
908
+ const query = this.substituteQuery();
909
+ const semicolon = query.endsWith(";") ? "" : ";";
910
+ const preamble = finalQuery.length;
911
+ finalQuery += `${query}${semicolon}`;
912
+ return { query: finalQuery, preamble };
913
+ }
914
+ substituteQuery() {
915
+ let query = this.query;
916
+ if (this.limitSubstitution !== void 0) {
917
+ query = query.replace(
918
+ /limit\s+\$\d+/g,
919
+ `limit ${this.limitSubstitution}`
920
+ );
897
921
  }
898
- const semicolon = this.query.endsWith(";") ? "" : ";";
899
- const preamble = query.length;
900
- query += `${this.query}${semicolon}`;
901
- return { query, preamble };
922
+ for (const [key, value] of Object.entries(this.parameters)) {
923
+ query = query.replaceAll(`\\$${key}`, value.toString());
924
+ }
925
+ return query;
902
926
  }
903
927
  };
904
928
 
@@ -916,7 +940,9 @@ var _PgIdentifier = class _PgIdentifier {
916
940
  const identifierRegex = /^[a-z_][a-zA-Z0-9_]*$/;
917
941
  const match = identifier.match(/^"(.+)"$/);
918
942
  if (match) {
919
- return new _PgIdentifier(match[1], true);
943
+ const value = match[1];
944
+ const quoted2 = !identifierRegex.test(value) || this.reservedKeywords.has(value.toLowerCase());
945
+ return new _PgIdentifier(value, quoted2);
920
946
  }
921
947
  const quoted = !identifierRegex.test(identifier) || this.reservedKeywords.has(identifier.toLowerCase());
922
948
  return new _PgIdentifier(identifier, quoted);
@@ -1121,6 +1147,24 @@ var PgIdentifier = _PgIdentifier;
1121
1147
 
1122
1148
  // src/optimizer/genalgo.ts
1123
1149
  var import_colorette2 = require("colorette");
1150
+
1151
+ // src/sql/permutations.ts
1152
+ function permutationsWithDescendingLength(arr) {
1153
+ const collected = [];
1154
+ function collect(path, rest) {
1155
+ for (let i = 0; i < rest.length; i++) {
1156
+ const nextRest = [...rest.slice(0, i), ...rest.slice(i + 1)];
1157
+ const nextPath = [...path, rest[i]];
1158
+ collected.push(nextPath);
1159
+ collect(nextPath, nextRest);
1160
+ }
1161
+ }
1162
+ collect([], arr);
1163
+ collected.sort((a, b) => b.length - a.length);
1164
+ return collected;
1165
+ }
1166
+
1167
+ // src/optimizer/genalgo.ts
1124
1168
  var _IndexOptimizer = class _IndexOptimizer {
1125
1169
  constructor(db, statistics, existingIndexes, config = {}) {
1126
1170
  this.db = db;
@@ -1253,14 +1297,12 @@ var _IndexOptimizer = class _IndexOptimizer {
1253
1297
  * Derive the list of indexes [tableA(X, Y, Z), tableB(H, I, J)]
1254
1298
  **/
1255
1299
  indexesToCreate(rootCandidates) {
1256
- const permutedIndexes = this.tableColumnIndexCandidates(rootCandidates);
1300
+ const permutedIndexes = this.groupPotentialIndexColumnsByTable(rootCandidates);
1257
1301
  const nextStage = [];
1258
1302
  for (const permutation of permutedIndexes.values()) {
1259
1303
  const { table: rawTable, schema: rawSchema, columns } = permutation;
1260
- const permutations = permuteWithFeedback(columns);
1261
- let iter = permutations.next(PROCEED);
1262
- while (!iter.done) {
1263
- const columns2 = iter.value;
1304
+ const permutations = permutationsWithDescendingLength(columns);
1305
+ for (const columns2 of permutations) {
1264
1306
  const schema = PgIdentifier.fromString(rawSchema);
1265
1307
  const table = PgIdentifier.fromString(rawTable);
1266
1308
  const existingIndex = this.indexAlreadyExists(
@@ -1268,12 +1310,10 @@ var _IndexOptimizer = class _IndexOptimizer {
1268
1310
  columns2
1269
1311
  );
1270
1312
  if (existingIndex) {
1271
- iter = permutations.next(PROCEED);
1272
1313
  continue;
1273
1314
  }
1274
1315
  const indexName = this.indexName();
1275
1316
  const definition = this.toDefinition({ table, schema, columns: columns2 }).raw;
1276
- iter = permutations.next(PROCEED);
1277
1317
  nextStage.push({
1278
1318
  name: indexName,
1279
1319
  schema: schema.toString(),
@@ -1405,7 +1445,7 @@ var _IndexOptimizer = class _IndexOptimizer {
1405
1445
  }
1406
1446
  throw new Error("Unreachable");
1407
1447
  }
1408
- tableColumnIndexCandidates(indexes) {
1448
+ groupPotentialIndexColumnsByTable(indexes) {
1409
1449
  const tableColumns = /* @__PURE__ */ new Map();
1410
1450
  for (const index of indexes) {
1411
1451
  const existing = tableColumns.get(`${index.schema}.${index.table}`);
@@ -1475,21 +1515,6 @@ var RollbackError = class {
1475
1515
  };
1476
1516
  var PROCEED = Symbol("PROCEED");
1477
1517
  var SKIP = Symbol("SKIP");
1478
- function* permuteWithFeedback(arr) {
1479
- function* helper(path, rest) {
1480
- let i = 0;
1481
- while (i < rest.length) {
1482
- const nextPath = [...path, rest[i]];
1483
- const nextRest = [...rest.slice(0, i), ...rest.slice(i + 1)];
1484
- const input = yield nextPath;
1485
- if (input === PROCEED) {
1486
- yield* helper(nextPath, nextRest);
1487
- }
1488
- i++;
1489
- }
1490
- }
1491
- yield* helper([], arr);
1492
- }
1493
1518
 
1494
1519
  // src/optimizer/statistics.ts
1495
1520
  var import_colorette3 = require("colorette");
@@ -2206,7 +2231,6 @@ var PssRewriter = class {
2206
2231
  ignoredIdentifier,
2207
2232
  isIndexProbablyDroppable,
2208
2233
  isIndexSupported,
2209
- parseNudges,
2210
- permuteWithFeedback
2234
+ parseNudges
2211
2235
  });
2212
2236
  //# sourceMappingURL=index.cjs.map