@query-doctor/core 0.8.3 → 0.8.6-rc.1

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.
@@ -141,6 +141,112 @@ function estimateIndexRelpages(reltuples, columns, fillfactor, amname, tableRelp
141
141
  const keyWidth = columns.reduce((sum, col) => sum + estimateStawidth(col) + 16, 0);
142
142
  return Math.ceil(reltuples * keyWidth / DEFAULT_PAGE_SIZE / fillfactor);
143
143
  }
144
+ const DUMP_STATS_SQL = dedent`
145
+ WITH table_columns AS (
146
+ SELECT
147
+ cl.relname,
148
+ n.nspname,
149
+ cl.reltuples,
150
+ cl.relpages,
151
+ cl.relallvisible,
152
+ -- cl.relallfrozen,
153
+ json_agg(
154
+ json_build_object(
155
+ 'columnName', a.attname,
156
+ 'attlen', CASE WHEN a.attlen > 0 THEN a.attlen ELSE NULL END,
157
+ 'dataType', t.typname,
158
+ 'stats', (
159
+ SELECT json_build_object(
160
+ 'starelid', s.starelid,
161
+ 'staattnum', s.staattnum,
162
+ 'stanullfrac', s.stanullfrac,
163
+ 'stawidth', s.stawidth,
164
+ 'stadistinct', s.stadistinct,
165
+ 'stakind1', s.stakind1, 'staop1', s.staop1, 'stacoll1', s.stacoll1, 'stanumbers1', s.stanumbers1,
166
+ 'stakind2', s.stakind2, 'staop2', s.staop2, 'stacoll2', s.stacoll2, 'stanumbers2', s.stanumbers2,
167
+ 'stakind3', s.stakind3, 'staop3', s.staop3, 'stacoll3', s.stacoll3, 'stanumbers3', s.stanumbers3,
168
+ 'stakind4', s.stakind4, 'staop4', s.staop4, 'stacoll4', s.stacoll4, 'stanumbers4', s.stanumbers4,
169
+ 'stakind5', s.stakind5, 'staop5', s.staop5, 'stacoll5', s.stacoll5, 'stanumbers5', s.stanumbers5,
170
+ 'stavalues1', s.stavalues1,
171
+ 'stavalues2', s.stavalues2,
172
+ 'stavalues3', s.stavalues3,
173
+ 'stavalues4', s.stavalues4,
174
+ 'stavalues5', s.stavalues5
175
+ )
176
+ FROM pg_statistic s
177
+ WHERE s.starelid = a.attrelid AND s.staattnum = a.attnum
178
+ )
179
+ )
180
+ ORDER BY a.attnum
181
+ ) AS columns
182
+ FROM pg_class cl
183
+ JOIN pg_namespace n ON n.oid = cl.relnamespace
184
+ JOIN pg_attribute a ON a.attrelid = cl.oid AND a.attnum > 0 AND NOT a.attisdropped
185
+ JOIN pg_type t ON t.oid = a.atttypid
186
+ WHERE cl.relkind = 'r'
187
+ AND n.nspname NOT IN ('pg_catalog', 'information_schema', 'tiger', 'tiger_data', 'topology')
188
+ AND cl.relname NOT IN ('pg_stat_statements', 'pg_stat_statements_info')
189
+ GROUP BY cl.relname, n.nspname, cl.reltuples, cl.relpages, cl.relallvisible
190
+ ),
191
+ table_indexes AS (
192
+ SELECT
193
+ t.relname AS table_name,
194
+ json_agg(
195
+ json_build_object(
196
+ 'indexName', i.relname,
197
+ 'amname', am.amname,
198
+ 'reltuples', i.reltuples,
199
+ 'relpages', i.relpages,
200
+ 'relallvisible', i.relallvisible,
201
+ -- 'relallfrozen', i.relallfrozen,
202
+ 'fillfactor', COALESCE(
203
+ (
204
+ SELECT (regexp_match(opt, 'fillfactor=(\\d+)'))[1]::integer
205
+ FROM unnest(i.reloptions) AS opt
206
+ WHERE opt LIKE 'fillfactor=%'
207
+ LIMIT 1
208
+ ),
209
+ 90
210
+ ),
211
+ 'columns', COALESCE(
212
+ (
213
+ SELECT json_agg(json_build_object(
214
+ 'attlen', CASE WHEN a.attlen > 0 THEN a.attlen ELSE NULL END
215
+ ) ORDER BY col_pos.ord)
216
+ FROM unnest(ix.indkey) WITH ORDINALITY AS col_pos(attnum, ord)
217
+ JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = col_pos.attnum
218
+ WHERE col_pos.attnum > 0
219
+ ),
220
+ '[]'::json
221
+ )
222
+ )
223
+ ) AS indexes
224
+ FROM pg_class t
225
+ JOIN pg_index ix ON ix.indrelid = t.oid
226
+ JOIN pg_class i ON i.oid = ix.indexrelid
227
+ JOIN pg_am am ON am.oid = i.relam
228
+ JOIN pg_namespace n ON n.oid = t.relnamespace
229
+ WHERE t.relname NOT LIKE 'pg_%'
230
+ AND n.nspname <> 'information_schema'
231
+ AND n.nspname NOT IN ('tiger', 'tiger_data', 'topology')
232
+ GROUP BY t.relname
233
+ )
234
+ SELECT json_agg(
235
+ json_build_object(
236
+ 'tableName', tc.relname,
237
+ 'schemaName', tc.nspname,
238
+ 'reltuples', tc.reltuples,
239
+ 'relpages', tc.relpages,
240
+ 'relallvisible', tc.relallvisible,
241
+ -- 'relallfrozen', tc.relallfrozen,
242
+ 'columns', COALESCE(tc.columns, '[]'::json),
243
+ 'indexes', COALESCE(ti.indexes, '[]'::json)
244
+ )
245
+ )
246
+ FROM table_columns tc
247
+ LEFT JOIN table_indexes ti
248
+ ON ti.table_name = tc.relname
249
+ `;
144
250
  var Statistics = class Statistics {
145
251
  constructor(db, postgresVersion, ownMetadata, statsMode) {
146
252
  this.db = db;
@@ -313,7 +419,7 @@ var Statistics = class Statistics {
313
419
  }
314
420
  static async fromPostgres(db, statsMode) {
315
421
  const version = await db.serverNum();
316
- return new Statistics(db, version, await Statistics.dumpStats(db, version, "full"), statsMode);
422
+ return new Statistics(db, version, await Statistics.dumpStats(db, version), statsMode);
317
423
  }
318
424
  restoreStats(tx) {
319
425
  return this.restoreStats17(tx);
@@ -419,115 +525,9 @@ var Statistics = class Statistics {
419
525
  if (reltuplesUpdates.length !== this.computedStats.reltuples.length) console.error(`Did not update expected reltuples/relpages`);
420
526
  return warnings;
421
527
  }
422
- static async dumpStats(db, postgresVersion, kind) {
423
- const fullDump = kind === "full";
528
+ static async dumpStats(db, postgresVersion) {
424
529
  console.log(`dumping stats for postgres ${gray(postgresVersion)}`);
425
- const stats = await db.exec(`
426
- WITH table_columns AS (
427
- SELECT
428
- cl.relname,
429
- n.nspname,
430
- cl.reltuples,
431
- cl.relpages,
432
- cl.relallvisible,
433
- -- cl.relallfrozen,
434
- json_agg(
435
- json_build_object(
436
- 'columnName', a.attname,
437
- 'attlen', CASE WHEN a.attlen > 0 THEN a.attlen ELSE NULL END,
438
- 'dataType', t.typname,
439
- 'stats', (
440
- SELECT json_build_object(
441
- 'starelid', s.starelid,
442
- 'staattnum', s.staattnum,
443
- 'stanullfrac', s.stanullfrac,
444
- 'stawidth', s.stawidth,
445
- 'stadistinct', s.stadistinct,
446
- 'stakind1', s.stakind1, 'staop1', s.staop1, 'stacoll1', s.stacoll1, 'stanumbers1', s.stanumbers1,
447
- 'stakind2', s.stakind2, 'staop2', s.staop2, 'stacoll2', s.stacoll2, 'stanumbers2', s.stanumbers2,
448
- 'stakind3', s.stakind3, 'staop3', s.staop3, 'stacoll3', s.stacoll3, 'stanumbers3', s.stanumbers3,
449
- 'stakind4', s.stakind4, 'staop4', s.staop4, 'stacoll4', s.stacoll4, 'stanumbers4', s.stanumbers4,
450
- 'stakind5', s.stakind5, 'staop5', s.staop5, 'stacoll5', s.stacoll5, 'stanumbers5', s.stanumbers5,
451
- 'stavalues1', CASE WHEN $1 THEN s.stavalues1 ELSE NULL END,
452
- 'stavalues2', CASE WHEN $1 THEN s.stavalues2 ELSE NULL END,
453
- 'stavalues3', CASE WHEN $1 THEN s.stavalues3 ELSE NULL END,
454
- 'stavalues4', CASE WHEN $1 THEN s.stavalues4 ELSE NULL END,
455
- 'stavalues5', CASE WHEN $1 THEN s.stavalues5 ELSE NULL END
456
- )
457
- FROM pg_statistic s
458
- WHERE s.starelid = a.attrelid AND s.staattnum = a.attnum
459
- )
460
- )
461
- ORDER BY a.attnum
462
- ) AS columns
463
- FROM pg_class cl
464
- JOIN pg_namespace n ON n.oid = cl.relnamespace
465
- JOIN pg_attribute a ON a.attrelid = cl.oid AND a.attnum > 0 AND NOT a.attisdropped
466
- JOIN pg_type t ON t.oid = a.atttypid
467
- WHERE cl.relkind = 'r'
468
- AND n.nspname NOT IN ('pg_catalog', 'information_schema', 'tiger', 'tiger_data', 'topology')
469
- AND cl.relname NOT IN ('pg_stat_statements', 'pg_stat_statements_info')
470
- GROUP BY cl.relname, n.nspname, cl.reltuples, cl.relpages, cl.relallvisible
471
- ),
472
- table_indexes AS (
473
- SELECT
474
- t.relname AS table_name,
475
- json_agg(
476
- json_build_object(
477
- 'indexName', i.relname,
478
- 'amname', am.amname,
479
- 'reltuples', i.reltuples,
480
- 'relpages', i.relpages,
481
- 'relallvisible', i.relallvisible,
482
- -- 'relallfrozen', i.relallfrozen,
483
- 'fillfactor', COALESCE(
484
- (
485
- SELECT (regexp_match(opt, 'fillfactor=(\\d+)'))[1]::integer
486
- FROM unnest(i.reloptions) AS opt
487
- WHERE opt LIKE 'fillfactor=%'
488
- LIMIT 1
489
- ),
490
- 90
491
- ),
492
- 'columns', COALESCE(
493
- (
494
- SELECT json_agg(json_build_object(
495
- 'attlen', CASE WHEN a.attlen > 0 THEN a.attlen ELSE NULL END
496
- ) ORDER BY col_pos.ord)
497
- FROM unnest(ix.indkey) WITH ORDINALITY AS col_pos(attnum, ord)
498
- JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = col_pos.attnum
499
- WHERE col_pos.attnum > 0
500
- ),
501
- '[]'::json
502
- )
503
- )
504
- ) AS indexes
505
- FROM pg_class t
506
- JOIN pg_index ix ON ix.indrelid = t.oid
507
- JOIN pg_class i ON i.oid = ix.indexrelid
508
- JOIN pg_am am ON am.oid = i.relam
509
- JOIN pg_namespace n ON n.oid = t.relnamespace
510
- WHERE t.relname NOT LIKE 'pg_%'
511
- AND n.nspname <> 'information_schema'
512
- AND n.nspname NOT IN ('tiger', 'tiger_data', 'topology')
513
- GROUP BY t.relname
514
- )
515
- SELECT json_agg(
516
- json_build_object(
517
- 'tableName', tc.relname,
518
- 'schemaName', tc.nspname,
519
- 'reltuples', tc.reltuples,
520
- 'relpages', tc.relpages,
521
- 'relallvisible', tc.relallvisible,
522
- -- 'relallfrozen', tc.relallfrozen,
523
- 'columns', COALESCE(tc.columns, '[]'::json),
524
- 'indexes', COALESCE(ti.indexes, '[]'::json)
525
- )
526
- )
527
- FROM table_columns tc
528
- LEFT JOIN table_indexes ti
529
- ON ti.table_name = tc.relname;
530
- `, [fullDump]);
530
+ const stats = await db.exec(DUMP_STATS_SQL);
531
531
  return z.array(ExportedStats).parse(stats[0].json_agg);
532
532
  }
533
533
  /**
@@ -779,6 +779,6 @@ _defineProperty(Statistics, "columnStatsSQL", dedent`
779
779
  )
780
780
  select * from updated union all (select * from inserted); -- @qd_introspection`);
781
781
  //#endregion
782
- export { ComputedColumnStats, ComputedReltuples, ComputedStats, ExportedStats, ExportedStatsColumns, ExportedStatsIndex, ExportedStatsStatistics, ExportedStatsV1, Statistics, StatisticsMode, StatisticsSource };
782
+ export { ComputedColumnStats, ComputedReltuples, ComputedStats, DUMP_STATS_SQL, ExportedStats, ExportedStatsColumns, ExportedStatsIndex, ExportedStatsStatistics, ExportedStatsV1, Statistics, StatisticsMode, StatisticsSource };
783
783
 
784
784
  //# sourceMappingURL=statistics.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"statistics.mjs","names":[],"sources":["../../src/optimizer/statistics.ts"],"sourcesContent":["import { gray } from \"colorette\";\nimport dedent from \"dedent\";\nimport { z } from \"zod\";\nimport type {\n Postgres,\n PostgresTransaction,\n PostgresVersion,\n} from \"../sql/database.ts\";\n\ntype StaValueKind = \"real\" | \"text\" | \"boolean\" | null;\n\nexport type Path = string;\n\nexport const StatisticsSource = z.union([\n z.object({\n kind: z.literal(\"path\"),\n path: z.string().min(1),\n }),\n z.object({\n kind: z.literal(\"inline\"),\n }),\n]);\n\nexport const ExportedStatsStatistics = z.object({\n stawidth: z.number(),\n stainherit: z.boolean().default(false),\n // 0 representing unknown\n stadistinct: z.number(),\n // this has no \"nullable\" state\n stanullfrac: z.number(),\n stakind1: z.number().min(0),\n stakind2: z.number().min(0),\n stakind3: z.number().min(0),\n stakind4: z.number().min(0),\n stakind5: z.number().min(0),\n staop1: z.string(),\n staop2: z.string(),\n staop3: z.string(),\n staop4: z.string(),\n staop5: z.string(),\n stacoll1: z.string(),\n stacoll2: z.string(),\n stacoll3: z.string(),\n stacoll4: z.string(),\n stacoll5: z.string(),\n stanumbers1: z.array(z.number()).nullable(),\n stanumbers2: z.array(z.number()).nullable(),\n stanumbers3: z.array(z.number()).nullable(),\n stanumbers4: z.array(z.number()).nullable(),\n stanumbers5: z.array(z.number()).nullable(),\n // theoretically... this could only be strings and numbers\n // but we don't have a crystal ball\n stavalues1: z.array(z.any()).nullable(),\n stavalues2: z.array(z.any()).nullable(),\n stavalues3: z.array(z.any()).nullable(),\n stavalues4: z.array(z.any()).nullable(),\n stavalues5: z.array(z.any()).nullable(),\n});\n\nexport const ExportedStatsColumns = z.object({\n columnName: z.string(),\n attlen: z.number().nullable(),\n dataType: z.string().optional(),\n stats: ExportedStatsStatistics.nullable(),\n});\n\nexport const ExportedStatsIndex = z.object({\n indexName: z.string(),\n amname: z.string().default(\"btree\"),\n relpages: z.number(),\n reltuples: z.number(),\n relallvisible: z.number(),\n relallfrozen: z.number().optional(),\n fillfactor: z.number().default(90),\n columns: z.array(z.object({ attlen: z.number().nullable() })).default([]),\n});\n\n// This should match the output of the `_qd_dump_stats` function in the analyzer README.md\n// Need to make sure this is versioned to accept ALL potential outputs from every version of\n// dump functions we make public\nexport const ExportedStatsV1 = z.object({\n tableName: z.string(),\n schemaName: z.string(),\n // can be negative\n relpages: z.number(),\n // can be negative\n reltuples: z.number(),\n relallvisible: z.number(),\n // only postgres 18+\n relallfrozen: z.number().optional(),\n columns: z.array(ExportedStatsColumns).default([]),\n indexes: z.array(ExportedStatsIndex),\n});\n\nexport const ExportedStats = z.union([ExportedStatsV1]);\n\nexport type ExportedStats = z.infer<typeof ExportedStats>;\n\nexport const StatisticsMode = z.discriminatedUnion(\"kind\", [\n z.object({\n kind: z.literal(\"fromAssumption\"),\n reltuples: z.number().min(0),\n }),\n z.object({\n kind: z.literal(\"fromStatisticsExport\"),\n stats: z.array(ExportedStats),\n source: StatisticsSource,\n }),\n]);\n\nexport type StatisticsMode = z.infer<typeof StatisticsMode>;\n\nexport const ComputedColumnStats = z.object({\n schema_name: z.string(),\n table_name: z.string(),\n column_name: z.string(),\n data_type: z.string().optional(),\n stainherit: z.boolean(),\n stanullfrac: z.number(),\n stawidth: z.number(),\n stadistinct: z.number(),\n stakind1: z.number(),\n stakind2: z.number(),\n stakind3: z.number(),\n stakind4: z.number(),\n stakind5: z.number(),\n staop1: z.string(),\n staop2: z.string(),\n staop3: z.string(),\n staop4: z.string(),\n staop5: z.string(),\n stacoll1: z.string(),\n stacoll2: z.string(),\n stacoll3: z.string(),\n stacoll4: z.string(),\n stacoll5: z.string(),\n stanumbers1: z.array(z.number()).nullable(),\n stanumbers2: z.array(z.number()).nullable(),\n stanumbers3: z.array(z.number()).nullable(),\n stanumbers4: z.array(z.number()).nullable(),\n stanumbers5: z.array(z.number()).nullable(),\n stavalues1: z.array(z.any()).nullable(),\n stavalues2: z.array(z.any()).nullable(),\n stavalues3: z.array(z.any()).nullable(),\n stavalues4: z.array(z.any()).nullable(),\n stavalues5: z.array(z.any()).nullable(),\n _value_type1: z.string().nullable(),\n _value_type2: z.string().nullable(),\n _value_type3: z.string().nullable(),\n _value_type4: z.string().nullable(),\n _value_type5: z.string().nullable(),\n});\n\nexport type ComputedColumnStats = z.infer<typeof ComputedColumnStats>;\n\nexport const ComputedReltuples = z.object({\n relname: z.string(),\n schema_name: z.string(),\n reltuples: z.number(),\n relpages: z.number(),\n relallvisible: z.number(),\n relallfrozen: z.number().optional(),\n});\n\nexport type ComputedReltuples = z.infer<typeof ComputedReltuples>;\n\nexport const ComputedStats = z.object({\n columnStats: z.array(ComputedColumnStats),\n reltuples: z.array(ComputedReltuples),\n});\n\nexport type ComputedStats = z.infer<typeof ComputedStats>;\n\nconst DEFAULT_RELTUPLES = 10_000_000;\nconst DEFAULT_RELPAGES = 1;\n// it's _very_ rare that the default page size is ever changed\nconst DEFAULT_PAGE_SIZE = 2 ** 13;\n\nfunction estimateStawidth(col: { attlen?: number | null }): number {\n return col.attlen ?? 32;\n}\n\nfunction estimateRelpages(\n reltuples: number,\n columns: { attlen: number | null }[],\n): number {\n const rowWidth =\n // 23 byte tuple header + 4 bytes alignment/null bitmap\n columns.reduce((sum, col) => sum + estimateStawidth(col), 0) + 27;\n return Math.ceil((reltuples * rowWidth) / DEFAULT_PAGE_SIZE);\n}\n\nfunction estimateIndexRelpages(\n reltuples: number,\n columns: { attlen: number | null }[],\n fillfactor: number,\n amname: string,\n tableRelpages: number,\n): number {\n if (amname === \"gin\") {\n // GIN has an inverted structure; distinct element counts per row are unknown\n // without real data, so fall back to a ratio of the table page count\n return Math.ceil(tableRelpages * 0.3);\n }\n // 16 bytes btree entry overhead per key in addition to the key width\n const keyWidth = columns.reduce(\n (sum, col) => sum + estimateStawidth(col) + 16,\n 0,\n );\n return Math.ceil((reltuples * keyWidth) / DEFAULT_PAGE_SIZE / fillfactor);\n}\n\nexport class Statistics {\n readonly mode: StatisticsMode;\n readonly computedStats: ComputedStats;\n private readonly exportedMetadata: ExportedStats[] | undefined;\n // preventing accidental internal mutations\n static readonly defaultStatsMode: StatisticsMode = Object.freeze({\n kind: \"fromAssumption\",\n reltuples: DEFAULT_RELTUPLES,\n });\n constructor(\n private readonly db: Postgres,\n public readonly postgresVersion: PostgresVersion,\n public readonly ownMetadata: ExportedStats[],\n statsMode: StatisticsMode,\n ) {\n if (statsMode) {\n this.mode = statsMode;\n if (statsMode.kind === \"fromStatisticsExport\") {\n this.exportedMetadata = statsMode.stats;\n }\n } else {\n this.mode = Statistics.defaultStatsMode;\n }\n this.computedStats = this.buildComputedStats();\n }\n\n private buildComputedStats(): ComputedStats {\n const columnStats: ComputedColumnStats[] = [];\n const reltuples: ComputedReltuples[] = [];\n\n for (const table of this.ownMetadata) {\n const targetTable = this.exportedMetadata?.find(\n (m) =>\n m.tableName === table.tableName && m.schemaName === table.schemaName,\n );\n const target = targetTable?.columns ?? table.columns;\n\n for (const column of target) {\n const { stats } = column;\n if (!stats || this.mode.kind === \"fromAssumption\") {\n const stawidth = stats?.stawidth || estimateStawidth(column);\n columnStats.push({\n schema_name: table.schemaName,\n table_name: table.tableName,\n column_name: column.columnName,\n data_type: column.dataType,\n stainherit: false,\n stanullfrac: 0.04,\n stawidth,\n stadistinct: -0.9,\n stakind1: 0,\n stakind2: 0,\n stakind3: 3,\n stakind4: 0,\n stakind5: 0,\n stacoll1: \"0\",\n stacoll2: \"0\",\n stacoll3: \"0\",\n stacoll4: \"0\",\n stacoll5: \"0\",\n staop1: \"0\",\n staop2: \"0\",\n staop3: \"0\",\n staop4: \"0\",\n staop5: \"0\",\n stanumbers1: null,\n stanumbers2: null,\n stanumbers3: [0.9],\n stanumbers4: null,\n stanumbers5: null,\n stavalues1: null,\n stavalues2: null,\n stavalues3: null,\n stavalues4: null,\n stavalues5: null,\n _value_type1: \"real\",\n _value_type2: \"real\",\n _value_type3: \"real\",\n _value_type4: \"real\",\n _value_type5: \"real\",\n });\n } else {\n columnStats.push({\n schema_name: table.schemaName,\n table_name: table.tableName,\n column_name: column.columnName,\n data_type: column.dataType,\n stainherit: stats.stainherit ?? false,\n stanullfrac: stats.stanullfrac,\n stawidth: stats.stawidth,\n stadistinct: stats.stadistinct,\n stakind1: stats.stakind1,\n stakind2: stats.stakind2,\n stakind3: stats.stakind3,\n stakind4: stats.stakind4,\n stakind5: stats.stakind5,\n staop1: stats.staop1,\n staop2: stats.staop2,\n staop3: stats.staop3,\n staop4: stats.staop4,\n staop5: stats.staop5,\n stacoll1: stats.stacoll1,\n stacoll2: stats.stacoll2,\n stacoll3: stats.stacoll3,\n stacoll4: stats.stacoll4,\n stacoll5: stats.stacoll5,\n stanumbers1: stats.stanumbers1,\n stanumbers2: stats.stanumbers2,\n stanumbers3: stats.stanumbers3,\n stanumbers4: stats.stanumbers4,\n stanumbers5: stats.stanumbers5,\n stavalues1: Statistics.safeStavalues(stats.stavalues1),\n stavalues2: Statistics.safeStavalues(stats.stavalues2),\n stavalues3: Statistics.safeStavalues(stats.stavalues3),\n stavalues4: Statistics.safeStavalues(stats.stavalues4),\n stavalues5: Statistics.safeStavalues(stats.stavalues5),\n _value_type1: this.stavalueKind(\n Statistics.safeStavalues(stats.stavalues1),\n ),\n _value_type2: this.stavalueKind(\n Statistics.safeStavalues(stats.stavalues2),\n ),\n _value_type3: this.stavalueKind(\n Statistics.safeStavalues(stats.stavalues3),\n ),\n _value_type4: this.stavalueKind(\n Statistics.safeStavalues(stats.stavalues4),\n ),\n _value_type5: this.stavalueKind(\n Statistics.safeStavalues(stats.stavalues5),\n ),\n });\n }\n }\n\n let tableReltuples: number;\n let tableRelpages: number;\n let relallvisible = 0;\n let relallfrozen: number | undefined;\n\n if (this.mode.kind === \"fromAssumption\") {\n tableReltuples = this.mode.reltuples;\n tableRelpages = estimateRelpages(tableReltuples, table.columns);\n } else if (targetTable) {\n tableReltuples = targetTable.reltuples;\n tableRelpages = targetTable.relpages;\n relallvisible = targetTable.relallvisible;\n relallfrozen = targetTable.relallfrozen;\n } else {\n tableReltuples = DEFAULT_RELTUPLES;\n tableRelpages = DEFAULT_RELPAGES;\n }\n\n reltuples.push({\n relname: table.tableName,\n schema_name: table.schemaName,\n reltuples: tableReltuples,\n relpages: tableRelpages,\n relallfrozen,\n relallvisible,\n });\n\n if (this.mode.kind === \"fromAssumption\") {\n for (const index of table.indexes) {\n const indexRelpages = estimateIndexRelpages(\n this.mode.reltuples,\n index.columns,\n index.fillfactor / 100,\n index.amname,\n tableRelpages,\n );\n reltuples.push({\n relname: index.indexName,\n schema_name: table.schemaName,\n reltuples: this.mode.reltuples,\n relpages: indexRelpages,\n relallfrozen: 0,\n relallvisible: indexRelpages,\n });\n }\n } else if (targetTable) {\n for (const index of targetTable.indexes) {\n reltuples.push({\n relname: index.indexName,\n schema_name: targetTable.schemaName,\n reltuples: index.reltuples,\n relpages: index.relpages,\n relallfrozen: index.relallfrozen,\n relallvisible: index.relallvisible,\n });\n }\n }\n }\n\n return { columnStats, reltuples };\n }\n\n static statsModeFromAssumption({\n reltuples,\n }: {\n reltuples: number;\n }): StatisticsMode {\n return {\n kind: \"fromAssumption\",\n reltuples,\n };\n }\n\n /**\n * Create a statistic mode from stats exported from another database\n **/\n static statsModeFromExport(stats: ExportedStats[]): StatisticsMode {\n return {\n kind: \"fromStatisticsExport\",\n source: { kind: \"inline\" },\n stats,\n };\n }\n\n static async fromPostgres(\n db: Postgres,\n statsMode: StatisticsMode,\n ): Promise<Statistics> {\n const version = await db.serverNum();\n const ownStats = await Statistics.dumpStats(db, version, \"full\");\n return new Statistics(db, version, ownStats, statsMode);\n }\n\n restoreStats(tx: PostgresTransaction) {\n // if (this.postgresVersion < \"180000\") {\n return this.restoreStats17(tx);\n // }\n // return this.restoreStats18(tx);\n }\n\n approximateTotalRows() {\n if (!this.exportedMetadata) {\n return 0;\n }\n let totalRows = 0;\n for (const table of this.exportedMetadata) {\n totalRows += table.reltuples;\n }\n return totalRows;\n }\n\n /**\n * We have to cast stavaluesN to the correct type\n * This derives that type for us so it can be used in `array_in`\n */\n private stavalueKind(values: unknown[] | null): StaValueKind {\n if (!values || values.length === 0) {\n return null;\n }\n const [elem] = values;\n if (typeof elem === \"number\") {\n return \"real\";\n } else if (typeof elem === \"boolean\") {\n return \"boolean\";\n }\n // is everything else a text? What about strinfied dates?\n // we might need column metadata access here if we do\n return \"text\";\n }\n\n /**\n * PostgreSQL's anyarray columns in pg_statistic can hold arrays of arrays\n * for columns with array types (e.g. text[], int4[]). These create\n * multidimensional arrays that can be \"ragged\" (sub-arrays with different\n * lengths). jsonb_to_recordset can't reconstruct ragged multidimensional\n * arrays from JSON, so we need to drop these values.\n */\n private static safeStavalues(values: unknown[] | null): unknown[] | null {\n if (!values || values.length === 0) return values;\n if (values.some((v) => Array.isArray(v))) {\n console.warn(\"Discarding ragged multidimensional stavalues array\");\n return null;\n }\n return values;\n }\n\n private async restoreStats17(tx: PostgresTransaction) {\n const warnings = {\n tablesNotInExports: [] as string[],\n tablesNotInTest: [] as string[],\n tableNotAnalyzed: [] as string[],\n statsMissing: [] as {\n statistic: string;\n table: string;\n schema: string;\n column: string;\n }[],\n };\n\n const processedTables = new Set<string>(\n this.ownMetadata.map((t) => `${t.schemaName}.${t.tableName}`),\n );\n\n const columnStatsUpdatePromise = tx\n .exec(Statistics.columnStatsSQL, [this.computedStats.columnStats])\n .catch((err) => {\n console.error(\"Something wrong wrong updating column stats\");\n console.error(err);\n throw err;\n });\n\n /**\n * Postgres has 5 different slots for storing statistics per column and a potentially unlimited\n * number of statistic types to choose from. Each code in `stakindN` can mean different things.\n * Some statistics are just numerical values such as `n_distinct` and `correlation`, meaning\n * they're only derived from `stanumbersN` and the value of `stanumbersN` is never read.\n * Others take advantage of the `stavaluesN` columns which use `anyarray` type to store\n * concrete values internally for things like histogram bounds.\n * Unfortunately we cannot change anyarrays without a C extension.\n *\n * (1) = most common values\n * (2) = scalar histogram\n * (3) = correlation <- can change\n * (4) = most common elements\n * (5) = distinct elem count histogram <- can change\n * (6) = length histogram (?) These don't appear in pg_stats\n * (7) = bounds histogram (?) These don't appear in pg_stats\n * (N) = potentially many more kinds of statistics. But postgres <=18 only uses these 7.\n *\n * What we're doing here is setting ANY statistic we cannot directly control\n * (anything that relies on stavaluesN) to 0 to make sure the planner isn't influenced by what\n * what the db collected from the test data.\n * Because we do our tests with `generic_plan` it seems it's already unlikely that the planner will be\n * using things like common values or histogram bounds to make the planning decisions we care about.\n * This is a just in case.\n */\n const reltuplesQuery = dedent`\n update pg_class p\n set reltuples = v.reltuples,\n relpages = v.relpages,\n -- relallfrozen = case when v.relallfrozen is null then p.relallfrozen else v.relallfrozen end,\n relallvisible = case when v.relallvisible is null then p.relallvisible else v.relallvisible end\n from jsonb_to_recordset($1::jsonb)\n as v(reltuples real, relpages integer, relallfrozen integer, relallvisible integer, relname text, schema_name text)\n where p.relname = v.relname\n and p.relnamespace = (select oid from pg_namespace where nspname = v.schema_name)\n returning p.relname, p.relnamespace, p.reltuples, p.relpages;\n `;\n\n const reltuplesPromise = tx\n .exec(reltuplesQuery, [this.computedStats.reltuples])\n .catch((err) => {\n console.error(\"Something went wrong updating reltuples/relpages\");\n console.error(err);\n return err;\n });\n\n if (this.exportedMetadata) {\n for (const table of this.exportedMetadata) {\n const tableExists = processedTables.has(\n `${table.schemaName}.${table.tableName}`,\n );\n if (tableExists && table.reltuples === -1) {\n console.warn(\n `Table ${table.tableName} has reltuples -1. Your production database is probably not analyzed properly`,\n );\n warnings.tableNotAnalyzed.push(\n `${table.schemaName}.${table.tableName}`,\n );\n }\n if (tableExists) {\n continue;\n }\n warnings.tablesNotInTest.push(`${table.schemaName}.${table.tableName}`);\n }\n }\n const [statsUpdates, reltuplesUpdates] = await Promise.all([\n columnStatsUpdatePromise,\n reltuplesPromise,\n ]);\n const updatedColumnsProperly = statsUpdates\n ? statsUpdates.length === this.computedStats.columnStats.length\n : true;\n if (!updatedColumnsProperly) {\n console.error(`Did not update expected column stats`);\n }\n if (reltuplesUpdates.length !== this.computedStats.reltuples.length) {\n console.error(`Did not update expected reltuples/relpages`);\n }\n return warnings;\n }\n\n private static readonly columnStatsSQL = dedent`\n WITH input AS (\n SELECT\n c.oid AS starelid,\n a.attnum AS staattnum,\n v.stainherit,\n v.stanullfrac,\n v.stawidth,\n v.stadistinct,\n v.stakind1,\n v.stakind2,\n v.stakind3,\n v.stakind4,\n v.stakind5,\n v.staop1,\n v.staop2,\n v.staop3,\n v.staop4,\n v.staop5,\n v.stacoll1,\n v.stacoll2,\n v.stacoll3,\n v.stacoll4,\n v.stacoll5,\n v.stanumbers1,\n v.stanumbers2,\n v.stanumbers3,\n v.stanumbers4,\n v.stanumbers5,\n v.stavalues1,\n v.stavalues2,\n v.stavalues3,\n v.stavalues4,\n v.stavalues5,\n _value_type1,\n _value_type2,\n _value_type3,\n _value_type4,\n _value_type5\n FROM jsonb_to_recordset($1::jsonb) AS v(\n schema_name text,\n table_name text,\n column_name text,\n stainherit boolean,\n stanullfrac real,\n stawidth integer,\n stadistinct real,\n stakind1 real,\n stakind2 real,\n stakind3 real,\n stakind4 real,\n stakind5 real,\n staop1 oid,\n staop2 oid,\n staop3 oid,\n staop4 oid,\n staop5 oid,\n stacoll1 oid,\n stacoll2 oid,\n stacoll3 oid,\n stacoll4 oid,\n stacoll5 oid,\n stanumbers1 real[],\n stanumbers2 real[],\n stanumbers3 real[],\n stanumbers4 real[],\n stanumbers5 real[],\n stavalues1 text[],\n stavalues2 text[],\n stavalues3 text[],\n stavalues4 text[],\n stavalues5 text[],\n _value_type1 text,\n _value_type2 text,\n _value_type3 text,\n _value_type4 text,\n _value_type5 text\n )\n JOIN pg_class c ON c.relname = v.table_name\n JOIN pg_namespace n ON n.oid = c.relnamespace AND n.nspname = v.schema_name\n JOIN pg_attribute a ON a.attrelid = c.oid AND a.attname = v.column_name\n ),\n updated AS (\n UPDATE pg_statistic s\n SET\n stanullfrac = i.stanullfrac,\n stawidth = i.stawidth,\n stadistinct = i.stadistinct,\n stakind1 = i.stakind1,\n stakind2 = i.stakind2,\n stakind3 = i.stakind3,\n stakind4 = i.stakind4,\n stakind5 = i.stakind5,\n staop1 = i.staop1,\n staop2 = i.staop2,\n staop3 = i.staop3,\n staop4 = i.staop4,\n staop5 = i.staop5,\n stacoll1 = i.stacoll1,\n stacoll2 = i.stacoll2,\n stacoll3 = i.stacoll3,\n stacoll4 = i.stacoll4,\n stacoll5 = i.stacoll5,\n stanumbers1 = i.stanumbers1,\n stanumbers2 = i.stanumbers2,\n stanumbers3 = i.stanumbers3,\n stanumbers4 = i.stanumbers4,\n stanumbers5 = i.stanumbers5,\n stavalues1 = case\n when i.stavalues1 is null then null\n else array_in(i.stavalues1::text::cstring, i._value_type1::regtype::oid, -1)\n end,\n stavalues2 = case\n when i.stavalues2 is null then null\n else array_in(i.stavalues2::text::cstring, i._value_type2::regtype::oid, -1)\n end,\n stavalues3 = case\n when i.stavalues3 is null then null\n else array_in(i.stavalues3::text::cstring, i._value_type3::regtype::oid, -1)\n end,\n stavalues4 = case\n when i.stavalues4 is null then null\n else array_in(i.stavalues4::text::cstring, i._value_type4::regtype::oid, -1)\n end,\n stavalues5 = case\n when i.stavalues5 is null then null\n else array_in(i.stavalues5::text::cstring, i._value_type5::regtype::oid, -1)\n end\n -- stavalues1 = i.stavalues1,\n -- stavalues2 = i.stavalues2,\n -- stavalues3 = i.stavalues3,\n -- stavalues4 = i.stavalues4,\n -- stavalues5 = i.stavalues5\n FROM input i\n WHERE s.starelid = i.starelid AND s.staattnum = i.staattnum AND s.stainherit = i.stainherit\n RETURNING s.starelid, s.staattnum, s.stainherit, s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5\n ),\n inserted as (\n INSERT INTO pg_statistic (\n starelid, staattnum, stainherit,\n stanullfrac, stawidth, stadistinct,\n stakind1, stakind2, stakind3, stakind4, stakind5,\n staop1, staop2, staop3, staop4, staop5,\n stacoll1, stacoll2, stacoll3, stacoll4, stacoll5,\n stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5,\n stavalues1, stavalues2, stavalues3, stavalues4, stavalues5\n )\n SELECT\n i.starelid, i.staattnum, i.stainherit,\n i.stanullfrac, i.stawidth, i.stadistinct,\n i.stakind1, i.stakind2, i.stakind3, i.stakind4, i.stakind5,\n i.staop1, i.staop2, i.staop3, i.staop4, i.staop5,\n i.stacoll1, i.stacoll2, i.stacoll3, i.stacoll4, i.stacoll5,\n i.stanumbers1, i.stanumbers2, i.stanumbers3, i.stanumbers4, i.stanumbers5,\n -- i.stavalues1, i.stavalues2, i.stavalues3, i.stavalues4, i.stavalues5,\n case\n when i.stavalues1 is null then null\n else array_in(i.stavalues1::text::cstring, i._value_type1::regtype::oid, -1)\n end,\n case\n when i.stavalues2 is null then null\n else array_in(i.stavalues2::text::cstring, i._value_type2::regtype::oid, -1)\n end,\n case\n when i.stavalues3 is null then null\n else array_in(i.stavalues3::text::cstring, i._value_type3::regtype::oid, -1)\n end,\n case\n when i.stavalues4 is null then null\n else array_in(i.stavalues4::text::cstring, i._value_type4::regtype::oid, -1)\n end,\n case\n when i.stavalues5 is null then null\n else array_in(i.stavalues5::text::cstring, i._value_type5::regtype::oid, -1)\n end\n -- i._value_type1, i._value_type2, i._value_type3, i._value_type4, i._value_type5\n FROM input i\n LEFT JOIN updated u\n ON i.starelid = u.starelid AND i.staattnum = u.staattnum AND i.stainherit = u.stainherit\n WHERE u.starelid IS NULL\n returning starelid, staattnum, stainherit, stakind1, stakind2, stakind3, stakind4, stakind5\n )\n select * from updated union all (select * from inserted); -- @qd_introspection`;\n\n static async dumpStats(\n db: PostgresTransaction,\n postgresVersion: PostgresVersion,\n kind: \"anonymous\" | \"full\",\n ): Promise<ExportedStats[]> {\n const fullDump = kind === \"full\";\n console.log(`dumping stats for postgres ${gray(postgresVersion)}`);\n // certain things are only supported with pg17\n const stats = await db.exec<{ json_agg: ExportedStats[] }>(\n `\n WITH table_columns AS (\n SELECT\n cl.relname,\n n.nspname,\n cl.reltuples,\n cl.relpages,\n cl.relallvisible,\n -- cl.relallfrozen,\n json_agg(\n json_build_object(\n 'columnName', a.attname,\n 'attlen', CASE WHEN a.attlen > 0 THEN a.attlen ELSE NULL END,\n 'dataType', t.typname,\n 'stats', (\n SELECT json_build_object(\n 'starelid', s.starelid,\n 'staattnum', s.staattnum,\n 'stanullfrac', s.stanullfrac,\n 'stawidth', s.stawidth,\n 'stadistinct', s.stadistinct,\n 'stakind1', s.stakind1, 'staop1', s.staop1, 'stacoll1', s.stacoll1, 'stanumbers1', s.stanumbers1,\n 'stakind2', s.stakind2, 'staop2', s.staop2, 'stacoll2', s.stacoll2, 'stanumbers2', s.stanumbers2,\n 'stakind3', s.stakind3, 'staop3', s.staop3, 'stacoll3', s.stacoll3, 'stanumbers3', s.stanumbers3,\n 'stakind4', s.stakind4, 'staop4', s.staop4, 'stacoll4', s.stacoll4, 'stanumbers4', s.stanumbers4,\n 'stakind5', s.stakind5, 'staop5', s.staop5, 'stacoll5', s.stacoll5, 'stanumbers5', s.stanumbers5,\n 'stavalues1', CASE WHEN $1 THEN s.stavalues1 ELSE NULL END,\n 'stavalues2', CASE WHEN $1 THEN s.stavalues2 ELSE NULL END,\n 'stavalues3', CASE WHEN $1 THEN s.stavalues3 ELSE NULL END,\n 'stavalues4', CASE WHEN $1 THEN s.stavalues4 ELSE NULL END,\n 'stavalues5', CASE WHEN $1 THEN s.stavalues5 ELSE NULL END\n )\n FROM pg_statistic s\n WHERE s.starelid = a.attrelid AND s.staattnum = a.attnum\n )\n )\n ORDER BY a.attnum\n ) AS columns\n FROM pg_class cl\n JOIN pg_namespace n ON n.oid = cl.relnamespace\n JOIN pg_attribute a ON a.attrelid = cl.oid AND a.attnum > 0 AND NOT a.attisdropped\n JOIN pg_type t ON t.oid = a.atttypid\n WHERE cl.relkind = 'r'\n AND n.nspname NOT IN ('pg_catalog', 'information_schema', 'tiger', 'tiger_data', 'topology')\n AND cl.relname NOT IN ('pg_stat_statements', 'pg_stat_statements_info')\n GROUP BY cl.relname, n.nspname, cl.reltuples, cl.relpages, cl.relallvisible\n ),\n table_indexes AS (\n SELECT\n t.relname AS table_name,\n json_agg(\n json_build_object(\n 'indexName', i.relname,\n 'amname', am.amname,\n 'reltuples', i.reltuples,\n 'relpages', i.relpages,\n 'relallvisible', i.relallvisible,\n -- 'relallfrozen', i.relallfrozen,\n 'fillfactor', COALESCE(\n (\n SELECT (regexp_match(opt, 'fillfactor=(\\\\d+)'))[1]::integer\n FROM unnest(i.reloptions) AS opt\n WHERE opt LIKE 'fillfactor=%'\n LIMIT 1\n ),\n 90\n ),\n 'columns', COALESCE(\n (\n SELECT json_agg(json_build_object(\n 'attlen', CASE WHEN a.attlen > 0 THEN a.attlen ELSE NULL END\n ) ORDER BY col_pos.ord)\n FROM unnest(ix.indkey) WITH ORDINALITY AS col_pos(attnum, ord)\n JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = col_pos.attnum\n WHERE col_pos.attnum > 0\n ),\n '[]'::json\n )\n )\n ) AS indexes\n FROM pg_class t\n JOIN pg_index ix ON ix.indrelid = t.oid\n JOIN pg_class i ON i.oid = ix.indexrelid\n JOIN pg_am am ON am.oid = i.relam\n JOIN pg_namespace n ON n.oid = t.relnamespace\n WHERE t.relname NOT LIKE 'pg_%'\n AND n.nspname <> 'information_schema'\n AND n.nspname NOT IN ('tiger', 'tiger_data', 'topology')\n GROUP BY t.relname\n )\n SELECT json_agg(\n json_build_object(\n 'tableName', tc.relname,\n 'schemaName', tc.nspname,\n 'reltuples', tc.reltuples,\n 'relpages', tc.relpages,\n 'relallvisible', tc.relallvisible,\n -- 'relallfrozen', tc.relallfrozen,\n 'columns', COALESCE(tc.columns, '[]'::json),\n 'indexes', COALESCE(ti.indexes, '[]'::json)\n )\n )\n FROM table_columns tc\n LEFT JOIN table_indexes ti\n ON ti.table_name = tc.relname;\n `,\n [fullDump],\n );\n const out = z.array(ExportedStats).parse(stats[0].json_agg);\n return out;\n }\n\n /**\n * Returns all indexes in the database.\n * ONLY handles regular btree indexes\n */\n async getExistingIndexes(): Promise<IndexedTable[]> {\n const indexes = await this.db.exec<IndexedTable>(`\n WITH partitioned_tables AS (\n SELECT\n inhparent::regclass AS parent_table,\n inhrelid::regclass AS partition_table\n FROM\n pg_inherits\n )\n SELECT\n n.nspname AS schema_name,\n COALESCE(pt.parent_table::text, t.relname) AS table_name,\n i.relname AS index_name,\n ix.indisprimary as is_primary,\n ix.indisunique as is_unique,\n am.amname AS index_type,\n array_agg(\n CASE\n -- Handle regular columns\n WHEN a.attname IS NOT NULL THEN\n json_build_object('name', a.attname, 'order',\n CASE\n WHEN (indoption[array_position(ix.indkey, a.attnum)] & 1) = 1 THEN 'DESC'\n ELSE 'ASC'\n END,\n 'opclass', CASE WHEN opc.opcdefault THEN NULL ELSE opc.opcname END)\n -- Handle expressions\n ELSE\n json_build_object('name', pg_get_expr((ix.indexprs)::pg_node_tree, t.oid), 'order',\n CASE\n WHEN (indoption[array_position(ix.indkey, k.attnum)] & 1) = 1 THEN 'DESC'\n ELSE 'ASC'\n END,\n 'opclass', CASE WHEN opc.opcdefault THEN NULL ELSE opc.opcname END)\n END\n ORDER BY array_position(ix.indkey, k.attnum)\n ) AS index_columns\n FROM\n pg_class t\n LEFT JOIN partitioned_tables pt ON t.oid = pt.partition_table\n JOIN pg_index ix ON t.oid = ix.indrelid\n JOIN pg_class i ON i.oid = ix.indexrelid\n JOIN pg_am am ON i.relam = am.oid\n LEFT JOIN LATERAL unnest(ix.indkey) WITH ORDINALITY k(attnum, ordinality) ON true\n LEFT JOIN pg_attribute a ON a.attnum = k.attnum AND a.attrelid = t.oid\n LEFT JOIN pg_opclass opc ON opc.oid = ix.indclass[k.ordinality - 1]\n JOIN pg_namespace n ON t.relnamespace = n.oid\n WHERE\n n.nspname not like 'pg_%' and\n n.nspname <> 'information_schema'\n GROUP BY\n n.nspname, COALESCE(pt.parent_table::text, t.relname), i.relname, am.amname, ix.indisprimary, ix.indisunique\n ORDER BY\n COALESCE(pt.parent_table::text, t.relname), i.relname; -- @qd_introspection\n `);\n return indexes;\n }\n}\n\nexport type ColumnMetadata = {\n columnName: string;\n dataType: string;\n isNullable: boolean;\n stats: ColumnStats | null;\n};\n\ntype ColumnStats = {\n stainherit: boolean;\n stanullfrac: number;\n stawidth: number;\n stadistinct: number;\n stakind1: number;\n stakind2: number;\n stakind3: number;\n stakind4: number;\n stakind5: number;\n staop1: number;\n staop2: number;\n staop3: number;\n staop4: number;\n staop5: number;\n stacoll1: number;\n stacoll2: number;\n stacoll3: number;\n stacoll4: number;\n stacoll5: number;\n stanumbers1: number;\n stanumbers2: number;\n stanumbers3: number;\n stanumbers4: number;\n stanumbers5: number;\n};\n\nexport type TableMetadata = {\n tableName: string;\n schemaName: string;\n reltuples: number;\n relpages: number;\n relallvisible: number;\n relallfrozen?: number;\n columns: ColumnMetadata[];\n};\n\ntype TableName = string;\nexport type TableStats = {\n tupleEstimate: bigint;\n pageCount: number;\n};\n\nexport type SerializeResult = {\n schema: TableMetadata[];\n serialized: string;\n sampledRecords: Record<TableName, number>;\n};\n\nexport type IndexOrder = \"ASC\" | \"DESC\";\n\nexport type IndexedTable = {\n index_columns: Array<{ name: string; order: IndexOrder; opclass?: string }>;\n is_primary: boolean;\n is_unique: boolean;\n index_name: string;\n // eslint-disable-next-line @typescript-eslint/ban-types\n index_type: \"btree\" | \"gin\" | (string & {});\n // this is always public\n schema_name: string;\n table_name: string;\n};\n"],"mappings":";;;;;;AAaA,MAAa,mBAAmB,EAAE,MAAM,CACtC,EAAE,OAAO;CACP,MAAM,EAAE,QAAQ,OAAO;CACvB,MAAM,EAAE,QAAQ,CAAC,IAAI,EAAE;CACxB,CAAC,EACF,EAAE,OAAO,EACP,MAAM,EAAE,QAAQ,SAAS,EAC1B,CAAC,CACH,CAAC;AAEF,MAAa,0BAA0B,EAAE,OAAO;CAC9C,UAAU,EAAE,QAAQ;CACpB,YAAY,EAAE,SAAS,CAAC,QAAQ,MAAM;CAEtC,aAAa,EAAE,QAAQ;CAEvB,aAAa,EAAE,QAAQ;CACvB,UAAU,EAAE,QAAQ,CAAC,IAAI,EAAE;CAC3B,UAAU,EAAE,QAAQ,CAAC,IAAI,EAAE;CAC3B,UAAU,EAAE,QAAQ,CAAC,IAAI,EAAE;CAC3B,UAAU,EAAE,QAAQ,CAAC,IAAI,EAAE;CAC3B,UAAU,EAAE,QAAQ,CAAC,IAAI,EAAE;CAC3B,QAAQ,EAAE,QAAQ;CAClB,QAAQ,EAAE,QAAQ;CAClB,QAAQ,EAAE,QAAQ;CAClB,QAAQ,EAAE,QAAQ;CAClB,QAAQ,EAAE,QAAQ;CAClB,UAAU,EAAE,QAAQ;CACpB,UAAU,EAAE,QAAQ;CACpB,UAAU,EAAE,QAAQ;CACpB,UAAU,EAAE,QAAQ;CACpB,UAAU,EAAE,QAAQ;CACpB,aAAa,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,UAAU;CAC3C,aAAa,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,UAAU;CAC3C,aAAa,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,UAAU;CAC3C,aAAa,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,UAAU;CAC3C,aAAa,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,UAAU;CAG3C,YAAY,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,UAAU;CACvC,YAAY,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,UAAU;CACvC,YAAY,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,UAAU;CACvC,YAAY,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,UAAU;CACvC,YAAY,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,UAAU;CACxC,CAAC;AAEF,MAAa,uBAAuB,EAAE,OAAO;CAC3C,YAAY,EAAE,QAAQ;CACtB,QAAQ,EAAE,QAAQ,CAAC,UAAU;CAC7B,UAAU,EAAE,QAAQ,CAAC,UAAU;CAC/B,OAAO,wBAAwB,UAAU;CAC1C,CAAC;AAEF,MAAa,qBAAqB,EAAE,OAAO;CACzC,WAAW,EAAE,QAAQ;CACrB,QAAQ,EAAE,QAAQ,CAAC,QAAQ,QAAQ;CACnC,UAAU,EAAE,QAAQ;CACpB,WAAW,EAAE,QAAQ;CACrB,eAAe,EAAE,QAAQ;CACzB,cAAc,EAAE,QAAQ,CAAC,UAAU;CACnC,YAAY,EAAE,QAAQ,CAAC,QAAQ,GAAG;CAClC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;CAC1E,CAAC;AAKF,MAAa,kBAAkB,EAAE,OAAO;CACtC,WAAW,EAAE,QAAQ;CACrB,YAAY,EAAE,QAAQ;CAEtB,UAAU,EAAE,QAAQ;CAEpB,WAAW,EAAE,QAAQ;CACrB,eAAe,EAAE,QAAQ;CAEzB,cAAc,EAAE,QAAQ,CAAC,UAAU;CACnC,SAAS,EAAE,MAAM,qBAAqB,CAAC,QAAQ,EAAE,CAAC;CAClD,SAAS,EAAE,MAAM,mBAAmB;CACrC,CAAC;AAEF,MAAa,gBAAgB,EAAE,MAAM,CAAC,gBAAgB,CAAC;AAIvD,MAAa,iBAAiB,EAAE,mBAAmB,QAAQ,CACzD,EAAE,OAAO;CACP,MAAM,EAAE,QAAQ,iBAAiB;CACjC,WAAW,EAAE,QAAQ,CAAC,IAAI,EAAE;CAC7B,CAAC,EACF,EAAE,OAAO;CACP,MAAM,EAAE,QAAQ,uBAAuB;CACvC,OAAO,EAAE,MAAM,cAAc;CAC7B,QAAQ;CACT,CAAC,CACH,CAAC;AAIF,MAAa,sBAAsB,EAAE,OAAO;CAC1C,aAAa,EAAE,QAAQ;CACvB,YAAY,EAAE,QAAQ;CACtB,aAAa,EAAE,QAAQ;CACvB,WAAW,EAAE,QAAQ,CAAC,UAAU;CAChC,YAAY,EAAE,SAAS;CACvB,aAAa,EAAE,QAAQ;CACvB,UAAU,EAAE,QAAQ;CACpB,aAAa,EAAE,QAAQ;CACvB,UAAU,EAAE,QAAQ;CACpB,UAAU,EAAE,QAAQ;CACpB,UAAU,EAAE,QAAQ;CACpB,UAAU,EAAE,QAAQ;CACpB,UAAU,EAAE,QAAQ;CACpB,QAAQ,EAAE,QAAQ;CAClB,QAAQ,EAAE,QAAQ;CAClB,QAAQ,EAAE,QAAQ;CAClB,QAAQ,EAAE,QAAQ;CAClB,QAAQ,EAAE,QAAQ;CAClB,UAAU,EAAE,QAAQ;CACpB,UAAU,EAAE,QAAQ;CACpB,UAAU,EAAE,QAAQ;CACpB,UAAU,EAAE,QAAQ;CACpB,UAAU,EAAE,QAAQ;CACpB,aAAa,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,UAAU;CAC3C,aAAa,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,UAAU;CAC3C,aAAa,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,UAAU;CAC3C,aAAa,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,UAAU;CAC3C,aAAa,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,UAAU;CAC3C,YAAY,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,UAAU;CACvC,YAAY,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,UAAU;CACvC,YAAY,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,UAAU;CACvC,YAAY,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,UAAU;CACvC,YAAY,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,UAAU;CACvC,cAAc,EAAE,QAAQ,CAAC,UAAU;CACnC,cAAc,EAAE,QAAQ,CAAC,UAAU;CACnC,cAAc,EAAE,QAAQ,CAAC,UAAU;CACnC,cAAc,EAAE,QAAQ,CAAC,UAAU;CACnC,cAAc,EAAE,QAAQ,CAAC,UAAU;CACpC,CAAC;AAIF,MAAa,oBAAoB,EAAE,OAAO;CACxC,SAAS,EAAE,QAAQ;CACnB,aAAa,EAAE,QAAQ;CACvB,WAAW,EAAE,QAAQ;CACrB,UAAU,EAAE,QAAQ;CACpB,eAAe,EAAE,QAAQ;CACzB,cAAc,EAAE,QAAQ,CAAC,UAAU;CACpC,CAAC;AAIF,MAAa,gBAAgB,EAAE,OAAO;CACpC,aAAa,EAAE,MAAM,oBAAoB;CACzC,WAAW,EAAE,MAAM,kBAAkB;CACtC,CAAC;AAIF,MAAM,oBAAoB;AAC1B,MAAM,mBAAmB;AAEzB,MAAM,oBAAoB,KAAK;AAE/B,SAAS,iBAAiB,KAAyC;AACjE,QAAO,IAAI,UAAU;;AAGvB,SAAS,iBACP,WACA,SACQ;CACR,MAAM,WAEJ,QAAQ,QAAQ,KAAK,QAAQ,MAAM,iBAAiB,IAAI,EAAE,EAAE,GAAG;AACjE,QAAO,KAAK,KAAM,YAAY,WAAY,kBAAkB;;AAG9D,SAAS,sBACP,WACA,SACA,YACA,QACA,eACQ;AACR,KAAI,WAAW,MAGb,QAAO,KAAK,KAAK,gBAAgB,GAAI;CAGvC,MAAM,WAAW,QAAQ,QACtB,KAAK,QAAQ,MAAM,iBAAiB,IAAI,GAAG,IAC5C,EACD;AACD,QAAO,KAAK,KAAM,YAAY,WAAY,oBAAoB,WAAW;;AAG3E,IAAa,aAAb,MAAa,WAAW;CAStB,YACE,IACA,iBACA,aACA,WACA;AAJiB,OAAA,KAAA;AACD,OAAA,kBAAA;AACA,OAAA,cAAA;wBAXT,QAAA,KAAA,EAAqB;wBACrB,iBAAA,KAAA,EAA6B;wBACrB,oBAAA,KAAA,EAA8C;AAY7D,MAAI,WAAW;AACb,QAAK,OAAO;AACZ,OAAI,UAAU,SAAS,uBACrB,MAAK,mBAAmB,UAAU;QAGpC,MAAK,OAAO,WAAW;AAEzB,OAAK,gBAAgB,KAAK,oBAAoB;;CAGhD,qBAA4C;EAC1C,MAAM,cAAqC,EAAE;EAC7C,MAAM,YAAiC,EAAE;AAEzC,OAAK,MAAM,SAAS,KAAK,aAAa;GACpC,MAAM,cAAc,KAAK,kBAAkB,MACxC,MACC,EAAE,cAAc,MAAM,aAAa,EAAE,eAAe,MAAM,WAC7D;GACD,MAAM,SAAS,aAAa,WAAW,MAAM;AAE7C,QAAK,MAAM,UAAU,QAAQ;IAC3B,MAAM,EAAE,UAAU;AAClB,QAAI,CAAC,SAAS,KAAK,KAAK,SAAS,kBAAkB;KACjD,MAAM,WAAW,OAAO,YAAY,iBAAiB,OAAO;AAC5D,iBAAY,KAAK;MACf,aAAa,MAAM;MACnB,YAAY,MAAM;MAClB,aAAa,OAAO;MACpB,WAAW,OAAO;MAClB,YAAY;MACZ,aAAa;MACb;MACA,aAAa;MACb,UAAU;MACV,UAAU;MACV,UAAU;MACV,UAAU;MACV,UAAU;MACV,UAAU;MACV,UAAU;MACV,UAAU;MACV,UAAU;MACV,UAAU;MACV,QAAQ;MACR,QAAQ;MACR,QAAQ;MACR,QAAQ;MACR,QAAQ;MACR,aAAa;MACb,aAAa;MACb,aAAa,CAAC,GAAI;MAClB,aAAa;MACb,aAAa;MACb,YAAY;MACZ,YAAY;MACZ,YAAY;MACZ,YAAY;MACZ,YAAY;MACZ,cAAc;MACd,cAAc;MACd,cAAc;MACd,cAAc;MACd,cAAc;MACf,CAAC;UAEF,aAAY,KAAK;KACf,aAAa,MAAM;KACnB,YAAY,MAAM;KAClB,aAAa,OAAO;KACpB,WAAW,OAAO;KAClB,YAAY,MAAM,cAAc;KAChC,aAAa,MAAM;KACnB,UAAU,MAAM;KAChB,aAAa,MAAM;KACnB,UAAU,MAAM;KAChB,UAAU,MAAM;KAChB,UAAU,MAAM;KAChB,UAAU,MAAM;KAChB,UAAU,MAAM;KAChB,QAAQ,MAAM;KACd,QAAQ,MAAM;KACd,QAAQ,MAAM;KACd,QAAQ,MAAM;KACd,QAAQ,MAAM;KACd,UAAU,MAAM;KAChB,UAAU,MAAM;KAChB,UAAU,MAAM;KAChB,UAAU,MAAM;KAChB,UAAU,MAAM;KAChB,aAAa,MAAM;KACnB,aAAa,MAAM;KACnB,aAAa,MAAM;KACnB,aAAa,MAAM;KACnB,aAAa,MAAM;KACnB,YAAY,WAAW,cAAc,MAAM,WAAW;KACtD,YAAY,WAAW,cAAc,MAAM,WAAW;KACtD,YAAY,WAAW,cAAc,MAAM,WAAW;KACtD,YAAY,WAAW,cAAc,MAAM,WAAW;KACtD,YAAY,WAAW,cAAc,MAAM,WAAW;KACtD,cAAc,KAAK,aACjB,WAAW,cAAc,MAAM,WAAW,CAC3C;KACD,cAAc,KAAK,aACjB,WAAW,cAAc,MAAM,WAAW,CAC3C;KACD,cAAc,KAAK,aACjB,WAAW,cAAc,MAAM,WAAW,CAC3C;KACD,cAAc,KAAK,aACjB,WAAW,cAAc,MAAM,WAAW,CAC3C;KACD,cAAc,KAAK,aACjB,WAAW,cAAc,MAAM,WAAW,CAC3C;KACF,CAAC;;GAIN,IAAI;GACJ,IAAI;GACJ,IAAI,gBAAgB;GACpB,IAAI;AAEJ,OAAI,KAAK,KAAK,SAAS,kBAAkB;AACvC,qBAAiB,KAAK,KAAK;AAC3B,oBAAgB,iBAAiB,gBAAgB,MAAM,QAAQ;cACtD,aAAa;AACtB,qBAAiB,YAAY;AAC7B,oBAAgB,YAAY;AAC5B,oBAAgB,YAAY;AAC5B,mBAAe,YAAY;UACtB;AACL,qBAAiB;AACjB,oBAAgB;;AAGlB,aAAU,KAAK;IACb,SAAS,MAAM;IACf,aAAa,MAAM;IACnB,WAAW;IACX,UAAU;IACV;IACA;IACD,CAAC;AAEF,OAAI,KAAK,KAAK,SAAS,iBACrB,MAAK,MAAM,SAAS,MAAM,SAAS;IACjC,MAAM,gBAAgB,sBACpB,KAAK,KAAK,WACV,MAAM,SACN,MAAM,aAAa,KACnB,MAAM,QACN,cACD;AACD,cAAU,KAAK;KACb,SAAS,MAAM;KACf,aAAa,MAAM;KACnB,WAAW,KAAK,KAAK;KACrB,UAAU;KACV,cAAc;KACd,eAAe;KAChB,CAAC;;YAEK,YACT,MAAK,MAAM,SAAS,YAAY,QAC9B,WAAU,KAAK;IACb,SAAS,MAAM;IACf,aAAa,YAAY;IACzB,WAAW,MAAM;IACjB,UAAU,MAAM;IAChB,cAAc,MAAM;IACpB,eAAe,MAAM;IACtB,CAAC;;AAKR,SAAO;GAAE;GAAa;GAAW;;CAGnC,OAAO,wBAAwB,EAC7B,aAGiB;AACjB,SAAO;GACL,MAAM;GACN;GACD;;;;;CAMH,OAAO,oBAAoB,OAAwC;AACjE,SAAO;GACL,MAAM;GACN,QAAQ,EAAE,MAAM,UAAU;GAC1B;GACD;;CAGH,aAAa,aACX,IACA,WACqB;EACrB,MAAM,UAAU,MAAM,GAAG,WAAW;AAEpC,SAAO,IAAI,WAAW,IAAI,SADT,MAAM,WAAW,UAAU,IAAI,SAAS,OAAO,EACnB,UAAU;;CAGzD,aAAa,IAAyB;AAEpC,SAAO,KAAK,eAAe,GAAG;;CAKhC,uBAAuB;AACrB,MAAI,CAAC,KAAK,iBACR,QAAO;EAET,IAAI,YAAY;AAChB,OAAK,MAAM,SAAS,KAAK,iBACvB,cAAa,MAAM;AAErB,SAAO;;;;;;CAOT,aAAqB,QAAwC;AAC3D,MAAI,CAAC,UAAU,OAAO,WAAW,EAC/B,QAAO;EAET,MAAM,CAAC,QAAQ;AACf,MAAI,OAAO,SAAS,SAClB,QAAO;WACE,OAAO,SAAS,UACzB,QAAO;AAIT,SAAO;;;;;;;;;CAUT,OAAe,cAAc,QAA4C;AACvE,MAAI,CAAC,UAAU,OAAO,WAAW,EAAG,QAAO;AAC3C,MAAI,OAAO,MAAM,MAAM,MAAM,QAAQ,EAAE,CAAC,EAAE;AACxC,WAAQ,KAAK,qDAAqD;AAClE,UAAO;;AAET,SAAO;;CAGT,MAAc,eAAe,IAAyB;EACpD,MAAM,WAAW;GACf,oBAAoB,EAAE;GACtB,iBAAiB,EAAE;GACnB,kBAAkB,EAAE;GACpB,cAAc,EAAE;GAMjB;EAED,MAAM,kBAAkB,IAAI,IAC1B,KAAK,YAAY,KAAK,MAAM,GAAG,EAAE,WAAW,GAAG,EAAE,YAAY,CAC9D;EAED,MAAM,2BAA2B,GAC9B,KAAK,WAAW,gBAAgB,CAAC,KAAK,cAAc,YAAY,CAAC,CACjE,OAAO,QAAQ;AACd,WAAQ,MAAM,8CAA8C;AAC5D,WAAQ,MAAM,IAAI;AAClB,SAAM;IACN;;;;;;;;;;;;;;;;;;;;;;;;;;EA2BJ,MAAM,iBAAiB,MAAM;;;;;;;;;;;;EAa7B,MAAM,mBAAmB,GACtB,KAAK,gBAAgB,CAAC,KAAK,cAAc,UAAU,CAAC,CACpD,OAAO,QAAQ;AACd,WAAQ,MAAM,mDAAmD;AACjE,WAAQ,MAAM,IAAI;AAClB,UAAO;IACP;AAEJ,MAAI,KAAK,iBACP,MAAK,MAAM,SAAS,KAAK,kBAAkB;GACzC,MAAM,cAAc,gBAAgB,IAClC,GAAG,MAAM,WAAW,GAAG,MAAM,YAC9B;AACD,OAAI,eAAe,MAAM,cAAc,IAAI;AACzC,YAAQ,KACN,SAAS,MAAM,UAAU,+EAC1B;AACD,aAAS,iBAAiB,KACxB,GAAG,MAAM,WAAW,GAAG,MAAM,YAC9B;;AAEH,OAAI,YACF;AAEF,YAAS,gBAAgB,KAAK,GAAG,MAAM,WAAW,GAAG,MAAM,YAAY;;EAG3E,MAAM,CAAC,cAAc,oBAAoB,MAAM,QAAQ,IAAI,CACzD,0BACA,iBACD,CAAC;AAIF,MAAI,EAH2B,eAC3B,aAAa,WAAW,KAAK,cAAc,YAAY,SACvD,MAEF,SAAQ,MAAM,uCAAuC;AAEvD,MAAI,iBAAiB,WAAW,KAAK,cAAc,UAAU,OAC3D,SAAQ,MAAM,6CAA6C;AAE7D,SAAO;;CA2LT,aAAa,UACX,IACA,iBACA,MAC0B;EAC1B,MAAM,WAAW,SAAS;AAC1B,UAAQ,IAAI,8BAA8B,KAAK,gBAAgB,GAAG;EAElE,MAAM,QAAQ,MAAM,GAAG,KACrB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;SA0GA,CAAC,SAAS,CACX;AAED,SADY,EAAE,MAAM,cAAc,CAAC,MAAM,MAAM,GAAG,SAAS;;;;;;CAQ7D,MAAM,qBAA8C;AAuDlD,SAtDgB,MAAM,KAAK,GAAG,KAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAqD7C;;;4BAzuBU,oBAAmC,OAAO,OAAO;CAC/D,MAAM;CACN,WAAW;CACZ,CAAC,CAAC;4BA2XqB,kBAAiB,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oFAsLmC"}
1
+ {"version":3,"file":"statistics.mjs","names":[],"sources":["../../src/optimizer/statistics.ts"],"sourcesContent":["import { gray } from \"colorette\";\nimport dedent from \"dedent\";\nimport { z } from \"zod\";\nimport type {\n Postgres,\n PostgresTransaction,\n PostgresVersion,\n} from \"../sql/database.ts\";\n\ntype StaValueKind = \"real\" | \"text\" | \"boolean\" | null;\n\nexport type Path = string;\n\nexport const StatisticsSource = z.union([\n z.object({\n kind: z.literal(\"path\"),\n path: z.string().min(1),\n }),\n z.object({\n kind: z.literal(\"inline\"),\n }),\n]);\n\nexport const ExportedStatsStatistics = z.object({\n stawidth: z.number(),\n stainherit: z.boolean().default(false),\n // 0 representing unknown\n stadistinct: z.number(),\n // this has no \"nullable\" state\n stanullfrac: z.number(),\n stakind1: z.number().min(0),\n stakind2: z.number().min(0),\n stakind3: z.number().min(0),\n stakind4: z.number().min(0),\n stakind5: z.number().min(0),\n staop1: z.string(),\n staop2: z.string(),\n staop3: z.string(),\n staop4: z.string(),\n staop5: z.string(),\n stacoll1: z.string(),\n stacoll2: z.string(),\n stacoll3: z.string(),\n stacoll4: z.string(),\n stacoll5: z.string(),\n stanumbers1: z.array(z.number()).nullable(),\n stanumbers2: z.array(z.number()).nullable(),\n stanumbers3: z.array(z.number()).nullable(),\n stanumbers4: z.array(z.number()).nullable(),\n stanumbers5: z.array(z.number()).nullable(),\n // theoretically... this could only be strings and numbers\n // but we don't have a crystal ball\n stavalues1: z.array(z.any()).nullable(),\n stavalues2: z.array(z.any()).nullable(),\n stavalues3: z.array(z.any()).nullable(),\n stavalues4: z.array(z.any()).nullable(),\n stavalues5: z.array(z.any()).nullable(),\n});\n\nexport const ExportedStatsColumns = z.object({\n columnName: z.string(),\n attlen: z.number().nullable(),\n dataType: z.string().optional(),\n stats: ExportedStatsStatistics.nullable(),\n});\n\nexport const ExportedStatsIndex = z.object({\n indexName: z.string(),\n amname: z.string().default(\"btree\"),\n relpages: z.number(),\n reltuples: z.number(),\n relallvisible: z.number(),\n relallfrozen: z.number().optional(),\n fillfactor: z.number().default(90),\n columns: z.array(z.object({ attlen: z.number().nullable() })).default([]),\n});\n\n// This should match the output of the `_qd_dump_stats` function in the analyzer README.md\n// Need to make sure this is versioned to accept ALL potential outputs from every version of\n// dump functions we make public\nexport const ExportedStatsV1 = z.object({\n tableName: z.string(),\n schemaName: z.string(),\n // can be negative\n relpages: z.number(),\n // can be negative\n reltuples: z.number(),\n relallvisible: z.number(),\n // only postgres 18+\n relallfrozen: z.number().optional(),\n columns: z.array(ExportedStatsColumns).default([]),\n indexes: z.array(ExportedStatsIndex),\n});\n\nexport const ExportedStats = z.union([ExportedStatsV1]);\n\nexport type ExportedStats = z.infer<typeof ExportedStats>;\n\nexport const StatisticsMode = z.discriminatedUnion(\"kind\", [\n z.object({\n kind: z.literal(\"fromAssumption\"),\n reltuples: z.number().min(0),\n }),\n z.object({\n kind: z.literal(\"fromStatisticsExport\"),\n stats: z.array(ExportedStats),\n source: StatisticsSource,\n }),\n]);\n\nexport type StatisticsMode = z.infer<typeof StatisticsMode>;\n\nexport const ComputedColumnStats = z.object({\n schema_name: z.string(),\n table_name: z.string(),\n column_name: z.string(),\n data_type: z.string().optional(),\n stainherit: z.boolean(),\n stanullfrac: z.number(),\n stawidth: z.number(),\n stadistinct: z.number(),\n stakind1: z.number(),\n stakind2: z.number(),\n stakind3: z.number(),\n stakind4: z.number(),\n stakind5: z.number(),\n staop1: z.string(),\n staop2: z.string(),\n staop3: z.string(),\n staop4: z.string(),\n staop5: z.string(),\n stacoll1: z.string(),\n stacoll2: z.string(),\n stacoll3: z.string(),\n stacoll4: z.string(),\n stacoll5: z.string(),\n stanumbers1: z.array(z.number()).nullable(),\n stanumbers2: z.array(z.number()).nullable(),\n stanumbers3: z.array(z.number()).nullable(),\n stanumbers4: z.array(z.number()).nullable(),\n stanumbers5: z.array(z.number()).nullable(),\n stavalues1: z.array(z.any()).nullable(),\n stavalues2: z.array(z.any()).nullable(),\n stavalues3: z.array(z.any()).nullable(),\n stavalues4: z.array(z.any()).nullable(),\n stavalues5: z.array(z.any()).nullable(),\n _value_type1: z.string().nullable(),\n _value_type2: z.string().nullable(),\n _value_type3: z.string().nullable(),\n _value_type4: z.string().nullable(),\n _value_type5: z.string().nullable(),\n});\n\nexport type ComputedColumnStats = z.infer<typeof ComputedColumnStats>;\n\nexport const ComputedReltuples = z.object({\n relname: z.string(),\n schema_name: z.string(),\n reltuples: z.number(),\n relpages: z.number(),\n relallvisible: z.number(),\n relallfrozen: z.number().optional(),\n});\n\nexport type ComputedReltuples = z.infer<typeof ComputedReltuples>;\n\nexport const ComputedStats = z.object({\n columnStats: z.array(ComputedColumnStats),\n reltuples: z.array(ComputedReltuples),\n});\n\nexport type ComputedStats = z.infer<typeof ComputedStats>;\n\nconst DEFAULT_RELTUPLES = 10_000_000;\nconst DEFAULT_RELPAGES = 1;\n// it's _very_ rare that the default page size is ever changed\nconst DEFAULT_PAGE_SIZE = 2 ** 13;\n\nfunction estimateStawidth(col: { attlen?: number | null }): number {\n return col.attlen ?? 32;\n}\n\nfunction estimateRelpages(\n reltuples: number,\n columns: { attlen: number | null }[],\n): number {\n const rowWidth =\n // 23 byte tuple header + 4 bytes alignment/null bitmap\n columns.reduce((sum, col) => sum + estimateStawidth(col), 0) + 27;\n return Math.ceil((reltuples * rowWidth) / DEFAULT_PAGE_SIZE);\n}\n\nfunction estimateIndexRelpages(\n reltuples: number,\n columns: { attlen: number | null }[],\n fillfactor: number,\n amname: string,\n tableRelpages: number,\n): number {\n if (amname === \"gin\") {\n // GIN has an inverted structure; distinct element counts per row are unknown\n // without real data, so fall back to a ratio of the table page count\n return Math.ceil(tableRelpages * 0.3);\n }\n // 16 bytes btree entry overhead per key in addition to the key width\n const keyWidth = columns.reduce(\n (sum, col) => sum + estimateStawidth(col) + 16,\n 0,\n );\n return Math.ceil((reltuples * keyWidth) / DEFAULT_PAGE_SIZE / fillfactor);\n}\n\nexport const DUMP_STATS_SQL = dedent`\n WITH table_columns AS (\n SELECT\n cl.relname,\n n.nspname,\n cl.reltuples,\n cl.relpages,\n cl.relallvisible,\n -- cl.relallfrozen,\n json_agg(\n json_build_object(\n 'columnName', a.attname,\n 'attlen', CASE WHEN a.attlen > 0 THEN a.attlen ELSE NULL END,\n 'dataType', t.typname,\n 'stats', (\n SELECT json_build_object(\n 'starelid', s.starelid,\n 'staattnum', s.staattnum,\n 'stanullfrac', s.stanullfrac,\n 'stawidth', s.stawidth,\n 'stadistinct', s.stadistinct,\n 'stakind1', s.stakind1, 'staop1', s.staop1, 'stacoll1', s.stacoll1, 'stanumbers1', s.stanumbers1,\n 'stakind2', s.stakind2, 'staop2', s.staop2, 'stacoll2', s.stacoll2, 'stanumbers2', s.stanumbers2,\n 'stakind3', s.stakind3, 'staop3', s.staop3, 'stacoll3', s.stacoll3, 'stanumbers3', s.stanumbers3,\n 'stakind4', s.stakind4, 'staop4', s.staop4, 'stacoll4', s.stacoll4, 'stanumbers4', s.stanumbers4,\n 'stakind5', s.stakind5, 'staop5', s.staop5, 'stacoll5', s.stacoll5, 'stanumbers5', s.stanumbers5,\n 'stavalues1', s.stavalues1,\n 'stavalues2', s.stavalues2,\n 'stavalues3', s.stavalues3,\n 'stavalues4', s.stavalues4,\n 'stavalues5', s.stavalues5\n )\n FROM pg_statistic s\n WHERE s.starelid = a.attrelid AND s.staattnum = a.attnum\n )\n )\n ORDER BY a.attnum\n ) AS columns\n FROM pg_class cl\n JOIN pg_namespace n ON n.oid = cl.relnamespace\n JOIN pg_attribute a ON a.attrelid = cl.oid AND a.attnum > 0 AND NOT a.attisdropped\n JOIN pg_type t ON t.oid = a.atttypid\n WHERE cl.relkind = 'r'\n AND n.nspname NOT IN ('pg_catalog', 'information_schema', 'tiger', 'tiger_data', 'topology')\n AND cl.relname NOT IN ('pg_stat_statements', 'pg_stat_statements_info')\n GROUP BY cl.relname, n.nspname, cl.reltuples, cl.relpages, cl.relallvisible\n ),\n table_indexes AS (\n SELECT\n t.relname AS table_name,\n json_agg(\n json_build_object(\n 'indexName', i.relname,\n 'amname', am.amname,\n 'reltuples', i.reltuples,\n 'relpages', i.relpages,\n 'relallvisible', i.relallvisible,\n -- 'relallfrozen', i.relallfrozen,\n 'fillfactor', COALESCE(\n (\n SELECT (regexp_match(opt, 'fillfactor=(\\\\d+)'))[1]::integer\n FROM unnest(i.reloptions) AS opt\n WHERE opt LIKE 'fillfactor=%'\n LIMIT 1\n ),\n 90\n ),\n 'columns', COALESCE(\n (\n SELECT json_agg(json_build_object(\n 'attlen', CASE WHEN a.attlen > 0 THEN a.attlen ELSE NULL END\n ) ORDER BY col_pos.ord)\n FROM unnest(ix.indkey) WITH ORDINALITY AS col_pos(attnum, ord)\n JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = col_pos.attnum\n WHERE col_pos.attnum > 0\n ),\n '[]'::json\n )\n )\n ) AS indexes\n FROM pg_class t\n JOIN pg_index ix ON ix.indrelid = t.oid\n JOIN pg_class i ON i.oid = ix.indexrelid\n JOIN pg_am am ON am.oid = i.relam\n JOIN pg_namespace n ON n.oid = t.relnamespace\n WHERE t.relname NOT LIKE 'pg_%'\n AND n.nspname <> 'information_schema'\n AND n.nspname NOT IN ('tiger', 'tiger_data', 'topology')\n GROUP BY t.relname\n )\n SELECT json_agg(\n json_build_object(\n 'tableName', tc.relname,\n 'schemaName', tc.nspname,\n 'reltuples', tc.reltuples,\n 'relpages', tc.relpages,\n 'relallvisible', tc.relallvisible,\n -- 'relallfrozen', tc.relallfrozen,\n 'columns', COALESCE(tc.columns, '[]'::json),\n 'indexes', COALESCE(ti.indexes, '[]'::json)\n )\n )\n FROM table_columns tc\n LEFT JOIN table_indexes ti\n ON ti.table_name = tc.relname\n`;\n\nexport class Statistics {\n readonly mode: StatisticsMode;\n readonly computedStats: ComputedStats;\n private readonly exportedMetadata: ExportedStats[] | undefined;\n // preventing accidental internal mutations\n static readonly defaultStatsMode: StatisticsMode = Object.freeze({\n kind: \"fromAssumption\",\n reltuples: DEFAULT_RELTUPLES,\n });\n constructor(\n private readonly db: Postgres,\n public readonly postgresVersion: PostgresVersion,\n public readonly ownMetadata: ExportedStats[],\n statsMode: StatisticsMode,\n ) {\n if (statsMode) {\n this.mode = statsMode;\n if (statsMode.kind === \"fromStatisticsExport\") {\n this.exportedMetadata = statsMode.stats;\n }\n } else {\n this.mode = Statistics.defaultStatsMode;\n }\n this.computedStats = this.buildComputedStats();\n }\n\n private buildComputedStats(): ComputedStats {\n const columnStats: ComputedColumnStats[] = [];\n const reltuples: ComputedReltuples[] = [];\n\n for (const table of this.ownMetadata) {\n const targetTable = this.exportedMetadata?.find(\n (m) =>\n m.tableName === table.tableName && m.schemaName === table.schemaName,\n );\n const target = targetTable?.columns ?? table.columns;\n\n for (const column of target) {\n const { stats } = column;\n if (!stats || this.mode.kind === \"fromAssumption\") {\n const stawidth = stats?.stawidth || estimateStawidth(column);\n columnStats.push({\n schema_name: table.schemaName,\n table_name: table.tableName,\n column_name: column.columnName,\n data_type: column.dataType,\n stainherit: false,\n stanullfrac: 0.04,\n stawidth,\n stadistinct: -0.9,\n stakind1: 0,\n stakind2: 0,\n stakind3: 3,\n stakind4: 0,\n stakind5: 0,\n stacoll1: \"0\",\n stacoll2: \"0\",\n stacoll3: \"0\",\n stacoll4: \"0\",\n stacoll5: \"0\",\n staop1: \"0\",\n staop2: \"0\",\n staop3: \"0\",\n staop4: \"0\",\n staop5: \"0\",\n stanumbers1: null,\n stanumbers2: null,\n stanumbers3: [0.9],\n stanumbers4: null,\n stanumbers5: null,\n stavalues1: null,\n stavalues2: null,\n stavalues3: null,\n stavalues4: null,\n stavalues5: null,\n _value_type1: \"real\",\n _value_type2: \"real\",\n _value_type3: \"real\",\n _value_type4: \"real\",\n _value_type5: \"real\",\n });\n } else {\n columnStats.push({\n schema_name: table.schemaName,\n table_name: table.tableName,\n column_name: column.columnName,\n data_type: column.dataType,\n stainherit: stats.stainherit ?? false,\n stanullfrac: stats.stanullfrac,\n stawidth: stats.stawidth,\n stadistinct: stats.stadistinct,\n stakind1: stats.stakind1,\n stakind2: stats.stakind2,\n stakind3: stats.stakind3,\n stakind4: stats.stakind4,\n stakind5: stats.stakind5,\n staop1: stats.staop1,\n staop2: stats.staop2,\n staop3: stats.staop3,\n staop4: stats.staop4,\n staop5: stats.staop5,\n stacoll1: stats.stacoll1,\n stacoll2: stats.stacoll2,\n stacoll3: stats.stacoll3,\n stacoll4: stats.stacoll4,\n stacoll5: stats.stacoll5,\n stanumbers1: stats.stanumbers1,\n stanumbers2: stats.stanumbers2,\n stanumbers3: stats.stanumbers3,\n stanumbers4: stats.stanumbers4,\n stanumbers5: stats.stanumbers5,\n stavalues1: Statistics.safeStavalues(stats.stavalues1),\n stavalues2: Statistics.safeStavalues(stats.stavalues2),\n stavalues3: Statistics.safeStavalues(stats.stavalues3),\n stavalues4: Statistics.safeStavalues(stats.stavalues4),\n stavalues5: Statistics.safeStavalues(stats.stavalues5),\n _value_type1: this.stavalueKind(\n Statistics.safeStavalues(stats.stavalues1),\n ),\n _value_type2: this.stavalueKind(\n Statistics.safeStavalues(stats.stavalues2),\n ),\n _value_type3: this.stavalueKind(\n Statistics.safeStavalues(stats.stavalues3),\n ),\n _value_type4: this.stavalueKind(\n Statistics.safeStavalues(stats.stavalues4),\n ),\n _value_type5: this.stavalueKind(\n Statistics.safeStavalues(stats.stavalues5),\n ),\n });\n }\n }\n\n let tableReltuples: number;\n let tableRelpages: number;\n let relallvisible = 0;\n let relallfrozen: number | undefined;\n\n if (this.mode.kind === \"fromAssumption\") {\n tableReltuples = this.mode.reltuples;\n tableRelpages = estimateRelpages(tableReltuples, table.columns);\n } else if (targetTable) {\n tableReltuples = targetTable.reltuples;\n tableRelpages = targetTable.relpages;\n relallvisible = targetTable.relallvisible;\n relallfrozen = targetTable.relallfrozen;\n } else {\n tableReltuples = DEFAULT_RELTUPLES;\n tableRelpages = DEFAULT_RELPAGES;\n }\n\n reltuples.push({\n relname: table.tableName,\n schema_name: table.schemaName,\n reltuples: tableReltuples,\n relpages: tableRelpages,\n relallfrozen,\n relallvisible,\n });\n\n if (this.mode.kind === \"fromAssumption\") {\n for (const index of table.indexes) {\n const indexRelpages = estimateIndexRelpages(\n this.mode.reltuples,\n index.columns,\n index.fillfactor / 100,\n index.amname,\n tableRelpages,\n );\n reltuples.push({\n relname: index.indexName,\n schema_name: table.schemaName,\n reltuples: this.mode.reltuples,\n relpages: indexRelpages,\n relallfrozen: 0,\n relallvisible: indexRelpages,\n });\n }\n } else if (targetTable) {\n for (const index of targetTable.indexes) {\n reltuples.push({\n relname: index.indexName,\n schema_name: targetTable.schemaName,\n reltuples: index.reltuples,\n relpages: index.relpages,\n relallfrozen: index.relallfrozen,\n relallvisible: index.relallvisible,\n });\n }\n }\n }\n\n return { columnStats, reltuples };\n }\n\n static statsModeFromAssumption({\n reltuples,\n }: {\n reltuples: number;\n }): StatisticsMode {\n return {\n kind: \"fromAssumption\",\n reltuples,\n };\n }\n\n /**\n * Create a statistic mode from stats exported from another database\n **/\n static statsModeFromExport(stats: ExportedStats[]): StatisticsMode {\n return {\n kind: \"fromStatisticsExport\",\n source: { kind: \"inline\" },\n stats,\n };\n }\n\n static async fromPostgres(\n db: Postgres,\n statsMode: StatisticsMode,\n ): Promise<Statistics> {\n const version = await db.serverNum();\n const ownStats = await Statistics.dumpStats(db, version);\n return new Statistics(db, version, ownStats, statsMode);\n }\n\n restoreStats(tx: PostgresTransaction) {\n // if (this.postgresVersion < \"180000\") {\n return this.restoreStats17(tx);\n // }\n // return this.restoreStats18(tx);\n }\n\n approximateTotalRows() {\n if (!this.exportedMetadata) {\n return 0;\n }\n let totalRows = 0;\n for (const table of this.exportedMetadata) {\n totalRows += table.reltuples;\n }\n return totalRows;\n }\n\n /**\n * We have to cast stavaluesN to the correct type\n * This derives that type for us so it can be used in `array_in`\n */\n private stavalueKind(values: unknown[] | null): StaValueKind {\n if (!values || values.length === 0) {\n return null;\n }\n const [elem] = values;\n if (typeof elem === \"number\") {\n return \"real\";\n } else if (typeof elem === \"boolean\") {\n return \"boolean\";\n }\n // is everything else a text? What about strinfied dates?\n // we might need column metadata access here if we do\n return \"text\";\n }\n\n /**\n * PostgreSQL's anyarray columns in pg_statistic can hold arrays of arrays\n * for columns with array types (e.g. text[], int4[]). These create\n * multidimensional arrays that can be \"ragged\" (sub-arrays with different\n * lengths). jsonb_to_recordset can't reconstruct ragged multidimensional\n * arrays from JSON, so we need to drop these values.\n */\n private static safeStavalues(values: unknown[] | null): unknown[] | null {\n if (!values || values.length === 0) return values;\n if (values.some((v) => Array.isArray(v))) {\n console.warn(\"Discarding ragged multidimensional stavalues array\");\n return null;\n }\n return values;\n }\n\n private async restoreStats17(tx: PostgresTransaction) {\n const warnings = {\n tablesNotInExports: [] as string[],\n tablesNotInTest: [] as string[],\n tableNotAnalyzed: [] as string[],\n statsMissing: [] as {\n statistic: string;\n table: string;\n schema: string;\n column: string;\n }[],\n };\n\n const processedTables = new Set<string>(\n this.ownMetadata.map((t) => `${t.schemaName}.${t.tableName}`),\n );\n\n const columnStatsUpdatePromise = tx\n .exec(Statistics.columnStatsSQL, [this.computedStats.columnStats])\n .catch((err) => {\n console.error(\"Something wrong wrong updating column stats\");\n console.error(err);\n throw err;\n });\n\n /**\n * Postgres has 5 different slots for storing statistics per column and a potentially unlimited\n * number of statistic types to choose from. Each code in `stakindN` can mean different things.\n * Some statistics are just numerical values such as `n_distinct` and `correlation`, meaning\n * they're only derived from `stanumbersN` and the value of `stanumbersN` is never read.\n * Others take advantage of the `stavaluesN` columns which use `anyarray` type to store\n * concrete values internally for things like histogram bounds.\n * Unfortunately we cannot change anyarrays without a C extension.\n *\n * (1) = most common values\n * (2) = scalar histogram\n * (3) = correlation <- can change\n * (4) = most common elements\n * (5) = distinct elem count histogram <- can change\n * (6) = length histogram (?) These don't appear in pg_stats\n * (7) = bounds histogram (?) These don't appear in pg_stats\n * (N) = potentially many more kinds of statistics. But postgres <=18 only uses these 7.\n *\n * What we're doing here is setting ANY statistic we cannot directly control\n * (anything that relies on stavaluesN) to 0 to make sure the planner isn't influenced by what\n * what the db collected from the test data.\n * Because we do our tests with `generic_plan` it seems it's already unlikely that the planner will be\n * using things like common values or histogram bounds to make the planning decisions we care about.\n * This is a just in case.\n */\n const reltuplesQuery = dedent`\n update pg_class p\n set reltuples = v.reltuples,\n relpages = v.relpages,\n -- relallfrozen = case when v.relallfrozen is null then p.relallfrozen else v.relallfrozen end,\n relallvisible = case when v.relallvisible is null then p.relallvisible else v.relallvisible end\n from jsonb_to_recordset($1::jsonb)\n as v(reltuples real, relpages integer, relallfrozen integer, relallvisible integer, relname text, schema_name text)\n where p.relname = v.relname\n and p.relnamespace = (select oid from pg_namespace where nspname = v.schema_name)\n returning p.relname, p.relnamespace, p.reltuples, p.relpages;\n `;\n\n const reltuplesPromise = tx\n .exec(reltuplesQuery, [this.computedStats.reltuples])\n .catch((err) => {\n console.error(\"Something went wrong updating reltuples/relpages\");\n console.error(err);\n return err;\n });\n\n if (this.exportedMetadata) {\n for (const table of this.exportedMetadata) {\n const tableExists = processedTables.has(\n `${table.schemaName}.${table.tableName}`,\n );\n if (tableExists && table.reltuples === -1) {\n console.warn(\n `Table ${table.tableName} has reltuples -1. Your production database is probably not analyzed properly`,\n );\n warnings.tableNotAnalyzed.push(\n `${table.schemaName}.${table.tableName}`,\n );\n }\n if (tableExists) {\n continue;\n }\n warnings.tablesNotInTest.push(`${table.schemaName}.${table.tableName}`);\n }\n }\n const [statsUpdates, reltuplesUpdates] = await Promise.all([\n columnStatsUpdatePromise,\n reltuplesPromise,\n ]);\n const updatedColumnsProperly = statsUpdates\n ? statsUpdates.length === this.computedStats.columnStats.length\n : true;\n if (!updatedColumnsProperly) {\n console.error(`Did not update expected column stats`);\n }\n if (reltuplesUpdates.length !== this.computedStats.reltuples.length) {\n console.error(`Did not update expected reltuples/relpages`);\n }\n return warnings;\n }\n\n private static readonly columnStatsSQL = dedent`\n WITH input AS (\n SELECT\n c.oid AS starelid,\n a.attnum AS staattnum,\n v.stainherit,\n v.stanullfrac,\n v.stawidth,\n v.stadistinct,\n v.stakind1,\n v.stakind2,\n v.stakind3,\n v.stakind4,\n v.stakind5,\n v.staop1,\n v.staop2,\n v.staop3,\n v.staop4,\n v.staop5,\n v.stacoll1,\n v.stacoll2,\n v.stacoll3,\n v.stacoll4,\n v.stacoll5,\n v.stanumbers1,\n v.stanumbers2,\n v.stanumbers3,\n v.stanumbers4,\n v.stanumbers5,\n v.stavalues1,\n v.stavalues2,\n v.stavalues3,\n v.stavalues4,\n v.stavalues5,\n _value_type1,\n _value_type2,\n _value_type3,\n _value_type4,\n _value_type5\n FROM jsonb_to_recordset($1::jsonb) AS v(\n schema_name text,\n table_name text,\n column_name text,\n stainherit boolean,\n stanullfrac real,\n stawidth integer,\n stadistinct real,\n stakind1 real,\n stakind2 real,\n stakind3 real,\n stakind4 real,\n stakind5 real,\n staop1 oid,\n staop2 oid,\n staop3 oid,\n staop4 oid,\n staop5 oid,\n stacoll1 oid,\n stacoll2 oid,\n stacoll3 oid,\n stacoll4 oid,\n stacoll5 oid,\n stanumbers1 real[],\n stanumbers2 real[],\n stanumbers3 real[],\n stanumbers4 real[],\n stanumbers5 real[],\n stavalues1 text[],\n stavalues2 text[],\n stavalues3 text[],\n stavalues4 text[],\n stavalues5 text[],\n _value_type1 text,\n _value_type2 text,\n _value_type3 text,\n _value_type4 text,\n _value_type5 text\n )\n JOIN pg_class c ON c.relname = v.table_name\n JOIN pg_namespace n ON n.oid = c.relnamespace AND n.nspname = v.schema_name\n JOIN pg_attribute a ON a.attrelid = c.oid AND a.attname = v.column_name\n ),\n updated AS (\n UPDATE pg_statistic s\n SET\n stanullfrac = i.stanullfrac,\n stawidth = i.stawidth,\n stadistinct = i.stadistinct,\n stakind1 = i.stakind1,\n stakind2 = i.stakind2,\n stakind3 = i.stakind3,\n stakind4 = i.stakind4,\n stakind5 = i.stakind5,\n staop1 = i.staop1,\n staop2 = i.staop2,\n staop3 = i.staop3,\n staop4 = i.staop4,\n staop5 = i.staop5,\n stacoll1 = i.stacoll1,\n stacoll2 = i.stacoll2,\n stacoll3 = i.stacoll3,\n stacoll4 = i.stacoll4,\n stacoll5 = i.stacoll5,\n stanumbers1 = i.stanumbers1,\n stanumbers2 = i.stanumbers2,\n stanumbers3 = i.stanumbers3,\n stanumbers4 = i.stanumbers4,\n stanumbers5 = i.stanumbers5,\n stavalues1 = case\n when i.stavalues1 is null then null\n else array_in(i.stavalues1::text::cstring, i._value_type1::regtype::oid, -1)\n end,\n stavalues2 = case\n when i.stavalues2 is null then null\n else array_in(i.stavalues2::text::cstring, i._value_type2::regtype::oid, -1)\n end,\n stavalues3 = case\n when i.stavalues3 is null then null\n else array_in(i.stavalues3::text::cstring, i._value_type3::regtype::oid, -1)\n end,\n stavalues4 = case\n when i.stavalues4 is null then null\n else array_in(i.stavalues4::text::cstring, i._value_type4::regtype::oid, -1)\n end,\n stavalues5 = case\n when i.stavalues5 is null then null\n else array_in(i.stavalues5::text::cstring, i._value_type5::regtype::oid, -1)\n end\n -- stavalues1 = i.stavalues1,\n -- stavalues2 = i.stavalues2,\n -- stavalues3 = i.stavalues3,\n -- stavalues4 = i.stavalues4,\n -- stavalues5 = i.stavalues5\n FROM input i\n WHERE s.starelid = i.starelid AND s.staattnum = i.staattnum AND s.stainherit = i.stainherit\n RETURNING s.starelid, s.staattnum, s.stainherit, s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5\n ),\n inserted as (\n INSERT INTO pg_statistic (\n starelid, staattnum, stainherit,\n stanullfrac, stawidth, stadistinct,\n stakind1, stakind2, stakind3, stakind4, stakind5,\n staop1, staop2, staop3, staop4, staop5,\n stacoll1, stacoll2, stacoll3, stacoll4, stacoll5,\n stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5,\n stavalues1, stavalues2, stavalues3, stavalues4, stavalues5\n )\n SELECT\n i.starelid, i.staattnum, i.stainherit,\n i.stanullfrac, i.stawidth, i.stadistinct,\n i.stakind1, i.stakind2, i.stakind3, i.stakind4, i.stakind5,\n i.staop1, i.staop2, i.staop3, i.staop4, i.staop5,\n i.stacoll1, i.stacoll2, i.stacoll3, i.stacoll4, i.stacoll5,\n i.stanumbers1, i.stanumbers2, i.stanumbers3, i.stanumbers4, i.stanumbers5,\n -- i.stavalues1, i.stavalues2, i.stavalues3, i.stavalues4, i.stavalues5,\n case\n when i.stavalues1 is null then null\n else array_in(i.stavalues1::text::cstring, i._value_type1::regtype::oid, -1)\n end,\n case\n when i.stavalues2 is null then null\n else array_in(i.stavalues2::text::cstring, i._value_type2::regtype::oid, -1)\n end,\n case\n when i.stavalues3 is null then null\n else array_in(i.stavalues3::text::cstring, i._value_type3::regtype::oid, -1)\n end,\n case\n when i.stavalues4 is null then null\n else array_in(i.stavalues4::text::cstring, i._value_type4::regtype::oid, -1)\n end,\n case\n when i.stavalues5 is null then null\n else array_in(i.stavalues5::text::cstring, i._value_type5::regtype::oid, -1)\n end\n -- i._value_type1, i._value_type2, i._value_type3, i._value_type4, i._value_type5\n FROM input i\n LEFT JOIN updated u\n ON i.starelid = u.starelid AND i.staattnum = u.staattnum AND i.stainherit = u.stainherit\n WHERE u.starelid IS NULL\n returning starelid, staattnum, stainherit, stakind1, stakind2, stakind3, stakind4, stakind5\n )\n select * from updated union all (select * from inserted); -- @qd_introspection`;\n\n static async dumpStats(\n db: PostgresTransaction,\n postgresVersion: PostgresVersion,\n ): Promise<ExportedStats[]> {\n console.log(`dumping stats for postgres ${gray(postgresVersion)}`);\n const stats = await db.exec<{ json_agg: ExportedStats[] }>(DUMP_STATS_SQL);\n const out = z.array(ExportedStats).parse(stats[0].json_agg);\n return out;\n }\n\n /**\n * Returns all indexes in the database.\n * ONLY handles regular btree indexes\n */\n async getExistingIndexes(): Promise<IndexedTable[]> {\n const indexes = await this.db.exec<IndexedTable>(`\n WITH partitioned_tables AS (\n SELECT\n inhparent::regclass AS parent_table,\n inhrelid::regclass AS partition_table\n FROM\n pg_inherits\n )\n SELECT\n n.nspname AS schema_name,\n COALESCE(pt.parent_table::text, t.relname) AS table_name,\n i.relname AS index_name,\n ix.indisprimary as is_primary,\n ix.indisunique as is_unique,\n am.amname AS index_type,\n array_agg(\n CASE\n -- Handle regular columns\n WHEN a.attname IS NOT NULL THEN\n json_build_object('name', a.attname, 'order',\n CASE\n WHEN (indoption[array_position(ix.indkey, a.attnum)] & 1) = 1 THEN 'DESC'\n ELSE 'ASC'\n END,\n 'opclass', CASE WHEN opc.opcdefault THEN NULL ELSE opc.opcname END)\n -- Handle expressions\n ELSE\n json_build_object('name', pg_get_expr((ix.indexprs)::pg_node_tree, t.oid), 'order',\n CASE\n WHEN (indoption[array_position(ix.indkey, k.attnum)] & 1) = 1 THEN 'DESC'\n ELSE 'ASC'\n END,\n 'opclass', CASE WHEN opc.opcdefault THEN NULL ELSE opc.opcname END)\n END\n ORDER BY array_position(ix.indkey, k.attnum)\n ) AS index_columns\n FROM\n pg_class t\n LEFT JOIN partitioned_tables pt ON t.oid = pt.partition_table\n JOIN pg_index ix ON t.oid = ix.indrelid\n JOIN pg_class i ON i.oid = ix.indexrelid\n JOIN pg_am am ON i.relam = am.oid\n LEFT JOIN LATERAL unnest(ix.indkey) WITH ORDINALITY k(attnum, ordinality) ON true\n LEFT JOIN pg_attribute a ON a.attnum = k.attnum AND a.attrelid = t.oid\n LEFT JOIN pg_opclass opc ON opc.oid = ix.indclass[k.ordinality - 1]\n JOIN pg_namespace n ON t.relnamespace = n.oid\n WHERE\n n.nspname not like 'pg_%' and\n n.nspname <> 'information_schema'\n GROUP BY\n n.nspname, COALESCE(pt.parent_table::text, t.relname), i.relname, am.amname, ix.indisprimary, ix.indisunique\n ORDER BY\n COALESCE(pt.parent_table::text, t.relname), i.relname; -- @qd_introspection\n `);\n return indexes;\n }\n}\n\nexport type ColumnMetadata = {\n columnName: string;\n dataType: string;\n isNullable: boolean;\n stats: ColumnStats | null;\n};\n\ntype ColumnStats = {\n stainherit: boolean;\n stanullfrac: number;\n stawidth: number;\n stadistinct: number;\n stakind1: number;\n stakind2: number;\n stakind3: number;\n stakind4: number;\n stakind5: number;\n staop1: number;\n staop2: number;\n staop3: number;\n staop4: number;\n staop5: number;\n stacoll1: number;\n stacoll2: number;\n stacoll3: number;\n stacoll4: number;\n stacoll5: number;\n stanumbers1: number;\n stanumbers2: number;\n stanumbers3: number;\n stanumbers4: number;\n stanumbers5: number;\n};\n\nexport type TableMetadata = {\n tableName: string;\n schemaName: string;\n reltuples: number;\n relpages: number;\n relallvisible: number;\n relallfrozen?: number;\n columns: ColumnMetadata[];\n};\n\ntype TableName = string;\nexport type TableStats = {\n tupleEstimate: bigint;\n pageCount: number;\n};\n\nexport type SerializeResult = {\n schema: TableMetadata[];\n serialized: string;\n sampledRecords: Record<TableName, number>;\n};\n\nexport type IndexOrder = \"ASC\" | \"DESC\";\n\nexport type IndexedTable = {\n index_columns: Array<{ name: string; order: IndexOrder; opclass?: string }>;\n is_primary: boolean;\n is_unique: boolean;\n index_name: string;\n // eslint-disable-next-line @typescript-eslint/ban-types\n index_type: \"btree\" | \"gin\" | (string & {});\n // this is always public\n schema_name: string;\n table_name: string;\n};\n"],"mappings":"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"}
@@ -105,6 +105,10 @@ var Walker = class Walker {
105
105
  this.add(node, { ignored: true });
106
106
  return;
107
107
  }
108
+ if (stack[i] === "TypeCast" && stack[i + 1] === "arg") {
109
+ this.add(node, { ignored: true });
110
+ return;
111
+ }
108
112
  }
109
113
  this.add(node);
110
114
  }
@@ -1 +1 @@
1
- {"version":3,"file":"walker.cjs","names":["parseNudges","is","isANode","getNodeKind"],"sources":["../../src/sql/walker.ts"],"sourcesContent":["import type { Node, NullTestType, RangeVar } from \"@pgsql/types\";\nimport { deparseSync } from \"pgsql-deparser\";\nimport type {\n DiscoveredColumnReference,\n JsonbOperator,\n SortContext,\n} from \"./analyzer.js\";\nimport { getNodeKind, is, isANode, type KeysOfUnion } from \"./ast-utils.js\";\nimport { parseNudges } from \"./nudges.js\";\nimport type { Nudge } from \"./nudges.js\";\n\nconst JSONB_EXTRACTION_OPS = new Set([\"->\", \"->>\"]);\nconst COMPARISON_OPS = new Set([\n \"=\",\n \"<>\",\n \"!=\",\n \"<\",\n \"<=\",\n \">\",\n \">=\",\n \"~~\",\n \"~~*\",\n \"!~~\",\n \"!~~*\",\n]);\n\n/** Information about tables that appear in the query */\nexport type TableMappings = Map<string, ColumnReferencePart>;\ntype SeenReferences = Map<string, number>;\n\n/**\n * Walks the AST of a sql query and extracts query metadata.\n * This pattern is used to segregate the mutable state that's more common for the\n * AST walking process from the rest of the analyzer.\n */\nexport class Walker {\n private tableMappings: TableMappings = new Map();\n private tempTables = new Set<string>();\n private highlights: DiscoveredColumnReference[] = [];\n private indexRepresentations = new Set<string>();\n private indexesToCheck: DiscoveredColumnReference[] = [];\n private highlightPositions = new Set<number>();\n // used for tallying the amount of times we see stuff so\n // we have a better idea of what to start off the algorithm with\n private seenReferences: SeenReferences = new Map();\n private shadowedAliases: ColumnReferencePart[] = [];\n private nudges: Nudge[] = [];\n\n constructor(private readonly query: string) {}\n\n walk(root: Node) {\n // reset state in case the class instance is reused\n // reassigning vars here instead of using `map.clear()` to prevent\n // accidentally mutating existing references\n this.tableMappings = new Map();\n this.tempTables = new Set<string>();\n this.highlights = [];\n this.indexRepresentations = new Set<string>();\n this.indexesToCheck = [];\n this.highlightPositions = new Set<number>();\n this.seenReferences = new Map();\n this.shadowedAliases = [];\n this.nudges = [];\n\n Walker.traverse(root, (node, stack) => {\n const nodeNudges = parseNudges(node, stack);\n this.nudges = [...this.nudges, ...nodeNudges];\n\n // comments are not parsed here as they seem to be ignored.\n //\n // results cannot be indexed in any way because they alias a CTE\n // with alias as (select ...)\n // ^^^^^\n if (is(node, \"CommonTableExpr\")) {\n if (node.CommonTableExpr.ctename) {\n this.tempTables.add(node.CommonTableExpr.ctename);\n }\n }\n // results cannot be indexed in any way because they alias a subquery\n // select ... from (...) as alias\n // ^^^^^\n if (is(node, \"RangeSubselect\")) {\n if (node.RangeSubselect.alias?.aliasname) {\n this.tempTables.add(node.RangeSubselect.alias.aliasname);\n }\n }\n // select ... from (...) where col is null\n // ^^^^^^^\n if (is(node, \"NullTest\")) {\n if (\n node.NullTest.arg &&\n node.NullTest.nulltesttype &&\n is(node.NullTest.arg, \"ColumnRef\")\n ) {\n this.add(node.NullTest.arg, {\n where: { nulltest: node.NullTest.nulltesttype },\n });\n }\n }\n // The parser does not wrap the `relation` field of UpdateStmt\n // in a { RangeVar: ... } node (it's a raw RangeVar struct), so the\n // generic RangeVar handler below never sees it. Handle it explicitly.\n if (is(node, \"UpdateStmt\")) {\n if (node.UpdateStmt.relation) {\n this.registerTable(node.UpdateStmt.relation);\n }\n }\n // Same as UpdateStmt: DeleteStmt.relation is a raw RangeVar struct.\n if (is(node, \"DeleteStmt\")) {\n if (node.DeleteStmt.relation) {\n this.registerTable(node.DeleteStmt.relation);\n }\n }\n // can be indexed as the alias refers to a regular table\n // but the alias has to be mapped to the original table name\n // select ... from table as alias\n // ^^^^^\n if (is(node, \"RangeVar\") && node.RangeVar.relname) {\n this.registerTable(node.RangeVar);\n }\n // select ... from table order by col asc\n // ^^^^^^^^^^^^^^^^\n if (is(node, \"SortBy\")) {\n // we don't care about sorting by anything that's not a column reference\n // because it couldn't be indexed anyway.\n // TODO: mark that expression as unindexable? It's just better for debugging\n if (node.SortBy.node && is(node.SortBy.node, \"ColumnRef\")) {\n this.add(node.SortBy.node, {\n sort: {\n dir: node.SortBy.sortby_dir ?? \"SORTBY_DEFAULT\",\n nulls: node.SortBy.sortby_nulls ?? \"SORTBY_NULLS_DEFAULT\",\n },\n });\n }\n }\n // select ... from table1 join table2 t2 on table1.col = t2.col\n // ^^\n if (is(node, \"JoinExpr\") && node.JoinExpr.quals) {\n if (is(node.JoinExpr.quals, \"A_Expr\")) {\n if (\n node.JoinExpr.quals.A_Expr.lexpr &&\n is(node.JoinExpr.quals.A_Expr.lexpr, \"ColumnRef\")\n ) {\n this.add(node.JoinExpr.quals.A_Expr.lexpr);\n }\n if (\n node.JoinExpr.quals.A_Expr.rexpr &&\n is(node.JoinExpr.quals.A_Expr.rexpr, \"ColumnRef\")\n ) {\n this.add(node.JoinExpr.quals.A_Expr.rexpr);\n }\n }\n }\n // select ... from table where data @> '{\"key\": \"val\"}'\n // ^^^^\n if (is(node, \"A_Expr\") && node.A_Expr.kind === \"AEXPR_OP\") {\n const opName =\n node.A_Expr.name?.[0] &&\n is(node.A_Expr.name[0], \"String\") &&\n node.A_Expr.name[0].String.sval;\n if (\n opName &&\n (opName === \"@>\" ||\n opName === \"?\" ||\n opName === \"?|\" ||\n opName === \"?&\" ||\n opName === \"@@\" ||\n opName === \"@?\")\n ) {\n const jsonbOperator = opName as JsonbOperator;\n if (node.A_Expr.lexpr && is(node.A_Expr.lexpr, \"ColumnRef\")) {\n this.add(node.A_Expr.lexpr, { jsonbOperator });\n }\n if (node.A_Expr.rexpr && is(node.A_Expr.rexpr, \"ColumnRef\")) {\n this.add(node.A_Expr.rexpr, { jsonbOperator });\n }\n }\n // select ... from table where data->>'email' = 'alice@example.com'\n // ^^^^^^^^^^^^^^\n if (opName && COMPARISON_OPS.has(opName)) {\n for (const operand of [node.A_Expr.lexpr, node.A_Expr.rexpr]) {\n if (!operand) continue;\n const extraction = extractJsonbPath(operand);\n if (extraction) {\n this.add(extraction.columnRef, {\n jsonbExtraction: extraction.expression,\n });\n }\n }\n }\n }\n // any column reference anywhere\n if (is(node, \"ColumnRef\")) {\n // TODO: this approach needs refinement\n for (let i = 0; i < stack.length; i++) {\n const inReturningList =\n stack[i] === \"returningList\" &&\n stack[i + 1] === \"ResTarget\" &&\n stack[i + 2] === \"val\" &&\n stack[i + 3] === \"ColumnRef\";\n if (inReturningList) {\n this.add(node, { ignored: true });\n return;\n }\n if (\n // stack[i] === \"SelectStmt\" &&\n stack[i + 1] === \"targetList\" &&\n stack[i + 2] === \"ResTarget\" &&\n stack[i + 3] === \"val\" &&\n stack[i + 4] === \"ColumnRef\"\n ) {\n // we don't want to index the columns that are being selected\n this.add(node, { ignored: true });\n return;\n }\n\n // TODO: add functional index support here\n if (stack[i] === \"FuncCall\" && stack[i + 1] === \"args\") {\n // args of a function call can't be indexed (without functional indexes)\n this.add(node, { ignored: true });\n return;\n }\n }\n this.add(node);\n }\n });\n\n return {\n highlights: this.highlights,\n indexRepresentations: this.indexRepresentations,\n indexesToCheck: this.indexesToCheck,\n shadowedAliases: this.shadowedAliases,\n tempTables: this.tempTables,\n tableMappings: this.tableMappings,\n nudges: this.nudges,\n };\n }\n\n private add(\n node: Extract<Node, { ColumnRef: unknown }>,\n options?: {\n ignored?: boolean;\n sort?: SortContext;\n where?: { nulltest?: NullTestType };\n jsonbOperator?: JsonbOperator;\n jsonbExtraction?: string;\n },\n ) {\n if (!node.ColumnRef.location) {\n console.error(`Node did not have a location. Skipping`, node);\n return;\n }\n if (!node.ColumnRef.fields) {\n console.error(node);\n throw new Error(\"Column reference must have fields\");\n }\n let ignored = options?.ignored ?? false;\n let runningLength: number = node.ColumnRef.location;\n const parts: ColumnReferencePart[] = node.ColumnRef.fields.map(\n (field, i, length) => {\n if (!is(field, \"String\") || !field.String.sval) {\n const out = deparseSync(field);\n ignored = true;\n return {\n quoted: out.startsWith('\"'),\n text: out,\n start: runningLength,\n };\n }\n const start = runningLength;\n const size = field.String.sval?.length ?? 0;\n let quoted = false;\n if (node.ColumnRef.location !== undefined) {\n const boundary = this.query[runningLength];\n if (boundary === '\"') {\n quoted = true;\n }\n }\n // +1 for the dot that comes after\n const isLastIteration = i === length.length - 1;\n runningLength += size + (isLastIteration ? 0 : 1) + (quoted ? 2 : 0);\n return {\n text: field.String.sval,\n start,\n quoted,\n };\n },\n );\n const end = runningLength;\n if (this.highlightPositions.has(node.ColumnRef.location)) {\n return;\n }\n this.highlightPositions.add(node.ColumnRef.location);\n const highlighted = `${this.query.slice(node.ColumnRef.location, end)}`;\n const seen = this.seenReferences.get(highlighted);\n if (!ignored) {\n this.seenReferences.set(highlighted, (seen ?? 0) + 1);\n }\n const ref: DiscoveredColumnReference = {\n frequency: seen ?? 1,\n representation: highlighted,\n parts,\n ignored: ignored ?? false,\n position: {\n start: node.ColumnRef.location,\n end,\n },\n };\n if (options?.sort) {\n ref.sort = options.sort;\n }\n if (options?.where) {\n ref.where = options.where;\n }\n if (options?.jsonbOperator) {\n ref.jsonbOperator = options.jsonbOperator;\n }\n if (options?.jsonbExtraction) {\n ref.jsonbExtraction = options.jsonbExtraction;\n }\n this.highlights.push(ref);\n }\n\n private registerTable(rangeVar: RangeVar) {\n if (!rangeVar.relname) return;\n\n const columnReference: ColumnReferencePart = {\n text: rangeVar.relname,\n start: rangeVar.location,\n quoted: false,\n };\n if (rangeVar.schemaname) {\n columnReference.schema = rangeVar.schemaname;\n }\n this.tableMappings.set(rangeVar.relname, columnReference);\n\n if (rangeVar.alias?.aliasname) {\n const aliasName = rangeVar.alias.aliasname;\n const existingMapping = this.tableMappings.get(aliasName);\n const part: ColumnReferencePart = {\n text: rangeVar.relname,\n start: rangeVar.location,\n quoted: true,\n alias: aliasName,\n };\n if (rangeVar.schemaname) {\n part.schema = rangeVar.schemaname;\n }\n if (existingMapping) {\n const isSystemCatalog = rangeVar.relname?.startsWith(\"pg_\") ?? false;\n if (!isSystemCatalog) {\n console.warn(\n `Ignoring alias ${aliasName} as it shadows an existing mapping for ${existingMapping.text}. We currently do not support alias shadowing.`,\n );\n }\n this.shadowedAliases.push(part);\n return;\n }\n this.tableMappings.set(aliasName, part);\n }\n }\n\n /**\n * Descend only into shallow combinators of a node such as\n * - And\n * - Or\n * - Not\n * - ::typecast\n * without deep traversing into subqueries. Useful for checking members\n * of a `WHERE` clause\n */\n static shallowMatch<K extends KeysOfUnion<Node>>(\n expr: Node,\n kind: K,\n callback: (node: Extract<Node, Record<K, unknown>>) => void,\n ) {\n if (is(expr, kind)) {\n callback(expr as Extract<Node, Record<K, unknown>>);\n return;\n }\n if (is(expr, \"BoolExpr\") && expr.BoolExpr.args) {\n for (const arg of expr.BoolExpr.args) {\n Walker.shallowMatch(arg, kind, callback);\n }\n return;\n }\n if (is(expr, \"A_Expr\")) {\n if (expr.A_Expr.lexpr)\n Walker.shallowMatch(expr.A_Expr.lexpr, kind, callback);\n if (expr.A_Expr.rexpr)\n Walker.shallowMatch(expr.A_Expr.rexpr, kind, callback);\n return;\n }\n if (is(expr, \"NullTest\") && expr.NullTest.arg) {\n Walker.shallowMatch(expr.NullTest.arg, kind, callback);\n return;\n }\n if (is(expr, \"BooleanTest\") && expr.BooleanTest.arg) {\n Walker.shallowMatch(expr.BooleanTest.arg, kind, callback);\n return;\n }\n if (is(expr, \"SubLink\") && expr.SubLink.testexpr) {\n Walker.shallowMatch(expr.SubLink.testexpr, kind, callback);\n return;\n }\n if (is(expr, \"TypeCast\") && expr.TypeCast.arg) {\n Walker.shallowMatch(expr.TypeCast.arg, kind, callback);\n return;\n }\n }\n\n static traverse(\n node: Node,\n callback: (node: Node, stack: (KeysOfUnion<Node> | string)[]) => void,\n ) {\n Walker.doTraverse(node, [], callback);\n }\n\n private static doTraverse(\n node: unknown,\n stack: (KeysOfUnion<Node> | string)[],\n callback: (node: Node, stack: (KeysOfUnion<Node> | string)[]) => void,\n ) {\n if (isANode(node)) {\n callback(node, [...stack, getNodeKind(node)]);\n }\n if (typeof node !== \"object\" || node === null) {\n return;\n }\n if (Array.isArray(node)) {\n for (const item of node) {\n if (isANode(item)) {\n Walker.doTraverse(item, stack, callback);\n }\n }\n } else if (isANode(node)) {\n const keys = Object.keys(node);\n // @ts-expect-error | nodes don't allow dynamic access but it's the only way to do it\n Walker.doTraverse(node[keys[0]], [...stack, getNodeKind(node)], callback);\n } else {\n for (const [key, child] of Object.entries(node)) {\n Walker.doTraverse(\n child,\n [...stack, key as KeysOfUnion<Node>],\n callback,\n );\n }\n }\n }\n}\n\nexport type ColumnReferencePart = {\n schema?: string;\n /** the text of the column reference (excluding any potential quotes) */\n text: string;\n start?: number;\n quoted: boolean;\n alias?: string;\n};\n\ntype JsonbExtraction = {\n columnRef: Extract<Node, { ColumnRef: unknown }>;\n expression: string;\n};\n\n/**\n * Given an operand of a comparison (e.g. the left side of `=`), check whether\n * it is a JSONB path extraction expression such as `data->>'email'` or\n * `(data->>'age')::int`. If so, return the root ColumnRef (for the walker to\n * register) and the full expression string (for use in the expression index).\n *\n * Handles:\n * - `data->>'email'` — simple extraction\n * - `(data->>'age')::int` — extraction with cast\n * - `data->'addr'->>'city'` — chained extraction\n * - `t.data->>'email'` — table-qualified (strips qualifier from expression)\n */\nfunction extractJsonbPath(node: Node): JsonbExtraction | undefined {\n let exprNode = node;\n // Unwrap TypeCast (e.g. `(data->>'age')::int`)\n if (is(exprNode, \"TypeCast\") && exprNode.TypeCast.arg) {\n exprNode = exprNode.TypeCast.arg;\n }\n if (!is(exprNode, \"A_Expr\") || exprNode.A_Expr.kind !== \"AEXPR_OP\") {\n return undefined;\n }\n const innerOp =\n exprNode.A_Expr.name?.[0] &&\n is(exprNode.A_Expr.name[0], \"String\") &&\n exprNode.A_Expr.name[0].String.sval;\n if (!innerOp || !JSONB_EXTRACTION_OPS.has(innerOp)) {\n return undefined;\n }\n const rootCol = findRootColumnRef(exprNode);\n if (!rootCol) {\n return undefined;\n }\n // Deep-clone the whole outer node so we can strip qualifiers without\n // mutating the original AST.\n const cloned: Node = JSON.parse(JSON.stringify(node));\n stripTableQualifiers(cloned);\n const expression = deparseSync(cloned);\n return { columnRef: rootCol, expression };\n}\n\n/**\n * Walk the left side of a chain of `->` / `->>` operators to find the\n * root ColumnRef (the JSONB column itself).\n */\nfunction findRootColumnRef(\n node: Node,\n): Extract<Node, { ColumnRef: unknown }> | undefined {\n if (is(node, \"ColumnRef\")) {\n return node;\n }\n if (is(node, \"A_Expr\") && node.A_Expr.lexpr) {\n return findRootColumnRef(node.A_Expr.lexpr);\n }\n if (is(node, \"TypeCast\") && node.TypeCast.arg) {\n return findRootColumnRef(node.TypeCast.arg);\n }\n return undefined;\n}\n\n/**\n * Remove table/schema qualifiers from ColumnRef nodes inside the\n * expression so the deparsed expression contains only the column name.\n * e.g. `t.data->>'email'` → `data->>'email'`\n */\nfunction stripTableQualifiers(node: unknown): void {\n if (typeof node !== \"object\" || node === null) return;\n if (\n \"ColumnRef\" in (node as Record<string, unknown>) &&\n (node as any).ColumnRef?.fields\n ) {\n const fields: unknown[] = (node as any).ColumnRef.fields;\n if (fields.length > 1) {\n (node as any).ColumnRef.fields = [fields[fields.length - 1]];\n }\n return;\n }\n for (const value of Object.values(node as Record<string, unknown>)) {\n if (Array.isArray(value)) {\n for (const item of value) {\n stripTableQualifiers(item);\n }\n } else if (typeof value === \"object\" && value !== null) {\n stripTableQualifiers(value);\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"walker.cjs","names":["parseNudges","is","isANode","getNodeKind"],"sources":["../../src/sql/walker.ts"],"sourcesContent":["import type { Node, NullTestType, RangeVar } from \"@pgsql/types\";\nimport { deparseSync } from \"pgsql-deparser\";\nimport type {\n DiscoveredColumnReference,\n JsonbOperator,\n SortContext,\n} from \"./analyzer.js\";\nimport { getNodeKind, is, isANode, type KeysOfUnion } from \"./ast-utils.js\";\nimport { parseNudges } from \"./nudges.js\";\nimport type { Nudge } from \"./nudges.js\";\n\nconst JSONB_EXTRACTION_OPS = new Set([\"->\", \"->>\"]);\nconst COMPARISON_OPS = new Set([\n \"=\",\n \"<>\",\n \"!=\",\n \"<\",\n \"<=\",\n \">\",\n \">=\",\n \"~~\",\n \"~~*\",\n \"!~~\",\n \"!~~*\",\n]);\n\n/** Information about tables that appear in the query */\nexport type TableMappings = Map<string, ColumnReferencePart>;\ntype SeenReferences = Map<string, number>;\n\n/**\n * Walks the AST of a sql query and extracts query metadata.\n * This pattern is used to segregate the mutable state that's more common for the\n * AST walking process from the rest of the analyzer.\n */\nexport class Walker {\n private tableMappings: TableMappings = new Map();\n private tempTables = new Set<string>();\n private highlights: DiscoveredColumnReference[] = [];\n private indexRepresentations = new Set<string>();\n private indexesToCheck: DiscoveredColumnReference[] = [];\n private highlightPositions = new Set<number>();\n // used for tallying the amount of times we see stuff so\n // we have a better idea of what to start off the algorithm with\n private seenReferences: SeenReferences = new Map();\n private shadowedAliases: ColumnReferencePart[] = [];\n private nudges: Nudge[] = [];\n\n constructor(private readonly query: string) {}\n\n walk(root: Node) {\n // reset state in case the class instance is reused\n // reassigning vars here instead of using `map.clear()` to prevent\n // accidentally mutating existing references\n this.tableMappings = new Map();\n this.tempTables = new Set<string>();\n this.highlights = [];\n this.indexRepresentations = new Set<string>();\n this.indexesToCheck = [];\n this.highlightPositions = new Set<number>();\n this.seenReferences = new Map();\n this.shadowedAliases = [];\n this.nudges = [];\n\n Walker.traverse(root, (node, stack) => {\n const nodeNudges = parseNudges(node, stack);\n this.nudges = [...this.nudges, ...nodeNudges];\n\n // comments are not parsed here as they seem to be ignored.\n //\n // results cannot be indexed in any way because they alias a CTE\n // with alias as (select ...)\n // ^^^^^\n if (is(node, \"CommonTableExpr\")) {\n if (node.CommonTableExpr.ctename) {\n this.tempTables.add(node.CommonTableExpr.ctename);\n }\n }\n // results cannot be indexed in any way because they alias a subquery\n // select ... from (...) as alias\n // ^^^^^\n if (is(node, \"RangeSubselect\")) {\n if (node.RangeSubselect.alias?.aliasname) {\n this.tempTables.add(node.RangeSubselect.alias.aliasname);\n }\n }\n // select ... from (...) where col is null\n // ^^^^^^^\n if (is(node, \"NullTest\")) {\n if (\n node.NullTest.arg &&\n node.NullTest.nulltesttype &&\n is(node.NullTest.arg, \"ColumnRef\")\n ) {\n this.add(node.NullTest.arg, {\n where: { nulltest: node.NullTest.nulltesttype },\n });\n }\n }\n // The parser does not wrap the `relation` field of UpdateStmt\n // in a { RangeVar: ... } node (it's a raw RangeVar struct), so the\n // generic RangeVar handler below never sees it. Handle it explicitly.\n if (is(node, \"UpdateStmt\")) {\n if (node.UpdateStmt.relation) {\n this.registerTable(node.UpdateStmt.relation);\n }\n }\n // Same as UpdateStmt: DeleteStmt.relation is a raw RangeVar struct.\n if (is(node, \"DeleteStmt\")) {\n if (node.DeleteStmt.relation) {\n this.registerTable(node.DeleteStmt.relation);\n }\n }\n // can be indexed as the alias refers to a regular table\n // but the alias has to be mapped to the original table name\n // select ... from table as alias\n // ^^^^^\n if (is(node, \"RangeVar\") && node.RangeVar.relname) {\n this.registerTable(node.RangeVar);\n }\n // select ... from table order by col asc\n // ^^^^^^^^^^^^^^^^\n if (is(node, \"SortBy\")) {\n // we don't care about sorting by anything that's not a column reference\n // because it couldn't be indexed anyway.\n // TODO: mark that expression as unindexable? It's just better for debugging\n if (node.SortBy.node && is(node.SortBy.node, \"ColumnRef\")) {\n this.add(node.SortBy.node, {\n sort: {\n dir: node.SortBy.sortby_dir ?? \"SORTBY_DEFAULT\",\n nulls: node.SortBy.sortby_nulls ?? \"SORTBY_NULLS_DEFAULT\",\n },\n });\n }\n }\n // select ... from table1 join table2 t2 on table1.col = t2.col\n // ^^\n if (is(node, \"JoinExpr\") && node.JoinExpr.quals) {\n if (is(node.JoinExpr.quals, \"A_Expr\")) {\n if (\n node.JoinExpr.quals.A_Expr.lexpr &&\n is(node.JoinExpr.quals.A_Expr.lexpr, \"ColumnRef\")\n ) {\n this.add(node.JoinExpr.quals.A_Expr.lexpr);\n }\n if (\n node.JoinExpr.quals.A_Expr.rexpr &&\n is(node.JoinExpr.quals.A_Expr.rexpr, \"ColumnRef\")\n ) {\n this.add(node.JoinExpr.quals.A_Expr.rexpr);\n }\n }\n }\n // select ... from table where data @> '{\"key\": \"val\"}'\n // ^^^^\n if (is(node, \"A_Expr\") && node.A_Expr.kind === \"AEXPR_OP\") {\n const opName =\n node.A_Expr.name?.[0] &&\n is(node.A_Expr.name[0], \"String\") &&\n node.A_Expr.name[0].String.sval;\n if (\n opName &&\n (opName === \"@>\" ||\n opName === \"?\" ||\n opName === \"?|\" ||\n opName === \"?&\" ||\n opName === \"@@\" ||\n opName === \"@?\")\n ) {\n const jsonbOperator = opName as JsonbOperator;\n if (node.A_Expr.lexpr && is(node.A_Expr.lexpr, \"ColumnRef\")) {\n this.add(node.A_Expr.lexpr, { jsonbOperator });\n }\n if (node.A_Expr.rexpr && is(node.A_Expr.rexpr, \"ColumnRef\")) {\n this.add(node.A_Expr.rexpr, { jsonbOperator });\n }\n }\n // select ... from table where data->>'email' = 'alice@example.com'\n // ^^^^^^^^^^^^^^\n if (opName && COMPARISON_OPS.has(opName)) {\n for (const operand of [node.A_Expr.lexpr, node.A_Expr.rexpr]) {\n if (!operand) continue;\n const extraction = extractJsonbPath(operand);\n if (extraction) {\n this.add(extraction.columnRef, {\n jsonbExtraction: extraction.expression,\n });\n }\n }\n }\n }\n // any column reference anywhere\n if (is(node, \"ColumnRef\")) {\n // TODO: this approach needs refinement\n for (let i = 0; i < stack.length; i++) {\n const inReturningList =\n stack[i] === \"returningList\" &&\n stack[i + 1] === \"ResTarget\" &&\n stack[i + 2] === \"val\" &&\n stack[i + 3] === \"ColumnRef\";\n if (inReturningList) {\n this.add(node, { ignored: true });\n return;\n }\n if (\n // stack[i] === \"SelectStmt\" &&\n stack[i + 1] === \"targetList\" &&\n stack[i + 2] === \"ResTarget\" &&\n stack[i + 3] === \"val\" &&\n stack[i + 4] === \"ColumnRef\"\n ) {\n // we don't want to index the columns that are being selected\n this.add(node, { ignored: true });\n return;\n }\n\n // TODO: add functional index support here\n if (stack[i] === \"FuncCall\" && stack[i + 1] === \"args\") {\n // args of a function call can't be indexed (without functional indexes)\n this.add(node, { ignored: true });\n return;\n }\n // CAST(\"columnName\" as \"test\")\n if (stack[i] === \"TypeCast\" && stack[i + 1] === \"arg\") {\n this.add(node, { ignored: true });\n return;\n }\n }\n this.add(node);\n }\n });\n\n return {\n highlights: this.highlights,\n indexRepresentations: this.indexRepresentations,\n indexesToCheck: this.indexesToCheck,\n shadowedAliases: this.shadowedAliases,\n tempTables: this.tempTables,\n tableMappings: this.tableMappings,\n nudges: this.nudges,\n };\n }\n\n private add(\n node: Extract<Node, { ColumnRef: unknown }>,\n options?: {\n ignored?: boolean;\n sort?: SortContext;\n where?: { nulltest?: NullTestType };\n jsonbOperator?: JsonbOperator;\n jsonbExtraction?: string;\n },\n ) {\n if (!node.ColumnRef.location) {\n console.error(`Node did not have a location. Skipping`, node);\n return;\n }\n if (!node.ColumnRef.fields) {\n console.error(node);\n throw new Error(\"Column reference must have fields\");\n }\n let ignored = options?.ignored ?? false;\n let runningLength: number = node.ColumnRef.location;\n const parts: ColumnReferencePart[] = node.ColumnRef.fields.map(\n (field, i, length) => {\n if (!is(field, \"String\") || !field.String.sval) {\n const out = deparseSync(field);\n ignored = true;\n return {\n quoted: out.startsWith('\"'),\n text: out,\n start: runningLength,\n };\n }\n const start = runningLength;\n const size = field.String.sval?.length ?? 0;\n let quoted = false;\n if (node.ColumnRef.location !== undefined) {\n const boundary = this.query[runningLength];\n if (boundary === '\"') {\n quoted = true;\n }\n }\n // +1 for the dot that comes after\n const isLastIteration = i === length.length - 1;\n runningLength += size + (isLastIteration ? 0 : 1) + (quoted ? 2 : 0);\n return {\n text: field.String.sval,\n start,\n quoted,\n };\n },\n );\n const end = runningLength;\n if (this.highlightPositions.has(node.ColumnRef.location)) {\n return;\n }\n this.highlightPositions.add(node.ColumnRef.location);\n const highlighted = `${this.query.slice(node.ColumnRef.location, end)}`;\n const seen = this.seenReferences.get(highlighted);\n if (!ignored) {\n this.seenReferences.set(highlighted, (seen ?? 0) + 1);\n }\n const ref: DiscoveredColumnReference = {\n frequency: seen ?? 1,\n representation: highlighted,\n parts,\n ignored: ignored ?? false,\n position: {\n start: node.ColumnRef.location,\n end,\n },\n };\n if (options?.sort) {\n ref.sort = options.sort;\n }\n if (options?.where) {\n ref.where = options.where;\n }\n if (options?.jsonbOperator) {\n ref.jsonbOperator = options.jsonbOperator;\n }\n if (options?.jsonbExtraction) {\n ref.jsonbExtraction = options.jsonbExtraction;\n }\n this.highlights.push(ref);\n }\n\n private registerTable(rangeVar: RangeVar) {\n if (!rangeVar.relname) return;\n\n const columnReference: ColumnReferencePart = {\n text: rangeVar.relname,\n start: rangeVar.location,\n quoted: false,\n };\n if (rangeVar.schemaname) {\n columnReference.schema = rangeVar.schemaname;\n }\n this.tableMappings.set(rangeVar.relname, columnReference);\n\n if (rangeVar.alias?.aliasname) {\n const aliasName = rangeVar.alias.aliasname;\n const existingMapping = this.tableMappings.get(aliasName);\n const part: ColumnReferencePart = {\n text: rangeVar.relname,\n start: rangeVar.location,\n quoted: true,\n alias: aliasName,\n };\n if (rangeVar.schemaname) {\n part.schema = rangeVar.schemaname;\n }\n if (existingMapping) {\n const isSystemCatalog = rangeVar.relname?.startsWith(\"pg_\") ?? false;\n if (!isSystemCatalog) {\n console.warn(\n `Ignoring alias ${aliasName} as it shadows an existing mapping for ${existingMapping.text}. We currently do not support alias shadowing.`,\n );\n }\n this.shadowedAliases.push(part);\n return;\n }\n this.tableMappings.set(aliasName, part);\n }\n }\n\n /**\n * Descend only into shallow combinators of a node such as\n * - And\n * - Or\n * - Not\n * - ::typecast\n * without deep traversing into subqueries. Useful for checking members\n * of a `WHERE` clause\n */\n static shallowMatch<K extends KeysOfUnion<Node>>(\n expr: Node,\n kind: K,\n callback: (node: Extract<Node, Record<K, unknown>>) => void,\n ) {\n if (is(expr, kind)) {\n callback(expr as Extract<Node, Record<K, unknown>>);\n return;\n }\n if (is(expr, \"BoolExpr\") && expr.BoolExpr.args) {\n for (const arg of expr.BoolExpr.args) {\n Walker.shallowMatch(arg, kind, callback);\n }\n return;\n }\n if (is(expr, \"A_Expr\")) {\n if (expr.A_Expr.lexpr)\n Walker.shallowMatch(expr.A_Expr.lexpr, kind, callback);\n if (expr.A_Expr.rexpr)\n Walker.shallowMatch(expr.A_Expr.rexpr, kind, callback);\n return;\n }\n if (is(expr, \"NullTest\") && expr.NullTest.arg) {\n Walker.shallowMatch(expr.NullTest.arg, kind, callback);\n return;\n }\n if (is(expr, \"BooleanTest\") && expr.BooleanTest.arg) {\n Walker.shallowMatch(expr.BooleanTest.arg, kind, callback);\n return;\n }\n if (is(expr, \"SubLink\") && expr.SubLink.testexpr) {\n Walker.shallowMatch(expr.SubLink.testexpr, kind, callback);\n return;\n }\n if (is(expr, \"TypeCast\") && expr.TypeCast.arg) {\n Walker.shallowMatch(expr.TypeCast.arg, kind, callback);\n return;\n }\n }\n\n static traverse(\n node: Node,\n callback: (node: Node, stack: (KeysOfUnion<Node> | string)[]) => void,\n ) {\n Walker.doTraverse(node, [], callback);\n }\n\n private static doTraverse(\n node: unknown,\n stack: (KeysOfUnion<Node> | string)[],\n callback: (node: Node, stack: (KeysOfUnion<Node> | string)[]) => void,\n ) {\n if (isANode(node)) {\n callback(node, [...stack, getNodeKind(node)]);\n }\n if (typeof node !== \"object\" || node === null) {\n return;\n }\n if (Array.isArray(node)) {\n for (const item of node) {\n if (isANode(item)) {\n Walker.doTraverse(item, stack, callback);\n }\n }\n } else if (isANode(node)) {\n const keys = Object.keys(node);\n // @ts-expect-error | nodes don't allow dynamic access but it's the only way to do it\n Walker.doTraverse(node[keys[0]], [...stack, getNodeKind(node)], callback);\n } else {\n for (const [key, child] of Object.entries(node)) {\n Walker.doTraverse(\n child,\n [...stack, key as KeysOfUnion<Node>],\n callback,\n );\n }\n }\n }\n}\n\nexport type ColumnReferencePart = {\n schema?: string;\n /** the text of the column reference (excluding any potential quotes) */\n text: string;\n start?: number;\n quoted: boolean;\n alias?: string;\n};\n\ntype JsonbExtraction = {\n columnRef: Extract<Node, { ColumnRef: unknown }>;\n expression: string;\n};\n\n/**\n * Given an operand of a comparison (e.g. the left side of `=`), check whether\n * it is a JSONB path extraction expression such as `data->>'email'` or\n * `(data->>'age')::int`. If so, return the root ColumnRef (for the walker to\n * register) and the full expression string (for use in the expression index).\n *\n * Handles:\n * - `data->>'email'` — simple extraction\n * - `(data->>'age')::int` — extraction with cast\n * - `data->'addr'->>'city'` — chained extraction\n * - `t.data->>'email'` — table-qualified (strips qualifier from expression)\n */\nfunction extractJsonbPath(node: Node): JsonbExtraction | undefined {\n let exprNode = node;\n // Unwrap TypeCast (e.g. `(data->>'age')::int`)\n if (is(exprNode, \"TypeCast\") && exprNode.TypeCast.arg) {\n exprNode = exprNode.TypeCast.arg;\n }\n if (!is(exprNode, \"A_Expr\") || exprNode.A_Expr.kind !== \"AEXPR_OP\") {\n return undefined;\n }\n const innerOp =\n exprNode.A_Expr.name?.[0] &&\n is(exprNode.A_Expr.name[0], \"String\") &&\n exprNode.A_Expr.name[0].String.sval;\n if (!innerOp || !JSONB_EXTRACTION_OPS.has(innerOp)) {\n return undefined;\n }\n const rootCol = findRootColumnRef(exprNode);\n if (!rootCol) {\n return undefined;\n }\n // Deep-clone the whole outer node so we can strip qualifiers without\n // mutating the original AST.\n const cloned: Node = JSON.parse(JSON.stringify(node));\n stripTableQualifiers(cloned);\n const expression = deparseSync(cloned);\n return { columnRef: rootCol, expression };\n}\n\n/**\n * Walk the left side of a chain of `->` / `->>` operators to find the\n * root ColumnRef (the JSONB column itself).\n */\nfunction findRootColumnRef(\n node: Node,\n): Extract<Node, { ColumnRef: unknown }> | undefined {\n if (is(node, \"ColumnRef\")) {\n return node;\n }\n if (is(node, \"A_Expr\") && node.A_Expr.lexpr) {\n return findRootColumnRef(node.A_Expr.lexpr);\n }\n if (is(node, \"TypeCast\") && node.TypeCast.arg) {\n return findRootColumnRef(node.TypeCast.arg);\n }\n return undefined;\n}\n\n/**\n * Remove table/schema qualifiers from ColumnRef nodes inside the\n * expression so the deparsed expression contains only the column name.\n * e.g. `t.data->>'email'` → `data->>'email'`\n */\nfunction stripTableQualifiers(node: unknown): void {\n if (typeof node !== \"object\" || node === null) return;\n if (\n \"ColumnRef\" in (node as Record<string, unknown>) &&\n (node as any).ColumnRef?.fields\n ) {\n const fields: unknown[] = (node as any).ColumnRef.fields;\n if (fields.length > 1) {\n (node as any).ColumnRef.fields = [fields[fields.length - 1]];\n }\n return;\n }\n for (const value of Object.values(node as Record<string, unknown>)) {\n if (Array.isArray(value)) {\n for (const item of value) {\n stripTableQualifiers(item);\n }\n } else if (typeof value === \"object\" && value !== null) {\n stripTableQualifiers(value);\n }\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"walker.d.cts","names":[],"sources":["../../src/sql/walker.ts"],"mappings":";;;;;KAmcY,mBAAA;EACV,MAAA;EAEA,IAAA;EACA,KAAA;EACA,MAAA;EACA,KAAA;AAAA"}
1
+ {"version":3,"file":"walker.d.cts","names":[],"sources":["../../src/sql/walker.ts"],"mappings":";;;;;KAwcY,mBAAA;EACV,MAAA;EAEA,IAAA;EACA,KAAA;EACA,MAAA;EACA,KAAA;AAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"walker.d.mts","names":[],"sources":["../../src/sql/walker.ts"],"mappings":";;;;;KAmcY,mBAAA;EACV,MAAA;EAEA,IAAA;EACA,KAAA;EACA,MAAA;EACA,KAAA;AAAA"}
1
+ {"version":3,"file":"walker.d.mts","names":[],"sources":["../../src/sql/walker.ts"],"mappings":";;;;;KAwcY,mBAAA;EACV,MAAA;EAEA,IAAA;EACA,KAAA;EACA,MAAA;EACA,KAAA;AAAA"}