@@ -242,6 +242,24 @@ export interface RedundantIndex {
242242 redundant_to_parse_error ?: string ;
243243}
244244
245+ /**
246+ * Sequence overflow risk entry (L003)
247+ * Monitors sequence-generated columns for potential integer overflow
248+ */
249+ export interface SequenceOverflowRisk {
250+ schema_name : string ;
251+ table_name : string ;
252+ column_name : string ;
253+ sequence_name : string ;
254+ sequence_data_type : string ;
255+ column_data_type : string ;
256+ current_value : number ;
257+ max_value : number ;
258+ sequence_percent_used : number ;
259+ column_percent_used : number ;
260+ capacity_used_pretty : string ;
261+ }
262+
245263/**
246264 * Node result for reports
247265 */
@@ -839,6 +857,49 @@ export async function getRedundantIndexes(client: Client, pgMajorVersion: number
839857 } ) ;
840858}
841859
860+ /**
861+ * Get sequence overflow risks from database (L003)
862+ * Monitors sequence-generated columns (serial/identity) for potential integer overflow
863+ * by checking how close the current sequence value is to the max value for the data type.
864+ *
865+ * @param client - Connected PostgreSQL client
866+ * @param pgMajorVersion - PostgreSQL major version (default: 16)
867+ * @returns Array of sequence overflow risk entries
868+ */
869+ export async function getSequenceOverflowRisks (
870+ client : Client ,
871+ pgMajorVersion : number = 16
872+ ) : Promise < SequenceOverflowRisk [ ] > {
873+ const sql = getMetricSql ( METRIC_NAMES . L003 , pgMajorVersion ) ;
874+ const result = await client . query ( sql ) ;
875+ return result . rows . map ( ( row ) => {
876+ const transformed = transformMetricRow ( row ) ;
877+ const currentValue = parseInt ( String ( transformed . current_value || 0 ) , 10 ) ;
878+ const sequencePercentUsed = parseFloat ( String ( transformed . sequence_percent_used || 0 ) ) ;
879+ const columnPercentUsed = parseFloat ( String ( transformed . column_percent_used || 0 ) ) ;
880+ const capacityPercent = Math . max ( sequencePercentUsed , columnPercentUsed ) ;
881+
882+ // Use the smaller of the two max values (column type is the constraint)
883+ const sequenceMaxValue = parseInt ( String ( transformed . sequence_max_value || 0 ) , 10 ) ;
884+ const columnMaxValue = parseInt ( String ( transformed . column_max_value || 0 ) , 10 ) ;
885+ const effectiveMaxValue = Math . min ( sequenceMaxValue , columnMaxValue ) || columnMaxValue || sequenceMaxValue ;
886+
887+ return {
888+ schema_name : String ( transformed . schema_name || "" ) ,
889+ table_name : String ( transformed . table_name || "" ) ,
890+ column_name : String ( transformed . column_name || "" ) ,
891+ sequence_name : String ( transformed . sequence_name || "" ) ,
892+ sequence_data_type : String ( transformed . sequence_data_type || "" ) ,
893+ column_data_type : String ( transformed . column_data_type || "" ) ,
894+ current_value : currentValue ,
895+ max_value : effectiveMaxValue ,
896+ sequence_percent_used : Math . round ( sequencePercentUsed * 100 ) / 100 ,
897+ column_percent_used : Math . round ( columnPercentUsed * 100 ) / 100 ,
898+ capacity_used_pretty : `${ capacityPercent . toFixed ( 2 ) } %` ,
899+ } ;
900+ } ) ;
901+ }
902+
842903/**
843904 * Create base report structure
844905 */
@@ -1326,6 +1387,49 @@ async function generateG001(client: Client, nodeName: string): Promise<Report> {
13261387 return report ;
13271388}
13281389
1390+ /**
1391+ * Generate L003 report - Integer out-of-range risks in PKs
1392+ * Monitors sequence-generated columns (serial/identity) for potential integer overflow.
1393+ */
1394+ export async function generateL003 ( client : Client , nodeName : string = "node-01" ) : Promise < Report > {
1395+ const report = createBaseReport ( "L003" , "Integer out-of-range risks in PKs" , nodeName ) ;
1396+ const postgresVersion = await getPostgresVersion ( client ) ;
1397+ const pgMajorVersion = parseInt ( postgresVersion . server_major_ver , 10 ) || 16 ;
1398+ const overflowRisks = await getSequenceOverflowRisks ( client , pgMajorVersion ) ;
1399+ const { datname : dbName } = await getCurrentDatabaseInfo ( client , pgMajorVersion ) ;
1400+
1401+ // Default thresholds matching schema
1402+ const warningThreshold = 50 ;
1403+ const criticalThreshold = 75 ;
1404+
1405+ // Count high-risk items (above warning threshold)
1406+ const highRiskCount = overflowRisks . filter (
1407+ ( risk ) => Math . max ( risk . sequence_percent_used , risk . column_percent_used ) >= warningThreshold
1408+ ) . length ;
1409+
1410+ // Sort by highest capacity usage (descending)
1411+ overflowRisks . sort (
1412+ ( a , b ) =>
1413+ Math . max ( b . sequence_percent_used , b . column_percent_used ) -
1414+ Math . max ( a . sequence_percent_used , a . column_percent_used )
1415+ ) ;
1416+
1417+ report . results [ nodeName ] = {
1418+ data : {
1419+ [ dbName ] : {
1420+ overflow_risks : overflowRisks ,
1421+ total_count : overflowRisks . length ,
1422+ high_risk_count : highRiskCount ,
1423+ warning_threshold_pct : warningThreshold ,
1424+ critical_threshold_pct : criticalThreshold ,
1425+ } ,
1426+ } ,
1427+ postgres_version : postgresVersion ,
1428+ } ;
1429+
1430+ return report ;
1431+ }
1432+
13291433/**
13301434 * Available report generators
13311435 */
@@ -1341,6 +1445,7 @@ export const REPORT_GENERATORS: Record<string, (client: Client, nodeName: string
13411445 H001 : generateH001 ,
13421446 H002 : generateH002 ,
13431447 H004 : generateH004 ,
1448+ L003 : generateL003 ,
13441449} ;
13451450
13461451/**
@@ -1358,6 +1463,7 @@ export const CHECK_INFO: Record<string, string> = {
13581463 H001 : "Invalid indexes" ,
13591464 H002 : "Unused indexes" ,
13601465 H004 : "Redundant indexes" ,
1466+ L003 : "Integer out-of-range risks in PKs" ,
13611467} ;
13621468
13631469/**
0 commit comments