@query-doctor/core 0.8.9 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +21 -1
- package/dist/index.d.cts +8 -5
- package/dist/index.d.mts +8 -5
- package/dist/index.mjs +5 -3
- package/dist/optimizer/genalgo.cjs +5 -5
- package/dist/optimizer/genalgo.cjs.map +1 -1
- package/dist/optimizer/genalgo.d.cts +6 -5
- package/dist/optimizer/genalgo.d.cts.map +1 -1
- package/dist/optimizer/genalgo.d.mts +6 -5
- package/dist/optimizer/genalgo.d.mts.map +1 -1
- package/dist/optimizer/genalgo.mjs +5 -5
- package/dist/optimizer/genalgo.mjs.map +1 -1
- package/dist/optimizer/statistics.cjs +1 -61
- package/dist/optimizer/statistics.cjs.map +1 -1
- package/dist/optimizer/statistics.d.cts +1 -20
- package/dist/optimizer/statistics.d.cts.map +1 -1
- package/dist/optimizer/statistics.d.mts +1 -20
- package/dist/optimizer/statistics.d.mts.map +1 -1
- package/dist/optimizer/statistics.mjs +1 -61
- package/dist/optimizer/statistics.mjs.map +1 -1
- package/dist/query.cjs +33 -0
- package/dist/query.cjs.map +1 -0
- package/dist/query.d.cts +105 -0
- package/dist/query.d.cts.map +1 -0
- package/dist/query.d.mts +105 -0
- package/dist/query.d.mts.map +1 -0
- package/dist/query.mjs +31 -0
- package/dist/query.mjs.map +1 -0
- package/dist/schema-dump.cjs +365 -0
- package/dist/schema-dump.cjs.map +1 -0
- package/dist/schema-dump.mjs +365 -0
- package/dist/schema-dump.mjs.map +1 -0
- package/dist/schema.cjs +172 -0
- package/dist/schema.cjs.map +1 -0
- package/dist/schema.d.cts +352 -0
- package/dist/schema.d.cts.map +1 -0
- package/dist/schema.d.mts +352 -0
- package/dist/schema.d.mts.map +1 -0
- package/dist/schema.mjs +157 -0
- package/dist/schema.mjs.map +1 -0
- package/dist/sql/database.d.cts +1 -1
- package/dist/sql/database.d.mts +1 -1
- package/dist/sql/indexes.cjs +51 -2
- package/dist/sql/indexes.cjs.map +1 -1
- package/dist/sql/indexes.d.cts +20 -4
- package/dist/sql/indexes.d.cts.map +1 -1
- package/dist/sql/indexes.d.mts +20 -4
- package/dist/sql/indexes.d.mts.map +1 -1
- package/dist/sql/indexes.mjs +51 -3
- package/dist/sql/indexes.mjs.map +1 -1
- package/dist/sql/walker.cjs +1 -1
- package/dist/sql/walker.mjs +1 -1
- package/dist/websocket-server.d.cts +68 -0
- package/dist/websocket-server.d.cts.map +1 -0
- package/dist/websocket-server.d.mts +68 -0
- package/dist/websocket-server.d.mts.map +1 -0
- package/package.json +4 -2
package/dist/index.cjs
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const require_pg_identifier = require("./sql/pg-identifier.cjs");
|
|
4
|
+
const require_schema = require("./schema.cjs");
|
|
3
5
|
const require_nudges = require("./sql/nudges.cjs");
|
|
4
6
|
const require_analyzer = require("./sql/analyzer.cjs");
|
|
5
7
|
const require_database = require("./sql/database.cjs");
|
|
6
8
|
const require_display_query = require("./sql/display-query.cjs");
|
|
7
9
|
const require_indexes = require("./sql/indexes.cjs");
|
|
8
10
|
const require_builder = require("./sql/builder.cjs");
|
|
9
|
-
const require_pg_identifier = require("./sql/pg-identifier.cjs");
|
|
10
11
|
const require_genalgo = require("./optimizer/genalgo.cjs");
|
|
11
12
|
const require_statistics = require("./optimizer/statistics.cjs");
|
|
12
13
|
const require_dump = require("./optimizer/dump.cjs");
|
|
13
14
|
const require_pss_rewriter = require("./optimizer/pss-rewriter.cjs");
|
|
14
15
|
const require_sentry = require("./sentry.cjs");
|
|
16
|
+
const require_query = require("./query.cjs");
|
|
15
17
|
exports.Analyzer = require_analyzer.Analyzer;
|
|
16
18
|
exports.CombinedExport = require_dump.CombinedExport;
|
|
17
19
|
exports.ComputedColumnStats = require_statistics.ComputedColumnStats;
|
|
@@ -24,13 +26,29 @@ exports.ExportedStatsColumns = require_statistics.ExportedStatsColumns;
|
|
|
24
26
|
exports.ExportedStatsIndex = require_statistics.ExportedStatsIndex;
|
|
25
27
|
exports.ExportedStatsStatistics = require_statistics.ExportedStatsStatistics;
|
|
26
28
|
exports.ExportedStatsV1 = require_statistics.ExportedStatsV1;
|
|
29
|
+
exports.FullSchema = require_schema.FullSchema;
|
|
30
|
+
exports.FullSchemaColumn = require_schema.FullSchemaColumn;
|
|
31
|
+
exports.FullSchemaCompositeAttribute = require_schema.FullSchemaCompositeAttribute;
|
|
32
|
+
exports.FullSchemaConstraint = require_schema.FullSchemaConstraint;
|
|
33
|
+
exports.FullSchemaExtension = require_schema.FullSchemaExtension;
|
|
34
|
+
exports.FullSchemaFunction = require_schema.FullSchemaFunction;
|
|
35
|
+
exports.FullSchemaIncludedColumn = require_schema.FullSchemaIncludedColumn;
|
|
36
|
+
exports.FullSchemaIndex = require_schema.FullSchemaIndex;
|
|
37
|
+
exports.FullSchemaKeyColumn = require_schema.FullSchemaKeyColumn;
|
|
38
|
+
exports.FullSchemaTable = require_schema.FullSchemaTable;
|
|
39
|
+
exports.FullSchemaTrigger = require_schema.FullSchemaTrigger;
|
|
40
|
+
exports.FullSchemaType = require_schema.FullSchemaType;
|
|
41
|
+
exports.FullSchemaTypeConstraint = require_schema.FullSchemaTypeConstraint;
|
|
42
|
+
exports.FullSchemaView = require_schema.FullSchemaView;
|
|
27
43
|
exports.IndexOptimizer = require_genalgo.IndexOptimizer;
|
|
44
|
+
exports.OptimizedQuery = require_query.OptimizedQuery;
|
|
28
45
|
exports.PROCEED = require_genalgo.PROCEED;
|
|
29
46
|
exports.PgIdentifier = require_pg_identifier.PgIdentifier;
|
|
30
47
|
exports.PostgresExplainStageSchema = require_database.PostgresExplainStageSchema;
|
|
31
48
|
exports.PostgresQueryBuilder = require_builder.PostgresQueryBuilder;
|
|
32
49
|
exports.PostgresVersion = require_database.PostgresVersion;
|
|
33
50
|
exports.PssRewriter = require_pss_rewriter.PssRewriter;
|
|
51
|
+
exports.RecentQuery = require_query.RecentQuery;
|
|
34
52
|
exports.SKIP = require_genalgo.SKIP;
|
|
35
53
|
exports.Statistics = require_statistics.Statistics;
|
|
36
54
|
exports.StatisticsMode = require_statistics.StatisticsMode;
|
|
@@ -40,6 +58,8 @@ exports.compactSelectList = require_display_query.compactSelectList;
|
|
|
40
58
|
exports.deriveSentryEnvironment = require_sentry.deriveSentryEnvironment;
|
|
41
59
|
exports.dropIndex = require_database.dropIndex;
|
|
42
60
|
exports.dumpQueriesSql = require_dump.dumpQueriesSql;
|
|
61
|
+
exports.dumpSchema = require_schema.dumpSchema;
|
|
62
|
+
exports.groupDuplicateIndexes = require_indexes.groupDuplicateIndexes;
|
|
43
63
|
exports.ignoredIdentifier = require_analyzer.ignoredIdentifier;
|
|
44
64
|
exports.isIndexProbablyDroppable = require_indexes.isIndexProbablyDroppable;
|
|
45
65
|
exports.isIndexSupported = require_indexes.isIndexSupported;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { PgIdentifier } from "./sql/pg-identifier.cjs";
|
|
4
|
-
import { Parameter, PostgresQueryBuilder, PostgresQueryBuilderCommand } from "./sql/builder.cjs";
|
|
5
4
|
import { Postgres, PostgresConnectionInput, PostgresExplainResult, PostgresExplainStage, PostgresExplainStageCommon, PostgresExplainStageSchema, PostgresFactory, PostgresStage, PostgresStageId, PostgresTransaction, PostgresVersion, dropIndex } from "./sql/database.cjs";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
5
|
+
import { FullSchema, FullSchemaColumn, FullSchemaCompositeAttribute, FullSchemaConstraint, FullSchemaExtension, FullSchemaFunction, FullSchemaIncludedColumn, FullSchemaIndex, FullSchemaKeyColumn, FullSchemaTable, FullSchemaTrigger, FullSchemaType, FullSchemaTypeConstraint, FullSchemaView, dumpSchema } from "./schema.cjs";
|
|
6
|
+
import { Parameter, PostgresQueryBuilder, PostgresQueryBuilderCommand } from "./sql/builder.cjs";
|
|
7
|
+
import { ColumnMetadata, ComputedColumnStats, ComputedReltuples, ComputedStats, DUMP_STATS_SQL, ExportedStats, ExportedStatsColumns, ExportedStatsIndex, ExportedStatsStatistics, ExportedStatsV1, Path, SerializeResult, Statistics, StatisticsMode, StatisticsSource, TableMetadata, TableStats } from "./optimizer/statistics.cjs";
|
|
8
|
+
import { IndexIdentifier, IndexOptimizer, IndexToCreate, OptimizeResult, PROCEED, PermutedIndexCandidate, RootIndexCandidate, SKIP } from "./optimizer/genalgo.cjs";
|
|
8
9
|
import { Nudge, parseNudges } from "./sql/nudges.cjs";
|
|
9
10
|
import { AnalysisResult, Analyzer, DatabaseDriver, DiscoveredColumnReference, JsonbOperator, Parser, SQLCommenterExtraction, SQLCommenterTag, SortContext, StatementType, TableReference, ignoredIdentifier } from "./sql/analyzer.cjs";
|
|
10
11
|
import { CompactSelectListOptions, compactSelectList } from "./sql/display-query.cjs";
|
|
11
|
-
import { isIndexProbablyDroppable, isIndexSupported } from "./sql/indexes.cjs";
|
|
12
|
+
import { DuplicateIndexGroup, groupDuplicateIndexes, isIndexProbablyDroppable, isIndexSupported } from "./sql/indexes.cjs";
|
|
12
13
|
import { CombinedExport, ExportedQuery, QuerySource, combinedDumpSql, dumpQueriesSql } from "./optimizer/dump.cjs";
|
|
13
14
|
import { PssRewriter } from "./optimizer/pss-rewriter.cjs";
|
|
14
15
|
import { deriveSentryEnvironment } from "./sentry.cjs";
|
|
15
|
-
|
|
16
|
+
import { IndexRecommendation, LiveQueryOptimization, OptimizationResult, OptimizedQuery, QueryHash, RecentQuery } from "./query.cjs";
|
|
17
|
+
import { ClientApi, ConnectionMode, ExtensionPresence, IndexDefinition, RepoConfig, ServerApi, UnauthenticatedServerApi } from "./websocket-server.cjs";
|
|
18
|
+
export { AnalysisResult, Analyzer, ClientApi, ColumnMetadata, CombinedExport, CompactSelectListOptions, ComputedColumnStats, ComputedReltuples, ComputedStats, ConnectionMode, DUMP_STATS_SQL, DatabaseDriver, DiscoveredColumnReference, DuplicateIndexGroup, ExportedQuery, ExportedStats, ExportedStatsColumns, ExportedStatsIndex, ExportedStatsStatistics, ExportedStatsV1, ExtensionPresence, FullSchema, FullSchemaColumn, FullSchemaCompositeAttribute, FullSchemaConstraint, FullSchemaExtension, FullSchemaFunction, FullSchemaIncludedColumn, FullSchemaIndex, FullSchemaKeyColumn, FullSchemaTable, FullSchemaTrigger, FullSchemaType, FullSchemaTypeConstraint, FullSchemaView, IndexDefinition, IndexIdentifier, IndexOptimizer, IndexRecommendation, IndexToCreate, JsonbOperator, LiveQueryOptimization, Nudge, OptimizationResult, OptimizeResult, OptimizedQuery, PROCEED, Parameter, Parser, Path, PermutedIndexCandidate, PgIdentifier, Postgres, PostgresConnectionInput, PostgresExplainResult, PostgresExplainStage, PostgresExplainStageCommon, PostgresExplainStageSchema, PostgresFactory, PostgresQueryBuilder, PostgresQueryBuilderCommand, PostgresStage, PostgresStageId, PostgresTransaction, PostgresVersion, PssRewriter, QueryHash, QuerySource, RecentQuery, RepoConfig, RootIndexCandidate, SKIP, SQLCommenterExtraction, SQLCommenterTag, SerializeResult, ServerApi, SortContext, StatementType, Statistics, StatisticsMode, StatisticsSource, TableMetadata, TableReference, TableStats, UnauthenticatedServerApi, combinedDumpSql, compactSelectList, deriveSentryEnvironment, dropIndex, dumpQueriesSql, dumpSchema, groupDuplicateIndexes, ignoredIdentifier, isIndexProbablyDroppable, isIndexSupported, parseNudges };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { PgIdentifier } from "./sql/pg-identifier.mjs";
|
|
4
|
-
import { Parameter, PostgresQueryBuilder, PostgresQueryBuilderCommand } from "./sql/builder.mjs";
|
|
5
4
|
import { Postgres, PostgresConnectionInput, PostgresExplainResult, PostgresExplainStage, PostgresExplainStageCommon, PostgresExplainStageSchema, PostgresFactory, PostgresStage, PostgresStageId, PostgresTransaction, PostgresVersion, dropIndex } from "./sql/database.mjs";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
5
|
+
import { FullSchema, FullSchemaColumn, FullSchemaCompositeAttribute, FullSchemaConstraint, FullSchemaExtension, FullSchemaFunction, FullSchemaIncludedColumn, FullSchemaIndex, FullSchemaKeyColumn, FullSchemaTable, FullSchemaTrigger, FullSchemaType, FullSchemaTypeConstraint, FullSchemaView, dumpSchema } from "./schema.mjs";
|
|
6
|
+
import { Parameter, PostgresQueryBuilder, PostgresQueryBuilderCommand } from "./sql/builder.mjs";
|
|
7
|
+
import { ColumnMetadata, ComputedColumnStats, ComputedReltuples, ComputedStats, DUMP_STATS_SQL, ExportedStats, ExportedStatsColumns, ExportedStatsIndex, ExportedStatsStatistics, ExportedStatsV1, Path, SerializeResult, Statistics, StatisticsMode, StatisticsSource, TableMetadata, TableStats } from "./optimizer/statistics.mjs";
|
|
8
|
+
import { IndexIdentifier, IndexOptimizer, IndexToCreate, OptimizeResult, PROCEED, PermutedIndexCandidate, RootIndexCandidate, SKIP } from "./optimizer/genalgo.mjs";
|
|
8
9
|
import { Nudge, parseNudges } from "./sql/nudges.mjs";
|
|
9
10
|
import { AnalysisResult, Analyzer, DatabaseDriver, DiscoveredColumnReference, JsonbOperator, Parser, SQLCommenterExtraction, SQLCommenterTag, SortContext, StatementType, TableReference, ignoredIdentifier } from "./sql/analyzer.mjs";
|
|
10
11
|
import { CompactSelectListOptions, compactSelectList } from "./sql/display-query.mjs";
|
|
11
|
-
import { isIndexProbablyDroppable, isIndexSupported } from "./sql/indexes.mjs";
|
|
12
|
+
import { DuplicateIndexGroup, groupDuplicateIndexes, isIndexProbablyDroppable, isIndexSupported } from "./sql/indexes.mjs";
|
|
12
13
|
import { CombinedExport, ExportedQuery, QuerySource, combinedDumpSql, dumpQueriesSql } from "./optimizer/dump.mjs";
|
|
13
14
|
import { PssRewriter } from "./optimizer/pss-rewriter.mjs";
|
|
14
15
|
import { deriveSentryEnvironment } from "./sentry.mjs";
|
|
15
|
-
|
|
16
|
+
import { IndexRecommendation, LiveQueryOptimization, OptimizationResult, OptimizedQuery, QueryHash, RecentQuery } from "./query.mjs";
|
|
17
|
+
import { ClientApi, ConnectionMode, ExtensionPresence, IndexDefinition, RepoConfig, ServerApi, UnauthenticatedServerApi } from "./websocket-server.mjs";
|
|
18
|
+
export { AnalysisResult, Analyzer, ClientApi, ColumnMetadata, CombinedExport, CompactSelectListOptions, ComputedColumnStats, ComputedReltuples, ComputedStats, ConnectionMode, DUMP_STATS_SQL, DatabaseDriver, DiscoveredColumnReference, DuplicateIndexGroup, ExportedQuery, ExportedStats, ExportedStatsColumns, ExportedStatsIndex, ExportedStatsStatistics, ExportedStatsV1, ExtensionPresence, FullSchema, FullSchemaColumn, FullSchemaCompositeAttribute, FullSchemaConstraint, FullSchemaExtension, FullSchemaFunction, FullSchemaIncludedColumn, FullSchemaIndex, FullSchemaKeyColumn, FullSchemaTable, FullSchemaTrigger, FullSchemaType, FullSchemaTypeConstraint, FullSchemaView, IndexDefinition, IndexIdentifier, IndexOptimizer, IndexRecommendation, IndexToCreate, JsonbOperator, LiveQueryOptimization, Nudge, OptimizationResult, OptimizeResult, OptimizedQuery, PROCEED, Parameter, Parser, Path, PermutedIndexCandidate, PgIdentifier, Postgres, PostgresConnectionInput, PostgresExplainResult, PostgresExplainStage, PostgresExplainStageCommon, PostgresExplainStageSchema, PostgresFactory, PostgresQueryBuilder, PostgresQueryBuilderCommand, PostgresStage, PostgresStageId, PostgresTransaction, PostgresVersion, PssRewriter, QueryHash, QuerySource, RecentQuery, RepoConfig, RootIndexCandidate, SKIP, SQLCommenterExtraction, SQLCommenterTag, SerializeResult, ServerApi, SortContext, StatementType, Statistics, StatisticsMode, StatisticsSource, TableMetadata, TableReference, TableStats, UnauthenticatedServerApi, combinedDumpSql, compactSelectList, deriveSentryEnvironment, dropIndex, dumpQueriesSql, dumpSchema, groupDuplicateIndexes, ignoredIdentifier, isIndexProbablyDroppable, isIndexSupported, parseNudges };
|
package/dist/index.mjs
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
"use client";
|
|
2
|
+
import { PgIdentifier } from "./sql/pg-identifier.mjs";
|
|
3
|
+
import { FullSchema, FullSchemaColumn, FullSchemaCompositeAttribute, FullSchemaConstraint, FullSchemaExtension, FullSchemaFunction, FullSchemaIncludedColumn, FullSchemaIndex, FullSchemaKeyColumn, FullSchemaTable, FullSchemaTrigger, FullSchemaType, FullSchemaTypeConstraint, FullSchemaView, dumpSchema } from "./schema.mjs";
|
|
2
4
|
import { parseNudges } from "./sql/nudges.mjs";
|
|
3
5
|
import { Analyzer, ignoredIdentifier } from "./sql/analyzer.mjs";
|
|
4
6
|
import { PostgresExplainStageSchema, PostgresVersion, dropIndex } from "./sql/database.mjs";
|
|
5
7
|
import { compactSelectList } from "./sql/display-query.mjs";
|
|
6
|
-
import { isIndexProbablyDroppable, isIndexSupported } from "./sql/indexes.mjs";
|
|
8
|
+
import { groupDuplicateIndexes, isIndexProbablyDroppable, isIndexSupported } from "./sql/indexes.mjs";
|
|
7
9
|
import { PostgresQueryBuilder } from "./sql/builder.mjs";
|
|
8
|
-
import { PgIdentifier } from "./sql/pg-identifier.mjs";
|
|
9
10
|
import { IndexOptimizer, PROCEED, SKIP } from "./optimizer/genalgo.mjs";
|
|
10
11
|
import { ComputedColumnStats, ComputedReltuples, ComputedStats, DUMP_STATS_SQL, ExportedStats, ExportedStatsColumns, ExportedStatsIndex, ExportedStatsStatistics, ExportedStatsV1, Statistics, StatisticsMode, StatisticsSource } from "./optimizer/statistics.mjs";
|
|
11
12
|
import { CombinedExport, ExportedQuery, combinedDumpSql, dumpQueriesSql } from "./optimizer/dump.mjs";
|
|
12
13
|
import { PssRewriter } from "./optimizer/pss-rewriter.mjs";
|
|
13
14
|
import { deriveSentryEnvironment } from "./sentry.mjs";
|
|
14
|
-
|
|
15
|
+
import { OptimizedQuery, RecentQuery } from "./query.mjs";
|
|
16
|
+
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, combinedDumpSql, compactSelectList, deriveSentryEnvironment, dropIndex, dumpQueriesSql, dumpSchema, groupDuplicateIndexes, ignoredIdentifier, isIndexProbablyDroppable, isIndexSupported, parseNudges };
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
require("../_virtual/_rolldown/runtime.cjs");
|
|
3
3
|
const require_defineProperty = require("../_virtual/_@oxc-project_runtime@0.126.0/helpers/defineProperty.cjs");
|
|
4
|
+
const require_pg_identifier = require("../sql/pg-identifier.cjs");
|
|
4
5
|
const require_database = require("../sql/database.cjs");
|
|
5
6
|
const require_indexes = require("../sql/indexes.cjs");
|
|
6
7
|
const require_builder = require("../sql/builder.cjs");
|
|
7
|
-
const require_pg_identifier = require("../sql/pg-identifier.cjs");
|
|
8
8
|
const require_permutations = require("../sql/permutations.cjs");
|
|
9
9
|
let colorette = require("colorette");
|
|
10
10
|
//#region src/optimizer/genalgo.ts
|
|
@@ -100,8 +100,8 @@ var IndexOptimizer = class IndexOptimizer {
|
|
|
100
100
|
return require_pg_identifier.PgIdentifier.fromString(indexName);
|
|
101
101
|
}
|
|
102
102
|
indexAlreadyExists(table, columns) {
|
|
103
|
-
return this.existingIndexes.find((index) => index.
|
|
104
|
-
if (columns[i].column !== c.name) return false;
|
|
103
|
+
return this.existingIndexes.find((index) => index.indexType === "btree" && index.tableName.toString() === table && index.keyColumns.length === columns.length && index.keyColumns.every((c, i) => {
|
|
104
|
+
if (columns[i].column !== c.name.toString()) return false;
|
|
105
105
|
if (columns[i].where) return false;
|
|
106
106
|
if (columns[i].sort) switch (columns[i].sort.dir) {
|
|
107
107
|
case "SORTBY_DEFAULT":
|
|
@@ -251,7 +251,7 @@ var IndexOptimizer = class IndexOptimizer {
|
|
|
251
251
|
return groups;
|
|
252
252
|
}
|
|
253
253
|
ginIndexAlreadyExists(table, column) {
|
|
254
|
-
return this.existingIndexes.find((index) => index.
|
|
254
|
+
return this.existingIndexes.find((index) => index.indexType === "gin" && index.tableName.toString() === table && index.keyColumns.some((c) => c.name.toString() === column));
|
|
255
255
|
}
|
|
256
256
|
/**
|
|
257
257
|
* Drop indexes that can be dropped. Ignore the ones that can't
|
|
@@ -259,7 +259,7 @@ var IndexOptimizer = class IndexOptimizer {
|
|
|
259
259
|
async dropExistingIndexes(tx) {
|
|
260
260
|
for (const index of this.existingIndexes) {
|
|
261
261
|
if (!require_indexes.isIndexProbablyDroppable(index)) continue;
|
|
262
|
-
await require_database.dropIndex(tx, require_pg_identifier.PgIdentifier.fromParts(index.
|
|
262
|
+
await require_database.dropIndex(tx, require_pg_identifier.PgIdentifier.fromParts(index.schemaName, index.indexName));
|
|
263
263
|
}
|
|
264
264
|
}
|
|
265
265
|
whereClause(c, col, keyword) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"genalgo.cjs","names":["PostgresQueryBuilder","PgIdentifier","permutationsWithDescendingLength","green","yellow","magenta","blue","isIndexProbablyDroppable","dropIndex"],"sources":["../../src/optimizer/genalgo.ts"],"sourcesContent":["import type { NullTestType } from \"@pgsql/types\";\nimport { blue, gray, green, magenta, red, yellow } from \"colorette\";\nimport type { JsonbOperator } from \"../sql/analyzer.js\";\nimport type { SortContext } from \"../sql/analyzer.js\";\nimport { PostgresQueryBuilder } from \"../sql/builder.js\";\nimport {\n dropIndex,\n type Postgres,\n type PostgresExplainResult,\n type PostgresExplainStage,\n type PostgresTransaction,\n} from \"../sql/database.js\";\nimport { isIndexProbablyDroppable } from \"../sql/indexes.js\";\nimport { permutationsWithDescendingLength } from \"../sql/permutations.js\";\nimport { PgIdentifier } from \"../sql/pg-identifier.js\";\nimport type { IndexedTable, Statistics } from \"./statistics.js\";\n\nexport type IndexIdentifier = string;\n\nexport type IndexRecommendation = PermutedIndexCandidate & {\n definition: IndexIdentifier;\n};\n\ntype Color = (a: string) => string;\n\nexport type IndexToCreate = PermutedIndexCandidate & {\n name: PgIdentifier;\n definition: IndexIdentifier;\n};\n\nexport class IndexOptimizer {\n static prefix = \"__qd_\";\n\n constructor(\n private readonly db: Postgres,\n private readonly statistics: Statistics,\n private existingIndexes: IndexedTable[],\n private readonly config: {\n trace?: boolean;\n debug?: boolean;\n } = {},\n ) {}\n\n async run(\n builder: PostgresQueryBuilder,\n indexes: RootIndexCandidate[],\n beforeQuery?: (tx: PostgresTransaction) => Promise<void>,\n ): Promise<OptimizeResult> {\n const baseExplain = await this.testQueryWithStats(builder, async (tx) => {\n if (beforeQuery) {\n await beforeQuery(tx);\n }\n });\n const baseCost: number = Number(baseExplain.Plan[\"Total Cost\"]);\n if (baseCost === 0) {\n return {\n kind: \"zero_cost_plan\",\n explainPlan: baseExplain.Plan,\n };\n }\n const toCreate = this.indexesToCreate(indexes);\n if (toCreate.length === 0) {\n // No indexes to try: the 2nd EXPLAIN would be identical to the 1st,\n // so skip it. On large tables this saves a full re-run of the query.\n const baseIndexes = this.findUsedIndexes(baseExplain.Plan);\n return {\n kind: \"ok\",\n baseCost,\n finalCost: baseCost,\n newIndexes: new Set<string>(),\n existingIndexes: baseIndexes.existingIndexes,\n triedIndexes: new Map(),\n baseExplainPlan: baseExplain.Plan,\n explainPlan: baseExplain.Plan,\n };\n }\n this.statistics.setAdditionalIndexes(toCreate);\n const finalExplain = await this.testQueryWithStats(builder, async (tx) => {\n if (beforeQuery) {\n await beforeQuery(tx);\n }\n\n // Then create recommended indexes\n for (const permutation of toCreate) {\n const createIndex = PostgresQueryBuilder.createIndex(\n permutation.definition,\n permutation.name,\n )\n .introspect()\n .build();\n\n await tx.exec(createIndex);\n }\n });\n this.statistics.setAdditionalIndexes([]);\n const finalCost = Number(finalExplain.Plan[\"Total Cost\"]);\n if (this.config.debug) {\n console.dir(finalExplain, { depth: null });\n }\n const deltaPercentage = ((baseCost - finalCost) / baseCost) * 100;\n if (finalCost < baseCost) {\n console.log(\n ` 🎉🎉🎉 ${green(`+${deltaPercentage.toFixed(2).padStart(5, \"0\")}%`)}`,\n );\n } else if (finalCost > baseCost) {\n console.log(\n `${red(\n `-${Math.abs(deltaPercentage).toFixed(2).padStart(5, \"0\")}%`,\n )} ${gray(\"If there's a better index, we haven't tried it\")}`,\n );\n }\n const baseIndexes = this.findUsedIndexes(baseExplain.Plan);\n const finalIndexes = this.findUsedIndexes(finalExplain.Plan);\n const triedIndexes = new Map(\n toCreate.map((index) => [index.name.toString(), index]),\n );\n this.replaceUsedIndexesWithDefinition(finalExplain.Plan, triedIndexes);\n\n return {\n kind: \"ok\",\n baseCost,\n finalCost,\n newIndexes: finalIndexes.newIndexes,\n existingIndexes: baseIndexes.existingIndexes,\n triedIndexes,\n baseExplainPlan: baseExplain.Plan,\n explainPlan: finalExplain.Plan,\n };\n }\n\n async runWithoutIndexes(builder: PostgresQueryBuilder) {\n return await this.testQueryWithStats(builder, async (tx) => {\n await this.dropExistingIndexes(tx);\n });\n }\n\n /**\n * Given the current indexes in the optimizer, transform them in some\n * way to change which indexes will be assumed to exist when optimizing\n *\n * @example\n * ```\n * // resets indexes\n * optimizer.transformIndexes(() => [])\n *\n * // adds new index\n * optimizer.transformIndexes(indexes => [...indexes, newIndex])\n * ```\n */\n transformIndexes(f: (indexes: IndexedTable[]) => IndexedTable[]) {\n const newIndexes = f(this.existingIndexes);\n this.existingIndexes = newIndexes;\n return this;\n }\n\n /**\n * Postgres has a limit of 63 characters for index names.\n * So we use this to make sure we don't derive it from a list of columns that can\n * overflow that limit.\n */\n private indexName(): PgIdentifier {\n const indexName =\n IndexOptimizer.prefix + Math.random().toString(36).substring(2, 16);\n return PgIdentifier.fromString(indexName);\n }\n\n // TODO: this doesn't belong in the optimizer\n private indexAlreadyExists(\n table: string,\n columns: RootIndexCandidate[],\n ): IndexedTable | undefined {\n return this.existingIndexes.find(\n (index) =>\n index.index_type === \"btree\" &&\n index.table_name === table &&\n index.index_columns.length === columns.length &&\n index.index_columns.every((c, i) => {\n if (columns[i].column !== c.name) {\n return false;\n }\n\n // we should assume any index with `WHERE`\n // can't be counted as a duplicate\n if (columns[i].where) {\n return false;\n }\n\n if (columns[i].sort) {\n switch (columns[i].sort.dir) {\n // Sorting is ASC by default in postgres\n case \"SORTBY_DEFAULT\":\n case \"SORTBY_ASC\":\n if (c.order !== \"ASC\") {\n return false;\n }\n break;\n case \"SORTBY_DESC\":\n if (c.order !== \"DESC\") {\n return false;\n }\n break;\n }\n }\n return true;\n }),\n );\n }\n\n /**\n * Derive the list of indexes [tableA(X, Y, Z), tableB(H, I, J)]\n **/\n private indexesToCreate(\n rootCandidates: RootIndexCandidate[],\n ): IndexToCreate[] {\n const expressionCandidates = rootCandidates.filter(\n (c) => c.jsonbExtraction,\n );\n const btreeCandidates = rootCandidates.filter(\n (c) => !c.jsonbOperator && !c.jsonbExtraction,\n );\n const ginCandidates = rootCandidates.filter((c) => c.jsonbOperator);\n\n const nextStage: IndexToCreate[] = [];\n\n const permutedIndexes =\n this.groupPotentialIndexColumnsByTable(btreeCandidates);\n for (const permutation of permutedIndexes.values()) {\n const { table: rawTable, schema: rawSchema, columns } = permutation;\n const permutations = permutationsWithDescendingLength(columns);\n for (const columns of permutations) {\n // TODO: accept PgIdentifier values instead\n // required refactoring `PermutedIndexCandidate`\n const schema = PgIdentifier.fromString(rawSchema);\n const table = PgIdentifier.fromString(rawTable);\n const existingIndex = this.indexAlreadyExists(\n table.toString(),\n columns,\n );\n if (existingIndex) {\n continue;\n }\n const indexName = this.indexName();\n\n const definition = this.toDefinition({ table, schema, columns }).raw;\n\n nextStage.push({\n name: indexName,\n schema: schema.toString(),\n table: table.toString(),\n columns,\n definition,\n });\n }\n }\n\n const ginGroups = this.groupGinCandidatesByColumn(ginCandidates);\n for (const group of ginGroups.values()) {\n const { schema: rawSchema, table: rawTable, column, operators } = group;\n const schema = PgIdentifier.fromString(rawSchema);\n const table = PgIdentifier.fromString(rawTable);\n\n // jsonb_path_ops is smaller/faster but only supports @>.\n // All other operators (key-existence and jsonpath) need the full jsonb_ops.\n const needsFullOps = operators.some((op) => op !== \"@>\");\n const opclass = needsFullOps ? undefined : \"jsonb_path_ops\";\n\n const existingGin = this.ginIndexAlreadyExists(table.toString(), column);\n if (existingGin) {\n continue;\n }\n\n const indexName = this.indexName();\n const candidate: RootIndexCandidate = {\n schema: rawSchema,\n table: rawTable,\n column,\n };\n const definition = this.toGinDefinition({\n table,\n schema,\n column: PgIdentifier.fromString(column),\n opclass,\n });\n\n nextStage.push({\n name: indexName,\n schema: schema.toString(),\n table: table.toString(),\n columns: [candidate],\n definition,\n indexMethod: \"gin\",\n opclass,\n });\n }\n\n // Expression B-tree indexes for JSONB path extraction (-> / ->>)\n const seenExpressions = new Set<string>();\n for (const candidate of expressionCandidates) {\n const expression = candidate.jsonbExtraction!;\n const key = `${candidate.schema}.${candidate.table}.${expression}`;\n if (seenExpressions.has(key)) continue;\n seenExpressions.add(key);\n\n const schema = PgIdentifier.fromString(candidate.schema);\n const table = PgIdentifier.fromString(candidate.table);\n const indexName = this.indexName();\n const definition = this.toExpressionDefinition({\n table,\n schema,\n expression,\n });\n\n nextStage.push({\n name: indexName,\n schema: schema.toString(),\n table: table.toString(),\n columns: [candidate],\n definition,\n });\n }\n\n return nextStage;\n }\n\n private toDefinition({\n schema,\n table,\n columns,\n }: {\n schema: PgIdentifier;\n table: PgIdentifier;\n columns: RootIndexCandidate[];\n }) {\n const make = (col: Color, order: Color, _where: Color, _keyword: Color) => {\n // TODO: refactor all of this class to accept PgIdentifiers\n let fullyQualifiedTable: PgIdentifier;\n\n if (schema.toString() === \"public\") {\n fullyQualifiedTable = table;\n } else {\n fullyQualifiedTable = PgIdentifier.fromParts(schema, table);\n }\n const baseColumn = `${fullyQualifiedTable}(${columns\n .map((c) => {\n const column = PgIdentifier.fromString(c.column);\n const direction = c.sort && this.sortDirection(c.sort);\n const nulls = c.sort && this.nullsOrder(c.sort);\n let sort = col(column.toString());\n if (direction) {\n sort += ` ${order(direction)}`;\n }\n if (nulls) {\n sort += ` ${order(nulls)}`;\n }\n return sort;\n })\n .join(\", \")})`;\n // TODO: add support for generating partial indexes\n // if (clauses.length > 0) {\n // return `${baseColumn} ${where(\"where\")} ${clauses.join(\" and \")}`;\n // }\n return baseColumn;\n };\n const id: Color = (a) => a;\n const raw = make(id, id, id, id);\n const colored = make(green, yellow, magenta, blue);\n return { raw, colored };\n }\n\n private toGinDefinition({\n schema,\n table,\n column,\n opclass,\n }: {\n schema: PgIdentifier;\n table: PgIdentifier;\n column: PgIdentifier;\n opclass?: string;\n }): string {\n let fullyQualifiedTable: PgIdentifier;\n if (schema.toString() === \"public\") {\n fullyQualifiedTable = table;\n } else {\n fullyQualifiedTable = PgIdentifier.fromParts(schema, table);\n }\n const opclassSuffix = opclass ? ` ${opclass}` : \"\";\n return `${fullyQualifiedTable} using gin (${column}${opclassSuffix})`;\n }\n\n private toExpressionDefinition({\n schema,\n table,\n expression,\n }: {\n schema: PgIdentifier;\n table: PgIdentifier;\n expression: string;\n }): string {\n let fullyQualifiedTable: PgIdentifier;\n if (schema.toString() === \"public\") {\n fullyQualifiedTable = table;\n } else {\n fullyQualifiedTable = PgIdentifier.fromParts(schema, table);\n }\n return `${fullyQualifiedTable}((${expression}))`;\n }\n\n private groupGinCandidatesByColumn(candidates: RootIndexCandidate[]) {\n const groups = new Map<\n string,\n {\n schema: string;\n table: string;\n column: string;\n operators: JsonbOperator[];\n }\n >();\n for (const c of candidates) {\n if (!c.jsonbOperator) continue;\n const key = `${c.schema}.${c.table}.${c.column}`;\n const existing = groups.get(key);\n if (existing) {\n if (!existing.operators.includes(c.jsonbOperator)) {\n existing.operators.push(c.jsonbOperator);\n }\n } else {\n groups.set(key, {\n schema: c.schema,\n table: c.table,\n column: c.column,\n operators: [c.jsonbOperator],\n });\n }\n }\n return groups;\n }\n\n private ginIndexAlreadyExists(\n table: string,\n column: string,\n ): IndexedTable | undefined {\n return this.existingIndexes.find(\n (index) =>\n index.index_type === \"gin\" &&\n index.table_name === table &&\n index.index_columns.some((c) => c.name === column),\n );\n }\n\n /**\n * Drop indexes that can be dropped. Ignore the ones that can't\n */\n private async dropExistingIndexes(tx: PostgresTransaction) {\n for (const index of this.existingIndexes) {\n if (!isIndexProbablyDroppable(index)) {\n continue;\n }\n const indexName = PgIdentifier.fromParts(\n index.schema_name,\n index.index_name,\n );\n await dropIndex(tx, indexName);\n }\n }\n\n private whereClause(c: RootIndexCandidate, col: Color, keyword: Color) {\n if (!c.where) {\n return \"\";\n }\n if (c.where.nulltest === \"IS_NULL\") {\n return `${col(`\"${c.column}\"`)} is ${keyword(\"null\")}`;\n }\n if (c.where.nulltest === \"IS_NOT_NULL\") {\n return `${col(`\"${c.column}\"`)} is not ${keyword(\"null\")}`;\n }\n return \"\";\n }\n\n private nullsOrder(s: SortContext) {\n if (!s.nulls) {\n return \"\";\n }\n switch (s.nulls) {\n case \"SORTBY_NULLS_FIRST\":\n return \"nulls first\";\n case \"SORTBY_NULLS_LAST\":\n return \"nulls last\";\n case \"SORTBY_NULLS_DEFAULT\":\n default:\n return \"\";\n }\n }\n\n private sortDirection(s: SortContext) {\n if (!s.dir) {\n return \"\";\n }\n switch (s.dir) {\n case \"SORTBY_DESC\":\n return \"desc\";\n case \"SORTBY_ASC\":\n return \"asc\";\n case \"SORTBY_DEFAULT\":\n // god help us if we ever run into this\n case \"SORTBY_USING\":\n default:\n return \"\";\n }\n }\n\n async testQueryWithStats(\n builder: PostgresQueryBuilder,\n f?: (tx: PostgresTransaction) => Promise<void>,\n options?: { params?: unknown[]; genericPlan?: boolean },\n ): Promise<{ Plan: PostgresExplainStage }> {\n try {\n await this.db.transaction(async (tx) => {\n await f?.(tx);\n await this.statistics.restoreStats(tx);\n const flags = [\"format json\"];\n if (options && !options.genericPlan) {\n flags.push(\"analyze\");\n if (this.config.trace) {\n // trace can only be used alongside analyze\n // since it depends on the results of the query execution\n flags.push(\"trace\");\n }\n } else {\n flags.push(\"generic_plan\");\n }\n const { commands, query } = builder\n .introspect()\n .explain(flags)\n .buildParts();\n // this is done in a separate step to prevent sending multiple commands when using parameters\n await tx.exec(commands);\n const result = await tx.exec<PostgresExplainResult>(\n query,\n options?.params,\n );\n const explain = result[0][\"QUERY PLAN\"][0];\n throw new RollbackError(explain);\n });\n } catch (error) {\n if (error instanceof RollbackError) {\n return error.value;\n }\n throw error;\n }\n throw new Error(\"Unreachable\");\n }\n\n private groupPotentialIndexColumnsByTable(indexes: RootIndexCandidate[]) {\n const tableColumns: Map<\n string,\n { schema: string; table: string; columns: RootIndexCandidate[] }\n > = new Map();\n for (const index of indexes) {\n const existing = tableColumns.get(`${index.schema}.${index.table}`);\n if (existing) {\n existing.columns.push(index);\n } else {\n tableColumns.set(`${index.schema}.${index.table}`, {\n table: index.table,\n schema: index.schema,\n columns: [index],\n });\n }\n }\n return tableColumns;\n }\n\n private findUsedIndexes(explain: Record<string, any>) {\n const newIndexes: Set<string> = new Set();\n const existingIndexes: Set<string> = new Set();\n const prefix = IndexOptimizer.prefix;\n walkExplain(explain, (stage) => {\n const indexName = stage[\"Index Name\"];\n if (indexName) {\n // Check for prefix at start or embedded (for hypertable chunk indexes like _hyper_1_1_chunk___qd_xxx)\n if (indexName.startsWith(prefix)) {\n newIndexes.add(indexName);\n } else if (indexName.includes(prefix)) {\n // Extract the actual index name from chunk-prefixed names (e.g., _hyper_1_1_chunk___qd_xxx -> __qd_xxx)\n const actualName = indexName.substring(indexName.indexOf(prefix));\n newIndexes.add(actualName);\n } else {\n existingIndexes.add(indexName);\n }\n }\n });\n return {\n newIndexes,\n existingIndexes,\n };\n }\n\n private replaceUsedIndexesWithDefinition(\n explain: Record<string, any>,\n triedIndexes: Map<string, IndexRecommendation>,\n ) {\n walkExplain(explain, (stage) => {\n const indexName = stage[\"Index Name\"];\n if (typeof indexName === \"string\") {\n const recommendation = triedIndexes.get(indexName);\n if (recommendation) {\n stage[\"Index Name\"] = recommendation.definition;\n }\n }\n });\n }\n}\n\nfunction walkExplain(explain: Record<string, any>, f: (stage: any) => void) {\n function go(plan: any) {\n f(plan);\n if (plan.Plans) {\n for (const p of plan.Plans) {\n go(p);\n }\n }\n }\n go(explain);\n}\n\nexport type OptimizeResult =\n | {\n kind: \"ok\";\n baseExplainPlan: PostgresExplainStage;\n baseCost: number;\n finalCost: number;\n newIndexes: Set<string>;\n existingIndexes: Set<string>;\n triedIndexes: Map<string, IndexRecommendation>;\n explainPlan: PostgresExplainStage;\n }\n | {\n kind: \"zero_cost_plan\";\n explainPlan: PostgresExplainStage;\n };\n\nclass RollbackError<T> {\n constructor(public readonly value?: T) {}\n}\n\nexport type RootIndexCandidate = {\n schema: string;\n table: string;\n column: string;\n sort?: SortContext;\n where?: { nulltest?: NullTestType };\n jsonbOperator?: JsonbOperator;\n jsonbExtraction?: string;\n};\n\nexport type PermutedIndexCandidate = {\n schema: string;\n table: string;\n columns: RootIndexCandidate[];\n // TODO: functional indexes\n where?: string;\n indexMethod?: \"btree\" | \"gin\";\n opclass?: string;\n};\n\nexport const PROCEED = Symbol(\"PROCEED\");\nexport const SKIP = Symbol(\"SKIP\");\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"genalgo.cjs","names":["PostgresQueryBuilder","PgIdentifier","permutationsWithDescendingLength","green","yellow","magenta","blue","isIndexProbablyDroppable","dropIndex"],"sources":["../../src/optimizer/genalgo.ts"],"sourcesContent":["import type { NullTestType } from \"@pgsql/types\";\nimport { blue, gray, green, magenta, red, yellow } from \"colorette\";\nimport type { FullSchemaIndex } from \"../schema.js\";\nimport type { JsonbOperator } from \"../sql/analyzer.js\";\nimport type { SortContext } from \"../sql/analyzer.js\";\nimport { PostgresQueryBuilder } from \"../sql/builder.js\";\nimport {\n dropIndex,\n type Postgres,\n type PostgresExplainResult,\n type PostgresExplainStage,\n type PostgresTransaction,\n} from \"../sql/database.js\";\nimport { isIndexProbablyDroppable } from \"../sql/indexes.js\";\nimport { permutationsWithDescendingLength } from \"../sql/permutations.js\";\nimport { PgIdentifier } from \"../sql/pg-identifier.js\";\nimport type { Statistics } from \"./statistics.js\";\n\nexport type IndexIdentifier = string;\n\ntype IndexRecommendation = PermutedIndexCandidate & {\n definition: IndexIdentifier;\n};\n\ntype Color = (a: string) => string;\n\nexport type IndexToCreate = PermutedIndexCandidate & {\n name: PgIdentifier;\n definition: IndexIdentifier;\n};\n\nexport class IndexOptimizer {\n static prefix = \"__qd_\";\n\n constructor(\n private readonly db: Postgres,\n private readonly statistics: Statistics,\n private existingIndexes: FullSchemaIndex[],\n private readonly config: {\n trace?: boolean;\n debug?: boolean;\n } = {},\n ) {}\n\n async run(\n builder: PostgresQueryBuilder,\n indexes: RootIndexCandidate[],\n beforeQuery?: (tx: PostgresTransaction) => Promise<void>,\n ): Promise<OptimizeResult> {\n const baseExplain = await this.testQueryWithStats(builder, async (tx) => {\n if (beforeQuery) {\n await beforeQuery(tx);\n }\n });\n const baseCost: number = Number(baseExplain.Plan[\"Total Cost\"]);\n if (baseCost === 0) {\n return {\n kind: \"zero_cost_plan\",\n explainPlan: baseExplain.Plan,\n };\n }\n const toCreate = this.indexesToCreate(indexes);\n if (toCreate.length === 0) {\n // No indexes to try: the 2nd EXPLAIN would be identical to the 1st,\n // so skip it. On large tables this saves a full re-run of the query.\n const baseIndexes = this.findUsedIndexes(baseExplain.Plan);\n return {\n kind: \"ok\",\n baseCost,\n finalCost: baseCost,\n newIndexes: new Set<string>(),\n existingIndexes: baseIndexes.existingIndexes,\n triedIndexes: new Map(),\n baseExplainPlan: baseExplain.Plan,\n explainPlan: baseExplain.Plan,\n };\n }\n this.statistics.setAdditionalIndexes(toCreate);\n const finalExplain = await this.testQueryWithStats(builder, async (tx) => {\n if (beforeQuery) {\n await beforeQuery(tx);\n }\n\n // Then create recommended indexes\n for (const permutation of toCreate) {\n const createIndex = PostgresQueryBuilder.createIndex(\n permutation.definition,\n permutation.name,\n )\n .introspect()\n .build();\n\n await tx.exec(createIndex);\n }\n });\n this.statistics.setAdditionalIndexes([]);\n const finalCost = Number(finalExplain.Plan[\"Total Cost\"]);\n if (this.config.debug) {\n console.dir(finalExplain, { depth: null });\n }\n const deltaPercentage = ((baseCost - finalCost) / baseCost) * 100;\n if (finalCost < baseCost) {\n console.log(\n ` 🎉🎉🎉 ${green(`+${deltaPercentage.toFixed(2).padStart(5, \"0\")}%`)}`,\n );\n } else if (finalCost > baseCost) {\n console.log(\n `${red(\n `-${Math.abs(deltaPercentage).toFixed(2).padStart(5, \"0\")}%`,\n )} ${gray(\"If there's a better index, we haven't tried it\")}`,\n );\n }\n const baseIndexes = this.findUsedIndexes(baseExplain.Plan);\n const finalIndexes = this.findUsedIndexes(finalExplain.Plan);\n const triedIndexes = new Map(\n toCreate.map((index) => [index.name.toString(), index]),\n );\n this.replaceUsedIndexesWithDefinition(finalExplain.Plan, triedIndexes);\n\n return {\n kind: \"ok\",\n baseCost,\n finalCost,\n newIndexes: finalIndexes.newIndexes,\n existingIndexes: baseIndexes.existingIndexes,\n triedIndexes,\n baseExplainPlan: baseExplain.Plan,\n explainPlan: finalExplain.Plan,\n };\n }\n\n async runWithoutIndexes(builder: PostgresQueryBuilder) {\n return await this.testQueryWithStats(builder, async (tx) => {\n await this.dropExistingIndexes(tx);\n });\n }\n\n /**\n * Given the current indexes in the optimizer, transform them in some\n * way to change which indexes will be assumed to exist when optimizing\n *\n * @example\n * ```\n * // resets indexes\n * optimizer.transformIndexes(() => [])\n *\n * // adds new index\n * optimizer.transformIndexes(indexes => [...indexes, newIndex])\n * ```\n */\n transformIndexes(f: (indexes: FullSchemaIndex[]) => FullSchemaIndex[]) {\n const newIndexes = f(this.existingIndexes);\n this.existingIndexes = newIndexes;\n return this;\n }\n\n /**\n * Postgres has a limit of 63 characters for index names.\n * So we use this to make sure we don't derive it from a list of columns that can\n * overflow that limit.\n */\n private indexName(): PgIdentifier {\n const indexName =\n IndexOptimizer.prefix + Math.random().toString(36).substring(2, 16);\n return PgIdentifier.fromString(indexName);\n }\n\n // TODO: this doesn't belong in the optimizer\n private indexAlreadyExists(\n table: string,\n columns: RootIndexCandidate[],\n ): FullSchemaIndex | undefined {\n return this.existingIndexes.find(\n (index) =>\n index.indexType === \"btree\" &&\n index.tableName.toString() === table &&\n index.keyColumns.length === columns.length &&\n index.keyColumns.every((c, i) => {\n if (columns[i].column !== c.name.toString()) {\n return false;\n }\n\n // we should assume any index with `WHERE`\n // can't be counted as a duplicate\n if (columns[i].where) {\n return false;\n }\n\n if (columns[i].sort) {\n switch (columns[i].sort.dir) {\n // Sorting is ASC by default in postgres\n case \"SORTBY_DEFAULT\":\n case \"SORTBY_ASC\":\n if (c.order !== \"ASC\") {\n return false;\n }\n break;\n case \"SORTBY_DESC\":\n if (c.order !== \"DESC\") {\n return false;\n }\n break;\n }\n }\n return true;\n }),\n );\n }\n\n /**\n * Derive the list of indexes [tableA(X, Y, Z), tableB(H, I, J)]\n **/\n private indexesToCreate(\n rootCandidates: RootIndexCandidate[],\n ): IndexToCreate[] {\n const expressionCandidates = rootCandidates.filter(\n (c) => c.jsonbExtraction,\n );\n const btreeCandidates = rootCandidates.filter(\n (c) => !c.jsonbOperator && !c.jsonbExtraction,\n );\n const ginCandidates = rootCandidates.filter((c) => c.jsonbOperator);\n\n const nextStage: IndexToCreate[] = [];\n\n const permutedIndexes =\n this.groupPotentialIndexColumnsByTable(btreeCandidates);\n for (const permutation of permutedIndexes.values()) {\n const { table: rawTable, schema: rawSchema, columns } = permutation;\n const permutations = permutationsWithDescendingLength(columns);\n for (const columns of permutations) {\n // TODO: accept PgIdentifier values instead\n // required refactoring `PermutedIndexCandidate`\n const schema = PgIdentifier.fromString(rawSchema);\n const table = PgIdentifier.fromString(rawTable);\n const existingIndex = this.indexAlreadyExists(\n table.toString(),\n columns,\n );\n if (existingIndex) {\n continue;\n }\n const indexName = this.indexName();\n\n const definition = this.toDefinition({ table, schema, columns }).raw;\n\n nextStage.push({\n name: indexName,\n schema: schema.toString(),\n table: table.toString(),\n columns,\n definition,\n });\n }\n }\n\n const ginGroups = this.groupGinCandidatesByColumn(ginCandidates);\n for (const group of ginGroups.values()) {\n const { schema: rawSchema, table: rawTable, column, operators } = group;\n const schema = PgIdentifier.fromString(rawSchema);\n const table = PgIdentifier.fromString(rawTable);\n\n // jsonb_path_ops is smaller/faster but only supports @>.\n // All other operators (key-existence and jsonpath) need the full jsonb_ops.\n const needsFullOps = operators.some((op) => op !== \"@>\");\n const opclass = needsFullOps ? undefined : \"jsonb_path_ops\";\n\n const existingGin = this.ginIndexAlreadyExists(table.toString(), column);\n if (existingGin) {\n continue;\n }\n\n const indexName = this.indexName();\n const candidate: RootIndexCandidate = {\n schema: rawSchema,\n table: rawTable,\n column,\n };\n const definition = this.toGinDefinition({\n table,\n schema,\n column: PgIdentifier.fromString(column),\n opclass,\n });\n\n nextStage.push({\n name: indexName,\n schema: schema.toString(),\n table: table.toString(),\n columns: [candidate],\n definition,\n indexMethod: \"gin\",\n opclass,\n });\n }\n\n // Expression B-tree indexes for JSONB path extraction (-> / ->>)\n const seenExpressions = new Set<string>();\n for (const candidate of expressionCandidates) {\n const expression = candidate.jsonbExtraction!;\n const key = `${candidate.schema}.${candidate.table}.${expression}`;\n if (seenExpressions.has(key)) continue;\n seenExpressions.add(key);\n\n const schema = PgIdentifier.fromString(candidate.schema);\n const table = PgIdentifier.fromString(candidate.table);\n const indexName = this.indexName();\n const definition = this.toExpressionDefinition({\n table,\n schema,\n expression,\n });\n\n nextStage.push({\n name: indexName,\n schema: schema.toString(),\n table: table.toString(),\n columns: [candidate],\n definition,\n });\n }\n\n return nextStage;\n }\n\n private toDefinition({\n schema,\n table,\n columns,\n }: {\n schema: PgIdentifier;\n table: PgIdentifier;\n columns: RootIndexCandidate[];\n }) {\n const make = (col: Color, order: Color, _where: Color, _keyword: Color) => {\n // TODO: refactor all of this class to accept PgIdentifiers\n let fullyQualifiedTable: PgIdentifier;\n\n if (schema.toString() === \"public\") {\n fullyQualifiedTable = table;\n } else {\n fullyQualifiedTable = PgIdentifier.fromParts(schema, table);\n }\n const baseColumn = `${fullyQualifiedTable}(${columns\n .map((c) => {\n const column = PgIdentifier.fromString(c.column);\n const direction = c.sort && this.sortDirection(c.sort);\n const nulls = c.sort && this.nullsOrder(c.sort);\n let sort = col(column.toString());\n if (direction) {\n sort += ` ${order(direction)}`;\n }\n if (nulls) {\n sort += ` ${order(nulls)}`;\n }\n return sort;\n })\n .join(\", \")})`;\n // TODO: add support for generating partial indexes\n // if (clauses.length > 0) {\n // return `${baseColumn} ${where(\"where\")} ${clauses.join(\" and \")}`;\n // }\n return baseColumn;\n };\n const id: Color = (a) => a;\n const raw = make(id, id, id, id);\n const colored = make(green, yellow, magenta, blue);\n return { raw, colored };\n }\n\n private toGinDefinition({\n schema,\n table,\n column,\n opclass,\n }: {\n schema: PgIdentifier;\n table: PgIdentifier;\n column: PgIdentifier;\n opclass?: string;\n }): string {\n let fullyQualifiedTable: PgIdentifier;\n if (schema.toString() === \"public\") {\n fullyQualifiedTable = table;\n } else {\n fullyQualifiedTable = PgIdentifier.fromParts(schema, table);\n }\n const opclassSuffix = opclass ? ` ${opclass}` : \"\";\n return `${fullyQualifiedTable} using gin (${column}${opclassSuffix})`;\n }\n\n private toExpressionDefinition({\n schema,\n table,\n expression,\n }: {\n schema: PgIdentifier;\n table: PgIdentifier;\n expression: string;\n }): string {\n let fullyQualifiedTable: PgIdentifier;\n if (schema.toString() === \"public\") {\n fullyQualifiedTable = table;\n } else {\n fullyQualifiedTable = PgIdentifier.fromParts(schema, table);\n }\n return `${fullyQualifiedTable}((${expression}))`;\n }\n\n private groupGinCandidatesByColumn(candidates: RootIndexCandidate[]) {\n const groups = new Map<\n string,\n {\n schema: string;\n table: string;\n column: string;\n operators: JsonbOperator[];\n }\n >();\n for (const c of candidates) {\n if (!c.jsonbOperator) continue;\n const key = `${c.schema}.${c.table}.${c.column}`;\n const existing = groups.get(key);\n if (existing) {\n if (!existing.operators.includes(c.jsonbOperator)) {\n existing.operators.push(c.jsonbOperator);\n }\n } else {\n groups.set(key, {\n schema: c.schema,\n table: c.table,\n column: c.column,\n operators: [c.jsonbOperator],\n });\n }\n }\n return groups;\n }\n\n private ginIndexAlreadyExists(\n table: string,\n column: string,\n ): FullSchemaIndex | undefined {\n return this.existingIndexes.find(\n (index) =>\n index.indexType === \"gin\" &&\n index.tableName.toString() === table &&\n index.keyColumns.some((c) => c.name.toString() === column),\n );\n }\n\n /**\n * Drop indexes that can be dropped. Ignore the ones that can't\n */\n private async dropExistingIndexes(tx: PostgresTransaction) {\n for (const index of this.existingIndexes) {\n if (!isIndexProbablyDroppable(index)) {\n continue;\n }\n const indexName = PgIdentifier.fromParts(\n index.schemaName,\n index.indexName,\n );\n await dropIndex(tx, indexName);\n }\n }\n\n private whereClause(c: RootIndexCandidate, col: Color, keyword: Color) {\n if (!c.where) {\n return \"\";\n }\n if (c.where.nulltest === \"IS_NULL\") {\n return `${col(`\"${c.column}\"`)} is ${keyword(\"null\")}`;\n }\n if (c.where.nulltest === \"IS_NOT_NULL\") {\n return `${col(`\"${c.column}\"`)} is not ${keyword(\"null\")}`;\n }\n return \"\";\n }\n\n private nullsOrder(s: SortContext) {\n if (!s.nulls) {\n return \"\";\n }\n switch (s.nulls) {\n case \"SORTBY_NULLS_FIRST\":\n return \"nulls first\";\n case \"SORTBY_NULLS_LAST\":\n return \"nulls last\";\n case \"SORTBY_NULLS_DEFAULT\":\n default:\n return \"\";\n }\n }\n\n private sortDirection(s: SortContext) {\n if (!s.dir) {\n return \"\";\n }\n switch (s.dir) {\n case \"SORTBY_DESC\":\n return \"desc\";\n case \"SORTBY_ASC\":\n return \"asc\";\n case \"SORTBY_DEFAULT\":\n // god help us if we ever run into this\n case \"SORTBY_USING\":\n default:\n return \"\";\n }\n }\n\n async testQueryWithStats(\n builder: PostgresQueryBuilder,\n f?: (tx: PostgresTransaction) => Promise<void>,\n options?: { params?: unknown[]; genericPlan?: boolean },\n ): Promise<{ Plan: PostgresExplainStage }> {\n try {\n await this.db.transaction(async (tx) => {\n await f?.(tx);\n await this.statistics.restoreStats(tx);\n const flags = [\"format json\"];\n if (options && !options.genericPlan) {\n flags.push(\"analyze\");\n if (this.config.trace) {\n // trace can only be used alongside analyze\n // since it depends on the results of the query execution\n flags.push(\"trace\");\n }\n } else {\n flags.push(\"generic_plan\");\n }\n const { commands, query } = builder\n .introspect()\n .explain(flags)\n .buildParts();\n // this is done in a separate step to prevent sending multiple commands when using parameters\n await tx.exec(commands);\n const result = await tx.exec<PostgresExplainResult>(\n query,\n options?.params,\n );\n const explain = result[0][\"QUERY PLAN\"][0];\n throw new RollbackError(explain);\n });\n } catch (error) {\n if (error instanceof RollbackError) {\n return error.value;\n }\n throw error;\n }\n throw new Error(\"Unreachable\");\n }\n\n private groupPotentialIndexColumnsByTable(indexes: RootIndexCandidate[]) {\n const tableColumns: Map<\n string,\n { schema: string; table: string; columns: RootIndexCandidate[] }\n > = new Map();\n for (const index of indexes) {\n const existing = tableColumns.get(`${index.schema}.${index.table}`);\n if (existing) {\n existing.columns.push(index);\n } else {\n tableColumns.set(`${index.schema}.${index.table}`, {\n table: index.table,\n schema: index.schema,\n columns: [index],\n });\n }\n }\n return tableColumns;\n }\n\n private findUsedIndexes(explain: Record<string, any>) {\n const newIndexes: Set<string> = new Set();\n const existingIndexes: Set<string> = new Set();\n const prefix = IndexOptimizer.prefix;\n walkExplain(explain, (stage) => {\n const indexName = stage[\"Index Name\"];\n if (indexName) {\n // Check for prefix at start or embedded (for hypertable chunk indexes like _hyper_1_1_chunk___qd_xxx)\n if (indexName.startsWith(prefix)) {\n newIndexes.add(indexName);\n } else if (indexName.includes(prefix)) {\n // Extract the actual index name from chunk-prefixed names (e.g., _hyper_1_1_chunk___qd_xxx -> __qd_xxx)\n const actualName = indexName.substring(indexName.indexOf(prefix));\n newIndexes.add(actualName);\n } else {\n existingIndexes.add(indexName);\n }\n }\n });\n return {\n newIndexes,\n existingIndexes,\n };\n }\n\n private replaceUsedIndexesWithDefinition(\n explain: Record<string, any>,\n triedIndexes: Map<string, IndexRecommendation>,\n ) {\n walkExplain(explain, (stage) => {\n const indexName = stage[\"Index Name\"];\n if (typeof indexName === \"string\") {\n const recommendation = triedIndexes.get(indexName);\n if (recommendation) {\n stage[\"Index Name\"] = recommendation.definition;\n }\n }\n });\n }\n}\n\nfunction walkExplain(explain: Record<string, any>, f: (stage: any) => void) {\n function go(plan: any) {\n f(plan);\n if (plan.Plans) {\n for (const p of plan.Plans) {\n go(p);\n }\n }\n }\n go(explain);\n}\n\nexport type OptimizeResult =\n | {\n kind: \"ok\";\n baseExplainPlan: PostgresExplainStage;\n baseCost: number;\n finalCost: number;\n newIndexes: Set<string>;\n existingIndexes: Set<string>;\n triedIndexes: Map<string, IndexRecommendation>;\n explainPlan: PostgresExplainStage;\n }\n | {\n kind: \"zero_cost_plan\";\n explainPlan: PostgresExplainStage;\n };\n\nclass RollbackError<T> {\n constructor(public readonly value?: T) {}\n}\n\nexport type RootIndexCandidate = {\n schema: string;\n table: string;\n column: string;\n sort?: SortContext;\n where?: { nulltest?: NullTestType };\n jsonbOperator?: JsonbOperator;\n jsonbExtraction?: string;\n};\n\nexport type PermutedIndexCandidate = {\n schema: string;\n table: string;\n columns: RootIndexCandidate[];\n // TODO: functional indexes\n where?: string;\n indexMethod?: \"btree\" | \"gin\";\n opclass?: string;\n};\n\nexport const PROCEED = Symbol(\"PROCEED\");\nexport const SKIP = Symbol(\"SKIP\");\n"],"mappings":"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"}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { PgIdentifier } from "../sql/pg-identifier.cjs";
|
|
4
|
-
import { PostgresQueryBuilder } from "../sql/builder.cjs";
|
|
5
4
|
import { Postgres, PostgresExplainStage, PostgresTransaction } from "../sql/database.cjs";
|
|
6
|
-
import {
|
|
5
|
+
import { FullSchemaIndex } from "../schema.cjs";
|
|
6
|
+
import { PostgresQueryBuilder } from "../sql/builder.cjs";
|
|
7
|
+
import { Statistics } from "./statistics.cjs";
|
|
7
8
|
import { JsonbOperator, SortContext } from "../sql/analyzer.cjs";
|
|
8
9
|
import { NullTestType } from "@pgsql/types";
|
|
9
10
|
|
|
@@ -22,7 +23,7 @@ declare class IndexOptimizer {
|
|
|
22
23
|
private existingIndexes;
|
|
23
24
|
private readonly config;
|
|
24
25
|
static prefix: string;
|
|
25
|
-
constructor(db: Postgres, statistics: Statistics, existingIndexes:
|
|
26
|
+
constructor(db: Postgres, statistics: Statistics, existingIndexes: FullSchemaIndex[], config?: {
|
|
26
27
|
trace?: boolean;
|
|
27
28
|
debug?: boolean;
|
|
28
29
|
});
|
|
@@ -43,7 +44,7 @@ declare class IndexOptimizer {
|
|
|
43
44
|
* optimizer.transformIndexes(indexes => [...indexes, newIndex])
|
|
44
45
|
* ```
|
|
45
46
|
*/
|
|
46
|
-
transformIndexes(f: (indexes:
|
|
47
|
+
transformIndexes(f: (indexes: FullSchemaIndex[]) => FullSchemaIndex[]): this;
|
|
47
48
|
/**
|
|
48
49
|
* Postgres has a limit of 63 characters for index names.
|
|
49
50
|
* So we use this to make sure we don't derive it from a list of columns that can
|
|
@@ -112,5 +113,5 @@ type PermutedIndexCandidate = {
|
|
|
112
113
|
declare const PROCEED: unique symbol;
|
|
113
114
|
declare const SKIP: unique symbol;
|
|
114
115
|
//#endregion
|
|
115
|
-
export { IndexIdentifier, IndexOptimizer,
|
|
116
|
+
export { IndexIdentifier, IndexOptimizer, IndexToCreate, OptimizeResult, PROCEED, PermutedIndexCandidate, RootIndexCandidate, SKIP };
|
|
116
117
|
//# sourceMappingURL=genalgo.d.cts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"genalgo.d.cts","names":[],"sources":["../../src/optimizer/genalgo.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"genalgo.d.cts","names":[],"sources":["../../src/optimizer/genalgo.ts"],"mappings":";;;;;;;;;;;KAkBY,eAAA;AAAA,KAEP,mBAAA,GAAsB,sBAAA;EACzB,UAAA,EAAY,eAAA;AAAA;AAAA,KAKF,aAAA,GAAgB,sBAAA;EAC1B,IAAA,EAAM,YAAA;EACN,UAAA,EAAY,eAAA;AAAA;AAAA,cAGD,cAAA;EAAA,iBAIQ,EAAA;EAAA,iBACA,UAAA;EAAA,QACT,eAAA;EAAA,iBACS,MAAA;EAAA,OANZ,MAAA;cAGY,EAAA,EAAI,QAAA,EACJ,UAAA,EAAY,UAAA,EACrB,eAAA,EAAiB,eAAA,IACR,MAAA;IACf,KAAA;IACA,KAAA;EAAA;EAIE,GAAA,CACJ,OAAA,EAAS,oBAAA,EACT,OAAA,EAAS,kBAAA,IACT,WAAA,IAAe,EAAA,EAAI,mBAAA,KAAwB,OAAA,SAC1C,OAAA,CAAQ,cAAA;EAmFL,iBAAA,CAAkB,OAAA,EAAS,oBAAA,GAAoB,OAAA;UAiYlC,oBAAA;EAAA;EAxeQ;;;;;;;;;AAG7B;;;;EAuHE,gBAAA,CAAiB,CAAA,GAAI,OAAA,EAAS,eAAA,OAAsB,eAAA;EAjHzB;;;;;EAAA,QA4HnB,SAAA;EAAA,QAOA,kBAAA;EArCyB;;;EAAA,QAiFzB,eAAA;EAAA,QAiHA,YAAA;EAAA,QA6CA,eAAA;EAAA,QAqBA,sBAAA;EAAA,QAkBA,0BAAA;EAAA,QA8BA,qBAAA;EA6EL;;;EAAA,QA9DW,mBAAA;EAAA,QAaN,WAAA;EAAA,QAaA,UAAA;EAAA,QAeA,aAAA;EAiBF,kBAAA,CACJ,OAAA,EAAS,oBAAA,EACT,CAAA,IAAK,EAAA,EAAI,mBAAA,KAAwB,OAAA,QACjC,OAAA;IAAY,MAAA;IAAoB,WAAA;EAAA,IAC/B,OAAA;IAAU,IAAA,EAAM,oBAAA;EAAA;EAAA,QAsCX,iCAAA;EAAA,QAoBA,eAAA;EAAA,QAyBA,gCAAA;AAAA;AAAA,KA4BE,cAAA;EAEN,IAAA;EACA,eAAA,EAAiB,oBAAA;EACjB,QAAA;EACA,SAAA;EACA,UAAA,EAAY,GAAA;EACZ,eAAA,EAAiB,GAAA;EACjB,YAAA,EAAc,GAAA,SAAY,mBAAA;EAC1B,WAAA,EAAa,oBAAA;AAAA;EAGb,IAAA;EACA,WAAA,EAAa,oBAAA;AAAA;AAAA,KAOP,kBAAA;EACV,MAAA;EACA,KAAA;EACA,MAAA;EACA,IAAA,GAAO,WAAA;EACP,KAAA;IAAU,QAAA,GAAW,YAAA;EAAA;EACrB,aAAA,GAAgB,aAAA;EAChB,eAAA;AAAA;AAAA,KAGU,sBAAA;EACV,MAAA;EACA,KAAA;EACA,OAAA,EAAS,kBAAA;EAET,KAAA;EACA,WAAA;EACA,OAAA;AAAA;AAAA,cAGW,OAAA;AAAA,cACA,IAAA"}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { PgIdentifier } from "../sql/pg-identifier.mjs";
|
|
4
|
-
import { PostgresQueryBuilder } from "../sql/builder.mjs";
|
|
5
4
|
import { Postgres, PostgresExplainStage, PostgresTransaction } from "../sql/database.mjs";
|
|
6
|
-
import {
|
|
5
|
+
import { FullSchemaIndex } from "../schema.mjs";
|
|
6
|
+
import { PostgresQueryBuilder } from "../sql/builder.mjs";
|
|
7
|
+
import { Statistics } from "./statistics.mjs";
|
|
7
8
|
import { JsonbOperator, SortContext } from "../sql/analyzer.mjs";
|
|
8
9
|
import { NullTestType } from "@pgsql/types";
|
|
9
10
|
|
|
@@ -22,7 +23,7 @@ declare class IndexOptimizer {
|
|
|
22
23
|
private existingIndexes;
|
|
23
24
|
private readonly config;
|
|
24
25
|
static prefix: string;
|
|
25
|
-
constructor(db: Postgres, statistics: Statistics, existingIndexes:
|
|
26
|
+
constructor(db: Postgres, statistics: Statistics, existingIndexes: FullSchemaIndex[], config?: {
|
|
26
27
|
trace?: boolean;
|
|
27
28
|
debug?: boolean;
|
|
28
29
|
});
|
|
@@ -43,7 +44,7 @@ declare class IndexOptimizer {
|
|
|
43
44
|
* optimizer.transformIndexes(indexes => [...indexes, newIndex])
|
|
44
45
|
* ```
|
|
45
46
|
*/
|
|
46
|
-
transformIndexes(f: (indexes:
|
|
47
|
+
transformIndexes(f: (indexes: FullSchemaIndex[]) => FullSchemaIndex[]): this;
|
|
47
48
|
/**
|
|
48
49
|
* Postgres has a limit of 63 characters for index names.
|
|
49
50
|
* So we use this to make sure we don't derive it from a list of columns that can
|
|
@@ -112,5 +113,5 @@ type PermutedIndexCandidate = {
|
|
|
112
113
|
declare const PROCEED: unique symbol;
|
|
113
114
|
declare const SKIP: unique symbol;
|
|
114
115
|
//#endregion
|
|
115
|
-
export { IndexIdentifier, IndexOptimizer,
|
|
116
|
+
export { IndexIdentifier, IndexOptimizer, IndexToCreate, OptimizeResult, PROCEED, PermutedIndexCandidate, RootIndexCandidate, SKIP };
|
|
116
117
|
//# sourceMappingURL=genalgo.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"genalgo.d.mts","names":[],"sources":["../../src/optimizer/genalgo.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"genalgo.d.mts","names":[],"sources":["../../src/optimizer/genalgo.ts"],"mappings":";;;;;;;;;;;KAkBY,eAAA;AAAA,KAEP,mBAAA,GAAsB,sBAAA;EACzB,UAAA,EAAY,eAAA;AAAA;AAAA,KAKF,aAAA,GAAgB,sBAAA;EAC1B,IAAA,EAAM,YAAA;EACN,UAAA,EAAY,eAAA;AAAA;AAAA,cAGD,cAAA;EAAA,iBAIQ,EAAA;EAAA,iBACA,UAAA;EAAA,QACT,eAAA;EAAA,iBACS,MAAA;EAAA,OANZ,MAAA;cAGY,EAAA,EAAI,QAAA,EACJ,UAAA,EAAY,UAAA,EACrB,eAAA,EAAiB,eAAA,IACR,MAAA;IACf,KAAA;IACA,KAAA;EAAA;EAIE,GAAA,CACJ,OAAA,EAAS,oBAAA,EACT,OAAA,EAAS,kBAAA,IACT,WAAA,IAAe,EAAA,EAAI,mBAAA,KAAwB,OAAA,SAC1C,OAAA,CAAQ,cAAA;EAmFL,iBAAA,CAAkB,OAAA,EAAS,oBAAA,GAAoB,OAAA;UAiYlC,oBAAA;EAAA;EAxeQ;;;;;;;;;AAG7B;;;;EAuHE,gBAAA,CAAiB,CAAA,GAAI,OAAA,EAAS,eAAA,OAAsB,eAAA;EAjHzB;;;;;EAAA,QA4HnB,SAAA;EAAA,QAOA,kBAAA;EArCyB;;;EAAA,QAiFzB,eAAA;EAAA,QAiHA,YAAA;EAAA,QA6CA,eAAA;EAAA,QAqBA,sBAAA;EAAA,QAkBA,0BAAA;EAAA,QA8BA,qBAAA;EA6EL;;;EAAA,QA9DW,mBAAA;EAAA,QAaN,WAAA;EAAA,QAaA,UAAA;EAAA,QAeA,aAAA;EAiBF,kBAAA,CACJ,OAAA,EAAS,oBAAA,EACT,CAAA,IAAK,EAAA,EAAI,mBAAA,KAAwB,OAAA,QACjC,OAAA;IAAY,MAAA;IAAoB,WAAA;EAAA,IAC/B,OAAA;IAAU,IAAA,EAAM,oBAAA;EAAA;EAAA,QAsCX,iCAAA;EAAA,QAoBA,eAAA;EAAA,QAyBA,gCAAA;AAAA;AAAA,KA4BE,cAAA;EAEN,IAAA;EACA,eAAA,EAAiB,oBAAA;EACjB,QAAA;EACA,SAAA;EACA,UAAA,EAAY,GAAA;EACZ,eAAA,EAAiB,GAAA;EACjB,YAAA,EAAc,GAAA,SAAY,mBAAA;EAC1B,WAAA,EAAa,oBAAA;AAAA;EAGb,IAAA;EACA,WAAA,EAAa,oBAAA;AAAA;AAAA,KAOP,kBAAA;EACV,MAAA;EACA,KAAA;EACA,MAAA;EACA,IAAA,GAAO,WAAA;EACP,KAAA;IAAU,QAAA,GAAW,YAAA;EAAA;EACrB,aAAA,GAAgB,aAAA;EAChB,eAAA;AAAA;AAAA,KAGU,sBAAA;EACV,MAAA;EACA,KAAA;EACA,OAAA,EAAS,kBAAA;EAET,KAAA;EACA,WAAA;EACA,OAAA;AAAA;AAAA,cAGW,OAAA;AAAA,cACA,IAAA"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { _defineProperty } from "../_virtual/_@oxc-project_runtime@0.126.0/helpers/defineProperty.mjs";
|
|
3
|
+
import { PgIdentifier } from "../sql/pg-identifier.mjs";
|
|
3
4
|
import { dropIndex } from "../sql/database.mjs";
|
|
4
5
|
import { isIndexProbablyDroppable } from "../sql/indexes.mjs";
|
|
5
6
|
import { PostgresQueryBuilder } from "../sql/builder.mjs";
|
|
6
|
-
import { PgIdentifier } from "../sql/pg-identifier.mjs";
|
|
7
7
|
import { permutationsWithDescendingLength } from "../sql/permutations.mjs";
|
|
8
8
|
import { blue, gray, green, magenta, red, yellow } from "colorette";
|
|
9
9
|
//#region src/optimizer/genalgo.ts
|
|
@@ -99,8 +99,8 @@ var IndexOptimizer = class IndexOptimizer {
|
|
|
99
99
|
return PgIdentifier.fromString(indexName);
|
|
100
100
|
}
|
|
101
101
|
indexAlreadyExists(table, columns) {
|
|
102
|
-
return this.existingIndexes.find((index) => index.
|
|
103
|
-
if (columns[i].column !== c.name) return false;
|
|
102
|
+
return this.existingIndexes.find((index) => index.indexType === "btree" && index.tableName.toString() === table && index.keyColumns.length === columns.length && index.keyColumns.every((c, i) => {
|
|
103
|
+
if (columns[i].column !== c.name.toString()) return false;
|
|
104
104
|
if (columns[i].where) return false;
|
|
105
105
|
if (columns[i].sort) switch (columns[i].sort.dir) {
|
|
106
106
|
case "SORTBY_DEFAULT":
|
|
@@ -250,7 +250,7 @@ var IndexOptimizer = class IndexOptimizer {
|
|
|
250
250
|
return groups;
|
|
251
251
|
}
|
|
252
252
|
ginIndexAlreadyExists(table, column) {
|
|
253
|
-
return this.existingIndexes.find((index) => index.
|
|
253
|
+
return this.existingIndexes.find((index) => index.indexType === "gin" && index.tableName.toString() === table && index.keyColumns.some((c) => c.name.toString() === column));
|
|
254
254
|
}
|
|
255
255
|
/**
|
|
256
256
|
* Drop indexes that can be dropped. Ignore the ones that can't
|
|
@@ -258,7 +258,7 @@ var IndexOptimizer = class IndexOptimizer {
|
|
|
258
258
|
async dropExistingIndexes(tx) {
|
|
259
259
|
for (const index of this.existingIndexes) {
|
|
260
260
|
if (!isIndexProbablyDroppable(index)) continue;
|
|
261
|
-
await dropIndex(tx, PgIdentifier.fromParts(index.
|
|
261
|
+
await dropIndex(tx, PgIdentifier.fromParts(index.schemaName, index.indexName));
|
|
262
262
|
}
|
|
263
263
|
}
|
|
264
264
|
whereClause(c, col, keyword) {
|