@query-doctor/core 0.1.0 → 0.1.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.
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Rewriter for pg_stat_statements queries.
3
+ * Not all queries found in pg_stat_statements can be
4
+ * directly sent back to the database without first being rewritten.
5
+ */
6
+ export declare class PssRewriter {
7
+ rewrite(query: string): string;
8
+ private rewriteKeywordWithParameter;
9
+ private problematicKeywords;
10
+ }
11
+ //# sourceMappingURL=pss-rewriter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pss-rewriter.d.ts","sourceRoot":"","sources":["../../src/optimizer/pss-rewriter.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAI9B,OAAO,CAAC,2BAA2B;IAanC,OAAO,CAAC,mBAAmB,CAAyC;CACrE"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=pss-rewriter.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pss-rewriter.test.d.ts","sourceRoot":"","sources":["../../src/optimizer/pss-rewriter.test.ts"],"names":[],"mappings":""}
@@ -58,10 +58,14 @@ export type DiscoveredColumnReference = {
58
58
  };
59
59
  /** A function defined by @pgsql/parser */
60
60
  export type Parser = (query: string) => Promise<unknown>;
61
+ export type TableReference = {
62
+ schema?: string;
63
+ table: string;
64
+ };
61
65
  export type AnalysisResult = {
62
66
  indexesToCheck: DiscoveredColumnReference[];
63
67
  ansiHighlightedQuery: string;
64
- referencedTables: string[];
68
+ referencedTables: TableReference[];
65
69
  shadowedAliases: ColumnReferencePart[];
66
70
  tags: SQLCommenterTag[];
67
71
  queryWithoutTags: string;
@@ -1 +1 @@
1
- {"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../../src/sql/analyzer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EAEZ,SAAS,EACT,WAAW,EACZ,MAAM,cAAc,CAAC;AAQtB,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAClE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,mBAAmB,EAAyB,MAAM,aAAa,CAAC;AAEzE,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;CAC7D;AAED,eAAO,MAAM,iBAAiB,qBAAqB,CAAC;AAEpD,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,GAAG,EAAE,SAAS,CAAC;IACf,KAAK,EAAE,WAAW,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACtC,2DAA2D;IAC3D,SAAS,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,cAAc,EAAE,MAAM,CAAC;IACvB;;;;;;;;;;;;;OAaG;IACH,KAAK,EAAE,mBAAmB,EAAE,CAAC;IAC7B;;OAEG;IACH,OAAO,EAAE,OAAO,CAAC;IACjB,yDAAyD;IACzD,QAAQ,EAAE;QACR,KAAK,EAAE,MAAM,CAAC;QACd,GAAG,EAAE,MAAM,CAAC;KACb,CAAC;IACF;;;OAGG;IACH,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,KAAK,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,YAAY,CAAA;KAAE,CAAC;CACrC,CAAC;AAEF,0CAA0C;AAC1C,MAAM,MAAM,MAAM,GAAG,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;AAEzD,MAAM,MAAM,cAAc,GAAG;IAC3B,cAAc,EAAE,yBAAyB,EAAE,CAAC;IAC5C,oBAAoB,EAAE,MAAM,CAAC;IAC7B,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,eAAe,EAAE,mBAAmB,EAAE,CAAC;IACvC,IAAI,EAAE,eAAe,EAAE,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;IACzB,yBAAyB,CAAC,EAAE,MAAM,CAAC;IACnC,MAAM,EAAE,KAAK,EAAE,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,IAAI,EAAE,eAAe,EAAE,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF;;;;;GAKG;AACH,qBAAa,QAAQ;IACP,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,MAAM;IACrC,OAAO,CACX,KAAK,EAAE,MAAM,EACb,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,cAAc,CAAC;IA6F1B,aAAa,CACX,MAAM,EAAE,aAAa,EAAE,EACvB,UAAU,EAAE,yBAAyB,EAAE,GACtC,kBAAkB,EAAE;IA+GvB,OAAO,CAAC,gBAAgB;IAgBxB;;;;;OAKG;IACH,OAAO,CAAC,mBAAmB;IAkB3B,OAAO,CAAC,SAAS;IAOjB,OAAO,CAAC,mBAAmB;CAuC5B"}
1
+ {"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../../src/sql/analyzer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EAEZ,SAAS,EACT,WAAW,EACZ,MAAM,cAAc,CAAC;AAQtB,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAClE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,mBAAmB,EAAyB,MAAM,aAAa,CAAC;AAEzE,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;CAC7D;AAED,eAAO,MAAM,iBAAiB,qBAAqB,CAAC;AAEpD,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,GAAG,EAAE,SAAS,CAAC;IACf,KAAK,EAAE,WAAW,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACtC,2DAA2D;IAC3D,SAAS,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,cAAc,EAAE,MAAM,CAAC;IACvB;;;;;;;;;;;;;OAaG;IACH,KAAK,EAAE,mBAAmB,EAAE,CAAC;IAC7B;;OAEG;IACH,OAAO,EAAE,OAAO,CAAC;IACjB,yDAAyD;IACzD,QAAQ,EAAE;QACR,KAAK,EAAE,MAAM,CAAC;QACd,GAAG,EAAE,MAAM,CAAC;KACb,CAAC;IACF;;;OAGG;IACH,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,KAAK,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,YAAY,CAAA;KAAE,CAAC;CACrC,CAAC;AAEF,0CAA0C;AAC1C,MAAM,MAAM,MAAM,GAAG,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;AAEzD,MAAM,MAAM,cAAc,GAAG;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,cAAc,EAAE,yBAAyB,EAAE,CAAC;IAC5C,oBAAoB,EAAE,MAAM,CAAC;IAC7B,gBAAgB,EAAE,cAAc,EAAE,CAAC;IACnC,eAAe,EAAE,mBAAmB,EAAE,CAAC;IACvC,IAAI,EAAE,eAAe,EAAE,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;IACzB,yBAAyB,CAAC,EAAE,MAAM,CAAC;IACnC,MAAM,EAAE,KAAK,EAAE,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,IAAI,EAAE,eAAe,EAAE,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF;;;;;GAKG;AACH,qBAAa,QAAQ;IACP,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,MAAM;IACrC,OAAO,CACX,KAAK,EAAE,MAAM,EACb,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,cAAc,CAAC;IAgG1B,aAAa,CACX,MAAM,EAAE,aAAa,EAAE,EACvB,UAAU,EAAE,yBAAyB,EAAE,GACtC,kBAAkB,EAAE;IAqHvB,OAAO,CAAC,gBAAgB;IAgBxB;;;;;OAKG;IACH,OAAO,CAAC,mBAAmB;IAkB3B,OAAO,CAAC,SAAS;IAOjB,OAAO,CAAC,mBAAmB;CAuC5B"}
@@ -33,6 +33,7 @@ export declare class Walker {
33
33
  static traverse(node: unknown, stack: (KeysOfUnion<Node> | string)[], callback: (node: Node, stack: (KeysOfUnion<Node> | string)[]) => void): void;
34
34
  }
35
35
  export type ColumnReferencePart = {
36
+ schema?: string;
36
37
  /** the text of the column reference (excluding any potential quotes) */
37
38
  text: string;
38
39
  start?: number;
@@ -1 +1 @@
1
- {"version":3,"file":"walker.d.ts","sourceRoot":"","sources":["../../src/sql/walker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAgB,MAAM,cAAc,CAAC;AAEvD,OAAO,KAAK,EAAE,yBAAyB,EAAe,MAAM,eAAe,CAAC;AAE5E,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAEzC,wDAAwD;AACxD,MAAM,MAAM,aAAa,GAAG,GAAG,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;AAG7D;;;;GAIG;AACH,qBAAa,MAAM;IAaL,OAAO,CAAC,QAAQ,CAAC,KAAK;IAZlC,OAAO,CAAC,aAAa,CAA4B;IACjD,OAAO,CAAC,UAAU,CAAqB;IACvC,OAAO,CAAC,UAAU,CAAmC;IACrD,OAAO,CAAC,oBAAoB,CAAqB;IACjD,OAAO,CAAC,cAAc,CAAmC;IACzD,OAAO,CAAC,kBAAkB,CAAqB;IAG/C,OAAO,CAAC,cAAc,CAA6B;IACnD,OAAO,CAAC,eAAe,CAA6B;IACpD,OAAO,CAAC,MAAM,CAAe;gBAEA,KAAK,EAAE,MAAM;IAE1C,IAAI,CAAC,IAAI,EAAE,IAAI;;;;;;;;;IAoLf,OAAO,CAAC,GAAG;IA6EX,MAAM,CAAC,QAAQ,CACb,IAAI,EAAE,OAAO,EACb,KAAK,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,EAAE,EACrC,QAAQ,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,EAAE,KAAK,IAAI;CAwBxE;AAED,MAAM,MAAM,mBAAmB,GAAG;IAChC,wEAAwE;IACxE,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,KAAK,WAAW,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,MAAM,CAAC,GAAG,KAAK,CAAC"}
1
+ {"version":3,"file":"walker.d.ts","sourceRoot":"","sources":["../../src/sql/walker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAgB,MAAM,cAAc,CAAC;AAEvD,OAAO,KAAK,EAAE,yBAAyB,EAAe,MAAM,eAAe,CAAC;AAE5E,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAEzC,wDAAwD;AACxD,MAAM,MAAM,aAAa,GAAG,GAAG,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;AAG7D;;;;GAIG;AACH,qBAAa,MAAM;IAaL,OAAO,CAAC,QAAQ,CAAC,KAAK;IAZlC,OAAO,CAAC,aAAa,CAA4B;IACjD,OAAO,CAAC,UAAU,CAAqB;IACvC,OAAO,CAAC,UAAU,CAAmC;IACrD,OAAO,CAAC,oBAAoB,CAAqB;IACjD,OAAO,CAAC,cAAc,CAAmC;IACzD,OAAO,CAAC,kBAAkB,CAAqB;IAG/C,OAAO,CAAC,cAAc,CAA6B;IACnD,OAAO,CAAC,eAAe,CAA6B;IACpD,OAAO,CAAC,MAAM,CAAe;gBAEA,KAAK,EAAE,MAAM;IAE1C,IAAI,CAAC,IAAI,EAAE,IAAI;;;;;;;;;IA2Lf,OAAO,CAAC,GAAG;IA6EX,MAAM,CAAC,QAAQ,CACb,IAAI,EAAE,OAAO,EACb,KAAK,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,EAAE,EACrC,QAAQ,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,EAAE,KAAK,IAAI;CAwBxE;AAED,MAAM,MAAM,mBAAmB,GAAG;IAChC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wEAAwE;IACxE,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,KAAK,WAAW,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,MAAM,CAAC,GAAG,KAAK,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@query-doctor/core",
3
3
  "private": false,
4
- "version": "0.1.0",
4
+ "version": "0.1.2",
5
5
  "description": "Core logic for Query Doctor",
6
6
  "license": "",
7
7
  "author": "Query Doctor",
@@ -1,304 +0,0 @@
1
- import { blue, gray, green, magenta, red, yellow } from "colorette";
2
- import { PostgresQueryBuilder } from "../sql/builder.js";
3
- import { dropIndex, } from "../sql/database.js";
4
- import { isIndexProbablyDroppable } from "../sql/indexes.js";
5
- export class IndexOptimizer {
6
- db;
7
- statistics;
8
- existingIndexes;
9
- config;
10
- static prefix = "__qd_";
11
- constructor(db, statistics, existingIndexes, config = {}) {
12
- this.db = db;
13
- this.statistics = statistics;
14
- this.existingIndexes = existingIndexes;
15
- this.config = config;
16
- }
17
- async run(builder, indexes) {
18
- const baseExplain = await this.testQueryWithStats(builder);
19
- const baseCost = Number(baseExplain.Plan["Total Cost"]);
20
- if (baseCost === 0) {
21
- return {
22
- kind: "zero_cost_plan",
23
- explainPlan: baseExplain,
24
- };
25
- }
26
- const toCreate = this.indexesToCreate(indexes);
27
- const finalExplain = await this.testQueryWithStats(builder, async (sql) => {
28
- for (const permutation of toCreate) {
29
- const createIndex = PostgresQueryBuilder.createIndex(this.toDefinition(permutation).raw, permutation.name)
30
- .introspect()
31
- .build();
32
- await sql.exec(createIndex);
33
- }
34
- });
35
- const finalCost = Number(finalExplain.Plan["Total Cost"]);
36
- if (this.config.debug) {
37
- console.dir(finalExplain, { depth: null });
38
- }
39
- const deltaPercentage = ((baseCost - finalCost) / baseCost) * 100;
40
- if (finalCost < baseCost) {
41
- console.log(` 🎉🎉🎉 ${green(`+${deltaPercentage.toFixed(2).padStart(5, "0")}%`)}`);
42
- }
43
- else if (finalCost > baseCost) {
44
- console.log(`${red(`-${Math.abs(deltaPercentage).toFixed(2).padStart(5, "0")}%`)} ${gray("If there's a better index, we haven't tried it")}`);
45
- }
46
- const { newIndexes, existingIndexes: existingIndexesUsedByQuery } = this.findUsedIndexes(finalExplain.Plan);
47
- return {
48
- kind: "ok",
49
- baseCost,
50
- finalCost,
51
- newIndexes,
52
- existingIndexes: existingIndexesUsedByQuery,
53
- triedIndexes: new Map(toCreate.map((index) => [index.name, index])),
54
- baseExplainPlan: baseExplain,
55
- explainPlan: finalExplain,
56
- };
57
- }
58
- async runWithoutIndexes(builder) {
59
- return await this.testQueryWithStats(builder, async (tx) => {
60
- await this.dropExistingIndexes(tx);
61
- });
62
- }
63
- /**
64
- * Postgres has a limit of 63 characters for index names.
65
- * So we use this to make sure we don't derive it from a list of columns that can
66
- * overflow that limit.
67
- */
68
- indexName() {
69
- return IndexOptimizer.prefix + Math.random().toString(36).substring(2, 16);
70
- }
71
- // TODO: this doesn't belong in the optimizer
72
- indexAlreadyExists(table, columns) {
73
- return this.existingIndexes.find((index) => index.index_type === "btree" &&
74
- index.table_name === table &&
75
- index.index_columns.length === columns.length &&
76
- index.index_columns.every((c, i) => columns[i].column === c.name));
77
- }
78
- /**
79
- * Derive the list of indexes [tableA(X, Y, Z), tableB(H, I, J)]
80
- **/
81
- indexesToCreate(rootCandidates) {
82
- const permutedIndexes = this.tableColumnIndexCandidates(rootCandidates);
83
- const nextStage = [];
84
- for (const { table, schema, columns } of permutedIndexes.values()) {
85
- const permutations = permuteWithFeedback(columns);
86
- let iter = permutations.next(PROCEED);
87
- while (!iter.done) {
88
- const columns = iter.value;
89
- const existingIndex = this.indexAlreadyExists(table, columns);
90
- if (existingIndex) {
91
- iter = permutations.next(PROCEED);
92
- continue;
93
- }
94
- const indexName = this.indexName();
95
- const shortenedSchema = schema === "public" ? "" : `"${schema}".`;
96
- // TODO: this is silly, turn this into a data structure here ONLY
97
- const indexDefinitionClean = `${shortenedSchema}"${table}"(${columns
98
- .map((c) => `"${c.column}"`)
99
- .join(", ")})`;
100
- iter = permutations.next(PROCEED);
101
- nextStage.push({
102
- name: indexName,
103
- schema,
104
- table,
105
- columns,
106
- definition: indexDefinitionClean,
107
- });
108
- }
109
- }
110
- return nextStage;
111
- }
112
- toDefinition(permuted) {
113
- const make = (col, order, where, keyword) => {
114
- // let clauses: string[] = [];
115
- // const columns = [...permuted.columns];
116
- // // TODO
117
- // for (let i = columns.length - 1; i >= 0; i--) {
118
- // const c = columns[i];
119
- // const clause = this.whereClause(c, col, where);
120
- // if (clause) {
121
- // clauses.push(clause);
122
- // // TODO: make this
123
- // if (columns.length > 1) {
124
- // columns.splice(i, 1);
125
- // }
126
- // }
127
- // }
128
- const baseColumn = `"${permuted.schema}"."${permuted.table}"(${permuted.columns
129
- .map((c) => {
130
- const direction = c.sort && this.sortDirection(c.sort);
131
- const nulls = c.sort && this.nullsOrder(c.sort);
132
- let sort = col(`"${c.column}"`);
133
- if (direction) {
134
- sort += ` ${order(direction)}`;
135
- }
136
- if (nulls) {
137
- sort += ` ${order(nulls)}`;
138
- }
139
- return sort;
140
- })
141
- .join(", ")})`;
142
- // TODO: add support for generating partial indexes
143
- // if (clauses.length > 0) {
144
- // return `${baseColumn} ${where("where")} ${clauses.join(" and ")}`;
145
- // }
146
- return baseColumn;
147
- };
148
- const id = (a) => a;
149
- const raw = make(id, id, id, id);
150
- const colored = make(green, yellow, magenta, blue);
151
- return { raw, colored };
152
- }
153
- /**
154
- * Drop indexes that can be dropped. Ignore the ones that can't
155
- */
156
- async dropExistingIndexes(tx) {
157
- for (const index of this.existingIndexes) {
158
- if (!isIndexProbablyDroppable(index)) {
159
- continue;
160
- }
161
- const indexName = `${index.schema_name}.${index.index_name}`;
162
- await dropIndex(tx, indexName);
163
- }
164
- }
165
- whereClause(c, col, keyword) {
166
- if (!c.where) {
167
- return "";
168
- }
169
- if (c.where.nulltest === "IS_NULL") {
170
- return `${col(`"${c.column}"`)} is ${keyword("null")}`;
171
- }
172
- if (c.where.nulltest === "IS_NOT_NULL") {
173
- return `${col(`"${c.column}"`)} is not ${keyword("null")}`;
174
- }
175
- return "";
176
- }
177
- nullsOrder(s) {
178
- if (!s.nulls) {
179
- return "";
180
- }
181
- switch (s.nulls) {
182
- case "SORTBY_NULLS_FIRST":
183
- return "nulls first";
184
- case "SORTBY_NULLS_LAST":
185
- return "nulls last";
186
- case "SORTBY_NULLS_DEFAULT":
187
- default:
188
- return "";
189
- }
190
- }
191
- sortDirection(s) {
192
- if (!s.dir) {
193
- return "";
194
- }
195
- switch (s.dir) {
196
- case "SORTBY_DESC":
197
- return "desc";
198
- case "SORTBY_ASC":
199
- return "asc";
200
- case "SORTBY_DEFAULT":
201
- // god help us if we ever run into this
202
- case "SORTBY_USING":
203
- default:
204
- return "";
205
- }
206
- }
207
- async testQueryWithStats(builder, f, options) {
208
- try {
209
- await this.db.transaction(async (tx) => {
210
- await f?.(tx);
211
- await this.statistics.restoreStats(tx);
212
- const flags = ["format json", "trace"];
213
- if (options && !options.genericPlan) {
214
- flags.push("analyze");
215
- }
216
- else {
217
- flags.push("generic_plan");
218
- }
219
- const { commands, query } = builder.explain(flags).buildParts();
220
- // this is done in a separate step to prevent sending multiple commands when using parameters
221
- await tx.exec(commands);
222
- const result = await tx.exec(query, options?.params);
223
- const explain = result[0]["QUERY PLAN"][0];
224
- throw new RollbackError(explain);
225
- });
226
- }
227
- catch (error) {
228
- if (error instanceof RollbackError) {
229
- return error.value;
230
- }
231
- throw error;
232
- }
233
- throw new Error("Unreachable");
234
- }
235
- tableColumnIndexCandidates(indexes) {
236
- const tableColumns = new Map();
237
- for (const index of indexes) {
238
- const existing = tableColumns.get(`${index.schema}.${index.table}`);
239
- if (existing) {
240
- existing.columns.push(index);
241
- }
242
- else {
243
- tableColumns.set(`${index.schema}.${index.table}`, {
244
- table: index.table,
245
- schema: index.schema,
246
- columns: [index],
247
- });
248
- }
249
- }
250
- return tableColumns;
251
- }
252
- findUsedIndexes(explain) {
253
- const newIndexes = new Set();
254
- const existingIndexes = new Set();
255
- function go(plan) {
256
- const indexName = plan["Index Name"];
257
- if (indexName) {
258
- if (indexName.startsWith(IndexOptimizer.prefix)) {
259
- newIndexes.add(indexName);
260
- }
261
- else {
262
- existingIndexes.add(indexName);
263
- }
264
- }
265
- if (plan.Plans) {
266
- for (const p of plan.Plans) {
267
- go(p);
268
- }
269
- }
270
- }
271
- go(explain);
272
- return {
273
- newIndexes,
274
- existingIndexes,
275
- };
276
- }
277
- }
278
- class RollbackError {
279
- value;
280
- constructor(value) {
281
- this.value = value;
282
- }
283
- }
284
- export const PROCEED = Symbol("PROCEED");
285
- export const SKIP = Symbol("SKIP");
286
- /**
287
- * Allows permuting over an array of items.
288
- * The generator allows the caller to prematurely stop the permutation chain.
289
- */
290
- export function* permuteWithFeedback(arr) {
291
- function* helper(path, rest) {
292
- let i = 0;
293
- while (i < rest.length) {
294
- const nextPath = [...path, rest[i]];
295
- const nextRest = [...rest.slice(0, i), ...rest.slice(i + 1)];
296
- const input = yield nextPath;
297
- if (input === PROCEED) {
298
- yield* helper(nextPath, nextRest);
299
- }
300
- i++;
301
- }
302
- }
303
- yield* helper([], arr);
304
- }