@query-doctor/core 0.7.2-rc.1 → 0.8.0-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.
- package/dist/index.cjs +80 -45
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +28 -7
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +28 -7
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +80 -45
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1817,14 +1817,18 @@ const ExportedStatsStatistics = zod.z.object({
|
|
|
1817
1817
|
});
|
|
1818
1818
|
const ExportedStatsColumns = zod.z.object({
|
|
1819
1819
|
columnName: zod.z.string(),
|
|
1820
|
+
attlen: zod.z.number().nullable(),
|
|
1820
1821
|
stats: ExportedStatsStatistics.nullable()
|
|
1821
1822
|
});
|
|
1822
1823
|
const ExportedStatsIndex = zod.z.object({
|
|
1823
1824
|
indexName: zod.z.string(),
|
|
1825
|
+
amname: zod.z.string().default("btree"),
|
|
1824
1826
|
relpages: zod.z.number(),
|
|
1825
1827
|
reltuples: zod.z.number(),
|
|
1826
1828
|
relallvisible: zod.z.number(),
|
|
1827
|
-
relallfrozen: zod.z.number().optional()
|
|
1829
|
+
relallfrozen: zod.z.number().optional(),
|
|
1830
|
+
fillfactor: zod.z.number().default(90),
|
|
1831
|
+
columns: zod.z.array(zod.z.object({ attlen: zod.z.number().nullable() })).default([])
|
|
1828
1832
|
});
|
|
1829
1833
|
const ExportedStatsV1 = zod.z.object({
|
|
1830
1834
|
tableName: zod.z.string(),
|
|
@@ -1833,14 +1837,13 @@ const ExportedStatsV1 = zod.z.object({
|
|
|
1833
1837
|
reltuples: zod.z.number(),
|
|
1834
1838
|
relallvisible: zod.z.number(),
|
|
1835
1839
|
relallfrozen: zod.z.number().optional(),
|
|
1836
|
-
columns: zod.z.array(ExportedStatsColumns).
|
|
1840
|
+
columns: zod.z.array(ExportedStatsColumns).default([]),
|
|
1837
1841
|
indexes: zod.z.array(ExportedStatsIndex)
|
|
1838
1842
|
});
|
|
1839
1843
|
const ExportedStats = zod.z.union([ExportedStatsV1]);
|
|
1840
1844
|
const StatisticsMode = zod.z.discriminatedUnion("kind", [zod.z.object({
|
|
1841
1845
|
kind: zod.z.literal("fromAssumption"),
|
|
1842
|
-
reltuples: zod.z.number().min(0)
|
|
1843
|
-
relpages: zod.z.number().min(0)
|
|
1846
|
+
reltuples: zod.z.number().min(0)
|
|
1844
1847
|
}), zod.z.object({
|
|
1845
1848
|
kind: zod.z.literal("fromStatisticsExport"),
|
|
1846
1849
|
stats: zod.z.array(ExportedStats),
|
|
@@ -1848,6 +1851,19 @@ const StatisticsMode = zod.z.discriminatedUnion("kind", [zod.z.object({
|
|
|
1848
1851
|
})]);
|
|
1849
1852
|
const DEFAULT_RELTUPLES = 1e4;
|
|
1850
1853
|
const DEFAULT_RELPAGES = 1;
|
|
1854
|
+
const DEFAULT_PAGE_SIZE = 2 ** 13;
|
|
1855
|
+
function estimateStawidth(col) {
|
|
1856
|
+
return col.attlen ?? 32;
|
|
1857
|
+
}
|
|
1858
|
+
function estimateRelpages(reltuples, columns) {
|
|
1859
|
+
const rowWidth = columns.reduce((sum, col) => sum + estimateStawidth(col), 0) + 27;
|
|
1860
|
+
return Math.ceil(reltuples * rowWidth / DEFAULT_PAGE_SIZE);
|
|
1861
|
+
}
|
|
1862
|
+
function estimateIndexRelpages(reltuples, columns, fillfactor, amname, tableRelpages) {
|
|
1863
|
+
if (amname === "gin") return Math.ceil(tableRelpages * .3);
|
|
1864
|
+
const keyWidth = columns.reduce((sum, col) => sum + estimateStawidth(col) + 16, 0);
|
|
1865
|
+
return Math.ceil(reltuples * keyWidth / DEFAULT_PAGE_SIZE / fillfactor);
|
|
1866
|
+
}
|
|
1851
1867
|
var Statistics = class Statistics {
|
|
1852
1868
|
constructor(db, postgresVersion, ownMetadata, statsMode) {
|
|
1853
1869
|
this.db = db;
|
|
@@ -1860,11 +1876,10 @@ var Statistics = class Statistics {
|
|
|
1860
1876
|
if (statsMode.kind === "fromStatisticsExport") this.exportedMetadata = statsMode.stats;
|
|
1861
1877
|
} else this.mode = Statistics.defaultStatsMode;
|
|
1862
1878
|
}
|
|
1863
|
-
static statsModeFromAssumption({ reltuples
|
|
1879
|
+
static statsModeFromAssumption({ reltuples }) {
|
|
1864
1880
|
return {
|
|
1865
1881
|
kind: "fromAssumption",
|
|
1866
|
-
reltuples
|
|
1867
|
-
relpages
|
|
1882
|
+
reltuples
|
|
1868
1883
|
};
|
|
1869
1884
|
}
|
|
1870
1885
|
/**
|
|
@@ -1928,17 +1943,17 @@ var Statistics = class Statistics {
|
|
|
1928
1943
|
const columnStatsValues = [];
|
|
1929
1944
|
for (const table of this.ownMetadata) {
|
|
1930
1945
|
const target = (this.exportedMetadata?.find((m) => m.tableName === table.tableName && m.schemaName === table.schemaName))?.columns ?? table.columns;
|
|
1931
|
-
if (!target) continue;
|
|
1932
1946
|
for (const column of target) {
|
|
1933
1947
|
const { stats } = column;
|
|
1934
1948
|
if (!stats || this.mode.kind === "fromAssumption") {
|
|
1949
|
+
const stawidth = stats?.stawidth || estimateStawidth(column);
|
|
1935
1950
|
columnStatsValues.push({
|
|
1936
1951
|
schema_name: table.schemaName,
|
|
1937
1952
|
table_name: table.tableName,
|
|
1938
1953
|
column_name: column.columnName,
|
|
1939
1954
|
stainherit: false,
|
|
1940
1955
|
stanullfrac: .04,
|
|
1941
|
-
stawidth
|
|
1956
|
+
stawidth,
|
|
1942
1957
|
stadistinct: -.9,
|
|
1943
1958
|
stakind1: 0,
|
|
1944
1959
|
stakind2: 0,
|
|
@@ -2239,7 +2254,7 @@ var Statistics = class Statistics {
|
|
|
2239
2254
|
let relallfrozen;
|
|
2240
2255
|
if (this.mode.kind === "fromAssumption") {
|
|
2241
2256
|
reltuples = this.mode.reltuples;
|
|
2242
|
-
relpages =
|
|
2257
|
+
relpages = estimateRelpages(reltuples, table.columns);
|
|
2243
2258
|
} else if (targetTable) {
|
|
2244
2259
|
reltuples = targetTable.reltuples;
|
|
2245
2260
|
relpages = targetTable.relpages;
|
|
@@ -2258,15 +2273,18 @@ var Statistics = class Statistics {
|
|
|
2258
2273
|
relallfrozen,
|
|
2259
2274
|
relallvisible
|
|
2260
2275
|
});
|
|
2261
|
-
if (this.mode.kind === "fromAssumption") for (const index of table.indexes)
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2276
|
+
if (this.mode.kind === "fromAssumption") for (const index of table.indexes) {
|
|
2277
|
+
const indexRelpages = estimateIndexRelpages(this.mode.reltuples, index.columns, index.fillfactor / 100, index.amname, relpages);
|
|
2278
|
+
reltuplesValues.push({
|
|
2279
|
+
relname: index.indexName,
|
|
2280
|
+
schema_name: table.schemaName,
|
|
2281
|
+
reltuples: this.mode.reltuples,
|
|
2282
|
+
relpages: indexRelpages,
|
|
2283
|
+
relallfrozen: 0,
|
|
2284
|
+
relallvisible: indexRelpages
|
|
2285
|
+
});
|
|
2286
|
+
}
|
|
2287
|
+
else if (targetTable) for (const index of targetTable.indexes) reltuplesValues.push({
|
|
2270
2288
|
relname: index.indexName,
|
|
2271
2289
|
schema_name: targetTable.schemaName,
|
|
2272
2290
|
reltuples: index.reltuples,
|
|
@@ -2309,19 +2327,19 @@ var Statistics = class Statistics {
|
|
|
2309
2327
|
static async dumpStats(db, postgresVersion, kind) {
|
|
2310
2328
|
const fullDump = kind === "full";
|
|
2311
2329
|
console.log(`dumping stats for postgres ${(0, colorette.gray)(postgresVersion)}`);
|
|
2312
|
-
|
|
2330
|
+
const stats = await db.exec(`
|
|
2313
2331
|
WITH table_columns AS (
|
|
2314
2332
|
SELECT
|
|
2315
|
-
|
|
2316
|
-
|
|
2333
|
+
cl.relname,
|
|
2334
|
+
n.nspname,
|
|
2317
2335
|
cl.reltuples,
|
|
2318
2336
|
cl.relpages,
|
|
2319
2337
|
cl.relallvisible,
|
|
2320
2338
|
-- cl.relallfrozen,
|
|
2321
|
-
n.nspname AS schema_name,
|
|
2322
2339
|
json_agg(
|
|
2323
2340
|
json_build_object(
|
|
2324
|
-
'columnName',
|
|
2341
|
+
'columnName', a.attname,
|
|
2342
|
+
'attlen', CASE WHEN a.attlen > 0 THEN a.attlen ELSE NULL END,
|
|
2325
2343
|
'stats', (
|
|
2326
2344
|
SELECT json_build_object(
|
|
2327
2345
|
'starelid', s.starelid,
|
|
@@ -2344,21 +2362,15 @@ var Statistics = class Statistics {
|
|
|
2344
2362
|
WHERE s.starelid = a.attrelid AND s.staattnum = a.attnum
|
|
2345
2363
|
)
|
|
2346
2364
|
)
|
|
2347
|
-
ORDER BY
|
|
2365
|
+
ORDER BY a.attnum
|
|
2348
2366
|
) AS columns
|
|
2349
|
-
FROM
|
|
2350
|
-
JOIN
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
ON n.oid = cl.relnamespace
|
|
2357
|
-
WHERE c.table_name NOT LIKE 'pg_%'
|
|
2358
|
-
AND n.nspname <> 'information_schema'
|
|
2359
|
-
AND n.nspname NOT IN ('tiger', 'tiger_data', 'topology')
|
|
2360
|
-
AND c.table_name NOT IN ('pg_stat_statements', 'pg_stat_statements_info')
|
|
2361
|
-
GROUP BY c.table_name, c.table_schema, cl.reltuples, cl.relpages, cl.relallvisible, n.nspname
|
|
2367
|
+
FROM pg_class cl
|
|
2368
|
+
JOIN pg_namespace n ON n.oid = cl.relnamespace
|
|
2369
|
+
JOIN pg_attribute a ON a.attrelid = cl.oid AND a.attnum > 0 AND NOT a.attisdropped
|
|
2370
|
+
WHERE cl.relkind = 'r'
|
|
2371
|
+
AND n.nspname NOT IN ('pg_catalog', 'information_schema', 'tiger', 'tiger_data', 'topology')
|
|
2372
|
+
AND cl.relname NOT IN ('pg_stat_statements', 'pg_stat_statements_info')
|
|
2373
|
+
GROUP BY cl.relname, n.nspname, cl.reltuples, cl.relpages, cl.relallvisible
|
|
2362
2374
|
),
|
|
2363
2375
|
table_indexes AS (
|
|
2364
2376
|
SELECT
|
|
@@ -2366,15 +2378,37 @@ var Statistics = class Statistics {
|
|
|
2366
2378
|
json_agg(
|
|
2367
2379
|
json_build_object(
|
|
2368
2380
|
'indexName', i.relname,
|
|
2381
|
+
'amname', am.amname,
|
|
2369
2382
|
'reltuples', i.reltuples,
|
|
2370
2383
|
'relpages', i.relpages,
|
|
2371
|
-
'relallvisible', i.relallvisible
|
|
2372
|
-
-- 'relallfrozen', i.relallfrozen
|
|
2384
|
+
'relallvisible', i.relallvisible,
|
|
2385
|
+
-- 'relallfrozen', i.relallfrozen,
|
|
2386
|
+
'fillfactor', COALESCE(
|
|
2387
|
+
(
|
|
2388
|
+
SELECT (regexp_match(opt, 'fillfactor=(\\d+)'))[1]::integer
|
|
2389
|
+
FROM unnest(i.reloptions) AS opt
|
|
2390
|
+
WHERE opt LIKE 'fillfactor=%'
|
|
2391
|
+
LIMIT 1
|
|
2392
|
+
),
|
|
2393
|
+
90
|
|
2394
|
+
),
|
|
2395
|
+
'columns', COALESCE(
|
|
2396
|
+
(
|
|
2397
|
+
SELECT json_agg(json_build_object(
|
|
2398
|
+
'attlen', CASE WHEN a.attlen > 0 THEN a.attlen ELSE NULL END
|
|
2399
|
+
) ORDER BY col_pos.ord)
|
|
2400
|
+
FROM unnest(ix.indkey) WITH ORDINALITY AS col_pos(attnum, ord)
|
|
2401
|
+
JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = col_pos.attnum
|
|
2402
|
+
WHERE col_pos.attnum > 0
|
|
2403
|
+
),
|
|
2404
|
+
'[]'::json
|
|
2405
|
+
)
|
|
2373
2406
|
)
|
|
2374
2407
|
) AS indexes
|
|
2375
2408
|
FROM pg_class t
|
|
2376
2409
|
JOIN pg_index ix ON ix.indrelid = t.oid
|
|
2377
2410
|
JOIN pg_class i ON i.oid = ix.indexrelid
|
|
2411
|
+
JOIN pg_am am ON am.oid = i.relam
|
|
2378
2412
|
JOIN pg_namespace n ON n.oid = t.relnamespace
|
|
2379
2413
|
WHERE t.relname NOT LIKE 'pg_%'
|
|
2380
2414
|
AND n.nspname <> 'information_schema'
|
|
@@ -2383,20 +2417,21 @@ var Statistics = class Statistics {
|
|
|
2383
2417
|
)
|
|
2384
2418
|
SELECT json_agg(
|
|
2385
2419
|
json_build_object(
|
|
2386
|
-
'tableName', tc.
|
|
2387
|
-
'schemaName', tc.
|
|
2420
|
+
'tableName', tc.relname,
|
|
2421
|
+
'schemaName', tc.nspname,
|
|
2388
2422
|
'reltuples', tc.reltuples,
|
|
2389
2423
|
'relpages', tc.relpages,
|
|
2390
2424
|
'relallvisible', tc.relallvisible,
|
|
2391
2425
|
-- 'relallfrozen', tc.relallfrozen,
|
|
2392
|
-
'columns', tc.columns,
|
|
2426
|
+
'columns', COALESCE(tc.columns, '[]'::json),
|
|
2393
2427
|
'indexes', COALESCE(ti.indexes, '[]'::json)
|
|
2394
2428
|
)
|
|
2395
2429
|
)
|
|
2396
2430
|
FROM table_columns tc
|
|
2397
2431
|
LEFT JOIN table_indexes ti
|
|
2398
|
-
ON ti.table_name = tc.
|
|
2399
|
-
`, [fullDump])
|
|
2432
|
+
ON ti.table_name = tc.relname;
|
|
2433
|
+
`, [fullDump]);
|
|
2434
|
+
return zod.z.array(ExportedStats).parse(stats[0].json_agg);
|
|
2400
2435
|
}
|
|
2401
2436
|
/**
|
|
2402
2437
|
* Returns all indexes in the database.
|