2525#include "tail_sql.h"
2626#include "tail_file.h"
2727
28+ #include <inttypes.h>
29+
2830struct query_status {
2931 int id ;
3032 int rows ;
@@ -271,6 +273,132 @@ static int stmt_add_param_concat(struct flb_tail_config *ctx,
271273 return 0 ;
272274}
273275
276+ /*
277+ * Scalable stale inode cleanup: use a temp table to avoid SQLite variable limits.
278+ *
279+ * The legacy implementation builds:
280+ * DELETE ... WHERE inode NOT IN (?,?,?,...);
281+ * which requires one bound parameter per inode and fails when the number of
282+ * monitored files exceeds SQLITE_LIMIT_VARIABLE_NUMBER (commonly 32766 in our
283+ * bundled SQLite, but can vary).
284+ */
285+ static int flb_tail_db_stale_file_delete_temp_table (struct flb_tail_config * ctx ,
286+ uint64_t file_count )
287+ {
288+ int ret ;
289+ int changes ;
290+ int txn_started = FLB_FALSE ;
291+ sqlite3_stmt * stmt_insert_inode = NULL ;
292+ struct mk_list * head ;
293+ struct mk_list * tmp ;
294+ struct flb_tail_file * file ;
295+
296+ /* If there are no monitored files, delete everything from the DB table. */
297+ if (file_count == 0 ) {
298+ ret = flb_sqldb_query (ctx -> db , "DELETE FROM in_tail_files;" , NULL , NULL );
299+ if (ret != FLB_OK ) {
300+ flb_plg_error (ctx -> ins , "db: cannot delete all stale inodes (no monitored files)" );
301+ return -1 ;
302+ }
303+
304+ changes = sqlite3_changes (ctx -> db -> handler );
305+ flb_plg_info (ctx -> ins , "db: delete unmonitored stale inodes from the database: count=%d" ,
306+ changes );
307+ return 0 ;
308+ }
309+
310+ /* Create/clear temp table holding current monitored inodes. */
311+ ret = flb_sqldb_query (ctx -> db ,
312+ "CREATE TEMP TABLE IF NOT EXISTS in_tail_current_inodes ("
313+ " inode INTEGER PRIMARY KEY"
314+ ");" ,
315+ NULL , NULL );
316+ if (ret != FLB_OK ) {
317+ flb_plg_error (ctx -> ins , "db: cannot create temp table for inode cleanup" );
318+ return -1 ;
319+ }
320+
321+ ret = flb_sqldb_query (ctx -> db , "DELETE FROM in_tail_current_inodes;" , NULL , NULL );
322+ if (ret != FLB_OK ) {
323+ flb_plg_error (ctx -> ins , "db: cannot clear temp inode table" );
324+ return -1 ;
325+ }
326+
327+ /* Use a transaction for faster bulk inserts. */
328+ ret = flb_sqldb_query (ctx -> db , "BEGIN;" , NULL , NULL );
329+ if (ret != FLB_OK ) {
330+ flb_plg_error (ctx -> ins , "db: cannot begin transaction for temp inode inserts" );
331+ return -1 ;
332+ }
333+ txn_started = FLB_TRUE ;
334+
335+ ret = sqlite3_prepare_v2 (ctx -> db -> handler ,
336+ "INSERT OR IGNORE INTO in_tail_current_inodes(inode) VALUES (?);" ,
337+ -1 , & stmt_insert_inode , 0 );
338+ if (ret != SQLITE_OK ) {
339+ flb_plg_error (ctx -> ins , "db: cannot prepare temp inode insert statement, ret=%d" , ret );
340+ goto error ;
341+ }
342+
343+ mk_list_foreach_safe (head , tmp , & ctx -> files_static ) {
344+ file = mk_list_entry (head , struct flb_tail_file , _head );
345+
346+ ret = sqlite3_bind_int64 (stmt_insert_inode , 1 , (sqlite3_int64 ) file -> inode );
347+ if (ret != SQLITE_OK ) {
348+ flb_plg_error (ctx -> ins , "db: error binding temp inode insert: inode=%" PRIu64 ", ret=%d" ,
349+ file -> inode , ret );
350+ goto error ;
351+ }
352+
353+ ret = sqlite3_step (stmt_insert_inode );
354+ if (ret != SQLITE_DONE ) {
355+ flb_plg_error (ctx -> ins , "db: error inserting inode into temp table: inode=%" PRIu64 ", ret=%d" ,
356+ file -> inode , ret );
357+ goto error ;
358+ }
359+
360+ sqlite3_clear_bindings (stmt_insert_inode );
361+ sqlite3_reset (stmt_insert_inode );
362+ }
363+
364+ sqlite3_finalize (stmt_insert_inode );
365+ stmt_insert_inode = NULL ;
366+
367+ /* Delete any inode that is not in the current monitored set. */
368+ ret = flb_sqldb_query (ctx -> db ,
369+ "DELETE FROM in_tail_files "
370+ "WHERE inode NOT IN (SELECT inode FROM in_tail_current_inodes);" ,
371+ NULL , NULL );
372+ if (ret != FLB_OK ) {
373+ flb_plg_error (ctx -> ins , "db: cannot delete stale inodes using temp table" );
374+ goto error ;
375+ }
376+
377+ ret = flb_sqldb_query (ctx -> db , "COMMIT;" , NULL , NULL );
378+ if (ret != FLB_OK ) {
379+ flb_plg_error (ctx -> ins , "db: cannot commit transaction for temp inode inserts" );
380+ goto error ;
381+ }
382+ txn_started = FLB_FALSE ;
383+
384+ changes = sqlite3_changes (ctx -> db -> handler );
385+ flb_plg_info (ctx -> ins , "db: delete unmonitored stale inodes from the database: count=%d" ,
386+ changes );
387+ return 0 ;
388+
389+ error :
390+ if (stmt_insert_inode ) {
391+ sqlite3_finalize (stmt_insert_inode );
392+ }
393+
394+ if (txn_started == FLB_TRUE ) {
395+ /* Best-effort rollback */
396+ flb_sqldb_query (ctx -> db , "ROLLBACK;" , NULL , NULL );
397+ }
398+
399+ return -1 ;
400+ }
401+
274402int flb_tail_db_file_set (struct flb_tail_file * file ,
275403 struct flb_tail_config * ctx )
276404{
@@ -430,6 +558,7 @@ int flb_tail_db_stale_file_delete(struct flb_input_instance *ins,
430558 size_t sql_size ;
431559 uint64_t idx ;
432560 uint64_t file_count = ctx -> files_static_count ;
561+ int max_vars = -1 ;
433562 flb_sds_t stale_delete_sql ;
434563 flb_sds_t sds_tmp ;
435564 sqlite3_stmt * stmt_delete_inodes = NULL ;
@@ -441,6 +570,23 @@ int flb_tail_db_stale_file_delete(struct flb_input_instance *ins,
441570 return 0 ;
442571 }
443572
573+ /*
574+ * Avoid SQLite variable limits for large monitored file sets.
575+ *
576+ * sqlite3_limit(..., SQLITE_LIMIT_VARIABLE_NUMBER, -1) returns the current
577+ * runtime limit (compile-time hard limit may be higher). If our monitored
578+ * file count exceeds this, the legacy NOT IN (?,?,...) statement will fail
579+ * at prepare-time.
580+ */
581+ max_vars = sqlite3_limit (ctx -> db -> handler , SQLITE_LIMIT_VARIABLE_NUMBER , -1 );
582+ if (max_vars > 0 && file_count > (uint64_t ) max_vars ) {
583+ flb_plg_warn (ctx -> ins ,
584+ "db: large file set detected (%" PRIu64 " files) exceeds SQLite variable limit (%d); "
585+ "using temp-table cleanup for stale inode deletion" ,
586+ file_count , max_vars );
587+ return flb_tail_db_stale_file_delete_temp_table (ctx , file_count );
588+ }
589+
444590 ret = tail_db_lock (ctx );
445591 if (ret != 0 ) {
446592 flb_plg_error (ctx -> ins , "db: could not acquire lock" );
@@ -454,7 +600,13 @@ int flb_tail_db_stale_file_delete(struct flb_input_instance *ins,
454600 sql_size += SQL_STMT_PARAM_END_LEN ;
455601 sql_size += SQL_STMT_END_LEN ;
456602 if (file_count > 0 ) {
457- sql_size += (SQL_STMT_ADD_PARAM_LEN * file_count );
603+ /*
604+ * We already account for the first '?' via SQL_STMT_START_PARAM_LEN.
605+ * Additional parameters are count-1 occurrences of ",?".
606+ */
607+ if (file_count > 1 ) {
608+ sql_size += (SQL_STMT_ADD_PARAM_LEN * (file_count - 1 ));
609+ }
458610 }
459611
460612 stale_delete_sql = flb_sds_create_size (sql_size + 1 );
0 commit comments