| 2753 | } |
| 2754 | |
| 2755 | int cbm_store_coverage_replace_ex(cbm_store_t *s, const char *project, |
| 2756 | const cbm_coverage_row_t *rows, int count, |
| 2757 | const cbm_coverage_meta_t *meta) { |
| 2758 | if (!s || !s->db || !project || count < 0 || (count > 0 && !rows)) { |
| 2759 | return CBM_STORE_ERR; |
| 2760 | } |
| 2761 | if (exec_sql(s, "BEGIN;") != CBM_STORE_OK) { |
| 2762 | return CBM_STORE_ERR; |
| 2763 | } |
| 2764 | sqlite3_stmt *del = NULL; |
| 2765 | if (sqlite3_prepare_v2(s->db, "DELETE FROM index_coverage WHERE project = ?1;", CBM_NOT_FOUND, |
| 2766 | &del, NULL) != SQLITE_OK) { |
| 2767 | store_set_error_sqlite(s, "coverage delete prepare"); |
| 2768 | (void)exec_sql(s, "ROLLBACK;"); |
| 2769 | return CBM_STORE_ERR; |
| 2770 | } |
| 2771 | bind_text(del, SKIP_ONE, project); |
| 2772 | int rc = sqlite3_step(del); |
| 2773 | sqlite3_finalize(del); |
| 2774 | if (rc != SQLITE_DONE) { |
| 2775 | store_set_error_sqlite(s, "coverage delete"); |
| 2776 | (void)exec_sql(s, "ROLLBACK;"); |
| 2777 | return CBM_STORE_ERR; |
| 2778 | } |
| 2779 | sqlite3_stmt *ins = NULL; |
| 2780 | if (sqlite3_prepare_v2( |
| 2781 | s->db, |
| 2782 | "INSERT OR REPLACE INTO index_coverage (project, rel_path, kind, detail) " |
| 2783 | "VALUES (?1, ?2, ?3, ?4);", |
| 2784 | CBM_NOT_FOUND, &ins, NULL) != SQLITE_OK) { |
| 2785 | store_set_error_sqlite(s, "coverage insert prepare"); |
| 2786 | (void)exec_sql(s, "ROLLBACK;"); |
| 2787 | return CBM_STORE_ERR; |
| 2788 | } |
| 2789 | for (int i = 0; i < count; i++) { |
| 2790 | if (!rows[i].rel_path || !rows[i].kind) { |
| 2791 | continue; |
| 2792 | } |
| 2793 | bind_text(ins, SKIP_ONE, project); |
| 2794 | bind_text(ins, ST_COL_2, rows[i].rel_path); |
| 2795 | bind_text(ins, ST_COL_3, rows[i].kind); |
| 2796 | bind_text(ins, CBM_SZ_4, rows[i].detail ? rows[i].detail : ""); |
| 2797 | if (sqlite3_step(ins) != SQLITE_DONE) { |
| 2798 | store_set_error_sqlite(s, "coverage insert"); |
| 2799 | sqlite3_finalize(ins); |
| 2800 | (void)exec_sql(s, "ROLLBACK;"); |
| 2801 | return CBM_STORE_ERR; |
| 2802 | } |
| 2803 | sqlite3_reset(ins); |
| 2804 | } |
| 2805 | sqlite3_finalize(ins); |
| 2806 | /* Prune FAILURE rows for files no longer known to the index (deleted from |
| 2807 | * the repo): file_hashes is the authoritative live-file set after |
| 2808 | * persist. By-design not_indexed_* rows are exempt — deliberately |
| 2809 | * unindexed paths never have hash rows (discovery rewrites them fresh |
| 2810 | * every run instead). */ |
| 2811 | sqlite3_stmt *prune = NULL; |
| 2812 | if (sqlite3_prepare_v2(s->db, |