diff --git a/package-lock.json b/package-lock.json index f8c34b1..18aec9d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,7 @@ "@libpg-query/parser": "^17.6.3", "@opentelemetry/api": "^1.9.0", "@pgsql/types": "^17.6.2", - "@query-doctor/core": "^0.7.2", + "@query-doctor/core": "^0.8.0", "async-sema": "^3.1.1", "dedent": "^1.7.1", "fast-csv": "^5.0.5", @@ -1124,9 +1124,9 @@ "license": "BSD-3-Clause" }, "node_modules/@query-doctor/core": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/@query-doctor/core/-/core-0.7.2.tgz", - "integrity": "sha512-Bijy5s5D2C7bsc479pWcIIY7oZdw3hTV32qYOOqSu0WBsFLplkHKvYzwYAIcThQawBsVFlXH6p1lxRLaaJdObA==", + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@query-doctor/core/-/core-0.8.0.tgz", + "integrity": "sha512-NBljItPZe+410ErC7UsJbUGD08ZIPb9ZrMYZLWfDWssFwucHKtvNICCzSF38EHYD/f9i9FZpPalFb2o9WGeiyQ==", "dependencies": { "@pgsql/types": "^17.6.2", "colorette": "^2.0.20", diff --git a/package.json b/package.json index fb6e18e..630c4b0 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "@libpg-query/parser": "^17.6.3", "@opentelemetry/api": "^1.9.0", "@pgsql/types": "^17.6.2", - "@query-doctor/core": "^0.7.2", + "@query-doctor/core": "^0.8.0", "async-sema": "^3.1.1", "dedent": "^1.7.1", "fast-csv": "^5.0.5", diff --git a/src/remote/gin-indexes.test.ts b/src/remote/gin-indexes.test.ts index 97ba728..6f62b29 100644 --- a/src/remote/gin-indexes.test.ts +++ b/src/remote/gin-indexes.test.ts @@ -86,8 +86,8 @@ test("GIN: basic @> containment recommends GIN with jsonb_path_ops", async () => reltuples: 100_000, relallvisible: 1, columns: [ - { columnName: "id", stats: null }, - { columnName: "data", stats: null }, + { columnName: "id", stats: null, attlen: 8 }, + { columnName: "data", stats: null, attlen: null }, ], indexes: [], }], @@ -171,8 +171,8 @@ test("GIN: key existence (?) recommends GIN with default jsonb_ops", async () => reltuples: 100_000, relallvisible: 1, columns: [ - { columnName: "id", stats: null }, - { columnName: "payload", stats: null }, + { columnName: "id", stats: null, attlen: 8 }, + { columnName: "payload", stats: null, attlen: null }, ], indexes: [], }], @@ -258,8 +258,8 @@ test("GIN: any-key existence (?|) recommends GIN with default jsonb_ops", async reltuples: 100_000, relallvisible: 1, columns: [ - { columnName: "id", stats: null }, - { columnName: "payload", stats: null }, + { columnName: "id", stats: null, attlen: 8 }, + { columnName: "payload", stats: null, attlen: null }, ], indexes: [], }], @@ -342,8 +342,8 @@ test("GIN: all-keys existence (?&) recommends GIN with default jsonb_ops", async reltuples: 100_000, relallvisible: 1, columns: [ - { columnName: "id", stats: null }, - { columnName: "payload", stats: null }, + { columnName: "id", stats: null, attlen: 8 }, + { columnName: "payload", stats: null, attlen: null }, ], indexes: [], }], @@ -428,9 +428,9 @@ test("GIN: mixed JSONB and regular column produces both GIN and B-tree", async ( reltuples: 100_000, relallvisible: 1, columns: [ - { columnName: "id", stats: null }, - { columnName: "data", stats: null }, - { columnName: "price", stats: null }, + { columnName: "id", stats: null, attlen: 8 }, + { columnName: "data", stats: null, attlen: null }, + { columnName: "price", stats: null, attlen: 8 }, ], indexes: [], }], @@ -536,8 +536,8 @@ test("GIN: mixed @> and ? on same column escalates to jsonb_ops", async () => { reltuples: 100_000, relallvisible: 1, columns: [ - { columnName: "id", stats: null }, - { columnName: "data", stats: null }, + { columnName: "id", stats: null, attlen: 8 }, + { columnName: "data", stats: null, attlen: null }, ], indexes: [], }], @@ -626,8 +626,8 @@ test("GIN: table alias resolves to correct table for GIN recommendation", async reltuples: 100_000, relallvisible: 1, columns: [ - { columnName: "id", stats: null }, - { columnName: "data", stats: null }, + { columnName: "id", stats: null, attlen: 8 }, + { columnName: "data", stats: null, attlen: null }, ], indexes: [], }], @@ -710,8 +710,8 @@ test("GIN: non-JSONB query produces B-tree only, no GIN", async () => { reltuples: 100_000, relallvisible: 1, columns: [ - { columnName: "id", stats: null }, - { columnName: "name", stats: null }, + { columnName: "id", stats: null, attlen: 8 }, + { columnName: "name", stats: null, attlen: 20 }, ], indexes: [], }], @@ -802,14 +802,17 @@ test("GIN: existing GIN index prevents duplicate recommendation", async () => { reltuples: 100_000, relallvisible: 1, columns: [ - { columnName: "id", stats: null }, - { columnName: "data", stats: null }, + { columnName: "id", stats: null, attlen: 8 }, + { columnName: "data", stats: null, attlen: null }, ], indexes: [{ indexName: "idx_products_data", relpages: 50, reltuples: 100_000, relallvisible: 1, + amname: "gin", + columns: [{ attlen: null }], + fillfactor: 0.9 }], }], }); diff --git a/src/remote/query-optimizer.test.ts b/src/remote/query-optimizer.test.ts index 0732834..a71d862 100644 --- a/src/remote/query-optimizer.test.ts +++ b/src/remote/query-optimizer.test.ts @@ -84,15 +84,22 @@ test("controller syncs correctly", async () => { columns: [{ columnName: "a", stats: null, + attlen: null, }, { columnName: "b", stats: null, + attlen: null, }], indexes: [{ indexName: "testing_index", relpages: 2, reltuples: 10000, relallvisible: 1, + amname: "btree", + columns: [{ + attlen: 8 + }], + fillfactor: 0.9, }], }], }); @@ -157,15 +164,20 @@ test("controller syncs correctly", async () => { columns: [{ columnName: "a", stats: null, + attlen: 8, }, { columnName: "b", stats: null, + attlen: 8, }], indexes: [{ indexName: "testing_index", relpages: 2, reltuples: 10000, relallvisible: 1, + amname: "btree", + columns: [{ attlen: 8 }], + fillfactor: 0.9, }], }], }); @@ -223,14 +235,17 @@ test("disabling an index removes it from indexesUsed and recommends it", async ( reltuples: 10_000_000, relallvisible: 1, columns: [ - { columnName: "id", stats: null }, - { columnName: "email", stats: null }, + { columnName: "id", stats: null, attlen: null }, + { columnName: "email", stats: null, attlen: null }, ], indexes: [{ indexName: "users_email_idx", relpages: 100, reltuples: 10_000_000, relallvisible: 1, + amname: "btree", + fillfactor: 0.9, + columns: [{ attlen: null }], }], }], }; @@ -392,11 +407,11 @@ test("hypertable optimization includes index recommendations", async () => { reltuples: 100_000, relallvisible: 1, columns: [ - { columnName: "time", stats: null }, - { columnName: "sensor_id", stats: null }, - { columnName: "aqi", stats: null }, - { columnName: "pm25_ugm3", stats: null }, - { columnName: "pm10_ugm3", stats: null }, + { columnName: "time", stats: null, attlen: null }, + { columnName: "sensor_id", stats: null, attlen: null }, + { columnName: "aqi", stats: null, attlen: null }, + { columnName: "pm25_ugm3", stats: null, attlen: null }, + { columnName: "pm10_ugm3", stats: null, attlen: null }, ], indexes: [], }, @@ -407,9 +422,9 @@ test("hypertable optimization includes index recommendations", async () => { reltuples: 100, relallvisible: 1, columns: [ - { columnName: "id", stats: null }, - { columnName: "name", stats: null }, - { columnName: "location_type", stats: null }, + { columnName: "id", stats: null, attlen: null }, + { columnName: "name", stats: null, attlen: null }, + { columnName: "location_type", stats: null, attlen: null }, ], indexes: [], }, @@ -490,8 +505,8 @@ test("timed out queries are retried with exponential backoff up to maxRetries", reltuples: 1_000_000, relallvisible: 1, columns: [ - { columnName: "id", stats: null }, - { columnName: "data", stats: null }, + { columnName: "id", stats: null, attlen: null }, + { columnName: "data", stats: null, attlen: null }, ], indexes: [], }], @@ -577,15 +592,18 @@ test("optimizer does not treat ASC index as duplicate of DESC candidate", async reltuples: 100_000, relallvisible: 1, columns: [ - { columnName: "id", stats: null }, - { columnName: "created_at", stats: null }, - { columnName: "status", stats: null }, + { columnName: "id", stats: null, attlen: null }, + { columnName: "created_at", stats: null, attlen: null }, + { columnName: "status", stats: null, attlen: null }, ], indexes: [{ indexName: "orders_multi_asc", relpages: 50, reltuples: 100_000, relallvisible: 1, + amname: "btree", + fillfactor: 0.9, + columns: [{ attlen: null }, { attlen: null }], }], }], }); diff --git a/src/remote/query-optimizer.ts b/src/remote/query-optimizer.ts index bc0fc44..8a0dee7 100644 --- a/src/remote/query-optimizer.ts +++ b/src/remote/query-optimizer.ts @@ -43,7 +43,6 @@ export class QueryOptimizer extends EventEmitter { private static readonly MAX_CONCURRENCY = 1; private static readonly defaultStatistics: StatisticsMode = { kind: "fromAssumption", - relpages: 1, reltuples: 10_000, }; private readonly queries = new Map(); diff --git a/src/reporters/github/github.test.ts b/src/reporters/github/github.test.ts index 07fa0b5..79525bb 100644 --- a/src/reporters/github/github.test.ts +++ b/src/reporters/github/github.test.ts @@ -107,7 +107,7 @@ function makeRecommendation(overrides: { function makeContext(overrides: Partial = {}): ReportContext { return { - statisticsMode: { kind: "fromAssumption", reltuples: 10000, relpages: 1000 }, + statisticsMode: { kind: "fromAssumption", reltuples: 10000 }, recommendations: [], queriesPastThreshold: [], queryStats: { total: 28, matched: 10, optimized: 2, errored: 0 }, diff --git a/src/runner.ts b/src/runner.ts index 337f829..bc44d19 100644 --- a/src/runner.ts +++ b/src/runner.ts @@ -20,7 +20,7 @@ import { ConnectionManager } from "./sync/connection-manager.ts"; import { RecentQuery } from "./sql/recent-query.ts"; import { QueryHash } from "./sql/recent-query.ts"; import type { OptimizedQuery } from "./sql/recent-query.ts"; -import { ExportedStats, StatisticsMode } from "@query-doctor/core"; +import { ExportedStats } from "@query-doctor/core"; import { readFile } from "node:fs/promises"; export class Runner { @@ -80,7 +80,6 @@ export class Runner { stats: { kind: "fromAssumption", reltuples: 10_000_000, - relpages: 200_000 } } }