@query-doctor/core 0.8.1 → 0.8.2-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.
Files changed (118) hide show
  1. package/dist/_virtual/_@oxc-project_runtime@0.122.0/helpers/defineProperty.cjs +13 -0
  2. package/dist/_virtual/_@oxc-project_runtime@0.122.0/helpers/defineProperty.mjs +13 -0
  3. package/dist/_virtual/_@oxc-project_runtime@0.122.0/helpers/toPrimitive.cjs +15 -0
  4. package/dist/_virtual/_@oxc-project_runtime@0.122.0/helpers/toPrimitive.mjs +15 -0
  5. package/dist/_virtual/_@oxc-project_runtime@0.122.0/helpers/toPropertyKey.cjs +10 -0
  6. package/dist/_virtual/_@oxc-project_runtime@0.122.0/helpers/toPropertyKey.mjs +10 -0
  7. package/dist/_virtual/_@oxc-project_runtime@0.122.0/helpers/typeof.cjs +17 -0
  8. package/dist/_virtual/_@oxc-project_runtime@0.122.0/helpers/typeof.mjs +12 -0
  9. package/dist/_virtual/_rolldown/runtime.cjs +24 -0
  10. package/dist/index.cjs +33 -2568
  11. package/dist/index.d.cts +11 -781
  12. package/dist/index.d.mts +11 -781
  13. package/dist/index.mjs +10 -2522
  14. package/dist/optimizer/genalgo.cjs +365 -0
  15. package/dist/optimizer/genalgo.cjs.map +1 -0
  16. package/dist/optimizer/genalgo.d.cts +111 -0
  17. package/dist/optimizer/genalgo.d.cts.map +1 -0
  18. package/dist/optimizer/genalgo.d.mts +111 -0
  19. package/dist/optimizer/genalgo.d.mts.map +1 -0
  20. package/dist/optimizer/genalgo.mjs +362 -0
  21. package/dist/optimizer/genalgo.mjs.map +1 -0
  22. package/dist/optimizer/pss-rewriter.cjs +31 -0
  23. package/dist/optimizer/pss-rewriter.cjs.map +1 -0
  24. package/dist/optimizer/pss-rewriter.d.cts +16 -0
  25. package/dist/optimizer/pss-rewriter.d.cts.map +1 -0
  26. package/dist/optimizer/pss-rewriter.d.mts +16 -0
  27. package/dist/optimizer/pss-rewriter.d.mts.map +1 -0
  28. package/dist/optimizer/pss-rewriter.mjs +31 -0
  29. package/dist/optimizer/pss-rewriter.mjs.map +1 -0
  30. package/dist/optimizer/statistics.cjs +738 -0
  31. package/dist/optimizer/statistics.cjs.map +1 -0
  32. package/dist/optimizer/statistics.d.cts +389 -0
  33. package/dist/optimizer/statistics.d.cts.map +1 -0
  34. package/dist/optimizer/statistics.d.mts +389 -0
  35. package/dist/optimizer/statistics.d.mts.map +1 -0
  36. package/dist/optimizer/statistics.mjs +729 -0
  37. package/dist/optimizer/statistics.mjs.map +1 -0
  38. package/dist/sentry.cjs +13 -0
  39. package/dist/sentry.cjs.map +1 -0
  40. package/dist/sentry.d.cts +7 -0
  41. package/dist/sentry.d.cts.map +1 -0
  42. package/dist/sentry.d.mts +7 -0
  43. package/dist/sentry.d.mts.map +1 -0
  44. package/dist/sentry.mjs +13 -0
  45. package/dist/sentry.mjs.map +1 -0
  46. package/dist/sql/analyzer.cjs +242 -0
  47. package/dist/sql/analyzer.cjs.map +1 -0
  48. package/dist/sql/analyzer.d.cts +112 -0
  49. package/dist/sql/analyzer.d.cts.map +1 -0
  50. package/dist/sql/analyzer.d.mts +112 -0
  51. package/dist/sql/analyzer.d.mts.map +1 -0
  52. package/dist/sql/analyzer.mjs +240 -0
  53. package/dist/sql/analyzer.mjs.map +1 -0
  54. package/dist/sql/ast-utils.cjs +19 -0
  55. package/dist/sql/ast-utils.cjs.map +1 -0
  56. package/dist/sql/ast-utils.d.cts +9 -0
  57. package/dist/sql/ast-utils.d.cts.map +1 -0
  58. package/dist/sql/ast-utils.d.mts +9 -0
  59. package/dist/sql/ast-utils.d.mts.map +1 -0
  60. package/dist/sql/ast-utils.mjs +17 -0
  61. package/dist/sql/ast-utils.mjs.map +1 -0
  62. package/dist/sql/builder.cjs +94 -0
  63. package/dist/sql/builder.cjs.map +1 -0
  64. package/dist/sql/builder.d.cts +37 -0
  65. package/dist/sql/builder.d.cts.map +1 -0
  66. package/dist/sql/builder.d.mts +37 -0
  67. package/dist/sql/builder.d.mts.map +1 -0
  68. package/dist/sql/builder.mjs +94 -0
  69. package/dist/sql/builder.mjs.map +1 -0
  70. package/dist/sql/database.cjs +35 -0
  71. package/dist/sql/database.cjs.map +1 -0
  72. package/dist/sql/database.d.cts +91 -0
  73. package/dist/sql/database.d.cts.map +1 -0
  74. package/dist/sql/database.d.mts +91 -0
  75. package/dist/sql/database.d.mts.map +1 -0
  76. package/dist/sql/database.mjs +32 -0
  77. package/dist/sql/database.mjs.map +1 -0
  78. package/dist/sql/indexes.cjs +17 -0
  79. package/dist/sql/indexes.cjs.map +1 -0
  80. package/dist/sql/indexes.d.cts +14 -0
  81. package/dist/sql/indexes.d.cts.map +1 -0
  82. package/dist/sql/indexes.d.mts +14 -0
  83. package/dist/sql/indexes.d.mts.map +1 -0
  84. package/dist/sql/indexes.mjs +16 -0
  85. package/dist/sql/indexes.mjs.map +1 -0
  86. package/dist/sql/nudges.cjs +484 -0
  87. package/dist/sql/nudges.cjs.map +1 -0
  88. package/dist/sql/nudges.d.cts +21 -0
  89. package/dist/sql/nudges.d.cts.map +1 -0
  90. package/dist/sql/nudges.d.mts +21 -0
  91. package/dist/sql/nudges.d.mts.map +1 -0
  92. package/dist/sql/nudges.mjs +484 -0
  93. package/dist/sql/nudges.mjs.map +1 -0
  94. package/dist/sql/permutations.cjs +28 -0
  95. package/dist/sql/permutations.cjs.map +1 -0
  96. package/dist/sql/permutations.mjs +28 -0
  97. package/dist/sql/permutations.mjs.map +1 -0
  98. package/dist/sql/pg-identifier.cjs +216 -0
  99. package/dist/sql/pg-identifier.cjs.map +1 -0
  100. package/dist/sql/pg-identifier.d.cts +31 -0
  101. package/dist/sql/pg-identifier.d.cts.map +1 -0
  102. package/dist/sql/pg-identifier.d.mts +31 -0
  103. package/dist/sql/pg-identifier.d.mts.map +1 -0
  104. package/dist/sql/pg-identifier.mjs +216 -0
  105. package/dist/sql/pg-identifier.mjs.map +1 -0
  106. package/dist/sql/walker.cjs +314 -0
  107. package/dist/sql/walker.cjs.map +1 -0
  108. package/dist/sql/walker.d.cts +15 -0
  109. package/dist/sql/walker.d.cts.map +1 -0
  110. package/dist/sql/walker.d.mts +15 -0
  111. package/dist/sql/walker.d.mts.map +1 -0
  112. package/dist/sql/walker.mjs +313 -0
  113. package/dist/sql/walker.mjs.map +1 -0
  114. package/package.json +2 -1
  115. package/dist/index.cjs.map +0 -1
  116. package/dist/index.d.cts.map +0 -1
  117. package/dist/index.d.mts.map +0 -1
  118. package/dist/index.mjs.map +0 -1
