Skip to content

Commit 8b01da2

Browse files
committed
fix: support single-quoted strings in query parser for NOT exclusions
The tokenizer now handles both double-quoted ("...") and single-quoted ('...') strings. Previously, single quotes were treated as part of identifiers, causing NOT 'AND' to search for literal "'AND'" instead of "AND".
1 parent 044b140 commit 8b01da2

File tree

2 files changed

+47
-3
lines changed

2 files changed

+47
-3
lines changed

src/Core/Search/Query/Parsers/InfixQueryParser.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -161,12 +161,13 @@ private List<Token> Tokenize(string query)
161161
continue;
162162
}
163163

164-
// Quoted string
165-
if (query[i] == '"')
164+
// Quoted string (double or single quotes)
165+
if (query[i] == '"' || query[i] == '\'')
166166
{
167+
var quoteChar = query[i];
167168
i++;
168169
var start = i;
169-
while (i < query.Length && query[i] != '"')
170+
while (i < query.Length && query[i] != quoteChar)
170171
{
171172
i++;
172173
}

tests/Core.Tests/Search/SearchEndToEndTests.cs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -907,5 +907,48 @@ public async Task KnownIssue1_MongoNor_ExcludesAllConditions()
907907
Assert.Contains(this._insertedIds["doc4"], resultIds);
908908
}
909909

910+
[Fact]
911+
public async Task KnownIssue1_NOTWithSingleQuotedReservedWord_ExcludesCorrectly()
912+
{
913+
// Single-quoted reserved words in NOT should be excluded correctly
914+
// Bug: NOT 'AND' was searching for literal "'AND'" instead of "AND"
915+
916+
// Arrange
917+
await this.InsertAsync("doc1", "Meeting with Alice AND Bob tomorrow").ConfigureAwait(false);
918+
await this.InsertAsync("doc2", "Regular meeting notes").ConfigureAwait(false);
919+
await this.InsertAsync("doc3", "Project status update").ConfigureAwait(false);
920+
921+
// Act: NOT with single-quoted AND (reserved word) - should exclude docs containing literal AND
922+
var response = await this.SearchAsync("NOT 'AND'").ConfigureAwait(false);
923+
924+
// Assert: Should return doc2 and doc3 (not containing "AND")
925+
Assert.Equal(2, response.TotalResults);
926+
var resultIds = response.Results.Select(r => r.Id).ToHashSet();
927+
Assert.DoesNotContain(this._insertedIds["doc1"], resultIds); // Contains "AND"
928+
Assert.Contains(this._insertedIds["doc2"], resultIds);
929+
Assert.Contains(this._insertedIds["doc3"], resultIds);
930+
}
931+
932+
[Fact]
933+
public async Task KnownIssue1_NOTWithDoubleQuotedReservedWord_ExcludesCorrectly()
934+
{
935+
// Double-quoted reserved words in NOT should be excluded correctly
936+
937+
// Arrange
938+
await this.InsertAsync("doc1", "This is NOT important").ConfigureAwait(false);
939+
await this.InsertAsync("doc2", "Regular document content").ConfigureAwait(false);
940+
await this.InsertAsync("doc3", "Something else entirely").ConfigureAwait(false);
941+
942+
// Act: NOT with double-quoted NOT (reserved word) - should exclude docs containing literal NOT
943+
var response = await this.SearchAsync("NOT \"NOT\"").ConfigureAwait(false);
944+
945+
// Assert: Should return doc2 and doc3 (not containing "NOT")
946+
Assert.Equal(2, response.TotalResults);
947+
var resultIds = response.Results.Select(r => r.Id).ToHashSet();
948+
Assert.DoesNotContain(this._insertedIds["doc1"], resultIds); // Contains "NOT"
949+
Assert.Contains(this._insertedIds["doc2"], resultIds);
950+
Assert.Contains(this._insertedIds["doc3"], resultIds);
951+
}
952+
910953
#endregion
911954
}

0 commit comments

Comments
 (0)