Skip to content

Fix profile search prefix matching#117

Open
alltheseas wants to merge 2 commits intodamus-io:masterfrom
alltheseas:fix-profile-search-prefix
Open

Fix profile search prefix matching#117
alltheseas wants to merge 2 commits intodamus-io:masterfrom
alltheseas:fix-profile-search-prefix

Conversation

@alltheseas
Copy link

@alltheseas alltheseas commented Jan 6, 2026

Summary

  • Fix ndb_search_profile() and ndb_search_profile_next() to properly validate query prefix matching
  • Add query[24] field to struct ndb_search to store the original search query
  • Add regression test test_profile_search_prefix() to verify all results match the query prefix

Problem

ndb_search_profile() uses LMDB's MDB_SET_RANGE to position the cursor at keys >= query, but ndb_search_profile_next() was iterating through ALL subsequent keys without checking if they still matched the query prefix.

This caused searches like "jack" to return unrelated profiles starting with "j" (e.g., "jb55", "jen", etc.) because LMDB would continue past the query prefix into subsequent entries.

Test plan

  • Added test_profile_search_prefix() to verify all returned results match the query prefix
  • Verified fix works in Damus iOS app

Closes #115
Fixes: damus-io/damus#3504

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Enhanced profile search to properly validate that results match the entered search prefix with case-insensitive matching, preventing unintended results from being returned.
  • Tests

    • Added test coverage for prefix-based profile search functionality.

✏️ Tip: You can customize this high-level summary in your review settings.

ndb_search_profile() uses MDB_SET_RANGE to position the cursor at keys
>= query, but ndb_search_profile_next() was iterating through ALL
subsequent keys without checking if they still matched the query prefix.

This caused searches like "jack" to return unrelated profiles starting
with "j" (e.g., "jb55", "jen", etc.) because LMDB would continue past
the query prefix into subsequent entries.

Fix:
- Add query[24] field to struct ndb_search to store the original query
- Store lowercased query in ndb_search_profile() for later comparison
- Validate first result matches prefix before returning
- Check prefix match in ndb_search_profile_next() and stop when
  results no longer match

Add test_profile_search_prefix() to verify all returned results
match the query prefix, preventing regression.

Fixes: damus-io/damus#3504
Closes damus-io#115

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@coderabbitai
Copy link

coderabbitai bot commented Jan 6, 2026

Warning

Rate limit exceeded

@alltheseas has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 9 minutes and 22 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 62c68a6 and a8350da.

📒 Files selected for processing (1)
  • test.c
📝 Walkthrough

Walkthrough

This change adds prefix matching validation to profile searches in nostrdb. The ndb_search structure now stores the lowercased query string, and both ndb_search_profile and ndb_search_profile_next verify that returned profiles match the query prefix, preventing searches from returning unrelated results beyond the prefix boundary.

Changes

Cohort / File(s) Change Summary
Header Structure
src/nostrdb.h
Added char query[24] field to ndb_search struct to store the original query string for use during prefix matching operations.
Search Logic
src/nostrdb.c
Modified ndb_search_profile to compute and store lowercased query with length, then verify the first result's key matches the query prefix; abort if no match. Enhanced ndb_search_profile_next to enforce prefix matching for each successive result, returning 0 when prefix no longer matches, while preserving duplicate pubkey skipping.
Test Coverage
test.c
Added test_profile_search_prefix helper to validate prefix-based search using "jean" prefix; validates all results match case-insensitive prefix. Integrated test into test_load_profiles and test_migrate. Added #include <ctype.h> for character operations.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A search that wandered far and wide,
Through prefixes it could not confide—
Now "jack" stays true to "jack" alone,
No "jb55" in the search zone shown!
Prefix matching, properly grown. ✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Fix profile search prefix matching' clearly and concisely summarizes the main change: fixing prefix-matching validation in profile search functions.
Linked Issues check ✅ Passed The PR fully addresses both linked issues: it fixes the ndb_search_profile_next() prefix-matching bug (#115) and restores correct autocomplete behavior in Damus (#3504) by validating prefix matches in search iteration.
Out of Scope Changes check ✅ Passed All changes are directly aligned with the stated objectives: struct field addition for query storage, prefix validation logic in search functions, and regression test coverage. No out-of-scope modifications detected.

Comment @coderabbitai help to get the list of available commands and usage tips.

The old test expected ndb_search_profile_next() to return profiles
that don't match the query prefix (broken behavior). Updated to verify
that all results correctly match the "jean" prefix.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
test.c (1)

882-912: Fix the old test to handle new prefix matching behavior.

The pipeline failure at line 897 is caused by test_profile_search() expecting the old buggy behavior where ndb_search_profile_next() would continue iterating past the "jean" prefix (e.g., returning profiles starting with "jb", "je"). With the prefix matching fix, ndb_search_profile_next() correctly returns false when the next result doesn't match the "jean" prefix, causing the assertion to fail.

The test needs to be updated to either:

  1. Remove the assertion at line 897 and check the return value conditionally
  2. Use a different search query that's guaranteed to have multiple matching results in the test data
  3. Remove this test entirely since test_profile_search_prefix() now provides comprehensive coverage
🔎 Suggested fix: Make the second result check conditional
 	assert(!strncmp(name, "jean", 4));
 
-	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);
-		//print_search(&txn, &search);
-	}
-
-	//assert(!strcmp(name, "jb55"));
+	// With prefix matching, we may not have additional results starting with "jean"
+	if (ndb_search_profile_next(&search)) {
+		profile = lookup_profile(&txn, search.profile_key);
+		name = NdbProfile_name_get(profile);
+		// Verify any additional results also match the prefix
+		assert(!strncasecmp(name, "jean", 4));
+	}
 
 	ndb_search_profile_end(&search);
 	ndb_end_query(&txn);
