| 3860 | enum { DEPFILE_CHUNK = 200 }; |
| 3861 | |
| 3862 | static int dependent_files_chunk(cbm_store_t *s, const char *project, |
| 3863 | const char *const *target_files, int chunk_count, |
| 3864 | CBMHashTable *seen, char ***out, int *out_count, int *out_cap) { |
| 3865 | char sql[CBM_SZ_4K]; |
| 3866 | int pos = snprintf(sql, sizeof(sql), |
| 3867 | /* CROSS JOIN pins nodes-first (see the delta snapshot query: |
| 3868 | * the free planner walks every project edge instead). */ |
| 3869 | "SELECT DISTINCT src.file_path FROM nodes tgt" |
| 3870 | " CROSS JOIN edges e ON e.target_id = tgt.id" |
| 3871 | " CROSS JOIN nodes src ON e.source_id = src.id" |
| 3872 | " WHERE tgt.project = ?1 AND tgt.file_path IN ("); |
| 3873 | for (int i = 0; i < chunk_count; i++) { |
| 3874 | pos += snprintf(sql + pos, sizeof(sql) - (size_t)pos, "%s?%d", i ? "," : "", i + ST_COL_2); |
| 3875 | if ((size_t)pos >= sizeof(sql)) { |
| 3876 | return CBM_STORE_ERR; |
| 3877 | } |
| 3878 | } |
| 3879 | /* Structural containment is not resolution: a Folder/Project container |
| 3880 | * (whose file_path is a properties placeholder, not a real file) or a |
| 3881 | * CONTAINS_* edge says nothing about who consumed the target's |
| 3882 | * definitions, and the closure planner would otherwise decline on a |
| 3883 | * "dependent" no discovery can ever produce. */ |
| 3884 | pos += snprintf(sql + pos, sizeof(sql) - (size_t)pos, |
| 3885 | ") AND src.file_path <> '' AND src.file_path IS NOT NULL" |
| 3886 | " AND src.label NOT IN ('Folder','Project')" |
| 3887 | " AND e.type NOT IN ('CONTAINS_FILE','CONTAINS_FOLDER')"); |
| 3888 | if ((size_t)pos >= sizeof(sql)) { |
| 3889 | return CBM_STORE_ERR; |
| 3890 | } |
| 3891 | sqlite3_stmt *stmt = NULL; |
| 3892 | if (sqlite3_prepare_v2(s->db, sql, CBM_NOT_FOUND, &stmt, NULL) != SQLITE_OK) { |
| 3893 | store_set_error_sqlite(s, "dependent_files prepare"); |
| 3894 | return CBM_STORE_ERR; |
| 3895 | } |
| 3896 | bind_text(stmt, ST_COL_1, project); |
| 3897 | for (int i = 0; i < chunk_count; i++) { |
| 3898 | bind_text(stmt, i + ST_COL_2, target_files[i]); |
| 3899 | } |
| 3900 | int step_rc; |
| 3901 | while ((step_rc = sqlite3_step(stmt)) == SQLITE_ROW) { |
| 3902 | const char *path = (const char *)sqlite3_column_text(stmt, 0); |
| 3903 | if (!path || !path[0] || cbm_ht_get(seen, path)) { |
| 3904 | continue; |
| 3905 | } |
| 3906 | if (*out_count >= *out_cap) { |
| 3907 | int ncap = *out_cap ? *out_cap * ST_GROWTH : ST_INIT_CAP_8; |
| 3908 | char **grown = realloc(*out, (size_t)ncap * sizeof(char *)); |
| 3909 | if (!grown) { |
| 3910 | sqlite3_finalize(stmt); |
| 3911 | return CBM_STORE_ERR; |
| 3912 | } |
| 3913 | *out = grown; |
| 3914 | *out_cap = ncap; |
| 3915 | } |
| 3916 | char *copy = strdup(path); |
| 3917 | if (!copy) { |
| 3918 | sqlite3_finalize(stmt); |
| 3919 | return CBM_STORE_ERR; |
no test coverage detected