@query-doctor/core 0.8.1 → 0.8.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.
Files changed (118) hide show
  1. package/dist/_virtual/_@oxc-project_runtime@0.122.0/helpers/defineProperty.cjs +13 -0
  2. package/dist/_virtual/_@oxc-project_runtime@0.122.0/helpers/defineProperty.mjs +13 -0
  3. package/dist/_virtual/_@oxc-project_runtime@0.122.0/helpers/toPrimitive.cjs +15 -0
  4. package/dist/_virtual/_@oxc-project_runtime@0.122.0/helpers/toPrimitive.mjs +15 -0
  5. package/dist/_virtual/_@oxc-project_runtime@0.122.0/helpers/toPropertyKey.cjs +10 -0
  6. package/dist/_virtual/_@oxc-project_runtime@0.122.0/helpers/toPropertyKey.mjs +10 -0
  7. package/dist/_virtual/_@oxc-project_runtime@0.122.0/helpers/typeof.cjs +17 -0
  8. package/dist/_virtual/_@oxc-project_runtime@0.122.0/helpers/typeof.mjs +12 -0
  9. package/dist/_virtual/_rolldown/runtime.cjs +24 -0
  10. package/dist/index.cjs +33 -2568
  11. package/dist/index.d.cts +11 -781
  12. package/dist/index.d.mts +11 -781
  13. package/dist/index.mjs +10 -2522
  14. package/dist/optimizer/genalgo.cjs +365 -0
  15. package/dist/optimizer/genalgo.cjs.map +1 -0
  16. package/dist/optimizer/genalgo.d.cts +111 -0
  17. package/dist/optimizer/genalgo.d.cts.map +1 -0
  18. package/dist/optimizer/genalgo.d.mts +111 -0
  19. package/dist/optimizer/genalgo.d.mts.map +1 -0
  20. package/dist/optimizer/genalgo.mjs +362 -0
  21. package/dist/optimizer/genalgo.mjs.map +1 -0
  22. package/dist/optimizer/pss-rewriter.cjs +31 -0
  23. package/dist/optimizer/pss-rewriter.cjs.map +1 -0
  24. package/dist/optimizer/pss-rewriter.d.cts +16 -0
  25. package/dist/optimizer/pss-rewriter.d.cts.map +1 -0
  26. package/dist/optimizer/pss-rewriter.d.mts +16 -0
  27. package/dist/optimizer/pss-rewriter.d.mts.map +1 -0
  28. package/dist/optimizer/pss-rewriter.mjs +31 -0
  29. package/dist/optimizer/pss-rewriter.mjs.map +1 -0
  30. package/dist/optimizer/statistics.cjs +738 -0
  31. package/dist/optimizer/statistics.cjs.map +1 -0
  32. package/dist/optimizer/statistics.d.cts +389 -0
  33. package/dist/optimizer/statistics.d.cts.map +1 -0
  34. package/dist/optimizer/statistics.d.mts +389 -0
  35. package/dist/optimizer/statistics.d.mts.map +1 -0
  36. package/dist/optimizer/statistics.mjs +729 -0
  37. package/dist/optimizer/statistics.mjs.map +1 -0
  38. package/dist/sentry.cjs +13 -0
  39. package/dist/sentry.cjs.map +1 -0
  40. package/dist/sentry.d.cts +7 -0
  41. package/dist/sentry.d.cts.map +1 -0
  42. package/dist/sentry.d.mts +7 -0
  43. package/dist/sentry.d.mts.map +1 -0
  44. package/dist/sentry.mjs +13 -0
  45. package/dist/sentry.mjs.map +1 -0
  46. package/dist/sql/analyzer.cjs +242 -0
  47. package/dist/sql/analyzer.cjs.map +1 -0
  48. package/dist/sql/analyzer.d.cts +112 -0
  49. package/dist/sql/analyzer.d.cts.map +1 -0
  50. package/dist/sql/analyzer.d.mts +112 -0
  51. package/dist/sql/analyzer.d.mts.map +1 -0
  52. package/dist/sql/analyzer.mjs +240 -0
  53. package/dist/sql/analyzer.mjs.map +1 -0
  54. package/dist/sql/ast-utils.cjs +19 -0
  55. package/dist/sql/ast-utils.cjs.map +1 -0
  56. package/dist/sql/ast-utils.d.cts +9 -0
  57. package/dist/sql/ast-utils.d.cts.map +1 -0
  58. package/dist/sql/ast-utils.d.mts +9 -0
  59. package/dist/sql/ast-utils.d.mts.map +1 -0
  60. package/dist/sql/ast-utils.mjs +17 -0
  61. package/dist/sql/ast-utils.mjs.map +1 -0
  62. package/dist/sql/builder.cjs +94 -0
  63. package/dist/sql/builder.cjs.map +1 -0
  64. package/dist/sql/builder.d.cts +37 -0
  65. package/dist/sql/builder.d.cts.map +1 -0
  66. package/dist/sql/builder.d.mts +37 -0
  67. package/dist/sql/builder.d.mts.map +1 -0
  68. package/dist/sql/builder.mjs +94 -0
  69. package/dist/sql/builder.mjs.map +1 -0
  70. package/dist/sql/database.cjs +35 -0
  71. package/dist/sql/database.cjs.map +1 -0
  72. package/dist/sql/database.d.cts +91 -0
  73. package/dist/sql/database.d.cts.map +1 -0
  74. package/dist/sql/database.d.mts +91 -0
  75. package/dist/sql/database.d.mts.map +1 -0
  76. package/dist/sql/database.mjs +32 -0
  77. package/dist/sql/database.mjs.map +1 -0
  78. package/dist/sql/indexes.cjs +17 -0
  79. package/dist/sql/indexes.cjs.map +1 -0
  80. package/dist/sql/indexes.d.cts +14 -0
  81. package/dist/sql/indexes.d.cts.map +1 -0
  82. package/dist/sql/indexes.d.mts +14 -0
  83. package/dist/sql/indexes.d.mts.map +1 -0
  84. package/dist/sql/indexes.mjs +16 -0
  85. package/dist/sql/indexes.mjs.map +1 -0
  86. package/dist/sql/nudges.cjs +484 -0
  87. package/dist/sql/nudges.cjs.map +1 -0
  88. package/dist/sql/nudges.d.cts +21 -0
  89. package/dist/sql/nudges.d.cts.map +1 -0
  90. package/dist/sql/nudges.d.mts +21 -0
  91. package/dist/sql/nudges.d.mts.map +1 -0
  92. package/dist/sql/nudges.mjs +484 -0
  93. package/dist/sql/nudges.mjs.map +1 -0
  94. package/dist/sql/permutations.cjs +28 -0
  95. package/dist/sql/permutations.cjs.map +1 -0
  96. package/dist/sql/permutations.mjs +28 -0
  97. package/dist/sql/permutations.mjs.map +1 -0
  98. package/dist/sql/pg-identifier.cjs +216 -0
  99. package/dist/sql/pg-identifier.cjs.map +1 -0
  100. package/dist/sql/pg-identifier.d.cts +31 -0
  101. package/dist/sql/pg-identifier.d.cts.map +1 -0
  102. package/dist/sql/pg-identifier.d.mts +31 -0
  103. package/dist/sql/pg-identifier.d.mts.map +1 -0
  104. package/dist/sql/pg-identifier.mjs +216 -0
  105. package/dist/sql/pg-identifier.mjs.map +1 -0
  106. package/dist/sql/walker.cjs +314 -0
  107. package/dist/sql/walker.cjs.map +1 -0
  108. package/dist/sql/walker.d.cts +15 -0
  109. package/dist/sql/walker.d.cts.map +1 -0
  110. package/dist/sql/walker.d.mts +15 -0
  111. package/dist/sql/walker.d.mts.map +1 -0
  112. package/dist/sql/walker.mjs +313 -0
  113. package/dist/sql/walker.mjs.map +1 -0
  114. package/package.json +2 -1
  115. package/dist/index.cjs.map +0 -1
  116. package/dist/index.d.cts.map +0 -1
  117. package/dist/index.d.mts.map +0 -1
  118. package/dist/index.mjs.map +0 -1
