aboutsummaryrefslogtreecommitdiffstats
path: root/container-search
diff options
context:
space:
mode:
authorJon Bratseth <bratseth@vespa.ai>2023-06-05 13:10:06 +0200
committerJon Bratseth <bratseth@vespa.ai>2023-06-05 13:10:06 +0200
commit3ecff4fb067f090789f2e85e0e195c1fd69e3c3e (patch)
treee0b68ac002cb604d3725afd356fbebb0cfcfa861 /container-search
parent4864d94e48919a8cb734191ab90b80738e843d08 (diff)
Validate prefix matching
Diffstat (limited to 'container-search')
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/Index.java9
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/SearchDefinition.java3
-rw-r--r--container-search/src/main/java/com/yahoo/search/searchers/QueryValidator.java48
-rw-r--r--container-search/src/test/java/com/yahoo/search/searchers/test/QueryValidatorTestCase.java49
4 files changed, 85 insertions, 24 deletions
diff --git a/container-search/src/main/java/com/yahoo/prelude/Index.java b/container-search/src/main/java/com/yahoo/prelude/Index.java
index e245faec919..af8f63ab9f2 100644
--- a/container-search/src/main/java/com/yahoo/prelude/Index.java
+++ b/container-search/src/main/java/com/yahoo/prelude/Index.java
@@ -36,6 +36,7 @@ public class Index {
private boolean hostIndex = false;
private StemMode stemMode = StemMode.NONE;
private boolean isAttribute = false;
+ private boolean isIndex = false;
private boolean isDefaultPosition = false;
private boolean dynamicSummary=false;
private boolean highlightSummary=false;
@@ -157,6 +158,8 @@ public class Index {
setNGram(true, Integer.parseInt(command.substring(6)));
} else if (command.equals("attribute")) {
setAttribute(true);
+ } else if (command.equals("index")) {
+ setIndex(true);
} else if (command.equals("default-position")) {
setDefaultPosition(true);
} else if (command.equals("plain-tokens")) {
@@ -273,6 +276,12 @@ public class Index {
this.isAttribute = isAttribute;
}
+ public boolean isIndex() { return isIndex; }
+
+ public void setIndex(boolean isIndex) {
+ this.isIndex = isIndex;
+ }
+
public boolean hasPlainTokens() { return plainTokens; }
public void setPlainTokens(boolean plainTokens) {
diff --git a/container-search/src/main/java/com/yahoo/prelude/SearchDefinition.java b/container-search/src/main/java/com/yahoo/prelude/SearchDefinition.java
index 1d9e32ec374..a232841f29f 100644
--- a/container-search/src/main/java/com/yahoo/prelude/SearchDefinition.java
+++ b/container-search/src/main/java/com/yahoo/prelude/SearchDefinition.java
@@ -86,12 +86,13 @@ public class SearchDefinition {
return idx;
}
- public void addCommand(String indexName, String commandString) {
+ public Index addCommand(String indexName, String commandString) {
Index index = getOrCreateIndex(indexName);
index.addCommand(commandString);
if (index.isDefaultPosition()) {
defaultPosition = index.getName();
}
+ return index;
}
}
diff --git a/container-search/src/main/java/com/yahoo/search/searchers/QueryValidator.java b/container-search/src/main/java/com/yahoo/search/searchers/QueryValidator.java
index a2e3d038053..341ab342468 100644
--- a/container-search/src/main/java/com/yahoo/search/searchers/QueryValidator.java
+++ b/container-search/src/main/java/com/yahoo/search/searchers/QueryValidator.java
@@ -5,9 +5,9 @@ import com.yahoo.component.chain.dependencies.After;
import com.yahoo.component.chain.dependencies.Before;
import com.yahoo.prelude.Index;
import com.yahoo.prelude.IndexFacts;
-import com.yahoo.prelude.query.CompositeItem;
import com.yahoo.prelude.query.HasIndexItem;
import com.yahoo.prelude.query.Item;
+import com.yahoo.prelude.query.PrefixItem;
import com.yahoo.prelude.query.ToolBox;
import com.yahoo.search.Query;
import com.yahoo.search.Result;
@@ -29,30 +29,58 @@ public class QueryValidator extends Searcher {
@Override
public Result search(Query query, Execution execution) {
IndexFacts.Session session = execution.context().getIndexFacts().newSession(query);
- ToolBox.visit(new ItemValidator(session), query.getModel().getQueryTree().getRoot());
+ ToolBox.visit(new TermSearchValidator(session), query.getModel().getQueryTree().getRoot());
+ ToolBox.visit(new PrefixSearchValidator(session), query.getModel().getQueryTree().getRoot());
return execution.search(query);
}
- private static class ItemValidator extends ToolBox.QueryVisitor {
+ private abstract static class TermValidator extends ToolBox.QueryVisitor {
- IndexFacts.Session session;
+ final IndexFacts.Session session;
- public ItemValidator(IndexFacts.Session session) {
+ public TermValidator(IndexFacts.Session session) {
this.session = session;
}
@Override
+ public void onExit() { }
+
+ }
+
+ private static class TermSearchValidator extends TermValidator {
+
+ public TermSearchValidator(IndexFacts.Session session) {
+ super(session);
+ }
+
+ @Override
public boolean visit(Item item) {
- if (item instanceof HasIndexItem) {
- String indexName = ((HasIndexItem)item).getIndexName();
- if (session.getIndex(indexName).isTensor())
- throw new IllegalArgumentException("Cannot search '" + indexName + "': It is a tensor field");
+ if (item instanceof HasIndexItem indexItem) {
+ if (session.getIndex(indexItem.getIndexName()).isTensor())
+ throw new IllegalArgumentException("Cannot search for terms in '" + indexItem.getIndexName() + "': It is a tensor field");
}
return true;
}
+ }
+
+ private static class PrefixSearchValidator extends TermValidator {
+
+ public PrefixSearchValidator(IndexFacts.Session session) {
+ super(session);
+ }
+
@Override
- public void onExit() { }
+ public boolean visit(Item item) {
+ if (item instanceof PrefixItem prefixItem) {
+ Index index = session.getIndex(prefixItem.getIndexName());
+ if ( ! index.isAttribute())
+ throw new IllegalArgumentException("'" + prefixItem.getIndexName() + "' is not an attribute field: Prefix matching is not supported");
+ if (index.isIndex()) // index overrides attribute
+ throw new IllegalArgumentException("'" + prefixItem.getIndexName() + "' is an index field: Prefix matching is not supported even when it is also an attribute");
+ }
+ return true;
+ }
}
diff --git a/container-search/src/test/java/com/yahoo/search/searchers/test/QueryValidatorTestCase.java b/container-search/src/test/java/com/yahoo/search/searchers/test/QueryValidatorTestCase.java
index 64fb4354003..8c525b2975a 100644
--- a/container-search/src/test/java/com/yahoo/search/searchers/test/QueryValidatorTestCase.java
+++ b/container-search/src/test/java/com/yahoo/search/searchers/test/QueryValidatorTestCase.java
@@ -18,7 +18,7 @@ import static org.junit.jupiter.api.Assertions.fail;
public class QueryValidatorTestCase {
@Test
- void testValidation() {
+ void testTensorsCannotBeSearchedForTerms() {
SearchDefinition sd = new SearchDefinition("test");
sd.addCommand("mytensor1", "type tensor(x[100]");
sd.addCommand("mytensor2", "type tensor<float>(x[100]");
@@ -27,24 +27,47 @@ public class QueryValidatorTestCase {
IndexFacts indexFacts = new IndexFacts(model);
Execution execution = new Execution(Execution.Context.createContextStub(indexFacts));
- new QueryValidator().search(new Query("?query=mystring:foo"), execution);
+ assertSucceeds("?query=mystring:foo", execution);
+ assertFails("Cannot search for terms in 'mytensor1': It is a tensor field",
+ "?query=mytensor1:foo", execution);
+ assertFails("Cannot search for terms in 'mytensor2': It is a tensor field",
+ "?query=mytensor2:foo", execution);
+ }
- try {
- new QueryValidator().search(new Query("?query=mytensor1:foo"), execution);
- fail("Expected validation error");
- }
- catch (IllegalArgumentException e) {
- // success
- assertEquals("Cannot search 'mytensor1': It is a tensor field", e.getMessage());
- }
+ @Test
+ void testPrefixRequiresAttribute() {
+ SearchDefinition sd = new SearchDefinition("test");
+ sd.addCommand("attributeOnly", "type string")
+ .addCommand("attribute");
+ sd.addCommand("indexOnly", "type string")
+ .addCommand("index");
+ sd.addCommand("attributeAndIndex", "type string")
+ .addCommand("attribute")
+ .addCommand("index");
+ IndexModel model = new IndexModel(sd);
+
+ IndexFacts indexFacts = new IndexFacts(model);
+ Execution execution = new Execution(Execution.Context.createContextStub(indexFacts));
+
+ assertSucceeds("?query=attributeOnly:foo*", execution);
+ assertFails("'indexOnly' is not an attribute field: Prefix matching is not supported",
+ "?query=indexOnly:foo*", execution);
+ assertFails("'attributeAndIndex' is an index field: Prefix matching is not supported even when it is also an attribute",
+ "?query=attributeAndIndex:foo*", execution);
+ }
+
+ private void assertSucceeds(String query, Execution execution) {
+ new QueryValidator().search(new Query(query), execution);
+ }
+ private void assertFails(String expectedError, String query, Execution execution) {
try {
- new QueryValidator().search(new Query("?query=mytensor2:foo"), execution);
- fail("Expected validation error");
+ new QueryValidator().search(new Query(query), execution);
+ fail("Expected validation error from " + query);
}
catch (IllegalArgumentException e) {
// success
- assertEquals("Cannot search 'mytensor2': It is a tensor field", e.getMessage());
+ assertEquals(expectedError, e.getMessage());
}
}