| 608 | } |
| 609 | |
| 610 | static cbm_store_t *store_open_internal(const char *path, bool in_memory) { |
| 611 | cbm_store_t *s = calloc(CBM_ALLOC_ONE, sizeof(cbm_store_t)); |
| 612 | if (!s) { |
| 613 | return NULL; |
| 614 | } |
| 615 | |
| 616 | int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE; |
| 617 | if (in_memory) { |
| 618 | flags |= SQLITE_OPEN_MEMORY; |
| 619 | } |
| 620 | |
| 621 | int rc = sqlite3_open_v2(path, &s->db, flags, NULL); |
| 622 | if (rc != SQLITE_OK) { |
| 623 | free(s); |
| 624 | return NULL; |
| 625 | } |
| 626 | |
| 627 | if (path && !in_memory) { |
| 628 | s->db_path = heap_strdup(path); |
| 629 | } |
| 630 | |
| 631 | /* Security: block ATTACH/DETACH to prevent file creation via SQL injection. |
| 632 | * The authorizer runs inside SQLite's query planner — no string-level bypass. */ |
| 633 | sqlite3_set_authorizer(s->db, store_authorizer, NULL); |
| 634 | |
| 635 | /* Register REGEXP function (SQLite doesn't have one built-in) */ |
| 636 | sqlite3_create_function(s->db, "regexp", ST_COL_2, SQLITE_UTF8 | SQLITE_DETERMINISTIC, NULL, |
| 637 | sqlite_regexp, NULL, NULL); |
| 638 | /* Case-insensitive variant for search with case_sensitive=false */ |
| 639 | sqlite3_create_function(s->db, "iregexp", ST_COL_2, SQLITE_UTF8 | SQLITE_DETERMINISTIC, NULL, |
| 640 | sqlite_iregexp, NULL, NULL); |
| 641 | /* Int8 cosine similarity for vector search */ |
| 642 | sqlite3_create_function(s->db, "cbm_cosine_i8", ST_COL_2, SQLITE_UTF8 | SQLITE_DETERMINISTIC, |
| 643 | NULL, sqlite_cosine_i8, NULL, NULL); |
| 644 | /* camelCase splitter for FTS5 BM25 indexing */ |
| 645 | sqlite3_create_function(s->db, "cbm_camel_split", SKIP_ONE, SQLITE_UTF8 | SQLITE_DETERMINISTIC, |
| 646 | NULL, sqlite_camel_split, NULL, NULL); |
| 647 | |
| 648 | if (configure_pragmas(s, in_memory, false) != CBM_STORE_OK || init_schema(s) != CBM_STORE_OK || |
| 649 | create_user_indexes(s) != CBM_STORE_OK) { |
| 650 | sqlite3_close(s->db); |
| 651 | safe_str_free(&s->db_path); |
| 652 | free(s); |
| 653 | return NULL; |
| 654 | } |
| 655 | |
| 656 | return s; |
| 657 | } |
| 658 | |
| 659 | cbm_store_t *cbm_store_open_memory(void) { |
| 660 | return store_open_internal(":memory:", true); |
no test coverage detected