@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.
- package/dist/_virtual/_@oxc-project_runtime@0.122.0/helpers/defineProperty.cjs +13 -0
- package/dist/_virtual/_@oxc-project_runtime@0.122.0/helpers/defineProperty.mjs +13 -0
- package/dist/_virtual/_@oxc-project_runtime@0.122.0/helpers/toPrimitive.cjs +15 -0
- package/dist/_virtual/_@oxc-project_runtime@0.122.0/helpers/toPrimitive.mjs +15 -0
- package/dist/_virtual/_@oxc-project_runtime@0.122.0/helpers/toPropertyKey.cjs +10 -0
- package/dist/_virtual/_@oxc-project_runtime@0.122.0/helpers/toPropertyKey.mjs +10 -0
- package/dist/_virtual/_@oxc-project_runtime@0.122.0/helpers/typeof.cjs +17 -0
- package/dist/_virtual/_@oxc-project_runtime@0.122.0/helpers/typeof.mjs +12 -0
- package/dist/_virtual/_rolldown/runtime.cjs +24 -0
- package/dist/index.cjs +33 -2568
- package/dist/index.d.cts +11 -781
- package/dist/index.d.mts +11 -781
- package/dist/index.mjs +10 -2522
- package/dist/optimizer/genalgo.cjs +365 -0
- package/dist/optimizer/genalgo.cjs.map +1 -0
- package/dist/optimizer/genalgo.d.cts +111 -0
- package/dist/optimizer/genalgo.d.cts.map +1 -0
- package/dist/optimizer/genalgo.d.mts +111 -0
- package/dist/optimizer/genalgo.d.mts.map +1 -0
- package/dist/optimizer/genalgo.mjs +362 -0
- package/dist/optimizer/genalgo.mjs.map +1 -0
- package/dist/optimizer/pss-rewriter.cjs +31 -0
- package/dist/optimizer/pss-rewriter.cjs.map +1 -0
- package/dist/optimizer/pss-rewriter.d.cts +16 -0
- package/dist/optimizer/pss-rewriter.d.cts.map +1 -0
- package/dist/optimizer/pss-rewriter.d.mts +16 -0
- package/dist/optimizer/pss-rewriter.d.mts.map +1 -0
- package/dist/optimizer/pss-rewriter.mjs +31 -0
- package/dist/optimizer/pss-rewriter.mjs.map +1 -0
- package/dist/optimizer/statistics.cjs +738 -0
- package/dist/optimizer/statistics.cjs.map +1 -0
- package/dist/optimizer/statistics.d.cts +389 -0
- package/dist/optimizer/statistics.d.cts.map +1 -0
- package/dist/optimizer/statistics.d.mts +389 -0
- package/dist/optimizer/statistics.d.mts.map +1 -0
- package/dist/optimizer/statistics.mjs +729 -0
- package/dist/optimizer/statistics.mjs.map +1 -0
- package/dist/sentry.cjs +13 -0
- package/dist/sentry.cjs.map +1 -0
- package/dist/sentry.d.cts +7 -0
- package/dist/sentry.d.cts.map +1 -0
- package/dist/sentry.d.mts +7 -0
- package/dist/sentry.d.mts.map +1 -0
- package/dist/sentry.mjs +13 -0
- package/dist/sentry.mjs.map +1 -0
- package/dist/sql/analyzer.cjs +242 -0
- package/dist/sql/analyzer.cjs.map +1 -0
- package/dist/sql/analyzer.d.cts +112 -0
- package/dist/sql/analyzer.d.cts.map +1 -0
- package/dist/sql/analyzer.d.mts +112 -0
- package/dist/sql/analyzer.d.mts.map +1 -0
- package/dist/sql/analyzer.mjs +240 -0
- package/dist/sql/analyzer.mjs.map +1 -0
- package/dist/sql/ast-utils.cjs +19 -0
- package/dist/sql/ast-utils.cjs.map +1 -0
- package/dist/sql/ast-utils.d.cts +9 -0
- package/dist/sql/ast-utils.d.cts.map +1 -0
- package/dist/sql/ast-utils.d.mts +9 -0
- package/dist/sql/ast-utils.d.mts.map +1 -0
- package/dist/sql/ast-utils.mjs +17 -0
- package/dist/sql/ast-utils.mjs.map +1 -0
- package/dist/sql/builder.cjs +94 -0
- package/dist/sql/builder.cjs.map +1 -0
- package/dist/sql/builder.d.cts +37 -0
- package/dist/sql/builder.d.cts.map +1 -0
- package/dist/sql/builder.d.mts +37 -0
- package/dist/sql/builder.d.mts.map +1 -0
- package/dist/sql/builder.mjs +94 -0
- package/dist/sql/builder.mjs.map +1 -0
- package/dist/sql/database.cjs +35 -0
- package/dist/sql/database.cjs.map +1 -0
- package/dist/sql/database.d.cts +91 -0
- package/dist/sql/database.d.cts.map +1 -0
- package/dist/sql/database.d.mts +91 -0
- package/dist/sql/database.d.mts.map +1 -0
- package/dist/sql/database.mjs +32 -0
- package/dist/sql/database.mjs.map +1 -0
- package/dist/sql/indexes.cjs +17 -0
- package/dist/sql/indexes.cjs.map +1 -0
- package/dist/sql/indexes.d.cts +14 -0
- package/dist/sql/indexes.d.cts.map +1 -0
- package/dist/sql/indexes.d.mts +14 -0
- package/dist/sql/indexes.d.mts.map +1 -0
- package/dist/sql/indexes.mjs +16 -0
- package/dist/sql/indexes.mjs.map +1 -0
- package/dist/sql/nudges.cjs +484 -0
- package/dist/sql/nudges.cjs.map +1 -0
- package/dist/sql/nudges.d.cts +21 -0
- package/dist/sql/nudges.d.cts.map +1 -0
- package/dist/sql/nudges.d.mts +21 -0
- package/dist/sql/nudges.d.mts.map +1 -0
- package/dist/sql/nudges.mjs +484 -0
- package/dist/sql/nudges.mjs.map +1 -0
- package/dist/sql/permutations.cjs +28 -0
- package/dist/sql/permutations.cjs.map +1 -0
- package/dist/sql/permutations.mjs +28 -0
- package/dist/sql/permutations.mjs.map +1 -0
- package/dist/sql/pg-identifier.cjs +216 -0
- package/dist/sql/pg-identifier.cjs.map +1 -0
- package/dist/sql/pg-identifier.d.cts +31 -0
- package/dist/sql/pg-identifier.d.cts.map +1 -0
- package/dist/sql/pg-identifier.d.mts +31 -0
- package/dist/sql/pg-identifier.d.mts.map +1 -0
- package/dist/sql/pg-identifier.mjs +216 -0
- package/dist/sql/pg-identifier.mjs.map +1 -0
- package/dist/sql/walker.cjs +314 -0
- package/dist/sql/walker.cjs.map +1 -0
- package/dist/sql/walker.d.cts +15 -0
- package/dist/sql/walker.d.cts.map +1 -0
- package/dist/sql/walker.d.mts +15 -0
- package/dist/sql/walker.d.mts.map +1 -0
- package/dist/sql/walker.mjs +313 -0
- package/dist/sql/walker.mjs.map +1 -0
- package/package.json +2 -1
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts.map +0 -1
- package/dist/index.d.mts.map +0 -1
- package/dist/index.mjs.map +0 -1
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { ExportedStats } from "../optimizer/statistics.mjs";
|
|
4
|
+
import { RootIndexCandidate } from "../optimizer/genalgo.mjs";
|
|
5
|
+
import { Nudge } from "./nudges.mjs";
|
|
6
|
+
import { ColumnReferencePart } from "./walker.mjs";
|
|
7
|
+
import { NullTestType, SortByDir, SortByNulls } from "@pgsql/types";
|
|
8
|
+
|
|
9
|
+
//#region src/sql/analyzer.d.ts
|
|
10
|
+
interface DatabaseDriver {
|
|
11
|
+
query(query: string, params: unknown[]): Promise<unknown[]>;
|
|
12
|
+
}
|
|
13
|
+
declare const ignoredIdentifier = "__qd_placeholder";
|
|
14
|
+
interface SQLCommenterTag {
|
|
15
|
+
key: string;
|
|
16
|
+
value: string;
|
|
17
|
+
}
|
|
18
|
+
type SortContext = {
|
|
19
|
+
dir: SortByDir;
|
|
20
|
+
nulls: SortByNulls;
|
|
21
|
+
};
|
|
22
|
+
type DiscoveredColumnReference = {
|
|
23
|
+
/** How often the column reference appears in the query. */frequency: number;
|
|
24
|
+
/**
|
|
25
|
+
* Representation of the column reference exactly
|
|
26
|
+
* as it appears in the query.
|
|
27
|
+
*/
|
|
28
|
+
representation: string;
|
|
29
|
+
/**
|
|
30
|
+
* Parts of the column reference separated by dots in the query.
|
|
31
|
+
* The table reference (if it exists) is resolved if the query
|
|
32
|
+
* uses an alias.
|
|
33
|
+
*
|
|
34
|
+
* Has 3 different potential configurations (in theory)
|
|
35
|
+
* `a.b.c` - a column reference with a table and a schema reference
|
|
36
|
+
* `a.b` - a column reference with a table reference but no schema
|
|
37
|
+
* `a` - a column reference with no table reference.
|
|
38
|
+
*
|
|
39
|
+
* We use a simple array here to allow parsing of any syntactically correct
|
|
40
|
+
* but logically incorrect query. The checks happen later when we're deriving
|
|
41
|
+
* potential indexes from parts of a column reference in `deriveIndexes`
|
|
42
|
+
*/
|
|
43
|
+
parts: ColumnReferencePart[];
|
|
44
|
+
/**
|
|
45
|
+
* Whether the column reference is invalid. This
|
|
46
|
+
*/
|
|
47
|
+
ignored: boolean; /** The position of the column reference in the query. */
|
|
48
|
+
position: {
|
|
49
|
+
start: number;
|
|
50
|
+
end: number;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* A sort direction associated by the column reference.
|
|
54
|
+
* Only relevant to references from sorts
|
|
55
|
+
*/
|
|
56
|
+
sort?: SortContext;
|
|
57
|
+
where?: {
|
|
58
|
+
nulltest?: NullTestType;
|
|
59
|
+
};
|
|
60
|
+
jsonbOperator?: JsonbOperator;
|
|
61
|
+
jsonbExtraction?: string;
|
|
62
|
+
};
|
|
63
|
+
type JsonbOperator = "@>" | "?" | "?|" | "?&" | "@@" | "@?";
|
|
64
|
+
type StatementType = "select" | "insert" | "update" | "delete" | "other";
|
|
65
|
+
/** A function defined by @pgsql/parser */
|
|
66
|
+
type Parser = (query: string) => Promise<unknown>;
|
|
67
|
+
type TableReference = {
|
|
68
|
+
schema?: string;
|
|
69
|
+
table: string;
|
|
70
|
+
};
|
|
71
|
+
type AnalysisResult = {
|
|
72
|
+
statementType: StatementType;
|
|
73
|
+
indexesToCheck: DiscoveredColumnReference[];
|
|
74
|
+
ansiHighlightedQuery: string;
|
|
75
|
+
referencedTables: TableReference[];
|
|
76
|
+
shadowedAliases: ColumnReferencePart[];
|
|
77
|
+
tags: SQLCommenterTag[];
|
|
78
|
+
queryWithoutTags: string;
|
|
79
|
+
formattedQueryWithoutTags?: string;
|
|
80
|
+
nudges: Nudge[];
|
|
81
|
+
};
|
|
82
|
+
type SQLCommenterExtraction = {
|
|
83
|
+
tags: SQLCommenterTag[];
|
|
84
|
+
queryWithoutTags: string;
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* Analyzes a query and returns a list of column references that
|
|
88
|
+
* should be indexed.
|
|
89
|
+
*
|
|
90
|
+
* This should be instantiated once per analyzed query.
|
|
91
|
+
*/
|
|
92
|
+
declare class Analyzer {
|
|
93
|
+
private readonly parser;
|
|
94
|
+
constructor(parser: Parser);
|
|
95
|
+
analyze(query: string, formattedQuery?: string): Promise<AnalysisResult>;
|
|
96
|
+
deriveIndexes(tables: ExportedStats[], discovered: DiscoveredColumnReference[], referencedTables: TableReference[]): RootIndexCandidate[];
|
|
97
|
+
private filterReferences;
|
|
98
|
+
private hasColumn;
|
|
99
|
+
private colorizeKeywords;
|
|
100
|
+
/**
|
|
101
|
+
* Resolves aliases such as `a.b` to `x.b` if `a` is a known
|
|
102
|
+
* alias to a table called x.
|
|
103
|
+
*
|
|
104
|
+
* Ignores all other combination of parts such as `a.b.c`
|
|
105
|
+
*/
|
|
106
|
+
private resolveTableAliases;
|
|
107
|
+
private normalize;
|
|
108
|
+
private extractSqlcommenter;
|
|
109
|
+
}
|
|
110
|
+
//#endregion
|
|
111
|
+
export { AnalysisResult, Analyzer, DatabaseDriver, DiscoveredColumnReference, JsonbOperator, Parser, SQLCommenterExtraction, SQLCommenterTag, SortContext, StatementType, TableReference, ignoredIdentifier };
|
|
112
|
+
//# sourceMappingURL=analyzer.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyzer.d.mts","names":[],"sources":["../../src/sql/analyzer.ts"],"mappings":";;;;;;;;;UAmBiB,cAAA;EACf,KAAA,CAAM,KAAA,UAAe,MAAA,cAAoB,OAAA;AAAA;AAAA,cAG9B,iBAAA;AAAA,UAEI,eAAA;EACf,GAAA;EACA,KAAA;AAAA;AAAA,KAGU,WAAA;EACV,GAAA,EAAK,SAAA;EACL,KAAA,EAAO,WAAA;AAAA;AAAA,KAGG,yBAAA;EAZC,2DAcX,SAAA;;;;AAZF;EAiBE,cAAA;;;;AAZF;;;;;;;;;;AAKA;EAsBE,KAAA,EAAO,mBAAA;;;;EAIP,OAAA,WAYgB;EAVhB,QAAA;IACE,KAAA;IACA,GAAA;EAAA;EARF;;;;EAcA,IAAA,GAAO,WAAA;EACP,KAAA;IAAU,QAAA,GAAW,YAAA;EAAA;EACrB,aAAA,GAAgB,aAAA;EAChB,eAAA;AAAA;AAAA,KAGU,aAAA;AAAA,KAEA,aAAA;;KAUA,MAAA,IAAU,KAAA,aAAkB,OAAA;AAAA,KAE5B,cAAA;EACV,MAAA;EACA,KAAA;AAAA;AAAA,KAGU,cAAA;EACV,aAAA,EAAe,aAAA;EACf,cAAA,EAAgB,yBAAA;EAChB,oBAAA;EACA,gBAAA,EAAkB,cAAA;EAClB,eAAA,EAAiB,mBAAA;EACjB,IAAA,EAAM,eAAA;EACN,gBAAA;EACA,yBAAA;EACA,MAAA,EAAQ,KAAA;AAAA;AAAA,KAGE,sBAAA;EACV,IAAA,EAAM,eAAA;EACN,gBAAA;AAAA;;;AAdF;;;;cAuBa,QAAA;EAAA,iBACkB,MAAA;cAAA,MAAA,EAAQ,MAAA;EAC/B,OAAA,CACJ,KAAA,UACA,cAAA,YACC,OAAA,CAAQ,cAAA;EAuGX,aAAA,CACE,MAAA,EAAQ,aAAA,IACR,UAAA,EAAY,yBAAA,IACZ,gBAAA,EAAkB,cAAA,KACjB,kBAAA;EAAA,QAuHK,gBAAA;EAAA,QAmBA,SAAA;EAAA,QAMA,gBAAA;EAtRO;;;;;;EAAA,QA4SP,mBAAA;EAAA,QAkBA,SAAA;EAAA,QAOA,mBAAA;AAAA"}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { getNodeKind } from "./ast-utils.mjs";
|
|
3
|
+
import { Walker } from "./walker.mjs";
|
|
4
|
+
import { bgMagentaBright, blue, dim, strikethrough } from "colorette";
|
|
5
|
+
//#region src/sql/analyzer.ts
|
|
6
|
+
const ignoredIdentifier = "__qd_placeholder";
|
|
7
|
+
const STATEMENT_TYPE_MAP = {
|
|
8
|
+
SelectStmt: "select",
|
|
9
|
+
InsertStmt: "insert",
|
|
10
|
+
UpdateStmt: "update",
|
|
11
|
+
DeleteStmt: "delete"
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Analyzes a query and returns a list of column references that
|
|
15
|
+
* should be indexed.
|
|
16
|
+
*
|
|
17
|
+
* This should be instantiated once per analyzed query.
|
|
18
|
+
*/
|
|
19
|
+
var Analyzer = class {
|
|
20
|
+
constructor(parser) {
|
|
21
|
+
this.parser = parser;
|
|
22
|
+
}
|
|
23
|
+
async analyze(query, formattedQuery) {
|
|
24
|
+
const ast = await this.parser(query);
|
|
25
|
+
if (!ast.stmts) throw new Error("Query did not have any statements. This should probably never happen?");
|
|
26
|
+
const stmt = ast.stmts[0].stmt;
|
|
27
|
+
if (!stmt) throw new Error("Query did not have any statements. This should probably never happen?");
|
|
28
|
+
const statementType = STATEMENT_TYPE_MAP[getNodeKind(stmt)] ?? "other";
|
|
29
|
+
const { highlights, indexRepresentations, indexesToCheck, shadowedAliases, tempTables, tableMappings, nudges } = new Walker(query).walk(stmt);
|
|
30
|
+
const sortedHighlights = highlights.sort((a, b) => b.position.end - a.position.end);
|
|
31
|
+
let currQuery = query;
|
|
32
|
+
for (const highlight of sortedHighlights) {
|
|
33
|
+
const parts = this.resolveTableAliases(highlight.parts, tableMappings);
|
|
34
|
+
if (parts.length === 0) {
|
|
35
|
+
console.error(highlight);
|
|
36
|
+
throw new Error("Highlight must have at least one part");
|
|
37
|
+
}
|
|
38
|
+
let color;
|
|
39
|
+
let skip = false;
|
|
40
|
+
if (highlight.ignored) {
|
|
41
|
+
color = (x) => dim(strikethrough(x));
|
|
42
|
+
skip = true;
|
|
43
|
+
} else if (parts.length === 2 && tempTables.has(parts[0].text) && !tableMappings.has(parts[0].text)) {
|
|
44
|
+
color = blue;
|
|
45
|
+
skip = true;
|
|
46
|
+
} else color = bgMagentaBright;
|
|
47
|
+
const queryRepr = highlight.representation;
|
|
48
|
+
const queryBeforeMatch = currQuery.slice(0, highlight.position.start);
|
|
49
|
+
const queryAfterToken = currQuery.slice(highlight.position.end);
|
|
50
|
+
currQuery = `${queryBeforeMatch}${color(queryRepr)}${this.colorizeKeywords(queryAfterToken, color)}`;
|
|
51
|
+
const reprKey = highlight.jsonbExtraction ? `${queryRepr}::${highlight.jsonbExtraction}` : queryRepr;
|
|
52
|
+
if (indexRepresentations.has(reprKey)) skip = true;
|
|
53
|
+
if (!skip) {
|
|
54
|
+
indexesToCheck.push(highlight);
|
|
55
|
+
indexRepresentations.add(reprKey);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const referencedTables = [];
|
|
59
|
+
for (const value of tableMappings.values()) if (!value.alias) referencedTables.push({
|
|
60
|
+
schema: value.schema,
|
|
61
|
+
table: value.text
|
|
62
|
+
});
|
|
63
|
+
const { tags, queryWithoutTags } = this.extractSqlcommenter(query);
|
|
64
|
+
const formattedQueryWithoutTags = formattedQuery ? this.extractSqlcommenter(formattedQuery).queryWithoutTags : void 0;
|
|
65
|
+
return {
|
|
66
|
+
statementType,
|
|
67
|
+
indexesToCheck,
|
|
68
|
+
ansiHighlightedQuery: currQuery,
|
|
69
|
+
referencedTables,
|
|
70
|
+
shadowedAliases,
|
|
71
|
+
tags,
|
|
72
|
+
queryWithoutTags,
|
|
73
|
+
formattedQueryWithoutTags,
|
|
74
|
+
nudges
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
deriveIndexes(tables, discovered, referencedTables) {
|
|
78
|
+
/**
|
|
79
|
+
* There are 3 different kinds of parts a col reference can have
|
|
80
|
+
* {a} = just a column within context. Find out the table
|
|
81
|
+
* {a, b} = a column reference with a table reference. There's still ambiguity here
|
|
82
|
+
* with what the schema could be in case there are 2 tables with the same name in different schemas.
|
|
83
|
+
* {a, b, c} = a column reference with a table reference and a schema reference.
|
|
84
|
+
* This is the best case scenario.
|
|
85
|
+
*/
|
|
86
|
+
const allIndexes = [];
|
|
87
|
+
const seenIndexes = /* @__PURE__ */ new Set();
|
|
88
|
+
function addIndex(index) {
|
|
89
|
+
const extractionSuffix = index.jsonbExtraction ? `:"${index.jsonbExtraction}"` : "";
|
|
90
|
+
const key = `"${index.schema}":"${index.table}":"${index.column}"${extractionSuffix}`;
|
|
91
|
+
if (seenIndexes.has(key)) return;
|
|
92
|
+
seenIndexes.add(key);
|
|
93
|
+
allIndexes.push(index);
|
|
94
|
+
}
|
|
95
|
+
const matchingTables = this.filterReferences(referencedTables, tables);
|
|
96
|
+
for (const colReference of discovered) {
|
|
97
|
+
const partsCount = colReference.parts.length;
|
|
98
|
+
const columnOnlyReference = partsCount === 1;
|
|
99
|
+
const tableReference = partsCount === 2;
|
|
100
|
+
const fullReference = partsCount === 3;
|
|
101
|
+
if (columnOnlyReference) {
|
|
102
|
+
const [column] = colReference.parts;
|
|
103
|
+
const referencedColumn = this.normalize(column);
|
|
104
|
+
for (const table of matchingTables) {
|
|
105
|
+
if (!this.hasColumn(table, referencedColumn)) continue;
|
|
106
|
+
const index = {
|
|
107
|
+
schema: table.schemaName,
|
|
108
|
+
table: table.tableName,
|
|
109
|
+
column: referencedColumn
|
|
110
|
+
};
|
|
111
|
+
if (colReference.sort) index.sort = colReference.sort;
|
|
112
|
+
if (colReference.where) index.where = colReference.where;
|
|
113
|
+
if (colReference.jsonbOperator) index.jsonbOperator = colReference.jsonbOperator;
|
|
114
|
+
if (colReference.jsonbExtraction) index.jsonbExtraction = colReference.jsonbExtraction;
|
|
115
|
+
addIndex(index);
|
|
116
|
+
}
|
|
117
|
+
} else if (tableReference) {
|
|
118
|
+
const [table, column] = colReference.parts;
|
|
119
|
+
const referencedTable = this.normalize(table);
|
|
120
|
+
const referencedColumn = this.normalize(column);
|
|
121
|
+
for (const matchingTable of matchingTables) {
|
|
122
|
+
if (!this.hasColumn(matchingTable, referencedColumn)) continue;
|
|
123
|
+
const index = {
|
|
124
|
+
schema: matchingTable.schemaName,
|
|
125
|
+
table: referencedTable,
|
|
126
|
+
column: referencedColumn
|
|
127
|
+
};
|
|
128
|
+
if (colReference.sort) index.sort = colReference.sort;
|
|
129
|
+
if (colReference.where) index.where = colReference.where;
|
|
130
|
+
if (colReference.jsonbOperator) index.jsonbOperator = colReference.jsonbOperator;
|
|
131
|
+
if (colReference.jsonbExtraction) index.jsonbExtraction = colReference.jsonbExtraction;
|
|
132
|
+
addIndex(index);
|
|
133
|
+
}
|
|
134
|
+
} else if (fullReference) {
|
|
135
|
+
const [schema, table, column] = colReference.parts;
|
|
136
|
+
const index = {
|
|
137
|
+
schema: this.normalize(schema),
|
|
138
|
+
table: this.normalize(table),
|
|
139
|
+
column: this.normalize(column)
|
|
140
|
+
};
|
|
141
|
+
if (colReference.sort) index.sort = colReference.sort;
|
|
142
|
+
if (colReference.where) index.where = colReference.where;
|
|
143
|
+
if (colReference.jsonbOperator) index.jsonbOperator = colReference.jsonbOperator;
|
|
144
|
+
if (colReference.jsonbExtraction) index.jsonbExtraction = colReference.jsonbExtraction;
|
|
145
|
+
addIndex(index);
|
|
146
|
+
} else {
|
|
147
|
+
console.error("Column reference has too many parts. The query is malformed", colReference);
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return allIndexes;
|
|
152
|
+
}
|
|
153
|
+
filterReferences(referencedTables, tables) {
|
|
154
|
+
const matchingTables = [];
|
|
155
|
+
for (const referencedTable of referencedTables) {
|
|
156
|
+
const refs = tables.filter(({ tableName, schemaName }) => {
|
|
157
|
+
let schemaMatches = true;
|
|
158
|
+
if (referencedTable.schema) schemaMatches = schemaName === referencedTable.schema;
|
|
159
|
+
return schemaMatches && tableName === referencedTable.table;
|
|
160
|
+
});
|
|
161
|
+
matchingTables.push(...refs);
|
|
162
|
+
}
|
|
163
|
+
return matchingTables;
|
|
164
|
+
}
|
|
165
|
+
hasColumn(table, columnName) {
|
|
166
|
+
return table.columns?.some((column) => column.columnName === columnName) ?? false;
|
|
167
|
+
}
|
|
168
|
+
colorizeKeywords(query, color) {
|
|
169
|
+
return query.replace(/(^\s+)(asc|desc)?(\s+(nulls first|nulls last))?/i, (_, pre, dir, spaceNulls, nulls) => {
|
|
170
|
+
return `${pre}${dir ? color(dir) : ""}${nulls ? spaceNulls.replace(nulls, color(nulls)) : ""}`;
|
|
171
|
+
}).replace(/(^\s+)(is (null|not null))/i, (_, pre, nulltest) => {
|
|
172
|
+
return `${pre}${color(nulltest)}`;
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Resolves aliases such as `a.b` to `x.b` if `a` is a known
|
|
177
|
+
* alias to a table called x.
|
|
178
|
+
*
|
|
179
|
+
* Ignores all other combination of parts such as `a.b.c`
|
|
180
|
+
*/
|
|
181
|
+
resolveTableAliases(parts, tableMappings) {
|
|
182
|
+
if (parts.length !== 2) return parts;
|
|
183
|
+
const tablePart = parts[0];
|
|
184
|
+
const mapping = tableMappings.get(tablePart.text);
|
|
185
|
+
if (mapping) parts[0] = mapping;
|
|
186
|
+
return parts;
|
|
187
|
+
}
|
|
188
|
+
normalize(columnReference) {
|
|
189
|
+
return columnReference.quoted ? columnReference.text : columnReference.text.toLowerCase();
|
|
190
|
+
}
|
|
191
|
+
extractSqlcommenter(query) {
|
|
192
|
+
const trimmedQuery = query.trimEnd();
|
|
193
|
+
const startPosition = trimmedQuery.lastIndexOf("/*");
|
|
194
|
+
const endPosition = trimmedQuery.lastIndexOf("*/");
|
|
195
|
+
if (startPosition === -1 || endPosition === -1) return {
|
|
196
|
+
tags: [],
|
|
197
|
+
queryWithoutTags: trimmedQuery
|
|
198
|
+
};
|
|
199
|
+
const afterComment = trimmedQuery.slice(endPosition + 2).trim();
|
|
200
|
+
if (afterComment && afterComment !== ";") return {
|
|
201
|
+
tags: [],
|
|
202
|
+
queryWithoutTags: trimmedQuery
|
|
203
|
+
};
|
|
204
|
+
const queryWithoutTags = trimmedQuery.slice(0, startPosition);
|
|
205
|
+
const tagString = trimmedQuery.slice(startPosition + 2, endPosition).trim();
|
|
206
|
+
if (!tagString || typeof tagString !== "string") return {
|
|
207
|
+
tags: [],
|
|
208
|
+
queryWithoutTags
|
|
209
|
+
};
|
|
210
|
+
const tags = [];
|
|
211
|
+
for (const match of tagString.split(",")) {
|
|
212
|
+
const [key, value] = match.split("=");
|
|
213
|
+
if (!key || !value) {
|
|
214
|
+
if (tags.length > 0) console.warn(`Invalid sqlcommenter tag: ${match} in comment: ${tagString}. Ignoring`);
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
try {
|
|
218
|
+
let sliceStart = 0;
|
|
219
|
+
if (value.startsWith("'")) sliceStart = 1;
|
|
220
|
+
let sliceEnd = value.length;
|
|
221
|
+
if (value.endsWith("'")) sliceEnd -= 1;
|
|
222
|
+
const decoded = decodeURIComponent(value.slice(sliceStart, sliceEnd));
|
|
223
|
+
tags.push({
|
|
224
|
+
key: key.trim(),
|
|
225
|
+
value: decoded
|
|
226
|
+
});
|
|
227
|
+
} catch (err) {
|
|
228
|
+
console.error(err);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return {
|
|
232
|
+
tags,
|
|
233
|
+
queryWithoutTags
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
//#endregion
|
|
238
|
+
export { Analyzer, ignoredIdentifier };
|
|
239
|
+
|
|
240
|
+
//# sourceMappingURL=analyzer.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyzer.mjs","names":[],"sources":["../../src/sql/analyzer.ts"],"sourcesContent":["import type {\n NullTestType,\n ParseResult,\n SortByDir,\n SortByNulls,\n} from \"@pgsql/types\";\nimport {\n bgMagentaBright,\n blue,\n type Color,\n dim,\n strikethrough,\n} from \"colorette\";\nimport type { RootIndexCandidate } from \"../optimizer/genalgo.js\";\nimport type { ExportedStats } from \"../optimizer/statistics.js\";\nimport { getNodeKind } from \"./ast-utils.js\";\nimport type { Nudge } from \"./nudges.js\";\nimport { ColumnReferencePart, TableMappings, Walker } from \"./walker.js\";\n\nexport interface DatabaseDriver {\n query(query: string, params: unknown[]): Promise<unknown[]>;\n}\n\nexport const ignoredIdentifier = \"__qd_placeholder\";\n\nexport interface SQLCommenterTag {\n key: string;\n value: string;\n}\n\nexport type SortContext = {\n dir: SortByDir;\n nulls: SortByNulls;\n};\n\nexport type DiscoveredColumnReference = {\n /** How often the column reference appears in the query. */\n frequency: number;\n /**\n * Representation of the column reference exactly\n * as it appears in the query.\n */\n representation: string;\n /**\n * Parts of the column reference separated by dots in the query.\n * The table reference (if it exists) is resolved if the query\n * uses an alias.\n *\n * Has 3 different potential configurations (in theory)\n * `a.b.c` - a column reference with a table and a schema reference\n * `a.b` - a column reference with a table reference but no schema\n * `a` - a column reference with no table reference.\n *\n * We use a simple array here to allow parsing of any syntactically correct\n * but logically incorrect query. The checks happen later when we're deriving\n * potential indexes from parts of a column reference in `deriveIndexes`\n */\n parts: ColumnReferencePart[];\n /**\n * Whether the column reference is invalid. This\n */\n ignored: boolean;\n /** The position of the column reference in the query. */\n position: {\n start: number;\n end: number;\n };\n /**\n * A sort direction associated by the column reference.\n * Only relevant to references from sorts\n */\n sort?: SortContext;\n where?: { nulltest?: NullTestType };\n jsonbOperator?: JsonbOperator;\n jsonbExtraction?: string;\n};\n\nexport type JsonbOperator = \"@>\" | \"?\" | \"?|\" | \"?&\" | \"@@\" | \"@?\";\n\nexport type StatementType = \"select\" | \"insert\" | \"update\" | \"delete\" | \"other\";\n\nconst STATEMENT_TYPE_MAP: Record<string, StatementType> = {\n SelectStmt: \"select\",\n InsertStmt: \"insert\",\n UpdateStmt: \"update\",\n DeleteStmt: \"delete\",\n};\n\n/** A function defined by @pgsql/parser */\nexport type Parser = (query: string) => Promise<unknown>;\n\nexport type TableReference = {\n schema?: string;\n table: string;\n};\n\nexport type AnalysisResult = {\n statementType: StatementType;\n indexesToCheck: DiscoveredColumnReference[];\n ansiHighlightedQuery: string;\n referencedTables: TableReference[];\n shadowedAliases: ColumnReferencePart[];\n tags: SQLCommenterTag[];\n queryWithoutTags: string;\n formattedQueryWithoutTags?: string;\n nudges: Nudge[];\n};\n\nexport type SQLCommenterExtraction = {\n tags: SQLCommenterTag[];\n queryWithoutTags: string;\n};\n\n/**\n * Analyzes a query and returns a list of column references that\n * should be indexed.\n *\n * This should be instantiated once per analyzed query.\n */\nexport class Analyzer {\n constructor(private readonly parser: Parser) {}\n async analyze(\n query: string,\n formattedQuery?: string,\n ): Promise<AnalysisResult> {\n const ast = (await this.parser(query)) as ParseResult;\n if (!ast.stmts) {\n throw new Error(\n \"Query did not have any statements. This should probably never happen?\",\n );\n }\n const stmt = ast.stmts[0].stmt;\n if (!stmt) {\n throw new Error(\n \"Query did not have any statements. This should probably never happen?\",\n );\n }\n const stmtKind = getNodeKind(stmt);\n const statementType: StatementType =\n STATEMENT_TYPE_MAP[stmtKind] ?? \"other\";\n const walker = new Walker(query);\n const {\n highlights,\n indexRepresentations,\n indexesToCheck,\n shadowedAliases,\n tempTables,\n tableMappings,\n nudges,\n } = walker.walk(stmt);\n const sortedHighlights = highlights.sort(\n (a, b) => b.position.end - a.position.end,\n );\n let currQuery = query;\n for (const highlight of sortedHighlights) {\n // our parts might have\n const parts = this.resolveTableAliases(highlight.parts, tableMappings);\n if (parts.length === 0) {\n console.error(highlight);\n throw new Error(\"Highlight must have at least one part\");\n }\n let color: Color;\n let skip = false;\n if (highlight.ignored) {\n color = (x) => dim(strikethrough(x));\n skip = true;\n } else if (\n parts.length === 2 &&\n tempTables.has(parts[0].text) &&\n // sometimes temp tables are aliased as existing tables\n // we don't want to ignore them if they are\n !tableMappings.has(parts[0].text)\n ) {\n color = blue;\n skip = true;\n } else {\n color = bgMagentaBright;\n }\n const queryRepr = highlight.representation;\n const queryBeforeMatch = currQuery.slice(0, highlight.position.start);\n const queryAfterToken = currQuery.slice(highlight.position.end);\n currQuery = `${queryBeforeMatch}${color(queryRepr)}${this.colorizeKeywords(\n queryAfterToken,\n color,\n )}`;\n const reprKey = highlight.jsonbExtraction\n ? `${queryRepr}::${highlight.jsonbExtraction}`\n : queryRepr;\n if (indexRepresentations.has(reprKey)) {\n skip = true;\n }\n if (!skip) {\n indexesToCheck.push(highlight);\n indexRepresentations.add(reprKey);\n }\n }\n\n const referencedTables: TableReference[] = [];\n for (const value of tableMappings.values()) {\n // aliased mappings are not concrete tables\n // eg: select * from table t -> t is not a table\n if (!value.alias) {\n referencedTables.push({\n schema: value.schema,\n table: value.text,\n });\n }\n }\n const { tags, queryWithoutTags } = this.extractSqlcommenter(query);\n\n const formattedQueryWithoutTags = formattedQuery\n ? this.extractSqlcommenter(formattedQuery).queryWithoutTags\n : undefined;\n\n return {\n statementType,\n indexesToCheck,\n ansiHighlightedQuery: currQuery,\n referencedTables,\n shadowedAliases,\n tags,\n queryWithoutTags,\n formattedQueryWithoutTags,\n nudges,\n };\n }\n\n deriveIndexes(\n tables: ExportedStats[],\n discovered: DiscoveredColumnReference[],\n referencedTables: TableReference[],\n ): RootIndexCandidate[] {\n /**\n * There are 3 different kinds of parts a col reference can have\n * {a} = just a column within context. Find out the table\n * {a, b} = a column reference with a table reference. There's still ambiguity here\n * with what the schema could be in case there are 2 tables with the same name in different schemas.\n * {a, b, c} = a column reference with a table reference and a schema reference.\n * This is the best case scenario.\n */\n const allIndexes: RootIndexCandidate[] = [];\n const seenIndexes = new Set<string>();\n function addIndex(index: RootIndexCandidate) {\n const extractionSuffix = index.jsonbExtraction\n ? `:\"${index.jsonbExtraction}\"`\n : \"\";\n const key = `\"${index.schema}\":\"${index.table}\":\"${index.column}\"${extractionSuffix}`;\n if (seenIndexes.has(key)) {\n return;\n }\n seenIndexes.add(key);\n allIndexes.push(index);\n }\n const matchingTables = this.filterReferences(referencedTables, tables);\n for (const colReference of discovered) {\n const partsCount = colReference.parts.length;\n const columnOnlyReference = partsCount === 1;\n const tableReference = partsCount === 2;\n const fullReference = partsCount === 3;\n if (columnOnlyReference) {\n // select c from x\n const [column] = colReference.parts;\n const referencedColumn = this.normalize(column);\n for (const table of matchingTables) {\n if (!this.hasColumn(table, referencedColumn)) {\n continue;\n }\n const index: RootIndexCandidate = {\n schema: table.schemaName,\n table: table.tableName,\n column: referencedColumn,\n };\n if (colReference.sort) {\n index.sort = colReference.sort;\n }\n if (colReference.where) {\n index.where = colReference.where;\n }\n if (colReference.jsonbOperator) {\n index.jsonbOperator = colReference.jsonbOperator;\n }\n if (colReference.jsonbExtraction) {\n index.jsonbExtraction = colReference.jsonbExtraction;\n }\n addIndex(index);\n }\n } else if (tableReference) {\n // select b.c from x\n const [table, column] = colReference.parts;\n const referencedTable = this.normalize(table);\n const referencedColumn = this.normalize(column);\n for (const matchingTable of matchingTables) {\n if (!this.hasColumn(matchingTable, referencedColumn)) {\n continue;\n }\n const index: RootIndexCandidate = {\n schema: matchingTable.schemaName,\n table: referencedTable,\n column: referencedColumn,\n };\n if (colReference.sort) {\n index.sort = colReference.sort;\n }\n if (colReference.where) {\n index.where = colReference.where;\n }\n if (colReference.jsonbOperator) {\n index.jsonbOperator = colReference.jsonbOperator;\n }\n if (colReference.jsonbExtraction) {\n index.jsonbExtraction = colReference.jsonbExtraction;\n }\n addIndex(index);\n }\n } else if (fullReference) {\n // select a.b.c from x\n const [schema, table, column] = colReference.parts;\n const referencedSchema = this.normalize(schema);\n const referencedTable = this.normalize(table);\n const referencedColumn = this.normalize(column);\n const index: RootIndexCandidate = {\n schema: referencedSchema,\n table: referencedTable,\n column: referencedColumn,\n };\n if (colReference.sort) {\n index.sort = colReference.sort;\n }\n if (colReference.where) {\n index.where = colReference.where;\n }\n if (colReference.jsonbOperator) {\n index.jsonbOperator = colReference.jsonbOperator;\n }\n if (colReference.jsonbExtraction) {\n index.jsonbExtraction = colReference.jsonbExtraction;\n }\n addIndex(index);\n } else {\n // select huh.a.b.c from x\n console.error(\n \"Column reference has too many parts. The query is malformed\",\n colReference,\n );\n continue;\n }\n }\n return allIndexes;\n }\n\n private filterReferences(\n referencedTables: TableReference[],\n tables: ExportedStats[],\n ): ExportedStats[] {\n const matchingTables: ExportedStats[] = [];\n for (const referencedTable of referencedTables) {\n const refs = tables.filter(({ tableName, schemaName }) => {\n // not every referenced table carries a schema with it\n let schemaMatches = true;\n if (referencedTable.schema) {\n schemaMatches = schemaName === referencedTable.schema;\n }\n return schemaMatches && tableName === referencedTable.table;\n });\n matchingTables.push(...refs);\n }\n return matchingTables;\n }\n\n private hasColumn(table: ExportedStats, columnName: string): boolean {\n return (\n table.columns?.some((column) => column.columnName === columnName) ?? false\n );\n }\n\n private colorizeKeywords(query: string, color: Color) {\n return query\n .replace(\n // eh? This kinda sucks\n /(^\\s+)(asc|desc)?(\\s+(nulls first|nulls last))?/i,\n (_, pre, dir, spaceNulls, nulls) => {\n return `${pre}${dir ? color(dir) : \"\"}${\n nulls ? spaceNulls.replace(nulls, color(nulls)) : \"\"\n }`;\n },\n )\n .replace(/(^\\s+)(is (null|not null))/i, (_, pre, nulltest) => {\n return `${pre}${color(nulltest)}`;\n });\n }\n\n /**\n * Resolves aliases such as `a.b` to `x.b` if `a` is a known\n * alias to a table called x.\n *\n * Ignores all other combination of parts such as `a.b.c`\n */\n private resolveTableAliases(\n parts: ColumnReferencePart[],\n tableMappings: TableMappings,\n ): ColumnReferencePart[] {\n // we don't want to resolve aliases for references such as\n // `a.b.c` - this is fully qualified with a schema and can't be an alias\n // `c` - because there's no table reference here (as far as we can tell)\n if (parts.length !== 2) {\n return parts;\n }\n const tablePart = parts[0];\n const mapping = tableMappings.get(tablePart.text);\n if (mapping) {\n parts[0] = mapping;\n }\n return parts;\n }\n\n private normalize(columnReference: ColumnReferencePart): string {\n return columnReference.quoted\n ? columnReference.text\n : // postgres automatically lowercases column names if not quoted\n columnReference.text.toLowerCase();\n }\n\n private extractSqlcommenter(query: string): SQLCommenterExtraction {\n const trimmedQuery = query.trimEnd();\n const startPosition = trimmedQuery.lastIndexOf(\"/*\");\n const endPosition = trimmedQuery.lastIndexOf(\"*/\");\n if (startPosition === -1 || endPosition === -1) {\n return { tags: [], queryWithoutTags: trimmedQuery };\n }\n // Only treat as SQLCommenter if the comment is at the end of the query.\n // pg_stat_statements (PG 18+) puts /*, ... */ inside IN clauses —\n // those have SQL after the closing */, so skip them.\n const afterComment = trimmedQuery.slice(endPosition + 2).trim();\n if (afterComment && afterComment !== \";\") {\n return { tags: [], queryWithoutTags: trimmedQuery };\n }\n const queryWithoutTags = trimmedQuery.slice(0, startPosition);\n const tagString = trimmedQuery.slice(startPosition + 2, endPosition).trim();\n if (!tagString || typeof tagString !== \"string\") {\n return { tags: [], queryWithoutTags: queryWithoutTags };\n }\n const tags: SQLCommenterTag[] = [];\n for (const match of tagString.split(\",\")) {\n const [key, value] = match.split(\"=\");\n // just because a comment has a `,` but not a `=` in it doesn't\n // mean that it's a malformed sqlcommenter tag. It might just be\n // a long comment with good punctuation.\n if (!key || !value) {\n // however, if there was a previously valid tag, the comment\n // is more likely to be a malformed sqlcommenter tag\n if (tags.length > 0) {\n console.warn(\n `Invalid sqlcommenter tag: ${match} in comment: ${tagString}. Ignoring`,\n );\n }\n continue;\n }\n try {\n let sliceStart = 0;\n if (value.startsWith(\"'\")) {\n sliceStart = 1;\n }\n let sliceEnd = value.length;\n if (value.endsWith(\"'\")) {\n sliceEnd -= 1;\n }\n\n const decoded = decodeURIComponent(value.slice(sliceStart, sliceEnd));\n // should we be trimming here?\n tags.push({ key: key.trim(), value: decoded });\n } catch (err) {\n // we want to be very conservative with this parser and ignore errors\n console.error(err);\n }\n }\n return { tags, queryWithoutTags };\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
//#region src/sql/ast-utils.ts
|
|
3
|
+
function is(node, kind) {
|
|
4
|
+
return kind in node;
|
|
5
|
+
}
|
|
6
|
+
function isANode(node) {
|
|
7
|
+
if (typeof node !== "object" || node === null) return false;
|
|
8
|
+
const keys = Object.keys(node);
|
|
9
|
+
return keys.length === 1 && /^[A-Z]/.test(keys[0]);
|
|
10
|
+
}
|
|
11
|
+
function getNodeKind(node) {
|
|
12
|
+
return Object.keys(node)[0];
|
|
13
|
+
}
|
|
14
|
+
//#endregion
|
|
15
|
+
exports.getNodeKind = getNodeKind;
|
|
16
|
+
exports.is = is;
|
|
17
|
+
exports.isANode = isANode;
|
|
18
|
+
|
|
19
|
+
//# sourceMappingURL=ast-utils.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ast-utils.cjs","names":[],"sources":["../../src/sql/ast-utils.ts"],"sourcesContent":["import type { Node } from \"@pgsql/types\";\n\nexport type KeysOfUnion<T> = T extends T ? keyof T : never;\n\nexport function is<K extends KeysOfUnion<Node>>(\n node: Node,\n kind: K,\n): node is Extract<Node, Record<K, unknown>> {\n return kind in node;\n}\n\nexport function isANode(node: unknown): node is Node {\n if (typeof node !== \"object\" || node === null) {\n return false;\n }\n const keys = Object.keys(node);\n return keys.length === 1 && /^[A-Z]/.test(keys[0]);\n}\n\nexport function getNodeKind(node: Node): KeysOfUnion<Node> {\n const keys = Object.keys(node);\n return keys[0] as KeysOfUnion<Node>;\n}\n"],"mappings":";;AAIA,SAAgB,GACd,MACA,MAC2C;AAC3C,QAAO,QAAQ;;AAGjB,SAAgB,QAAQ,MAA6B;AACnD,KAAI,OAAO,SAAS,YAAY,SAAS,KACvC,QAAO;CAET,MAAM,OAAO,OAAO,KAAK,KAAK;AAC9B,QAAO,KAAK,WAAW,KAAK,SAAS,KAAK,KAAK,GAAG;;AAGpD,SAAgB,YAAY,MAA+B;AAEzD,QADa,OAAO,KAAK,KAAK,CAClB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ast-utils.d.cts","names":[],"sources":["../../src/sql/ast-utils.ts"],"mappings":";;;;;KAEY,WAAA,MAAiB,CAAA,SAAU,CAAA,SAAU,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ast-utils.d.mts","names":[],"sources":["../../src/sql/ast-utils.ts"],"mappings":";;;;;KAEY,WAAA,MAAiB,CAAA,SAAU,CAAA,SAAU,CAAA"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
//#region src/sql/ast-utils.ts
|
|
3
|
+
function is(node, kind) {
|
|
4
|
+
return kind in node;
|
|
5
|
+
}
|
|
6
|
+
function isANode(node) {
|
|
7
|
+
if (typeof node !== "object" || node === null) return false;
|
|
8
|
+
const keys = Object.keys(node);
|
|
9
|
+
return keys.length === 1 && /^[A-Z]/.test(keys[0]);
|
|
10
|
+
}
|
|
11
|
+
function getNodeKind(node) {
|
|
12
|
+
return Object.keys(node)[0];
|
|
13
|
+
}
|
|
14
|
+
//#endregion
|
|
15
|
+
export { getNodeKind, is, isANode };
|
|
16
|
+
|
|
17
|
+
//# sourceMappingURL=ast-utils.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ast-utils.mjs","names":[],"sources":["../../src/sql/ast-utils.ts"],"sourcesContent":["import type { Node } from \"@pgsql/types\";\n\nexport type KeysOfUnion<T> = T extends T ? keyof T : never;\n\nexport function is<K extends KeysOfUnion<Node>>(\n node: Node,\n kind: K,\n): node is Extract<Node, Record<K, unknown>> {\n return kind in node;\n}\n\nexport function isANode(node: unknown): node is Node {\n if (typeof node !== \"object\" || node === null) {\n return false;\n }\n const keys = Object.keys(node);\n return keys.length === 1 && /^[A-Z]/.test(keys[0]);\n}\n\nexport function getNodeKind(node: Node): KeysOfUnion<Node> {\n const keys = Object.keys(node);\n return keys[0] as KeysOfUnion<Node>;\n}\n"],"mappings":";;AAIA,SAAgB,GACd,MACA,MAC2C;AAC3C,QAAO,QAAQ;;AAGjB,SAAgB,QAAQ,MAA6B;AACnD,KAAI,OAAO,SAAS,YAAY,SAAS,KACvC,QAAO;CAET,MAAM,OAAO,OAAO,KAAK,KAAK;AAC9B,QAAO,KAAK,WAAW,KAAK,SAAS,KAAK,KAAK,GAAG;;AAGpD,SAAgB,YAAY,MAA+B;AAEzD,QADa,OAAO,KAAK,KAAK,CAClB"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
const require_defineProperty = require("../_virtual/_@oxc-project_runtime@0.122.0/helpers/defineProperty.cjs");
|
|
3
|
+
//#region src/sql/builder.ts
|
|
4
|
+
var PostgresQueryBuilder = class PostgresQueryBuilder {
|
|
5
|
+
constructor(query) {
|
|
6
|
+
this.query = query;
|
|
7
|
+
require_defineProperty._defineProperty(this, "commands", {});
|
|
8
|
+
require_defineProperty._defineProperty(this, "isIntrospection", false);
|
|
9
|
+
require_defineProperty._defineProperty(this, "explainFlags", []);
|
|
10
|
+
require_defineProperty._defineProperty(this, "_preamble", 0);
|
|
11
|
+
require_defineProperty._defineProperty(this, "parameters", {});
|
|
12
|
+
require_defineProperty._defineProperty(this, "limitSubstitution", void 0);
|
|
13
|
+
}
|
|
14
|
+
get preamble() {
|
|
15
|
+
return this._preamble;
|
|
16
|
+
}
|
|
17
|
+
static createIndex(definition, name) {
|
|
18
|
+
if (name) return new PostgresQueryBuilder(`create index "${name}" on ${definition};`);
|
|
19
|
+
return new PostgresQueryBuilder(`create index on ${definition};`);
|
|
20
|
+
}
|
|
21
|
+
enable(command, value = true) {
|
|
22
|
+
const commandString = `enable_${command}`;
|
|
23
|
+
if (value) this.commands[commandString] = "on";
|
|
24
|
+
else this.commands[commandString] = "off";
|
|
25
|
+
return this;
|
|
26
|
+
}
|
|
27
|
+
withQuery(query) {
|
|
28
|
+
this.query = query;
|
|
29
|
+
return this;
|
|
30
|
+
}
|
|
31
|
+
introspect() {
|
|
32
|
+
this.isIntrospection = true;
|
|
33
|
+
return this;
|
|
34
|
+
}
|
|
35
|
+
explain(flags) {
|
|
36
|
+
this.explainFlags = flags;
|
|
37
|
+
return this;
|
|
38
|
+
}
|
|
39
|
+
parameterize(parameters) {
|
|
40
|
+
Object.assign(this.parameters, parameters);
|
|
41
|
+
return this;
|
|
42
|
+
}
|
|
43
|
+
replaceLimit(limit) {
|
|
44
|
+
this.limitSubstitution = limit;
|
|
45
|
+
return this;
|
|
46
|
+
}
|
|
47
|
+
build() {
|
|
48
|
+
let commands = this.generateSetCommands();
|
|
49
|
+
commands += this.generateExplain().query;
|
|
50
|
+
if (this.isIntrospection) commands += " -- @qd_introspection";
|
|
51
|
+
return commands;
|
|
52
|
+
}
|
|
53
|
+
/** Return the "set a=b" parts of the command in the query separate from the explain select ... part */
|
|
54
|
+
buildParts() {
|
|
55
|
+
const commands = this.generateSetCommands();
|
|
56
|
+
const explain = this.generateExplain();
|
|
57
|
+
this._preamble = explain.preamble;
|
|
58
|
+
if (this.isIntrospection) explain.query += " -- @qd_introspection";
|
|
59
|
+
return {
|
|
60
|
+
commands,
|
|
61
|
+
query: explain.query
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
generateSetCommands() {
|
|
65
|
+
let commands = "";
|
|
66
|
+
for (const key in this.commands) {
|
|
67
|
+
const value = this.commands[key];
|
|
68
|
+
commands += `set local ${key}=${value};\n`;
|
|
69
|
+
}
|
|
70
|
+
return commands;
|
|
71
|
+
}
|
|
72
|
+
generateExplain() {
|
|
73
|
+
let finalQuery = "";
|
|
74
|
+
if (this.explainFlags.length > 0) finalQuery += `explain (${this.explainFlags.join(", ")}) `;
|
|
75
|
+
const query = this.substituteQuery();
|
|
76
|
+
const semicolon = query.endsWith(";") ? "" : ";";
|
|
77
|
+
const preamble = finalQuery.length;
|
|
78
|
+
finalQuery += `${query}${semicolon}`;
|
|
79
|
+
return {
|
|
80
|
+
query: finalQuery,
|
|
81
|
+
preamble
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
substituteQuery() {
|
|
85
|
+
let query = this.query;
|
|
86
|
+
if (this.limitSubstitution !== void 0) query = query.replace(/limit\s+\$\d+/g, `limit ${this.limitSubstitution}`);
|
|
87
|
+
for (const [key, value] of Object.entries(this.parameters)) query = query.replaceAll(`\\$${key}`, value.toString());
|
|
88
|
+
return query;
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
//#endregion
|
|
92
|
+
exports.PostgresQueryBuilder = PostgresQueryBuilder;
|
|
93
|
+
|
|
94
|
+
//# sourceMappingURL=builder.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"builder.cjs","names":[],"sources":["../../src/sql/builder.ts"],"sourcesContent":["import { PgIdentifier } from \"./pg-identifier\";\n\nexport type PostgresQueryBuilderCommand =\n | \"bitmapscan\"\n | \"indexscan\"\n | \"seqscan\";\n\nexport class PostgresQueryBuilder {\n private readonly commands: Record<string, string> = {};\n private isIntrospection: boolean = false;\n private explainFlags: string[] = [];\n private _preamble = 0;\n private parameters: Record<Parameter, number> = {};\n // substitution for `limit $1` -> `limit 5`\n private limitSubstitution?: number;\n\n constructor(private query: string) {}\n\n get preamble(): number {\n return this._preamble;\n }\n\n static createIndex(definition: string, name?: PgIdentifier) {\n if (name) {\n return new PostgresQueryBuilder(\n `create index \"${name}\" on ${definition};`,\n );\n }\n return new PostgresQueryBuilder(`create index on ${definition};`);\n }\n\n enable(command: PostgresQueryBuilderCommand, value: boolean = true): this {\n const commandString = `enable_${command}`;\n if (value) {\n this.commands[commandString] = \"on\";\n } else {\n this.commands[commandString] = \"off\";\n }\n return this;\n }\n\n withQuery(query: string): this {\n this.query = query;\n return this;\n }\n\n introspect(): this {\n this.isIntrospection = true;\n return this;\n }\n\n explain(flags: string[]): this {\n this.explainFlags = flags;\n return this;\n }\n\n parameterize(parameters: Record<Parameter, number>) {\n Object.assign(this.parameters, parameters);\n return this;\n }\n\n replaceLimit(limit: number) {\n this.limitSubstitution = limit;\n return this;\n }\n\n build(): string {\n let commands = this.generateSetCommands();\n commands += this.generateExplain().query;\n if (this.isIntrospection) {\n commands += \" -- @qd_introspection\";\n }\n return commands;\n }\n\n /** Return the \"set a=b\" parts of the command in the query separate from the explain select ... part */\n buildParts() {\n const commands = this.generateSetCommands();\n const explain = this.generateExplain();\n this._preamble = explain.preamble;\n if (this.isIntrospection) {\n explain.query += \" -- @qd_introspection\";\n }\n return { commands, query: explain.query };\n }\n\n private generateSetCommands() {\n let commands = \"\";\n for (const key in this.commands) {\n const value = this.commands[key];\n commands += `set local ${key}=${value};\\n`;\n }\n return commands;\n }\n\n private generateExplain() {\n let finalQuery = \"\";\n if (this.explainFlags.length > 0) {\n finalQuery += `explain (${this.explainFlags.join(\", \")}) `;\n }\n const query = this.substituteQuery();\n const semicolon = query.endsWith(\";\") ? \"\" : \";\";\n\n const preamble = finalQuery.length;\n finalQuery += `${query}${semicolon}`;\n return { query: finalQuery, preamble };\n }\n\n private substituteQuery() {\n let query = this.query;\n if (this.limitSubstitution !== undefined) {\n query = query.replace(\n /limit\\s+\\$\\d+/g,\n `limit ${this.limitSubstitution}`,\n );\n }\n for (const [key, value] of Object.entries(this.parameters)) {\n query = query.replaceAll(`\\\\$${key}`, value.toString());\n }\n return query;\n }\n}\n\nexport type Parameter = `$${string}`;\n"],"mappings":";;;AAOA,IAAa,uBAAb,MAAa,qBAAqB;CAShC,YAAY,OAAuB;AAAf,OAAA,QAAA;+CARH,YAAmC,EAAE,CAAC;+CAC/C,mBAA2B,MAAM;+CACjC,gBAAyB,EAAE,CAAC;+CAC5B,aAAY,EAAE;+CACd,cAAwC,EAAE,CAAC;+CAE3C,qBAAA,KAAA,EAA2B;;CAInC,IAAI,WAAmB;AACrB,SAAO,KAAK;;CAGd,OAAO,YAAY,YAAoB,MAAqB;AAC1D,MAAI,KACF,QAAO,IAAI,qBACT,iBAAiB,KAAK,OAAO,WAAW,GACzC;AAEH,SAAO,IAAI,qBAAqB,mBAAmB,WAAW,GAAG;;CAGnE,OAAO,SAAsC,QAAiB,MAAY;EACxE,MAAM,gBAAgB,UAAU;AAChC,MAAI,MACF,MAAK,SAAS,iBAAiB;MAE/B,MAAK,SAAS,iBAAiB;AAEjC,SAAO;;CAGT,UAAU,OAAqB;AAC7B,OAAK,QAAQ;AACb,SAAO;;CAGT,aAAmB;AACjB,OAAK,kBAAkB;AACvB,SAAO;;CAGT,QAAQ,OAAuB;AAC7B,OAAK,eAAe;AACpB,SAAO;;CAGT,aAAa,YAAuC;AAClD,SAAO,OAAO,KAAK,YAAY,WAAW;AAC1C,SAAO;;CAGT,aAAa,OAAe;AAC1B,OAAK,oBAAoB;AACzB,SAAO;;CAGT,QAAgB;EACd,IAAI,WAAW,KAAK,qBAAqB;AACzC,cAAY,KAAK,iBAAiB,CAAC;AACnC,MAAI,KAAK,gBACP,aAAY;AAEd,SAAO;;;CAIT,aAAa;EACX,MAAM,WAAW,KAAK,qBAAqB;EAC3C,MAAM,UAAU,KAAK,iBAAiB;AACtC,OAAK,YAAY,QAAQ;AACzB,MAAI,KAAK,gBACP,SAAQ,SAAS;AAEnB,SAAO;GAAE;GAAU,OAAO,QAAQ;GAAO;;CAG3C,sBAA8B;EAC5B,IAAI,WAAW;AACf,OAAK,MAAM,OAAO,KAAK,UAAU;GAC/B,MAAM,QAAQ,KAAK,SAAS;AAC5B,eAAY,aAAa,IAAI,GAAG,MAAM;;AAExC,SAAO;;CAGT,kBAA0B;EACxB,IAAI,aAAa;AACjB,MAAI,KAAK,aAAa,SAAS,EAC7B,eAAc,YAAY,KAAK,aAAa,KAAK,KAAK,CAAC;EAEzD,MAAM,QAAQ,KAAK,iBAAiB;EACpC,MAAM,YAAY,MAAM,SAAS,IAAI,GAAG,KAAK;EAE7C,MAAM,WAAW,WAAW;AAC5B,gBAAc,GAAG,QAAQ;AACzB,SAAO;GAAE,OAAO;GAAY;GAAU;;CAGxC,kBAA0B;EACxB,IAAI,QAAQ,KAAK;AACjB,MAAI,KAAK,sBAAsB,KAAA,EAC7B,SAAQ,MAAM,QACZ,kBACA,SAAS,KAAK,oBACf;AAEH,OAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,KAAK,WAAW,CACxD,SAAQ,MAAM,WAAW,MAAM,OAAO,MAAM,UAAU,CAAC;AAEzD,SAAO"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { PgIdentifier } from "./pg-identifier.cjs";
|
|
4
|
+
|
|
5
|
+
//#region src/sql/builder.d.ts
|
|
6
|
+
type PostgresQueryBuilderCommand = "bitmapscan" | "indexscan" | "seqscan";
|
|
7
|
+
declare class PostgresQueryBuilder {
|
|
8
|
+
private query;
|
|
9
|
+
private readonly commands;
|
|
10
|
+
private isIntrospection;
|
|
11
|
+
private explainFlags;
|
|
12
|
+
private _preamble;
|
|
13
|
+
private parameters;
|
|
14
|
+
private limitSubstitution?;
|
|
15
|
+
constructor(query: string);
|
|
16
|
+
get preamble(): number;
|
|
17
|
+
static createIndex(definition: string, name?: PgIdentifier): PostgresQueryBuilder;
|
|
18
|
+
enable(command: PostgresQueryBuilderCommand, value?: boolean): this;
|
|
19
|
+
withQuery(query: string): this;
|
|
20
|
+
introspect(): this;
|
|
21
|
+
explain(flags: string[]): this;
|
|
22
|
+
parameterize(parameters: Record<Parameter, number>): this;
|
|
23
|
+
replaceLimit(limit: number): this;
|
|
24
|
+
build(): string;
|
|
25
|
+
/** Return the "set a=b" parts of the command in the query separate from the explain select ... part */
|
|
26
|
+
buildParts(): {
|
|
27
|
+
commands: string;
|
|
28
|
+
query: string;
|
|
29
|
+
};
|
|
30
|
+
private generateSetCommands;
|
|
31
|
+
private generateExplain;
|
|
32
|
+
private substituteQuery;
|
|
33
|
+
}
|
|
34
|
+
type Parameter = `$${string}`;
|
|
35
|
+
//#endregion
|
|
36
|
+
export { Parameter, PostgresQueryBuilder, PostgresQueryBuilderCommand };
|
|
37
|
+
//# sourceMappingURL=builder.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"builder.d.cts","names":[],"sources":["../../src/sql/builder.ts"],"mappings":";;;;;KAEY,2BAAA;AAAA,cAKC,oBAAA;EAAA,QASS,KAAA;EAAA,iBARH,QAAA;EAAA,QACT,eAAA;EAAA,QACA,YAAA;EAAA,QACA,SAAA;EAAA,QACA,UAAA;EAAA,QAEA,iBAAA;cAEY,KAAA;EAAA,IAEhB,QAAA,CAAA;EAAA,OAIG,WAAA,CAAY,UAAA,UAAoB,IAAA,GAAO,YAAA,GAAY,oBAAA;EAS1D,MAAA,CAAO,OAAA,EAAS,2BAAA,EAA6B,KAAA;EAU7C,SAAA,CAAU,KAAA;EAKV,UAAA,CAAA;EAKA,OAAA,CAAQ,KAAA;EAKR,YAAA,CAAa,UAAA,EAAY,MAAA,CAAO,SAAA;EAKhC,YAAA,CAAa,KAAA;EAKb,KAAA,CAAA;EA1DiB;EAoEjB,UAAA,CAAA;;;;UAUQ,mBAAA;EAAA,QASA,eAAA;EAAA,QAaA,eAAA;AAAA;AAAA,KAeE,SAAA"}
|