🧹 Nitpick comments (3)
src/nostrdb.h (1)

179-184: New query[24] field is consistent with index key size; clarify semantics

The added buffer size matches struct ndb_search_key::search[24], which is good for keeping prefix logic consistent. Two small refinements you might consider:

  • Make the comment explicit that this holds a lowercased, truncated query used for prefix checks, not the raw user string.
  • Optionally tie the two sizes together (e.g., a shared #define NDB_PROFILE_SEARCH_STR_LEN 24 or a STATIC_ASSERT(sizeof(((struct ndb_search_key *)0)->search) == sizeof(((struct ndb_search *)0)->query), search_query_size_mismatch)) to avoid silent divergence if one changes later.
src/nostrdb.c (2)

3623-3668: Prefix validation in ndb_search_profile looks correct; document truncation/empty‑query behavior

The new logic to:

  • store a lowercased query into search->query and
  • immediately reject the first hit when search->key->search no longer matches that prefix

correctly fixes the “SET_RANGE then walk unrelated keys” bug and aligns with how ndb_search_key.search is constructed.

A couple of behavioral details worth making explicit in comments or docs:

  • Queries are effectively limited to sizeof(search->query) - 1 bytes; longer inputs are truncated before prefix comparison, but the index keys are truncated in the same way, so the behavior is consistent.
  • An empty query (query_len == 0) still returns all profiles because strncmp(..., 0) is always 0. That matches prior behavior; if you ever want empty queries to return no results instead, this is the place to special‑case it.

Optionally, you could also zero search->key / search->profile_key in the cleanup path to avoid leaving stale data around after a failed search, though callers should already be checking the return code.


3679-3712: ndb_search_profile_next correctly enforces the prefix boundary; consider small robustness tweaks

The added prefix check:

  • computes query_len = strlen(search->query) and
  • returns 0 as soon as search->key->search no longer starts with that prefix,

ensures iteration stops at the end of the matching range, which is what you want on top of MDB_SET_RANGE. Duplicate‑pubkey skipping is still preserved via init_id and retry.

A few low‑impact improvements you might consider:

  • Cache the query length in struct ndb_search (e.g., size_t query_len;) when starting the search, and reuse it here instead of recomputing strlen on each _next. This also makes behavior independent of any accidental mutation of search->query.
  • For defensive programming, you could early‑return 0 if search->cursor == NULL in _next to guard against misuse (calling _next after an initial 0 result), rather than relying on callers to follow the contract.

These don’t affect correctness of the prefix‑matching fix, which looks solid.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5fe515f and 62c68a6.

📒 Files selected for processing (3)
  • src/nostrdb.c
  • src/nostrdb.h
  • test.c
🧰 Additional context used
🪛 GitHub Actions: C/C++ CI
test.c

[error] 897-897: make check failed. test_profile_search assertion failed in test.c:897. Assertion `ndb_search_profile_next(&search)' failed.

🔇 Additional comments (4)
test.c (4)

21-21: LGTM!

The ctype.h include is necessary for the tolower() function used in the new prefix matching test.


914-964: Well-structured regression test for prefix matching.

The test correctly validates that:

  • The first result matches the query prefix
  • All subsequent results match the prefix (case-insensitive)
  • The search terminates when results no longer match the prefix

The safety limit at line 955 is a good defensive measure for test environments.


1050-1050: LGTM!

Appropriately adds the new prefix matching test to the profile loading test suite.


1095-1095: LGTM!

Appropriately adds the new prefix matching test to the migration test suite.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ndb_search_profile_next() does not check if results matched the query prefix suggest name is missing entries

2 participants