diff --git a/src/nostrdb.c b/src/nostrdb.c index 6d5acbd80..eb27d8bbf 100644 --- a/src/nostrdb.c +++ b/src/nostrdb.c @@ -3625,12 +3625,18 @@ int ndb_search_profile(struct ndb_txn *txn, struct ndb_search *search, const cha int rc; struct ndb_search_key s; MDB_val k, v; + size_t query_len; search->cursor = NULL; MDB_cursor **cursor = (MDB_cursor **)&search->cursor; ndb_make_search_key_low(&s, query); + // Store the lowercased query for prefix matching in subsequent calls + lowercase_strncpy(search->query, query, sizeof(search->query) - 1); + search->query[sizeof(search->query) - 1] = '\0'; + query_len = strlen(search->query); + k.mv_data = &s; k.mv_size = sizeof(s); @@ -3650,6 +3656,11 @@ int ndb_search_profile(struct ndb_txn *txn, struct ndb_search *search, const cha search->key = k.mv_data; assert(v.mv_size == 8); search->profile_key = *((uint64_t*)v.mv_data); + + // Verify the first result matches the query prefix + if (strncmp(search->key->search, search->query, query_len) != 0) { + goto cleanup; + } return 1; } @@ -3670,10 +3681,12 @@ int ndb_search_profile_next(struct ndb_search *search) int rc; MDB_val k, v; unsigned char *init_id; + size_t query_len; init_id = search->key->id; k.mv_data = search->key; k.mv_size = sizeof(*search->key); + query_len = strlen(search->query); retry: if ((rc = mdb_cursor_get(search->cursor, &k, &v, MDB_NEXT))) { @@ -3685,6 +3698,11 @@ int ndb_search_profile_next(struct ndb_search *search) assert(v.mv_size == 8); search->profile_key = *((uint64_t*)v.mv_data); + // Check if this result still matches the query prefix + if (strncmp(search->key->search, search->query, query_len) != 0) { + return 0; + } + // skip duplicate pubkeys if (!memcmp(init_id, search->key->id, 32)) goto retry; diff --git a/src/nostrdb.h b/src/nostrdb.h index 3f43de856..e33d83a7b 100644 --- a/src/nostrdb.h +++ b/src/nostrdb.h @@ -180,6 +180,7 @@ struct ndb_search { struct ndb_search_key *key; uint64_t profile_key; void *cursor; // MDB_cursor * + char query[24]; // Original query for prefix matching }; // From-client event diff --git a/test.c b/test.c index c40f54004..5f61461ba 100644 --- a/test.c +++ b/test.c @@ -18,6 +18,7 @@ #include #include #include +#include #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0])) @@ -882,7 +883,7 @@ static void test_profile_search(struct ndb *ndb) { struct ndb_txn txn; struct ndb_search search; - int i; + int count = 0; const char *name; NdbProfile_table_t profile; @@ -892,22 +893,73 @@ static void test_profile_search(struct ndb *ndb) profile = lookup_profile(&txn, search.profile_key); name = NdbProfile_name_get(profile); assert(!strncmp(name, "jean", 4)); + count++; - assert(ndb_search_profile_next(&search)); - //print_search(&txn, &search); - profile = lookup_profile(&txn, search.profile_key); - name = NdbProfile_name_get(profile); - //assert(strncmp(name, "jean", 4)); - - for (i = 0; i < 3; i++) { - ndb_search_profile_next(&search); + // With prefix matching fix, _next only returns profiles matching "jean" + while (ndb_search_profile_next(&search)) { //print_search(&txn, &search); + profile = lookup_profile(&txn, search.profile_key); + name = NdbProfile_name_get(profile); + // All results must match the "jean" prefix + assert(!strncmp(name, "jean", 4)); + count++; } - //assert(!strcmp(name, "jb55")); + ndb_search_profile_end(&search); + ndb_end_query(&txn); + printf("test_profile_search: found %d profiles matching 'jean'\n", count); +} +static void test_profile_search_prefix(struct ndb *ndb) +{ + struct ndb_txn txn; + struct ndb_search search; + int count = 0; + const char *name; + NdbProfile_table_t profile; + const char *prefix = "jean"; + size_t prefix_len = strlen(prefix); + + assert(ndb_begin_query(ndb, &txn)); + + // Search for prefix "jean" + if (!ndb_search_profile(&txn, &search, prefix)) { + // No results is valid if no profiles match + ndb_end_query(&txn); + printf("ok test_profile_search_prefix (no matches)\n"); + return; + } + + // First result should match the prefix + profile = lookup_profile(&txn, search.profile_key); + name = NdbProfile_name_get(profile); + assert(name != NULL); + // Convert to lowercase for comparison since search is case-insensitive + for (size_t i = 0; i < prefix_len && name[i]; i++) { + assert(tolower((unsigned char)name[i]) == tolower((unsigned char)prefix[i])); + } + count++; + + // All subsequent results should also match the prefix + while (ndb_search_profile_next(&search)) { + profile = lookup_profile(&txn, search.profile_key); + name = NdbProfile_name_get(profile); + assert(name != NULL); + // Verify prefix match (case-insensitive) + for (size_t i = 0; i < prefix_len && name[i]; i++) { + assert(tolower((unsigned char)name[i]) == tolower((unsigned char)prefix[i])); + } + count++; + // Safety limit to avoid infinite loops in tests + if (count > 1000) break; + } + + // The search should have stopped because prefix no longer matches, + // not because we ran out of entries in the entire database ndb_search_profile_end(&search); ndb_end_query(&txn); + + printf("ok test_profile_search_prefix (found %d matches)\n", count); } static void test_profile_updates() @@ -994,6 +1046,7 @@ static void test_load_profiles() ndb_end_query(&txn); test_profile_search(ndb); + test_profile_search_prefix(ndb); ndb_destroy(ndb); @@ -1038,6 +1091,7 @@ static void test_migrate() { assert(ndb_end_query(&txn)); test_profile_search(ndb); + test_profile_search_prefix(ndb); ndb_destroy(ndb); }