| 2992 | } |
| 2993 | |
| 2994 | static void expand_pattern_rels(cbm_store_t *store, cbm_pattern_t *pat, binding_t **bindings, |
| 2995 | int *bind_count, const int *bind_cap, const char **var_name, |
| 2996 | bool is_optional) { |
| 2997 | for (int ri = 0; ri < pat->rel_count; ri++) { |
| 2998 | cbm_rel_pattern_t *rel = &pat->rels[ri]; |
| 2999 | cbm_node_pattern_t *target_node = &pat->nodes[ri + SKIP_ONE]; |
| 3000 | const char *to_var = target_node->variable ? target_node->variable : "_n_t"; |
| 3001 | |
| 3002 | bool is_variable_length = (rel->min_hops != SKIP_ONE || rel->max_hops != SKIP_ONE); |
| 3003 | |
| 3004 | size_t alloc_n = (size_t)*bind_cap * (size_t)CYP_GROWTH_10 + SKIP_ONE; |
| 3005 | binding_t *new_bindings = malloc(alloc_n * sizeof(binding_t)); |
| 3006 | if (!new_bindings) { |
| 3007 | return; /* OOM: leave existing bindings untouched rather than corrupt */ |
| 3008 | } |
| 3009 | int new_count = 0; |
| 3010 | |
| 3011 | for (int bi = 0; bi < *bind_count; bi++) { |
| 3012 | binding_t *b = &(*bindings)[bi]; |
| 3013 | cbm_node_t *src = binding_get(b, *var_name); |
| 3014 | if (!src) { |
| 3015 | continue; |
| 3016 | } |
| 3017 | |
| 3018 | int match_count = 0; |
| 3019 | |
| 3020 | int max_new = *bind_cap * CYP_GROWTH_10; |
| 3021 | if (is_variable_length) { |
| 3022 | expand_var_length(store, rel, target_node, b, src, to_var, new_bindings, &new_count, |
| 3023 | max_new, &match_count); |
| 3024 | } else { |
| 3025 | expand_fixed_length(store, rel, target_node, b, src, to_var, new_bindings, |
| 3026 | &new_count, max_new, &match_count); |
| 3027 | } |
| 3028 | |
| 3029 | /* OPTIONAL MATCH: keep binding with empty target if no matches */ |
| 3030 | if (is_optional && match_count == 0) { |
| 3031 | binding_t nb = {0}; |
| 3032 | binding_copy(&nb, b); |
| 3033 | /* Don't set to_var — it remains unbound; projection returns "" */ |
| 3034 | new_bindings[new_count++] = nb; |
| 3035 | } |
| 3036 | } |
| 3037 | |
| 3038 | for (int bi = 0; bi < *bind_count; bi++) { |
| 3039 | binding_free(&(*bindings)[bi]); |
| 3040 | } |
| 3041 | free(*bindings); |
| 3042 | *bindings = new_bindings; |
| 3043 | *bind_count = new_count; |
| 3044 | *var_name = to_var; |
| 3045 | } |
| 3046 | } |
| 3047 | |
| 3048 | /* ── Result postprocessing helpers ─────────────────────────────── */ |
| 3049 |
no test coverage detected