@query-doctor/core 0.10.5 → 0.10.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +2 -0
- package/dist/index.d.cts +2 -1
- package/dist/index.d.mts +2 -1
- package/dist/index.mjs +2 -1
- package/dist/sql/normalized-fingerprint.cjs +69 -0
- package/dist/sql/normalized-fingerprint.cjs.map +1 -0
- package/dist/sql/normalized-fingerprint.d.cts +26 -0
- package/dist/sql/normalized-fingerprint.d.cts.map +1 -0
- package/dist/sql/normalized-fingerprint.d.mts +26 -0
- package/dist/sql/normalized-fingerprint.d.mts.map +1 -0
- package/dist/sql/normalized-fingerprint.mjs +68 -0
- package/dist/sql/normalized-fingerprint.mjs.map +1 -0
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -6,6 +6,7 @@ const require_nudges = require("./sql/nudges.cjs");
|
|
|
6
6
|
const require_analyzer = require("./sql/analyzer.cjs");
|
|
7
7
|
const require_database = require("./sql/database.cjs");
|
|
8
8
|
const require_display_query = require("./sql/display-query.cjs");
|
|
9
|
+
const require_normalized_fingerprint = require("./sql/normalized-fingerprint.cjs");
|
|
9
10
|
const require_indexes = require("./sql/indexes.cjs");
|
|
10
11
|
const require_builder = require("./sql/builder.cjs");
|
|
11
12
|
const require_genalgo = require("./optimizer/genalgo.cjs");
|
|
@@ -72,4 +73,5 @@ exports.ignoredIdentifier = require_analyzer.ignoredIdentifier;
|
|
|
72
73
|
exports.indexCovers = require_index_coverage.indexCovers;
|
|
73
74
|
exports.isIndexProbablyDroppable = require_indexes.isIndexProbablyDroppable;
|
|
74
75
|
exports.isIndexSupported = require_indexes.isIndexSupported;
|
|
76
|
+
exports.normalizedFingerprint = require_normalized_fingerprint.normalizedFingerprint;
|
|
75
77
|
exports.parseNudges = require_nudges.parseNudges;
|
package/dist/index.d.cts
CHANGED
|
@@ -9,6 +9,7 @@ import { IndexIdentifier, IndexOptimizer, IndexToCreate, OptimizeResult, PROCEED
|
|
|
9
9
|
import { Nudge, parseNudges } from "./sql/nudges.cjs";
|
|
10
10
|
import { AnalysisResult, Analyzer, DatabaseDriver, DiscoveredColumnReference, JsonbOperator, Parser, SQLCommenterExtraction, SQLCommenterTag, SortContext, StatementType, TableReference, ignoredIdentifier } from "./sql/analyzer.cjs";
|
|
11
11
|
import { CompactSelectListOptions, compactSelectList } from "./sql/display-query.cjs";
|
|
12
|
+
import { FingerprintFn, normalizedFingerprint } from "./sql/normalized-fingerprint.cjs";
|
|
12
13
|
import { DuplicateIndexGroup, groupDuplicateIndexes, isIndexProbablyDroppable, isIndexSupported } from "./sql/indexes.cjs";
|
|
13
14
|
import { CombinedExport, ExportedQuery, QuerySource, combinedDumpSql, dumpQueriesSql } from "./optimizer/dump.cjs";
|
|
14
15
|
import { PssRewriter } from "./optimizer/pss-rewriter.cjs";
|
|
@@ -19,4 +20,4 @@ import { ActionPlanNudgeQuery, ActionableStep, AffectedQueryCost, CostReduction,
|
|
|
19
20
|
import { deriveSentryEnvironment } from "./sentry.cjs";
|
|
20
21
|
import { ClientApi, ConnectionMode, ExtensionPresence, IndexDefinition, RepoConfig, ServerApi, UnauthenticatedServerApi } from "./websocket-server.cjs";
|
|
21
22
|
import { QueryFinding, QueryFindingCode, QueryFindingSeverity, analyzeQueryFindings } from "./findings/query-findings.cjs";
|
|
22
|
-
export { ActionPlanNudgeQuery, ActionPlanQuery, ActionableStep, AffectedQueryCost, AggregatedIndexRecommendation, AnalysisResult, Analyzer, ClientApi, ColumnMetadata, CombinedExport, CompactSelectListOptions, ComputedColumnStats, ComputedReltuples, ComputedStats, ConnectionMode, CostReduction, DUMP_STATS_SQL, DatabaseDriver, DiscoveredColumnReference, DomainLabel, DuplicateIndexGroup, ExportedQuery, ExportedStats, ExportedStatsColumns, ExportedStatsIndex, ExportedStatsStatistics, ExportedStatsV1, ExtensionPresence, FullSchema, FullSchemaColumn, FullSchemaCompositeAttribute, FullSchemaConstraint, FullSchemaExtension, FullSchemaFunction, FullSchemaIncludedColumn, FullSchemaIndex, FullSchemaKeyColumn, FullSchemaTable, FullSchemaTrigger, FullSchemaType, FullSchemaTypeConstraint, FullSchemaView, IndexAction, IndexDefinition, IndexGroup, IndexIdentifier, IndexOptimizer, IndexRecommendation, IndexToCreate, JsonbOperator, LiveQueryOptimization, Nudge, NudgeFinding, OptimizationResult, OptimizeResult, OptimizedQuery, PROCEED, Parameter, Parser, Path, PermutedIndexCandidate, PgIdentifier, Postgres, PostgresConnectionInput, PostgresExplainResult, PostgresExplainStage, PostgresExplainStageCommon, PostgresExplainStageSchema, PostgresFactory, PostgresQueryBuilder, PostgresQueryBuilderCommand, PostgresStage, PostgresStageId, PostgresTransaction, PostgresVersion, PssRewriter, QueryFinding, QueryFindingCode, QueryFindingSeverity, QueryHash, QuerySource, RecentQuery, RegressionBreach, RepoConfig, RootIndexCandidate, SKIP, SQLCommenterExtraction, SQLCommenterTag, SerializeResult, ServerApi, SortContext, StatementType, Statistics, StatisticsMode, StatisticsSource, TableMetadata, TableReference, TableStats, UnauthenticatedServerApi, aggregateIndexRecommendations, analyzeQueryFindings, buildActionPlan, combinedDumpSql, compactSelectList, deriveSentryEnvironment, dropIndex, dumpQueriesSql, dumpSchema, groupDuplicateIndexes, groupIndexesByCoverage, ignoredIdentifier, indexCovers, isIndexProbablyDroppable, isIndexSupported, parseNudges };
|
|
23
|
+
export { ActionPlanNudgeQuery, ActionPlanQuery, ActionableStep, AffectedQueryCost, AggregatedIndexRecommendation, AnalysisResult, Analyzer, ClientApi, ColumnMetadata, CombinedExport, CompactSelectListOptions, ComputedColumnStats, ComputedReltuples, ComputedStats, ConnectionMode, CostReduction, DUMP_STATS_SQL, DatabaseDriver, DiscoveredColumnReference, DomainLabel, DuplicateIndexGroup, ExportedQuery, ExportedStats, ExportedStatsColumns, ExportedStatsIndex, ExportedStatsStatistics, ExportedStatsV1, ExtensionPresence, FingerprintFn, FullSchema, FullSchemaColumn, FullSchemaCompositeAttribute, FullSchemaConstraint, FullSchemaExtension, FullSchemaFunction, FullSchemaIncludedColumn, FullSchemaIndex, FullSchemaKeyColumn, FullSchemaTable, FullSchemaTrigger, FullSchemaType, FullSchemaTypeConstraint, FullSchemaView, IndexAction, IndexDefinition, IndexGroup, IndexIdentifier, IndexOptimizer, IndexRecommendation, IndexToCreate, JsonbOperator, LiveQueryOptimization, Nudge, NudgeFinding, OptimizationResult, OptimizeResult, OptimizedQuery, PROCEED, Parameter, Parser, Path, PermutedIndexCandidate, PgIdentifier, Postgres, PostgresConnectionInput, PostgresExplainResult, PostgresExplainStage, PostgresExplainStageCommon, PostgresExplainStageSchema, PostgresFactory, PostgresQueryBuilder, PostgresQueryBuilderCommand, PostgresStage, PostgresStageId, PostgresTransaction, PostgresVersion, PssRewriter, QueryFinding, QueryFindingCode, QueryFindingSeverity, QueryHash, QuerySource, RecentQuery, RegressionBreach, RepoConfig, RootIndexCandidate, SKIP, SQLCommenterExtraction, SQLCommenterTag, SerializeResult, ServerApi, SortContext, StatementType, Statistics, StatisticsMode, StatisticsSource, TableMetadata, TableReference, TableStats, UnauthenticatedServerApi, aggregateIndexRecommendations, analyzeQueryFindings, buildActionPlan, combinedDumpSql, compactSelectList, deriveSentryEnvironment, dropIndex, dumpQueriesSql, dumpSchema, groupDuplicateIndexes, groupIndexesByCoverage, ignoredIdentifier, indexCovers, isIndexProbablyDroppable, isIndexSupported, normalizedFingerprint, parseNudges };
|
package/dist/index.d.mts
CHANGED
|
@@ -9,6 +9,7 @@ import { IndexIdentifier, IndexOptimizer, IndexToCreate, OptimizeResult, PROCEED
|
|
|
9
9
|
import { Nudge, parseNudges } from "./sql/nudges.mjs";
|
|
10
10
|
import { AnalysisResult, Analyzer, DatabaseDriver, DiscoveredColumnReference, JsonbOperator, Parser, SQLCommenterExtraction, SQLCommenterTag, SortContext, StatementType, TableReference, ignoredIdentifier } from "./sql/analyzer.mjs";
|
|
11
11
|
import { CompactSelectListOptions, compactSelectList } from "./sql/display-query.mjs";
|
|
12
|
+
import { FingerprintFn, normalizedFingerprint } from "./sql/normalized-fingerprint.mjs";
|
|
12
13
|
import { DuplicateIndexGroup, groupDuplicateIndexes, isIndexProbablyDroppable, isIndexSupported } from "./sql/indexes.mjs";
|
|
13
14
|
import { CombinedExport, ExportedQuery, QuerySource, combinedDumpSql, dumpQueriesSql } from "./optimizer/dump.mjs";
|
|
14
15
|
import { PssRewriter } from "./optimizer/pss-rewriter.mjs";
|
|
@@ -19,4 +20,4 @@ import { ActionPlanNudgeQuery, ActionableStep, AffectedQueryCost, CostReduction,
|
|
|
19
20
|
import { deriveSentryEnvironment } from "./sentry.mjs";
|
|
20
21
|
import { ClientApi, ConnectionMode, ExtensionPresence, IndexDefinition, RepoConfig, ServerApi, UnauthenticatedServerApi } from "./websocket-server.mjs";
|
|
21
22
|
import { QueryFinding, QueryFindingCode, QueryFindingSeverity, analyzeQueryFindings } from "./findings/query-findings.mjs";
|
|
22
|
-
export { ActionPlanNudgeQuery, ActionPlanQuery, ActionableStep, AffectedQueryCost, AggregatedIndexRecommendation, AnalysisResult, Analyzer, ClientApi, ColumnMetadata, CombinedExport, CompactSelectListOptions, ComputedColumnStats, ComputedReltuples, ComputedStats, ConnectionMode, CostReduction, DUMP_STATS_SQL, DatabaseDriver, DiscoveredColumnReference, DomainLabel, DuplicateIndexGroup, ExportedQuery, ExportedStats, ExportedStatsColumns, ExportedStatsIndex, ExportedStatsStatistics, ExportedStatsV1, ExtensionPresence, FullSchema, FullSchemaColumn, FullSchemaCompositeAttribute, FullSchemaConstraint, FullSchemaExtension, FullSchemaFunction, FullSchemaIncludedColumn, FullSchemaIndex, FullSchemaKeyColumn, FullSchemaTable, FullSchemaTrigger, FullSchemaType, FullSchemaTypeConstraint, FullSchemaView, IndexAction, IndexDefinition, IndexGroup, IndexIdentifier, IndexOptimizer, IndexRecommendation, IndexToCreate, JsonbOperator, LiveQueryOptimization, Nudge, NudgeFinding, OptimizationResult, OptimizeResult, OptimizedQuery, PROCEED, Parameter, Parser, Path, PermutedIndexCandidate, PgIdentifier, Postgres, PostgresConnectionInput, PostgresExplainResult, PostgresExplainStage, PostgresExplainStageCommon, PostgresExplainStageSchema, PostgresFactory, PostgresQueryBuilder, PostgresQueryBuilderCommand, PostgresStage, PostgresStageId, PostgresTransaction, PostgresVersion, PssRewriter, QueryFinding, QueryFindingCode, QueryFindingSeverity, QueryHash, QuerySource, RecentQuery, RegressionBreach, RepoConfig, RootIndexCandidate, SKIP, SQLCommenterExtraction, SQLCommenterTag, SerializeResult, ServerApi, SortContext, StatementType, Statistics, StatisticsMode, StatisticsSource, TableMetadata, TableReference, TableStats, UnauthenticatedServerApi, aggregateIndexRecommendations, analyzeQueryFindings, buildActionPlan, combinedDumpSql, compactSelectList, deriveSentryEnvironment, dropIndex, dumpQueriesSql, dumpSchema, groupDuplicateIndexes, groupIndexesByCoverage, ignoredIdentifier, indexCovers, isIndexProbablyDroppable, isIndexSupported, parseNudges };
|
|
23
|
+
export { ActionPlanNudgeQuery, ActionPlanQuery, ActionableStep, AffectedQueryCost, AggregatedIndexRecommendation, AnalysisResult, Analyzer, ClientApi, ColumnMetadata, CombinedExport, CompactSelectListOptions, ComputedColumnStats, ComputedReltuples, ComputedStats, ConnectionMode, CostReduction, DUMP_STATS_SQL, DatabaseDriver, DiscoveredColumnReference, DomainLabel, DuplicateIndexGroup, ExportedQuery, ExportedStats, ExportedStatsColumns, ExportedStatsIndex, ExportedStatsStatistics, ExportedStatsV1, ExtensionPresence, FingerprintFn, FullSchema, FullSchemaColumn, FullSchemaCompositeAttribute, FullSchemaConstraint, FullSchemaExtension, FullSchemaFunction, FullSchemaIncludedColumn, FullSchemaIndex, FullSchemaKeyColumn, FullSchemaTable, FullSchemaTrigger, FullSchemaType, FullSchemaTypeConstraint, FullSchemaView, IndexAction, IndexDefinition, IndexGroup, IndexIdentifier, IndexOptimizer, IndexRecommendation, IndexToCreate, JsonbOperator, LiveQueryOptimization, Nudge, NudgeFinding, OptimizationResult, OptimizeResult, OptimizedQuery, PROCEED, Parameter, Parser, Path, PermutedIndexCandidate, PgIdentifier, Postgres, PostgresConnectionInput, PostgresExplainResult, PostgresExplainStage, PostgresExplainStageCommon, PostgresExplainStageSchema, PostgresFactory, PostgresQueryBuilder, PostgresQueryBuilderCommand, PostgresStage, PostgresStageId, PostgresTransaction, PostgresVersion, PssRewriter, QueryFinding, QueryFindingCode, QueryFindingSeverity, QueryHash, QuerySource, RecentQuery, RegressionBreach, RepoConfig, RootIndexCandidate, SKIP, SQLCommenterExtraction, SQLCommenterTag, SerializeResult, ServerApi, SortContext, StatementType, Statistics, StatisticsMode, StatisticsSource, TableMetadata, TableReference, TableStats, UnauthenticatedServerApi, aggregateIndexRecommendations, analyzeQueryFindings, buildActionPlan, combinedDumpSql, compactSelectList, deriveSentryEnvironment, dropIndex, dumpQueriesSql, dumpSchema, groupDuplicateIndexes, groupIndexesByCoverage, ignoredIdentifier, indexCovers, isIndexProbablyDroppable, isIndexSupported, normalizedFingerprint, parseNudges };
|
package/dist/index.mjs
CHANGED
|
@@ -5,6 +5,7 @@ import { parseNudges } from "./sql/nudges.mjs";
|
|
|
5
5
|
import { Analyzer, ignoredIdentifier } from "./sql/analyzer.mjs";
|
|
6
6
|
import { PostgresExplainStageSchema, PostgresVersion, dropIndex } from "./sql/database.mjs";
|
|
7
7
|
import { compactSelectList } from "./sql/display-query.mjs";
|
|
8
|
+
import { normalizedFingerprint } from "./sql/normalized-fingerprint.mjs";
|
|
8
9
|
import { groupDuplicateIndexes, isIndexProbablyDroppable, isIndexSupported } from "./sql/indexes.mjs";
|
|
9
10
|
import { PostgresQueryBuilder } from "./sql/builder.mjs";
|
|
10
11
|
import { IndexOptimizer, PROCEED, SKIP } from "./optimizer/genalgo.mjs";
|
|
@@ -17,4 +18,4 @@ import { buildActionPlan } from "./action-plan/build-action-plan.mjs";
|
|
|
17
18
|
import { deriveSentryEnvironment } from "./sentry.mjs";
|
|
18
19
|
import { OptimizedQuery, RecentQuery } from "./query.mjs";
|
|
19
20
|
import { analyzeQueryFindings } from "./findings/query-findings.mjs";
|
|
20
|
-
export { Analyzer, CombinedExport, ComputedColumnStats, ComputedReltuples, ComputedStats, DUMP_STATS_SQL, ExportedQuery, ExportedStats, ExportedStatsColumns, ExportedStatsIndex, ExportedStatsStatistics, ExportedStatsV1, FullSchema, FullSchemaColumn, FullSchemaCompositeAttribute, FullSchemaConstraint, FullSchemaExtension, FullSchemaFunction, FullSchemaIncludedColumn, FullSchemaIndex, FullSchemaKeyColumn, FullSchemaTable, FullSchemaTrigger, FullSchemaType, FullSchemaTypeConstraint, FullSchemaView, IndexOptimizer, OptimizedQuery, PROCEED, PgIdentifier, PostgresExplainStageSchema, PostgresQueryBuilder, PostgresVersion, PssRewriter, RecentQuery, SKIP, Statistics, StatisticsMode, StatisticsSource, aggregateIndexRecommendations, analyzeQueryFindings, buildActionPlan, combinedDumpSql, compactSelectList, deriveSentryEnvironment, dropIndex, dumpQueriesSql, dumpSchema, groupDuplicateIndexes, groupIndexesByCoverage, ignoredIdentifier, indexCovers, isIndexProbablyDroppable, isIndexSupported, parseNudges };
|
|
21
|
+
export { Analyzer, CombinedExport, ComputedColumnStats, ComputedReltuples, ComputedStats, DUMP_STATS_SQL, ExportedQuery, ExportedStats, ExportedStatsColumns, ExportedStatsIndex, ExportedStatsStatistics, ExportedStatsV1, FullSchema, FullSchemaColumn, FullSchemaCompositeAttribute, FullSchemaConstraint, FullSchemaExtension, FullSchemaFunction, FullSchemaIncludedColumn, FullSchemaIndex, FullSchemaKeyColumn, FullSchemaTable, FullSchemaTrigger, FullSchemaType, FullSchemaTypeConstraint, FullSchemaView, IndexOptimizer, OptimizedQuery, PROCEED, PgIdentifier, PostgresExplainStageSchema, PostgresQueryBuilder, PostgresVersion, PssRewriter, RecentQuery, SKIP, Statistics, StatisticsMode, StatisticsSource, aggregateIndexRecommendations, analyzeQueryFindings, buildActionPlan, combinedDumpSql, compactSelectList, deriveSentryEnvironment, dropIndex, dumpQueriesSql, dumpSchema, groupDuplicateIndexes, groupIndexesByCoverage, ignoredIdentifier, indexCovers, isIndexProbablyDroppable, isIndexSupported, normalizedFingerprint, parseNudges };
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
require("../_virtual/_rolldown/runtime.cjs");
|
|
3
|
+
const require_ast_utils = require("./ast-utils.cjs");
|
|
4
|
+
const require_walker = require("./walker.cjs");
|
|
5
|
+
let pgsql_deparser = require("pgsql-deparser");
|
|
6
|
+
//#region src/sql/normalized-fingerprint.ts
|
|
7
|
+
const SELECT_LIST_PLACEHOLDER = { ResTarget: {
|
|
8
|
+
val: { A_Const: {
|
|
9
|
+
ival: { ival: 1 },
|
|
10
|
+
location: -1
|
|
11
|
+
} },
|
|
12
|
+
location: -1
|
|
13
|
+
} };
|
|
14
|
+
/**
|
|
15
|
+
* A "bare column" target: any `ColumnRef`, including `a`, qualified `t.a`, and
|
|
16
|
+
* the wildcards `*` and `t.*`. `count(*)`, `a + b`, literals, and `AS` aliases
|
|
17
|
+
* are not column refs and survive normalization.
|
|
18
|
+
*/
|
|
19
|
+
function isBareColumn(target) {
|
|
20
|
+
if (!require_ast_utils.is(target, "ResTarget")) return false;
|
|
21
|
+
const val = target.ResTarget.val;
|
|
22
|
+
if (!val) return false;
|
|
23
|
+
return require_ast_utils.is(val, "ColumnRef");
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Set-operation arms (`larg`/`rarg`) are inlined `SelectStmt` bodies rather
|
|
27
|
+
* than wrapped `{ SelectStmt }` nodes, so `Walker.traverse` never emits them.
|
|
28
|
+
* Drill into them explicitly; they can nest (`a UNION b UNION c`).
|
|
29
|
+
*/
|
|
30
|
+
function normalizeSelect(select) {
|
|
31
|
+
const targetList = select.targetList;
|
|
32
|
+
if (targetList) {
|
|
33
|
+
const survivors = targetList.filter((t) => !isBareColumn(t));
|
|
34
|
+
if (survivors.length > 1) {
|
|
35
|
+
const keyed = survivors.map((t) => ({
|
|
36
|
+
key: (0, pgsql_deparser.deparseSync)(t),
|
|
37
|
+
t
|
|
38
|
+
}));
|
|
39
|
+
keyed.sort((a, b) => a.key < b.key ? -1 : a.key > b.key ? 1 : 0);
|
|
40
|
+
select.targetList = keyed.map((k) => k.t);
|
|
41
|
+
} else select.targetList = survivors.length ? survivors : [SELECT_LIST_PLACEHOLDER];
|
|
42
|
+
}
|
|
43
|
+
if (select.larg) normalizeSelect(select.larg);
|
|
44
|
+
if (select.rarg) normalizeSelect(select.rarg);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Fingerprint a query while normalizing every SELECT target list in the tree
|
|
48
|
+
* so that the presence and order of bare column references don't affect the
|
|
49
|
+
* result. Applies to the top-level SELECT as well as SELECTs nested in CTEs,
|
|
50
|
+
* subqueries, and set-operation arms. A change to the set of
|
|
51
|
+
* aggregates/expressions, the WHERE, the FROM, etc. still moves it.
|
|
52
|
+
*
|
|
53
|
+
* Takes the already-parsed AST and an injected {@link FingerprintFn} so core
|
|
54
|
+
* needs no parser or fingerprint runtime. The AST is cloned before mutation.
|
|
55
|
+
*/
|
|
56
|
+
async function normalizedFingerprint(ast, fingerprint) {
|
|
57
|
+
const cloned = JSON.parse(JSON.stringify(ast));
|
|
58
|
+
for (const raw of cloned.stmts ?? []) {
|
|
59
|
+
if (!raw.stmt) continue;
|
|
60
|
+
require_walker.Walker.traverse(raw.stmt, (node) => {
|
|
61
|
+
if (require_ast_utils.is(node, "SelectStmt")) normalizeSelect(node.SelectStmt);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
return fingerprint((0, pgsql_deparser.deparseSync)(cloned));
|
|
65
|
+
}
|
|
66
|
+
//#endregion
|
|
67
|
+
exports.normalizedFingerprint = normalizedFingerprint;
|
|
68
|
+
|
|
69
|
+
//# sourceMappingURL=normalized-fingerprint.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalized-fingerprint.cjs","names":["is"],"sources":["../../src/sql/normalized-fingerprint.ts"],"sourcesContent":["import type { Node, ParseResult } from \"@pgsql/types\";\nimport { deparseSync } from \"pgsql-deparser\";\nimport { is } from \"./ast-utils.js\";\nimport { Walker } from \"./walker.js\";\n\n/**\n * A function that fingerprints a SQL string. Injected so this module stays\n * free of a fingerprint runtime dependency, mirroring how `Analyzer` accepts\n * an injected parser. In practice this is `fingerprint` from\n * `@libpg-query/parser`.\n */\nexport type FingerprintFn = (query: string) => Promise<string>;\n\ntype SelectStmt = Extract<Node, { SelectStmt: unknown }>[\"SelectStmt\"];\n\nconst SELECT_LIST_PLACEHOLDER: Node = {\n ResTarget: {\n val: { A_Const: { ival: { ival: 1 }, location: -1 } },\n location: -1,\n },\n};\n\n/**\n * A \"bare column\" target: any `ColumnRef`, including `a`, qualified `t.a`, and\n * the wildcards `*` and `t.*`. `count(*)`, `a + b`, literals, and `AS` aliases\n * are not column refs and survive normalization.\n */\nfunction isBareColumn(target: Node): boolean {\n if (!is(target, \"ResTarget\")) return false;\n const val = target.ResTarget.val;\n if (!val) return false;\n return is(val, \"ColumnRef\");\n}\n\n/**\n * Set-operation arms (`larg`/`rarg`) are inlined `SelectStmt` bodies rather\n * than wrapped `{ SelectStmt }` nodes, so `Walker.traverse` never emits them.\n * Drill into them explicitly; they can nest (`a UNION b UNION c`).\n */\nfunction normalizeSelect(select: SelectStmt): void {\n const targetList = select.targetList;\n if (targetList) {\n const survivors = targetList.filter((t) => !isBareColumn(t));\n if (survivors.length > 1) {\n const keyed = survivors.map((t) => ({ key: deparseSync(t), t }));\n keyed.sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0));\n select.targetList = keyed.map((k) => k.t);\n } else {\n select.targetList = survivors.length ? survivors : [SELECT_LIST_PLACEHOLDER];\n }\n }\n if (select.larg) normalizeSelect(select.larg);\n if (select.rarg) normalizeSelect(select.rarg);\n}\n\n/**\n * Fingerprint a query while normalizing every SELECT target list in the tree\n * so that the presence and order of bare column references don't affect the\n * result. Applies to the top-level SELECT as well as SELECTs nested in CTEs,\n * subqueries, and set-operation arms. A change to the set of\n * aggregates/expressions, the WHERE, the FROM, etc. still moves it.\n *\n * Takes the already-parsed AST and an injected {@link FingerprintFn} so core\n * needs no parser or fingerprint runtime. The AST is cloned before mutation.\n */\nexport async function normalizedFingerprint(\n ast: ParseResult,\n fingerprint: FingerprintFn,\n): Promise<string> {\n const cloned: ParseResult = JSON.parse(JSON.stringify(ast));\n\n for (const raw of cloned.stmts ?? []) {\n if (!raw.stmt) continue;\n Walker.traverse(raw.stmt, (node) => {\n if (is(node, \"SelectStmt\")) {\n normalizeSelect(node.SelectStmt);\n }\n });\n }\n\n return fingerprint(deparseSync(cloned));\n}\n"],"mappings":";;;;;;AAeA,MAAM,0BAAgC,EACpC,WAAW;CACT,KAAK,EAAE,SAAS;EAAE,MAAM,EAAE,MAAM,GAAG;EAAE,UAAU;EAAI,EAAE;CACrD,UAAU;CACX,EACF;;;;;;AAOD,SAAS,aAAa,QAAuB;AAC3C,KAAI,CAACA,kBAAAA,GAAG,QAAQ,YAAY,CAAE,QAAO;CACrC,MAAM,MAAM,OAAO,UAAU;AAC7B,KAAI,CAAC,IAAK,QAAO;AACjB,QAAOA,kBAAAA,GAAG,KAAK,YAAY;;;;;;;AAQ7B,SAAS,gBAAgB,QAA0B;CACjD,MAAM,aAAa,OAAO;AAC1B,KAAI,YAAY;EACd,MAAM,YAAY,WAAW,QAAQ,MAAM,CAAC,aAAa,EAAE,CAAC;AAC5D,MAAI,UAAU,SAAS,GAAG;GACxB,MAAM,QAAQ,UAAU,KAAK,OAAO;IAAE,MAAA,GAAA,eAAA,aAAiB,EAAE;IAAE;IAAG,EAAE;AAChE,SAAM,MAAM,GAAG,MAAO,EAAE,MAAM,EAAE,MAAM,KAAK,EAAE,MAAM,EAAE,MAAM,IAAI,EAAG;AAClE,UAAO,aAAa,MAAM,KAAK,MAAM,EAAE,EAAE;QAEzC,QAAO,aAAa,UAAU,SAAS,YAAY,CAAC,wBAAwB;;AAGhF,KAAI,OAAO,KAAM,iBAAgB,OAAO,KAAK;AAC7C,KAAI,OAAO,KAAM,iBAAgB,OAAO,KAAK;;;;;;;;;;;;AAa/C,eAAsB,sBACpB,KACA,aACiB;CACjB,MAAM,SAAsB,KAAK,MAAM,KAAK,UAAU,IAAI,CAAC;AAE3D,MAAK,MAAM,OAAO,OAAO,SAAS,EAAE,EAAE;AACpC,MAAI,CAAC,IAAI,KAAM;AACf,iBAAA,OAAO,SAAS,IAAI,OAAO,SAAS;AAClC,OAAIA,kBAAAA,GAAG,MAAM,aAAa,CACxB,iBAAgB,KAAK,WAAW;IAElC;;AAGJ,QAAO,aAAA,GAAA,eAAA,aAAwB,OAAO,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { ParseResult } from "@pgsql/types";
|
|
4
|
+
|
|
5
|
+
//#region src/sql/normalized-fingerprint.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* A function that fingerprints a SQL string. Injected so this module stays
|
|
8
|
+
* free of a fingerprint runtime dependency, mirroring how `Analyzer` accepts
|
|
9
|
+
* an injected parser. In practice this is `fingerprint` from
|
|
10
|
+
* `@libpg-query/parser`.
|
|
11
|
+
*/
|
|
12
|
+
type FingerprintFn = (query: string) => Promise<string>;
|
|
13
|
+
/**
|
|
14
|
+
* Fingerprint a query while normalizing every SELECT target list in the tree
|
|
15
|
+
* so that the presence and order of bare column references don't affect the
|
|
16
|
+
* result. Applies to the top-level SELECT as well as SELECTs nested in CTEs,
|
|
17
|
+
* subqueries, and set-operation arms. A change to the set of
|
|
18
|
+
* aggregates/expressions, the WHERE, the FROM, etc. still moves it.
|
|
19
|
+
*
|
|
20
|
+
* Takes the already-parsed AST and an injected {@link FingerprintFn} so core
|
|
21
|
+
* needs no parser or fingerprint runtime. The AST is cloned before mutation.
|
|
22
|
+
*/
|
|
23
|
+
declare function normalizedFingerprint(ast: ParseResult, fingerprint: FingerprintFn): Promise<string>;
|
|
24
|
+
//#endregion
|
|
25
|
+
export { FingerprintFn, normalizedFingerprint };
|
|
26
|
+
//# sourceMappingURL=normalized-fingerprint.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalized-fingerprint.d.cts","names":[],"sources":["../../src/sql/normalized-fingerprint.ts"],"mappings":";;;;;;;;AAWA;;;KAAY,aAAA,IAAiB,KAAA,aAAkB,OAAA;;AAsD/C;;;;;;;;;iBAAsB,qBAAA,CACpB,GAAA,EAAK,WAAA,EACL,WAAA,EAAa,aAAA,GACZ,OAAA"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { ParseResult } from "@pgsql/types";
|
|
4
|
+
|
|
5
|
+
//#region src/sql/normalized-fingerprint.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* A function that fingerprints a SQL string. Injected so this module stays
|
|
8
|
+
* free of a fingerprint runtime dependency, mirroring how `Analyzer` accepts
|
|
9
|
+
* an injected parser. In practice this is `fingerprint` from
|
|
10
|
+
* `@libpg-query/parser`.
|
|
11
|
+
*/
|
|
12
|
+
type FingerprintFn = (query: string) => Promise<string>;
|
|
13
|
+
/**
|
|
14
|
+
* Fingerprint a query while normalizing every SELECT target list in the tree
|
|
15
|
+
* so that the presence and order of bare column references don't affect the
|
|
16
|
+
* result. Applies to the top-level SELECT as well as SELECTs nested in CTEs,
|
|
17
|
+
* subqueries, and set-operation arms. A change to the set of
|
|
18
|
+
* aggregates/expressions, the WHERE, the FROM, etc. still moves it.
|
|
19
|
+
*
|
|
20
|
+
* Takes the already-parsed AST and an injected {@link FingerprintFn} so core
|
|
21
|
+
* needs no parser or fingerprint runtime. The AST is cloned before mutation.
|
|
22
|
+
*/
|
|
23
|
+
declare function normalizedFingerprint(ast: ParseResult, fingerprint: FingerprintFn): Promise<string>;
|
|
24
|
+
//#endregion
|
|
25
|
+
export { FingerprintFn, normalizedFingerprint };
|
|
26
|
+
//# sourceMappingURL=normalized-fingerprint.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalized-fingerprint.d.mts","names":[],"sources":["../../src/sql/normalized-fingerprint.ts"],"mappings":";;;;;;;;AAWA;;;KAAY,aAAA,IAAiB,KAAA,aAAkB,OAAA;;AAsD/C;;;;;;;;;iBAAsB,qBAAA,CACpB,GAAA,EAAK,WAAA,EACL,WAAA,EAAa,aAAA,GACZ,OAAA"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { is } from "./ast-utils.mjs";
|
|
3
|
+
import { Walker } from "./walker.mjs";
|
|
4
|
+
import { deparseSync } from "pgsql-deparser";
|
|
5
|
+
//#region src/sql/normalized-fingerprint.ts
|
|
6
|
+
const SELECT_LIST_PLACEHOLDER = { ResTarget: {
|
|
7
|
+
val: { A_Const: {
|
|
8
|
+
ival: { ival: 1 },
|
|
9
|
+
location: -1
|
|
10
|
+
} },
|
|
11
|
+
location: -1
|
|
12
|
+
} };
|
|
13
|
+
/**
|
|
14
|
+
* A "bare column" target: any `ColumnRef`, including `a`, qualified `t.a`, and
|
|
15
|
+
* the wildcards `*` and `t.*`. `count(*)`, `a + b`, literals, and `AS` aliases
|
|
16
|
+
* are not column refs and survive normalization.
|
|
17
|
+
*/
|
|
18
|
+
function isBareColumn(target) {
|
|
19
|
+
if (!is(target, "ResTarget")) return false;
|
|
20
|
+
const val = target.ResTarget.val;
|
|
21
|
+
if (!val) return false;
|
|
22
|
+
return is(val, "ColumnRef");
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Set-operation arms (`larg`/`rarg`) are inlined `SelectStmt` bodies rather
|
|
26
|
+
* than wrapped `{ SelectStmt }` nodes, so `Walker.traverse` never emits them.
|
|
27
|
+
* Drill into them explicitly; they can nest (`a UNION b UNION c`).
|
|
28
|
+
*/
|
|
29
|
+
function normalizeSelect(select) {
|
|
30
|
+
const targetList = select.targetList;
|
|
31
|
+
if (targetList) {
|
|
32
|
+
const survivors = targetList.filter((t) => !isBareColumn(t));
|
|
33
|
+
if (survivors.length > 1) {
|
|
34
|
+
const keyed = survivors.map((t) => ({
|
|
35
|
+
key: deparseSync(t),
|
|
36
|
+
t
|
|
37
|
+
}));
|
|
38
|
+
keyed.sort((a, b) => a.key < b.key ? -1 : a.key > b.key ? 1 : 0);
|
|
39
|
+
select.targetList = keyed.map((k) => k.t);
|
|
40
|
+
} else select.targetList = survivors.length ? survivors : [SELECT_LIST_PLACEHOLDER];
|
|
41
|
+
}
|
|
42
|
+
if (select.larg) normalizeSelect(select.larg);
|
|
43
|
+
if (select.rarg) normalizeSelect(select.rarg);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Fingerprint a query while normalizing every SELECT target list in the tree
|
|
47
|
+
* so that the presence and order of bare column references don't affect the
|
|
48
|
+
* result. Applies to the top-level SELECT as well as SELECTs nested in CTEs,
|
|
49
|
+
* subqueries, and set-operation arms. A change to the set of
|
|
50
|
+
* aggregates/expressions, the WHERE, the FROM, etc. still moves it.
|
|
51
|
+
*
|
|
52
|
+
* Takes the already-parsed AST and an injected {@link FingerprintFn} so core
|
|
53
|
+
* needs no parser or fingerprint runtime. The AST is cloned before mutation.
|
|
54
|
+
*/
|
|
55
|
+
async function normalizedFingerprint(ast, fingerprint) {
|
|
56
|
+
const cloned = JSON.parse(JSON.stringify(ast));
|
|
57
|
+
for (const raw of cloned.stmts ?? []) {
|
|
58
|
+
if (!raw.stmt) continue;
|
|
59
|
+
Walker.traverse(raw.stmt, (node) => {
|
|
60
|
+
if (is(node, "SelectStmt")) normalizeSelect(node.SelectStmt);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
return fingerprint(deparseSync(cloned));
|
|
64
|
+
}
|
|
65
|
+
//#endregion
|
|
66
|
+
export { normalizedFingerprint };
|
|
67
|
+
|
|
68
|
+
//# sourceMappingURL=normalized-fingerprint.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalized-fingerprint.mjs","names":[],"sources":["../../src/sql/normalized-fingerprint.ts"],"sourcesContent":["import type { Node, ParseResult } from \"@pgsql/types\";\nimport { deparseSync } from \"pgsql-deparser\";\nimport { is } from \"./ast-utils.js\";\nimport { Walker } from \"./walker.js\";\n\n/**\n * A function that fingerprints a SQL string. Injected so this module stays\n * free of a fingerprint runtime dependency, mirroring how `Analyzer` accepts\n * an injected parser. In practice this is `fingerprint` from\n * `@libpg-query/parser`.\n */\nexport type FingerprintFn = (query: string) => Promise<string>;\n\ntype SelectStmt = Extract<Node, { SelectStmt: unknown }>[\"SelectStmt\"];\n\nconst SELECT_LIST_PLACEHOLDER: Node = {\n ResTarget: {\n val: { A_Const: { ival: { ival: 1 }, location: -1 } },\n location: -1,\n },\n};\n\n/**\n * A \"bare column\" target: any `ColumnRef`, including `a`, qualified `t.a`, and\n * the wildcards `*` and `t.*`. `count(*)`, `a + b`, literals, and `AS` aliases\n * are not column refs and survive normalization.\n */\nfunction isBareColumn(target: Node): boolean {\n if (!is(target, \"ResTarget\")) return false;\n const val = target.ResTarget.val;\n if (!val) return false;\n return is(val, \"ColumnRef\");\n}\n\n/**\n * Set-operation arms (`larg`/`rarg`) are inlined `SelectStmt` bodies rather\n * than wrapped `{ SelectStmt }` nodes, so `Walker.traverse` never emits them.\n * Drill into them explicitly; they can nest (`a UNION b UNION c`).\n */\nfunction normalizeSelect(select: SelectStmt): void {\n const targetList = select.targetList;\n if (targetList) {\n const survivors = targetList.filter((t) => !isBareColumn(t));\n if (survivors.length > 1) {\n const keyed = survivors.map((t) => ({ key: deparseSync(t), t }));\n keyed.sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0));\n select.targetList = keyed.map((k) => k.t);\n } else {\n select.targetList = survivors.length ? survivors : [SELECT_LIST_PLACEHOLDER];\n }\n }\n if (select.larg) normalizeSelect(select.larg);\n if (select.rarg) normalizeSelect(select.rarg);\n}\n\n/**\n * Fingerprint a query while normalizing every SELECT target list in the tree\n * so that the presence and order of bare column references don't affect the\n * result. Applies to the top-level SELECT as well as SELECTs nested in CTEs,\n * subqueries, and set-operation arms. A change to the set of\n * aggregates/expressions, the WHERE, the FROM, etc. still moves it.\n *\n * Takes the already-parsed AST and an injected {@link FingerprintFn} so core\n * needs no parser or fingerprint runtime. The AST is cloned before mutation.\n */\nexport async function normalizedFingerprint(\n ast: ParseResult,\n fingerprint: FingerprintFn,\n): Promise<string> {\n const cloned: ParseResult = JSON.parse(JSON.stringify(ast));\n\n for (const raw of cloned.stmts ?? []) {\n if (!raw.stmt) continue;\n Walker.traverse(raw.stmt, (node) => {\n if (is(node, \"SelectStmt\")) {\n normalizeSelect(node.SelectStmt);\n }\n });\n }\n\n return fingerprint(deparseSync(cloned));\n}\n"],"mappings":";;;;;AAeA,MAAM,0BAAgC,EACpC,WAAW;CACT,KAAK,EAAE,SAAS;EAAE,MAAM,EAAE,MAAM,GAAG;EAAE,UAAU;EAAI,EAAE;CACrD,UAAU;CACX,EACF;;;;;;AAOD,SAAS,aAAa,QAAuB;AAC3C,KAAI,CAAC,GAAG,QAAQ,YAAY,CAAE,QAAO;CACrC,MAAM,MAAM,OAAO,UAAU;AAC7B,KAAI,CAAC,IAAK,QAAO;AACjB,QAAO,GAAG,KAAK,YAAY;;;;;;;AAQ7B,SAAS,gBAAgB,QAA0B;CACjD,MAAM,aAAa,OAAO;AAC1B,KAAI,YAAY;EACd,MAAM,YAAY,WAAW,QAAQ,MAAM,CAAC,aAAa,EAAE,CAAC;AAC5D,MAAI,UAAU,SAAS,GAAG;GACxB,MAAM,QAAQ,UAAU,KAAK,OAAO;IAAE,KAAK,YAAY,EAAE;IAAE;IAAG,EAAE;AAChE,SAAM,MAAM,GAAG,MAAO,EAAE,MAAM,EAAE,MAAM,KAAK,EAAE,MAAM,EAAE,MAAM,IAAI,EAAG;AAClE,UAAO,aAAa,MAAM,KAAK,MAAM,EAAE,EAAE;QAEzC,QAAO,aAAa,UAAU,SAAS,YAAY,CAAC,wBAAwB;;AAGhF,KAAI,OAAO,KAAM,iBAAgB,OAAO,KAAK;AAC7C,KAAI,OAAO,KAAM,iBAAgB,OAAO,KAAK;;;;;;;;;;;;AAa/C,eAAsB,sBACpB,KACA,aACiB;CACjB,MAAM,SAAsB,KAAK,MAAM,KAAK,UAAU,IAAI,CAAC;AAE3D,MAAK,MAAM,OAAO,OAAO,SAAS,EAAE,EAAE;AACpC,MAAI,CAAC,IAAI,KAAM;AACf,SAAO,SAAS,IAAI,OAAO,SAAS;AAClC,OAAI,GAAG,MAAM,aAAa,CACxB,iBAAgB,KAAK,WAAW;IAElC;;AAGJ,QAAO,YAAY,YAAY,OAAO,CAAC"}
|