Build basic WHERE clauses: project, label, name, file, qn patterns. */
| 2496 | |
| 2497 | /* Build basic WHERE clauses: project, label, name, file, qn patterns. */ |
| 2498 | static int search_where_basic(const cbm_search_params_t *params, char *where, int where_sz, |
| 2499 | int *wlen, int *nparams, search_bind_t *binds, int *bind_idx, |
| 2500 | search_like_pool_t *pool) { |
| 2501 | char bind_buf[CBM_SZ_64]; |
| 2502 | |
| 2503 | if (params->project) { |
| 2504 | snprintf(bind_buf, sizeof(bind_buf), "n.project = ?%d", *bind_idx + SKIP_ONE); |
| 2505 | *wlen = where_append(where, where_sz, *wlen, nparams, bind_buf); |
| 2506 | where_bind_text(binds, bind_idx, params->project); |
| 2507 | } |
| 2508 | /* Ignore an empty-string label: it is non-NULL but should behave like an |
| 2509 | * omitted label (no filter), matching the BM25 query path. Without the |
| 2510 | * params->label[0] guard, name_pattern/qn_pattern searches that pass |
| 2511 | * label="" append `n.label = ''`, which matches no node and silently |
| 2512 | * returns zero results (issue #481). */ |
| 2513 | if (params->label && params->label[0]) { |
| 2514 | snprintf(bind_buf, sizeof(bind_buf), "n.label = ?%d", *bind_idx + SKIP_ONE); |
| 2515 | *wlen = where_append(where, where_sz, *wlen, nparams, bind_buf); |
| 2516 | where_bind_text(binds, bind_idx, params->label); |
| 2517 | } |
| 2518 | if (params->name_pattern) { |
| 2519 | where_add_like_hints("n.name", params->name_pattern, where, where_sz, wlen, nparams, binds, |
| 2520 | bind_idx, pool); |
| 2521 | where_add_regex(where, where_sz, wlen, nparams, binds, bind_idx, "n.name", |
| 2522 | params->name_pattern, params->case_sensitive); |
| 2523 | } |
| 2524 | if (params->qn_pattern) { |
| 2525 | where_add_like_hints("n.qualified_name", params->qn_pattern, where, where_sz, wlen, nparams, |
| 2526 | binds, bind_idx, pool); |
| 2527 | where_add_regex(where, where_sz, wlen, nparams, binds, bind_idx, "n.qualified_name", |
| 2528 | params->qn_pattern, params->case_sensitive); |
| 2529 | } |
| 2530 | if (params->file_pattern) { |
| 2531 | char *lp = cbm_glob_to_like(params->file_pattern); |
| 2532 | /* A file_pattern with no glob wildcards is treated as a path-substring |
| 2533 | * match (issue #200): file_pattern="offer-server" should match |
| 2534 | * "src/offer-server/x.js", not only a path equal to "offer-server". |
| 2535 | * Patterns that DO contain * / ? keep their explicit glob semantics. */ |
| 2536 | if (lp && !strchr(params->file_pattern, '*') && !strchr(params->file_pattern, '?')) { |
| 2537 | size_t lplen = strlen(lp); |
| 2538 | char *contains = malloc(lplen + 3); |
| 2539 | if (contains) { |
| 2540 | contains[0] = '%'; |
| 2541 | memcpy(contains + 1, lp, lplen); |
| 2542 | contains[lplen + 1] = '%'; |
| 2543 | contains[lplen + 2] = '\0'; |
| 2544 | free(lp); |
| 2545 | lp = contains; |
| 2546 | } |
| 2547 | } |
| 2548 | int pool_was_full = (pool->count >= ST_LIKE_POOL_MAX); |
| 2549 | like_pool_add(pool, lp); |
| 2550 | if (!pool_was_full && lp) { |
| 2551 | snprintf(bind_buf, sizeof(bind_buf), "n.file_path LIKE ?%d", *bind_idx + SKIP_ONE); |
| 2552 | *wlen = where_append(where, where_sz, *wlen, nparams, bind_buf); |
| 2553 | where_bind_text(binds, bind_idx, lp); |
| 2554 | } |
| 2555 | } |
no test coverage detected