postgresai 0.14.0-dev.45 → 0.14.0-dev.46

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.
@@ -13064,7 +13064,7 @@ var {
13064
13064
  // package.json
13065
13065
  var package_default = {
13066
13066
  name: "postgresai",
13067
- version: "0.14.0-dev.45",
13067
+ version: "0.14.0-dev.46",
13068
13068
  description: "postgres_ai CLI",
13069
13069
  license: "Apache-2.0",
13070
13070
  private: false,
@@ -15881,7 +15881,7 @@ var Result = import_lib.default.Result;
15881
15881
  var TypeOverrides = import_lib.default.TypeOverrides;
15882
15882
  var defaults = import_lib.default.defaults;
15883
15883
  // package.json
15884
- var version = "0.14.0-dev.45";
15884
+ var version = "0.14.0-dev.46";
15885
15885
  var package_default2 = {
15886
15886
  name: "postgresai",
15887
15887
  version,
@@ -27375,12 +27375,216 @@ async function generateH004(client, nodeName = "node-01") {
27375
27375
  };
27376
27376
  return report;
27377
27377
  }
27378
+ async function generateD004(client, nodeName) {
27379
+ const report = createBaseReport("D004", "pg_stat_statements and pg_stat_kcache settings", nodeName);
27380
+ const postgresVersion = await getPostgresVersion(client);
27381
+ const allSettings = await getSettings(client);
27382
+ const pgssSettings = {};
27383
+ for (const [name, setting] of Object.entries(allSettings)) {
27384
+ if (name.startsWith("pg_stat_statements") || name.startsWith("pg_stat_kcache")) {
27385
+ pgssSettings[name] = setting;
27386
+ }
27387
+ }
27388
+ let pgssAvailable = false;
27389
+ let pgssMetricsCount = 0;
27390
+ let pgssTotalCalls = 0;
27391
+ const pgssSampleQueries = [];
27392
+ try {
27393
+ const extCheck = await client.query("select 1 from pg_extension where extname = 'pg_stat_statements'");
27394
+ if (extCheck.rows.length > 0) {
27395
+ pgssAvailable = true;
27396
+ const statsResult = await client.query(`
27397
+ select count(*) as cnt, coalesce(sum(calls), 0) as total_calls
27398
+ from pg_stat_statements
27399
+ `);
27400
+ pgssMetricsCount = parseInt(statsResult.rows[0]?.cnt || "0", 10);
27401
+ pgssTotalCalls = parseInt(statsResult.rows[0]?.total_calls || "0", 10);
27402
+ const sampleResult = await client.query(`
27403
+ select
27404
+ queryid::text as queryid,
27405
+ coalesce(usename, 'unknown') as "user",
27406
+ coalesce(datname, 'unknown') as database,
27407
+ calls
27408
+ from pg_stat_statements s
27409
+ left join pg_database d on s.dbid = d.oid
27410
+ left join pg_user u on s.userid = u.usesysid
27411
+ order by calls desc
27412
+ limit 5
27413
+ `);
27414
+ for (const row of sampleResult.rows) {
27415
+ pgssSampleQueries.push({
27416
+ queryid: row.queryid,
27417
+ user: row.user,
27418
+ database: row.database,
27419
+ calls: parseInt(row.calls, 10)
27420
+ });
27421
+ }
27422
+ }
27423
+ } catch {}
27424
+ let kcacheAvailable = false;
27425
+ let kcacheMetricsCount = 0;
27426
+ let kcacheTotalExecTime = 0;
27427
+ let kcacheTotalUserTime = 0;
27428
+ let kcacheTotalSystemTime = 0;
27429
+ const kcacheSampleQueries = [];
27430
+ try {
27431
+ const extCheck = await client.query("select 1 from pg_extension where extname = 'pg_stat_kcache'");
27432
+ if (extCheck.rows.length > 0) {
27433
+ kcacheAvailable = true;
27434
+ const statsResult = await client.query(`
27435
+ select
27436
+ count(*) as cnt,
27437
+ coalesce(sum(exec_user_time + exec_system_time), 0) as total_exec_time,
27438
+ coalesce(sum(exec_user_time), 0) as total_user_time,
27439
+ coalesce(sum(exec_system_time), 0) as total_system_time
27440
+ from pg_stat_kcache
27441
+ `);
27442
+ kcacheMetricsCount = parseInt(statsResult.rows[0]?.cnt || "0", 10);
27443
+ kcacheTotalExecTime = parseFloat(statsResult.rows[0]?.total_exec_time || "0");
27444
+ kcacheTotalUserTime = parseFloat(statsResult.rows[0]?.total_user_time || "0");
27445
+ kcacheTotalSystemTime = parseFloat(statsResult.rows[0]?.total_system_time || "0");
27446
+ const sampleResult = await client.query(`
27447
+ select
27448
+ queryid::text as queryid,
27449
+ coalesce(usename, 'unknown') as "user",
27450
+ (exec_user_time + exec_system_time) as exec_total_time
27451
+ from pg_stat_kcache k
27452
+ left join pg_user u on k.userid = u.usesysid
27453
+ order by (exec_user_time + exec_system_time) desc
27454
+ limit 5
27455
+ `);
27456
+ for (const row of sampleResult.rows) {
27457
+ kcacheSampleQueries.push({
27458
+ queryid: row.queryid,
27459
+ user: row.user,
27460
+ exec_total_time: parseFloat(row.exec_total_time)
27461
+ });
27462
+ }
27463
+ }
27464
+ } catch {}
27465
+ report.results[nodeName] = {
27466
+ data: {
27467
+ settings: pgssSettings,
27468
+ pg_stat_statements_status: {
27469
+ extension_available: pgssAvailable,
27470
+ metrics_count: pgssMetricsCount,
27471
+ total_calls: pgssTotalCalls,
27472
+ sample_queries: pgssSampleQueries
27473
+ },
27474
+ pg_stat_kcache_status: {
27475
+ extension_available: kcacheAvailable,
27476
+ metrics_count: kcacheMetricsCount,
27477
+ total_exec_time: kcacheTotalExecTime,
27478
+ total_user_time: kcacheTotalUserTime,
27479
+ total_system_time: kcacheTotalSystemTime,
27480
+ sample_queries: kcacheSampleQueries
27481
+ }
27482
+ },
27483
+ postgres_version: postgresVersion
27484
+ };
27485
+ return report;
27486
+ }
27487
+ async function generateF001(client, nodeName) {
27488
+ const report = createBaseReport("F001", "Autovacuum: current settings", nodeName);
27489
+ const postgresVersion = await getPostgresVersion(client);
27490
+ const allSettings = await getSettings(client);
27491
+ const autovacuumSettings = {};
27492
+ for (const [name, setting] of Object.entries(allSettings)) {
27493
+ if (name.includes("autovacuum") || name.includes("vacuum")) {
27494
+ autovacuumSettings[name] = setting;
27495
+ }
27496
+ }
27497
+ report.results[nodeName] = {
27498
+ data: autovacuumSettings,
27499
+ postgres_version: postgresVersion
27500
+ };
27501
+ return report;
27502
+ }
27503
+ async function generateG001(client, nodeName) {
27504
+ const report = createBaseReport("G001", "Memory-related settings", nodeName);
27505
+ const postgresVersion = await getPostgresVersion(client);
27506
+ const allSettings = await getSettings(client);
27507
+ const memorySettingNames = [
27508
+ "shared_buffers",
27509
+ "work_mem",
27510
+ "maintenance_work_mem",
27511
+ "effective_cache_size",
27512
+ "wal_buffers",
27513
+ "temp_buffers",
27514
+ "max_connections",
27515
+ "autovacuum_work_mem",
27516
+ "hash_mem_multiplier",
27517
+ "logical_decoding_work_mem",
27518
+ "max_stack_depth",
27519
+ "max_prepared_transactions",
27520
+ "max_locks_per_transaction",
27521
+ "max_pred_locks_per_transaction"
27522
+ ];
27523
+ const memorySettings = {};
27524
+ for (const name of memorySettingNames) {
27525
+ if (allSettings[name]) {
27526
+ memorySettings[name] = allSettings[name];
27527
+ }
27528
+ }
27529
+ let memoryUsage = {};
27530
+ try {
27531
+ const memQuery = await client.query(`
27532
+ select
27533
+ pg_size_bytes(current_setting('shared_buffers')) as shared_buffers_bytes,
27534
+ pg_size_bytes(current_setting('wal_buffers')) as wal_buffers_bytes,
27535
+ pg_size_bytes(current_setting('work_mem')) as work_mem_bytes,
27536
+ pg_size_bytes(current_setting('maintenance_work_mem')) as maintenance_work_mem_bytes,
27537
+ pg_size_bytes(current_setting('effective_cache_size')) as effective_cache_size_bytes,
27538
+ current_setting('max_connections')::int as max_connections
27539
+ `);
27540
+ if (memQuery.rows.length > 0) {
27541
+ const row = memQuery.rows[0];
27542
+ const sharedBuffersBytes = parseInt(row.shared_buffers_bytes, 10);
27543
+ const walBuffersBytes = parseInt(row.wal_buffers_bytes, 10);
27544
+ const workMemBytes = parseInt(row.work_mem_bytes, 10);
27545
+ const maintenanceWorkMemBytes = parseInt(row.maintenance_work_mem_bytes, 10);
27546
+ const effectiveCacheSizeBytes = parseInt(row.effective_cache_size_bytes, 10);
27547
+ const maxConnections = row.max_connections;
27548
+ const sharedMemoryTotal = sharedBuffersBytes + walBuffersBytes;
27549
+ const maxWorkMemUsage = workMemBytes * maxConnections;
27550
+ memoryUsage = {
27551
+ shared_buffers_bytes: sharedBuffersBytes,
27552
+ shared_buffers_pretty: formatBytes(sharedBuffersBytes),
27553
+ wal_buffers_bytes: walBuffersBytes,
27554
+ wal_buffers_pretty: formatBytes(walBuffersBytes),
27555
+ shared_memory_total_bytes: sharedMemoryTotal,
27556
+ shared_memory_total_pretty: formatBytes(sharedMemoryTotal),
27557
+ work_mem_per_connection_bytes: workMemBytes,
27558
+ work_mem_per_connection_pretty: formatBytes(workMemBytes),
27559
+ max_work_mem_usage_bytes: maxWorkMemUsage,
27560
+ max_work_mem_usage_pretty: formatBytes(maxWorkMemUsage),
27561
+ maintenance_work_mem_bytes: maintenanceWorkMemBytes,
27562
+ maintenance_work_mem_pretty: formatBytes(maintenanceWorkMemBytes),
27563
+ effective_cache_size_bytes: effectiveCacheSizeBytes,
27564
+ effective_cache_size_pretty: formatBytes(effectiveCacheSizeBytes)
27565
+ };
27566
+ }
27567
+ } catch {}
27568
+ report.results[nodeName] = {
27569
+ data: {
27570
+ settings: memorySettings,
27571
+ analysis: {
27572
+ estimated_total_memory_usage: memoryUsage
27573
+ }
27574
+ },
27575
+ postgres_version: postgresVersion
27576
+ };
27577
+ return report;
27578
+ }
27378
27579
  var REPORT_GENERATORS = {
27379
27580
  A002: generateA002,
27380
27581
  A003: generateA003,
27381
27582
  A004: generateA004,
27382
27583
  A007: generateA007,
27383
27584
  A013: generateA013,
27585
+ D004: generateD004,
27586
+ F001: generateF001,
27587
+ G001: generateG001,
27384
27588
  H001: generateH001,
27385
27589
  H002: generateH002,
27386
27590
  H004: generateH004
@@ -27391,6 +27595,9 @@ var CHECK_INFO = {
27391
27595
  A004: "Cluster information",
27392
27596
  A007: "Altered settings",
27393
27597
  A013: "Postgres minor version",
27598
+ D004: "pg_stat_statements and pg_stat_kcache settings",
27599
+ F001: "Autovacuum: current settings",
27600
+ G001: "Memory-related settings",
27394
27601
  H001: "Invalid indexes",
27395
27602
  H002: "Unused indexes",
27396
27603
  H004: "Redundant indexes"
package/lib/checkup.ts CHANGED
@@ -780,6 +780,276 @@ export async function generateH004(client: Client, nodeName: string = "node-01")
780
780
  return report;
781
781
  }
782
782
 
783
+ /**
784
+ * Generate D004 report - pg_stat_statements and pg_stat_kcache settings
785
+ */
786
+ async function generateD004(client: Client, nodeName: string): Promise<Report> {
787
+ const report = createBaseReport("D004", "pg_stat_statements and pg_stat_kcache settings", nodeName);
788
+ const postgresVersion = await getPostgresVersion(client);
789
+ const allSettings = await getSettings(client);
790
+
791
+ // Filter settings related to pg_stat_statements and pg_stat_kcache
792
+ const pgssSettings: Record<string, SettingInfo> = {};
793
+ for (const [name, setting] of Object.entries(allSettings)) {
794
+ if (name.startsWith("pg_stat_statements") || name.startsWith("pg_stat_kcache")) {
795
+ pgssSettings[name] = setting;
796
+ }
797
+ }
798
+
799
+ // Check pg_stat_statements extension
800
+ let pgssAvailable = false;
801
+ let pgssMetricsCount = 0;
802
+ let pgssTotalCalls = 0;
803
+ const pgssSampleQueries: Array<{ queryid: string; user: string; database: string; calls: number }> = [];
804
+
805
+ try {
806
+ const extCheck = await client.query(
807
+ "select 1 from pg_extension where extname = 'pg_stat_statements'"
808
+ );
809
+ if (extCheck.rows.length > 0) {
810
+ pgssAvailable = true;
811
+ const statsResult = await client.query(`
812
+ select count(*) as cnt, coalesce(sum(calls), 0) as total_calls
813
+ from pg_stat_statements
814
+ `);
815
+ pgssMetricsCount = parseInt(statsResult.rows[0]?.cnt || "0", 10);
816
+ pgssTotalCalls = parseInt(statsResult.rows[0]?.total_calls || "0", 10);
817
+
818
+ // Get sample queries (top 5 by calls)
819
+ const sampleResult = await client.query(`
820
+ select
821
+ queryid::text as queryid,
822
+ coalesce(usename, 'unknown') as "user",
823
+ coalesce(datname, 'unknown') as database,
824
+ calls
825
+ from pg_stat_statements s
826
+ left join pg_database d on s.dbid = d.oid
827
+ left join pg_user u on s.userid = u.usesysid
828
+ order by calls desc
829
+ limit 5
830
+ `);
831
+ for (const row of sampleResult.rows) {
832
+ pgssSampleQueries.push({
833
+ queryid: row.queryid,
834
+ user: row.user,
835
+ database: row.database,
836
+ calls: parseInt(row.calls, 10),
837
+ });
838
+ }
839
+ }
840
+ } catch {
841
+ // Extension not available or accessible
842
+ }
843
+
844
+ // Check pg_stat_kcache extension
845
+ let kcacheAvailable = false;
846
+ let kcacheMetricsCount = 0;
847
+ let kcacheTotalExecTime = 0;
848
+ let kcacheTotalUserTime = 0;
849
+ let kcacheTotalSystemTime = 0;
850
+ const kcacheSampleQueries: Array<{ queryid: string; user: string; exec_total_time: number }> = [];
851
+
852
+ try {
853
+ const extCheck = await client.query(
854
+ "select 1 from pg_extension where extname = 'pg_stat_kcache'"
855
+ );
856
+ if (extCheck.rows.length > 0) {
857
+ kcacheAvailable = true;
858
+ const statsResult = await client.query(`
859
+ select
860
+ count(*) as cnt,
861
+ coalesce(sum(exec_user_time + exec_system_time), 0) as total_exec_time,
862
+ coalesce(sum(exec_user_time), 0) as total_user_time,
863
+ coalesce(sum(exec_system_time), 0) as total_system_time
864
+ from pg_stat_kcache
865
+ `);
866
+ kcacheMetricsCount = parseInt(statsResult.rows[0]?.cnt || "0", 10);
867
+ kcacheTotalExecTime = parseFloat(statsResult.rows[0]?.total_exec_time || "0");
868
+ kcacheTotalUserTime = parseFloat(statsResult.rows[0]?.total_user_time || "0");
869
+ kcacheTotalSystemTime = parseFloat(statsResult.rows[0]?.total_system_time || "0");
870
+
871
+ // Get sample queries (top 5 by exec time)
872
+ const sampleResult = await client.query(`
873
+ select
874
+ queryid::text as queryid,
875
+ coalesce(usename, 'unknown') as "user",
876
+ (exec_user_time + exec_system_time) as exec_total_time
877
+ from pg_stat_kcache k
878
+ left join pg_user u on k.userid = u.usesysid
879
+ order by (exec_user_time + exec_system_time) desc
880
+ limit 5
881
+ `);
882
+ for (const row of sampleResult.rows) {
883
+ kcacheSampleQueries.push({
884
+ queryid: row.queryid,
885
+ user: row.user,
886
+ exec_total_time: parseFloat(row.exec_total_time),
887
+ });
888
+ }
889
+ }
890
+ } catch {
891
+ // Extension not available or accessible
892
+ }
893
+
894
+ report.results[nodeName] = {
895
+ data: {
896
+ settings: pgssSettings,
897
+ pg_stat_statements_status: {
898
+ extension_available: pgssAvailable,
899
+ metrics_count: pgssMetricsCount,
900
+ total_calls: pgssTotalCalls,
901
+ sample_queries: pgssSampleQueries,
902
+ },
903
+ pg_stat_kcache_status: {
904
+ extension_available: kcacheAvailable,
905
+ metrics_count: kcacheMetricsCount,
906
+ total_exec_time: kcacheTotalExecTime,
907
+ total_user_time: kcacheTotalUserTime,
908
+ total_system_time: kcacheTotalSystemTime,
909
+ sample_queries: kcacheSampleQueries,
910
+ },
911
+ },
912
+ postgres_version: postgresVersion,
913
+ };
914
+
915
+ return report;
916
+ }
917
+
918
+ /**
919
+ * Generate F001 report - Autovacuum: current settings
920
+ */
921
+ async function generateF001(client: Client, nodeName: string): Promise<Report> {
922
+ const report = createBaseReport("F001", "Autovacuum: current settings", nodeName);
923
+ const postgresVersion = await getPostgresVersion(client);
924
+ const allSettings = await getSettings(client);
925
+
926
+ // Filter autovacuum-related settings
927
+ const autovacuumSettings: Record<string, SettingInfo> = {};
928
+ for (const [name, setting] of Object.entries(allSettings)) {
929
+ if (name.includes("autovacuum") || name.includes("vacuum")) {
930
+ autovacuumSettings[name] = setting;
931
+ }
932
+ }
933
+
934
+ report.results[nodeName] = {
935
+ data: autovacuumSettings,
936
+ postgres_version: postgresVersion,
937
+ };
938
+
939
+ return report;
940
+ }
941
+
942
+ /**
943
+ * Generate G001 report - Memory-related settings
944
+ */
945
+ async function generateG001(client: Client, nodeName: string): Promise<Report> {
946
+ const report = createBaseReport("G001", "Memory-related settings", nodeName);
947
+ const postgresVersion = await getPostgresVersion(client);
948
+ const allSettings = await getSettings(client);
949
+
950
+ // Memory-related setting names
951
+ const memorySettingNames = [
952
+ "shared_buffers",
953
+ "work_mem",
954
+ "maintenance_work_mem",
955
+ "effective_cache_size",
956
+ "wal_buffers",
957
+ "temp_buffers",
958
+ "max_connections",
959
+ "autovacuum_work_mem",
960
+ "hash_mem_multiplier",
961
+ "logical_decoding_work_mem",
962
+ "max_stack_depth",
963
+ "max_prepared_transactions",
964
+ "max_locks_per_transaction",
965
+ "max_pred_locks_per_transaction",
966
+ ];
967
+
968
+ const memorySettings: Record<string, SettingInfo> = {};
969
+ for (const name of memorySettingNames) {
970
+ if (allSettings[name]) {
971
+ memorySettings[name] = allSettings[name];
972
+ }
973
+ }
974
+
975
+ // Calculate memory usage estimates
976
+ interface MemoryUsage {
977
+ shared_buffers_bytes: number;
978
+ shared_buffers_pretty: string;
979
+ wal_buffers_bytes: number;
980
+ wal_buffers_pretty: string;
981
+ shared_memory_total_bytes: number;
982
+ shared_memory_total_pretty: string;
983
+ work_mem_per_connection_bytes: number;
984
+ work_mem_per_connection_pretty: string;
985
+ max_work_mem_usage_bytes: number;
986
+ max_work_mem_usage_pretty: string;
987
+ maintenance_work_mem_bytes: number;
988
+ maintenance_work_mem_pretty: string;
989
+ effective_cache_size_bytes: number;
990
+ effective_cache_size_pretty: string;
991
+ }
992
+
993
+ let memoryUsage: MemoryUsage | Record<string, never> = {};
994
+
995
+ try {
996
+ // Get actual byte values from PostgreSQL
997
+ const memQuery = await client.query(`
998
+ select
999
+ pg_size_bytes(current_setting('shared_buffers')) as shared_buffers_bytes,
1000
+ pg_size_bytes(current_setting('wal_buffers')) as wal_buffers_bytes,
1001
+ pg_size_bytes(current_setting('work_mem')) as work_mem_bytes,
1002
+ pg_size_bytes(current_setting('maintenance_work_mem')) as maintenance_work_mem_bytes,
1003
+ pg_size_bytes(current_setting('effective_cache_size')) as effective_cache_size_bytes,
1004
+ current_setting('max_connections')::int as max_connections
1005
+ `);
1006
+
1007
+ if (memQuery.rows.length > 0) {
1008
+ const row = memQuery.rows[0];
1009
+ const sharedBuffersBytes = parseInt(row.shared_buffers_bytes, 10);
1010
+ const walBuffersBytes = parseInt(row.wal_buffers_bytes, 10);
1011
+ const workMemBytes = parseInt(row.work_mem_bytes, 10);
1012
+ const maintenanceWorkMemBytes = parseInt(row.maintenance_work_mem_bytes, 10);
1013
+ const effectiveCacheSizeBytes = parseInt(row.effective_cache_size_bytes, 10);
1014
+ const maxConnections = row.max_connections;
1015
+
1016
+ const sharedMemoryTotal = sharedBuffersBytes + walBuffersBytes;
1017
+ const maxWorkMemUsage = workMemBytes * maxConnections;
1018
+
1019
+ memoryUsage = {
1020
+ shared_buffers_bytes: sharedBuffersBytes,
1021
+ shared_buffers_pretty: formatBytes(sharedBuffersBytes),
1022
+ wal_buffers_bytes: walBuffersBytes,
1023
+ wal_buffers_pretty: formatBytes(walBuffersBytes),
1024
+ shared_memory_total_bytes: sharedMemoryTotal,
1025
+ shared_memory_total_pretty: formatBytes(sharedMemoryTotal),
1026
+ work_mem_per_connection_bytes: workMemBytes,
1027
+ work_mem_per_connection_pretty: formatBytes(workMemBytes),
1028
+ max_work_mem_usage_bytes: maxWorkMemUsage,
1029
+ max_work_mem_usage_pretty: formatBytes(maxWorkMemUsage),
1030
+ maintenance_work_mem_bytes: maintenanceWorkMemBytes,
1031
+ maintenance_work_mem_pretty: formatBytes(maintenanceWorkMemBytes),
1032
+ effective_cache_size_bytes: effectiveCacheSizeBytes,
1033
+ effective_cache_size_pretty: formatBytes(effectiveCacheSizeBytes),
1034
+ };
1035
+ }
1036
+ } catch {
1037
+ // If we can't calculate, leave empty object (schema allows this)
1038
+ }
1039
+
1040
+ report.results[nodeName] = {
1041
+ data: {
1042
+ settings: memorySettings,
1043
+ analysis: {
1044
+ estimated_total_memory_usage: memoryUsage,
1045
+ },
1046
+ },
1047
+ postgres_version: postgresVersion,
1048
+ };
1049
+
1050
+ return report;
1051
+ }
1052
+
783
1053
  /**
784
1054
  * Available report generators
785
1055
  */
@@ -789,6 +1059,9 @@ export const REPORT_GENERATORS: Record<string, (client: Client, nodeName: string
789
1059
  A004: generateA004,
790
1060
  A007: generateA007,
791
1061
  A013: generateA013,
1062
+ D004: generateD004,
1063
+ F001: generateF001,
1064
+ G001: generateG001,
792
1065
  H001: generateH001,
793
1066
  H002: generateH002,
794
1067
  H004: generateH004,
@@ -803,6 +1076,9 @@ export const CHECK_INFO: Record<string, string> = {
803
1076
  A004: "Cluster information",
804
1077
  A007: "Altered settings",
805
1078
  A013: "Postgres minor version",
1079
+ D004: "pg_stat_statements and pg_stat_kcache settings",
1080
+ F001: "Autovacuum: current settings",
1081
+ G001: "Memory-related settings",
806
1082
  H001: "Invalid indexes",
807
1083
  H002: "Unused indexes",
808
1084
  H004: "Redundant indexes",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "postgresai",
3
- "version": "0.14.0-dev.45",
3
+ "version": "0.14.0-dev.46",
4
4
  "description": "postgres_ai CLI",
5
5
  "license": "Apache-2.0",
6
6
  "private": false,
@@ -99,6 +99,25 @@ function createMockClient(versionRows: any[], settingsRows: any[], options: Mock
99
99
  if (sql.includes("redundant_indexes") && sql.includes("columns like")) {
100
100
  return { rows: redundantIndexesRows };
101
101
  }
102
+ // D004: pg_stat_statements extension check
103
+ if (sql.includes("pg_extension") && sql.includes("pg_stat_statements")) {
104
+ return { rows: [] }; // Extension not installed by default
105
+ }
106
+ // D004: pg_stat_kcache extension check
107
+ if (sql.includes("pg_extension") && sql.includes("pg_stat_kcache")) {
108
+ return { rows: [] }; // Extension not installed by default
109
+ }
110
+ // G001: Memory settings query
111
+ if (sql.includes("pg_size_bytes") && sql.includes("shared_buffers") && sql.includes("work_mem")) {
112
+ return { rows: [{
113
+ shared_buffers_bytes: "134217728",
114
+ wal_buffers_bytes: "4194304",
115
+ work_mem_bytes: "4194304",
116
+ maintenance_work_mem_bytes: "67108864",
117
+ effective_cache_size_bytes: "4294967296",
118
+ max_connections: 100,
119
+ }] };
120
+ }
102
121
  throw new Error(`Unexpected query: ${sql}`);
103
122
  },
104
123
  };
@@ -208,6 +227,21 @@ describe("CHECK_INFO", () => {
208
227
  expect("H004" in checkup.CHECK_INFO).toBe(true);
209
228
  expect(checkup.CHECK_INFO.H004).toBe("Redundant indexes");
210
229
  });
230
+
231
+ test("contains D004", () => {
232
+ expect("D004" in checkup.CHECK_INFO).toBe(true);
233
+ expect(checkup.CHECK_INFO.D004).toBe("pg_stat_statements and pg_stat_kcache settings");
234
+ });
235
+
236
+ test("contains F001", () => {
237
+ expect("F001" in checkup.CHECK_INFO).toBe(true);
238
+ expect(checkup.CHECK_INFO.F001).toBe("Autovacuum: current settings");
239
+ });
240
+
241
+ test("contains G001", () => {
242
+ expect("G001" in checkup.CHECK_INFO).toBe(true);
243
+ expect(checkup.CHECK_INFO.G001).toBe("Memory-related settings");
244
+ });
211
245
  });
212
246
 
213
247
  // Tests for REPORT_GENERATORS
@@ -252,6 +286,21 @@ describe("REPORT_GENERATORS", () => {
252
286
  expect(typeof checkup.REPORT_GENERATORS.H004).toBe("function");
253
287
  });
254
288
 
289
+ test("has generator for D004", () => {
290
+ expect("D004" in checkup.REPORT_GENERATORS).toBe(true);
291
+ expect(typeof checkup.REPORT_GENERATORS.D004).toBe("function");
292
+ });
293
+
294
+ test("has generator for F001", () => {
295
+ expect("F001" in checkup.REPORT_GENERATORS).toBe(true);
296
+ expect(typeof checkup.REPORT_GENERATORS.F001).toBe("function");
297
+ });
298
+
299
+ test("has generator for G001", () => {
300
+ expect("G001" in checkup.REPORT_GENERATORS).toBe(true);
301
+ expect(typeof checkup.REPORT_GENERATORS.G001).toBe("function");
302
+ });
303
+
255
304
  test("REPORT_GENERATORS and CHECK_INFO have same keys", () => {
256
305
  const generatorKeys = Object.keys(checkup.REPORT_GENERATORS).sort();
257
306
  const infoKeys = Object.keys(checkup.CHECK_INFO).sort();
@@ -28,6 +28,7 @@ function validateReport(report: any, checkId: string): { valid: boolean; errors:
28
28
  // Mock client for testing
29
29
  function createMockClient(options: {
30
30
  versionRows?: any[];
31
+ settingsRows?: any[];
31
32
  invalidIndexesRows?: any[];
32
33
  unusedIndexesRows?: any[];
33
34
  redundantIndexesRows?: any[];
@@ -37,6 +38,12 @@ function createMockClient(options: {
37
38
  { name: "server_version", setting: "16.3" },
38
39
  { name: "server_version_num", setting: "160003" },
39
40
  ],
41
+ settingsRows = [
42
+ { name: "shared_buffers", setting: "128MB", unit: "", category: "Resource Usage / Memory", context: "postmaster", vartype: "string", pretty_value: "128 MB" },
43
+ { name: "work_mem", setting: "4MB", unit: "", category: "Resource Usage / Memory", context: "user", vartype: "string", pretty_value: "4 MB" },
44
+ { name: "autovacuum", setting: "on", unit: "", category: "Autovacuum", context: "sighup", vartype: "bool", pretty_value: "on" },
45
+ { name: "pg_stat_statements.max", setting: "5000", unit: "", category: "Custom", context: "superuser", vartype: "integer", pretty_value: "5000" },
46
+ ],
40
47
  invalidIndexesRows = [],
41
48
  unusedIndexesRows = [],
42
49
  redundantIndexesRows = [],
@@ -47,6 +54,10 @@ function createMockClient(options: {
47
54
  if (sql.includes("server_version") && sql.includes("server_version_num") && !sql.includes("order by")) {
48
55
  return { rows: versionRows };
49
56
  }
57
+ // Full settings query
58
+ if (sql.includes("pg_settings") && sql.includes("order by") && sql.includes("is_default")) {
59
+ return { rows: settingsRows };
60
+ }
50
61
  if (sql.includes("current_database()") && sql.includes("pg_database_size")) {
51
62
  return { rows: [{ datname: "testdb", size_bytes: "1073741824" }] };
52
63
  }
@@ -68,6 +79,25 @@ function createMockClient(options: {
68
79
  if (sql.includes("redundant_indexes") && sql.includes("columns like")) {
69
80
  return { rows: redundantIndexesRows };
70
81
  }
82
+ // D004: pg_stat_statements extension check
83
+ if (sql.includes("pg_extension") && sql.includes("pg_stat_statements")) {
84
+ return { rows: [] }; // Extension not installed
85
+ }
86
+ // D004: pg_stat_kcache extension check
87
+ if (sql.includes("pg_extension") && sql.includes("pg_stat_kcache")) {
88
+ return { rows: [] }; // Extension not installed
89
+ }
90
+ // G001: Memory settings query
91
+ if (sql.includes("pg_size_bytes") && sql.includes("shared_buffers") && sql.includes("work_mem")) {
92
+ return { rows: [{
93
+ shared_buffers_bytes: "134217728",
94
+ wal_buffers_bytes: "4194304",
95
+ work_mem_bytes: "4194304",
96
+ maintenance_work_mem_bytes: "67108864",
97
+ effective_cache_size_bytes: "4294967296",
98
+ max_connections: 100,
99
+ }] };
100
+ }
71
101
  throw new Error(`Unexpected query: ${sql}`);
72
102
  },
73
103
  };
@@ -186,3 +216,42 @@ describe("H004 schema validation", () => {
186
216
  });
187
217
  });
188
218
 
219
+ describe("D004 schema validation", () => {
220
+ test("D004 report validates against schema (extensions not installed)", async () => {
221
+ const mockClient = createMockClient();
222
+ const report = await checkup.REPORT_GENERATORS.D004(mockClient as any, "node-01");
223
+
224
+ const result = validateReport(report, "D004");
225
+ if (!result.valid) {
226
+ console.error("D004 validation errors:", result.errors);
227
+ }
228
+ expect(result.valid).toBe(true);
229
+ });
230
+ });
231
+
232
+ describe("F001 schema validation", () => {
233
+ test("F001 report validates against schema", async () => {
234
+ const mockClient = createMockClient();
235
+ const report = await checkup.REPORT_GENERATORS.F001(mockClient as any, "node-01");
236
+
237
+ const result = validateReport(report, "F001");
238
+ if (!result.valid) {
239
+ console.error("F001 validation errors:", result.errors);
240
+ }
241
+ expect(result.valid).toBe(true);
242
+ });
243
+ });
244
+
245
+ describe("G001 schema validation", () => {
246
+ test("G001 report validates against schema", async () => {
247
+ const mockClient = createMockClient();
248
+ const report = await checkup.REPORT_GENERATORS.G001(mockClient as any, "node-01");
249
+
250
+ const result = validateReport(report, "G001");
251
+ if (!result.valid) {
252
+ console.error("G001 validation errors:", result.errors);
253
+ }
254
+ expect(result.valid).toBe(true);
255
+ });
256
+ });
257
+