@@ -80,6 +80,55 @@ public function execute() {
8080
8181 $ io ->boldGreen ("Successfully purged {$ rowCount } old records from apm_requests table " , true );
8282
83+ // Clean up any orphaned child records in case foreign keys were disabled in the past
84+ $ childTables = [
85+ 'apm_routes ' ,
86+ 'apm_middleware ' ,
87+ 'apm_views ' ,
88+ 'apm_db_connections ' ,
89+ 'apm_db_queries ' ,
90+ 'apm_errors ' ,
91+ 'apm_cache ' ,
92+ 'apm_custom_events ' ,
93+ 'apm_custom_event_data ' ,
94+ 'apm_raw_metrics '
95+ ];
96+
97+ $ orphanedCount = 0 ;
98+ foreach ($ childTables as $ table ) {
99+ try {
100+ $ io ->info ("Scanning {$ table } for orphaned records... " , true );
101+ $ orphansDeleted = $ db ->exec ("DELETE FROM {$ table } WHERE NOT EXISTS (SELECT 1 FROM apm_requests WHERE id = {$ table }.request_id) " );
102+ if ($ orphansDeleted > 0 ) {
103+ $ orphanedCount += (int )$ orphansDeleted ;
104+ $ io ->boldGreen ("Purged {$ orphansDeleted } orphaned records from {$ table }" , true );
105+ }
106+ } catch (PDOException $ e ) {
107+ // Table might not exist in an older schema version, continue
108+ }
109+ }
110+
111+ if ($ orphanedCount > 0 ) {
112+ $ io ->boldGreen ("Successfully cleaned up a total of {$ orphanedCount } orphaned child records " , true );
113+ }
114+
115+ // Clean up JSON buffer tables if they exist using JSON extraction
116+ try {
117+ $ cutoffUnix = strtotime ("- {$ daysToKeep } days " );
118+ $ jsonExtractFunc = $ storageType === 'mysql ' ? 'JSON_EXTRACT ' : 'json_extract ' ;
119+
120+ $ stmt = $ db ->prepare ("DELETE FROM apm_metrics_log WHERE {$ jsonExtractFunc }(metrics_json, '$.start_time') < :cutoff_unix " );
121+ $ stmt ->bindParam (':cutoff_unix ' , $ cutoffUnix );
122+ if ($ stmt ->execute ()) {
123+ $ logCount = $ stmt ->rowCount ();
124+ if ($ logCount > 0 ) {
125+ $ io ->info ("Purged {$ logCount } old records from apm_metrics_log table by JSON timestamp " , true );
126+ }
127+ }
128+ } catch (PDOException $ e ) {
129+ // Table might not exist, continue smoothly
130+ }
131+
83132 // If SQLite, vacuum the database to reclaim space
84133 if ($ storageType === 'sqlite ' ) {
85134 $ db ->exec ('VACUUM ' );
@@ -105,11 +154,14 @@ protected function getDatabaseConnection(array $config): PDO {
105154 switch ($ storageType ) {
106155 case 'sqlite ' :
107156 $ dsn = $ config ['dest_db_dsn ' ];
108- return new PDO ($ dsn , null , null , [
157+ $ pdo = new PDO ($ dsn , null , null , [
109158 PDO ::ATTR_ERRMODE => PDO ::ERRMODE_EXCEPTION ,
110159 PDO ::ATTR_DEFAULT_FETCH_MODE => PDO ::FETCH_ASSOC ,
111160 PDO ::ATTR_EMULATE_PREPARES => false ,
112161 ]);
162+ // Enable foreign keys explicitly so CASCADE works correctly
163+ $ pdo ->exec ('PRAGMA foreign_keys = ON; ' );
164+ return $ pdo ;
113165
114166 case 'mysql ' :
115167 $ dsn = $ config ['dest_db_dsn ' ];
0 commit comments