Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,22 @@ public class SearchLiteral extends SearchExpression {

@Override
public String toQueryString(Function<String, ExprType> fieldTypeResolver) {
// Unfielded literal: no enclosing field, so no index type. Take the field-agnostic branch.
// Unfielded literal: no enclosing field, so no index type. The value is matched against
// index.query.default_field, which usually includes analyzed text, so the user's quoting is
// honored the same way as on a text field: a quoted value is a phrase even without whitespace.
// Otherwise a value the analyzer splits (`foo-bar`) becomes an OR of its tokens. On fields
// with a no-op analyzer the phrase and the bare term resolve to the same single term.
if (userQuoted
&& literal instanceof Literal
&& ((Literal) literal).getValue() instanceof String) {
String str = (String) ((Literal) literal).getValue();
// Same exception as on text: a whitespace-free wildcard value stays unquoted so `*` and `?`
// keep their operator meaning.
boolean wildcardTerm = hasUnescapedWildcard(str) && !str.contains(" ");
if (!wildcardTerm) {
return quoted(str);
}
}
return toQueryString((ExprType) null);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,50 @@ void argless_toQueryString_matches_null_type_branch() {
assertEquals(lit.toQueryString((ExprType) null), lit.toQueryString());
}

// -------------------------------------------------------------------------
// UNFIELDED (free text): no index type, so honor the user's quoting as on text.
// -------------------------------------------------------------------------

/**
* A quoted free-text value the analyzer splits must not become an OR of tokens. These values hold
* no whitespace, so the legacy space check emitted them unquoted.
*/
@Test
void unfielded_quoted_value_the_analyzer_splits_emits_phrase() {
assertEquals("\"foo\\-bar\"", q("foo-bar").toQueryString());
assertEquals("\"foo=bar\"", q("foo=bar").toQueryString());
assertEquals("\"user@example.com\"", q("user@example.com").toQueryString());
}

@Test
void unfielded_quoted_with_whitespace_emits_phrase() {
assertEquals("\"foo bar\"", q("foo bar").toQueryString());
assertEquals("\"foo bar*\"", q("foo bar*").toQueryString());
}

@Test
void unfielded_quoted_wildcard_term_stays_unquoted() {
assertEquals("foo*", q("foo*").toQueryString());
assertEquals("log_?", q("log_?").toQueryString());
}

@Test
void unfielded_quoted_escaped_wildcard_emits_phrase() {
assertEquals("\"foo\\*\"", q("foo\\*").toQueryString());
}

@Test
void unfielded_unquoted_keeps_bare_term() {
assertEquals("foo", bare("foo").toQueryString());
assertEquals("foo\\-bar", bare("foo-bar").toQueryString());
assertEquals("foo*", bare("foo*").toQueryString());
}

@Test
void unfielded_non_string_literals_are_unchanged() {
assertEquals("42", new SearchLiteral(new Literal(42, DataType.INTEGER), false).toQueryString());
}

// -------------------------------------------------------------------------
// End-to-end via SearchComparison + resolver — the actual planner call path.
// -------------------------------------------------------------------------
Expand Down
2 changes: 2 additions & 0 deletions docs/user/ppl/cmd/search.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ The following queries show both syntax types:

* **Unquoted**: `search error`, `search user-123`, `search log_*`
* **Quoted**: `search "error message"`, `search "user@example.com"`

A quoted value is matched as a phrase on analyzed text fields, even when it contains no spaces. For example, `search "user-123"` matches only documents in which the analyzed tokens `user` and `123` appear next to each other in that order, while the unquoted `search user-123` matches documents containing either token. A quoted value that contains a wildcard and no spaces, such as `search "log_*"`, is treated as a wildcard pattern instead of a phrase.

### Field values

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public void testSimpleSearchTerm() {
assertTrue(plan instanceof Search);
Search search = (Search) plan;
// Double quotes are preserved for phrase search
assertEquals("error", search.getQueryString());
assertEquals("\"error\"", search.getQueryString());

// Check the child is a Relation
assertTrue(search.getChild().get(0) instanceof Relation);
Expand Down
Loading