diff options
Diffstat (limited to 'container-search/src/main/java/com/yahoo')
12 files changed, 263 insertions, 69 deletions
diff --git a/container-search/src/main/java/com/yahoo/prelude/IndexModel.java b/container-search/src/main/java/com/yahoo/prelude/IndexModel.java index dd2bd1ee2f1..c83294efed7 100644 --- a/container-search/src/main/java/com/yahoo/prelude/IndexModel.java +++ b/container-search/src/main/java/com/yahoo/prelude/IndexModel.java @@ -3,6 +3,7 @@ package com.yahoo.prelude; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -28,6 +29,11 @@ public final class IndexModel { private Map<String, SearchDefinition> searchDefinitions; private SearchDefinition unionSearchDefinition; + /** Create an index model for a single search definition */ + public IndexModel(SearchDefinition searchDefinition) { + this(Collections.emptyMap(), Collections.singleton(searchDefinition)); + } + /** * Create an index model. */ diff --git a/container-search/src/main/java/com/yahoo/prelude/query/CompositeItem.java b/container-search/src/main/java/com/yahoo/prelude/query/CompositeItem.java index eee9949d831..b9e8d700cd6 100644 --- a/container-search/src/main/java/com/yahoo/prelude/query/CompositeItem.java +++ b/container-search/src/main/java/com/yahoo/prelude/query/CompositeItem.java @@ -9,6 +9,7 @@ import com.yahoo.search.query.QueryTree; import java.nio.ByteBuffer; import java.util.ArrayList; +import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.ListIterator; @@ -162,16 +163,19 @@ public abstract class CompositeItem extends Item { return removed; } - /** Returns the number of direct ancestors of this item */ + /** Returns the number of direct children of this item */ public int getItemCount() { return subitems.size(); } - /** Returns a modifiable list iterator */ + /** Returns a modifiable list iterator of the immediate children of this */ public ListIterator<Item> getItemIterator() { return new ListIteratorWrapper(this); } + /** Returns a read only list of the immediate children of this */ + public List<Item> items() { return Collections.unmodifiableList(subitems); } + public int encode(ByteBuffer buffer) { encodeThis(buffer); int itemCount = 1; diff --git a/container-search/src/main/java/com/yahoo/prelude/query/CompositeTaggableItem.java b/container-search/src/main/java/com/yahoo/prelude/query/CompositeTaggableItem.java index 7d9ae751930..b1912e4128d 100644 --- a/container-search/src/main/java/com/yahoo/prelude/query/CompositeTaggableItem.java +++ b/container-search/src/main/java/com/yahoo/prelude/query/CompositeTaggableItem.java @@ -68,4 +68,5 @@ public abstract class CompositeTaggableItem extends CompositeItem implements Tag public boolean hasUniqueID() { return super.hasUniqueID(); } + } diff --git a/container-search/src/main/java/com/yahoo/prelude/query/Item.java b/container-search/src/main/java/com/yahoo/prelude/query/Item.java index d8b5fc9451a..9d8ccce1b76 100644 --- a/container-search/src/main/java/com/yahoo/prelude/query/Item.java +++ b/container-search/src/main/java/com/yahoo/prelude/query/Item.java @@ -373,6 +373,7 @@ public abstract class Item implements Cloneable { protected abstract void appendBodyString(StringBuilder buffer); /** Returns a deep copy of this item */ + @Override public Item clone() { try { Item clone = (Item)super.clone(); @@ -389,6 +390,7 @@ public abstract class Item implements Cloneable { * Returns whether this item is of the same class and * contains the same state as the given item */ + @Override public boolean equals(Object object) { if (object == null) { return false; @@ -405,11 +407,11 @@ public abstract class Item implements Cloneable { if (this.weight != other.weight) { return false; } - // if (this.termIndex!=other.termIndex) return false; return true; } + @Override public int hashCode() { return weight * 29 + creator.code; } @@ -433,12 +435,7 @@ public abstract class Item implements Cloneable { this.label = label; } - /** - * Obtain the label for this item. This method will return null if - * no label has been set. - * - * @return label for this item - **/ + /** Returns the label for this item. This method will return null if no label has been set. */ public String getLabel() { return label; } diff --git a/container-search/src/main/java/com/yahoo/prelude/query/MarkerWordItem.java b/container-search/src/main/java/com/yahoo/prelude/query/MarkerWordItem.java index 9fa530eb5ea..4e7036e3481 100644 --- a/container-search/src/main/java/com/yahoo/prelude/query/MarkerWordItem.java +++ b/container-search/src/main/java/com/yahoo/prelude/query/MarkerWordItem.java @@ -9,32 +9,31 @@ import com.yahoo.prelude.query.textualrepresentation.Discloser; * Special words known by the index used for marking things. * The reserved word itself is not public, while a symbol representation is. * - * @author bratseth + * @author bratseth */ public class MarkerWordItem extends WordItem { - /** Creates a special word item which marks the start of a host name */ - public static WordItem createStartOfHost() { - return new MarkerWordItem("^", UrlTokenizer.TERM_STARTHOST); - } - - /** Creates a special word item which marks the end of a host name */ - public static WordItem createEndOfHost() { - return new MarkerWordItem("$", UrlTokenizer.TERM_ENDHOST); - } + private final String markerWord; - private String markerWord; + private final static String startAnchor = "^"; + private final static String endAnchor = "$"; - private MarkerWordItem(String publicSymbol, String markerWord) { - super(publicSymbol); + private MarkerWordItem(String publicSymbol, String markerWord, String indexName) { + super(publicSymbol, indexName); this.markerWord = markerWord; } + public boolean isStartAnchor() { return getWord().equals(startAnchor); } + + public boolean isEndAnchor() { return getWord().equals(endAnchor); } + /** Returns the marker word for encoding */ + @Override protected String getEncodedWord() { return markerWord; } + @Override public boolean equals(Object o) { if (!super.equals(o)) { return false; @@ -48,6 +47,7 @@ public class MarkerWordItem extends WordItem { return markerWord.equals(other.markerWord); } + @Override public int hashCode() { return super.hashCode() + 499 * markerWord.hashCode(); } @@ -57,4 +57,25 @@ public class MarkerWordItem extends WordItem { super.disclose(discloser); discloser.addProperty("markerWord", markerWord); } + + /** Creates a special word item which marks the start of a host name */ + public static MarkerWordItem createStartOfHost(String indexName) { + return new MarkerWordItem(startAnchor, UrlTokenizer.TERM_STARTHOST, indexName); + } + + /** Creates a special word item which marks the start of a host name, matching the default index */ + public static MarkerWordItem createStartOfHost() { + return createStartOfHost(""); + } + + /** Creates a special word item which marks the end of a host name */ + public static MarkerWordItem createEndOfHost(String indexName) { + return new MarkerWordItem(endAnchor, UrlTokenizer.TERM_ENDHOST, indexName); + } + + /** Creates a special word item which marks the end of a host name matching the default index */ + public static MarkerWordItem createEndOfHost() { + return createEndOfHost(""); + } + } diff --git a/container-search/src/main/java/com/yahoo/prelude/query/PhraseItem.java b/container-search/src/main/java/com/yahoo/prelude/query/PhraseItem.java index d7aec40bcf9..26da5eec7eb 100644 --- a/container-search/src/main/java/com/yahoo/prelude/query/PhraseItem.java +++ b/container-search/src/main/java/com/yahoo/prelude/query/PhraseItem.java @@ -32,10 +32,12 @@ public class PhraseItem extends CompositeIndexedItem { } } + @Override public ItemType getItemType() { return ItemType.PHRASE; } + @Override public String getName() { return "PHRASE"; } @@ -162,10 +164,12 @@ public class PhraseItem extends CompositeIndexedItem { return (BlockItem) getItem(index); } + @Override protected void encodeThis(ByteBuffer buffer) { super.encodeThis(buffer); // takes care of index bytes } + @Override public int encode(ByteBuffer buffer) { encodeThis(buffer); int itemCount = 1; @@ -186,13 +190,16 @@ public class PhraseItem extends CompositeIndexedItem { } /** Returns false, no parenthezes for phrases */ + @Override protected boolean shouldParenthize() { return false; } /** Phrase items uses a empty heading instead of "PHRASE " */ + @Override protected void appendHeadingString(StringBuilder buffer) { } + @Override protected void appendBodyString(StringBuilder buffer) { appendIndexString(buffer); diff --git a/container-search/src/main/java/com/yahoo/prelude/query/UriItem.java b/container-search/src/main/java/com/yahoo/prelude/query/UriItem.java new file mode 100644 index 00000000000..b8d7ea7aa11 --- /dev/null +++ b/container-search/src/main/java/com/yahoo/prelude/query/UriItem.java @@ -0,0 +1,81 @@ +// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.prelude.query; + +import java.util.ArrayList; +import java.util.List; +import java.util.ListIterator; +import java.util.stream.Collectors; + +/** + * An item matching a uri field. + * This is syntactic sugar for a phrase with anchor items. + * + * @author bratseth + */ +public class UriItem extends PhraseItem { + + // Fields here are auxiliary information not needed for semantics but used preserve a nice canonical form + private boolean startAnchorDefault = false; + private boolean endAnchorDefault = false; + private String sourceString; + + public UriItem() { + super(); + } + + public UriItem(String indexName) { + super(indexName); + } + + /** + * Adds a start anchor as the *current* first item. + * This does not enforce that the items stays at the start if further items are added. + * */ + public void addStartAnchorItem() { + addItem(0, MarkerWordItem.createStartOfHost(getIndexName())); + } + + /** + * Adds an end anchor as the *current* last item. + * This does not enforce that the items stays at the end if further items are added. + */ + public void addEndAnchorItem() { + addItem(MarkerWordItem.createEndOfHost(getIndexName())); + } + + /** Returns whether the first item of this is a start anchor */ + public boolean hasStartAnchor() { + return getItem(0) instanceof MarkerWordItem + && ((MarkerWordItem)getItem(0)).isStartAnchor(); + } + + /** Returns whether the last item of this is an end anchor */ + public boolean hasEndAnchor() { + return getItem(getItemCount()-1) instanceof MarkerWordItem + && ((MarkerWordItem)getItem(getItemCount()-1)).isEndAnchor(); + } + + public boolean isStartAnchorDefault() { return startAnchorDefault; } + public void setStartAnchorDefault(boolean startAnchorDefault) { this.startAnchorDefault = startAnchorDefault; } + + public boolean isEndAnchorDefault() { return endAnchorDefault; } + public void setEndAnchorDefault(boolean endAnchorDefault) { this.endAnchorDefault = endAnchorDefault; } + + public void setSourceString(String sourceString) { this.sourceString = sourceString; } + + /** + * Returns the canonical form of the tokens of this: Either the source string, or if none + * each token except the start and end anchor separated by space + */ + public String getArgumentString() { + if (sourceString != null) return sourceString; + + List<Item> items = new ArrayList<>(items()); + if (hasStartAnchor()) + items.remove(0); + if (hasEndAnchor()) + items.remove(items.size() - 1); + return items.stream().map(item -> ((WordItem)item).getWord()).collect(Collectors.joining(" ")); + } + +} diff --git a/container-search/src/main/java/com/yahoo/prelude/query/parser/AbstractParser.java b/container-search/src/main/java/com/yahoo/prelude/query/parser/AbstractParser.java index 9652caf8d58..1923fdbc50d 100644 --- a/container-search/src/main/java/com/yahoo/prelude/query/parser/AbstractParser.java +++ b/container-search/src/main/java/com/yahoo/prelude/query/parser/AbstractParser.java @@ -87,14 +87,10 @@ public abstract class AbstractParser implements CustomParser { } /** - * Returns whether we are in a mode which allows explicit anchoring - * markers, ^ and $ - * - * @return True if we are doing explicit anchoring. + * Returns whether we are in a mode which allows explicit anchoring markers, ^ and $ */ - public boolean explicitAnchoring() { - return site; - } + public boolean explicitAnchoring() { return site; } + } /** diff --git a/container-search/src/main/java/com/yahoo/prelude/query/parser/StructuredParser.java b/container-search/src/main/java/com/yahoo/prelude/query/parser/StructuredParser.java index 8ecd4d8f81c..8c77b3d2130 100644 --- a/container-search/src/main/java/com/yahoo/prelude/query/parser/StructuredParser.java +++ b/container-search/src/main/java/com/yahoo/prelude/query/parser/StructuredParser.java @@ -557,7 +557,16 @@ abstract class StructuredParser extends AbstractParser { if (phrase != null) { phrase.addItem(word); } else if (firstWord != null) { - phrase = new PhraseItem(); + if (submodes.site || submodes.url) { + UriItem uriItem = new UriItem(); + if (submodes.site) + uriItem.setEndAnchorDefault(true); + phrase = uriItem; + } + else { + phrase = new PhraseItem(); + } + if (quoted || submodes.site || submodes.url) { phrase.setExplicit(true); } diff --git a/container-search/src/main/java/com/yahoo/search/yql/MinimalQueryInserter.java b/container-search/src/main/java/com/yahoo/search/yql/MinimalQueryInserter.java index 91992c3e29e..940d6d51975 100644 --- a/container-search/src/main/java/com/yahoo/search/yql/MinimalQueryInserter.java +++ b/container-search/src/main/java/com/yahoo/search/yql/MinimalQueryInserter.java @@ -38,9 +38,8 @@ public class MinimalQueryInserter extends Searcher { @Override public Result search(Query query, Execution execution) { - if (query.properties().get(YQL) == null) { - return execution.search(query); - } + if (query.properties().get(YQL) == null) return execution.search(query); + ParserEnvironment env = ParserEnvironment.fromExecutionContext(execution.context()); YqlParser parser = (YqlParser) ParserFactory.newInstance(Query.Type.YQL, env); parser.setQueryParser(false); diff --git a/container-search/src/main/java/com/yahoo/search/yql/VespaSerializer.java b/container-search/src/main/java/com/yahoo/search/yql/VespaSerializer.java index c27c047c899..896d1cf5c9c 100644 --- a/container-search/src/main/java/com/yahoo/search/yql/VespaSerializer.java +++ b/container-search/src/main/java/com/yahoo/search/yql/VespaSerializer.java @@ -13,6 +13,7 @@ import static com.yahoo.search.yql.YqlParser.CONNECTION_WEIGHT; import static com.yahoo.search.yql.YqlParser.CONNECTIVITY; import static com.yahoo.search.yql.YqlParser.DISTANCE; import static com.yahoo.search.yql.YqlParser.DOT_PRODUCT; +import static com.yahoo.search.yql.YqlParser.END_ANCHOR; import static com.yahoo.search.yql.YqlParser.EQUIV; import static com.yahoo.search.yql.YqlParser.FILTER; import static com.yahoo.search.yql.YqlParser.HIT_LIMIT; @@ -33,12 +34,14 @@ import static com.yahoo.search.yql.YqlParser.RANKED; import static com.yahoo.search.yql.YqlParser.SAME_ELEMENT; import static com.yahoo.search.yql.YqlParser.SCORE_THRESHOLD; import static com.yahoo.search.yql.YqlParser.SIGNIFICANCE; +import static com.yahoo.search.yql.YqlParser.START_ANCHOR; import static com.yahoo.search.yql.YqlParser.STEM; import static com.yahoo.search.yql.YqlParser.SUBSTRING; import static com.yahoo.search.yql.YqlParser.SUFFIX; import static com.yahoo.search.yql.YqlParser.TARGET_NUM_HITS; import static com.yahoo.search.yql.YqlParser.THRESHOLD_BOOST_FACTOR; import static com.yahoo.search.yql.YqlParser.UNIQUE_ID; +import static com.yahoo.search.yql.YqlParser.URI; import static com.yahoo.search.yql.YqlParser.USE_POSITION_DATA; import static com.yahoo.search.yql.YqlParser.WAND; import static com.yahoo.search.yql.YqlParser.WEAK_AND; @@ -89,6 +92,7 @@ import com.yahoo.prelude.query.SuffixItem; import com.yahoo.prelude.query.TaggableItem; import com.yahoo.prelude.query.ToolBox; import com.yahoo.prelude.query.ToolBox.QueryVisitor; +import com.yahoo.prelude.query.UriItem; import com.yahoo.prelude.query.WandItem; import com.yahoo.prelude.query.WeakAndItem; import com.yahoo.prelude.query.WeightedSetItem; @@ -271,6 +275,47 @@ public class VespaSerializer { } + private static class UriSerializer extends Serializer { + @Override + void onExit(StringBuilder destination, Item item) { } + + @Override + boolean serialize(StringBuilder destination, Item item) { + UriItem uriItem = (UriItem) item; + String annotations = uriAnnotations(uriItem); + + destination.append(uriItem.getIndexName()).append(" contains "); + if (annotations.length() > 0) + destination.append('(').append(annotations); + destination.append(URI).append("(\""); + destination.append(uriItem.getArgumentString()); + destination.append("\")"); + if (annotations.length() > 0) + destination.append(')'); + return false; + } + + static String uriAnnotations(UriItem item) { + if (item.hasStartAnchor() == item.isStartAnchorDefault() && + item.hasEndAnchor() == item.isEndAnchorDefault()) + return ""; + + StringBuilder b = new StringBuilder(); + b.append("[{"); + if (item.hasStartAnchor() != item.isStartAnchorDefault()) { + b.append("\"" + START_ANCHOR + "\": " + item.hasStartAnchor()); + } + if (item.hasEndAnchor() != item.isEndAnchorDefault()) { + if (b.length() > 2) + b.append(", "); + b.append("\"" + END_ANCHOR + "\": " + item.hasEndAnchor()); + } + b.append("}]"); + return b.toString(); + } + + } + private static class NotSerializer extends Serializer { @Override @@ -1111,6 +1156,7 @@ public class VespaSerializer { dispatchBuilder.put(WeightedSetItem.class, new WeightedSetSerializer()); dispatchBuilder.put(WordItem.class, new WordSerializer()); dispatchBuilder.put(RegExpItem.class, new RegExpSerializer()); + dispatchBuilder.put(UriItem.class, new UriSerializer()); dispatch = ImmutableMap.copyOf(dispatchBuilder); } diff --git a/container-search/src/main/java/com/yahoo/search/yql/YqlParser.java b/container-search/src/main/java/com/yahoo/search/yql/YqlParser.java index 23dcaba0b3b..097ed86bd9e 100644 --- a/container-search/src/main/java/com/yahoo/search/yql/YqlParser.java +++ b/container-search/src/main/java/com/yahoo/search/yql/YqlParser.java @@ -55,6 +55,7 @@ import com.yahoo.prelude.query.TaggableItem; import com.yahoo.prelude.query.TermItem; import com.yahoo.prelude.query.ToolBox; import com.yahoo.prelude.query.ToolBox.QueryVisitor; +import com.yahoo.prelude.query.UriItem; import com.yahoo.prelude.query.WandItem; import com.yahoo.prelude.query.WeakAndItem; import com.yahoo.prelude.query.WeightedSetItem; @@ -91,7 +92,6 @@ import edu.umd.cs.findbugs.annotations.NonNull; * @author Stian Kristoffersen * @author Simon Thoresen Hult */ -@Beta public class YqlParser implements Parser { private static final String DESCENDING_HITS_ORDER = "descending"; @@ -127,6 +127,8 @@ public class YqlParser implements Parser { private static final String USER_INPUT = "userInput"; private static final String USER_QUERY = "userQuery"; private static final String NON_EMPTY = "nonEmpty"; + public static final String START_ANCHOR = "startAnchor"; + public static final String END_ANCHOR = "endAnchor"; public static final String SORTING_FUNCTION = "function"; public static final String SORTING_LOCALE = "locale"; @@ -176,6 +178,7 @@ public class YqlParser implements Parser { static final String WEAK_AND = "weakAnd"; static final String WEIGHTED_SET = "weightedSet"; static final String WEIGHT = "weight"; + static final String URI = "uri"; private final IndexFacts indexFacts; private final List<ConnectedItem> connectedItems = new ArrayList<>(); @@ -316,7 +319,6 @@ public class YqlParser implements Parser { } } - @NonNull private Item convertExpression(OperatorNode<ExpressionOperator> ast) { try { annotationStack.addFirst(ast); @@ -354,7 +356,6 @@ public class YqlParser implements Parser { } } - @NonNull private Item buildFunctionCall(OperatorNode<ExpressionOperator> ast) { List<String> names = ast.getArgument(0); Preconditions.checkArgument(names.size() == 1, "Expected 1 name, got %s.", names.size()); @@ -481,8 +482,8 @@ public class YqlParser implements Parser { WandItem out = new WandItem(getIndex(args.get(0)), getAnnotation(ast, TARGET_NUM_HITS, Integer.class, DEFAULT_TARGET_NUM_HITS, "desired number of hits to accumulate in wand")); - Double scoreThreshold = getAnnotation(ast, SCORE_THRESHOLD, - Double.class, null, "min score for hit inclusion"); + Double scoreThreshold = getAnnotation(ast, SCORE_THRESHOLD, Double.class, null, + "min score for hit inclusion"); if (scoreThreshold != null) { out.setScoreThreshold(scoreThreshold); } @@ -1056,7 +1057,7 @@ public class YqlParser implements Parser { private Item buildTermSearch(OperatorNode<ExpressionOperator> ast) { assertHasOperator(ast, ExpressionOperator.CONTAINS); - String field = getIndex(ast.<OperatorNode<ExpressionOperator>>getArgument(0)); + String field = getIndex(ast.getArgument(0)); if (userQuery != null && indexFactsSession.getIndex(field).isAttribute()) { userQuery.trace("Field '" + field + "' is an attribute, 'contains' will only match exactly", 1); } @@ -1065,11 +1066,11 @@ public class YqlParser implements Parser { private Item buildRegExpSearch(OperatorNode<ExpressionOperator> ast) { assertHasOperator(ast, ExpressionOperator.MATCHES); - String field = getIndex(ast.<OperatorNode<ExpressionOperator>>getArgument(0)); + String field = getIndex(ast.getArgument(0)); if (userQuery != null && !indexFactsSession.getIndex(field).isAttribute()) { userQuery.trace("Field '" + field + "' is indexed, non-literal regular expressions will not be matched", 1); } - OperatorNode<ExpressionOperator> ast1 = ast.<OperatorNode<ExpressionOperator>> getArgument(1); + OperatorNode<ExpressionOperator> ast1 = ast.getArgument(1); String wordData = getStringContents(ast1); RegExpItem regExp = new RegExpItem(field, true, wordData); return leafStyleSettings(ast1, regExp); @@ -1079,7 +1080,7 @@ public class YqlParser implements Parser { assertHasOperator(spec, ExpressionOperator.CALL); assertHasFunctionName(spec, RANGE); - IntItem range = instantiateRangeItem(spec.<List<OperatorNode<ExpressionOperator>>> getArgument(1), spec); + IntItem range = instantiateRangeItem(spec.getArgument(1), spec); return leafStyleSettings(spec, range); } @@ -1101,16 +1102,15 @@ public class YqlParser implements Parser { } } - private IntItem instantiateRangeItem( - List<OperatorNode<ExpressionOperator>> args, - OperatorNode<ExpressionOperator> spec) { + private IntItem instantiateRangeItem(List<OperatorNode<ExpressionOperator>> args, + OperatorNode<ExpressionOperator> spec) { Preconditions.checkArgument(args.size() == 3, "Expected 3 arguments, got %s.", args.size()); Number lowerArg = getBound(args.get(1)); Number upperArg = getBound(args.get(2)); String bounds = getAnnotation(spec, BOUNDS, String.class, null, - "whether bounds should be open or closed"); + "whether bounds should be open or closed"); // TODO: add support for implicit transforms if (bounds == null) { return new RangeItem(lowerArg, upperArg, getIndex(args.get(0))); @@ -1127,8 +1127,7 @@ public class YqlParser implements Parser { from = new Limit(lowerArg, true); to = new Limit(upperArg, false); } else { - throw newUnexpectedArgumentException(bounds, BOUNDS_OPEN, - BOUNDS_LEFT_OPEN, BOUNDS_RIGHT_OPEN); + throw newUnexpectedArgumentException(bounds, BOUNDS_OPEN, BOUNDS_LEFT_OPEN, BOUNDS_RIGHT_OPEN); } return new IntItem(from, to, getIndex(args.get(0))); } @@ -1179,11 +1178,36 @@ public class YqlParser implements Parser { return instantiateEquivItem(field, ast); case ALTERNATIVES: return instantiateWordAlternativesItem(field, ast); + case URI: + return instantiateUriItem(field, ast); default: - throw newUnexpectedArgumentException(names.get(0), EQUIV, NEAR, ONEAR, PHRASE, SAME_ELEMENT); + throw newUnexpectedArgumentException(names.get(0), EQUIV, NEAR, ONEAR, PHRASE, SAME_ELEMENT, URI); } } + private Item instantiateEquivItem(String field, OperatorNode<ExpressionOperator> ast) { + List<OperatorNode<ExpressionOperator>> args = ast.getArgument(1); + Preconditions.checkArgument(args.size() >= 2, "Expected 2 or more arguments, got %s.", args.size()); + + EquivItem equiv = new EquivItem(); + equiv.setIndexName(field); + for (OperatorNode<ExpressionOperator> arg : args) { + switch (arg.getOperator()) { + case LITERAL: + equiv.addItem(instantiateWordItem(field, arg, equiv.getClass())); + break; + case CALL: + assertHasFunctionName(arg, PHRASE); + equiv.addItem(instantiatePhraseItem(field, arg)); + break; + default: + throw newUnexpectedArgumentException(arg.getOperator(), + ExpressionOperator.CALL, ExpressionOperator.LITERAL); + } + } + return leafStyleSettings(ast, equiv); + } + private Item instantiateWordAlternativesItem(String field, OperatorNode<ExpressionOperator> ast) { List<OperatorNode<ExpressionOperator>> args = ast.getArgument(1); Preconditions.checkArgument(args.size() >= 1, "Expected 1 or more arguments, got %s.", args.size()); @@ -1208,27 +1232,30 @@ public class YqlParser implements Parser { return leafStyleSettings(ast, new WordAlternativesItem(field, isFromQuery, origin, terms)); } - private Item instantiateEquivItem(String field, OperatorNode<ExpressionOperator> ast) { - List<OperatorNode<ExpressionOperator>> args = ast.getArgument(1); - Preconditions.checkArgument(args.size() >= 2, "Expected 2 or more arguments, got %s.", args.size()); + private UriItem instantiateUriItem(String field, OperatorNode<ExpressionOperator> ast) { + UriItem uriItem = new UriItem(field); - EquivItem equiv = new EquivItem(); - equiv.setIndexName(field); - for (OperatorNode<ExpressionOperator> arg : args) { - switch (arg.getOperator()) { - case LITERAL: - equiv.addItem(instantiateWordItem(field, arg, equiv.getClass())); - break; - case CALL: - assertHasFunctionName(arg, PHRASE); - equiv.addItem(instantiatePhraseItem(field, arg)); - break; - default: - throw newUnexpectedArgumentException(arg.getOperator(), - ExpressionOperator.CALL, ExpressionOperator.LITERAL); - } - } - return leafStyleSettings(ast, equiv); + boolean startAnchorDefault = false; + boolean endAnchorDefault = indexFactsSession.getIndex(field).isHostIndex(); + + if (getAnnotation(ast, START_ANCHOR, Boolean.class, startAnchorDefault, + "whether uri matching should be anchored to the start")) + uriItem.addStartAnchorItem(); + + String uriString = ast.<List<OperatorNode<ExpressionOperator>>> getArgument(1).get(0).getArgument(0); + for (String token : segmenter.segment(uriString, Language.ENGLISH)) + uriItem.addItem(new WordItem(token, field, true)); + + if (getAnnotation(ast, END_ANCHOR, Boolean.class, endAnchorDefault, + "whether uri matching should be anchored to the end")) + uriItem.addEndAnchorItem(); + + // Aux info to preserve minimal and expected canonical form + uriItem.setStartAnchorDefault(startAnchorDefault); + uriItem.setEndAnchorDefault(endAnchorDefault); + uriItem.setSourceString(uriString); + + return uriItem; } private Item instantiateWordItem(String field, OperatorNode<ExpressionOperator> ast, Class<?> parent) { |