| 960 | } |
| 961 | |
| 962 | cbm_store_t *cbm_store_open_path_query(const char *db_path) { |
| 963 | if (!db_path) { |
| 964 | return NULL; |
| 965 | } |
| 966 | |
| 967 | cbm_store_t *s = calloc(CBM_ALLOC_ONE, sizeof(cbm_store_t)); |
| 968 | if (!s) { |
| 969 | return NULL; |
| 970 | } |
| 971 | |
| 972 | /* Query tools open the project DB READ-ONLY: a read query must never |
| 973 | * mutate the DB (the previous READWRITE open + WAL write-pragmas did), |
| 974 | * and must work on a read-only DB file / filesystem. |
| 975 | * |
| 976 | * Try a plain READONLY open first — on a normal writable filesystem this |
| 977 | * reads WAL frames correctly via the -shm wal-index. SQLite opens lazily, |
| 978 | * so a read-only-filesystem failure (cannot create -shm for a WAL-mode |
| 979 | * DB) surfaces on first access, not at open time; we probe with a trivial |
| 980 | * read to force it. If the probe fails, retry once with an immutable URI |
| 981 | * that bypasses WAL and reads the main DB file directly. |
| 982 | * |
| 983 | * No SQLITE_OPEN_CREATE on either path — a missing DB must return NULL |
| 984 | * (no ghost .db for unknown/unindexed projects). */ |
| 985 | char open_path[4096]; |
| 986 | if (!cbm_path_for_file_api(db_path, open_path, sizeof(open_path))) { |
| 987 | free(s); |
| 988 | return NULL; |
| 989 | } |
| 990 | int rc = sqlite3_open_v2(open_path, &s->db, SQLITE_OPEN_READONLY, NULL); |
| 991 | if (rc == SQLITE_OK) { |
| 992 | /* Force first DB access so a read-only-FS WAL failure surfaces now. */ |
| 993 | if (sqlite3_exec(s->db, "SELECT 1 FROM sqlite_master LIMIT 1;", NULL, NULL, NULL) != |
| 994 | SQLITE_OK) { |
| 995 | sqlite3_close(s->db); |
| 996 | s->db = NULL; |
| 997 | rc = SQLITE_CANTOPEN; /* trigger immutable fallback */ |
| 998 | } |
| 999 | } |
| 1000 | if (rc != SQLITE_OK) { |
| 1001 | sqlite3_close(s->db); /* no-op if already NULL */ |
| 1002 | s->db = NULL; |
| 1003 | /* A genuinely missing DB must return NULL without creating anything — |
| 1004 | * only retry with the immutable URI when the file exists but could not |
| 1005 | * be opened (the read-only-filesystem case). This also keeps the |
| 1006 | * common "project not found" path to a single open attempt. */ |
| 1007 | if (!cbm_file_exists(db_path)) { |
| 1008 | free(s); |
| 1009 | return NULL; |
| 1010 | } |
| 1011 | char uri[ST_QUERY_URI_MAX]; |
| 1012 | if (!build_immutable_uri(db_path, uri, sizeof(uri))) { |
| 1013 | free(s); |
| 1014 | return NULL; |
| 1015 | } |
| 1016 | rc = sqlite3_open_v2(uri, &s->db, SQLITE_OPEN_READONLY | SQLITE_OPEN_URI, NULL); |
| 1017 | if (rc != SQLITE_OK) { |
| 1018 | /* sqlite3_open_v2 allocates a handle even on failure — must close it. */ |
| 1019 | sqlite3_close(s->db); |