MCPcopy Create free account
hub / github.com/DeusData/codebase-memory-mcp / expand_pattern_rels

Function expand_pattern_rels

src/cypher/cypher.c:3288–3359  ·  view source on GitHub ↗

Source from the content-addressed store, hash-verified

3286}
3287
3288static void expand_pattern_rels(cbm_store_t *store, cbm_pattern_t *pat, binding_t **bindings,
3289 int *bind_count, const int *bind_cap, const char **var_name,
3290 bool is_optional) {
3291 for (int ri = 0; ri < pat->rel_count; ri++) {
3292 /* #601: stop expanding further hops once the wall-clock budget is spent
3293 * (an unbounded expansion is exactly what blows up here). */
3294 if (cypher_deadline_exceeded()) {
3295 return;
3296 }
3297 cbm_rel_pattern_t *rel = &pat->rels[ri];
3298 cbm_node_pattern_t *target_node = &pat->nodes[ri + SKIP_ONE];
3299 const char *to_var = target_node->variable ? target_node->variable : "_n_t";
3300
3301 bool is_variable_length = (rel->min_hops != SKIP_ONE || rel->max_hops != SKIP_ONE);
3302
3303 /* Size this hop's output for BOTH writers without dropping any row: the
3304 * expansion helpers emit at most max_new = bind_cap*10 rows (they stop at
3305 * max_new), and the OPTIONAL fallback emits at most one row per source
3306 * (<= *bind_count). A source either matches (feeds the expansion) or takes
3307 * the fallback, never both, so the two counts are additive and bounded by
3308 * max_new + *bind_count. Computed in size_t so the product cannot overflow.
3309 * The previous "+ 1" sizing fit only a SINGLE fallback row after a
3310 * saturated expansion; a second one ran off the end (heap OOB, CWE-787). */
3311 size_t alloc_n = (size_t)*bind_cap * (size_t)CYP_GROWTH_10 + (size_t)*bind_count;
3312 binding_t *new_bindings = malloc(alloc_n * sizeof(binding_t));
3313 if (!new_bindings) {
3314 return; /* OOM: leave existing bindings untouched rather than corrupt */
3315 }
3316 int new_count = 0;
3317
3318 for (int bi = 0; bi < *bind_count; bi++) {
3319 if ((bi & CYPHER_DEADLINE_CHECK_MASK) == 0 && cypher_deadline_exceeded()) {
3320 break;
3321 }
3322 binding_t *b = &(*bindings)[bi];
3323 cbm_node_t *src = binding_get(b, *var_name);
3324 if (!src) {
3325 continue;
3326 }
3327
3328 int match_count = 0;
3329
3330 int max_new = *bind_cap * CYP_GROWTH_10;
3331 if (is_variable_length) {
3332 expand_var_length(store, rel, target_node, b, src, to_var, new_bindings, &new_count,
3333 max_new, &match_count);
3334 } else {
3335 expand_fixed_length(store, rel, target_node, b, src, to_var, new_bindings,
3336 &new_count, max_new, &match_count);
3337 }
3338
3339 /* OPTIONAL MATCH: no expansion for this source, so keep the binding
3340 * with the target unbound (projection renders it ""). The buffer is
3341 * sized max_new + *bind_count precisely so every such fallback row has
3342 * a slot — no guard needed, and no OPTIONAL no-match row is dropped. */
3343 if (is_optional && match_count == 0) {
3344 binding_t nb = {0};
3345 binding_copy(&nb, b);

Callers 3

cross_join_with_relsFunction · 0.85
execute_singleFunction · 0.85

Calls 6

cypher_deadline_exceededFunction · 0.85
binding_getFunction · 0.85
expand_var_lengthFunction · 0.85
expand_fixed_lengthFunction · 0.85
binding_copyFunction · 0.85
binding_freeFunction · 0.85

Tested by

no test coverage detected