@@ -0,0 +1,365 @@
1
+ "use client";
2
+ require("../_virtual/_rolldown/runtime.cjs");
3
+ const require_defineProperty = require("../_virtual/_@oxc-project_runtime@0.122.0/helpers/defineProperty.cjs");
4
+ const require_database = require("../sql/database.cjs");
5
+ const require_indexes = require("../sql/indexes.cjs");
6
+ const require_builder = require("../sql/builder.cjs");
7
+ const require_pg_identifier = require("../sql/pg-identifier.cjs");
8
+ const require_permutations = require("../sql/permutations.cjs");
9
+ let colorette = require("colorette");
10
+ //#region src/optimizer/genalgo.ts
11
+ var IndexOptimizer = class IndexOptimizer {
12
+ constructor(db, statistics, existingIndexes, config = {}) {
13
+ this.db = db;
14
+ this.statistics = statistics;
15
+ this.existingIndexes = existingIndexes;
16
+ this.config = config;
17
+ }
18
+ async run(builder, indexes, beforeQuery) {
19
+ const baseExplain = await this.testQueryWithStats(builder, async (tx) => {
20
+ if (beforeQuery) await beforeQuery(tx);
21
+ });
22
+ const baseCost = Number(baseExplain.Plan["Total Cost"]);
23
+ if (baseCost === 0) return {
24
+ kind: "zero_cost_plan",
25
+ explainPlan: baseExplain.Plan
26
+ };
27
+ const toCreate = this.indexesToCreate(indexes);
28
+ if (toCreate.length === 0) {
29
+ const baseIndexes = this.findUsedIndexes(baseExplain.Plan);
30
+ return {
31
+ kind: "ok",
32
+ baseCost,
33
+ finalCost: baseCost,
34
+ newIndexes: /* @__PURE__ */ new Set(),
35
+ existingIndexes: baseIndexes.existingIndexes,
36
+ triedIndexes: /* @__PURE__ */ new Map(),
37
+ baseExplainPlan: baseExplain.Plan,
38
+ explainPlan: baseExplain.Plan
39
+ };
40
+ }
41
+ const finalExplain = await this.testQueryWithStats(builder, async (tx) => {
42
+ if (beforeQuery) await beforeQuery(tx);
43
+ for (const permutation of toCreate) {
44
+ const createIndex = require_builder.PostgresQueryBuilder.createIndex(permutation.definition, permutation.name).introspect().build();
45
+ await tx.exec(createIndex);
46
+ }
47
+ });
48
+ const finalCost = Number(finalExplain.Plan["Total Cost"]);
49
+ if (this.config.debug) console.dir(finalExplain, { depth: null });
50
+ const deltaPercentage = (baseCost - finalCost) / baseCost * 100;
51
+ if (finalCost < baseCost) console.log(` 🎉🎉🎉 ${(0, colorette.green)(`+${deltaPercentage.toFixed(2).padStart(5, "0")}%`)}`);
52
+ else if (finalCost > baseCost) console.log(`${(0, colorette.red)(`-${Math.abs(deltaPercentage).toFixed(2).padStart(5, "0")}%`)} ${(0, colorette.gray)("If there's a better index, we haven't tried it")}`);
53
+ const baseIndexes = this.findUsedIndexes(baseExplain.Plan);
54
+ const finalIndexes = this.findUsedIndexes(finalExplain.Plan);
55
+ const triedIndexes = new Map(toCreate.map((index) => [index.name.toString(), index]));
56
+ this.replaceUsedIndexesWithDefinition(finalExplain.Plan, triedIndexes);
57
+ return {
58
+ kind: "ok",
59
+ baseCost,
60
+ finalCost,
61
+ newIndexes: finalIndexes.newIndexes,
62
+ existingIndexes: baseIndexes.existingIndexes,
63
+ triedIndexes,
64
+ baseExplainPlan: baseExplain.Plan,
65
+ explainPlan: finalExplain.Plan
66
+ };
67
+ }
68
+ async runWithoutIndexes(builder) {
69
+ return await this.testQueryWithStats(builder, async (tx) => {
70
+ await this.dropExistingIndexes(tx);
71
+ });
72
+ }
73
+ /**
74
+ * Given the current indexes in the optimizer, transform them in some
75
+ * way to change which indexes will be assumed to exist when optimizing
76
+ *
77
+ * @example
78
+ * ```
79
+ * // resets indexes
80
+ * optimizer.transformIndexes(() => [])
81
+ *
82
+ * // adds new index
83
+ * optimizer.transformIndexes(indexes => [...indexes, newIndex])
84
+ * ```
85
+ */
86
+ transformIndexes(f) {
87
+ this.existingIndexes = f(this.existingIndexes);
88
+ return this;
89
+ }
90
+ /**
91
+ * Postgres has a limit of 63 characters for index names.
92
+ * So we use this to make sure we don't derive it from a list of columns that can
93
+ * overflow that limit.
94
+ */
95
+ indexName() {
96
+ const indexName = IndexOptimizer.prefix + Math.random().toString(36).substring(2, 16);
97
+ return require_pg_identifier.PgIdentifier.fromString(indexName);
98
+ }
99
+ indexAlreadyExists(table, columns) {
100
+ return this.existingIndexes.find((index) => index.index_type === "btree" && index.table_name === table && index.index_columns.length === columns.length && index.index_columns.every((c, i) => {
101
+ if (columns[i].column !== c.name) return false;
102
+ if (columns[i].where) return false;
103
+ if (columns[i].sort) switch (columns[i].sort.dir) {
104
+ case "SORTBY_DEFAULT":
105
+ case "SORTBY_ASC":
106
+ if (c.order !== "ASC") return false;
107
+ break;
108
+ case "SORTBY_DESC":
109
+ if (c.order !== "DESC") return false;
110
+ break;
111
+ }
112
+ return true;
113
+ }));
114
+ }
115
+ /**
116
+ * Derive the list of indexes [tableA(X, Y, Z), tableB(H, I, J)]
117
+ **/
118
+ indexesToCreate(rootCandidates) {
119
+ const expressionCandidates = rootCandidates.filter((c) => c.jsonbExtraction);
120
+ const btreeCandidates = rootCandidates.filter((c) => !c.jsonbOperator && !c.jsonbExtraction);
121
+ const ginCandidates = rootCandidates.filter((c) => c.jsonbOperator);
122
+ const nextStage = [];
123
+ const permutedIndexes = this.groupPotentialIndexColumnsByTable(btreeCandidates);
124
+ for (const permutation of permutedIndexes.values()) {
125
+ const { table: rawTable, schema: rawSchema, columns } = permutation;
126
+ const permutations = require_permutations.permutationsWithDescendingLength(columns);
127
+ for (const columns of permutations) {
128
+ const schema = require_pg_identifier.PgIdentifier.fromString(rawSchema);
129
+ const table = require_pg_identifier.PgIdentifier.fromString(rawTable);
130
+ if (this.indexAlreadyExists(table.toString(), columns)) continue;
131
+ const indexName = this.indexName();
132
+ const definition = this.toDefinition({
133
+ table,
134
+ schema,
135
+ columns
136
+ }).raw;
137
+ nextStage.push({
138
+ name: indexName,
139
+ schema: schema.toString(),
140
+ table: table.toString(),
141
+ columns,
142
+ definition
143
+ });
144
+ }
145
+ }
146
+ const ginGroups = this.groupGinCandidatesByColumn(ginCandidates);
147
+ for (const group of ginGroups.values()) {
148
+ const { schema: rawSchema, table: rawTable, column, operators } = group;
149
+ const schema = require_pg_identifier.PgIdentifier.fromString(rawSchema);
150
+ const table = require_pg_identifier.PgIdentifier.fromString(rawTable);
151
+ const opclass = operators.some((op) => op !== "@>") ? void 0 : "jsonb_path_ops";
152
+ if (this.ginIndexAlreadyExists(table.toString(), column)) continue;
153
+ const indexName = this.indexName();
154
+ const candidate = {
155
+ schema: rawSchema,
156
+ table: rawTable,
157
+ column
158
+ };
159
+ const definition = this.toGinDefinition({
160
+ table,
161
+ schema,
162
+ column: require_pg_identifier.PgIdentifier.fromString(column),
163
+ opclass
164
+ });
165
+ nextStage.push({
166
+ name: indexName,
167
+ schema: schema.toString(),
168
+ table: table.toString(),
169
+ columns: [candidate],
170
+ definition,
171
+ indexMethod: "gin",
172
+ opclass
173
+ });
174
+ }
175
+ const seenExpressions = /* @__PURE__ */ new Set();
176
+ for (const candidate of expressionCandidates) {
177
+ const expression = candidate.jsonbExtraction;
178
+ const key = `${candidate.schema}.${candidate.table}.${expression}`;
179
+ if (seenExpressions.has(key)) continue;
180
+ seenExpressions.add(key);
181
+ const schema = require_pg_identifier.PgIdentifier.fromString(candidate.schema);
182
+ const table = require_pg_identifier.PgIdentifier.fromString(candidate.table);
183
+ const indexName = this.indexName();
184
+ const definition = this.toExpressionDefinition({
185
+ table,
186
+ schema,
187
+ expression
188
+ });
189
+ nextStage.push({
190
+ name: indexName,
191
+ schema: schema.toString(),
192
+ table: table.toString(),
193
+ columns: [candidate],
194
+ definition
195
+ });
196
+ }
197
+ return nextStage;
198
+ }
199
+ toDefinition({ schema, table, columns }) {
200
+ const make = (col, order, _where, _keyword) => {
201
+ let fullyQualifiedTable;
202
+ if (schema.toString() === "public") fullyQualifiedTable = table;
203
+ else fullyQualifiedTable = require_pg_identifier.PgIdentifier.fromParts(schema, table);
204
+ return `${fullyQualifiedTable}(${columns.map((c) => {
205
+ const column = require_pg_identifier.PgIdentifier.fromString(c.column);
206
+ const direction = c.sort && this.sortDirection(c.sort);
207
+ const nulls = c.sort && this.nullsOrder(c.sort);
208
+ let sort = col(column.toString());
209
+ if (direction) sort += ` ${order(direction)}`;
210
+ if (nulls) sort += ` ${order(nulls)}`;
211
+ return sort;
212
+ }).join(", ")})`;
213
+ };
214
+ const id = (a) => a;
215
+ return {
216
+ raw: make(id, id, id, id),
217
+ colored: make(colorette.green, colorette.yellow, colorette.magenta, colorette.blue)
218
+ };
219
+ }
220
+ toGinDefinition({ schema, table, column, opclass }) {
221
+ let fullyQualifiedTable;
222
+ if (schema.toString() === "public") fullyQualifiedTable = table;
223
+ else fullyQualifiedTable = require_pg_identifier.PgIdentifier.fromParts(schema, table);
224
+ const opclassSuffix = opclass ? ` ${opclass}` : "";
225
+ return `${fullyQualifiedTable} using gin (${column}${opclassSuffix})`;
226
+ }
227
+ toExpressionDefinition({ schema, table, expression }) {
228
+ let fullyQualifiedTable;
229
+ if (schema.toString() === "public") fullyQualifiedTable = table;
230
+ else fullyQualifiedTable = require_pg_identifier.PgIdentifier.fromParts(schema, table);
231
+ return `${fullyQualifiedTable}((${expression}))`;
232
+ }
233
+ groupGinCandidatesByColumn(candidates) {
234
+ const groups = /* @__PURE__ */ new Map();
235
+ for (const c of candidates) {
236
+ if (!c.jsonbOperator) continue;
237
+ const key = `${c.schema}.${c.table}.${c.column}`;
238
+ const existing = groups.get(key);
239
+ if (existing) {
240
+ if (!existing.operators.includes(c.jsonbOperator)) existing.operators.push(c.jsonbOperator);
241
+ } else groups.set(key, {
242
+ schema: c.schema,
243
+ table: c.table,
244
+ column: c.column,
245
+ operators: [c.jsonbOperator]
246
+ });
247
+ }
248
+ return groups;
249
+ }
250
+ ginIndexAlreadyExists(table, column) {
251
+ return this.existingIndexes.find((index) => index.index_type === "gin" && index.table_name === table && index.index_columns.some((c) => c.name === column));
252
+ }
253
+ /**
254
+ * Drop indexes that can be dropped. Ignore the ones that can't
255
+ */
256
+ async dropExistingIndexes(tx) {
257
+ for (const index of this.existingIndexes) {
258
+ if (!require_indexes.isIndexProbablyDroppable(index)) continue;
259
+ await require_database.dropIndex(tx, require_pg_identifier.PgIdentifier.fromParts(index.schema_name, index.index_name));
260
+ }
261
+ }
262
+ whereClause(c, col, keyword) {
263
+ if (!c.where) return "";
264
+ if (c.where.nulltest === "IS_NULL") return `${col(`"${c.column}"`)} is ${keyword("null")}`;
265
+ if (c.where.nulltest === "IS_NOT_NULL") return `${col(`"${c.column}"`)} is not ${keyword("null")}`;
266
+ return "";
267
+ }
268
+ nullsOrder(s) {
269
+ if (!s.nulls) return "";
270
+ switch (s.nulls) {
271
+ case "SORTBY_NULLS_FIRST": return "nulls first";
272
+ case "SORTBY_NULLS_LAST": return "nulls last";
273
+ default: return "";
274
+ }
275
+ }
276
+ sortDirection(s) {
277
+ if (!s.dir) return "";
278
+ switch (s.dir) {
279
+ case "SORTBY_DESC": return "desc";
280
+ case "SORTBY_ASC": return "asc";
281
+ default: return "";
282
+ }
283
+ }
284
+ async testQueryWithStats(builder, f, options) {
285
+ try {
286
+ await this.db.transaction(async (tx) => {
287
+ await f?.(tx);
288
+ await this.statistics.restoreStats(tx);
289
+ const flags = ["format json"];
290
+ if (options && !options.genericPlan) {
291
+ flags.push("analyze");
292
+ if (this.config.trace) flags.push("trace");
293
+ } else flags.push("generic_plan");
294
+ const { commands, query } = builder.introspect().explain(flags).buildParts();
295
+ await tx.exec(commands);
296
+ const explain = (await tx.exec(query, options?.params))[0]["QUERY PLAN"][0];
297
+ throw new RollbackError(explain);
298
+ });
299
+ } catch (error) {
300
+ if (error instanceof RollbackError) return error.value;
301
+ throw error;
302
+ }
303
+ throw new Error("Unreachable");
304
+ }
305
+ groupPotentialIndexColumnsByTable(indexes) {
306
+ const tableColumns = /* @__PURE__ */ new Map();
307
+ for (const index of indexes) {
308
+ const existing = tableColumns.get(`${index.schema}.${index.table}`);
309
+ if (existing) existing.columns.push(index);
310
+ else tableColumns.set(`${index.schema}.${index.table}`, {
311
+ table: index.table,
312
+ schema: index.schema,
313
+ columns: [index]
314
+ });
315
+ }
316
+ return tableColumns;
317
+ }
318
+ findUsedIndexes(explain) {
319
+ const newIndexes = /* @__PURE__ */ new Set();
320
+ const existingIndexes = /* @__PURE__ */ new Set();
321
+ const prefix = IndexOptimizer.prefix;
322
+ walkExplain(explain, (stage) => {
323
+ const indexName = stage["Index Name"];
324
+ if (indexName) if (indexName.startsWith(prefix)) newIndexes.add(indexName);
325
+ else if (indexName.includes(prefix)) {
326
+ const actualName = indexName.substring(indexName.indexOf(prefix));
327
+ newIndexes.add(actualName);
328
+ } else existingIndexes.add(indexName);
329
+ });
330
+ return {
331
+ newIndexes,
332
+ existingIndexes
333
+ };
334
+ }
335
+ replaceUsedIndexesWithDefinition(explain, triedIndexes) {
336
+ walkExplain(explain, (stage) => {
337
+ const indexName = stage["Index Name"];
338
+ if (typeof indexName === "string") {
339
+ const recommendation = triedIndexes.get(indexName);
340
+ if (recommendation) stage["Index Name"] = recommendation.definition;
341
+ }
342
+ });
343
+ }
344
+ };
345
+ require_defineProperty._defineProperty(IndexOptimizer, "prefix", "__qd_");
346
+ function walkExplain(explain, f) {
347
+ function go(plan) {
348
+ f(plan);
349
+ if (plan.Plans) for (const p of plan.Plans) go(p);
350
+ }
351
+ go(explain);
352
+ }
353
+ var RollbackError = class {
354
+ constructor(value) {
355
+ this.value = value;
356
+ }
357
+ };
358
+ const PROCEED = Symbol("PROCEED");
359
+ const SKIP = Symbol("SKIP");
360
+ //#endregion
361
+ exports.IndexOptimizer = IndexOptimizer;
362
+ exports.PROCEED = PROCEED;
363
+ exports.SKIP = SKIP;
364
+
365
+ //# sourceMappingURL=genalgo.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"genalgo.cjs","names":["PostgresQueryBuilder","PgIdentifier","permutationsWithDescendingLength","green","yellow","magenta","blue","isIndexProbablyDroppable","dropIndex"],"sources":["../../src/optimizer/genalgo.ts"],"sourcesContent":["import type { NullTestType } from \"@pgsql/types\";\nimport { blue, gray, green, magenta, red, yellow } from \"colorette\";\nimport type { JsonbOperator } from \"../sql/analyzer.js\";\nimport type { SortContext } from \"../sql/analyzer.js\";\nimport { PostgresQueryBuilder } from \"../sql/builder.js\";\nimport {\n dropIndex,\n type Postgres,\n type PostgresExplainResult,\n type PostgresExplainStage,\n type PostgresTransaction,\n} from \"../sql/database.js\";\nimport { isIndexProbablyDroppable } from \"../sql/indexes.js\";\nimport { permutationsWithDescendingLength } from \"../sql/permutations.js\";\nimport { PgIdentifier } from \"../sql/pg-identifier.js\";\nimport type { IndexedTable, Statistics } from \"./statistics.js\";\n\nexport type IndexIdentifier = string;\n\nexport type IndexRecommendation = PermutedIndexCandidate & {\n definition: IndexIdentifier;\n};\n\ntype Color = (a: string) => string;\n\ntype IndexToCreate = PermutedIndexCandidate & {\n name: PgIdentifier;\n definition: IndexIdentifier;\n};\n\nexport class IndexOptimizer {\n static prefix = \"__qd_\";\n\n constructor(\n private readonly db: Postgres,\n private readonly statistics: Statistics,\n private existingIndexes: IndexedTable[],\n private readonly config: {\n trace?: boolean;\n debug?: boolean;\n } = {},\n ) {}\n\n async run(\n builder: PostgresQueryBuilder,\n indexes: RootIndexCandidate[],\n beforeQuery?: (tx: PostgresTransaction) => Promise<void>,\n ): Promise<OptimizeResult> {\n const baseExplain = await this.testQueryWithStats(builder, async (tx) => {\n if (beforeQuery) {\n await beforeQuery(tx);\n }\n });\n const baseCost: number = Number(baseExplain.Plan[\"Total Cost\"]);\n if (baseCost === 0) {\n return {\n kind: \"zero_cost_plan\",\n explainPlan: baseExplain.Plan,\n };\n }\n const toCreate = this.indexesToCreate(indexes);\n if (toCreate.length === 0) {\n // No indexes to try: the 2nd EXPLAIN would be identical to the 1st,\n // so skip it. On large tables this saves a full re-run of the query.\n const baseIndexes = this.findUsedIndexes(baseExplain.Plan);\n return {\n kind: \"ok\",\n baseCost,\n finalCost: baseCost,\n newIndexes: new Set<string>(),\n existingIndexes: baseIndexes.existingIndexes,\n triedIndexes: new Map(),\n baseExplainPlan: baseExplain.Plan,\n explainPlan: baseExplain.Plan,\n };\n }\n const finalExplain = await this.testQueryWithStats(builder, async (tx) => {\n if (beforeQuery) {\n await beforeQuery(tx);\n }\n\n // Then create recommended indexes\n for (const permutation of toCreate) {\n const createIndex = PostgresQueryBuilder.createIndex(\n permutation.definition,\n permutation.name,\n )\n .introspect()\n .build();\n\n await tx.exec(createIndex);\n }\n });\n const finalCost = Number(finalExplain.Plan[\"Total Cost\"]);\n if (this.config.debug) {\n console.dir(finalExplain, { depth: null });\n }\n const deltaPercentage = ((baseCost - finalCost) / baseCost) * 100;\n if (finalCost < baseCost) {\n console.log(\n ` 🎉🎉🎉 ${green(`+${deltaPercentage.toFixed(2).padStart(5, \"0\")}%`)}`,\n );\n } else if (finalCost > baseCost) {\n console.log(\n `${red(\n `-${Math.abs(deltaPercentage).toFixed(2).padStart(5, \"0\")}%`,\n )} ${gray(\"If there's a better index, we haven't tried it\")}`,\n );\n }\n const baseIndexes = this.findUsedIndexes(baseExplain.Plan);\n const finalIndexes = this.findUsedIndexes(finalExplain.Plan);\n const triedIndexes = new Map(\n toCreate.map((index) => [index.name.toString(), index]),\n );\n this.replaceUsedIndexesWithDefinition(finalExplain.Plan, triedIndexes);\n\n return {\n kind: \"ok\",\n baseCost,\n finalCost,\n newIndexes: finalIndexes.newIndexes,\n existingIndexes: baseIndexes.existingIndexes,\n triedIndexes,\n baseExplainPlan: baseExplain.Plan,\n explainPlan: finalExplain.Plan,\n };\n }\n\n async runWithoutIndexes(builder: PostgresQueryBuilder) {\n return await this.testQueryWithStats(builder, async (tx) => {\n await this.dropExistingIndexes(tx);\n });\n }\n\n /**\n * Given the current indexes in the optimizer, transform them in some\n * way to change which indexes will be assumed to exist when optimizing\n *\n * @example\n * ```\n * // resets indexes\n * optimizer.transformIndexes(() => [])\n *\n * // adds new index\n * optimizer.transformIndexes(indexes => [...indexes, newIndex])\n * ```\n */\n transformIndexes(f: (indexes: IndexedTable[]) => IndexedTable[]) {\n const newIndexes = f(this.existingIndexes);\n this.existingIndexes = newIndexes;\n return this;\n }\n\n /**\n * Postgres has a limit of 63 characters for index names.\n * So we use this to make sure we don't derive it from a list of columns that can\n * overflow that limit.\n */\n private indexName(): PgIdentifier {\n const indexName =\n IndexOptimizer.prefix + Math.random().toString(36).substring(2, 16);\n return PgIdentifier.fromString(indexName);\n }\n\n // TODO: this doesn't belong in the optimizer\n private indexAlreadyExists(\n table: string,\n columns: RootIndexCandidate[],\n ): IndexedTable | undefined {\n return this.existingIndexes.find(\n (index) =>\n index.index_type === \"btree\" &&\n index.table_name === table &&\n index.index_columns.length === columns.length &&\n index.index_columns.every((c, i) => {\n if (columns[i].column !== c.name) {\n return false;\n }\n\n // we should assume any index with `WHERE`\n // can't be counted as a duplicate\n if (columns[i].where) {\n return false;\n }\n\n if (columns[i].sort) {\n switch (columns[i].sort.dir) {\n // Sorting is ASC by default in postgres\n case \"SORTBY_DEFAULT\":\n case \"SORTBY_ASC\":\n if (c.order !== \"ASC\") {\n return false;\n }\n break;\n case \"SORTBY_DESC\":\n if (c.order !== \"DESC\") {\n return false;\n }\n break;\n }\n }\n return true;\n }),\n );\n }\n\n /**\n * Derive the list of indexes [tableA(X, Y, Z), tableB(H, I, J)]\n **/\n private indexesToCreate(\n rootCandidates: RootIndexCandidate[],\n ): IndexToCreate[] {\n const expressionCandidates = rootCandidates.filter(\n (c) => c.jsonbExtraction,\n );\n const btreeCandidates = rootCandidates.filter(\n (c) => !c.jsonbOperator && !c.jsonbExtraction,\n );\n const ginCandidates = rootCandidates.filter((c) => c.jsonbOperator);\n\n const nextStage: IndexToCreate[] = [];\n\n const permutedIndexes =\n this.groupPotentialIndexColumnsByTable(btreeCandidates);\n for (const permutation of permutedIndexes.values()) {\n const { table: rawTable, schema: rawSchema, columns } = permutation;\n const permutations = permutationsWithDescendingLength(columns);\n for (const columns of permutations) {\n // TODO: accept PgIdentifier values instead\n // required refactoring `PermutedIndexCandidate`\n const schema = PgIdentifier.fromString(rawSchema);\n const table = PgIdentifier.fromString(rawTable);\n const existingIndex = this.indexAlreadyExists(\n table.toString(),\n columns,\n );\n if (existingIndex) {\n continue;\n }\n const indexName = this.indexName();\n\n const definition = this.toDefinition({ table, schema, columns }).raw;\n\n nextStage.push({\n name: indexName,\n schema: schema.toString(),\n table: table.toString(),\n columns,\n definition,\n });\n }\n }\n\n const ginGroups = this.groupGinCandidatesByColumn(ginCandidates);\n for (const group of ginGroups.values()) {\n const { schema: rawSchema, table: rawTable, column, operators } = group;\n const schema = PgIdentifier.fromString(rawSchema);\n const table = PgIdentifier.fromString(rawTable);\n\n // jsonb_path_ops is smaller/faster but only supports @>.\n // All other operators (key-existence and jsonpath) need the full jsonb_ops.\n const needsFullOps = operators.some((op) => op !== \"@>\");\n const opclass = needsFullOps ? undefined : \"jsonb_path_ops\";\n\n const existingGin = this.ginIndexAlreadyExists(table.toString(), column);\n if (existingGin) {\n continue;\n }\n\n const indexName = this.indexName();\n const candidate: RootIndexCandidate = {\n schema: rawSchema,\n table: rawTable,\n column,\n };\n const definition = this.toGinDefinition({\n table,\n schema,\n column: PgIdentifier.fromString(column),\n opclass,\n });\n\n nextStage.push({\n name: indexName,\n schema: schema.toString(),\n table: table.toString(),\n columns: [candidate],\n definition,\n indexMethod: \"gin\",\n opclass,\n });\n }\n\n // Expression B-tree indexes for JSONB path extraction (-> / ->>)\n const seenExpressions = new Set<string>();\n for (const candidate of expressionCandidates) {\n const expression = candidate.jsonbExtraction!;\n const key = `${candidate.schema}.${candidate.table}.${expression}`;\n if (seenExpressions.has(key)) continue;\n seenExpressions.add(key);\n\n const schema = PgIdentifier.fromString(candidate.schema);\n const table = PgIdentifier.fromString(candidate.table);\n const indexName = this.indexName();\n const definition = this.toExpressionDefinition({\n table,\n schema,\n expression,\n });\n\n nextStage.push({\n name: indexName,\n schema: schema.toString(),\n table: table.toString(),\n columns: [candidate],\n definition,\n });\n }\n\n return nextStage;\n }\n\n private toDefinition({\n schema,\n table,\n columns,\n }: {\n schema: PgIdentifier;\n table: PgIdentifier;\n columns: RootIndexCandidate[];\n }) {\n const make = (col: Color, order: Color, _where: Color, _keyword: Color) => {\n // TODO: refactor all of this class to accept PgIdentifiers\n let fullyQualifiedTable: PgIdentifier;\n\n if (schema.toString() === \"public\") {\n fullyQualifiedTable = table;\n } else {\n fullyQualifiedTable = PgIdentifier.fromParts(schema, table);\n }\n const baseColumn = `${fullyQualifiedTable}(${columns\n .map((c) => {\n const column = PgIdentifier.fromString(c.column);\n const direction = c.sort && this.sortDirection(c.sort);\n const nulls = c.sort && this.nullsOrder(c.sort);\n let sort = col(column.toString());\n if (direction) {\n sort += ` ${order(direction)}`;\n }\n if (nulls) {\n sort += ` ${order(nulls)}`;\n }\n return sort;\n })\n .join(\", \")})`;\n // TODO: add support for generating partial indexes\n // if (clauses.length > 0) {\n // return `${baseColumn} ${where(\"where\")} ${clauses.join(\" and \")}`;\n // }\n return baseColumn;\n };\n const id: Color = (a) => a;\n const raw = make(id, id, id, id);\n const colored = make(green, yellow, magenta, blue);\n return { raw, colored };\n }\n\n private toGinDefinition({\n schema,\n table,\n column,\n opclass,\n }: {\n schema: PgIdentifier;\n table: PgIdentifier;\n column: PgIdentifier;\n opclass?: string;\n }): string {\n let fullyQualifiedTable: PgIdentifier;\n if (schema.toString() === \"public\") {\n fullyQualifiedTable = table;\n } else {\n fullyQualifiedTable = PgIdentifier.fromParts(schema, table);\n }\n const opclassSuffix = opclass ? ` ${opclass}` : \"\";\n return `${fullyQualifiedTable} using gin (${column}${opclassSuffix})`;\n }\n\n private toExpressionDefinition({\n schema,\n table,\n expression,\n }: {\n schema: PgIdentifier;\n table: PgIdentifier;\n expression: string;\n }): string {\n let fullyQualifiedTable: PgIdentifier;\n if (schema.toString() === \"public\") {\n fullyQualifiedTable = table;\n } else {\n fullyQualifiedTable = PgIdentifier.fromParts(schema, table);\n }\n return `${fullyQualifiedTable}((${expression}))`;\n }\n\n private groupGinCandidatesByColumn(candidates: RootIndexCandidate[]) {\n const groups = new Map<\n string,\n {\n schema: string;\n table: string;\n column: string;\n operators: JsonbOperator[];\n }\n >();\n for (const c of candidates) {\n if (!c.jsonbOperator) continue;\n const key = `${c.schema}.${c.table}.${c.column}`;\n const existing = groups.get(key);\n if (existing) {\n if (!existing.operators.includes(c.jsonbOperator)) {\n existing.operators.push(c.jsonbOperator);\n }\n } else {\n groups.set(key, {\n schema: c.schema,\n table: c.table,\n column: c.column,\n operators: [c.jsonbOperator],\n });\n }\n }\n return groups;\n }\n\n private ginIndexAlreadyExists(\n table: string,\n column: string,\n ): IndexedTable | undefined {\n return this.existingIndexes.find(\n (index) =>\n index.index_type === \"gin\" &&\n index.table_name === table &&\n index.index_columns.some((c) => c.name === column),\n );\n }\n\n /**\n * Drop indexes that can be dropped. Ignore the ones that can't\n */\n private async dropExistingIndexes(tx: PostgresTransaction) {\n for (const index of this.existingIndexes) {\n if (!isIndexProbablyDroppable(index)) {\n continue;\n }\n const indexName = PgIdentifier.fromParts(\n index.schema_name,\n index.index_name,\n );\n await dropIndex(tx, indexName);\n }\n }\n\n private whereClause(c: RootIndexCandidate, col: Color, keyword: Color) {\n if (!c.where) {\n return \"\";\n }\n if (c.where.nulltest === \"IS_NULL\") {\n return `${col(`\"${c.column}\"`)} is ${keyword(\"null\")}`;\n }\n if (c.where.nulltest === \"IS_NOT_NULL\") {\n return `${col(`\"${c.column}\"`)} is not ${keyword(\"null\")}`;\n }\n return \"\";\n }\n\n private nullsOrder(s: SortContext) {\n if (!s.nulls) {\n return \"\";\n }\n switch (s.nulls) {\n case \"SORTBY_NULLS_FIRST\":\n return \"nulls first\";\n case \"SORTBY_NULLS_LAST\":\n return \"nulls last\";\n case \"SORTBY_NULLS_DEFAULT\":\n default:\n return \"\";\n }\n }\n\n private sortDirection(s: SortContext) {\n if (!s.dir) {\n return \"\";\n }\n switch (s.dir) {\n case \"SORTBY_DESC\":\n return \"desc\";\n case \"SORTBY_ASC\":\n return \"asc\";\n case \"SORTBY_DEFAULT\":\n // god help us if we ever run into this\n case \"SORTBY_USING\":\n default:\n return \"\";\n }\n }\n\n async testQueryWithStats(\n builder: PostgresQueryBuilder,\n f?: (tx: PostgresTransaction) => Promise<void>,\n options?: { params?: unknown[]; genericPlan?: boolean },\n ): Promise<{ Plan: PostgresExplainStage }> {\n try {\n await this.db.transaction(async (tx) => {\n await f?.(tx);\n await this.statistics.restoreStats(tx);\n const flags = [\"format json\"];\n if (options && !options.genericPlan) {\n flags.push(\"analyze\");\n if (this.config.trace) {\n // trace can only be used alongside analyze\n // since it depends on the results of the query execution\n flags.push(\"trace\");\n }\n } else {\n flags.push(\"generic_plan\");\n }\n const { commands, query } = builder\n .introspect()\n .explain(flags)\n .buildParts();\n // this is done in a separate step to prevent sending multiple commands when using parameters\n await tx.exec(commands);\n const result = await tx.exec<PostgresExplainResult>(\n query,\n options?.params,\n );\n const explain = result[0][\"QUERY PLAN\"][0];\n throw new RollbackError(explain);\n });\n } catch (error) {\n if (error instanceof RollbackError) {\n return error.value;\n }\n throw error;\n }\n throw new Error(\"Unreachable\");\n }\n\n private groupPotentialIndexColumnsByTable(indexes: RootIndexCandidate[]) {\n const tableColumns: Map<\n string,\n { schema: string; table: string; columns: RootIndexCandidate[] }\n > = new Map();\n for (const index of indexes) {\n const existing = tableColumns.get(`${index.schema}.${index.table}`);\n if (existing) {\n existing.columns.push(index);\n } else {\n tableColumns.set(`${index.schema}.${index.table}`, {\n table: index.table,\n schema: index.schema,\n columns: [index],\n });\n }\n }\n return tableColumns;\n }\n\n private findUsedIndexes(explain: Record<string, any>) {\n const newIndexes: Set<string> = new Set();\n const existingIndexes: Set<string> = new Set();\n const prefix = IndexOptimizer.prefix;\n walkExplain(explain, (stage) => {\n const indexName = stage[\"Index Name\"];\n if (indexName) {\n // Check for prefix at start or embedded (for hypertable chunk indexes like _hyper_1_1_chunk___qd_xxx)\n if (indexName.startsWith(prefix)) {\n newIndexes.add(indexName);\n } else if (indexName.includes(prefix)) {\n // Extract the actual index name from chunk-prefixed names (e.g., _hyper_1_1_chunk___qd_xxx -> __qd_xxx)\n const actualName = indexName.substring(indexName.indexOf(prefix));\n newIndexes.add(actualName);\n } else {\n existingIndexes.add(indexName);\n }\n }\n });\n return {\n newIndexes,\n existingIndexes,\n };\n }\n\n private replaceUsedIndexesWithDefinition(\n explain: Record<string, any>,\n triedIndexes: Map<string, IndexRecommendation>,\n ) {\n walkExplain(explain, (stage) => {\n const indexName = stage[\"Index Name\"];\n if (typeof indexName === \"string\") {\n const recommendation = triedIndexes.get(indexName);\n if (recommendation) {\n stage[\"Index Name\"] = recommendation.definition;\n }\n }\n });\n }\n}\n\nfunction walkExplain(explain: Record<string, any>, f: (stage: any) => void) {\n function go(plan: any) {\n f(plan);\n if (plan.Plans) {\n for (const p of plan.Plans) {\n go(p);\n }\n }\n }\n go(explain);\n}\n\nexport type OptimizeResult =\n | {\n kind: \"ok\";\n baseExplainPlan: PostgresExplainStage;\n baseCost: number;\n finalCost: number;\n newIndexes: Set<string>;\n existingIndexes: Set<string>;\n triedIndexes: Map<string, IndexRecommendation>;\n explainPlan: PostgresExplainStage;\n }\n | {\n kind: \"zero_cost_plan\";\n explainPlan: PostgresExplainStage;\n };\n\nclass RollbackError<T> {\n constructor(public readonly value?: T) {}\n}\n\nexport type RootIndexCandidate = {\n schema: string;\n table: string;\n column: string;\n sort?: SortContext;\n where?: { nulltest?: NullTestType };\n jsonbOperator?: JsonbOperator;\n jsonbExtraction?: string;\n};\n\nexport type PermutedIndexCandidate = {\n schema: string;\n table: string;\n columns: RootIndexCandidate[];\n // TODO: functional indexes\n where?: string;\n indexMethod?: \"btree\" | \"gin\";\n opclass?: string;\n};\n\nexport const PROCEED = Symbol(\"PROCEED\");\nexport const SKIP = Symbol(\"SKIP\");\n"],"mappings":"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"}
@@ -0,0 +1,111 @@
1
+ 'use client';
2
+
3
+ import { PostgresQueryBuilder } from "../sql/builder.cjs";
4
+ import { Postgres, PostgresExplainStage, PostgresTransaction } from "../sql/database.cjs";
5
+ import { IndexedTable, Statistics } from "./statistics.cjs";
6
+ import { JsonbOperator, SortContext } from "../sql/analyzer.cjs";
7
+ import { NullTestType } from "@pgsql/types";
8
+
9
+ //#region src/optimizer/genalgo.d.ts
10
+ type IndexIdentifier = string;
11
+ type IndexRecommendation = PermutedIndexCandidate & {
12
+ definition: IndexIdentifier;
13
+ };
14
+ declare class IndexOptimizer {
15
+ private readonly db;
16
+ private readonly statistics;
17
+ private existingIndexes;
18
+ private readonly config;
19
+ static prefix: string;
20
+ constructor(db: Postgres, statistics: Statistics, existingIndexes: IndexedTable[], config?: {
21
+ trace?: boolean;
22
+ debug?: boolean;
23
+ });
24
+ run(builder: PostgresQueryBuilder, indexes: RootIndexCandidate[], beforeQuery?: (tx: PostgresTransaction) => Promise<void>): Promise<OptimizeResult>;
25
+ runWithoutIndexes(builder: PostgresQueryBuilder): Promise<{
26
+ Plan: PostgresExplainStage;
27
+ }>;
28
+ /**
29
+ * Given the current indexes in the optimizer, transform them in some
30
+ * way to change which indexes will be assumed to exist when optimizing
31
+ *
32
+ * @example
33
+ * ```
34
+ * // resets indexes
35
+ * optimizer.transformIndexes(() => [])
36
+ *
37
+ * // adds new index
38
+ * optimizer.transformIndexes(indexes => [...indexes, newIndex])
39
+ * ```
40
+ */
41
+ transformIndexes(f: (indexes: IndexedTable[]) => IndexedTable[]): this;
42
+ /**
43
+ * Postgres has a limit of 63 characters for index names.
44
+ * So we use this to make sure we don't derive it from a list of columns that can
45
+ * overflow that limit.
46
+ */
47
+ private indexName;
48
+ private indexAlreadyExists;
49
+ /**
50
+ * Derive the list of indexes [tableA(X, Y, Z), tableB(H, I, J)]
51
+ **/
52
+ private indexesToCreate;
53
+ private toDefinition;
54
+ private toGinDefinition;
55
+ private toExpressionDefinition;
56
+ private groupGinCandidatesByColumn;
57
+ private ginIndexAlreadyExists;
58
+ /**
59
+ * Drop indexes that can be dropped. Ignore the ones that can't
60
+ */
61
+ private dropExistingIndexes;
62
+ private whereClause;
63
+ private nullsOrder;
64
+ private sortDirection;
65
+ testQueryWithStats(builder: PostgresQueryBuilder, f?: (tx: PostgresTransaction) => Promise<void>, options?: {
66
+ params?: unknown[];
67
+ genericPlan?: boolean;
68
+ }): Promise<{
69
+ Plan: PostgresExplainStage;
70
+ }>;
71
+ private groupPotentialIndexColumnsByTable;
72
+ private findUsedIndexes;
73
+ private replaceUsedIndexesWithDefinition;
74
+ }
75
+ type OptimizeResult = {
76
+ kind: "ok";
77
+ baseExplainPlan: PostgresExplainStage;
78
+ baseCost: number;
79
+ finalCost: number;
80
+ newIndexes: Set<string>;
81
+ existingIndexes: Set<string>;
82
+ triedIndexes: Map<string, IndexRecommendation>;
83
+ explainPlan: PostgresExplainStage;
84
+ } | {
85
+ kind: "zero_cost_plan";
86
+ explainPlan: PostgresExplainStage;
87
+ };
88
+ type RootIndexCandidate = {
89
+ schema: string;
90
+ table: string;
91
+ column: string;
92
+ sort?: SortContext;
93
+ where?: {
94
+ nulltest?: NullTestType;
95
+ };
96
+ jsonbOperator?: JsonbOperator;
97
+ jsonbExtraction?: string;
98
+ };
99
+ type PermutedIndexCandidate = {
100
+ schema: string;
101
+ table: string;
102
+ columns: RootIndexCandidate[];
103
+ where?: string;
104
+ indexMethod?: "btree" | "gin";
105
+ opclass?: string;
106
+ };
107
+ declare const PROCEED: unique symbol;
108
+ declare const SKIP: unique symbol;
109
+ //#endregion
110
+ export { IndexIdentifier, IndexOptimizer, IndexRecommendation, OptimizeResult, PROCEED, PermutedIndexCandidate, RootIndexCandidate, SKIP };
111
+ //# sourceMappingURL=genalgo.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"genalgo.d.cts","names":[],"sources":["../../src/optimizer/genalgo.ts"],"mappings":";;;;;;;;;KAiBY,eAAA;AAAA,KAEA,mBAAA,GAAsB,sBAAA;EAChC,UAAA,EAAY,eAAA;AAAA;AAAA,cAUD,cAAA;EAAA,iBAIQ,EAAA;EAAA,iBACA,UAAA;EAAA,QACT,eAAA;EAAA,iBACS,MAAA;EAAA,OANZ,MAAA;cAGY,EAAA,EAAI,QAAA,EACJ,UAAA,EAAY,UAAA,EACrB,eAAA,EAAiB,YAAA,IACR,MAAA;IACf,KAAA;IACA,KAAA;EAAA;EAIE,GAAA,CACJ,OAAA,EAAS,oBAAA,EACT,OAAA,EAAS,kBAAA,IACT,WAAA,IAAe,EAAA,EAAI,mBAAA,KAAwB,OAAA,SAC1C,OAAA,CAAQ,cAAA;EAiFL,iBAAA,CAAkB,OAAA,EAAS,oBAAA,GAAoB,OAAA;UAiYlC,oBAAA;EAAA;;;;;;;;;;;;;;EA9WnB,gBAAA,CAAiB,CAAA,GAAI,OAAA,EAAS,YAAA,OAAmB,YAAA;EAAA;;;;;EAAA,QAWzC,SAAA;EAAA,QAOA,kBAAA;EA4VE;;;EAAA,QAhTF,eAAA;EAAA,QAiHA,YAAA;EAAA,QA6CA,eAAA;EAAA,QAqBA,sBAAA;EAAA,QAkBA,0BAAA;EAAA,QA8BA,qBAAA;EAjZuB;;;EAAA,QAgajB,mBAAA;EAAA,QAaN,WAAA;EAAA,QAaA,UAAA;EAAA,QAeA,aAAA;EAiBF,kBAAA,CACJ,OAAA,EAAS,oBAAA,EACT,CAAA,IAAK,EAAA,EAAI,mBAAA,KAAwB,OAAA,QACjC,OAAA;IAAY,MAAA;IAAoB,WAAA;EAAA,IAC/B,OAAA;IAAU,IAAA,EAAM,oBAAA;EAAA;EAAA,QAsCX,iCAAA;EAAA,QAoBA,eAAA;EAAA,QAyBA,gCAAA;AAAA;AAAA,KA4BE,cAAA;EAEN,IAAA;EACA,eAAA,EAAiB,oBAAA;EACjB,QAAA;EACA,SAAA;EACA,UAAA,EAAY,GAAA;EACZ,eAAA,EAAiB,GAAA;EACjB,YAAA,EAAc,GAAA,SAAY,mBAAA;EAC1B,WAAA,EAAa,oBAAA;AAAA;EAGb,IAAA;EACA,WAAA,EAAa,oBAAA;AAAA;AAAA,KAOP,kBAAA;EACV,MAAA;EACA,KAAA;EACA,MAAA;EACA,IAAA,GAAO,WAAA;EACP,KAAA;IAAU,QAAA,GAAW,YAAA;EAAA;EACrB,aAAA,GAAgB,aAAA;EAChB,eAAA;AAAA;AAAA,KAGU,sBAAA;EACV,MAAA;EACA,KAAA;EACA,OAAA,EAAS,kBAAA;EAET,KAAA;EACA,WAAA;EACA,OAAA;AAAA;AAAA,cAGW,OAAA;AAAA,cACA,IAAA"}
@@ -0,0 +1,111 @@
1
+ 'use client';
2
+
3
+ import { PostgresQueryBuilder } from "../sql/builder.mjs";
4
+ import { Postgres, PostgresExplainStage, PostgresTransaction } from "../sql/database.mjs";
5
+ import { IndexedTable, Statistics } from "./statistics.mjs";
6
+ import { JsonbOperator, SortContext } from "../sql/analyzer.mjs";
7
+ import { NullTestType } from "@pgsql/types";
8
+
9
+ //#region src/optimizer/genalgo.d.ts
10
+ type IndexIdentifier = string;
11
+ type IndexRecommendation = PermutedIndexCandidate & {
12
+ definition: IndexIdentifier;
13
+ };
14
+ declare class IndexOptimizer {
15
+ private readonly db;
16
+ private readonly statistics;
17
+ private existingIndexes;
18
+ private readonly config;
19
+ static prefix: string;
20
+ constructor(db: Postgres, statistics: Statistics, existingIndexes: IndexedTable[], config?: {
21
+ trace?: boolean;
22
+ debug?: boolean;
23
+ });
24
+ run(builder: PostgresQueryBuilder, indexes: RootIndexCandidate[], beforeQuery?: (tx: PostgresTransaction) => Promise<void>): Promise<OptimizeResult>;
25
+ runWithoutIndexes(builder: PostgresQueryBuilder): Promise<{
26
+ Plan: PostgresExplainStage;
27
+ }>;
28
+ /**
29
+ * Given the current indexes in the optimizer, transform them in some
30
+ * way to change which indexes will be assumed to exist when optimizing
31
+ *
32
+ * @example
33
+ * ```
34
+ * // resets indexes
35
+ * optimizer.transformIndexes(() => [])
36
+ *
37
+ * // adds new index
38
+ * optimizer.transformIndexes(indexes => [...indexes, newIndex])
39
+ * ```
40
+ */
41
+ transformIndexes(f: (indexes: IndexedTable[]) => IndexedTable[]): this;
42
+ /**
43
+ * Postgres has a limit of 63 characters for index names.
44
+ * So we use this to make sure we don't derive it from a list of columns that can
45
+ * overflow that limit.
46
+ */
47
+ private indexName;
48
+ private indexAlreadyExists;
49
+ /**
50
+ * Derive the list of indexes [tableA(X, Y, Z), tableB(H, I, J)]
51
+ **/
52
+ private indexesToCreate;
53
+ private toDefinition;
54
+ private toGinDefinition;
55
+ private toExpressionDefinition;
56
+ private groupGinCandidatesByColumn;
57
+ private ginIndexAlreadyExists;
58
+ /**
59
+ * Drop indexes that can be dropped. Ignore the ones that can't
60
+ */
61
+ private dropExistingIndexes;
62
+ private whereClause;
63
+ private nullsOrder;
64
+ private sortDirection;
65
+ testQueryWithStats(builder: PostgresQueryBuilder, f?: (tx: PostgresTransaction) => Promise<void>, options?: {
66
+ params?: unknown[];
67
+ genericPlan?: boolean;
68
+ }): Promise<{
69
+ Plan: PostgresExplainStage;
70
+ }>;
71
+ private groupPotentialIndexColumnsByTable;
72
+ private findUsedIndexes;
73
+ private replaceUsedIndexesWithDefinition;
74
+ }
75
+ type OptimizeResult = {
76
+ kind: "ok";
77
+ baseExplainPlan: PostgresExplainStage;
78
+ baseCost: number;
79
+ finalCost: number;
80
+ newIndexes: Set<string>;
81
+ existingIndexes: Set<string>;
82
+ triedIndexes: Map<string, IndexRecommendation>;
83
+ explainPlan: PostgresExplainStage;
84
+ } | {
85
+ kind: "zero_cost_plan";
86
+ explainPlan: PostgresExplainStage;
87
+ };
88
+ type RootIndexCandidate = {
89
+ schema: string;
90
+ table: string;
91
+ column: string;
92
+ sort?: SortContext;
93
+ where?: {
94
+ nulltest?: NullTestType;
95
+ };
96
+ jsonbOperator?: JsonbOperator;
97
+ jsonbExtraction?: string;
98
+ };
99
+ type PermutedIndexCandidate = {
100
+ schema: string;
101
+ table: string;
102
+ columns: RootIndexCandidate[];
103
+ where?: string;
104
+ indexMethod?: "btree" | "gin";
105
+ opclass?: string;
106
+ };
107
+ declare const PROCEED: unique symbol;
108
+ declare const SKIP: unique symbol;
109
+ //#endregion
110
+ export { IndexIdentifier, IndexOptimizer, IndexRecommendation, OptimizeResult, PROCEED, PermutedIndexCandidate, RootIndexCandidate, SKIP };
111
+ //# sourceMappingURL=genalgo.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"genalgo.d.mts","names":[],"sources":["../../src/optimizer/genalgo.ts"],"mappings":";;;;;;;;;KAiBY,eAAA;AAAA,KAEA,mBAAA,GAAsB,sBAAA;EAChC,UAAA,EAAY,eAAA;AAAA;AAAA,cAUD,cAAA;EAAA,iBAIQ,EAAA;EAAA,iBACA,UAAA;EAAA,QACT,eAAA;EAAA,iBACS,MAAA;EAAA,OANZ,MAAA;cAGY,EAAA,EAAI,QAAA,EACJ,UAAA,EAAY,UAAA,EACrB,eAAA,EAAiB,YAAA,IACR,MAAA;IACf,KAAA;IACA,KAAA;EAAA;EAIE,GAAA,CACJ,OAAA,EAAS,oBAAA,EACT,OAAA,EAAS,kBAAA,IACT,WAAA,IAAe,EAAA,EAAI,mBAAA,KAAwB,OAAA,SAC1C,OAAA,CAAQ,cAAA;EAiFL,iBAAA,CAAkB,OAAA,EAAS,oBAAA,GAAoB,OAAA;UAiYlC,oBAAA;EAAA;;;;;;;;;;;;;;EA9WnB,gBAAA,CAAiB,CAAA,GAAI,OAAA,EAAS,YAAA,OAAmB,YAAA;EAAA;;;;;EAAA,QAWzC,SAAA;EAAA,QAOA,kBAAA;EA4VE;;;EAAA,QAhTF,eAAA;EAAA,QAiHA,YAAA;EAAA,QA6CA,eAAA;EAAA,QAqBA,sBAAA;EAAA,QAkBA,0BAAA;EAAA,QA8BA,qBAAA;EAjZuB;;;EAAA,QAgajB,mBAAA;EAAA,QAaN,WAAA;EAAA,QAaA,UAAA;EAAA,QAeA,aAAA;EAiBF,kBAAA,CACJ,OAAA,EAAS,oBAAA,EACT,CAAA,IAAK,EAAA,EAAI,mBAAA,KAAwB,OAAA,QACjC,OAAA;IAAY,MAAA;IAAoB,WAAA;EAAA,IAC/B,OAAA;IAAU,IAAA,EAAM,oBAAA;EAAA;EAAA,QAsCX,iCAAA;EAAA,QAoBA,eAAA;EAAA,QAyBA,gCAAA;AAAA;AAAA,KA4BE,cAAA;EAEN,IAAA;EACA,eAAA,EAAiB,oBAAA;EACjB,QAAA;EACA,SAAA;EACA,UAAA,EAAY,GAAA;EACZ,eAAA,EAAiB,GAAA;EACjB,YAAA,EAAc,GAAA,SAAY,mBAAA;EAC1B,WAAA,EAAa,oBAAA;AAAA;EAGb,IAAA;EACA,WAAA,EAAa,oBAAA;AAAA;AAAA,KAOP,kBAAA;EACV,MAAA;EACA,KAAA;EACA,MAAA;EACA,IAAA,GAAO,WAAA;EACP,KAAA;IAAU,QAAA,GAAW,YAAA;EAAA;EACrB,aAAA,GAAgB,aAAA;EAChB,eAAA;AAAA;AAAA,KAGU,sBAAA;EACV,MAAA;EACA,KAAA;EACA,OAAA,EAAS,kBAAA;EAET,KAAA;EACA,WAAA;EACA,OAAA;AAAA;AAAA,cAGW,OAAA;AAAA,cACA,IAAA"}