@@ -0,0 +1,738 @@
1
+ "use client";
2
+ const require_runtime = require("../_virtual/_rolldown/runtime.cjs");
3
+ const require_defineProperty = require("../_virtual/_@oxc-project_runtime@0.122.0/helpers/defineProperty.cjs");
4
+ let colorette = require("colorette");
5
+ let zod = require("zod");
6
+ let dedent = require("dedent");
7
+ dedent = require_runtime.__toESM(dedent);
8
+ //#region src/optimizer/statistics.ts
9
+ const StatisticsSource = zod.z.union([zod.z.object({
10
+ kind: zod.z.literal("path"),
11
+ path: zod.z.string().min(1)
12
+ }), zod.z.object({ kind: zod.z.literal("inline") })]);
13
+ const ExportedStatsStatistics = zod.z.object({
14
+ stawidth: zod.z.number(),
15
+ stainherit: zod.z.boolean().default(false),
16
+ stadistinct: zod.z.number(),
17
+ stanullfrac: zod.z.number(),
18
+ stakind1: zod.z.number().min(0),
19
+ stakind2: zod.z.number().min(0),
20
+ stakind3: zod.z.number().min(0),
21
+ stakind4: zod.z.number().min(0),
22
+ stakind5: zod.z.number().min(0),
23
+ staop1: zod.z.string(),
24
+ staop2: zod.z.string(),
25
+ staop3: zod.z.string(),
26
+ staop4: zod.z.string(),
27
+ staop5: zod.z.string(),
28
+ stacoll1: zod.z.string(),
29
+ stacoll2: zod.z.string(),
30
+ stacoll3: zod.z.string(),
31
+ stacoll4: zod.z.string(),
32
+ stacoll5: zod.z.string(),
33
+ stanumbers1: zod.z.array(zod.z.number()).nullable(),
34
+ stanumbers2: zod.z.array(zod.z.number()).nullable(),
35
+ stanumbers3: zod.z.array(zod.z.number()).nullable(),
36
+ stanumbers4: zod.z.array(zod.z.number()).nullable(),
37
+ stanumbers5: zod.z.array(zod.z.number()).nullable(),
38
+ stavalues1: zod.z.array(zod.z.any()).nullable(),
39
+ stavalues2: zod.z.array(zod.z.any()).nullable(),
40
+ stavalues3: zod.z.array(zod.z.any()).nullable(),
41
+ stavalues4: zod.z.array(zod.z.any()).nullable(),
42
+ stavalues5: zod.z.array(zod.z.any()).nullable()
43
+ });
44
+ const ExportedStatsColumns = zod.z.object({
45
+ columnName: zod.z.string(),
46
+ attlen: zod.z.number().nullable(),
47
+ stats: ExportedStatsStatistics.nullable()
48
+ });
49
+ const ExportedStatsIndex = zod.z.object({
50
+ indexName: zod.z.string(),
51
+ amname: zod.z.string().default("btree"),
52
+ relpages: zod.z.number(),
53
+ reltuples: zod.z.number(),
54
+ relallvisible: zod.z.number(),
55
+ relallfrozen: zod.z.number().optional(),
56
+ fillfactor: zod.z.number().default(90),
57
+ columns: zod.z.array(zod.z.object({ attlen: zod.z.number().nullable() })).default([])
58
+ });
59
+ const ExportedStatsV1 = zod.z.object({
60
+ tableName: zod.z.string(),
61
+ schemaName: zod.z.string(),
62
+ relpages: zod.z.number(),
63
+ reltuples: zod.z.number(),
64
+ relallvisible: zod.z.number(),
65
+ relallfrozen: zod.z.number().optional(),
66
+ columns: zod.z.array(ExportedStatsColumns).default([]),
67
+ indexes: zod.z.array(ExportedStatsIndex)
68
+ });
69
+ const ExportedStats = zod.z.union([ExportedStatsV1]);
70
+ const StatisticsMode = zod.z.discriminatedUnion("kind", [zod.z.object({
71
+ kind: zod.z.literal("fromAssumption"),
72
+ reltuples: zod.z.number().min(0)
73
+ }), zod.z.object({
74
+ kind: zod.z.literal("fromStatisticsExport"),
75
+ stats: zod.z.array(ExportedStats),
76
+ source: StatisticsSource
77
+ })]);
78
+ const DEFAULT_RELTUPLES = 1e4;
79
+ const DEFAULT_RELPAGES = 1;
80
+ const DEFAULT_PAGE_SIZE = 2 ** 13;
81
+ function estimateStawidth(col) {
82
+ return col.attlen ?? 32;
83
+ }
84
+ function estimateRelpages(reltuples, columns) {
85
+ const rowWidth = columns.reduce((sum, col) => sum + estimateStawidth(col), 0) + 27;
86
+ return Math.ceil(reltuples * rowWidth / DEFAULT_PAGE_SIZE);
87
+ }
88
+ function estimateIndexRelpages(reltuples, columns, fillfactor, amname, tableRelpages) {
89
+ if (amname === "gin") return Math.ceil(tableRelpages * .3);
90
+ const keyWidth = columns.reduce((sum, col) => sum + estimateStawidth(col) + 16, 0);
91
+ return Math.ceil(reltuples * keyWidth / DEFAULT_PAGE_SIZE / fillfactor);
92
+ }
93
+ var Statistics = class Statistics {
94
+ constructor(db, postgresVersion, ownMetadata, statsMode) {
95
+ this.db = db;
96
+ this.postgresVersion = postgresVersion;
97
+ this.ownMetadata = ownMetadata;
98
+ require_defineProperty._defineProperty(this, "mode", void 0);
99
+ require_defineProperty._defineProperty(this, "exportedMetadata", void 0);
100
+ if (statsMode) {
101
+ this.mode = statsMode;
102
+ if (statsMode.kind === "fromStatisticsExport") this.exportedMetadata = statsMode.stats;
103
+ } else this.mode = Statistics.defaultStatsMode;
104
+ }
105
+ static statsModeFromAssumption({ reltuples }) {
106
+ return {
107
+ kind: "fromAssumption",
108
+ reltuples
109
+ };
110
+ }
111
+ /**
112
+ * Create a statistic mode from stats exported from another database
113
+ **/
114
+ static statsModeFromExport(stats) {
115
+ return {
116
+ kind: "fromStatisticsExport",
117
+ source: { kind: "inline" },
118
+ stats
119
+ };
120
+ }
121
+ static async fromPostgres(db, statsMode) {
122
+ const version = await db.serverNum();
123
+ return new Statistics(db, version, await Statistics.dumpStats(db, version, "full"), statsMode);
124
+ }
125
+ restoreStats(tx) {
126
+ return this.restoreStats17(tx);
127
+ }
128
+ approximateTotalRows() {
129
+ if (!this.exportedMetadata) return 0;
130
+ let totalRows = 0;
131
+ for (const table of this.exportedMetadata) totalRows += table.reltuples;
132
+ return totalRows;
133
+ }
134
+ /**
135
+ * We have to cast stavaluesN to the correct type
136
+ * This derives that type for us so it can be used in `array_in`
137
+ */
138
+ stavalueKind(values) {
139
+ if (!values || values.length === 0) return null;
140
+ const [elem] = values;
141
+ if (typeof elem === "number") return "real";
142
+ else if (typeof elem === "boolean") return "boolean";
143
+ return "text";
144
+ }
145
+ /**
146
+ * PostgreSQL's anyarray columns in pg_statistic can hold arrays of arrays
147
+ * for columns with array types (e.g. text[], int4[]). These create
148
+ * multidimensional arrays that can be "ragged" (sub-arrays with different
149
+ * lengths). jsonb_to_recordset can't reconstruct ragged multidimensional
150
+ * arrays from JSON, so we need to drop these values.
151
+ */
152
+ static safeStavalues(values) {
153
+ if (!values || values.length === 0) return values;
154
+ if (values.some((v) => Array.isArray(v))) {
155
+ console.warn("Discarding ragged multidimensional stavalues array");
156
+ return null;
157
+ }
158
+ return values;
159
+ }
160
+ async restoreStats17(tx) {
161
+ const warnings = {
162
+ tablesNotInExports: [],
163
+ tablesNotInTest: [],
164
+ tableNotAnalyzed: [],
165
+ statsMissing: []
166
+ };
167
+ const processedTables = /* @__PURE__ */ new Set();
168
+ let columnStatsUpdatePromise;
169
+ const columnStatsValues = [];
170
+ for (const table of this.ownMetadata) {
171
+ const target = (this.exportedMetadata?.find((m) => m.tableName === table.tableName && m.schemaName === table.schemaName))?.columns ?? table.columns;
172
+ for (const column of target) {
173
+ const { stats } = column;
174
+ if (!stats || this.mode.kind === "fromAssumption") {
175
+ const stawidth = stats?.stawidth || estimateStawidth(column);
176
+ columnStatsValues.push({
177
+ schema_name: table.schemaName,
178
+ table_name: table.tableName,
179
+ column_name: column.columnName,
180
+ stainherit: false,
181
+ stanullfrac: .04,
182
+ stawidth,
183
+ stadistinct: -.9,
184
+ stakind1: 0,
185
+ stakind2: 0,
186
+ stakind3: 3,
187
+ stakind4: 0,
188
+ stakind5: 0,
189
+ stacoll1: "0",
190
+ stacoll2: "0",
191
+ stacoll3: "0",
192
+ stacoll4: "0",
193
+ stacoll5: "0",
194
+ staop1: "0",
195
+ staop2: "0",
196
+ staop3: "0",
197
+ staop4: "0",
198
+ staop5: "0",
199
+ stanumbers1: null,
200
+ stanumbers2: null,
201
+ stanumbers3: [.9],
202
+ stanumbers4: null,
203
+ stanumbers5: null,
204
+ stavalues1: null,
205
+ stavalues2: null,
206
+ stavalues3: null,
207
+ stavalues4: null,
208
+ stavalues5: null,
209
+ _value_type1: "real",
210
+ _value_type2: "real",
211
+ _value_type3: "real",
212
+ _value_type4: "real",
213
+ _value_type5: "real"
214
+ });
215
+ continue;
216
+ }
217
+ columnStatsValues.push({
218
+ schema_name: table.schemaName,
219
+ table_name: table.tableName,
220
+ column_name: column.columnName,
221
+ stainherit: stats.stainherit ?? false,
222
+ stanullfrac: stats.stanullfrac,
223
+ stawidth: stats.stawidth,
224
+ stadistinct: stats.stadistinct,
225
+ stakind1: stats.stakind1,
226
+ stakind2: stats.stakind2,
227
+ stakind3: stats.stakind3,
228
+ stakind4: stats.stakind4,
229
+ stakind5: stats.stakind5,
230
+ staop1: stats.staop1,
231
+ staop2: stats.staop2,
232
+ staop3: stats.staop3,
233
+ staop4: stats.staop4,
234
+ staop5: stats.staop5,
235
+ stacoll1: stats.stacoll1,
236
+ stacoll2: stats.stacoll2,
237
+ stacoll3: stats.stacoll3,
238
+ stacoll4: stats.stacoll4,
239
+ stacoll5: stats.stacoll5,
240
+ stanumbers1: stats.stanumbers1,
241
+ stanumbers2: stats.stanumbers2,
242
+ stanumbers3: stats.stanumbers3,
243
+ stanumbers4: stats.stanumbers4,
244
+ stanumbers5: stats.stanumbers5,
245
+ stavalues1: Statistics.safeStavalues(stats.stavalues1),
246
+ stavalues2: Statistics.safeStavalues(stats.stavalues2),
247
+ stavalues3: Statistics.safeStavalues(stats.stavalues3),
248
+ stavalues4: Statistics.safeStavalues(stats.stavalues4),
249
+ stavalues5: Statistics.safeStavalues(stats.stavalues5),
250
+ _value_type1: this.stavalueKind(Statistics.safeStavalues(stats.stavalues1)),
251
+ _value_type2: this.stavalueKind(Statistics.safeStavalues(stats.stavalues2)),
252
+ _value_type3: this.stavalueKind(Statistics.safeStavalues(stats.stavalues3)),
253
+ _value_type4: this.stavalueKind(Statistics.safeStavalues(stats.stavalues4)),
254
+ _value_type5: this.stavalueKind(Statistics.safeStavalues(stats.stavalues5))
255
+ });
256
+ }
257
+ }
258
+ /**
259
+ * Postgres has 5 different slots for storing statistics per column and a potentially unlimited
260
+ * number of statistic types to choose from. Each code in `stakindN` can mean different things.
261
+ * Some statistics are just numerical values such as `n_distinct` and `correlation`, meaning
262
+ * they're only derived from `stanumbersN` and the value of `stanumbersN` is never read.
263
+ * Others take advantage of the `stavaluesN` columns which use `anyarray` type to store
264
+ * concrete values internally for things like histogram bounds.
265
+ * Unfortunately we cannot change anyarrays without a C extension.
266
+ *
267
+ * (1) = most common values
268
+ * (2) = scalar histogram
269
+ * (3) = correlation <- can change
270
+ * (4) = most common elements
271
+ * (5) = distinct elem count histogram <- can change
272
+ * (6) = length histogram (?) These don't appear in pg_stats
273
+ * (7) = bounds histogram (?) These don't appear in pg_stats
274
+ * (N) = potentially many more kinds of statistics. But postgres <=18 only uses these 7.
275
+ *
276
+ * What we're doing here is setting ANY statistic we cannot directly control
277
+ * (anything that relies on stavaluesN) to 0 to make sure the planner isn't influenced by what
278
+ * what the db collected from the test data.
279
+ * Because we do our tests with `generic_plan` it seems it's already unlikely that the planner will be
280
+ * using things like common values or histogram bounds to make the planning decisions we care about.
281
+ * This is a just in case.
282
+ */
283
+ const sql = dedent.default`
284
+ WITH input AS (
285
+ SELECT
286
+ c.oid AS starelid,
287
+ a.attnum AS staattnum,
288
+ v.stainherit,
289
+ v.stanullfrac,
290
+ v.stawidth,
291
+ v.stadistinct,
292
+ v.stakind1,
293
+ v.stakind2,
294
+ v.stakind3,
295
+ v.stakind4,
296
+ v.stakind5,
297
+ v.staop1,
298
+ v.staop2,
299
+ v.staop3,
300
+ v.staop4,
301
+ v.staop5,
302
+ v.stacoll1,
303
+ v.stacoll2,
304
+ v.stacoll3,
305
+ v.stacoll4,
306
+ v.stacoll5,
307
+ v.stanumbers1,
308
+ v.stanumbers2,
309
+ v.stanumbers3,
310
+ v.stanumbers4,
311
+ v.stanumbers5,
312
+ v.stavalues1,
313
+ v.stavalues2,
314
+ v.stavalues3,
315
+ v.stavalues4,
316
+ v.stavalues5,
317
+ _value_type1,
318
+ _value_type2,
319
+ _value_type3,
320
+ _value_type4,
321
+ _value_type5
322
+ FROM jsonb_to_recordset($1::jsonb) AS v(
323
+ schema_name text,
324
+ table_name text,
325
+ column_name text,
326
+ stainherit boolean,
327
+ stanullfrac real,
328
+ stawidth integer,
329
+ stadistinct real,
330
+ stakind1 real,
331
+ stakind2 real,
332
+ stakind3 real,
333
+ stakind4 real,
334
+ stakind5 real,
335
+ staop1 oid,
336
+ staop2 oid,
337
+ staop3 oid,
338
+ staop4 oid,
339
+ staop5 oid,
340
+ stacoll1 oid,
341
+ stacoll2 oid,
342
+ stacoll3 oid,
343
+ stacoll4 oid,
344
+ stacoll5 oid,
345
+ stanumbers1 real[],
346
+ stanumbers2 real[],
347
+ stanumbers3 real[],
348
+ stanumbers4 real[],
349
+ stanumbers5 real[],
350
+ stavalues1 text[],
351
+ stavalues2 text[],
352
+ stavalues3 text[],
353
+ stavalues4 text[],
354
+ stavalues5 text[],
355
+ _value_type1 text,
356
+ _value_type2 text,
357
+ _value_type3 text,
358
+ _value_type4 text,
359
+ _value_type5 text
360
+ )
361
+ JOIN pg_class c ON c.relname = v.table_name
362
+ JOIN pg_namespace n ON n.oid = c.relnamespace AND n.nspname = v.schema_name
363
+ JOIN pg_attribute a ON a.attrelid = c.oid AND a.attname = v.column_name
364
+ ),
365
+ updated AS (
366
+ UPDATE pg_statistic s
367
+ SET
368
+ stanullfrac = i.stanullfrac,
369
+ stawidth = i.stawidth,
370
+ stadistinct = i.stadistinct,
371
+ stakind1 = i.stakind1,
372
+ stakind2 = i.stakind2,
373
+ stakind3 = i.stakind3,
374
+ stakind4 = i.stakind4,
375
+ stakind5 = i.stakind5,
376
+ staop1 = i.staop1,
377
+ staop2 = i.staop2,
378
+ staop3 = i.staop3,
379
+ staop4 = i.staop4,
380
+ staop5 = i.staop5,
381
+ stacoll1 = i.stacoll1,
382
+ stacoll2 = i.stacoll2,
383
+ stacoll3 = i.stacoll3,
384
+ stacoll4 = i.stacoll4,
385
+ stacoll5 = i.stacoll5,
386
+ stanumbers1 = i.stanumbers1,
387
+ stanumbers2 = i.stanumbers2,
388
+ stanumbers3 = i.stanumbers3,
389
+ stanumbers4 = i.stanumbers4,
390
+ stanumbers5 = i.stanumbers5,
391
+ stavalues1 = case
392
+ when i.stavalues1 is null then null
393
+ else array_in(i.stavalues1::text::cstring, i._value_type1::regtype::oid, -1)
394
+ end,
395
+ stavalues2 = case
396
+ when i.stavalues2 is null then null
397
+ else array_in(i.stavalues2::text::cstring, i._value_type2::regtype::oid, -1)
398
+ end,
399
+ stavalues3 = case
400
+ when i.stavalues3 is null then null
401
+ else array_in(i.stavalues3::text::cstring, i._value_type3::regtype::oid, -1)
402
+ end,
403
+ stavalues4 = case
404
+ when i.stavalues4 is null then null
405
+ else array_in(i.stavalues4::text::cstring, i._value_type4::regtype::oid, -1)
406
+ end,
407
+ stavalues5 = case
408
+ when i.stavalues5 is null then null
409
+ else array_in(i.stavalues5::text::cstring, i._value_type5::regtype::oid, -1)
410
+ end
411
+ -- stavalues1 = i.stavalues1,
412
+ -- stavalues2 = i.stavalues2,
413
+ -- stavalues3 = i.stavalues3,
414
+ -- stavalues4 = i.stavalues4,
415
+ -- stavalues5 = i.stavalues5
416
+ FROM input i
417
+ WHERE s.starelid = i.starelid AND s.staattnum = i.staattnum AND s.stainherit = i.stainherit
418
+ RETURNING s.starelid, s.staattnum, s.stainherit, s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5
419
+ ),
420
+ inserted as (
421
+ INSERT INTO pg_statistic (
422
+ starelid, staattnum, stainherit,
423
+ stanullfrac, stawidth, stadistinct,
424
+ stakind1, stakind2, stakind3, stakind4, stakind5,
425
+ staop1, staop2, staop3, staop4, staop5,
426
+ stacoll1, stacoll2, stacoll3, stacoll4, stacoll5,
427
+ stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5,
428
+ stavalues1, stavalues2, stavalues3, stavalues4, stavalues5
429
+ )
430
+ SELECT
431
+ i.starelid, i.staattnum, i.stainherit,
432
+ i.stanullfrac, i.stawidth, i.stadistinct,
433
+ i.stakind1, i.stakind2, i.stakind3, i.stakind4, i.stakind5,
434
+ i.staop1, i.staop2, i.staop3, i.staop4, i.staop5,
435
+ i.stacoll1, i.stacoll2, i.stacoll3, i.stacoll4, i.stacoll5,
436
+ i.stanumbers1, i.stanumbers2, i.stanumbers3, i.stanumbers4, i.stanumbers5,
437
+ -- i.stavalues1, i.stavalues2, i.stavalues3, i.stavalues4, i.stavalues5,
438
+ case
439
+ when i.stavalues1 is null then null
440
+ else array_in(i.stavalues1::text::cstring, i._value_type1::regtype::oid, -1)
441
+ end,
442
+ case
443
+ when i.stavalues2 is null then null
444
+ else array_in(i.stavalues2::text::cstring, i._value_type2::regtype::oid, -1)
445
+ end,
446
+ case
447
+ when i.stavalues3 is null then null
448
+ else array_in(i.stavalues3::text::cstring, i._value_type3::regtype::oid, -1)
449
+ end,
450
+ case
451
+ when i.stavalues4 is null then null
452
+ else array_in(i.stavalues4::text::cstring, i._value_type4::regtype::oid, -1)
453
+ end,
454
+ case
455
+ when i.stavalues5 is null then null
456
+ else array_in(i.stavalues5::text::cstring, i._value_type5::regtype::oid, -1)
457
+ end
458
+ -- i._value_type1, i._value_type2, i._value_type3, i._value_type4, i._value_type5
459
+ FROM input i
460
+ LEFT JOIN updated u
461
+ ON i.starelid = u.starelid AND i.staattnum = u.staattnum AND i.stainherit = u.stainherit
462
+ WHERE u.starelid IS NULL
463
+ returning starelid, staattnum, stainherit, stakind1, stakind2, stakind3, stakind4, stakind5
464
+ )
465
+ select * from updated union all (select * from inserted); -- @qd_introspection`;
466
+ columnStatsUpdatePromise = tx.exec(sql, [columnStatsValues]).catch((err) => {
467
+ console.error("Something wrong wrong updating column stats");
468
+ console.error(err);
469
+ throw err;
470
+ });
471
+ const reltuplesValues = [];
472
+ for (const table of this.ownMetadata) {
473
+ processedTables.add(`${table.schemaName}.${table.tableName}`);
474
+ let targetTable;
475
+ if (this.exportedMetadata) targetTable = this.exportedMetadata.find((m) => m.tableName === table.tableName && m.schemaName === table.schemaName);
476
+ else targetTable = table;
477
+ let reltuples;
478
+ let relpages;
479
+ let relallvisible = 0;
480
+ let relallfrozen;
481
+ if (this.mode.kind === "fromAssumption") {
482
+ reltuples = this.mode.reltuples;
483
+ relpages = estimateRelpages(reltuples, table.columns);
484
+ } else if (targetTable) {
485
+ reltuples = targetTable.reltuples;
486
+ relpages = targetTable.relpages;
487
+ relallvisible = targetTable.relallvisible;
488
+ relallfrozen = targetTable.relallfrozen;
489
+ } else {
490
+ warnings.tablesNotInExports.push(`${table.schemaName}.${table.tableName}`);
491
+ reltuples = DEFAULT_RELTUPLES;
492
+ relpages = DEFAULT_RELPAGES;
493
+ }
494
+ reltuplesValues.push({
495
+ relname: table.tableName,
496
+ schema_name: table.schemaName,
497
+ reltuples,
498
+ relpages,
499
+ relallfrozen,
500
+ relallvisible
501
+ });
502
+ if (this.mode.kind === "fromAssumption") for (const index of table.indexes) {
503
+ const indexRelpages = estimateIndexRelpages(this.mode.reltuples, index.columns, index.fillfactor / 100, index.amname, relpages);
504
+ reltuplesValues.push({
505
+ relname: index.indexName,
506
+ schema_name: table.schemaName,
507
+ reltuples: this.mode.reltuples,
508
+ relpages: indexRelpages,
509
+ relallfrozen: 0,
510
+ relallvisible: indexRelpages
511
+ });
512
+ }
513
+ else if (targetTable) for (const index of targetTable.indexes) reltuplesValues.push({
514
+ relname: index.indexName,
515
+ schema_name: targetTable.schemaName,
516
+ reltuples: index.reltuples,
517
+ relpages: index.relpages,
518
+ relallfrozen: index.relallfrozen,
519
+ relallvisible: index.relallvisible
520
+ });
521
+ }
522
+ const reltuplesQuery = dedent.default`
523
+ update pg_class p
524
+ set reltuples = v.reltuples,
525
+ relpages = v.relpages,
526
+ -- relallfrozen = case when v.relallfrozen is null then p.relallfrozen else v.relallfrozen end,
527
+ relallvisible = case when v.relallvisible is null then p.relallvisible else v.relallvisible end
528
+ from jsonb_to_recordset($1::jsonb)
529
+ as v(reltuples real, relpages integer, relallfrozen integer, relallvisible integer, relname text, schema_name text)
530
+ where p.relname = v.relname
531
+ and p.relnamespace = (select oid from pg_namespace where nspname = v.schema_name)
532
+ returning p.relname, p.relnamespace, p.reltuples, p.relpages;
533
+ `;
534
+ const reltuplesPromise = tx.exec(reltuplesQuery, [reltuplesValues]).catch((err) => {
535
+ console.error("Something went wrong updating reltuples/relpages");
536
+ console.error(err);
537
+ return err;
538
+ });
539
+ if (this.exportedMetadata) for (const table of this.exportedMetadata) {
540
+ const tableExists = processedTables.has(`${table.schemaName}.${table.tableName}`);
541
+ if (tableExists && table.reltuples === -1) {
542
+ console.warn(`Table ${table.tableName} has reltuples -1. Your production database is probably not analyzed properly`);
543
+ warnings.tableNotAnalyzed.push(`${table.schemaName}.${table.tableName}`);
544
+ }
545
+ if (tableExists) continue;
546
+ warnings.tablesNotInTest.push(`${table.schemaName}.${table.tableName}`);
547
+ }
548
+ const [statsUpdates, reltuplesUpdates] = await Promise.all([columnStatsUpdatePromise, reltuplesPromise]);
549
+ if (!(statsUpdates ? statsUpdates.length === columnStatsValues.length : true)) console.error(`Did not update expected column stats`);
550
+ if (reltuplesUpdates.length !== reltuplesValues.length) console.error(`Did not update expected reltuples/relpages`);
551
+ return warnings;
552
+ }
553
+ static async dumpStats(db, postgresVersion, kind) {
554
+ const fullDump = kind === "full";
555
+ console.log(`dumping stats for postgres ${(0, colorette.gray)(postgresVersion)}`);
556
+ const stats = await db.exec(`
557
+ WITH table_columns AS (
558
+ SELECT
559
+ cl.relname,
560
+ n.nspname,
561
+ cl.reltuples,
562
+ cl.relpages,
563
+ cl.relallvisible,
564
+ -- cl.relallfrozen,
565
+ json_agg(
566
+ json_build_object(
567
+ 'columnName', a.attname,
568
+ 'attlen', CASE WHEN a.attlen > 0 THEN a.attlen ELSE NULL END,
569
+ 'stats', (
570
+ SELECT json_build_object(
571
+ 'starelid', s.starelid,
572
+ 'staattnum', s.staattnum,
573
+ 'stanullfrac', s.stanullfrac,
574
+ 'stawidth', s.stawidth,
575
+ 'stadistinct', s.stadistinct,
576
+ 'stakind1', s.stakind1, 'staop1', s.staop1, 'stacoll1', s.stacoll1, 'stanumbers1', s.stanumbers1,
577
+ 'stakind2', s.stakind2, 'staop2', s.staop2, 'stacoll2', s.stacoll2, 'stanumbers2', s.stanumbers2,
578
+ 'stakind3', s.stakind3, 'staop3', s.staop3, 'stacoll3', s.stacoll3, 'stanumbers3', s.stanumbers3,
579
+ 'stakind4', s.stakind4, 'staop4', s.staop4, 'stacoll4', s.stacoll4, 'stanumbers4', s.stanumbers4,
580
+ 'stakind5', s.stakind5, 'staop5', s.staop5, 'stacoll5', s.stacoll5, 'stanumbers5', s.stanumbers5,
581
+ 'stavalues1', CASE WHEN $1 THEN s.stavalues1 ELSE NULL END,
582
+ 'stavalues2', CASE WHEN $1 THEN s.stavalues2 ELSE NULL END,
583
+ 'stavalues3', CASE WHEN $1 THEN s.stavalues3 ELSE NULL END,
584
+ 'stavalues4', CASE WHEN $1 THEN s.stavalues4 ELSE NULL END,
585
+ 'stavalues5', CASE WHEN $1 THEN s.stavalues5 ELSE NULL END
586
+ )
587
+ FROM pg_statistic s
588
+ WHERE s.starelid = a.attrelid AND s.staattnum = a.attnum
589
+ )
590
+ )
591
+ ORDER BY a.attnum
592
+ ) AS columns
593
+ FROM pg_class cl
594
+ JOIN pg_namespace n ON n.oid = cl.relnamespace
595
+ JOIN pg_attribute a ON a.attrelid = cl.oid AND a.attnum > 0 AND NOT a.attisdropped
596
+ WHERE cl.relkind = 'r'
597
+ AND n.nspname NOT IN ('pg_catalog', 'information_schema', 'tiger', 'tiger_data', 'topology')
598
+ AND cl.relname NOT IN ('pg_stat_statements', 'pg_stat_statements_info')
599
+ GROUP BY cl.relname, n.nspname, cl.reltuples, cl.relpages, cl.relallvisible
600
+ ),
601
+ table_indexes AS (
602
+ SELECT
603
+ t.relname AS table_name,
604
+ json_agg(
605
+ json_build_object(
606
+ 'indexName', i.relname,
607
+ 'amname', am.amname,
608
+ 'reltuples', i.reltuples,
609
+ 'relpages', i.relpages,
610
+ 'relallvisible', i.relallvisible,
611
+ -- 'relallfrozen', i.relallfrozen,
612
+ 'fillfactor', COALESCE(
613
+ (
614
+ SELECT (regexp_match(opt, 'fillfactor=(\\d+)'))[1]::integer
615
+ FROM unnest(i.reloptions) AS opt
616
+ WHERE opt LIKE 'fillfactor=%'
617
+ LIMIT 1
618
+ ),
619
+ 90
620
+ ),
621
+ 'columns', COALESCE(
622
+ (
623
+ SELECT json_agg(json_build_object(
624
+ 'attlen', CASE WHEN a.attlen > 0 THEN a.attlen ELSE NULL END
625
+ ) ORDER BY col_pos.ord)
626
+ FROM unnest(ix.indkey) WITH ORDINALITY AS col_pos(attnum, ord)
627
+ JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = col_pos.attnum
628
+ WHERE col_pos.attnum > 0
629
+ ),
630
+ '[]'::json
631
+ )
632
+ )
633
+ ) AS indexes
634
+ FROM pg_class t
635
+ JOIN pg_index ix ON ix.indrelid = t.oid
636
+ JOIN pg_class i ON i.oid = ix.indexrelid
637
+ JOIN pg_am am ON am.oid = i.relam
638
+ JOIN pg_namespace n ON n.oid = t.relnamespace
639
+ WHERE t.relname NOT LIKE 'pg_%'
640
+ AND n.nspname <> 'information_schema'
641
+ AND n.nspname NOT IN ('tiger', 'tiger_data', 'topology')
642
+ GROUP BY t.relname
643
+ )
644
+ SELECT json_agg(
645
+ json_build_object(
646
+ 'tableName', tc.relname,
647
+ 'schemaName', tc.nspname,
648
+ 'reltuples', tc.reltuples,
649
+ 'relpages', tc.relpages,
650
+ 'relallvisible', tc.relallvisible,
651
+ -- 'relallfrozen', tc.relallfrozen,
652
+ 'columns', COALESCE(tc.columns, '[]'::json),
653
+ 'indexes', COALESCE(ti.indexes, '[]'::json)
654
+ )
655
+ )
656
+ FROM table_columns tc
657
+ LEFT JOIN table_indexes ti
658
+ ON ti.table_name = tc.relname;
659
+ `, [fullDump]);
660
+ return zod.z.array(ExportedStats).parse(stats[0].json_agg);
661
+ }
662
+ /**
663
+ * Returns all indexes in the database.
664
+ * ONLY handles regular btree indexes
665
+ */
666
+ async getExistingIndexes() {
667
+ return await this.db.exec(`
668
+ WITH partitioned_tables AS (
669
+ SELECT
670
+ inhparent::regclass AS parent_table,
671
+ inhrelid::regclass AS partition_table
672
+ FROM
673
+ pg_inherits
674
+ )
675
+ SELECT
676
+ n.nspname AS schema_name,
677
+ COALESCE(pt.parent_table::text, t.relname) AS table_name,
678
+ i.relname AS index_name,
679
+ ix.indisprimary as is_primary,
680
+ ix.indisunique as is_unique,
681
+ am.amname AS index_type,
682
+ array_agg(
683
+ CASE
684
+ -- Handle regular columns
685
+ WHEN a.attname IS NOT NULL THEN
686
+ json_build_object('name', a.attname, 'order',
687
+ CASE
688
+ WHEN (indoption[array_position(ix.indkey, a.attnum)] & 1) = 1 THEN 'DESC'
689
+ ELSE 'ASC'
690
+ END,
691
+ 'opclass', CASE WHEN opc.opcdefault THEN NULL ELSE opc.opcname END)
692
+ -- Handle expressions
693
+ ELSE
694
+ json_build_object('name', pg_get_expr((ix.indexprs)::pg_node_tree, t.oid), 'order',
695
+ CASE
696
+ WHEN (indoption[array_position(ix.indkey, k.attnum)] & 1) = 1 THEN 'DESC'
697
+ ELSE 'ASC'
698
+ END,
699
+ 'opclass', CASE WHEN opc.opcdefault THEN NULL ELSE opc.opcname END)
700
+ END
701
+ ORDER BY array_position(ix.indkey, k.attnum)
702
+ ) AS index_columns
703
+ FROM
704
+ pg_class t
705
+ LEFT JOIN partitioned_tables pt ON t.oid = pt.partition_table
706
+ JOIN pg_index ix ON t.oid = ix.indrelid
707
+ JOIN pg_class i ON i.oid = ix.indexrelid
708
+ JOIN pg_am am ON i.relam = am.oid
709
+ LEFT JOIN LATERAL unnest(ix.indkey) WITH ORDINALITY k(attnum, ordinality) ON true
710
+ LEFT JOIN pg_attribute a ON a.attnum = k.attnum AND a.attrelid = t.oid
711
+ LEFT JOIN pg_opclass opc ON opc.oid = ix.indclass[k.ordinality - 1]
712
+ JOIN pg_namespace n ON t.relnamespace = n.oid
713
+ WHERE
714
+ n.nspname not like 'pg_%' and
715
+ n.nspname <> 'information_schema'
716
+ GROUP BY
717
+ n.nspname, COALESCE(pt.parent_table::text, t.relname), i.relname, am.amname, ix.indisprimary, ix.indisunique
718
+ ORDER BY
719
+ COALESCE(pt.parent_table::text, t.relname), i.relname; -- @qd_introspection
720
+ `);
721
+ }
722
+ };
723
+ require_defineProperty._defineProperty(Statistics, "defaultStatsMode", Object.freeze({
724
+ kind: "fromAssumption",
725
+ reltuples: DEFAULT_RELTUPLES,
726
+ relpages: DEFAULT_RELPAGES
727
+ }));
728
+ //#endregion
729
+ exports.ExportedStats = ExportedStats;
730
+ exports.ExportedStatsColumns = ExportedStatsColumns;
731
+ exports.ExportedStatsIndex = ExportedStatsIndex;
732
+ exports.ExportedStatsStatistics = ExportedStatsStatistics;
733
+ exports.ExportedStatsV1 = ExportedStatsV1;
734
+ exports.Statistics = Statistics;
735
+ exports.StatisticsMode = StatisticsMode;
736
+ exports.StatisticsSource = StatisticsSource;
737
+
738
+ //# sourceMappingURL=statistics.cjs.map