| 2854 | } |
| 2855 | |
| 2856 | int cbm_store_coverage_replace_ex(cbm_store_t *s, const char *project, |
| 2857 | const cbm_coverage_row_t *rows, int count, |
| 2858 | const cbm_coverage_meta_t *meta) { |
| 2859 | if (!s || !s->db || !project || count < 0 || (count > 0 && !rows)) { |
| 2860 | return CBM_STORE_ERR; |
| 2861 | } |
| 2862 | if (exec_sql(s, "BEGIN;") != CBM_STORE_OK) { |
| 2863 | return CBM_STORE_ERR; |
| 2864 | } |
| 2865 | sqlite3_stmt *del = NULL; |
| 2866 | if (sqlite3_prepare_v2(s->db, "DELETE FROM index_coverage WHERE project = ?1;", CBM_NOT_FOUND, |
| 2867 | &del, NULL) != SQLITE_OK) { |
| 2868 | store_set_error_sqlite(s, "coverage delete prepare"); |
| 2869 | (void)exec_sql(s, "ROLLBACK;"); |
| 2870 | return CBM_STORE_ERR; |
| 2871 | } |
| 2872 | bind_text(del, SKIP_ONE, project); |
| 2873 | int rc = sqlite3_step(del); |
| 2874 | sqlite3_finalize(del); |
| 2875 | if (rc != SQLITE_DONE) { |
| 2876 | store_set_error_sqlite(s, "coverage delete"); |
| 2877 | (void)exec_sql(s, "ROLLBACK;"); |
| 2878 | return CBM_STORE_ERR; |
| 2879 | } |
| 2880 | sqlite3_stmt *ins = NULL; |
| 2881 | if (sqlite3_prepare_v2( |
| 2882 | s->db, |
| 2883 | "INSERT OR REPLACE INTO index_coverage (project, rel_path, kind, detail) " |
| 2884 | "VALUES (?1, ?2, ?3, ?4);", |
| 2885 | CBM_NOT_FOUND, &ins, NULL) != SQLITE_OK) { |
| 2886 | store_set_error_sqlite(s, "coverage insert prepare"); |
| 2887 | (void)exec_sql(s, "ROLLBACK;"); |
| 2888 | return CBM_STORE_ERR; |
| 2889 | } |
| 2890 | for (int i = 0; i < count; i++) { |
| 2891 | if (!rows[i].rel_path || !rows[i].kind) { |
| 2892 | continue; |
| 2893 | } |
| 2894 | bind_text(ins, SKIP_ONE, project); |
| 2895 | bind_text(ins, ST_COL_2, rows[i].rel_path); |
| 2896 | bind_text(ins, ST_COL_3, rows[i].kind); |
| 2897 | bind_text(ins, CBM_SZ_4, rows[i].detail ? rows[i].detail : ""); |
| 2898 | if (sqlite3_step(ins) != SQLITE_DONE) { |
| 2899 | store_set_error_sqlite(s, "coverage insert"); |
| 2900 | sqlite3_finalize(ins); |
| 2901 | (void)exec_sql(s, "ROLLBACK;"); |
| 2902 | return CBM_STORE_ERR; |
| 2903 | } |
| 2904 | sqlite3_reset(ins); |
| 2905 | } |
| 2906 | sqlite3_finalize(ins); |
| 2907 | /* Prune FAILURE rows for files no longer known to the index (deleted from |
| 2908 | * the repo): file_hashes is the authoritative live-file set after |
| 2909 | * persist. By-design not_indexed_* rows are exempt — deliberately |
| 2910 | * unindexed paths never have hash rows (discovery rewrites them fresh |
| 2911 | * every run instead). */ |
| 2912 | sqlite3_stmt *prune = NULL; |
| 2913 | if (sqlite3_prepare_v2(s->db, |