summaryrefslogtreecommitdiffstats
path: root/container-search/src/main
diff options
context:
space:
mode:
authorHenning Baldersheim <balder@yahoo-inc.com>2021-09-30 17:09:59 +0200
committerGitHub <noreply@github.com>2021-09-30 17:09:59 +0200
commitc75ff9433d7d8e7d1a0186592b57597ae632581c (patch)
tree9bed7a0bcca7752796a6c0bf2ead31694fcb4037 /container-search/src/main
parent041de033862d7e9ee8ff16770cd5cc63a11b5d16 (diff)
parent253fe67dfae65ab11ed008590403ffbe10fab02e (diff)
Merge branch 'master' into balder/do-not-depend-on-clusterinfo
Diffstat (limited to 'container-search/src/main')
-rw-r--r--container-search/src/main/antlr4/com/yahoo/search/yql/yqlplus.g4664
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/Index.java118
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/IndexFacts.java7
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/IndexModel.java1
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/Location.java174
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/SearchDefinition.java8
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/cluster/ClusterSearcher.java24
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/fastsearch/Base64DataField.java25
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/fastsearch/DataField.java2
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/fastsearch/DocsumField.java5
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/fastsearch/FS4ResourcePool.java62
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/fastsearch/FastHit.java9
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/fastsearch/FastSearcher.java6
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/fastsearch/GroupingListHit.java1
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/fastsearch/VespaBackEndSearcher.java21
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/hitfield/JSONString.java4
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/hitfield/RawBase64.java18
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/BoolItem.java4
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/CompositeItem.java2
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/CompositeTaggableItem.java4
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/DotProductItem.java3
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/FalseItem.java2
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/GeoLocationItem.java120
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/HasIndexItem.java5
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/IndexedItem.java2
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/IntItem.java2
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/Item.java9
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/ItemHelper.java6
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/NearestNeighborItem.java47
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/PhraseSegmentItem.java15
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/QueryCanonicalizer.java11
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/QueryException.java6
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/RegExpItem.java3
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/SameElementItem.java1
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/SegmentItem.java4
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/SegmentingRule.java5
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/SimpleTaggableItem.java4
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/SuffixItem.java3
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/TaggableSegmentItem.java4
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/TermItem.java30
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/TermType.java5
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/ToolBox.java3
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/WandItem.java24
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/WeakAndItem.java98
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/WeightedSetItem.java14
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/WordAlternativesItem.java44
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/parser/AbstractParser.java9
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/parser/AdvancedParser.java12
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/parser/AllParser.java5
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/parser/AnyParser.java33
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/parser/PhraseParser.java5
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/parser/SimpleParser.java51
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/parser/SpecialTokenRegistry.java137
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/parser/SpecialTokens.java168
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/parser/StructuredParser.java48
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/parser/Tokenizer.java66
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/querytransform/CJKSearcher.java30
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/querytransform/NoRankingSearcher.java3
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/querytransform/PhraseMatcher.java6
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/querytransform/QueryRewrite.java1
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/querytransform/StemmingSearcher.java19
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/searcher/BlendingSearcher.java3
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/searcher/FieldCollapsingSearcher.java3
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/searcher/FillSearcher.java1
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/searcher/JuniperSearcher.java21
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/searcher/PosSearcher.java6
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/searcher/ValidatePredicateSearcher.java42
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/semantics/RuleBase.java101
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/semantics/RuleBaseException.java3
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/semantics/RuleImporter.java120
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/semantics/engine/Evaluation.java116
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/semantics/engine/Match.java32
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/semantics/engine/ReferencedMatches.java14
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/semantics/engine/RuleEvaluation.java93
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/semantics/rule/LiteralPhraseProduction.java18
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/semantics/rule/LiteralTermProduction.java21
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/semantics/rule/Production.java2
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/semantics/rule/ProductionList.java24
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/semantics/rule/ReferenceTermProduction.java34
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/semantics/rule/ReplacingProductionRule.java10
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/semantics/rule/TermProduction.java18
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/statistics/StatisticsSearcher.java131
-rw-r--r--container-search/src/main/java/com/yahoo/search/Query.java126
-rw-r--r--container-search/src/main/java/com/yahoo/search/Result.java6
-rw-r--r--container-search/src/main/java/com/yahoo/search/Searcher.java1
-rw-r--r--container-search/src/main/java/com/yahoo/search/cluster/BaseNodeMonitor.java2
-rw-r--r--container-search/src/main/java/com/yahoo/search/cluster/ClusterMonitor.java6
-rw-r--r--container-search/src/main/java/com/yahoo/search/cluster/ClusterSearcher.java27
-rw-r--r--container-search/src/main/java/com/yahoo/search/cluster/Hasher.java1
-rw-r--r--container-search/src/main/java/com/yahoo/search/cluster/MonitorConfiguration.java77
-rw-r--r--container-search/src/main/java/com/yahoo/search/cluster/NodeManager.java5
-rw-r--r--container-search/src/main/java/com/yahoo/search/cluster/TrafficNodeMonitor.java14
-rw-r--r--container-search/src/main/java/com/yahoo/search/dispatch/CloseableInvoker.java2
-rw-r--r--container-search/src/main/java/com/yahoo/search/dispatch/Dispatcher.java61
-rw-r--r--container-search/src/main/java/com/yahoo/search/dispatch/FillInvoker.java4
-rw-r--r--container-search/src/main/java/com/yahoo/search/dispatch/InterleavedSearchInvoker.java75
-rw-r--r--container-search/src/main/java/com/yahoo/search/dispatch/InvokerFactory.java27
-rw-r--r--container-search/src/main/java/com/yahoo/search/dispatch/InvokerResult.java7
-rw-r--r--container-search/src/main/java/com/yahoo/search/dispatch/LeanHit.java16
-rw-r--r--container-search/src/main/java/com/yahoo/search/dispatch/LoadBalancer.java9
-rw-r--r--container-search/src/main/java/com/yahoo/search/dispatch/ResponseMonitor.java2
-rw-r--r--container-search/src/main/java/com/yahoo/search/dispatch/SearchErrorInvoker.java6
-rw-r--r--container-search/src/main/java/com/yahoo/search/dispatch/SearchInvoker.java11
-rw-r--r--container-search/src/main/java/com/yahoo/search/dispatch/SearchPath.java129
-rw-r--r--container-search/src/main/java/com/yahoo/search/dispatch/TopKEstimator.java79
-rw-r--r--container-search/src/main/java/com/yahoo/search/dispatch/rpc/Client.java3
-rw-r--r--container-search/src/main/java/com/yahoo/search/dispatch/rpc/ProtobufSerialization.java10
-rw-r--r--container-search/src/main/java/com/yahoo/search/dispatch/rpc/RpcClient.java19
-rw-r--r--container-search/src/main/java/com/yahoo/search/dispatch/rpc/RpcFillInvoker.java2
-rw-r--r--container-search/src/main/java/com/yahoo/search/dispatch/rpc/RpcPing.java2
-rw-r--r--container-search/src/main/java/com/yahoo/search/dispatch/rpc/RpcPingFactory.java5
-rw-r--r--container-search/src/main/java/com/yahoo/search/dispatch/rpc/RpcProtobufFillInvoker.java1
-rw-r--r--container-search/src/main/java/com/yahoo/search/dispatch/rpc/RpcResourcePool.java9
-rw-r--r--container-search/src/main/java/com/yahoo/search/dispatch/rpc/RpcSearchInvoker.java39
-rw-r--r--container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/Group.java60
-rw-r--r--container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/Node.java13
-rw-r--r--container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/PingFactory.java4
-rw-r--r--container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/Pinger.java2
-rw-r--r--container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/PongHandler.java3
-rw-r--r--container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/SearchCluster.java167
-rw-r--r--container-search/src/main/java/com/yahoo/search/federation/FederationResult.java4
-rw-r--r--container-search/src/main/java/com/yahoo/search/federation/FederationSearcher.java23
-rw-r--r--container-search/src/main/java/com/yahoo/search/federation/sourceref/SearchChainInvocationSpec.java2
-rw-r--r--container-search/src/main/java/com/yahoo/search/federation/sourceref/SearchChainResolver.java25
-rw-r--r--container-search/src/main/java/com/yahoo/search/federation/sourceref/SingleTarget.java2
-rw-r--r--container-search/src/main/java/com/yahoo/search/federation/sourceref/SourceRefResolver.java21
-rw-r--r--container-search/src/main/java/com/yahoo/search/federation/sourceref/Target.java2
-rw-r--r--container-search/src/main/java/com/yahoo/search/federation/sourceref/UnresolvedProviderException.java4
-rw-r--r--container-search/src/main/java/com/yahoo/search/federation/sourceref/UnresolvedSourceRefException.java4
-rw-r--r--container-search/src/main/java/com/yahoo/search/grouping/Continuation.java6
-rw-r--r--container-search/src/main/java/com/yahoo/search/grouping/GroupingQueryParser.java32
-rw-r--r--container-search/src/main/java/com/yahoo/search/grouping/GroupingRequest.java5
-rw-r--r--container-search/src/main/java/com/yahoo/search/grouping/GroupingValidator.java13
-rw-r--r--container-search/src/main/java/com/yahoo/search/grouping/UniqueGroupingSearcher.java4
-rw-r--r--container-search/src/main/java/com/yahoo/search/grouping/request/AddFunction.java6
-rw-r--r--container-search/src/main/java/com/yahoo/search/grouping/request/BucketResolver.java6
-rw-r--r--container-search/src/main/java/com/yahoo/search/grouping/request/GroupingOperation.java94
-rw-r--r--container-search/src/main/java/com/yahoo/search/grouping/request/MinFunction.java9
-rw-r--r--container-search/src/main/java/com/yahoo/search/grouping/vespa/GroupingExecutor.java10
-rw-r--r--container-search/src/main/java/com/yahoo/search/grouping/vespa/IntegerDecoder.java5
-rw-r--r--container-search/src/main/java/com/yahoo/search/handler/HttpSearchResponse.java50
-rw-r--r--container-search/src/main/java/com/yahoo/search/handler/SearchHandler.java227
-rw-r--r--container-search/src/main/java/com/yahoo/search/handler/SearchResponse.java6
-rw-r--r--container-search/src/main/java/com/yahoo/search/pagetemplates/PageTemplateSearcher.java72
-rw-r--r--container-search/src/main/java/com/yahoo/search/pagetemplates/PlaceholderMappingVisitor.java10
-rw-r--r--container-search/src/main/java/com/yahoo/search/pagetemplates/PlaceholderReferenceCreatingVisitor.java13
-rw-r--r--container-search/src/main/java/com/yahoo/search/pagetemplates/config/PageTemplateXMLReader.java62
-rw-r--r--container-search/src/main/java/com/yahoo/search/pagetemplates/engine/Organizer.java10
-rw-r--r--container-search/src/main/java/com/yahoo/search/pagetemplates/engine/Resolution.java6
-rw-r--r--container-search/src/main/java/com/yahoo/search/pagetemplates/result/PageTemplatesXmlRenderer.java6
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/Model.java13
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/ParameterParser.java15
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/Presentation.java10
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/Properties.java6
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/Ranking.java19
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/Select.java21
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/SelectParser.java250
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/Sorting.java9
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/context/QueryContext.java4
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/parser/ParserEnvironment.java5
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/profile/AllReferencesQueryProfileVisitor.java46
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/profile/AllTypesQueryProfileVisitor.java57
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/profile/AllUnoverridableQueryProfileVisitor.java54
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/profile/AllValuesQueryProfileVisitor.java32
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/profile/BackedOverridableQueryProfile.java30
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/profile/CompoundNameChildCache.java24
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/profile/DimensionBinding.java172
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/profile/DimensionValues.java10
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/profile/ModelObjectMap.java3
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/profile/PrefixQueryProfileVisitor.java26
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/profile/QueryProfile.java114
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/profile/QueryProfileCompiler.java153
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/profile/QueryProfileProperties.java103
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/profile/QueryProfileRegistry.java1
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/profile/QueryProfileVariant.java35
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/profile/QueryProfileVariants.java40
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/profile/compiled/Binding.java59
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/profile/compiled/CompiledQueryProfile.java29
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/profile/compiled/CompiledQueryProfileRegistry.java18
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/profile/compiled/DimensionalMap.java32
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/profile/compiled/DimensionalValue.java179
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/profile/compiled/ValueWithSource.java70
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/profile/config/QueryProfileConfigurer.java23
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/profile/config/QueryProfileXMLReader.java19
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/profile/types/ConversionContext.java40
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/profile/types/FieldDescription.java7
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/profile/types/FieldType.java5
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/profile/types/PrimitiveFieldType.java3
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/profile/types/QueryFieldType.java3
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/profile/types/QueryProfileFieldType.java5
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/profile/types/QueryProfileType.java10
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/profile/types/TensorFieldType.java21
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/properties/PropertyAliases.java2
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/properties/PropertyMap.java5
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/properties/QueryProperties.java112
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/ranking/MatchPhase.java4
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/ranking/Matching.java3
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/ranking/SoftTimeout.java5
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/rewrite/RewriterUtils.java6
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/rewrite/rewriters/GenericExpansionRewriter.java5
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/textserialize/item/ItemContext.java4
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/textserialize/item/TermConverter.java3
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/textserialize/item/TypeCheck.java2
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/textserialize/serializer/Serializer.java2
-rw-r--r--container-search/src/main/java/com/yahoo/search/querytransform/BooleanAttributeParser.java4
-rw-r--r--container-search/src/main/java/com/yahoo/search/querytransform/BooleanSearcher.java19
-rw-r--r--container-search/src/main/java/com/yahoo/search/querytransform/NGramSearcher.java12
-rw-r--r--container-search/src/main/java/com/yahoo/search/querytransform/VespaLowercasingSearcher.java6
-rw-r--r--container-search/src/main/java/com/yahoo/search/querytransform/WandSearcher.java83
-rw-r--r--container-search/src/main/java/com/yahoo/search/querytransform/WeakAndReplacementSearcher.java68
-rw-r--r--container-search/src/main/java/com/yahoo/search/rendering/JsonRenderer.java68
-rw-r--r--container-search/src/main/java/com/yahoo/search/rendering/RendererRegistry.java5
-rw-r--r--container-search/src/main/java/com/yahoo/search/rendering/SyncDefaultRenderer.java4
-rw-r--r--container-search/src/main/java/com/yahoo/search/rendering/XmlRenderer.java4
-rw-r--r--container-search/src/main/java/com/yahoo/search/result/FeatureData.java52
-rw-r--r--container-search/src/main/java/com/yahoo/search/result/Hit.java12
-rw-r--r--container-search/src/main/java/com/yahoo/search/searchchain/Execution.java7
-rw-r--r--container-search/src/main/java/com/yahoo/search/searchchain/ExecutionFactory.java2
-rw-r--r--container-search/src/main/java/com/yahoo/search/searchchain/PhaseNames.java1
-rw-r--r--container-search/src/main/java/com/yahoo/search/searchchain/model/VespaSearchers.java11
-rw-r--r--container-search/src/main/java/com/yahoo/search/searchchain/model/federation/FederationSearcherModel.java30
-rw-r--r--container-search/src/main/java/com/yahoo/search/searchchain/model/federation/LocalProviderSpec.java13
-rw-r--r--container-search/src/main/java/com/yahoo/search/searchchain/testutil/DocumentSourceSearcher.java14
-rw-r--r--container-search/src/main/java/com/yahoo/search/searchers/InputCheckingSearcher.java29
-rw-r--r--container-search/src/main/java/com/yahoo/search/searchers/QueryValidator.java59
-rw-r--r--container-search/src/main/java/com/yahoo/search/searchers/ValidateNearestNeighborSearcher.java104
-rw-r--r--container-search/src/main/java/com/yahoo/search/statistics/PeakQpsSearcher.java4
-rw-r--r--container-search/src/main/java/com/yahoo/search/statistics/TimingSearcher.java6
-rw-r--r--container-search/src/main/java/com/yahoo/search/yql/FieldFiller.java3
-rw-r--r--container-search/src/main/java/com/yahoo/search/yql/MinimalQueryInserter.java39
-rw-r--r--container-search/src/main/java/com/yahoo/search/yql/NullItemException.java5
-rw-r--r--container-search/src/main/java/com/yahoo/search/yql/OperatorNode.java4
-rw-r--r--container-search/src/main/java/com/yahoo/search/yql/ProgramCompileException.java25
-rw-r--r--container-search/src/main/java/com/yahoo/search/yql/ProgramParser.java1259
-rw-r--r--container-search/src/main/java/com/yahoo/search/yql/VespaSerializer.java56
-rw-r--r--container-search/src/main/java/com/yahoo/search/yql/YqlParser.java274
-rw-r--r--container-search/src/main/java/com/yahoo/text/interpretation/Annotations.java15
-rw-r--r--container-search/src/main/java/com/yahoo/text/interpretation/Interpretation.java50
-rw-r--r--container-search/src/main/java/com/yahoo/text/interpretation/Modification.java13
-rw-r--r--container-search/src/main/java/com/yahoo/text/interpretation/Span.java45
-rw-r--r--container-search/src/main/java/com/yahoo/vespa/streamingvisitors/TracingOptions.java12
-rw-r--r--container-search/src/main/java/com/yahoo/vespa/streamingvisitors/VdsStreamingSearcher.java56
-rw-r--r--container-search/src/main/java/com/yahoo/vespa/streamingvisitors/VdsVisitor.java105
-rw-r--r--container-search/src/main/java/com/yahoo/vespa/streamingvisitors/Visitor.java2
-rw-r--r--container-search/src/main/java/com/yahoo/vespa/streamingvisitors/VisitorFactory.java2
-rw-r--r--container-search/src/main/java/com/yahoo/vespa/streamingvisitors/tracing/LoggingTraceExporter.java4
-rw-r--r--container-search/src/main/javacc/com/yahoo/prelude/semantics/parser/SemanticsParser.jj1
-rw-r--r--container-search/src/main/resources/configdefinitions/container.search.fs4.def (renamed from container-search/src/main/resources/configdefinitions/fs4.def)0
-rw-r--r--container-search/src/main/resources/configdefinitions/prelude.cluster.qr-monitor.def (renamed from container-search/src/main/resources/configdefinitions/qr-monitor.def)0
-rw-r--r--container-search/src/main/resources/configdefinitions/prelude.emulation.def (renamed from container-search/src/main/resources/configdefinitions/emulation.def)0
-rw-r--r--container-search/src/main/resources/configdefinitions/prelude.fastsearch.documentdb-info.def (renamed from container-search/src/main/resources/configdefinitions/documentdb-info.def)0
-rw-r--r--container-search/src/main/resources/configdefinitions/prelude.searcher.keyvalue.def (renamed from container-search/src/main/resources/configdefinitions/keyvalue.def)0
-rw-r--r--container-search/src/main/resources/configdefinitions/prelude.searcher.qr-quotetable.def (renamed from container-search/src/main/resources/configdefinitions/qr-quotetable.def)0
-rw-r--r--container-search/src/main/resources/configdefinitions/prelude.semantics.semantic-rules.def (renamed from container-search/src/main/resources/configdefinitions/semantic-rules.def)0
-rw-r--r--container-search/src/main/resources/configdefinitions/search.config.cluster.def (renamed from container-search/src/main/resources/configdefinitions/cluster.def)3
-rw-r--r--container-search/src/main/resources/configdefinitions/search.config.index-info.def (renamed from container-search/src/main/resources/configdefinitions/index-info.def)0
-rw-r--r--container-search/src/main/resources/configdefinitions/search.config.qr-start.def (renamed from container-search/src/main/resources/configdefinitions/qr-start.def)3
-rw-r--r--container-search/src/main/resources/configdefinitions/search.config.rate-limiting.def (renamed from container-search/src/main/resources/configdefinitions/rate-limiting.def)0
-rw-r--r--container-search/src/main/resources/configdefinitions/search.federation.federation.def (renamed from container-search/src/main/resources/configdefinitions/federation.def)0
-rw-r--r--container-search/src/main/resources/configdefinitions/search.federation.provider.def (renamed from container-search/src/main/resources/configdefinitions/provider.def)0
-rw-r--r--container-search/src/main/resources/configdefinitions/search.federation.searchchain-forward.def (renamed from container-search/src/main/resources/configdefinitions/searchchain-forward.def)0
-rw-r--r--container-search/src/main/resources/configdefinitions/search.federation.strict-contracts.def (renamed from container-search/src/main/resources/configdefinitions/strict-contracts.def)0
-rw-r--r--container-search/src/main/resources/configdefinitions/search.handler.search-with-renderer-handler.def (renamed from container-search/src/main/resources/configdefinitions/search-with-renderer-handler.def)0
-rw-r--r--container-search/src/main/resources/configdefinitions/search.pagetemplates.page-templates.def (renamed from container-search/src/main/resources/configdefinitions/page-templates.def)0
-rw-r--r--container-search/src/main/resources/configdefinitions/search.pagetemplates.resolvers.def (renamed from container-search/src/main/resources/configdefinitions/resolvers.def)0
-rw-r--r--container-search/src/main/resources/configdefinitions/search.query.profile.config.query-profiles.def (renamed from container-search/src/main/resources/configdefinitions/query-profiles.def)4
-rw-r--r--container-search/src/main/resources/configdefinitions/search.query.rewrite.rewrites.def (renamed from container-search/src/main/resources/configdefinitions/rewrites.def)0
-rw-r--r--container-search/src/main/resources/configdefinitions/search.querytransform.lowercasing.def (renamed from container-search/src/main/resources/configdefinitions/lowercasing.def)0
-rw-r--r--container-search/src/main/resources/configdefinitions/search.statistics.measure-qps.def (renamed from container-search/src/main/resources/configdefinitions/measure-qps.def)0
-rw-r--r--container-search/src/main/resources/configdefinitions/search.statistics.timing-searcher.def (renamed from container-search/src/main/resources/configdefinitions/timing-searcher.def)0
270 files changed, 4627 insertions, 4704 deletions
diff --git a/container-search/src/main/antlr4/com/yahoo/search/yql/yqlplus.g4 b/container-search/src/main/antlr4/com/yahoo/search/yql/yqlplus.g4
index 639ff8255d2..c0cf293f2ea 100644
--- a/container-search/src/main/antlr4/com/yahoo/search/yql/yqlplus.g4
+++ b/container-search/src/main/antlr4/com/yahoo/search/yql/yqlplus.g4
@@ -18,22 +18,9 @@ options {
protected Stack<expression_scope> expression_stack = new Stack();
}
-// tokens for command syntax
- CREATE : 'create';
+// tokens
+
SELECT : 'select';
- INSERT : 'insert';
- UPDATE : 'update';
- SET : 'set';
- VIEW : 'view';
- TABLE : 'table';
- DELETE : 'delete';
- INTO : 'into';
- VALUES : 'values';
- IMPORT : 'import';
- NEXT : 'next';
- PAGED : 'paged';
- FALLBACK : 'fallback';
- IMPORT_FROM :;
LIMIT : 'limit';
OFFSET : 'offset';
@@ -44,36 +31,11 @@ options {
FROM : 'from';
SOURCES : 'sources';
AS : 'as';
- MERGE : 'merge';
- LEFT : 'left';
- JOIN : 'join';
-
- ON : 'on';
+
COMMA : ',';
OUTPUT : 'output';
COUNT : 'count';
- RETURNING : 'returning';
- APPLY : 'apply';
- CAST : 'cast';
-
- BEGIN : 'begin';
- END : 'end';
-
- // type-related
- TYPE_BYTE : 'byte';
- TYPE_INT16 : 'int16';
- TYPE_INT32 : 'int32';
- TYPE_INT64 : 'int64';
- TYPE_STRING : 'string';
- TYPE_DOUBLE : 'double';
- TYPE_TIMESTAMP : 'timestamp';
- TYPE_BOOLEAN : 'boolean';
- TYPE_ARRAY : 'array';
- TYPE_MAP : 'map';
-
- // READ_FIELD;
-
- // token literals
+
TRUE : 'true';
FALSE : 'false';
@@ -123,76 +85,14 @@ options {
// statement delimiter
SEMI : ';';
- PROGRAM : 'program';
TIMEOUT : 'timeout';
-//following node names seems only used for root node name
-//not exactly matching anything, should we still keep it?
-// Follow tree name are not no longer used
-//comment out maybe remove
-// ARGUMENT : ;
-// TYPE : ;
-// NAME : ;
-// DEFAULT :;
-//
-// ANNOTATE : ;
-//
-// PROJECT :;
-// FILTER :;
-//
-// PROPERTY :;
-// LITERAL :;
-// PARAMETER :;
-// SCALAR_LITERAL :;
-// MAP_LITERAL :;
-// // ARRAY_LITERAL :;
-// FIELD :;
-// //* FIELD_ASSIGNMENT;
-// ALL_SOURCE :;
-// MULTI_SOURCE :;
-// EXPRESSION_SOURCE :;
-// SEQUENCE_SOURCE :;
-// CALL_SOURCE :;
-// PIPELINE_STEP :;
-// AUTO_ALIAS :;
-// // BINOP_ADD :;
-// // BINOP_SUB :;
-// BINOP_MULT :;
-// BINOP_DIV :;
-// BINOP_MOD :;
-//
-// UNOP_MINUS :;
-// UNOP_NOT :;
-//
-// STATEMENT_SELECTVAR :;
-// STATEMENT_QUERY :;
-//
-// FIELDREF :;
-// PATH :;
-//
-// CALL :;
-//
-// // INDEXREF :;
-// PROPERTYREF :;
-//
-// LEFT_JOIN :;
-// RIGHT_JOIN :;
-//// FULL_JOIN;
-//
-// ALIAS :;
-//
-// INSERT_VALUES :;
-// UPDATE_VALUES :;
-//
-// NO_ARGUMENTS :;
-
-
/*------------------------------------------------------------------
* LEXER RULES
*------------------------------------------------------------------*/
-ID : ('a'..'z'|'A'..'Z'|'_') ('a'..'z'|'A'..'Z'|'0'..'9'|'_'|':')*
+ID : ('a'..'z'|'A'..'Z'|'_') ('a'..'z'|'A'..'Z'|'0'..'9'|'_'|':'|'-')*
;
LONG_INT : '-'?'0'..'9'+ ('L'|'l')
@@ -210,7 +110,6 @@ FLOAT
fragment
EXPONENT : ('e'|'E') ('+'|'-')? ('0'..'9')+ ;
-
fragment
DIGIT : '0'..'9'
;
@@ -220,13 +119,10 @@ LETTER : 'a'..'z'
| 'A'..'Z'
;
-//STRING : DQ ( ESC_SEQ | ~('\\'| DQ) )* DQ
-// | SQ ( ESC_SEQ | ~('\\' | SQ) )* SQ
STRING : '"' ( ESC_SEQ | ~('\\'| '"') )* '"'
| '\'' ( ESC_SEQ | ~('\\' | '\'') )* '\''
;
-/////////////////////////////
fragment
HEX_DIGIT : ('0'..'9'|'a'..'f'|'A'..'F') ;
@@ -235,18 +131,8 @@ fragment
ESC_SEQ
: '\\' ('b'|'t'|'n'|'f'|'r'|'\"'|'\''|'\\'|'/')
| UNICODE_ESC
- // | OCTAL_ESC
;
-/*
-fragment
-OCTAL_ESC
- : '\\' ('0'..'3') ('0'..'7') ('0'..'7')
- | '\\' ('0'..'7') ('0'..'7')
- | '\\' ('0'..'7')
- ;
-*/
-
fragment
UNICODE_ESC
: '\\' 'u' HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT
@@ -256,17 +142,11 @@ WS : ( ' '
| '\t'
| '\r'
| '\n'
- // ) {$channel=HIDDEN;}
) -> channel(HIDDEN)
;
-//COMMENT
-// : ('//') ~('\n'|'\r')* '\r'? '\n'? {$channel=HIDDEN;}
-// | '/*' ( options {greedy=false;} : . )* '*/' {$channel=HIDDEN;}
-// ;
COMMENT
: ( ('//') ~('\n'|'\r')* '\r'? '\n'?
- // | '/*' ( options {greedy=false;} : . )* '*/'
| '/*' .*? '*/'
)
-> channel(HIDDEN)
@@ -285,56 +165,20 @@ VESPA_GROUPING_ARG
(')' | ']' | '>')
;
-/*------------------------------------LPAREN! select_statement RPAREN!------------------------------
- * PARSER RULES
- *------------------------------------------------------------------*/
+// --------- parser rules ------------
ident
: keyword_as_ident //{addChild(new TerminalNodeImpl(keyword_as_ident.getText()));}
- //{return ID<IDNode>[$keyword_as_ident.text];}
| ID
;
keyword_as_ident
- : SELECT | TABLE | DELETE | INTO | VALUES | LIMIT | OFFSET | WHERE | 'order' | 'by' | DESC | MERGE | LEFT | JOIN
- | ON | OUTPUT | COUNT | BEGIN | END | APPLY | TYPE_BYTE | TYPE_INT16 | TYPE_INT32 | TYPE_INT64 | TYPE_BOOLEAN | TYPE_TIMESTAMP | TYPE_DOUBLE | TYPE_STRING | TYPE_ARRAY | TYPE_MAP
- | VIEW | CREATE | IMPORT | PROGRAM | NEXT | PAGED | SOURCES | SET | MATCHES | LIKE
+ : SELECT | LIMIT | OFFSET | WHERE | 'order' | 'by' | DESC | OUTPUT | COUNT | SOURCES | MATCHES | LIKE
;
-//program : params? (import_statement SEMI)* (ddl SEMI)* (statement SEMI)* EOF
-// -> ^(PROGRAM params? import_statement* ddl* statement*)
-// ;
-program : params? (import_statement SEMI)* (ddl SEMI)* (statement SEMI)* EOF
+program : (statement SEMI)* EOF
;
-//params
-// : PROGRAM! LPAREN! program_arglist? RPAREN! SEMI!
-// ;
-params
- : PROGRAM LPAREN program_arglist? RPAREN SEMI
- ;
-
-//import_statement
-// : IMPORT moduleName AS moduleId -> ^(IMPORT moduleName moduleId)
-// | IMPORT moduleId -> ^(IMPORT moduleId)
-// | FROM moduleName IMPORT import_list -> ^(IMPORT_FROM moduleName import_list+)
-// ;
-import_statement
- : IMPORT moduleName AS moduleId
- | IMPORT moduleId
- | FROM moduleName IMPORT import_list
- ;
-
-//import_list
-// : moduleId (',' moduleId)* -> moduleId+
-// ;
-import_list
- : moduleId (',' moduleId)*
- ;
-
-//moduleId
-// : ID -> STRING<LiteralNode>[$ID, $ID.text]
-// ;
moduleId
: ID
;
@@ -344,72 +188,18 @@ moduleName
| namespaced_name
;
-ddl
- : view
- ;
-
-//view : CREATE VIEW ID AS source_statement -> ^(VIEW ID source_statement)
-// ;
-view : CREATE VIEW ID AS source_statement
- ;
-
-
-//program_arglist
-// : procedure_argument (',' procedure_argument)* -> procedure_argument+
-// ;
-program_arglist
- : procedure_argument (',' procedure_argument)*
- ;
-
-//procedure_argument
-// : AT (ident TYPE_ARRAY LT typename GTEQ (expression[false])? ) {registerParameter($ident.tree, $typename.tree);} -> ^(ARGUMENT ident typename expression?)
-// | AT (ident typename ('=' expression[false])? ) {registerParameter($ident.tree, $typename.tree);} -> ^(ARGUMENT ident typename expression?)
-// ;
-procedure_argument
- :
- AT (ident TYPE_ARRAY LT typename GTEQ (expression[false])? ) {registerParameter($ident.start.getText(), $typename.start.getText());}
- | AT (ident typename ('=' expression[false])? ) {registerParameter($ident.start.getText(), $typename.start.getText());}
- ;
-
statement
: output_statement
- | selectvar_statement
- | next_statement
;
-//output_statement
-// : source_statement paged_clause? output_spec? -> ^(STATEMENT_QUERY source_statement paged_clause? output_spec?)
-// ;
output_statement
- : source_statement paged_clause? output_spec?
- ;
-
-//paged_clause
-// : PAGED^ fixed_or_parameter
-// ;
-paged_clause
- : PAGED fixed_or_parameter
+ : source_statement output_spec?
;
-//next_statement
-// : NEXT^ literalString OUTPUT! AS! ident
-// ;
-next_statement
- : NEXT literalString OUTPUT AS ident
- ;
-
-//source_statement
-// : query_statement (PIPE pipeline_step)+ -> ^(PIPE query_statement pipeline_step+)
-// | query_statement
-// ;
source_statement
: query_statement (PIPE pipeline_step)*
;
-
-//pipeline_step
-// : namespaced_name arguments[false]? -> ^(PIPELINE_STEP namespaced_name arguments?)
-// ;
pipeline_step
: namespaced_name arguments[false]?
| vespa_grouping
@@ -420,78 +210,19 @@ vespa_grouping
| annotation VESPA_GROUPING
;
-//selectvar_statement
-// : CREATE ('temp' | 'temporary') TABLE ident AS LPAREN source_statement RPAREN -> ^(STATEMENT_SELECTVAR ident source_statement)
-// ;
-selectvar_statement
- : CREATE ('temp' | 'temporary') TABLE ident AS LPAREN source_statement RPAREN
- ;
-
-
-typename
- : TYPE_BYTE | TYPE_INT16 | TYPE_INT32 | TYPE_INT64 | TYPE_STRING | TYPE_BOOLEAN | TYPE_TIMESTAMP
- | arrayType | mapType | TYPE_DOUBLE
- ;
-
-//arrayType
-// : TYPE_ARRAY^ LT! typename GT!
-// ;
-arrayType
- : TYPE_ARRAY LT typename GT
- ;
-
-//mapType
-// : TYPE_MAP^ LT! typename GT!
-// ;
-mapType
- : TYPE_MAP LT typename GT
- ;
-
-//output_spec
-// : (OUTPUT^ AS! ident)
-// | (OUTPUT! COUNT^ AS! ident)
-// ;
output_spec
: (OUTPUT AS ident)
| (OUTPUT COUNT AS ident)
;
-
query_statement
- : merge_statement
- | select_statement
- | insert_statement
- | delete_statement
- | update_statement
+ : select_statement
;
-// This does not use the UNION / UNION ALL from SQL because the semantics are different than SQL UNION
-// - no set operation is implied (no DISTINCT)
-// - CQL resultsets may be heterogeneous (rows may have heterogenous types)
-//merge_statement
-// : merge_component (MERGE merge_component)+ -> ^(MERGE merge_component+)
-// ;
-merge_statement
- : merge_component (MERGE merge_component)+
- ;
-
-merge_component
- : select_statement
- | LPAREN source_statement RPAREN
- ;
-
-//select_statement
-// : SELECT^ select_field_spec select_source where? orderby? limit? offset? timeout? fallback?
-// ;
select_statement
- : SELECT select_field_spec select_source? where? orderby? limit? offset? timeout? fallback?
-// | SELECT select_field_spec where? orderby? limit? offset? timeout? fallback?
+ : SELECT select_field_spec select_source? where? orderby? limit? offset? timeout?
;
-//select_field_spec
-// : field_def (COMMA field_def)* -> ^(PROJECT field_def+)
-// | STAR!
-// ;
select_field_spec
: project_spec
| STAR
@@ -501,31 +232,14 @@ project_spec
: field_def (COMMA field_def)*
;
-//fallback
-// : FALLBACK^ select_statement
-// ;
-fallback
- : FALLBACK select_statement
- ;
-
-//timeout
-// : TIMEOUT^ fixed_or_parameter
-// ;
timeout
: TIMEOUT fixed_or_parameter
;
-//select_source
-// : FROM SOURCES STAR -> ALL_SOURCE
-// | FROM SOURCES source_list -> ^(MULTI_SOURCE source_list)
-// | FROM^ source_spec join_expr*
-// | -> EXPRESSION_SOURCE
-// ;
-
select_source
: select_source_all
| select_source_multi
- | select_source_join
+ | select_source_from
;
select_source_all
@@ -536,53 +250,22 @@ select_source_multi
: FROM SOURCES source_list
;
-select_source_join
- : FROM source_spec join_expr*
+select_source_from
+ : FROM source_spec
;
-//source_list
-// : namespaced_name (COMMA namespaced_name)* -> namespaced_name+
-// ;
source_list
: namespaced_name (COMMA namespaced_name )*
;
-//join_expr
-// : (join_spec^ source_spec ON! joinExpression)
-// ;
-join_expr
- : (join_spec source_spec ON joinExpression)
- ;
-
-//join_spec
-// : LEFT JOIN -> LEFT_JOIN
-// | 'inner'? JOIN -> JOIN
-// ;
-join_spec
- : LEFT JOIN
- | 'inner'? JOIN
- ;
-
-//source_spec
-// : ( data_source (alias_def { ($data_source.tree).addChild($alias_def.tree); } )? ) -> data_source
-// ;
source_spec
: ( data_source (alias_def { ($data_source.ctx).addChild($alias_def.ctx); })? )
;
-//alias_def
-// : (AS? ID) -> ^(ALIAS ID)
-// ;
alias_def
: (AS? ID)
;
-//data_source
-// : namespaced_name arguments[true]? -> ^(CALL_SOURCE namespaced_name arguments?)
-// | LPAREN! source_statement RPAREN!
-// | AT ident -> ^(SEQUENCE_SOURCE ident)
-// ;
-
data_source
: call_source
| LPAREN source_statement RPAREN
@@ -597,84 +280,47 @@ sequence_source
: AT ident
;
-//namespaced_name
-// : (ident (DOT ident)* (DOT STAR)?) -> ^(PATH ident+ STAR?)
-// ;
namespaced_name
: (ident (DOT ident)* (DOT STAR)?)
;
-//orderby
-// : ORDERBY orderby_fields -> ^(ORDERBY orderby_fields)
-// ;
orderby
: ORDERBY orderby_fields
;
-//orderby_fields
-// : orderby_field (COMMA orderby_field)* -> orderby_field+
-// ;
orderby_fields
: orderby_field (COMMA orderby_field)*
;
-//orderby_field
-// : expression[true] DESC -> ^(DESC expression)
-// | expression[true] ('asc')? -> ^(ASC expression)
-// ;
orderby_field
: expression[true] DESC
| expression[true] ('asc')?
;
-//limit
-// : LIMIT fixed_or_parameter -> ^(LIMIT fixed_or_parameter)
-// ;
limit
: LIMIT fixed_or_parameter
;
-//offset
-// : OFFSET fixed_or_parameter -> ^(OFFSET fixed_or_parameter)
-// ;
offset
: OFFSET fixed_or_parameter
;
-
-//where
-// : WHERE expression[true] -> ^(FILTER expression)
-// ;
where
: WHERE expression[true]
;
-//field_def
-// : expression[true] alias_def? -> ^(FIELD expression alias_def?)
-// ;
field_def
: expression[true] alias_def?
;
-// Hive doesn't have syntax for creating maps - this is an extension
-//mapExpression
-// : LBRACE i+=propertyNameAndValue? (COMMA i+=propertyNameAndValue)* RBRACE -> ^(MAP_LITERAL $i*)
-// ;
mapExpression
: LBRACE propertyNameAndValue? (COMMA propertyNameAndValue)* RBRACE
;
-//constantMapExpression
-// : LBRACE i+=constantPropertyNameAndValue? (COMMA i+=constantPropertyNameAndValue)* RBRACE -> ^(MAP_LITERAL $i+)
-// ;
constantMapExpression
: LBRACE constantPropertyNameAndValue? (COMMA constantPropertyNameAndValue)* RBRACE
;
-//arguments[boolean in_select]
-// : LPAREN RPAREN -> NO_ARGUMENTS
-// | LPAREN (argument[$in_select] (COMMA argument[$in_select])*) RPAREN -> argument+
-// ;
arguments[boolean in_select]
: LPAREN RPAREN
| LPAREN (argument[$in_select] (COMMA argument[$in_select])*) RPAREN
@@ -684,45 +330,9 @@ argument[boolean in_select]
: expression[$in_select]
;
-// -------- join expressions ------------
-
-// Limit expression syntax for joins
-// for now, a single equality test and one field from each source
-// this makes it easier for the prototype to be sure it can generate code
-// effectively this means it can always turn the join into a query to one source, collecting all of the
-// keys from the results, and then a query to the other source
-// (or querying the other source inline)
-// do not support map or index references
-// this can become more general as the engine gets more capable
-
-//joinExpression
-// : joinDereferencedExpression EQ^ joinDereferencedExpression
-// ;
-joinExpression
- : joinDereferencedExpression EQ joinDereferencedExpression
- ;
-
-//joinDereferencedExpression
-// : (namespaced_name -> ^(FIELDREF namespaced_name))
-// ;
-joinDereferencedExpression
- : namespaced_name
- ;
-
// --------- expressions ------------
-//expression[boolean select]
-//scope {
-// boolean in_select;
-//}
-//@init {
-// $expression::in_select = $select;
-//}
-// : ( annotation logicalORExpression) -> ^(ANNOTATE annotation logicalORExpression)
-// | logicalORExpression
-// ;
-
-expression [boolean select]
+expression [boolean select]
@init {
expression_stack.push(new expression_scope());
expression_stack.peek().in_select = select;
@@ -739,50 +349,30 @@ nullOperator
: 'null'
;
-//annotation
-// : LBRACKET! constantMapExpression RBRACKET!
-// ;
annotateExpression
: annotation logicalORExpression
;
+
annotation
: LBRACKET constantMapExpression RBRACKET
;
-//logicalORExpression
-// : logicalANDExpression (OR logicalANDExpression)+ -> ^(OR logicalANDExpression+)
-// | logicalANDExpression
-// ;
logicalORExpression
: logicalANDExpression (OR logicalANDExpression)+
| logicalANDExpression
;
-//logicalANDExpression
-// : equalityExpression (AND equalityExpression)+ -> ^(AND equalityExpression+)
-// | equalityExpression
-// ;
logicalANDExpression
: equalityExpression (AND equalityExpression)*
;
-//equalityExpression
-// : relationalExpression ( ((IN^ | NOT_IN^) inNotInTarget)
-// | (IS_NULL^ | IS_NOT_NULL^)
-// | (equalityOp^ relationalExpression) )?
-// ;
-
-equalityExpression //changed for parsing literal tests
+equalityExpression
: relationalExpression ( ((IN | NOT_IN) inNotInTarget)
| (IS_NULL | IS_NOT_NULL)
| (equalityOp relationalExpression) )
| relationalExpression
;
-//inNotInTarget
-// : {$expression::in_select}? LPAREN select_statement RPAREN -> ^(QUERY_ARRAY select_statement)
-// | literal_list
-// ;
inNotInTarget
: {expression_stack.peek().in_select}? LPAREN select_statement RPAREN
| literal_list
@@ -792,9 +382,6 @@ equalityOp
: (EQ | NEQ | LIKE | NOTLIKE | MATCHES | NOTMATCHES | CONTAINS)
;
-//relationalExpression
-// : additiveExpression (relationalOp^ additiveExpression)*
-// ;
relationalExpression
: additiveExpression (relationalOp additiveExpression)?
;
@@ -803,64 +390,35 @@ relationalOp
: (LT | GT | LTEQ | GTEQ)
;
-//additiveExpression
-// : multiplicativeExpression (additiveOp^ additiveExpression)?
-// ;
additiveExpression
: multiplicativeExpression (additiveOp additiveExpression)?
;
-//additiveOp
-// : '+' -> BINOP_ADD
-// | '-' -> BINOP_SUB
-// ;
additiveOp
: '+'
| '-'
;
-//multiplicativeExpression
-// : unaryExpression (multOp^ multiplicativeExpression)?
-// ;
multiplicativeExpression
: unaryExpression (multOp multiplicativeExpression)?
;
-//multOp : '*' -> BINOP_MULT
-// | '/' -> BINOP_DIV
-// | '%' -> BINOP_MOD
-// ;
-multOp
+multOp
: '*'
| '/'
| '%'
;
-//unaryOp
-// : '-' -> UNOP_MINUS
-// | '!' -> UNOP_NOT
-// ;
unaryOp
: '-'
| '!'
;
-//unaryExpression
-// : dereferencedExpression
-// | unaryOp^ dereferencedExpression
-// ;
unaryExpression
: dereferencedExpression
| unaryOp dereferencedExpression
;
-//dereferencedExpression
-// : (primaryExpression -> primaryExpression)
-// (
-// (LBRACKET idx=expression[$expression::in_select] RBRACKET) -> ^(INDEXREF $dereferencedExpression $idx)
-// | (DOT nm=ID) -> ^(PROPERTYREF $dereferencedExpression $nm)
-// )*
-// ;
dereferencedExpression
@init{
boolean in_select = expression_stack.peek().in_select;
@@ -878,43 +436,12 @@ indexref[boolean in_select]
propertyref
: DOT nm=ID
;
-//operatorCall
-// : multOp arguments[$expression::in_select] -> ^(multOp arguments)
-// | additiveOp arguments[$expression::in_select] -> ^(additiveOp arguments)
-// | AND arguments[$expression::in_select] -> ^(AND arguments)
-// | OR arguments[$expression::in_select] -> ^(OR arguments)
-// ;
-operatorCall
-@init{
- boolean in_select = expression_stack.peek().in_select;
-}
- : multOp arguments[in_select]
- | additiveOp arguments[in_select]
- | AND arguments[in_select]
- | OR arguments[in_select]
- ;
-
-// TODO: temporarily disable CAST, need to think through how types are named
-
-//primaryExpression
-// : namespaced_name arguments[$expression::in_select] -> ^(CALL namespaced_name arguments)
-//// | CAST LPAREN expression[$expression::in_select] AS typename RPAREN -> ^(CAST expression typename)
-// | APPLY! operatorCall
-// | parameter
-// | namespaced_name -> ^(FIELDREF namespaced_name)
-// | scalar_literal
-// | LBRACKET i+=expression[$expression::in_select]? (COMMA (i+=expression[$expression::in_select]))* RBRACKET -> ^(ARRAY_LITERAL $i*)
-// | mapExpression
-// | LPAREN! expression[$expression::in_select] RPAREN!
-// ;
primaryExpression
@init {
boolean in_select = expression_stack.peek().in_select;
}
: callExpresion[in_select]
-// | CAST LPAREN expression[$expression::in_select] AS typename RPAREN -> ^(CAST expression typename)
-// | APPLY operatorCall
| parameter
| fieldref
| scalar_literal
@@ -937,32 +464,19 @@ arrayLiteral
: LBRACKET expression[in_select]? (COMMA expression[in_select])* RBRACKET
;
-// a parameter is an argument from outside the script/program
-//parameter
-// : AT ident -> ^(PARAMETER ident)
-// ;
-parameter
+// a parameter is an argument from outside the YQL statement
+parameter
: AT ident
;
-//propertyNameAndValue
-// : propertyName ':' expression[$expression::in_select] -> ^(PROPERTY propertyName expression)
-// ;
propertyNameAndValue
: propertyName ':' expression[{expression_stack.peek().in_select}] //{return (PROPERTY propertyName expression);}
;
-//constantPropertyNameAndValue
-// : propertyName ':' constantExpression -> ^(PROPERTY propertyName constantExpression)
-// ;
constantPropertyNameAndValue
: propertyName ':' constantExpression
;
-//propertyName
-// : ID -> STRING<LiteralNode>[$ID, $ID.text]
-// | literalString
-// ;
propertyName
: ID
| literalString
@@ -972,56 +486,32 @@ constantExpression
: scalar_literal
| constantMapExpression
| constantArray
+ | parameter
;
-//constantArray
-// : LBRACKET i+=constantExpression? (COMMA i+=constantExpression)* RBRACKET -> ^(ARRAY_LITERAL $i*)
-// ;
constantArray
: LBRACKET i+=constantExpression? (COMMA i+=constantExpression)* RBRACKET
;
-// TODO: fix INT L and INT b parsing -- should not permit whitespace between them
-//scalar_literal
-// : TRUE -> TRUE<LiteralNode>[$TRUE, true]
-// | FALSE -> FALSE<LiteralNode>[$FALSE, false]
-// | literalString
-// | LONG_INT -> INT<LiteralNode>[$LONG_INT, Long.parseLong($LONG_INT.text.substring(0, $LONG_INT.text.length()-1))]
-//// | INT 'b' -> INT<LiteralNode>[$INT, Byte.parseByte($INT.text)]
-// | INT -> INT<LiteralNode>[$INT, Integer.parseInt($INT.text)]
-// | FLOAT -> FLOAT<LiteralNode>[$FLOAT, Double.parseDouble($FLOAT.text)]
-// ;
scalar_literal
: TRUE
| FALSE
| STRING
| LONG_INT
-// | INT 'b' -> INT<LiteralNode>[$INT, Byte.parseByte($INT.text)]
- | INT
+ | INT
| FLOAT
;
-//literalString
-// : STRING -> STRING<LiteralNode>[$STRING, StringUnescaper.unquote($STRING.text)]
-// ;
literalString
: STRING
;
-//array_parameter
-// : AT i=ident {isArrayParameter($i.text)}? -> ^(PARAMETER $i)
-// ;
array_parameter
: AT i=ident {isArrayParameter($i.ctx)}?
;
-// array literal for IN/NOT_IN using SQL syntax
-//literal_list
-// : LPAREN array_parameter RPAREN -> array_parameter
-// | LPAREN literal_element (COMMA literal_element)* RPAREN -> ^(ARRAY_LITERAL literal_element+)
-// ;
literal_list
- : LPAREN literal_element (COMMA literal_element)* RPAREN //{return ^(ARRAY_LITERAL literal_element+);}
+ : LPAREN literal_element (COMMA literal_element)* RPAREN
;
literal_element
@@ -1029,115 +519,7 @@ literal_element
| parameter
;
-//fixed_or_parameter
-// : INT -> INT<LiteralNode>[$INT, Integer.parseInt($INT.text)]
-// | parameter
-// ;
fixed_or_parameter
: INT
| parameter
;
-
-
-// INSERT
-
-//insert_statement
-// : INSERT^ insert_source insert_values returning_spec?
-// ;
-insert_statement
- : INSERT insert_source insert_values returning_spec?
- ;
-
-//insert_source
-// : INTO^ write_data_source
-// ;
-insert_source
- : INTO write_data_source
- ;
-
-//write_data_source
-// : namespaced_name -> ^(CALL_SOURCE namespaced_name)
-// ;
-write_data_source
- : namespaced_name
- ;
-
-//insert_values
-// : field_names_spec VALUES field_values_group_spec (COMMA field_values_group_spec)* -> ^(INSERT_VALUES field_names_spec field_values_group_spec+)
-// | query_statement -> ^(INSERT_QUERY query_statement)
-// ;
-
-insert_values
- : field_names_spec VALUES field_values_group_spec (COMMA field_values_group_spec)*
- | query_statement
- ;
-
-//field_names_spec
-// : LPAREN field_def (COMMA field_def)* RPAREN -> field_def+
-// ;
-field_names_spec
- : LPAREN field_def (COMMA field_def)* RPAREN
- ;
-
-//field_values_spec
-// : LPAREN expression[true] (COMMA expression[true])* RPAREN -> expression+
-// ;
-field_values_spec
- : LPAREN expression[true] (COMMA expression[true])* RPAREN
- ;
-
-//field_values_group_spec
-// : LPAREN expression[true] (COMMA expression[true])* RPAREN -> ^(FIELD_VALUES_GROUP expression+)
-// ;
-field_values_group_spec
- : LPAREN expression[true] (COMMA expression[true])* RPAREN
- ;
-
-returning_spec
- : RETURNING select_field_spec
- ;
-
-// DELETE
-
-//delete_statement
-// : DELETE^ delete_source where? returning_spec?
-// ;
-delete_statement
- : DELETE delete_source where? returning_spec?
- ;
-
-//delete_source
-// : FROM^ write_data_source
-// ;
-delete_source
- : FROM write_data_source
- ;
-
-// UPDATE
-
-//update_statement
-// : UPDATE^ update_source SET update_values where? returning_spec?
-// ;
-update_statement
- : UPDATE update_source SET update_values where? returning_spec?
- ;
-
-//update_source
-// : namespaced_name
-// ;
-update_source
- : write_data_source
- ;
-
-//update_values
-// : field_names_spec EQ field_values_spec -> ^(UPDATE_VALUES field_names_spec field_values_spec)
-// | field_def (COMMA field_def)* -> ^(UPDATE_VALUES field_def+)
-// ;
-update_values
- : field_names_spec EQ field_values_spec
- | field_def (COMMA field_def)*
- ;
-
-
-
-
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 365ee299ca4..306c7c80577 100644
--- a/container-search/src/main/java/com/yahoo/prelude/Index.java
+++ b/container-search/src/main/java/com/yahoo/prelude/Index.java
@@ -1,7 +1,6 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.prelude;
-
import com.yahoo.language.process.StemMode;
import java.util.ArrayList;
@@ -10,7 +9,6 @@ import java.util.Iterator;
import java.util.List;
import java.util.Set;
-
/**
* Information about configured settings of a field or field collection (an actual index or not) in a search definition.
* There are two types of settings:
@@ -25,32 +23,15 @@ import java.util.Set;
*/
public class Index {
- public static class Attribute {
-
- private boolean tokenizedContent = false;
- public final String name;
-
- public Attribute(String name) {
- this.name = name;
- }
-
- public boolean isTokenizedContent() {
- return tokenizedContent;
- }
-
- public void setTokenizedContent(boolean tokenizedContent) {
- this.tokenizedContent = tokenizedContent;
- }
- }
-
/** The null index - don't use this for name lookups */
public static final Index nullIndex = new Index("(null)");
- private String name;
+ private final String name;
- private List<String> aliases = new ArrayList<>();
+ private final List<String> aliases = new ArrayList<>();
// The state resulting from adding commands to this (using addCommand)
+ private boolean tensor = false;
private boolean uriIndex = false;
private boolean hostIndex = false;
private StemMode stemMode = StemMode.NONE;
@@ -65,6 +46,7 @@ public class Index {
private boolean normalize = false;
private boolean literalBoost = false;
private boolean numerical = false;
+ private boolean predicate = false;
private long predicateUpperBound = Long.MAX_VALUE;
private long predicateLowerBound = Long.MIN_VALUE;
@@ -74,17 +56,17 @@ public class Index {
private boolean isNGram = false;
private int gramSize = 2;
- /** Whether implicit phrases should lead to a phrase item or an and item */
- private boolean phraseSegmenting = true;
+ /** Whether implicit phrases should lead to a phrase item or an and item. */
+ private Boolean phraseSegmenting = false;
/** The string terminating an exact token in this index, or null to use the default (space) */
private String exactTerminator = null;
- /** Commands which are not coverted into a field */
- private Set<String> commands = new java.util.HashSet<>();
+ /** Commands which are not converted into a field */
+ private final Set<String> commands = new java.util.HashSet<>();
/** All the commands added to this, including those converted to fields above */
- private List<String> allCommands = new java.util.ArrayList<>();
+ private final List<String> allCommands = new java.util.ArrayList<>();
public Index(String name) {
this.name = name;
@@ -140,61 +122,71 @@ public class Index {
}
/** Adds a type or untyped command string to this */
- public Index addCommand(String commandString) {
- allCommands.add(commandString);
+ public Index addCommand(String command) {
+ allCommands.add(command);
- if ("fullurl".equals(commandString)) {
+ if (command.startsWith("type tensor(") || command.startsWith("type tensor<")) { // TODO: Type info can replace numerical, predicate, multivalue
+ setTensor(true);
+ } else if ("fullurl".equals(command)) {
setUriIndex(true);
- } else if ("urlhost".equals(commandString)) {
+ } else if ("urlhost".equals(command)) {
setHostIndex(true);
- } else if (commandString.startsWith("stem ")) {
- setStemMode(commandString.substring(5));
- } else if (commandString.startsWith("stem:")) {
- setStemMode(commandString.substring(5));
- } else if ("stem".equals(commandString)) {
+ } else if (command.startsWith("stem ")) {
+ setStemMode(command.substring(5));
+ } else if (command.startsWith("stem:")) {
+ setStemMode(command.substring(5));
+ } else if ("stem".equals(command)) {
setStemMode(StemMode.SHORTEST);
- } else if ("word".equals(commandString)) {
+ } else if ("word".equals(command)) {
setExact(true, null);
- } else if ("exact".equals(commandString)) {
+ } else if ("exact".equals(command)) {
setExact(true, " ");
- } else if ("dynteaser".equals(commandString)) {
+ } else if ("dynteaser".equals(command)) {
setDynamicSummary(true);
- } else if ("highlight".equals(commandString)) {
+ } else if ("highlight".equals(command)) {
setHighlightSummary(true);
- } else if ("lowercase".equals(commandString)) {
+ } else if ("lowercase".equals(command)) {
setLowercase(true);
- } else if (commandString.startsWith("exact ")) {
- setExact(true, commandString.substring(6));
- } else if (commandString.startsWith("ngram ")) {
- setNGram(true, Integer.parseInt(commandString.substring(6)));
- } else if (commandString.equals("attribute")) {
+ } else if (command.startsWith("exact ")) {
+ setExact(true, command.substring(6));
+ } else if (command.startsWith("ngram ")) {
+ setNGram(true, Integer.parseInt(command.substring(6)));
+ } else if (command.equals("attribute")) {
setAttribute(true);
- } else if (commandString.equals("default-position")) {
+ } else if (command.equals("default-position")) {
setDefaultPosition(true);
- } else if (commandString.equals("plain-tokens")) {
+ } else if (command.equals("plain-tokens")) {
setPlainTokens(true);
- } else if (commandString.equals("multivalue")) {
+ } else if (command.equals("multivalue")) {
setMultivalue(true);
- } else if (commandString.equals("fast-search")) {
+ } else if (command.equals("fast-search")) {
setFastSearch(true);
- } else if (commandString.equals("normalize")) {
+ } else if (command.equals("normalize")) {
setNormalize(true);
- } else if (commandString.equals("literal-boost")) {
+ } else if (command.equals("literal-boost")) {
setLiteralBoost(true);
- } else if (commandString.equals("numerical")) {
+ } else if (command.equals("numerical")) {
setNumerical(true);
- } else if (commandString.startsWith("predicate-bounds ")) {
- setPredicateBounds(commandString.substring(17));
- } else if (commandString.equals("phrase-segmenting")) {
+ } else if (command.equals("predicate")) {
+ setPredicate(true);
+ } else if (command.startsWith("predicate-bounds ")) {
+ setPredicateBounds(command.substring(17));
+ } else if (command.equals("phrase-segmenting")) {
setPhraseSegmenting(true);
- } else if (commandString.startsWith("phrase-segmenting ")) {
- setPhraseSegmenting(Boolean.parseBoolean(commandString.substring("phrase-segmenting ".length())));
+ } else if (command.startsWith("phrase-segmenting ")) {
+ setPhraseSegmenting(Boolean.parseBoolean(command.substring("phrase-segmenting ".length())));
} else {
- commands.add(commandString);
+ commands.add(command);
}
return this;
}
+ private void setTensor(boolean tensor) {
+ this.tensor = tensor;
+ }
+
+ public boolean isTensor() { return tensor; }
+
private void setPredicateBounds(String bounds) {
if ( ! bounds.startsWith("[..")) {
predicateLowerBound = Long.parseLong(bounds.substring(1, bounds.indexOf("..")));
@@ -269,9 +261,7 @@ public class Index {
return "(null)".equals(name);
}
- public boolean isAttribute() {
- return isAttribute;
- }
+ public boolean isAttribute() { return isAttribute; }
public void setAttribute(boolean isAttribute) {
this.isAttribute = isAttribute;
@@ -306,6 +296,10 @@ public class Index {
public boolean isNumerical() { return numerical; }
+ public void setPredicate(boolean isPredicate) { this.predicate = isPredicate; }
+
+ public boolean isPredicate() { return predicate; }
+
public long getPredicateUpperBound() { return predicateUpperBound; }
public long getPredicateLowerBound() { return predicateLowerBound; }
diff --git a/container-search/src/main/java/com/yahoo/prelude/IndexFacts.java b/container-search/src/main/java/com/yahoo/prelude/IndexFacts.java
index 76eef33d6c0..7b403ca3659 100644
--- a/container-search/src/main/java/com/yahoo/prelude/IndexFacts.java
+++ b/container-search/src/main/java/com/yahoo/prelude/IndexFacts.java
@@ -48,7 +48,6 @@ public class IndexFacts {
static final String unionName = "unionOfAllKnown";
/** A search definition which contains the union of all settings. */
- @SuppressWarnings("deprecation")
private SearchDefinition unionSearchDefinition = new SearchDefinition(unionName);
private boolean frozen;
@@ -334,10 +333,8 @@ public class IndexFacts {
/**
* Returns the index for this name.
*
- * @param indexName the name of the index. If this is null or empty the index
- * named "default" is returned
- * @return the index best matching the input parameters or the nullIndex
- * (never null) if none is found
+ * @param indexName the name of the index. If this is null or empty the index named "default" is returned
+ * @return the index best matching the input parameters or the null Index (never null) if none is found
*/
public Index getIndex(String indexName) {
return IndexFacts.this.getIndexFromDocumentTypes(indexName, documentTypes);
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 062a514056b..00935392683 100644
--- a/container-search/src/main/java/com/yahoo/prelude/IndexModel.java
+++ b/container-search/src/main/java/com/yahoo/prelude/IndexModel.java
@@ -109,7 +109,6 @@ public final class IndexModel {
return searchDefinitions;
}
- @SuppressWarnings("deprecation")
private SearchDefinition unionOf(Collection<SearchDefinition> searchDefinitions) {
SearchDefinition union = new SearchDefinition(IndexFacts.unionName);
diff --git a/container-search/src/main/java/com/yahoo/prelude/Location.java b/container-search/src/main/java/com/yahoo/prelude/Location.java
index 37284bd6bcc..3e9c2382f31 100644
--- a/container-search/src/main/java/com/yahoo/prelude/Location.java
+++ b/container-search/src/main/java/com/yahoo/prelude/Location.java
@@ -9,7 +9,7 @@ import java.util.StringTokenizer;
/**
* Location data for a geographical query.
*
- * @author <a href="mailto:steinar@yahoo-inc.com">Steinar Knutsen</a>
+ * @author Steinar Knutsen
* @author arnej27959
*/
public class Location {
@@ -24,16 +24,9 @@ public class Location {
private int y2 = 1;
// center(x,y), radius
- private int x = 1;
- private int y = 1;
- private int r = 1;
-
- // next three are now UNUSED
- // ranking table, rank multiplier (scale)
- // {0, 1} an int to make parsing and rendering the hit even simpler
- private int tableId = 0;
- private int s = 1;
- private int replace = 0;
+ private int x = 0;
+ private int y = 0;
+ private int r = -1;
private boolean renderCircle = false;
private boolean renderRectangle = false;
@@ -47,14 +40,14 @@ public class Location {
return dimensions == l.dimensions
&& renderCircle == l.renderCircle
&& renderRectangle == l.renderRectangle
- && aspect == l.aspect
- && x1 == l.x1
- && x2 == l.x2
- && y1 == l.y1
- && y2 == l.y2
- && x == l.x
- && y == l.y
- && r == l.r;
+ && this.aspect == l.aspect
+ && this.x1 == l.x1
+ && this.x2 == l.x2
+ && this.y1 == l.y1
+ && this.y2 == l.y2
+ && this.x == l.x
+ && this.y == l.y
+ && this.r == l.r;
}
public boolean hasDimensions() {
@@ -64,10 +57,10 @@ public class Location {
if (hasDimensions() && dimensions != d) {
throw new IllegalArgumentException("already has dimensions="+dimensions+", cannot change it to "+d);
}
- if (d == 1 || d == 2) {
+ if (d == 2) {
dimensions = d;
} else {
- throw new IllegalArgumentException("Illegal location, dimensions must be 1 or 2, but was: "+d);
+ throw new IllegalArgumentException("Illegal location, dimensions must be 2, but was: "+d);
}
}
public int getDimensions() {
@@ -89,13 +82,13 @@ public class Location {
if (px1 > px2) {
throw new IllegalArgumentException("cannot have w > e");
}
- x1 = px1;
- x2 = px2;
+ this.x1 = px1;
+ this.x2 = px2;
if (py1 > py2) {
throw new IllegalArgumentException("cannot have s > n");
}
- y1 = py1;
- y2 = py2;
+ this.y1 = py1;
+ this.y2 = py2;
renderRectangle = true;
}
@@ -104,12 +97,12 @@ public class Location {
//no need to "optimize" for special cases, exactly 0, 30, 45, 60, or 90 degrees won't be input anyway
double degrees = (double) y / 1000000d;
if (degrees <= -90.0 || degrees >= +90.0) {
- aspect = 0;
+ this.aspect = 0;
return;
}
double radians = degrees * Math.PI / 180d;
double cosLatRadians = Math.cos(radians);
- aspect = (long) (cosLatRadians * 4294967295L);
+ this.aspect = (long) (cosLatRadians * 4294967295L);
}
public void setGeoCircle(double ns, double ew, double radius_in_degrees) {
@@ -126,12 +119,12 @@ public class Location {
if (ns < -90.1 || ns > +90.1) {
throw new IllegalArgumentException("n/s location must be in range [-90,+90]");
}
- if (radius_in_degrees < 0 || radius_in_degrees > 180.0) {
- throw new IllegalArgumentException("radius must be in range [0,180] degrees, approximately upto 20000km");
+ if (radius_in_degrees < 0) {
+ pr = -1;
}
- x = px;
- y = py;
- r = pr;
+ this.x = px;
+ this.y = py;
+ this.r = pr;
renderCircle = true;
adjustAspect();
}
@@ -142,11 +135,11 @@ public class Location {
throw new IllegalArgumentException("can only set geo circle once");
}
if (radius_in_units < 0) {
- throw new IllegalArgumentException("radius must be positive");
+ radius_in_units = -1;
}
- x = px;
- y = py;
- r = radius_in_units;
+ this.x = px;
+ this.y = py;
+ this.r = radius_in_units;
renderCircle = true;
}
@@ -158,17 +151,12 @@ public class Location {
String rectPart = rectangle.substring(1,endof);
StringTokenizer tokens = new StringTokenizer(rectPart, ",");
setDimensions(Integer.parseInt(tokens.nextToken()));
- if (dimensions == 1) {
- x1 = Integer.parseInt(tokens.nextToken());
- x2 = Integer.parseInt(tokens.nextToken());
- if (tokens.hasMoreTokens()) {
- throw new IllegalArgumentException("Illegal location syntax: "+rectangle);
- }
- } else if (dimensions == 2) {
- x1 = Integer.parseInt(tokens.nextToken());
- y1 = Integer.parseInt(tokens.nextToken());
- x2 = Integer.parseInt(tokens.nextToken());
- y2 = Integer.parseInt(tokens.nextToken());
+ this.x1 = Integer.parseInt(tokens.nextToken());
+ this.y1 = Integer.parseInt(tokens.nextToken());
+ this.x2 = Integer.parseInt(tokens.nextToken());
+ this.y2 = Integer.parseInt(tokens.nextToken());
+ if (tokens.hasMoreTokens()) {
+ throw new IllegalArgumentException("Illegal location syntax: "+rectangle);
}
renderRectangle = true;
String theRest = rectangle.substring(endof+1).trim();
@@ -185,34 +173,24 @@ public class Location {
String circlePart = circle.substring(1,endof);
StringTokenizer tokens = new StringTokenizer(circlePart, ",");
setDimensions(Integer.parseInt(tokens.nextToken()));
- x = Integer.parseInt(tokens.nextToken());
- if (dimensions == 2) {
- y = Integer.parseInt(tokens.nextToken());
- }
- r = Integer.parseInt(tokens.nextToken());
+ this.x = Integer.parseInt(tokens.nextToken());
+ this.y = Integer.parseInt(tokens.nextToken());
+ this.r = Integer.parseInt(tokens.nextToken());
Integer.parseInt(tokens.nextToken()); // was "tableId"
- Integer.parseInt(tokens.nextToken()); // was "scale" (multiplier)
+ Integer.parseInt(tokens.nextToken()); // was "scale"
Integer.parseInt(tokens.nextToken()); // was "replace"
-
- if (dimensions == 1) {
- if (tokens.hasMoreTokens()) {
- throw new IllegalArgumentException("Illegal location syntax: "+circle);
- }
- }
- else {
- if (tokens.hasMoreTokens()) {
- String aspectToken = tokens.nextToken();
- if (aspectToken.equalsIgnoreCase("CalcLatLon")) {
- adjustAspect();
- } else {
- try {
- aspect = Long.parseLong(aspectToken);
- } catch (NumberFormatException nfe) {
- throw new IllegalArgumentException("Aspect "+aspectToken+" for location must be an integer or 'CalcLatLon' for automatic aspect calculation.", nfe);
- }
- if (aspect > 4294967295L || aspect < 0) {
- throw new IllegalArgumentException("Aspect "+aspect+" for location parameter must be less than 4294967296 (2^32)");
- }
+ if (tokens.hasMoreTokens()) {
+ String aspectToken = tokens.nextToken();
+ if (aspectToken.equalsIgnoreCase("CalcLatLon")) {
+ adjustAspect();
+ } else {
+ try {
+ aspect = Long.parseLong(aspectToken);
+ } catch (NumberFormatException nfe) {
+ throw new IllegalArgumentException("Aspect "+aspectToken+" for location must be an integer or 'CalcLatLon' for automatic aspect calculation.", nfe);
+ }
+ if (aspect > 4294967295L || aspect < 0) {
+ throw new IllegalArgumentException("Aspect "+aspect+" for location parameter must be less than 4294967296 (2^32)");
}
}
}
@@ -248,34 +226,33 @@ public class Location {
}
public String toString() {
+ return render(false);
+ }
+ public String backendString() {
+ return render(true);
+ }
+
+ private String render(boolean forBackend) {
StringBuilder ser = new StringBuilder();
if (attribute != null) {
ser.append(attribute).append(':');
}
if (renderRectangle) {
ser.append("[").append(dimensions).append(",");
- if (dimensions == 1) {
- ser.append(x1).append(",").
- append(x2);
- }
- else {
- ser.append(x1).append(",").
- append(y1).append(",").
- append(x2).append(",").
- append(y2);
- }
+ ser.append(x1).append(",").
+ append(y1).append(",").
+ append(x2).append(",").
+ append(y2);
ser.append("]");
}
if (renderCircle) {
- ser.append("(").append(dimensions).append(",").append(x);
- if (dimensions == 2) {
- ser.append(",").append(y);
- }
- ser.append(",").append(r).
- append(",").append(tableId).
- append(",").append(s).
- append(",").append(replace);
- if (dimensions == 2 && aspect != 0) {
+ ser.append("(").append(dimensions).append(",").
+ append(this.x).append(",").append(this.y);
+ ser.append(",").append(forBackend ? backendRadius() : this.r).
+ append(",").append(0). // was "tableId"
+ append(",").append(1). // was "scale"
+ append(",").append(0); // was "replace"
+ if (aspect != 0) {
ser.append(",").append(aspect);
}
ser.append(")");
@@ -289,7 +266,7 @@ public class Location {
*/
public int getBoundingWidth() {
if (renderCircle) {
- return r * 2;
+ return this.r * 2;
} else {
return x2 - x1;
}
@@ -301,7 +278,7 @@ public class Location {
*/
public int getBoundingHeight() {
if (renderCircle) {
- return r * 2;
+ return this.r * 2;
} else {
return y2 - y1;
}
@@ -358,11 +335,16 @@ public class Location {
/**
* Obtain circle radius (in degrees).
+ * Note that "no radius" or "infinite radius" is represented as -1.
* May only be called when isGeoCircle() returns true.
**/
public double degRadius() {
checkGeoCircle();
- return 0.000001 * r;
+ return (this.r < 0) ? -1.0 : (0.000001 * this.r);
+ }
+
+ private int backendRadius() {
+ return (this.r < 0) ? -1 : this.r;
}
/**
@@ -370,7 +352,7 @@ public class Location {
* For internal use.
*/
public int encode(ByteBuffer buffer) {
- byte[] loc = Utf8.toBytes(toString());
+ byte[] loc = Utf8.toBytes(backendString());
buffer.put(loc);
return loc.length;
}
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 7859a9698d9..3d1240237e4 100644
--- a/container-search/src/main/java/com/yahoo/prelude/SearchDefinition.java
+++ b/container-search/src/main/java/com/yahoo/prelude/SearchDefinition.java
@@ -1,8 +1,6 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.prelude;
-import com.yahoo.prelude.Index.Attribute;
-
import java.util.HashMap;
import java.util.Map;
@@ -17,13 +15,13 @@ import static com.yahoo.text.Lowercase.toLowerCase;
// TODO: Make freezable!
public class SearchDefinition {
- private String name;
+ private final String name;
/** A map of all indices in this search definition, indexed by name */
- private Map<String, Index> indices = new HashMap<>();
+ private final Map<String, Index> indices = new HashMap<>();
/* A map of all indices in this search definition, indexed by lower cased name. */
- private Map<String, Index> lowerCase = new HashMap<>();
+ private final Map<String, Index> lowerCase = new HashMap<>();
private String defaultPosition;
diff --git a/container-search/src/main/java/com/yahoo/prelude/cluster/ClusterSearcher.java b/container-search/src/main/java/com/yahoo/prelude/cluster/ClusterSearcher.java
index 37b0fd7ebfb..8685400b5c1 100644
--- a/container-search/src/main/java/com/yahoo/prelude/cluster/ClusterSearcher.java
+++ b/container-search/src/main/java/com/yahoo/prelude/cluster/ClusterSearcher.java
@@ -1,15 +1,18 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.prelude.cluster;
+import com.google.inject.Inject;
import com.yahoo.component.ComponentId;
import com.yahoo.component.chain.dependencies.After;
import com.yahoo.component.provider.ComponentRegistry;
+import com.yahoo.container.QrConfig;
import com.yahoo.container.QrSearchersConfig;
+import com.yahoo.container.core.documentapi.VespaDocumentAccess;
import com.yahoo.container.handler.VipStatus;
+import com.yahoo.documentapi.DocumentAccess;
import com.yahoo.prelude.IndexFacts;
import com.yahoo.prelude.fastsearch.ClusterParams;
import com.yahoo.prelude.fastsearch.DocumentdbInfoConfig;
-import com.yahoo.prelude.fastsearch.FS4ResourcePool;
import com.yahoo.prelude.fastsearch.FastSearcher;
import com.yahoo.prelude.fastsearch.SummaryParameters;
import com.yahoo.prelude.fastsearch.VespaBackEndSearcher;
@@ -61,13 +64,15 @@ public class ClusterSearcher extends Searcher {
private VespaBackEndSearcher server = null;
+ @Inject
public ClusterSearcher(ComponentId id,
QrSearchersConfig qrsConfig,
ClusterConfig clusterConfig,
DocumentdbInfoConfig documentDbConfig,
ComponentRegistry<Dispatcher> dispatchers,
- FS4ResourcePool fs4ResourcePool,
- VipStatus vipStatus) {
+ QrConfig qrConfig,
+ VipStatus vipStatus,
+ VespaDocumentAccess access) {
super(id);
int searchClusterIndex = clusterConfig.clusterId();
@@ -92,12 +97,12 @@ public class ClusterSearcher extends Searcher {
}
if (searchClusterConfig.indexingmode() == STREAMING) {
- VdsStreamingSearcher searcher = vdsCluster(fs4ResourcePool.getServerId(), searchClusterIndex,
- searchClusterConfig, docSumParams, documentDbConfig);
+ VdsStreamingSearcher searcher = vdsCluster(qrConfig.discriminator(), searchClusterIndex,
+ searchClusterConfig, docSumParams, documentDbConfig, access);
addBackendSearcher(searcher);
vipStatus.addToRotation(searcher.getName());
} else {
- FastSearcher searcher = searchDispatch(searchClusterIndex, searchClusterName, fs4ResourcePool.getServerId(),
+ FastSearcher searcher = searchDispatch(searchClusterIndex, searchClusterName, qrConfig.discriminator(),
docSumParams, documentDbConfig, dispatchers);
addBackendSearcher(searcher);
@@ -139,13 +144,14 @@ public class ClusterSearcher extends Searcher {
int searchclusterIndex,
QrSearchersConfig.Searchcluster searchClusterConfig,
SummaryParameters docSumParams,
- DocumentdbInfoConfig documentdbInfoConfig) {
+ DocumentdbInfoConfig documentdbInfoConfig,
+ VespaDocumentAccess access) {
if (searchClusterConfig.searchdef().size() != 1) {
throw new IllegalArgumentException("Search clusters in streaming search shall only contain a single searchdefinition : " + searchClusterConfig.searchdef());
}
ClusterParams clusterParams = makeClusterParams(searchclusterIndex);
- VdsStreamingSearcher searcher = new VdsStreamingSearcher();
- searcher.setSearchClusterConfigId(searchClusterConfig.rankprofiles().configid());
+ VdsStreamingSearcher searcher = new VdsStreamingSearcher(access);
+ searcher.setSearchClusterName(searchClusterConfig.rankprofiles().configid());
searcher.setDocumentType(searchClusterConfig.searchdef(0));
searcher.setStorageClusterRouteSpec(searchClusterConfig.storagecluster().routespec());
searcher.init(serverId, docSumParams, clusterParams, documentdbInfoConfig);
diff --git a/container-search/src/main/java/com/yahoo/prelude/fastsearch/Base64DataField.java b/container-search/src/main/java/com/yahoo/prelude/fastsearch/Base64DataField.java
new file mode 100644
index 00000000000..d51bdc0fad1
--- /dev/null
+++ b/container-search/src/main/java/com/yahoo/prelude/fastsearch/Base64DataField.java
@@ -0,0 +1,25 @@
+package com.yahoo.prelude.fastsearch;
+
+import com.yahoo.data.access.Inspector;
+import com.yahoo.data.access.simple.Value;
+import com.yahoo.prelude.hitfield.RawBase64;
+
+/**
+ * Represents a binary field that is presented as base64
+ * @author baldersheim
+ */
+public class Base64DataField extends DocsumField {
+ public Base64DataField(String name) {
+ super(name);
+ }
+
+ @Override
+ public String toString() {
+ return "field " + getName() + " type raw";
+ }
+
+ @Override
+ public Object convert(Inspector value) {
+ return new RawBase64(value.asData(Value.empty().asData()));
+ }
+}
diff --git a/container-search/src/main/java/com/yahoo/prelude/fastsearch/DataField.java b/container-search/src/main/java/com/yahoo/prelude/fastsearch/DataField.java
index de07839e3e3..af7d98311f6 100644
--- a/container-search/src/main/java/com/yahoo/prelude/fastsearch/DataField.java
+++ b/container-search/src/main/java/com/yahoo/prelude/fastsearch/DataField.java
@@ -23,7 +23,7 @@ public class DataField extends DocsumField {
super(name);
}
- private Object convert(byte[] value) {
+ private RawData convert(byte[] value) {
return new RawData(value);
}
diff --git a/container-search/src/main/java/com/yahoo/prelude/fastsearch/DocsumField.java b/container-search/src/main/java/com/yahoo/prelude/fastsearch/DocsumField.java
index 329a9caaf91..70ffc71495a 100644
--- a/container-search/src/main/java/com/yahoo/prelude/fastsearch/DocsumField.java
+++ b/container-search/src/main/java/com/yahoo/prelude/fastsearch/DocsumField.java
@@ -2,7 +2,7 @@
package com.yahoo.prelude.fastsearch;
import com.yahoo.data.access.Inspector;
-import com.yahoo.log.LogLevel;
+import java.util.logging.Level;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
@@ -51,6 +51,7 @@ public abstract class DocsumField {
fieldFactory.put("double", DoubleField.class);
fieldFactory.put("string", StringField.class);
fieldFactory.put("data", DataField.class);
+ fieldFactory.put("raw", Base64DataField.class);
fieldFactory.put("longstring", LongstringField.class);
fieldFactory.put("longdata", LongdataField.class);
fieldFactory.put("jsonstring", StructDataField.class);
@@ -58,7 +59,7 @@ public abstract class DocsumField {
fieldFactory.put("xmlstring", XMLField.class);
fieldFactory.put("tensor", TensorField.class);
} catch (Exception e) {
- log.log(LogLevel.ERROR, "Could not initialize docsum decoding properly.", e);
+ log.log(Level.SEVERE, "Could not initialize docsum decoding properly.", e);
}
}
diff --git a/container-search/src/main/java/com/yahoo/prelude/fastsearch/FS4ResourcePool.java b/container-search/src/main/java/com/yahoo/prelude/fastsearch/FS4ResourcePool.java
deleted file mode 100644
index ed9eb72d7dd..00000000000
--- a/container-search/src/main/java/com/yahoo/prelude/fastsearch/FS4ResourcePool.java
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.prelude.fastsearch;
-
-import com.google.inject.Inject;
-import com.yahoo.component.AbstractComponent;
-import com.yahoo.concurrent.ThreadFactoryFactory;
-import com.yahoo.container.QrConfig;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-/**
- * All users will get the same pool instance.
- *
- * @author baldersheim
- */
-public class FS4ResourcePool extends AbstractComponent {
-
- private static final Logger logger = Logger.getLogger(FS4ResourcePool.class.getName());
- private static final AtomicInteger instanceCounter = new AtomicInteger(0);
- private final String serverId;
- private final int instanceId;
- private final ExecutorService executor;
- private final ScheduledExecutorService scheduledExecutor;
-
- @Inject
- public FS4ResourcePool(QrConfig config) {
- this(config.discriminator());
- }
-
- public FS4ResourcePool(String serverId) {
- this.serverId = serverId;
- instanceId = instanceCounter.getAndIncrement();
- String name = "FS4-" + instanceId;
- executor = Executors.newCachedThreadPool(ThreadFactoryFactory.getDaemonThreadFactory(name));
- scheduledExecutor = Executors.newScheduledThreadPool(1, ThreadFactoryFactory.getDaemonThreadFactory(name + ".scheduled"));
- }
-
- /** Returns an unique identifier of the server this runs in */
- public String getServerId() { return serverId; }
- public ExecutorService getExecutor() { return executor; }
- public ScheduledExecutorService getScheduledExecutor() { return scheduledExecutor; }
-
- @Override
- public void deconstruct() {
- logger.log(Level.INFO, "Deconstructing FS4ResourcePool with id '" + instanceId + "'.");
- super.deconstruct();
- executor.shutdown();
- scheduledExecutor.shutdown();
- try {
- executor.awaitTermination(10, TimeUnit.SECONDS);
- scheduledExecutor.awaitTermination(10, TimeUnit.SECONDS);
- } catch (InterruptedException e) {
- logger.warning("Executors failed terminating within timeout of 10 seconds : " + e);
- }
- }
-
-}
diff --git a/container-search/src/main/java/com/yahoo/prelude/fastsearch/FastHit.java b/container-search/src/main/java/com/yahoo/prelude/fastsearch/FastHit.java
index 338add37213..a639f2368a1 100644
--- a/container-search/src/main/java/com/yahoo/prelude/fastsearch/FastHit.java
+++ b/container-search/src/main/java/com/yahoo/prelude/fastsearch/FastHit.java
@@ -42,7 +42,7 @@ public class FastHit extends Hit {
private byte [] globalId;
private transient byte[] sortData = null;
- // TODO I supect this one can be dropped.
+ // TODO I suspect this one can be dropped.
private transient Sorting sortDataSorting = null;
/**
@@ -188,9 +188,14 @@ public class FastHit extends Hit {
summaries.add(0, new SummaryData(this, docsumDef, value, 1 + summaries.size()));
}
+ /** Returns the raw summary data available in this as an unmodifiable list */
+ public List<SummaryData> summaryData() {
+ return Collections.unmodifiableList(summaries);
+ }
+
/**
* Returns values for the features listed in
- * <a href="https://docs.vespa.ai/documentation/reference/search-definitions-reference.html#summary-features">summary-features</a>
+ * <a href="https://docs.vespa.ai/en/reference/schema-reference.html#summary-features">summary-features</a>
* in the rank profile specified in the query producing this.
*/
public FeatureData features() {
diff --git a/container-search/src/main/java/com/yahoo/prelude/fastsearch/FastSearcher.java b/container-search/src/main/java/com/yahoo/prelude/fastsearch/FastSearcher.java
index 14604d61c0a..33ebebd8d49 100644
--- a/container-search/src/main/java/com/yahoo/prelude/fastsearch/FastSearcher.java
+++ b/container-search/src/main/java/com/yahoo/prelude/fastsearch/FastSearcher.java
@@ -21,8 +21,6 @@ import java.io.IOException;
import java.util.Optional;
import java.util.logging.Level;
-import static com.yahoo.container.util.Util.quote;
-
/**
* The searcher which forwards queries to fdispatch nodes, using the fnet/fs4
* network layer.
@@ -83,7 +81,7 @@ public class FastSearcher extends VespaBackEndSearcher {
@Override
public Result doSearch2(Query query, Execution execution) {
- if (dispatcher.searchCluster().groupSize() == 1)
+ if (dispatcher.searchCluster().wantedGroupSize() == 1)
forceSinglePassGrouping(query);
try (SearchInvoker invoker = getSearchInvoker(query)) {
Result result = invoker.search(query, execution);
@@ -160,7 +158,7 @@ public class FastSearcher extends VespaBackEndSearcher {
}
private static Optional<String> quotedSummaryClass(String summaryClass) {
- return Optional.of(summaryClass == null ? "[null]" : quote(summaryClass));
+ return Optional.of(summaryClass == null ? "[null]" : "'" + summaryClass + "'");
}
public String toString() {
diff --git a/container-search/src/main/java/com/yahoo/prelude/fastsearch/GroupingListHit.java b/container-search/src/main/java/com/yahoo/prelude/fastsearch/GroupingListHit.java
index 740b9592efc..2b91941d1d4 100644
--- a/container-search/src/main/java/com/yahoo/prelude/fastsearch/GroupingListHit.java
+++ b/container-search/src/main/java/com/yahoo/prelude/fastsearch/GroupingListHit.java
@@ -26,4 +26,5 @@ public class GroupingListHit extends Hit {
private final List<Grouping> groupingList;
private final DocsumDefinitionSet defs;
+
}
diff --git a/container-search/src/main/java/com/yahoo/prelude/fastsearch/VespaBackEndSearcher.java b/container-search/src/main/java/com/yahoo/prelude/fastsearch/VespaBackEndSearcher.java
index bc3ac6cdef1..d30e67195c3 100644
--- a/container-search/src/main/java/com/yahoo/prelude/fastsearch/VespaBackEndSearcher.java
+++ b/container-search/src/main/java/com/yahoo/prelude/fastsearch/VespaBackEndSearcher.java
@@ -3,6 +3,8 @@ package com.yahoo.prelude.fastsearch;
import com.yahoo.collections.TinyIdentitySet;
import com.yahoo.fs4.DocsumPacket;
+import com.yahoo.prelude.query.CompositeItem;
+import com.yahoo.prelude.query.GeoLocationItem;
import com.yahoo.prelude.query.Item;
import com.yahoo.prelude.query.NullItem;
import com.yahoo.prelude.query.textualrepresentation.TextualQueryRepresentation;
@@ -41,7 +43,7 @@ public abstract class VespaBackEndSearcher extends PingableSearcher {
private String serverId;
/** The set of all document databases available in the backend handled by this searcher */
- private Map<String, DocumentDatabase> documentDbs = new LinkedHashMap<>();
+ private final Map<String, DocumentDatabase> documentDbs = new LinkedHashMap<>();
private DocumentDatabase defaultDocumentDb = null;
/** Default docsum class. null means "unset" and is the default value */
@@ -74,6 +76,19 @@ public abstract class VespaBackEndSearcher extends PingableSearcher {
protected abstract void doPartialFill(Result result, String summaryClass);
+ private boolean hasLocation(Item tree) {
+ if (tree instanceof GeoLocationItem) {
+ return true;
+ }
+ if (tree instanceof CompositeItem) {
+ var composite = (CompositeItem)tree;
+ for (Item child : composite.items()) {
+ if (hasLocation(child)) return true;
+ }
+ }
+ return false;
+ }
+
/**
* Returns whether we need to send the query when fetching summaries.
* This is necessary if the query requests summary features or dynamic snippeting
@@ -87,6 +102,8 @@ public abstract class VespaBackEndSearcher extends PingableSearcher {
DocsumDefinition docsumDefinition = documentDb.getDocsumDefinitionSet().getDocsum(query.getPresentation().getSummary());
if (docsumDefinition.isDynamic()) return true;
+ if (hasLocation(query.getModel().getQueryTree())) return true;
+
// Needed to generate ranking features?
RankProfile rankProfile = documentDb.rankProfiles().get(query.getRanking().getProfile());
if (rankProfile == null) return true; // stay safe
@@ -257,7 +274,7 @@ public abstract class VespaBackEndSearcher extends PingableSearcher {
if (query.getRanking().getLocation() != null) {
s.append(" location=")
- .append(query.getRanking().getLocation().toString());
+ .append(query.getRanking().getLocation().backendString());
}
if (query.getGroupingSessionCache()) {
diff --git a/container-search/src/main/java/com/yahoo/prelude/hitfield/JSONString.java b/container-search/src/main/java/com/yahoo/prelude/hitfield/JSONString.java
index 209bfd08e6b..55438aa35ba 100644
--- a/container-search/src/main/java/com/yahoo/prelude/hitfield/JSONString.java
+++ b/container-search/src/main/java/com/yahoo/prelude/hitfield/JSONString.java
@@ -21,6 +21,7 @@ import java.util.Iterator;
*
* @author Steinar Knutsen
*/
+// TODO Vespa 8: remove methods leaking org.json types (replace with Slime equivalent?)
public class JSONString implements Inspectable {
private Inspector value;
@@ -436,6 +437,8 @@ public class JSONString implements Inspectable {
return content;
}
+ /** @deprecated Use {@link #getContent()} instead and parse content yourself */
+ @Deprecated(forRemoval = true, since = "7")
public Object getParsedJSON() {
initContent();
if (parsedJSON == null) {
@@ -444,6 +447,7 @@ public class JSONString implements Inspectable {
return parsedJSON;
}
+ @Deprecated(forRemoval = true, since = "7")
public void setParsedJSON(Object parsedJSON) {
this.parsedJSON = parsedJSON;
}
diff --git a/container-search/src/main/java/com/yahoo/prelude/hitfield/RawBase64.java b/container-search/src/main/java/com/yahoo/prelude/hitfield/RawBase64.java
new file mode 100644
index 00000000000..134d0bc902a
--- /dev/null
+++ b/container-search/src/main/java/com/yahoo/prelude/hitfield/RawBase64.java
@@ -0,0 +1,18 @@
+package com.yahoo.prelude.hitfield;
+
+import java.util.Base64;
+
+/**
+ * @author baldersheim
+ */
+public class RawBase64 {
+ private final byte[] content;
+ public RawBase64(byte[] content) {
+ this.content = content;
+ }
+
+ @Override
+ public String toString() {
+ return Base64.getEncoder().encodeToString(content);
+ }
+}
diff --git a/container-search/src/main/java/com/yahoo/prelude/query/BoolItem.java b/container-search/src/main/java/com/yahoo/prelude/query/BoolItem.java
index 27045629780..542df9d4b8b 100644
--- a/container-search/src/main/java/com/yahoo/prelude/query/BoolItem.java
+++ b/container-search/src/main/java/com/yahoo/prelude/query/BoolItem.java
@@ -1,6 +1,8 @@
// 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 com.yahoo.processing.IllegalInputException;
+
import java.nio.ByteBuffer;
/**
@@ -59,7 +61,7 @@ public class BoolItem extends TermItem {
switch (stringValue.toLowerCase()) {
case "true" : return true;
case "false" : return false;
- default: throw new IllegalArgumentException("Expected 'true' or 'false', got '" + stringValue + "'");
+ default: throw new IllegalInputException("Expected 'true' or 'false', got '" + stringValue + "'");
}
}
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 64f759dcf9c..4609edb7446 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
@@ -46,7 +46,7 @@ public abstract class CompositeItem extends Item {
Item possibleCycle = i.next();
if (this == possibleCycle) {
- throw new QueryException("Cannot add " + item + " to " + this + " as it would create a cycle");
+ throw new IllegalArgumentException("Cannot add " + item + " to " + this + " as it would create a cycle");
} else if (possibleCycle instanceof CompositeItem) {
ensureNotInSubtree((CompositeItem) possibleCycle);
}
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 b1912e4128d..dd015b5f040 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
@@ -27,6 +27,10 @@ public abstract class CompositeTaggableItem extends CompositeItem implements Tag
/** See {@link TaggableItem#setConnectivity} */
public void setConnectivity(Item item, double connectivity) {
+ if (!(item instanceof TaggableItem)) {
+ throw new IllegalArgumentException("setConnectivity item must be taggable, was: "
+ + item.getClass() + " [" + item + "]");
+ }
setHasUniqueID(true);
item.setHasUniqueID(true);
if (connectedItem != null) {
diff --git a/container-search/src/main/java/com/yahoo/prelude/query/DotProductItem.java b/container-search/src/main/java/com/yahoo/prelude/query/DotProductItem.java
index 455f81c8a90..ee41efd943c 100644
--- a/container-search/src/main/java/com/yahoo/prelude/query/DotProductItem.java
+++ b/container-search/src/main/java/com/yahoo/prelude/query/DotProductItem.java
@@ -1,6 +1,8 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.prelude.query;
+import java.util.Map;
+
/**
* A weighted set query item to be evaluated as a sparse dot product.
*
@@ -11,6 +13,7 @@ package com.yahoo.prelude.query;
public class DotProductItem extends WeightedSetItem {
public DotProductItem(String indexName) { super(indexName); }
+ public DotProductItem(String indexName, Map<Object, Integer> map) { super(indexName, map); }
@Override
public ItemType getItemType() { return ItemType.DOTPRODUCT; }
diff --git a/container-search/src/main/java/com/yahoo/prelude/query/FalseItem.java b/container-search/src/main/java/com/yahoo/prelude/query/FalseItem.java
index 531a89312df..9abc6b2bdaa 100644
--- a/container-search/src/main/java/com/yahoo/prelude/query/FalseItem.java
+++ b/container-search/src/main/java/com/yahoo/prelude/query/FalseItem.java
@@ -4,7 +4,7 @@ package com.yahoo.prelude.query;
import java.nio.ByteBuffer;
/**
- * A query item which never matches. This is sometimes an useful output of query rewriting.
+ * A query item which never matches. This is sometimes a useful output of query rewriting.
*
* @author bratseth
*/
diff --git a/container-search/src/main/java/com/yahoo/prelude/query/GeoLocationItem.java b/container-search/src/main/java/com/yahoo/prelude/query/GeoLocationItem.java
new file mode 100644
index 00000000000..ba5270e7af7
--- /dev/null
+++ b/container-search/src/main/java/com/yahoo/prelude/query/GeoLocationItem.java
@@ -0,0 +1,120 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+package com.yahoo.prelude.query;
+
+import com.google.common.annotations.Beta;
+import com.yahoo.prelude.Location;
+import java.nio.ByteBuffer;
+
+/**
+ * This represents a geo-location in the query tree.
+ * Used for closeness(fieldname) and distance(fieldname) rank features.
+ * @author arnej
+ */
+@Beta
+public class GeoLocationItem extends TermItem {
+
+ private Location location;
+
+ /**
+ * Construct from a Location, which must be geo circle with an attribute set.
+ **/
+ public GeoLocationItem(Location location) {
+ this(location, location.getAttribute());
+ if (! location.hasAttribute()) {
+ throw new IllegalArgumentException("Missing attribute on location: " + location);
+ }
+ }
+
+ /**
+ * Construct from a Location and a field name.
+ * The Location must be a geo circle.
+ * If the Location has an attribute set, it must match the field name.
+ **/
+ public GeoLocationItem(Location location, String fieldName) {
+ super(fieldName, false);
+ if (location.hasAttribute() && ! location.getAttribute().equals(fieldName)) {
+ throw new IllegalArgumentException("Inconsistent attribute on location: " + location.getAttribute() +
+ " versus fieldName: " + fieldName);
+ }
+ if (! location.isGeoCircle()) {
+ throw new IllegalArgumentException("GeoLocationItem only supports Geo Circles, got: " + location);
+ }
+ if (location.hasBoundingBox()) {
+ throw new IllegalArgumentException("GeoLocationItem does not support bounding box, got: " + location);
+ }
+ this.location = new Location(location.toString());
+ this.location.setAttribute(null); // keep this in (superclass) indexName only
+ setNormalizable(false);
+ }
+
+ public Location getLocation() {
+ return location;
+ }
+
+ @Override
+ public String getRawWord() {
+ return stringValue();
+ }
+
+ @Override
+ public ItemType getItemType() {
+ return ItemType.GEO_LOCATION_TERM;
+ }
+
+ @Override
+ public String getName() {
+ return "GEO_LOCATION";
+ }
+
+ @Override
+ public String stringValue() {
+ return location.toString();
+ }
+
+ @Override
+ public void setValue(String value) {
+ throw new UnsupportedOperationException("Cannot setValue("+value+") on "+getName());
+ }
+
+ @Override
+ public int hashCode() {
+ return java.util.Objects.hash(super.hashCode(), location);
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ if ( ! super.equals(object)) return false;
+ GeoLocationItem other = (GeoLocationItem) object; // Ensured by superclass
+ if ( ! location.equals(other.location)) return false;
+ return true;
+ }
+
+ @Override
+ public String getIndexedString() {
+ return location.toString();
+ }
+
+ @Override
+ protected void encodeThis(ByteBuffer buffer) {
+ super.encodeThis(buffer); // takes care of index bytes
+ // TODO: use a better format for encoding the location on the wire.
+ putString(location.backendString(), buffer);
+ }
+
+ @Override
+ public int getNumWords() {
+ return 1;
+ }
+
+ @Override
+ public boolean isStemmed() {
+ return true;
+ }
+
+ @Override
+ public boolean isWords() {
+ return false;
+ }
+
+}
diff --git a/container-search/src/main/java/com/yahoo/prelude/query/HasIndexItem.java b/container-search/src/main/java/com/yahoo/prelude/query/HasIndexItem.java
index 6641dee9780..c70ee9e4def 100644
--- a/container-search/src/main/java/com/yahoo/prelude/query/HasIndexItem.java
+++ b/container-search/src/main/java/com/yahoo/prelude/query/HasIndexItem.java
@@ -2,8 +2,7 @@
package com.yahoo.prelude.query;
/**
- * An interface for items where it is useful to access an associated
- * index name.
+ * An interface for items where it is useful to access an index name.
*
* @author Steinar Knutsen
*/
@@ -11,7 +10,7 @@ public interface HasIndexItem {
String getIndexName();
- /** @return how many phrase words does this item contain */
+ /** Returns how many phrase words does this item contain */
int getNumWords();
}
diff --git a/container-search/src/main/java/com/yahoo/prelude/query/IndexedItem.java b/container-search/src/main/java/com/yahoo/prelude/query/IndexedItem.java
index 3dafa230eb8..cba289fa5d8 100644
--- a/container-search/src/main/java/com/yahoo/prelude/query/IndexedItem.java
+++ b/container-search/src/main/java/com/yahoo/prelude/query/IndexedItem.java
@@ -3,7 +3,7 @@ package com.yahoo.prelude.query;
/**
- * Interface for Items that is indexed
+ * Interface for Items that are indexed
*
* @author Lars Christian Jensen
*/
diff --git a/container-search/src/main/java/com/yahoo/prelude/query/IntItem.java b/container-search/src/main/java/com/yahoo/prelude/query/IntItem.java
index 714e8f9cb5e..1591d31f749 100644
--- a/container-search/src/main/java/com/yahoo/prelude/query/IntItem.java
+++ b/container-search/src/main/java/com/yahoo/prelude/query/IntItem.java
@@ -111,7 +111,7 @@ public class IntItem extends TermItem {
}
catch (IllegalArgumentException e) {
throw new IllegalArgumentException("'" + expression + "' is not an int item expression: " +
- "Expected NUMBER, '<'NUMBER, '>'NUMBER or ('['|'<')NUMBER;NUMBER(;NUMBER)?(']'|'>')", e);
+ "Expected NUMBER, '<'NUMBER, '>'NUMBER or ('['|'<')NUMBER;NUMBER(;NUMBER)?(']'|'>')", e);
}
}
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 ea65bc7d7d2..467fbd3127c 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
@@ -32,7 +32,7 @@ public abstract class Item implements Cloneable {
/**
* The definitions in Item.ItemType must match the ones in
- * searchlib/src/searchlib/parsequery/parse.h
+ * searchlib/src/vespa/searchlib/parsequery/parse.h
*/
public static enum ItemType {
OR(0),
@@ -42,7 +42,7 @@ public abstract class Item implements Cloneable {
WORD(4),
INT(5),
PHRASE(6),
- PAREN(7),
+ PAREN(7), // TODO not used - remove on Vespa 8
PREFIX(8),
SUBSTRING(9),
NEAR(11),
@@ -60,11 +60,12 @@ public abstract class Item implements Cloneable {
PREDICATE_QUERY(23),
REGEXP(24),
WORD_ALTERNATIVES(25),
- NEAREST_NEIGHBOR(26);
+ NEAREST_NEIGHBOR(26),
+ GEO_LOCATION_TERM(27);
public final int code;
- private ItemType(int code) {
+ ItemType(int code) {
this.code = code;
}
diff --git a/container-search/src/main/java/com/yahoo/prelude/query/ItemHelper.java b/container-search/src/main/java/com/yahoo/prelude/query/ItemHelper.java
index b535ae7b8bd..cbdaa04a49f 100644
--- a/container-search/src/main/java/com/yahoo/prelude/query/ItemHelper.java
+++ b/container-search/src/main/java/com/yahoo/prelude/query/ItemHelper.java
@@ -5,8 +5,9 @@ import java.util.Iterator;
import java.util.List;
/**
- * Helper function for Item
- * @author <a href="mailto:arnebef@yahoo-inc.com">Arne Bergene Fossaa</a>
+ * Helper functions for Item
+ *
+ * @author Arne Bergene Fossaa
*/
public class ItemHelper {
@@ -77,5 +78,4 @@ public class ItemHelper {
}
}
-
}
diff --git a/container-search/src/main/java/com/yahoo/prelude/query/NearestNeighborItem.java b/container-search/src/main/java/com/yahoo/prelude/query/NearestNeighborItem.java
index 35b87ec0190..4fe977bff2b 100644
--- a/container-search/src/main/java/com/yahoo/prelude/query/NearestNeighborItem.java
+++ b/container-search/src/main/java/com/yahoo/prelude/query/NearestNeighborItem.java
@@ -2,8 +2,8 @@
package com.yahoo.prelude.query;
-import com.google.common.annotations.Beta;
import com.yahoo.compress.IntegerCompressor;
+import com.yahoo.prelude.query.textualrepresentation.Discloser;
import java.nio.ByteBuffer;
@@ -16,12 +16,14 @@ import java.nio.ByteBuffer;
*
* @author arnej
*/
-@Beta
public class NearestNeighborItem extends SimpleTaggableItem {
private int targetNumHits = 0;
+ private int hnswExploreAdditionalHits = 0;
+ private double distanceThreshold = Double.POSITIVE_INFINITY;
+ private boolean approximate = true;
private String field;
- private String queryTensorName;
+ private final String queryTensorName;
public NearestNeighborItem(String fieldName, String queryTensorName) {
this.field = fieldName;
@@ -34,12 +36,30 @@ public class NearestNeighborItem extends SimpleTaggableItem {
/** Returns the field name */
public String getIndexName() { return field; }
+ /** Returns the distance threshold for nearest-neighbor hits */
+ public double getDistanceThreshold () { return this.distanceThreshold ; }
+
+ /** Returns the number of extra hits to explore in HNSW algorithm */
+ public int getHnswExploreAdditionalHits() { return hnswExploreAdditionalHits; }
+
+ /** Returns whether approximation is allowed */
+ public boolean getAllowApproximate() { return approximate; }
+
/** Returns the name of the query tensor */
public String getQueryTensorName() { return queryTensorName; }
/** Set the K number of hits to produce */
public void setTargetNumHits(int target) { this.targetNumHits = target; }
+ /** Set the distance threshold for nearest-neighbor hits */
+ public void setDistanceThreshold(double threshold) { this.distanceThreshold = threshold; }
+
+ /** Set the number of extra hits to explore in HNSW algorithm */
+ public void setHnswExploreAdditionalHits(int num) { this.hnswExploreAdditionalHits = num; }
+
+ /** Set whether approximation is allowed */
+ public void setAllowApproximate(boolean value) { this.approximate = value; }
+
@Override
public void setIndexName(String index) { this.field = index; }
@@ -57,7 +77,11 @@ public class NearestNeighborItem extends SimpleTaggableItem {
super.encodeThis(buffer);
putString(field, buffer);
putString(queryTensorName, buffer);
+ int approxNum = (approximate ? 1 : 0);
IntegerCompressor.putCompressedPositiveNumber(targetNumHits, buffer);
+ IntegerCompressor.putCompressedPositiveNumber(approxNum, buffer);
+ IntegerCompressor.putCompressedPositiveNumber(hnswExploreAdditionalHits, buffer);
+ buffer.putDouble(distanceThreshold);
return 1; // number of encoded stack dump items
}
@@ -65,6 +89,21 @@ public class NearestNeighborItem extends SimpleTaggableItem {
protected void appendBodyString(StringBuilder buffer) {
buffer.append("{field=").append(field);
buffer.append(",queryTensorName=").append(queryTensorName);
- buffer.append(",targetNumHits=").append(targetNumHits).append("}");
+ buffer.append(",hnsw.exploreAdditionalHits=").append(hnswExploreAdditionalHits);
+ buffer.append(",distanceThreshold=").append(distanceThreshold);
+ buffer.append(",approximate=").append(approximate);
+ buffer.append(",targetHits=").append(targetNumHits).append("}");
}
+
+ @Override
+ public void disclose(Discloser discloser) {
+ super.disclose(discloser);
+ discloser.addProperty("field", field);
+ discloser.addProperty("queryTensorName", queryTensorName);
+ discloser.addProperty("hnsw.exploreAdditionalHits", hnswExploreAdditionalHits);
+ discloser.addProperty("distanceThreshold", distanceThreshold);
+ discloser.addProperty("approximate", approximate);
+ discloser.addProperty("targetHits", targetNumHits);
+ }
+
}
diff --git a/container-search/src/main/java/com/yahoo/prelude/query/PhraseSegmentItem.java b/container-search/src/main/java/com/yahoo/prelude/query/PhraseSegmentItem.java
index 542f1393852..9b34fd7d62b 100644
--- a/container-search/src/main/java/com/yahoo/prelude/query/PhraseSegmentItem.java
+++ b/container-search/src/main/java/com/yahoo/prelude/query/PhraseSegmentItem.java
@@ -19,16 +19,13 @@ public class PhraseSegmentItem extends IndexedSegmentItem {
/** Whether this was explicitly written as a phrase using quotes by the user */
private boolean explicit = false;
- /**
- * Creates a phrase containing the same words and state (as pertinent) as
- * the given SegmentAndItem.
- */
- public PhraseSegmentItem(AndSegmentItem segAnd) {
- super(segAnd.getRawWord(), segAnd.stringValue(), segAnd.isFromQuery(), segAnd.isStemmed(), segAnd.getOrigin());
- if (segAnd.getItemCount() > 0) {
- WordItem w = (WordItem) segAnd.getItem(0);
+ /** Creates a phrase containing the same words and state (as pertinent) as the given SegmentAndItem. */
+ public PhraseSegmentItem(AndSegmentItem andSegment) {
+ super(andSegment.getRawWord(), andSegment.stringValue(), andSegment.isFromQuery(), andSegment.isStemmed(), andSegment.getOrigin());
+ if (andSegment.getItemCount() > 0) {
+ WordItem w = (WordItem) andSegment.getItem(0);
setIndexName(w.getIndexName());
- for (Iterator<Item> i = segAnd.getItemIterator(); i.hasNext();) {
+ for (Iterator<Item> i = andSegment.getItemIterator(); i.hasNext();) {
WordItem word = (WordItem) i.next();
addWordItem(word);
}
diff --git a/container-search/src/main/java/com/yahoo/prelude/query/QueryCanonicalizer.java b/container-search/src/main/java/com/yahoo/prelude/query/QueryCanonicalizer.java
index 88bae76b26d..2bf20bf7c5a 100644
--- a/container-search/src/main/java/com/yahoo/prelude/query/QueryCanonicalizer.java
+++ b/container-search/src/main/java/com/yahoo/prelude/query/QueryCanonicalizer.java
@@ -94,7 +94,7 @@ public class QueryCanonicalizer {
if (composite instanceof RankItem || composite instanceof NotItem) {
collapseLevels(composite, composite.getItemIterator()); // collapse the first item only
}
- else if (composite instanceof AndItem || composite instanceof OrItem) {
+ else if (composite instanceof AndItem || composite instanceof OrItem || composite instanceof WeakAndItem) {
for (ListIterator<Item> i = composite.getItemIterator(); i.hasNext(); )
collapseLevels(composite, i);
}
@@ -106,10 +106,17 @@ public class QueryCanonicalizer {
Item child = i.next();
if (child == null) return;
if (child.getClass() != composite.getClass()) return;
+ if (child instanceof WeakAndItem && !equalWeakAndSettings((WeakAndItem)child, (WeakAndItem)composite)) return;
i.remove();
moveChildren((CompositeItem) child, i);
}
-
+
+ private static boolean equalWeakAndSettings(WeakAndItem a, WeakAndItem b) {
+ if ( ! a.getIndexName().equals(b.getIndexName())) return false;
+ if (a.getN() != b.getN()) return false;
+ return true;
+ }
+
private static void moveChildren(CompositeItem from, ListIterator<Item> toIterator) {
for (ListIterator<Item> i = from.getItemIterator(); i.hasNext(); )
toIterator.add(i.next());
diff --git a/container-search/src/main/java/com/yahoo/prelude/query/QueryException.java b/container-search/src/main/java/com/yahoo/prelude/query/QueryException.java
index 34898827c2e..7d029d69077 100644
--- a/container-search/src/main/java/com/yahoo/prelude/query/QueryException.java
+++ b/container-search/src/main/java/com/yahoo/prelude/query/QueryException.java
@@ -4,10 +4,11 @@ package com.yahoo.prelude.query;
/**
* Runtime exception to mark errors in query parsing.
*
- * @author <a href="mailto:steinar@yahoo-inc.com">Steinar Knutsen</a>
+ * @author Steinar Knutsen
+ * @deprecated no methods throw this
*/
+@Deprecated // TODO: Remove on Vespa 8
public class QueryException extends RuntimeException {
- private static final long serialVersionUID = -2975856668328596533L;
public QueryException(String message) {
super(message);
@@ -16,4 +17,5 @@ public class QueryException extends RuntimeException {
public QueryException(String message, Throwable cause) {
super(message, cause);
}
+
}
diff --git a/container-search/src/main/java/com/yahoo/prelude/query/RegExpItem.java b/container-search/src/main/java/com/yahoo/prelude/query/RegExpItem.java
index 2a5c6135d71..ec3744306ed 100644
--- a/container-search/src/main/java/com/yahoo/prelude/query/RegExpItem.java
+++ b/container-search/src/main/java/com/yahoo/prelude/query/RegExpItem.java
@@ -7,9 +7,10 @@ import java.util.regex.Pattern;
/**
* Match a field with the contained regular expression.
*
- * @author <a href="mailto:steinar@yahoo-inc.com">Steinar Knutsen</a>
+ * @author Steinar Knutsen
*/
public class RegExpItem extends TermItem {
+
private String expression;
private Pattern regexp;
diff --git a/container-search/src/main/java/com/yahoo/prelude/query/SameElementItem.java b/container-search/src/main/java/com/yahoo/prelude/query/SameElementItem.java
index ac4e8b98b03..58bbcd7315c 100644
--- a/container-search/src/main/java/com/yahoo/prelude/query/SameElementItem.java
+++ b/container-search/src/main/java/com/yahoo/prelude/query/SameElementItem.java
@@ -50,6 +50,7 @@ public class SameElementItem extends NonReducibleCompositeItem {
super.adding(item);
//TODO See if we can require only SimpleIndexedItem instead of TermItem
Validator.ensureInstanceOf("Child item", item, TermItem.class);
+ Validator.ensureNotInstanceOf("Child item", item, WordAlternativesItem.class);
TermItem asTerm = (TermItem) item;
Validator.ensureNonEmpty("Struct fieldname", asTerm.getIndexName());
Validator.ensureNonEmpty("Query term", asTerm.getIndexedString());
diff --git a/container-search/src/main/java/com/yahoo/prelude/query/SegmentItem.java b/container-search/src/main/java/com/yahoo/prelude/query/SegmentItem.java
index 1c3eb261f90..f70bf8021ff 100644
--- a/container-search/src/main/java/com/yahoo/prelude/query/SegmentItem.java
+++ b/container-search/src/main/java/com/yahoo/prelude/query/SegmentItem.java
@@ -102,7 +102,7 @@ public abstract class SegmentItem extends CompositeItem implements BlockItem {
}
private void dontAdd() {
- throw new QueryException("Tried to add item to an immutable segment.");
+ throw new IllegalArgumentException("Tried to add item to an immutable segment.");
}
public Item removeItem(int index) {
@@ -120,7 +120,7 @@ public abstract class SegmentItem extends CompositeItem implements BlockItem {
}
private void dontRemove() {
- throw new QueryException("Tried to remove an item from an immutable segment.");
+ throw new IllegalArgumentException("Tried to remove an item from an immutable segment.");
}
// TODO: Add a getItemIterator which is safe for immutability
diff --git a/container-search/src/main/java/com/yahoo/prelude/query/SegmentingRule.java b/container-search/src/main/java/com/yahoo/prelude/query/SegmentingRule.java
index 2a7089ed20e..891a7628a65 100644
--- a/container-search/src/main/java/com/yahoo/prelude/query/SegmentingRule.java
+++ b/container-search/src/main/java/com/yahoo/prelude/query/SegmentingRule.java
@@ -7,9 +7,10 @@ package com.yahoo.prelude.query;
* the default is creating a phrase, but for business reasons, some East Asian
* languages use an AND instead.
*
- * @author <a href="mailto:steinar@yahoo-inc.com">Steinar Knutsen</a>
- * @since 5.1.28
+ * @author Steinar Knutsen
*/
public enum SegmentingRule {
+
LANGUAGE_DEFAULT, PHRASE, BOOLEAN_AND;
+
}
diff --git a/container-search/src/main/java/com/yahoo/prelude/query/SimpleTaggableItem.java b/container-search/src/main/java/com/yahoo/prelude/query/SimpleTaggableItem.java
index 6f82f340f4b..afdc859a5b7 100644
--- a/container-search/src/main/java/com/yahoo/prelude/query/SimpleTaggableItem.java
+++ b/container-search/src/main/java/com/yahoo/prelude/query/SimpleTaggableItem.java
@@ -27,6 +27,10 @@ public abstract class SimpleTaggableItem extends Item implements TaggableItem {
@Override
public void setConnectivity(Item item, double connectivity) {
+ if (!(item instanceof TaggableItem)) {
+ throw new IllegalArgumentException("setConnectivity item must be taggable, was: "
+ + item.getClass() + " [" + item + "]");
+ }
setHasUniqueID(true);
item.setHasUniqueID(true);
if (connectedItem != null) {
diff --git a/container-search/src/main/java/com/yahoo/prelude/query/SuffixItem.java b/container-search/src/main/java/com/yahoo/prelude/query/SuffixItem.java
index 7baca8d60ba..cb87ca85d9a 100644
--- a/container-search/src/main/java/com/yahoo/prelude/query/SuffixItem.java
+++ b/container-search/src/main/java/com/yahoo/prelude/query/SuffixItem.java
@@ -5,9 +5,8 @@ package com.yahoo.prelude.query;
/**
* A word that matches a suffix of words instead of a complete word.
*
- * @author <a href="mailto:steinar@yahoo-inc.com">Steinar Knutsen</a>
+ * @author Steinar Knutsen
*/
-
public class SuffixItem extends WordItem {
public SuffixItem(String suffix) {
diff --git a/container-search/src/main/java/com/yahoo/prelude/query/TaggableSegmentItem.java b/container-search/src/main/java/com/yahoo/prelude/query/TaggableSegmentItem.java
index 35df4bf443c..83122a0a512 100644
--- a/container-search/src/main/java/com/yahoo/prelude/query/TaggableSegmentItem.java
+++ b/container-search/src/main/java/com/yahoo/prelude/query/TaggableSegmentItem.java
@@ -31,6 +31,10 @@ public abstract class TaggableSegmentItem extends SegmentItem implements Taggabl
/** See {@link TaggableItem#setConnectivity} */
public void setConnectivity(Item item, double connectivity) {
+ if (!(item instanceof TaggableItem)) {
+ throw new IllegalArgumentException("setConnectivity item must be taggable, was: "
+ + item.getClass() + " [" + item + "]");
+ }
setHasUniqueID(true);
item.setHasUniqueID(true);
if (connectedItem != null) {
diff --git a/container-search/src/main/java/com/yahoo/prelude/query/TermItem.java b/container-search/src/main/java/com/yahoo/prelude/query/TermItem.java
index 2c33e7a2630..5df0cd120b2 100644
--- a/container-search/src/main/java/com/yahoo/prelude/query/TermItem.java
+++ b/container-search/src/main/java/com/yahoo/prelude/query/TermItem.java
@@ -97,37 +97,19 @@ public abstract class TermItem extends SimpleIndexedItem implements BlockItem {
@Override
public int getTermCount() { return 1; }
- /**
- * This refers to whether accent removal is a meaningful and possible
- * operation for this word. It should be named "isTransformable" or similar,
- * but for historical reasons that is not the case. This method has nothing
- * to do with Unicode normalization.
- *
- * @return true if accent removal can/should be performed
- */
- public boolean isNormalizable() {
- return normalizable;
- }
+ /** Returns whether accent removal is a meaningful and possible operation for this word. */
+ public boolean isNormalizable() { return normalizable; }
/**
- * This refers to whether accent removal is a meaningful and possible
- * operation for this word. It should be named "isTransformable" or similar,
- * but for historical reasons that is not the case. This method has nothing
- * to do with Unicode normalization.
+ * Sets whether accent removal is a meaningful and possible operation for this word.
*
* @param normalizable set to true if accent removal can/should be performed
*/
- public void setNormalizable(boolean normalizable) {
- this.normalizable = normalizable;
- }
+ public void setNormalizable(boolean normalizable) { this.normalizable = normalizable; }
@Override
- public SegmentingRule getSegmentingRule() {
- return segmentingRule;
- }
+ public SegmentingRule getSegmentingRule() { return segmentingRule; }
- public void setSegmentingRule(SegmentingRule segmentingRule) {
- this.segmentingRule = segmentingRule;
- }
+ public void setSegmentingRule(SegmentingRule segmentingRule) { this.segmentingRule = segmentingRule; }
}
diff --git a/container-search/src/main/java/com/yahoo/prelude/query/TermType.java b/container-search/src/main/java/com/yahoo/prelude/query/TermType.java
index a0467703b13..b818e916e71 100644
--- a/container-search/src/main/java/com/yahoo/prelude/query/TermType.java
+++ b/container-search/src/main/java/com/yahoo/prelude/query/TermType.java
@@ -20,6 +20,8 @@ public class TermType {
public static TermType PHRASE = new TermType("phrase", PhraseItem.class, null, "\"");
+ public static TermType EQUIV = new TermType("equiv", EquivItem.class, null, "");
+
public static TermType DEFAULT = new TermType("", CompositeItem.class, AndItem.class, "");
public final String name;
@@ -57,8 +59,7 @@ public class TermType {
* Returns an instance of the class corresponding to the given type, AndItem
* if this is the DEFAULT type
*
- * @throws RuntimeException
- * if an instance could not be created
+ * @throws RuntimeException if an instance could not be created
*/
public Item createItemClass() {
try {
diff --git a/container-search/src/main/java/com/yahoo/prelude/query/ToolBox.java b/container-search/src/main/java/com/yahoo/prelude/query/ToolBox.java
index 72ba012ab07..5a6d4cd6382 100644
--- a/container-search/src/main/java/com/yahoo/prelude/query/ToolBox.java
+++ b/container-search/src/main/java/com/yahoo/prelude/query/ToolBox.java
@@ -18,8 +18,7 @@ public final class ToolBox {
* {@link ToolBox#visit(QueryVisitor, Item)}. Return true to visit the
* sub-items of the given item, return false to ignore the sub-items.
*
- * @param item
- * each item in the query tree
+ * @param item each item in the query tree
* @return whether or not to visit the sub-items of the argument item
* (and then invoke the {@link #onExit()} method)
*/
diff --git a/container-search/src/main/java/com/yahoo/prelude/query/WandItem.java b/container-search/src/main/java/com/yahoo/prelude/query/WandItem.java
index a70d653b90a..c5679e113f1 100644
--- a/container-search/src/main/java/com/yahoo/prelude/query/WandItem.java
+++ b/container-search/src/main/java/com/yahoo/prelude/query/WandItem.java
@@ -5,6 +5,7 @@ import com.yahoo.compress.IntegerCompressor;
import com.yahoo.prelude.query.textualrepresentation.Discloser;
import java.nio.ByteBuffer;
+import java.util.Map;
/**
* A weighted set query item to be evaluated as a Wand with dot product scoring.
@@ -17,7 +18,7 @@ import java.nio.ByteBuffer;
*/
public class WandItem extends WeightedSetItem {
- private int targetNumHits;
+ private final int targetNumHits;
private double scoreThreshold = 0;
private double thresholdBoostFactor = 1;
@@ -33,6 +34,18 @@ public class WandItem extends WeightedSetItem {
}
/**
+ * Creates an empty WandItem.
+ *
+ * @param fieldName the name of the weighted set field to search with this WandItem.
+ * @param targetNumHits the target for minimum number of hits to produce by the backend search operator handling this WandItem.
+ * @param tokens the tokens to search for
+ */
+ public WandItem(String fieldName, int targetNumHits, Map<Object, Integer> tokens) {
+ super(fieldName, tokens);
+ this.targetNumHits = targetNumHits;
+ }
+
+ /**
* Sets the initial score threshold used by the backend search operator handling this WandItem.
* The score of a document must be larger than this threshold in order to be considered a match.
* Default value is 0.0.
@@ -86,13 +99,10 @@ public class WandItem extends WeightedSetItem {
protected void appendHeadingString(StringBuilder buffer) {
buffer.append(getName());
buffer.append("(");
- buffer.append(targetNumHits);
- buffer.append(",");
- buffer.append(scoreThreshold);
- buffer.append(",");
+ buffer.append(targetNumHits).append(",");
+ buffer.append(scoreThreshold).append(",");
buffer.append(thresholdBoostFactor);
- buffer.append(")");
- buffer.append(" ");
+ buffer.append(") ");
}
@Override
diff --git a/container-search/src/main/java/com/yahoo/prelude/query/WeakAndItem.java b/container-search/src/main/java/com/yahoo/prelude/query/WeakAndItem.java
index 033986dc90f..e8817a44133 100644
--- a/container-search/src/main/java/com/yahoo/prelude/query/WeakAndItem.java
+++ b/container-search/src/main/java/com/yahoo/prelude/query/WeakAndItem.java
@@ -18,110 +18,96 @@ import java.nio.ByteBuffer;
*/
public final class WeakAndItem extends NonReducibleCompositeItem {
- private int N;
+ /** The default N used if none is specified: 100 */
+ public static final int defaultN = 100;
+
+ private int n;
private String index;
private int scoreThreshold = 0;
- public ItemType getItemType() {
- return ItemType.WEAK_AND;
+ /** Creates a WAND item with default N */
+ public WeakAndItem() {
+ this(defaultN);
}
- public String getName() {
- return "WAND";
+ public WeakAndItem(int N) {
+ this("", N);
}
/**
- * Make a WAND item with no children. You can mention a common index or you can mention it on each child.
+ * Make a WeakAnd item with no children. You can mention a common index or you can mention it on each child.
*
- * @param index The index it shall search.
- * @param N the target for minimum number of hits to produce;
+ * @param index the index to search
+ * @param n the target for minimum number of hits to produce;
* a backend will not suppress any hits in the operator
* until N hits have been produced.
- **/
- public WeakAndItem(String index, int N) {
- this.N = N;
+ */
+ public WeakAndItem(String index, int n) {
+ this.n = n;
this.index = (index == null) ? "" : index;
}
- public WeakAndItem(int N) {
- this("", N);
- }
- /** Sets the index name of all subitems of this */
+ @Override
+ public ItemType getItemType() { return ItemType.WEAK_AND; }
+
+ @Override
+ public String getName() { return "WEAKAND"; }
+
+ @Override
public void setIndexName(String index) {
String toSet = (index == null) ? "" : index;
super.setIndexName(toSet);
this.index = toSet;
}
- public String getIndexName() {
- return index;
- }
+ public String getIndexName() { return index; }
/** Appends the heading of this string - <code>[getName()]([limit]) </code> */
+ @Override
protected void appendHeadingString(StringBuilder buffer) {
buffer.append(getName());
buffer.append("(");
- buffer.append(N);
- buffer.append(")");
- buffer.append(" ");
+ buffer.append(n);
+ buffer.append(") ");
}
- /** The default N used if none is specified: 100 */
- public static final int defaultN = 100;
-
- /** Creates a WAND item with default N */
- public WeakAndItem() {
- this(defaultN);
- }
-
- public int getN() {
- return N;
- }
+ public int getN() { return n; }
- public void setN(int N) {
- this.N = N;
- }
+ public void setN(int N) { this.n = N; }
- public int getScoreThreshold() {
- return scoreThreshold;
- }
+ @Deprecated // TODO: Remove on Vespa 8
+ public int getScoreThreshold() { return scoreThreshold; }
/**
- * Sets the score threshold used by the backend search operator handling this WeakAndItem.
- * This threshold is currently only used if the WeakAndItem is searching a RISE index field.
- * The score threshold then specifies the minimum dot product score a match needs to be part of the result set.
- * Default value is 0.
+ * Noop.
*
- * @param scoreThreshold the score threshold.
+ * @deprecated has no effect
*/
- public void setScoreThreshold(int scoreThreshold) {
- this.scoreThreshold = scoreThreshold;
- }
+ @Deprecated // TODO: Remove on Vespa 8
+ public void setScoreThreshold(int scoreThreshold) { this.scoreThreshold = scoreThreshold; }
+ @Override
protected void encodeThis(ByteBuffer buffer) {
super.encodeThis(buffer);
- IntegerCompressor.putCompressedPositiveNumber(N, buffer);
+ IntegerCompressor.putCompressedPositiveNumber(n, buffer);
putString(index, buffer);
}
@Override
public void disclose(Discloser discloser) {
super.disclose(discloser);
- discloser.addProperty("N", N);
+ discloser.addProperty("N", n);
}
- public int hashCode() {
- return super.hashCode() + 31 * N;
- }
+ @Override
+ public int hashCode() { return super.hashCode() + 31 * n; }
- /**
- * Returns whether this item is of the same class and
- * contains the same state as the given item
- */
+ /** 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 (!super.equals(object)) return false;
WeakAndItem other = (WeakAndItem) object; // Ensured by superclass
- if (this.N != other.N) return false;
+ if (this.n != other.n) return false;
return true;
}
diff --git a/container-search/src/main/java/com/yahoo/prelude/query/WeightedSetItem.java b/container-search/src/main/java/com/yahoo/prelude/query/WeightedSetItem.java
index 0e74099174f..15354f5f7d3 100644
--- a/container-search/src/main/java/com/yahoo/prelude/query/WeightedSetItem.java
+++ b/container-search/src/main/java/com/yahoo/prelude/query/WeightedSetItem.java
@@ -25,9 +25,8 @@ import java.util.Map;
*/
public class WeightedSetItem extends SimpleTaggableItem {
- private String indexName = "";
-
- private CopyOnWriteHashMap<Object,Integer> set = new CopyOnWriteHashMap<>(1000);
+ private String indexName;
+ private CopyOnWriteHashMap<Object,Integer> set;
/** Creates an empty weighted set; note you must provide an index name up front */
public WeightedSetItem(String indexName) {
@@ -36,6 +35,15 @@ public class WeightedSetItem extends SimpleTaggableItem {
} else {
this.indexName = indexName;
}
+ set = new CopyOnWriteHashMap<>(1000);
+ }
+ public WeightedSetItem(String indexName, Map<Object, Integer> map) {
+ if (indexName == null) {
+ this.indexName = "";
+ } else {
+ this.indexName = indexName;
+ }
+ set = new CopyOnWriteHashMap<>(map);
}
public Integer addToken(long value, int weight) {
diff --git a/container-search/src/main/java/com/yahoo/prelude/query/WordAlternativesItem.java b/container-search/src/main/java/com/yahoo/prelude/query/WordAlternativesItem.java
index 97c68ee3da8..d2df2aa6c89 100644
--- a/container-search/src/main/java/com/yahoo/prelude/query/WordAlternativesItem.java
+++ b/container-search/src/main/java/com/yahoo/prelude/query/WordAlternativesItem.java
@@ -12,15 +12,13 @@ import com.google.common.collect.ImmutableList;
import com.yahoo.compress.IntegerCompressor;
/**
- * A set words with differing exactness scores to be used for literal boost
- * ranking.
+ * A set of words with differing exactness scores to be used for literal boost ranking.
*
* @author Steinar Knutsen
*/
public class WordAlternativesItem extends TermItem {
private List<Alternative> alternatives;
- private int maxIndex;
public static final class Alternative {
@@ -49,7 +47,6 @@ public class WordAlternativesItem extends TermItem {
public void setAlternatives(Collection<Alternative> terms) {
this.alternatives = uniqueAlternatives(terms);
- setMaxIndex();
}
private static ImmutableList<Alternative> uniqueAlternatives(Collection<Alternative> terms) {
@@ -68,27 +65,6 @@ public class WordAlternativesItem extends TermItem {
return ImmutableList.copyOf(uniqueTerms);
}
- private void setMaxIndex() {
- int maxIndex = 0;
- int currentIndex = 0;
- double maxScore = 0.0d;
- boolean first = true;
- for (Alternative val : this.alternatives) {
- if (first) {
- first = false;
- maxIndex = 0;
- maxScore = val.exactness;
- } else {
- if (val.exactness > maxScore) {
- maxScore = val.exactness;
- maxIndex = currentIndex;
- }
- }
- ++currentIndex;
- }
- this.maxIndex = maxIndex;
- }
-
@Override
public String stringValue() {
StringBuilder builder = new StringBuilder();
@@ -145,17 +121,14 @@ public class WordAlternativesItem extends TermItem {
}
/**
- * Return an immutable snapshot of the contained terms. This list will not
- * reflect later changes to the item.
+ * Return an immutable snapshot of the contained terms. This list will not reflect later changes to the item.
*
- * @return an immutable list of word alternatives and their respective
- * scores
+ * @return an immutable list of word alternatives and their respective scores
*/
public List<Alternative> getAlternatives() {
return alternatives;
}
-
@Override
public void encodeThis(ByteBuffer target) {
super.encodeThis(target);
@@ -172,17 +145,14 @@ public class WordAlternativesItem extends TermItem {
* equal or higher exactness score. If the term string is present with a
* lower exactness score, the new, higher score will take precedence.
*
- * @param term
- * one of several string interpretations of the input word
- * @param exactness
- * how close the term string matches what the user input
+ * @param term one of several string interpretations of the input word
+ * @param exactness how close the term string matches what the user input
*/
public void addTerm(String term, double exactness) {
// do note, Item is Cloneable, and overwriting the reference is what
// saves us from overriding the method
- if (alternatives.stream().anyMatch((a) -> a.word.equals(term) && a.exactness >= exactness )) {
- return;
- }
+ if (alternatives.stream().anyMatch((a) -> a.word.equals(term) && a.exactness >= exactness )) return;
+
List<Alternative> newTerms = new ArrayList<>(alternatives.size() + 1);
newTerms.addAll(alternatives);
newTerms.add(new Alternative(term, exactness));
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 cd8579be7f0..732466748eb 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
@@ -19,7 +19,6 @@ import java.util.*;
* @author bratseth
* @author Steinar Knutsen
*/
-@SuppressWarnings("deprecation")
public abstract class AbstractParser implements CustomParser {
/** The current submodes of this parser */
@@ -48,7 +47,7 @@ public abstract class AbstractParser implements CustomParser {
* of these may be active at the same time. SubModes are activated or
* deactivated by specifying special indexes in the query.
*/
- final class Submodes {
+ static final class Submodes {
/**
* Url mode allows "_" and "-" as word characters. Default is false
@@ -326,6 +325,7 @@ public abstract class AbstractParser implements CustomParser {
*
* @param indexName the index name which preceeded this token, or null if none
* @param token the token to segment
+ * @param quoted whether this segment is within quoted text
* @return the resulting item
*/
// TODO: The segmenting stuff is a mess now, this will fix it:
@@ -341,7 +341,7 @@ public abstract class AbstractParser implements CustomParser {
// This can be solved by making the segment method language independent by
// always producing a query item containing the token text and resolve it to a WordItem or
// SegmentItem after parsing and language detection.
- protected Item segment(String indexName, Token token) {
+ protected Item segment(String indexName, Token token, boolean quoted) {
String normalizedToken = normalize(token.toString());
if (token.isSpecial()) {
@@ -361,12 +361,13 @@ public abstract class AbstractParser implements CustomParser {
if (segments.size() == 0) {
return null;
}
+
if (segments.size() == 1) {
return new WordItem(segments.get(0), "", true, token.substring);
}
CompositeItem composite;
- if (indexFacts.getIndex(indexName).getPhraseSegmenting()) {
+ if (indexFacts.getIndex(indexName).getPhraseSegmenting() || quoted) {
composite = new PhraseSegmentItem(token.toString(), normalizedToken, true, false, token.substring);
}
else {
diff --git a/container-search/src/main/java/com/yahoo/prelude/query/parser/AdvancedParser.java b/container-search/src/main/java/com/yahoo/prelude/query/parser/AdvancedParser.java
index e3d1b280a5a..8b878417912 100644
--- a/container-search/src/main/java/com/yahoo/prelude/query/parser/AdvancedParser.java
+++ b/container-search/src/main/java/com/yahoo/prelude/query/parser/AdvancedParser.java
@@ -147,11 +147,11 @@ public class AdvancedParser extends StructuredParser {
return equiv;
}
return topLevelItem;
- } else if (isTheWord("wand", item)) {
- int n=consumeNumericArgument();
- if (n==0)
- n=WeakAndItem.defaultN;
- if (topLevelIsClosed || !(topLevelItem instanceof WeakAndItem) || n!=((WeakAndItem)topLevelItem).getN()) {
+ } else if (isTheWord("wand", item) || isTheWord("weakand", item)) {
+ int n = consumeNumericArgument();
+ if (n == 0)
+ n = WeakAndItem.defaultN;
+ if (topLevelIsClosed || !(topLevelItem instanceof WeakAndItem) || n != ((WeakAndItem)topLevelItem).getN()) {
WeakAndItem wand = new WeakAndItem();
wand.setN(n);
wand.addItem(topLevelItem);
@@ -206,7 +206,7 @@ public class AdvancedParser extends StructuredParser {
if (!tokens.currentIs(LBRACE)) return 0;
tokens.skip(LBRACE);
if (!tokens.currentIsNoIgnore(NUMBER)) throw new IllegalArgumentException("Expected an integer argument");
- int distance=Integer.valueOf(tokens.next().image);
+ int distance = Integer.valueOf(tokens.next().image);
if (!tokens.skip(Token.Kind.RBRACE)) throw new IllegalArgumentException("Expected a right brace following the argument");
return distance;
}
diff --git a/container-search/src/main/java/com/yahoo/prelude/query/parser/AllParser.java b/container-search/src/main/java/com/yahoo/prelude/query/parser/AllParser.java
index d9b969757c2..793d394801f 100644
--- a/container-search/src/main/java/com/yahoo/prelude/query/parser/AllParser.java
+++ b/container-search/src/main/java/com/yahoo/prelude/query/parser/AllParser.java
@@ -30,6 +30,7 @@ public class AllParser extends SimpleParser {
super(environment);
}
+ @Override
protected Item parseItems() {
int position = tokens.getPosition();
try {
@@ -111,6 +112,7 @@ public class AllParser extends SimpleParser {
protected Item negativeItem() {
int position = tokens.getPosition();
Item item = null;
+ boolean isComposited = false;
try {
if ( ! tokens.skip(MINUS)) return null;
if (tokens.currentIsNoIgnore(SPACE)) return null;
@@ -120,6 +122,7 @@ public class AllParser extends SimpleParser {
item = compositeItem();
if (item != null) {
+ isComposited = true;
if (item instanceof OrItem) { // Turn into And
AndItem and = new AndItem();
@@ -136,9 +139,11 @@ public class AllParser extends SimpleParser {
// Heuristic overdrive engaged!
// Interpret -N as a positive item matching a negative number (by backtracking out of this)
// but not if there is an explicit index (such as -a:b)
+ // but interpret -(N) as a negative item matching a positive number
// but interpret --N as a negative item matching a negative number
if (item instanceof IntItem &&
((IntItem)item).getIndexName().isEmpty() &&
+ ! isComposited &&
! ((IntItem)item).getNumber().startsWith(("-")))
item = null;
diff --git a/container-search/src/main/java/com/yahoo/prelude/query/parser/AnyParser.java b/container-search/src/main/java/com/yahoo/prelude/query/parser/AnyParser.java
index dd836e9c8e1..b714a1d8b34 100644
--- a/container-search/src/main/java/com/yahoo/prelude/query/parser/AnyParser.java
+++ b/container-search/src/main/java/com/yahoo/prelude/query/parser/AnyParser.java
@@ -35,21 +35,12 @@ public class AnyParser extends SimpleParser {
return anyItems(true);
}
- Item parseFilter(String filter, Language queryLanguage, Set<String> searchDefinitions) {
- return parseFilter(filter, queryLanguage, environment.getIndexFacts().newSession(searchDefinitions, Collections.emptySet()));
- }
-
Item parseFilter(String filter, Language queryLanguage, IndexFacts.Session indexFacts) {
- Item filterRoot;
-
setState(queryLanguage, indexFacts);
tokenize(filter, null, indexFacts, queryLanguage);
- filterRoot = anyItems(true);
-
- if (filterRoot == null) {
- return null;
- }
+ Item filterRoot = anyItems(true);
+ if (filterRoot == null) return null;
markAllTermsAsFilters(filterRoot);
return filterRoot;
@@ -61,18 +52,10 @@ public class AnyParser extends SimpleParser {
try {
tokens.skipMultiple(PLUS);
+ if ( ! tokens.skipMultiple(MINUS)) return null;
+ if (tokens.currentIsNoIgnore(SPACE)) return null;
- if (!tokens.skipMultiple(MINUS)) {
- return null;
- }
-
- if (tokens.currentIsNoIgnore(SPACE)) {
- return null;
- }
-
- if (item == null) {
- item = indexableItem();
- }
+ item = indexableItem();
if (item == null) {
item = compositeItem();
@@ -88,13 +71,13 @@ public class AnyParser extends SimpleParser {
}
}
}
- if (item!=null)
+ if (item != null)
item.setProtected(true);
+
return item;
} finally {
- if (item == null) {
+ if (item == null)
tokens.setPosition(position);
- }
}
}
diff --git a/container-search/src/main/java/com/yahoo/prelude/query/parser/PhraseParser.java b/container-search/src/main/java/com/yahoo/prelude/query/parser/PhraseParser.java
index 6d4401aca04..12f63276269 100644
--- a/container-search/src/main/java/com/yahoo/prelude/query/parser/PhraseParser.java
+++ b/container-search/src/main/java/com/yahoo/prelude/query/parser/PhraseParser.java
@@ -23,8 +23,7 @@ public class PhraseParser extends AbstractParser {
/**
* Ignores everything but words and numbers
*
- * @return a phrase item if several words/numbers was found,
- * a word item if only one was found
+ * @return a phrase item if several words/numbers was found, a word item if only one was found
*/
private Item forcedPhrase() {
Item firstWord = null;
@@ -38,7 +37,7 @@ public class PhraseParser extends AbstractParser {
}
// Note, this depends on segment never creating AndItems when quoted
// (the second argument) is true.
- Item newWord = segment(null, token);
+ Item newWord = segment(null, token, true);
if (firstWord == null) { // First pass
firstWord = newWord;
diff --git a/container-search/src/main/java/com/yahoo/prelude/query/parser/SimpleParser.java b/container-search/src/main/java/com/yahoo/prelude/query/parser/SimpleParser.java
index 9ddfea6dffb..0686a4bdb43 100644
--- a/container-search/src/main/java/com/yahoo/prelude/query/parser/SimpleParser.java
+++ b/container-search/src/main/java/com/yahoo/prelude/query/parser/SimpleParser.java
@@ -50,32 +50,28 @@ abstract class SimpleParser extends StructuredParser {
private Item anyItemsBody(boolean topLevel) {
Item topLevelItem = null;
NotItem not = null;
- Item item;
+ Item item = null;
do {
- item = null;
-
- if (item == null) {
- item = positiveItem();
- if (item != null) {
- if (not == null) {
- not = new NotItem();
- not.addPositiveItem(item);
- topLevelItem = combineItems(topLevelItem, not);
- } else {
- not.addPositiveItem(item);
- }
+ item = positiveItem();
+ if (item != null) {
+ if (not == null) {
+ not = new NotItem();
+ not.addPositiveItem(item);
+ topLevelItem = combineItems(topLevelItem, not);
+ } else {
+ not.addPositiveItem(item);
}
}
if (item == null) {
item = negativeItem();
if (item != null) {
- if (not == null && item != null) {
+ if (not == null) {
not = new NotItem();
not.addNegativeItem(item);
topLevelItem = combineItems(topLevelItem, not);
- } else if (item != null) {
+ } else {
not.addNegativeItem(item);
}
}
@@ -97,9 +93,8 @@ abstract class SimpleParser extends StructuredParser {
if (item != null) {
if (topLevelItem == null) {
topLevelItem = item;
- } else if (needNewTopLevel(topLevelItem, item)) {
+ } else if (needNewORTopLevel(topLevelItem, item)) {
CompositeItem newTop = new OrItem();
-
newTop.addItem(topLevelItem);
newTop.addItem(item);
topLevelItem = newTop;
@@ -144,21 +139,13 @@ abstract class SimpleParser extends StructuredParser {
}
}
-
- /** Says whether we need a new top level item given the new item */
- private boolean needNewTopLevel(Item topLevelItem, Item item) {
- if (item == null) {
- return false;
- }
- if (topLevelItem instanceof TermItem) {
- return true;
- }
- if (topLevelItem instanceof PhraseItem) {
- return true;
- }
- if (topLevelItem instanceof BlockItem) {
- return true;
- }
+ /** Says whether we need a new top level OR item given the new item */
+ private boolean needNewORTopLevel(Item topLevelItem, Item item) {
+ if (item == null) return false;
+ if (topLevelItem instanceof TermItem) return true;
+ if (topLevelItem instanceof PhraseItem) return true;
+ if (topLevelItem instanceof BlockItem) return true;
+ if ( topLevelItem instanceof AndItem) return true;
return false;
}
diff --git a/container-search/src/main/java/com/yahoo/prelude/query/parser/SpecialTokenRegistry.java b/container-search/src/main/java/com/yahoo/prelude/query/parser/SpecialTokenRegistry.java
deleted file mode 100644
index 53fc552204f..00000000000
--- a/container-search/src/main/java/com/yahoo/prelude/query/parser/SpecialTokenRegistry.java
+++ /dev/null
@@ -1,137 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.prelude.query.parser;
-
-import com.yahoo.config.subscription.ConfigGetter;
-import com.yahoo.config.subscription.ConfigSubscriber;
-import com.yahoo.vespa.configdefinition.SpecialtokensConfig;
-import com.yahoo.vespa.configdefinition.SpecialtokensConfig.Tokenlist;
-import com.yahoo.vespa.configdefinition.SpecialtokensConfig.Tokenlist.Tokens;
-
-import java.util.*;
-import java.util.logging.Logger;
-
-
-/**
- * A <i>registry</i> which is responsible for knowing the current
- * set of special tokens. The default registry returns empty token lists
- * for all names. Usage of this registry is multithread safe.
- *
- * @author bratseth
- */
-public class SpecialTokenRegistry {
-
- /** The log of this */
- private static Logger log = Logger.getLogger(SpecialTokens.class.getName());
-
- private static final SpecialTokens nullSpecialTokens = new SpecialTokens();
-
- /**
- * The current authorative special token lists, indexed on name.
- * These lists are unmodifiable and used directly by clients of this
- */
- private Map<String,SpecialTokens> specialTokenMap = new HashMap<>();
-
- private boolean frozen = false;
-
- /**
- * Creates an empty special token registry which
- * does not subscribe to any configuration
- */
- public SpecialTokenRegistry() {}
-
- /**
- * Create a special token registry which subscribes to the specialtokens
- * configuration. Only used for testing.
- */
- public SpecialTokenRegistry(String configId) {
- try {
- build(new ConfigGetter<>(SpecialtokensConfig.class).getConfig(configId));
- } catch (Exception e) {
- log.config(
- "No special tokens are configured (" + e.getMessage() + ")");
- }
- }
-
- /**
- * Create a special token registry from a configuration object. This is the production code path.
- */
- public SpecialTokenRegistry(SpecialtokensConfig config) {
- if (config != null) {
- build(config);
- }
- freeze();
- }
-
- private void freeze() {
- frozen = true;
- }
-
- private void build(SpecialtokensConfig config) {
- List<SpecialTokens> list = new ArrayList<>();
- for (Iterator<Tokenlist> i = config.tokenlist().iterator(); i.hasNext();) {
- Tokenlist tokenList = i.next();
- SpecialTokens tokens = new SpecialTokens(tokenList.name());
-
- for (Iterator<Tokens> j = tokenList.tokens().iterator(); j.hasNext();) {
- Tokens token = j.next();
- tokens.addSpecialToken(token.token(), token.replace());
- }
- tokens.freeze();
- list.add(tokens);
- }
- addSpecialTokens(list);
- }
-
- /**
- * Adds a SpecialTokens instance to the registry. That is, add the
- * tokens contained for the name of the SpecialTokens instance
- * given.
- *
- * @param specialTokens the SpecialTokens object to add
- */
- public void addSpecialTokens(SpecialTokens specialTokens) {
- ensureNotFrozen();
- List<SpecialTokens> list = new ArrayList<>();
- list.add(specialTokens);
- addSpecialTokens(list);
-
- }
-
- private void ensureNotFrozen() {
- if (frozen) {
- throw new IllegalStateException("Tried to modify a frozen SpecialTokenRegistry instance.");
- }
- }
-
- private void addSpecialTokens(List<SpecialTokens> list) {
- HashMap<String,SpecialTokens> tokens = new HashMap<>(specialTokenMap);
- for(SpecialTokens t: list) {
- tokens.put(t.getName(),t);
- }
- specialTokenMap = tokens;
- }
-
-
- /**
- * Returns the currently authorative list of special tokens for
- * a given name.
- *
- * @param name the name of the special tokens to return
- * null, the empth string or the string "default" returns
- * the default ones
- * @return a read-only list of SpecialToken instances, an empty list if this name
- * has no special tokens
- */
- public SpecialTokens getSpecialTokens(String name) {
- if (name == null || name.trim().equals("")) {
- name = "default";
- }
- SpecialTokens specialTokens = specialTokenMap.get(name);
-
- if (specialTokens == null) {
- return nullSpecialTokens;
- }
- return specialTokens;
- }
-
-}
diff --git a/container-search/src/main/java/com/yahoo/prelude/query/parser/SpecialTokens.java b/container-search/src/main/java/com/yahoo/prelude/query/parser/SpecialTokens.java
deleted file mode 100644
index c206ff7567e..00000000000
--- a/container-search/src/main/java/com/yahoo/prelude/query/parser/SpecialTokens.java
+++ /dev/null
@@ -1,168 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.prelude.query.parser;
-
-import com.yahoo.log.LogLevel;
-import com.yahoo.prelude.query.Substring;
-
-import java.util.*;
-import java.util.logging.Logger;
-
-import static com.yahoo.language.LinguisticsCase.toLowerCase;
-
-/**
- * A list of special tokens - string that should be threated as word
- * no matter what they contain. Special tokens are case insensitive.
- *
- * @author bratseth
- */
-public class SpecialTokens {
-
- private static final Logger log = Logger.getLogger(SpecialTokens.class.getName());
-
- private String name;
-
- private List<SpecialToken> specialTokens = new ArrayList<>();
-
- private boolean frozen = false;
-
- private int currentMaximumLength = 0;
-
- /** Creates a null list of special tokens */
- public SpecialTokens() {
- this.name = "(null)";
- }
-
- public SpecialTokens(String name) {
- this.name = name;
- }
-
- /** Returns the name of this special tokens list */
- public String getName() {
- return name;
- }
-
- /**
- * Adds a special token to this
- *
- * @param token the special token string to add
- * @param replace the token to replace instances of the special token with,
- * or null to keep the token
- */
- public void addSpecialToken(String token, String replace) {
- ensureNotFrozen();
- if (!caseIndependentLength(token)) {
- return;
- }
- // TODO are special tokens correctly unicode normalized in reagards to query parsing?
- final SpecialToken specialTokenToAdd = new SpecialToken(token, replace);
- currentMaximumLength = Math.max(currentMaximumLength, specialTokenToAdd.token.length());
- specialTokens.add(specialTokenToAdd);
- Collections.sort(specialTokens);
- }
-
- private boolean caseIndependentLength(String token) {
- // XXX not fool proof length test, should test codepoint by codepoint for mixed case user input? not even that will necessarily be 100% robust...
- String asLow = toLowerCase(token);
- // TODO put along with the global toLowerCase
- String asHigh = token.toUpperCase(Locale.ENGLISH);
- if (asLow.length() != token.length() || asHigh.length() != token.length()) {
- log.log(LogLevel.ERROR, "Special token '" + token + "' has case sensitive length. Ignoring the token."
- + " Please report this message in a bug to the Vespa team.");
- return false;
- } else {
- return true;
- }
- }
-
- /**
- * Returns the special token starting at the start of the given string, or null if no
- * special token starts at this string
- *
- * @param string the string to search for a special token at the start position
- * @param substring true to allow the special token to be followed by a character which does not
- * mark the end of a token
- */
- public SpecialToken tokenize(String string, boolean substring) {
- // XXX detonator pattern token.length may be != the length of the
- // matching data in string, ref caseIndependentLength(String)
- final String input = toLowerCase(string.substring(0, Math.min(string.length(), currentMaximumLength)));
- for (Iterator<SpecialToken> i = specialTokens.iterator(); i.hasNext();) {
- SpecialTokens.SpecialToken special = i.next();
-
- if (input.startsWith(special.token())) {
- if (string.length() == special.token().length() || substring || tokenEndsAt(special.token().length(), string))
- return special;
- }
- }
- return null;
- }
-
- private boolean tokenEndsAt(int position,String string) {
- return !Character.isLetterOrDigit(string.charAt(position));
- }
-
- /** Returns the number of special tokens in this */
- public int size() {
- return specialTokens.size();
- }
-
- private void ensureNotFrozen() {
- if (frozen) {
- throw new IllegalStateException("Tried to modify a frozen SpecialTokens instance.");
- }
- }
-
- public void freeze() {
- frozen = true;
- }
-
- /** An immutable special token */
- public final static class SpecialToken implements Comparable<SpecialToken> {
-
- private String token;
-
- private String replace;
-
- public SpecialToken(String token, String replace) {
- this.token = toLowerCase(token);
- if (replace == null || replace.trim().equals("")) {
- this.replace = this.token;
- } else {
- this.replace = toLowerCase(replace);
- }
- }
-
- /** Returns the special token */
- public String token() {
- return token;
- }
-
- /** Returns the right replace value, never null or an empty string */
- public String replace() {
- return replace;
- }
-
- @Override
- public int compareTo(SpecialToken other) {
- if (this.token().length() < other.token().length()) return 1;
- if (this.token().length() == other.token().length()) return 0;
- return -1;
- }
-
- @Override
- public boolean equals(Object other) {
- if (other == this) return true;
- if ( ! (other instanceof SpecialToken)) return false;
- return Objects.equals(this.token, ((SpecialToken)other).token);
- }
-
- @Override
- public int hashCode() { return token.hashCode(); }
-
- public Token toToken(int start,String rawSource) {
- return new Token(Token.Kind.WORD, replace(), true, new Substring(start, start + token.length(), rawSource)); // XXX: Unsafe?
- }
-
- }
-
-}
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 5e292a06b0f..267880fc97a 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
@@ -406,13 +406,18 @@ abstract class StructuredParser extends AbstractParser {
}
}
- /** Words for phrases also permits numerals as words */
- private Item phraseWord(String indexName, boolean insidePhrase) {
+ /**
+ * Words for phrases also permits numerals as words
+ *
+ * @param quoted whether we are consuming text within quoted
+ * @param insidePhrase whether we are consuming additional items for an existing phrase
+ */
+ private Item phraseWord(String indexName, boolean quoted, boolean insidePhrase) {
int position = tokens.getPosition();
Item item = null;
try {
- item = word(indexName);
+ item = word(indexName, quoted);
if (item == null && tokens.currentIs(NUMBER)) {
Token t = tokens.next();
@@ -434,17 +439,19 @@ abstract class StructuredParser extends AbstractParser {
/**
* Returns a WordItem if this is a non CJK query,
- * a WordItem or PhraseSegmentItem if this is a CJK query,
+ * a WordItem or SegmentItem if this is a CJK query,
* null if the current item is not a word
+ *
+ * @param quoted whether this token is inside quotes
*/
- private Item word(String indexName) {
+ private Item word(String indexName, boolean quoted) {
int position = tokens.getPosition();
Item item = null;
try {
- if (!tokens.currentIs(WORD)
- && ((!tokens.currentIs(NUMBER) && !tokens.currentIs(MINUS)
- && !tokens.currentIs(UNDERSCORE)) || (!submodes.url && !submodes.site))) {
+ if ( ! tokens.currentIs(WORD)
+ && ((!tokens.currentIs(NUMBER) && !tokens.currentIs(MINUS)
+ && !tokens.currentIs(UNDERSCORE)) || (!submodes.url && !submodes.site))) {
return null;
}
Token word = tokens.next();
@@ -452,7 +459,7 @@ abstract class StructuredParser extends AbstractParser {
if (submodes.url) {
item = new WordItem(word, true);
} else {
- item = segment(indexName, word);
+ item = segment(indexName, word, quoted);
}
if (submodes.url || submodes.site) {
@@ -539,7 +546,7 @@ abstract class StructuredParser extends AbstractParser {
quoted = !quoted;
}
- Item word = phraseWord(indexName, (firstWord != null) || (composite != null));
+ Item word = phraseWord(indexName, quoted, (firstWord != null) || (composite != null));
if (word == null) {
if (tokens.skipMultiple(QUOTE)) {
@@ -557,6 +564,7 @@ abstract class StructuredParser extends AbstractParser {
if (composite != null) {
composite.addItem(word);
+ connectLastTermsIn(composite);
} else if (firstWord != null) {
if (submodes.site || submodes.url) {
UriItem uriItem = new UriItem();
@@ -584,6 +592,7 @@ abstract class StructuredParser extends AbstractParser {
}
composite.addItem(firstWord);
composite.addItem(word);
+ connectLastTermsIn(composite);
} else if (word instanceof PhraseItem) {
composite = (PhraseItem)word;
} else {
@@ -654,6 +663,25 @@ abstract class StructuredParser extends AbstractParser {
}
}
+ private void connectLastTermsIn(CompositeItem composite) {
+ int items = composite.items().size();
+ if (items < 2) return;
+ Item nextToLast = composite.items().get(items - 2);
+ if (nextToLast instanceof AndSegmentItem) {
+ var subItems = ((AndSegmentItem) nextToLast).items();
+ nextToLast = subItems.get(subItems.size() - 1);
+ }
+ if ( ! (nextToLast instanceof TermItem)) return;
+ Item last = composite.items().get(items - 1);
+ if (last instanceof AndSegmentItem) {
+ last = ((AndSegmentItem) last).items().get(0);
+ }
+ if (last instanceof TaggableItem) {
+ TermItem t1 = (TermItem) nextToLast;
+ t1.setConnectivity(last, 1);
+ }
+ }
+
private boolean addStartMarking() {
if (submodes.explicitAnchoring() && tokens.currentIs(HAT)) {
tokens.skip();
diff --git a/container-search/src/main/java/com/yahoo/prelude/query/parser/Tokenizer.java b/container-search/src/main/java/com/yahoo/prelude/query/parser/Tokenizer.java
index 61f09e2f7b7..b71bd57539f 100644
--- a/container-search/src/main/java/com/yahoo/prelude/query/parser/Tokenizer.java
+++ b/container-search/src/main/java/com/yahoo/prelude/query/parser/Tokenizer.java
@@ -3,11 +3,11 @@ package com.yahoo.prelude.query.parser;
import com.yahoo.language.Linguistics;
import com.yahoo.language.process.CharacterClasses;
+import com.yahoo.language.process.SpecialTokens;
import com.yahoo.prelude.Index;
import com.yahoo.prelude.IndexFacts;
import com.yahoo.prelude.query.Substring;
-import java.util.Collection;
import java.util.Collections;
import java.util.List;
@@ -20,7 +20,7 @@ import static com.yahoo.prelude.query.parser.Token.Kind.*;
*/
public final class Tokenizer {
- private List<Token> tokens = new java.util.ArrayList<>();
+ private final List<Token> tokens = new java.util.ArrayList<>();
private String source;
@@ -38,7 +38,7 @@ public final class Tokenizer {
/** Creates a tokenizer which initializes from a given Linguistics */
public Tokenizer(Linguistics linguistics) {
- this.characterClasses=linguistics.getCharacterClasses();
+ this.characterClasses = linguistics.getCharacterClasses();
}
/**
@@ -108,8 +108,7 @@ public final class Tokenizer {
if (i >= source.length()) break;
int c = source.codePointAt(i);
- if (characterClasses.isLetterOrDigit(c)
- || (c == '\'' && acceptApostropheAsWordCharacter(currentIndex))) {
+ if (characterClasses.isLetterOrDigit(c) || (c == '\'' && acceptApostropheAsWordCharacter(currentIndex))) {
i = consumeWordOrNumber(i, currentIndex);
} else if (Character.isWhitespace(c)) {
addToken(SPACE, " ", i, i + 1);
@@ -187,7 +186,6 @@ public final class Tokenizer {
return true;
}
- @SuppressWarnings({"deprecation"})
private Index determineCurrentIndex(Index defaultIndex, IndexFacts.Session indexFacts) {
int backtrack = tokens.size();
int tokencnt = 0;
@@ -203,7 +201,7 @@ public final class Tokenizer {
}
StringBuilder tmp = new StringBuilder();
for (int i = 0; i < tokencnt; i++) {
- Token useToken = tokens.get(backtrack+i);
+ Token useToken = tokens.get(backtrack + i);
tmp.append(useToken.image);
}
String indexName = tmp.toString();
@@ -219,22 +217,20 @@ public final class Tokenizer {
}
private int consumeSpecialToken(int start) {
- SpecialTokens.SpecialToken specialToken=getSpecialToken(start);
- if (specialToken==null) return start;
- tokens.add(specialToken.toToken(start,source));
- return start + specialToken.token().length();
+ SpecialTokens.Token token = getSpecialToken(start);
+ if (token == null) return start;
+ tokens.add(toToken(token, start, source));
+ return start + token.token().length();
}
- private SpecialTokens.SpecialToken getSpecialToken(int start) {
- if (specialTokens == null) {
- return null;
- }
+ private SpecialTokens.Token getSpecialToken(int start) {
+ if (specialTokens == null) return null;
return specialTokens.tokenize(source.substring(start), substringSpecialTokens);
}
private int consumeExact(int start,Index index) {
if (index.getExactTerminator() == null) return consumeHeuristicExact(start);
- return consumeToTerminator(start,index.getExactTerminator());
+ return consumeToTerminator(start, index.getExactTerminator());
}
private boolean looksLikeExactEnd(int end) {
@@ -328,7 +324,6 @@ public final class Tokenizer {
wantEndQuote = true;
actualStart = curPos+1;
} else if (wantEndQuote && looksLikeExactEnd(curPos+1)) {
- // System.err.println("seen quoted token from "+actualStart+" to "+curPos);
seenSome = true;
wantEndQuote = false;
isQuoted = true;
@@ -435,7 +430,7 @@ public final class Tokenizer {
if (suffStar) {
addToken(STAR, "*", starPos, starPos + 1);
}
- tokens.add(new Token(WORD, source.substring(actualStart, end), true, new Substring(actualStart, end, source))); // XXX: Unsafe?
+ tokens.add(new Token(WORD, source.substring(actualStart, end), true, new Substring(actualStart, end, source)));
// skip terminating quote
if (isQuoted) {
@@ -451,17 +446,17 @@ public final class Tokenizer {
break;
end++;
}
- tokens.add(new Token(WORD, source.substring(start, end), true, new Substring(start, end, source))); // XXX: Unsafe start?
- if (end>=source.length())
+ tokens.add(new Token(WORD, source.substring(start, end), true, new Substring(start, end, source)));
+ if (end >= source.length())
return end;
else
- return end+terminator.length(); // Don't create a token for the terminator
+ return end + terminator.length(); // Don't create a token for the terminator
}
private boolean terminatorStartsAt(int start,String terminator) {
- int terminatorPosition=0;
- while ((terminatorPosition+start)<source.length()) {
- if (source.charAt(start+terminatorPosition)!=terminator.charAt(terminatorPosition))
+ int terminatorPosition = 0;
+ while ((terminatorPosition + start) < source.length()) {
+ if (source.charAt(start+terminatorPosition) != terminator.charAt(terminatorPosition))
return false;
terminatorPosition++;
if (terminatorPosition >= terminator.length())
@@ -473,7 +468,7 @@ public final class Tokenizer {
/** Consumes a word or number <i>and/or possibly</i> a special token starting within this word or number */
private int consumeWordOrNumber(int start, Index currentIndex) {
int tokenEnd = start;
- SpecialTokens.SpecialToken substringSpecialToken = null;
+ SpecialTokens.Token substringToken = null;
boolean digitsOnly = true;
// int underscores = 0;
// boolean underscoresOnly = true;
@@ -481,8 +476,8 @@ public final class Tokenizer {
while (tokenEnd < source.length()) {
if (substringSpecialTokens) {
- substringSpecialToken=getSpecialToken(tokenEnd);
- if (substringSpecialToken!=null) break;
+ substringToken = getSpecialToken(tokenEnd);
+ if (substringToken != null) break;
}
int c = source.codePointAt(tokenEnd);
@@ -506,7 +501,7 @@ public final class Tokenizer {
// underscoresOnly = false;
quotesOnly = false;
} else if (c == '\'') {
- if (!acceptApostropheAsWordCharacter(currentIndex)) {
+ if ( ! acceptApostropheAsWordCharacter(currentIndex)) {
break;
}
// Otherwise consume apostrophes...
@@ -530,19 +525,26 @@ public final class Tokenizer {
}
}
- if (substringSpecialToken==null)
+ if (substringToken == null)
return --tokenEnd;
// TODO: test the logic around tokenEnd with friends
- addToken(substringSpecialToken.toToken(tokenEnd,source));
- return --tokenEnd+substringSpecialToken.token().length();
+ addToken(toToken(substringToken, tokenEnd, source));
+ return --tokenEnd + substringToken.token().length();
}
private void addToken(Token.Kind kind, String word, int start, int end) {
- addToken(new Token(kind, word, false, new Substring(start, end, source))); // XXX: Unsafe?
+ addToken(new Token(kind, word, false, new Substring(start, end, source)));
}
private void addToken(Token token) {
tokens.add(token);
}
+ public Token toToken(SpecialTokens.Token specialToken, int start, String rawSource) {
+ return new Token(Token.Kind.WORD,
+ specialToken.replacement(),
+ true,
+ new Substring(start, start + specialToken.token().length(), rawSource)); // XXX: Unsafe?
+ }
+
}
diff --git a/container-search/src/main/java/com/yahoo/prelude/querytransform/CJKSearcher.java b/container-search/src/main/java/com/yahoo/prelude/querytransform/CJKSearcher.java
index ae8c289a5b0..785477d6df7 100644
--- a/container-search/src/main/java/com/yahoo/prelude/querytransform/CJKSearcher.java
+++ b/container-search/src/main/java/com/yahoo/prelude/querytransform/CJKSearcher.java
@@ -56,26 +56,31 @@ public class CJKSearcher extends Searcher {
AndItem replacement = new AndItem();
for (ListIterator<Item> i = ((CompositeItem) root).getItemIterator(); i.hasNext();) {
Item item = i.next();
- if (item instanceof WordItem) replacement.addItem(item);
- else if (item instanceof PhraseSegmentItem) {
+ if (item instanceof WordItem)
+ replacement.addItem(item);
+ else if (item instanceof PhraseSegmentItem)
replacement.addItem(new AndSegmentItem((PhraseSegmentItem) item));
- }
- else replacement.addItem(item); // should never run, but hey... just convert and hope it's OK :)
+ else
+ replacement.addItem(item); // should never get here
}
return replacement;
- } else if (root instanceof PhraseSegmentItem) {
+ }
+ else if (root instanceof PhraseSegmentItem) {
PhraseSegmentItem asSegment = (PhraseSegmentItem) root;
- if (asSegment.isExplicit() || hasOverlappingTokens(asSegment)) return root;
- else return new AndSegmentItem(asSegment);
- } else if (root instanceof SegmentItem) {
+ if (asSegment.isExplicit() || hasOverlappingTokens(asSegment))
+ return root;
+ else
+ return new AndSegmentItem(asSegment);
+ }
+ else if (root instanceof SegmentItem) {
return root; // avoid descending into AndSegmentItems and similar
- } else if (root instanceof CompositeItem) {
+ }
+ else if (root instanceof CompositeItem) {
for (ListIterator<Item> i = ((CompositeItem) root).getItemIterator(); i.hasNext();) {
Item item = i.next();
Item transformedItem = transform(item);
- if (item != transformedItem) {
+ if (item != transformedItem)
i.set(transformedItem);
- }
}
return root;
}
@@ -96,8 +101,7 @@ public class CJKSearcher extends Searcher {
* We have overlapping tokens (see
* com.yahoo.prelude.querytransform.test.CJKSearcherTestCase
* .testCjkQueryWithOverlappingTokens and ParseTestCase for an explanation)
- * if the sum of length of tokens is greater than the lenght of the original
- * word
+ * if the sum of length of tokens is greater than the length of the original word
*/
private boolean hasOverlappingTokens(PhraseSegmentItem segments) {
int segmentsLength=0;
diff --git a/container-search/src/main/java/com/yahoo/prelude/querytransform/NoRankingSearcher.java b/container-search/src/main/java/com/yahoo/prelude/querytransform/NoRankingSearcher.java
index 7456f33d00f..30074f0306d 100644
--- a/container-search/src/main/java/com/yahoo/prelude/querytransform/NoRankingSearcher.java
+++ b/container-search/src/main/java/com/yahoo/prelude/querytransform/NoRankingSearcher.java
@@ -12,8 +12,7 @@ import com.yahoo.search.query.Sorting.FieldOrder;
import com.yahoo.search.searchchain.Execution;
/**
- * Avoid doing relevance calculations if sorting only
- * on attributes.
+ * Avoid doing relevance calculations if sorting only on attributes.
*
* @author Steinar Knutsen
*/
diff --git a/container-search/src/main/java/com/yahoo/prelude/querytransform/PhraseMatcher.java b/container-search/src/main/java/com/yahoo/prelude/querytransform/PhraseMatcher.java
index f49e49c1771..be33c0ee9e1 100644
--- a/container-search/src/main/java/com/yahoo/prelude/querytransform/PhraseMatcher.java
+++ b/container-search/src/main/java/com/yahoo/prelude/querytransform/PhraseMatcher.java
@@ -64,9 +64,9 @@ public class PhraseMatcher {
* @throws IllegalArgumentException if FSA is null
*/
public PhraseMatcher(FSA phraseAutomatonFSA,boolean ignorePluralForm) {
- if(phraseAutomatonFSA==null) throw new IllegalArgumentException("FSA is null");
- this.ignorePluralForm=ignorePluralForm;
- phraseFSA=phraseAutomatonFSA;
+ if (phraseAutomatonFSA == null) throw new NullPointerException("FSA is null");
+ this.ignorePluralForm = ignorePluralForm;
+ phraseFSA = phraseAutomatonFSA;
}
public boolean isEmpty() { return phraseFSA == null; }
diff --git a/container-search/src/main/java/com/yahoo/prelude/querytransform/QueryRewrite.java b/container-search/src/main/java/com/yahoo/prelude/querytransform/QueryRewrite.java
index 5a936d42ccc..329e886c3d3 100644
--- a/container-search/src/main/java/com/yahoo/prelude/querytransform/QueryRewrite.java
+++ b/container-search/src/main/java/com/yahoo/prelude/querytransform/QueryRewrite.java
@@ -186,6 +186,7 @@ public class QueryRewrite {
} else if ((item instanceof AndItem) || (item instanceof NearItem)) {
return Recall.RECALLS_NOTHING;
} else if (item instanceof RankItem) {
+ if (i == 0) return Recall.RECALLS_NOTHING;
item.removeItem(i);
} else {
throw new UnsupportedOperationException(item.getClass().getName());
diff --git a/container-search/src/main/java/com/yahoo/prelude/querytransform/StemmingSearcher.java b/container-search/src/main/java/com/yahoo/prelude/querytransform/StemmingSearcher.java
index 9a9044def2d..eb31b75cd6f 100644
--- a/container-search/src/main/java/com/yahoo/prelude/querytransform/StemmingSearcher.java
+++ b/container-search/src/main/java/com/yahoo/prelude/querytransform/StemmingSearcher.java
@@ -9,7 +9,7 @@ import com.yahoo.language.Language;
import com.yahoo.language.Linguistics;
import com.yahoo.language.process.StemMode;
import com.yahoo.language.process.StemList;
-import com.yahoo.log.LogLevel;
+import java.util.logging.Level;
import com.yahoo.prelude.Index;
import com.yahoo.prelude.IndexFacts;
import com.yahoo.prelude.query.*;
@@ -98,6 +98,7 @@ public class StemmingSearcher extends Searcher {
context.language = language;
context.indexFacts = indexFacts;
context.reverseConnectivity = createReverseConnectivities(q.getModel().getQueryTree().getRoot());
+ q.trace("Stemming with language="+language, 3);
return scan(q.getModel().getQueryTree().getRoot(), context);
}
@@ -167,7 +168,7 @@ public class StemmingSearcher extends Searcher {
if (i instanceof TermItem) {
return ((TermItem) i).getOrigin(); // this should always be the case
} else {
- getLogger().log(LogLevel.WARNING, "Weird, BlockItem '" + b + "' was a composite containing " +
+ getLogger().log(Level.WARNING, "Weird, BlockItem '" + b + "' was a composite containing " +
i.getClass().getName() + ", expected TermItem.");
}
}
@@ -183,9 +184,20 @@ public class StemmingSearcher extends Searcher {
Substring substring = getOffsets(current);
if (segments.size() == 1) {
+ getLogger().log(Level.FINE, () -> "Stem '"+current.stringValue()+"' mode "+index.getStemMode()
+ +" and language '"+context.language+"' -> '"+segments.get(0)+"'");
TaggableItem w = singleWordSegment(current, segments.get(0), index, substring, context.insidePhrase);
setMetaData(current, context.reverseConnectivity, w);
return (Item) w;
+ } else if (getLogger().isLoggable(Level.FINE)) {
+ var buf = new StringBuilder();
+ buf.append("Stem '").append(current.stringValue());
+ buf.append("' mode ").append(index.getStemMode());
+ buf.append(" and language '").append(context.language).append("' ->");
+ for (StemList segment : segments) {
+ buf.append(" '").append(segment).append("'");
+ }
+ getLogger().log(Level.FINE, buf.toString());
}
if (context.isCJK)
@@ -194,6 +206,7 @@ public class StemmingSearcher extends Searcher {
composite = chooseComposite(current, ((Item) current).getParent(), indexName);
for (StemList segment : segments) {
+ getLogger().log(Level.FINE, () -> "Stem to multiple segments '"+segment+"'");
TaggableItem w = singleWordSegment(current, segment, index, substring, context.insidePhrase);
if (composite instanceof AndSegmentItem) {
@@ -346,7 +359,7 @@ public class StemmingSearcher extends Searcher {
default:
throw new IllegalArgumentException("Unknown segmenting rule: " + current.getSegmentingRule() +
". This is a bug in Vespa, as the implementation has gotten out of sync." +
- " Please create a ticket as soon as possible.");
+ " Please create an issue.");
}
}
diff --git a/container-search/src/main/java/com/yahoo/prelude/searcher/BlendingSearcher.java b/container-search/src/main/java/com/yahoo/prelude/searcher/BlendingSearcher.java
index b2f5d104890..d79386a88ed 100644
--- a/container-search/src/main/java/com/yahoo/prelude/searcher/BlendingSearcher.java
+++ b/container-search/src/main/java/com/yahoo/prelude/searcher/BlendingSearcher.java
@@ -78,8 +78,7 @@ public class BlendingSearcher extends Searcher {
* This assumes that all hits are organized into hitgroups. If not, blending will not be performed.
*/
protected Result blendResults(Result result, Query q, int offset, int hits, Execution execution) {
-
- //Assert that there are more than one hitgroup and that there are only hitgroups on the lowest level
+ // Assert that there are more than one hitgroup and that there are only hitgroups on the lowest level
boolean foundNonGroup = false;
Iterator<Hit> hitIterator = result.hits().iterator();
List<HitGroup> groups = new ArrayList<>();
diff --git a/container-search/src/main/java/com/yahoo/prelude/searcher/FieldCollapsingSearcher.java b/container-search/src/main/java/com/yahoo/prelude/searcher/FieldCollapsingSearcher.java
index badb99c0523..709d9459e50 100644
--- a/container-search/src/main/java/com/yahoo/prelude/searcher/FieldCollapsingSearcher.java
+++ b/container-search/src/main/java/com/yahoo/prelude/searcher/FieldCollapsingSearcher.java
@@ -22,7 +22,6 @@ import java.util.Map;
*
* @author Steinar Knutsen
*/
-@SuppressWarnings("deprecation")
@After(PhaseNames.RAW_QUERY)
@Before(PhaseNames.TRANSFORMED_QUERY)
public class FieldCollapsingSearcher extends Searcher {
@@ -33,7 +32,7 @@ public class FieldCollapsingSearcher extends Searcher {
private static final CompoundName collapseSummaryName = new CompoundName("collapse.summary");
/** Maximum number of queries to send next searcher */
- private int maxQueries = 4;
+ private final int maxQueries = 4;
/**
* The max number of hits that will be preserved per unique
diff --git a/container-search/src/main/java/com/yahoo/prelude/searcher/FillSearcher.java b/container-search/src/main/java/com/yahoo/prelude/searcher/FillSearcher.java
index 45034482bb6..5390f202ef0 100644
--- a/container-search/src/main/java/com/yahoo/prelude/searcher/FillSearcher.java
+++ b/container-search/src/main/java/com/yahoo/prelude/searcher/FillSearcher.java
@@ -1,7 +1,6 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.prelude.searcher;
-import com.yahoo.component.ComponentId;
import com.yahoo.search.Query;
import com.yahoo.search.Result;
import com.yahoo.search.Searcher;
diff --git a/container-search/src/main/java/com/yahoo/prelude/searcher/JuniperSearcher.java b/container-search/src/main/java/com/yahoo/prelude/searcher/JuniperSearcher.java
index d11b8d86d29..4e4b4600814 100644
--- a/container-search/src/main/java/com/yahoo/prelude/searcher/JuniperSearcher.java
+++ b/container-search/src/main/java/com/yahoo/prelude/searcher/JuniperSearcher.java
@@ -46,14 +46,11 @@ public class JuniperSearcher extends Searcher {
private static final String ELLIPSIS = "...";
- // The name of the field containing document type
- private static final String MAGIC_FIELD = Hit.SDDOCNAME_FIELD;
-
public static final String JUNIPER_TAG_REPLACING = "JuniperTagReplacing";
- private String boldOpenTag;
- private String boldCloseTag;
- private String separatorTag;
+ private final String boldOpenTag;
+ private final String boldCloseTag;
+ private final String separatorTag;
@Inject
public JuniperSearcher(ComponentId id, QrSearchersConfig config) {
@@ -80,13 +77,13 @@ public class JuniperSearcher extends Searcher {
int worstCase = result.getHitCount();
List<Hit> hits = new ArrayList<>(worstCase);
for (Iterator<Hit> i = result.hits().deepIterator(); i.hasNext();) {
- Hit sniffHit = i.next();
- if ( ! (sniffHit instanceof FastHit)) continue;
+ Hit hit = i.next();
+ if ( ! (hit instanceof FastHit)) continue;
- FastHit hit = (FastHit) sniffHit;
- if (hit.isFilled(summaryClass)) continue;
+ FastHit fastHit = (FastHit)hit;
+ if (fastHit.isFilled(summaryClass)) continue;
- hits.add(hit);
+ hits.add(fastHit);
}
execution.fill(result, summaryClass);
highlight(result.getQuery().getPresentation().getBolding(), hits.iterator(), summaryClass,
@@ -102,7 +99,7 @@ public class JuniperSearcher extends Searcher {
FastHit fastHit = (FastHit) hit;
if (summaryClass != null && ! fastHit.isFilled(summaryClass)) continue;
- Object searchDefinitionField = fastHit.getField(MAGIC_FIELD);
+ Object searchDefinitionField = fastHit.getField(Hit.SDDOCNAME_FIELD);
if (searchDefinitionField == null) continue;
for (Index index : indexFacts.getIndexes(searchDefinitionField.toString())) {
diff --git a/container-search/src/main/java/com/yahoo/prelude/searcher/PosSearcher.java b/container-search/src/main/java/com/yahoo/prelude/searcher/PosSearcher.java
index 43717ecf6cd..7063a14a389 100644
--- a/container-search/src/main/java/com/yahoo/prelude/searcher/PosSearcher.java
+++ b/container-search/src/main/java/com/yahoo/prelude/searcher/PosSearcher.java
@@ -75,7 +75,7 @@ public class PosSearcher extends Searcher {
loc.setAttribute(posAttribute);
try {
- if (ll == null && xy == null && bb != null) {
+ if (ll == null && xy == null) {
parseBoundingBox(bb, loc);
} else {
if (ll != null && xy != null) {
@@ -147,7 +147,9 @@ public class PosSearcher extends Searcher {
String radius = query.properties().getString(posRadius);
int radiusUnits;
if (radius == null) {
- radiusUnits = 5000;
+ double radiuskm = 50.0;
+ double radiusdegrees = radiuskm * km2deg;
+ radiusUnits = (int)(radiusdegrees * 1000000);
} else if (radius.endsWith("km")) {
double radiuskm = Double.valueOf(radius.substring(0, radius.length()-2));
double radiusdegrees = radiuskm * km2deg;
diff --git a/container-search/src/main/java/com/yahoo/prelude/searcher/ValidatePredicateSearcher.java b/container-search/src/main/java/com/yahoo/prelude/searcher/ValidatePredicateSearcher.java
index 9b6f5926b61..a8b3c76fe00 100644
--- a/container-search/src/main/java/com/yahoo/prelude/searcher/ValidatePredicateSearcher.java
+++ b/container-search/src/main/java/com/yahoo/prelude/searcher/ValidatePredicateSearcher.java
@@ -1,12 +1,12 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.prelude.searcher;
-import java.util.Optional;
import com.yahoo.component.chain.dependencies.After;
import com.yahoo.prelude.Index;
import com.yahoo.prelude.IndexFacts;
import com.yahoo.prelude.query.Item;
import com.yahoo.prelude.query.PredicateQueryItem;
+import com.yahoo.prelude.query.SimpleIndexedItem;
import com.yahoo.prelude.query.ToolBox;
import com.yahoo.search.Query;
import com.yahoo.search.Result;
@@ -15,7 +15,8 @@ import com.yahoo.search.querytransform.BooleanSearcher;
import com.yahoo.search.result.ErrorMessage;
import com.yahoo.search.searchchain.Execution;
-import java.util.Collection;
+import java.util.ArrayList;
+import java.util.List;
/**
* Checks that predicate queries don't use values outside the defined upper/lower bounds.
@@ -27,26 +28,26 @@ public class ValidatePredicateSearcher extends Searcher {
@Override
public Result search(Query query, Execution execution) {
- Optional<ErrorMessage> e = validate(query, execution.context().getIndexFacts().newSession(query));
- if (e.isPresent()) {
+ List<ErrorMessage> errorMessages = validate(query, execution.context().getIndexFacts().newSession(query));
+ if (!errorMessages.isEmpty()) {
Result r = new Result(query);
- r.hits().addError(e.get());
+ errorMessages.forEach(msg -> r.hits().addError(msg));
return r;
}
return execution.search(query);
}
- private Optional<ErrorMessage> validate(Query query, IndexFacts.Session indexFacts) {
+ private List<ErrorMessage> validate(Query query, IndexFacts.Session indexFacts) {
ValidatePredicateVisitor visitor = new ValidatePredicateVisitor(indexFacts);
ToolBox.visit(visitor, query.getModel().getQueryTree().getRoot());
- return visitor.errorMessage;
+ return visitor.errorMessages;
}
private static class ValidatePredicateVisitor extends ToolBox.QueryVisitor {
private final IndexFacts.Session indexFacts;
- public Optional<ErrorMessage> errorMessage = Optional.empty();
+ final List<ErrorMessage> errorMessages = new ArrayList<>();
public ValidatePredicateVisitor(IndexFacts.Session indexFacts) {
this.indexFacts = indexFacts;
@@ -57,22 +58,37 @@ public class ValidatePredicateSearcher extends Searcher {
if (item instanceof PredicateQueryItem) {
visit((PredicateQueryItem) item);
}
+ if (item instanceof SimpleIndexedItem) {
+ visit((SimpleIndexedItem) item);
+ }
return true;
}
private void visit(PredicateQueryItem item) {
- Index index = getIndexFromUnionOfDocumentTypes(item);
+ Index index = getIndexFromUnionOfDocumentTypes(item.getIndexName());
+ if (!index.isPredicate()) {
+ errorMessages.add(ErrorMessage.createIllegalQuery(String.format("Index '%s' is not a predicate attribute.", index.getName())));
+ }
for (PredicateQueryItem.RangeEntry entry : item.getRangeFeatures()) {
long value = entry.getValue();
if (value < index.getPredicateLowerBound() || value > index.getPredicateUpperBound()) {
- errorMessage = Optional.of(ErrorMessage.createIllegalQuery(
- String.format("%s=%d outside configured predicate bounds.", entry.getKey(), value)));
+ errorMessages.add(
+ ErrorMessage.createIllegalQuery(String.format("%s=%d outside configured predicate bounds.", entry.getKey(), value)));
}
}
}
- private Index getIndexFromUnionOfDocumentTypes(PredicateQueryItem item) {
- return indexFacts.getIndex(item.getIndexName());
+ private void visit(SimpleIndexedItem item) {
+ String indexName = item.getIndexName();
+ Index index = getIndexFromUnionOfDocumentTypes(indexName);
+ if (index.isPredicate()) {
+ errorMessages.add(
+ ErrorMessage.createIllegalQuery(String.format("Index '%s' is predicate attribute and can only be used in conjunction with a predicate query operator.", indexName)));
+ }
+ }
+
+ private Index getIndexFromUnionOfDocumentTypes(String indexName) {
+ return indexFacts.getIndex(indexName);
}
@Override
diff --git a/container-search/src/main/java/com/yahoo/prelude/semantics/RuleBase.java b/container-search/src/main/java/com/yahoo/prelude/semantics/RuleBase.java
index ccc9c1d2f8f..feab8faf898 100644
--- a/container-search/src/main/java/com/yahoo/prelude/semantics/RuleBase.java
+++ b/container-search/src/main/java/com/yahoo/prelude/semantics/RuleBase.java
@@ -26,43 +26,43 @@ public class RuleBase {
private String source;
/** The name of the automata file used, or null if none */
- protected String automataFileName=null;
+ protected String automataFileName = null;
/**
* True if this rule base is default.
* The semantics of default is left to the surrounding framework
*/
- private boolean isDefault=false;
+ private boolean isDefault = false;
- private List<ProductionRule> productionRules=new java.util.ArrayList<>();
+ private final List<ProductionRule> productionRules = new java.util.ArrayList<>();
- private Map<String, NamedCondition> namedConditions=new java.util.LinkedHashMap<>();
+ private Map<String, NamedCondition> namedConditions = new java.util.LinkedHashMap<>();
/** The analyzer used to do evaluations over this rule base */
- private RuleEngine analyzer=new RuleEngine(this);
+ private final RuleEngine analyzer = new RuleEngine(this);
- private static final PhraseMatcher nullPhraseMatcher=PhraseMatcher.getNullMatcher();
+ private static final PhraseMatcher nullPhraseMatcher = PhraseMatcher.getNullMatcher();
/**
* The matcher using an automata to match terms and phrases prior to matching rules
* or the null matcher if no matcher is used.
*/
- private PhraseMatcher phraseMatcher=nullPhraseMatcher;
+ private PhraseMatcher phraseMatcher = nullPhraseMatcher;
/**
* The names of the rule bases included indirectly or directly in this
* Ordered by first to last included
*/
- private Set<String> includedNames=new java.util.LinkedHashSet<>();
+ private final Set<String> includedNames = new java.util.LinkedHashSet<>();
/**
* True if this uses an automata, even if an automata is not present right now. Useful to validate without
* having automatas available
*/
- private boolean usesAutomata=false;
+ private boolean usesAutomata = false;
/** Should we allow stemmed matches? */
- private boolean stemming=true;
+ private boolean stemming = true;
/** Creates an empty rule base. TODO: Disallow */
public RuleBase() {
@@ -82,8 +82,8 @@ public class RuleBase {
* @throws ParseException if the rule file can not be parsed correctly
* @throws RuleBaseException if the rule file contains inconsistencies
*/
- public static RuleBase createFromFile(String ruleFile,String automataFile) throws java.io.IOException, ParseException {
- return new RuleImporter().importFile(ruleFile,automataFile);
+ public static RuleBase createFromFile(String ruleFile, String automataFile) throws java.io.IOException, ParseException {
+ return new RuleImporter().importFile(ruleFile, automataFile);
}
/**
@@ -96,14 +96,14 @@ public class RuleBase {
* @throws com.yahoo.prelude.semantics.parser.ParseException if the rule file can not be parsed correctly
* @throws com.yahoo.prelude.semantics.RuleBaseException if the rule file contains inconsistencies
*/
- public static RuleBase createFromString(String name,String ruleString,String automataFile) throws java.io.IOException, ParseException {
- RuleBase base=new RuleImporter().importString(ruleString,automataFile,new RuleBase());
+ public static RuleBase createFromString(String name, String ruleString, String automataFile) throws java.io.IOException, ParseException {
+ RuleBase base = new RuleImporter().importString(ruleString, automataFile, new RuleBase());
base.setName(name);
return base;
}
/** Set to true to enable stemmed matches. True by default */
- public void setStemming(boolean stemming) { this.stemming=stemming; }
+ public void setStemming(boolean stemming) { this.stemming = stemming; }
/** Returns whether stemmed matches are allowed. True by default */
public boolean getStemming() { return stemming; }
@@ -125,19 +125,19 @@ public class RuleBase {
/** Rules are order based - they are included recursively depth first */
private void inlineIncluded() {
// Re-add our own conditions last to - added later overrides
- Map<String, NamedCondition> thisConditions=namedConditions;
- namedConditions=new LinkedHashMap<>();
+ Map<String, NamedCondition> thisConditions = namedConditions;
+ namedConditions = new LinkedHashMap<>();
- Set<RuleBase> included=new HashSet<>();
+ Set<RuleBase> included = new HashSet<>();
included.add(this);
- for (ListIterator<ProductionRule> i=productionRules.listIterator(); i.hasNext(); ) {
- ProductionRule rule=i.next();
+ for (ListIterator<ProductionRule> i = productionRules.listIterator(); i.hasNext(); ) {
+ ProductionRule rule = i.next();
if ( ! (rule instanceof IncludeDirective) ) continue;
i.remove();
- RuleBase toInclude=((IncludeDirective)rule).getIncludedBase();
+ RuleBase toInclude = ((IncludeDirective)rule).getIncludedBase();
if ( ! included.contains(toInclude))
- toInclude.inlineIn(this,i,included);
+ toInclude.inlineIn(this, i, included);
}
namedConditions.putAll(thisConditions);
@@ -147,14 +147,14 @@ public class RuleBase {
* Recursively include this and everything it includes into the given rule base.
* Skips bases already included in this.
*/
- private void inlineIn(RuleBase receiver,ListIterator<ProductionRule> receiverRules,Set<RuleBase> included) {
+ private void inlineIn(RuleBase receiver, ListIterator<ProductionRule> receiverRules, Set<RuleBase> included) {
if (included.contains(this)) return;
included.add(this);
- for (Iterator<ProductionRule> i=productionRules.iterator(); i.hasNext(); ) {
- ProductionRule rule=i.next();
+ for (Iterator<ProductionRule> i = productionRules.iterator(); i.hasNext(); ) {
+ ProductionRule rule = i.next();
if (rule instanceof IncludeDirective)
- ((IncludeDirective)rule).getIncludedBase().inlineIn(receiver,receiverRules,included);
+ ((IncludeDirective)rule).getIncludedBase().inlineIn(receiver, receiverRules, included);
else
receiverRules.add(rule);
}
@@ -164,11 +164,11 @@ public class RuleBase {
/** Adds a named condition which can be referenced by rules */
public void addCondition(NamedCondition namedCondition) {
- namedConditions.put(namedCondition.getName(),namedCondition);
+ namedConditions.put(namedCondition.getName(), namedCondition);
- Condition condition=namedCondition.getCondition();
- Condition superCondition=findIncludedCondition(namedCondition.getName());
- resolveSuper(condition,superCondition);
+ Condition condition = namedCondition.getCondition();
+ Condition superCondition = findIncludedCondition(namedCondition.getName());
+ resolveSuper(condition, superCondition);
}
private void resolveSuper(Condition condition,Condition superCondition) {
@@ -176,24 +176,22 @@ public class RuleBase {
((SuperCondition)condition).setCondition(superCondition);
}
else if (condition instanceof CompositeCondition) {
- for (Iterator<Condition> i=((CompositeCondition)condition).conditionIterator(); i.hasNext(); ) {
- Condition subCondition=i.next();
- resolveSuper(subCondition,superCondition);
+ for (Iterator<Condition> i = ((CompositeCondition)condition).conditionIterator(); i.hasNext(); ) {
+ Condition subCondition = i.next();
+ resolveSuper(subCondition, superCondition);
}
}
}
private Condition findIncludedCondition(String name) {
- for (Iterator<ProductionRule> i=productionRules.iterator(); i.hasNext(); ) {
- ProductionRule rule=i.next();
+ for (Iterator<ProductionRule> i = productionRules.iterator(); i.hasNext(); ) {
+ ProductionRule rule = i.next();
if ( ! (rule instanceof IncludeDirective) ) continue;
- RuleBase included=((IncludeDirective)rule).getIncludedBase();
- NamedCondition condition=included.getCondition(name);
- if (condition!=null) return condition.getCondition();
+ RuleBase included = ((IncludeDirective)rule).getIncludedBase();
+ NamedCondition condition = included.getCondition(name);
+ if (condition != null) return condition.getCondition();
included.findIncludedCondition(name);
- // FIXME: dead code commented out
- // if (condition!=null) return condition.getCondition();
}
return null;
}
@@ -212,8 +210,8 @@ public class RuleBase {
* change, and then re-added
*/
public void setName(String name) {
- Validator.ensureNotNull("Rule base name",name);
- this.name=name;
+ Validator.ensureNotNull("Rule base name", name);
+ this.name = name;
}
/** Returns the name of this rule base. This is never null. */
@@ -230,27 +228,27 @@ public class RuleBase {
if ( ! new File(automataFile).exists())
throw new IllegalArgumentException("Automata file '" + automataFile + "' " +
"included in " + this + " not found");
- phraseMatcher=new PhraseMatcher(automataFile);
+ phraseMatcher = new PhraseMatcher(automataFile);
phraseMatcher.setIgnorePluralForm(true);
phraseMatcher.setMatchAll(true);
phraseMatcher.setMatchPhraseItems(true);
phraseMatcher.setMatchSingleItems(true);
setPhraseMatcher(phraseMatcher);
- this.automataFileName=automataFile;
+ this.automataFileName = automataFile;
}
/** Returns the name of the automata file used, or null if none */
public String getAutomataFile() { return automataFileName; }
/** Sets whether this base is default, the semantics of default is left to the application */
- public void setDefault(boolean isDefault) { this.isDefault=isDefault; }
+ public void setDefault(boolean isDefault) { this.isDefault = isDefault; }
/** Returns whether this base is default, the semantics of default is left to the application */
public boolean isDefault() { return isDefault; }
/** Thread safely sets the phrase matcher to use in this, or null to not use a phrase matcher */
public synchronized void setPhraseMatcher(PhraseMatcher matcher) {
- if (matcher==null)
+ if (matcher == null)
this.phraseMatcher = nullPhraseMatcher;
else
this.phraseMatcher = matcher;
@@ -282,7 +280,7 @@ public class RuleBase {
* Set to truew if this uses an automata, even if an automata is not present right now.
* Useful to validate without having automatas available
*/
- void setUsesAutomata(boolean usesAutomata) { this.usesAutomata=usesAutomata; }
+ void setUsesAutomata(boolean usesAutomata) { this.usesAutomata = usesAutomata; }
// Note that included rules are added though a list iterator, not this */
public void addRule(ProductionRule productionRule) {
@@ -399,13 +397,13 @@ public class RuleBase {
public String toContentString() {
StringBuilder b = new StringBuilder();
for (Iterator<ProductionRule> i = productionRules.iterator(); i.hasNext(); ) {
- b.append(i.next().toString());
+ b.append(i.next());
b.append("\n");
}
b.append("\n");
b.append("\n");
for (Iterator<NamedCondition> i = namedConditions.values().iterator(); i.hasNext(); ) {
- b.append(i.next().toString());
+ b.append(i.next());
b.append("\n");
}
return b.toString();
@@ -414,10 +412,10 @@ public class RuleBase {
/** A placeholder for an included rule base until it is inlined */
private static class IncludeDirective extends ProductionRule {
- private RuleBase includedBase;
+ private final RuleBase includedBase;
public IncludeDirective(RuleBase ruleBase) {
- this.includedBase=ruleBase;
+ this.includedBase = ruleBase;
}
public RuleBase getIncludedBase() { return includedBase; }
@@ -425,7 +423,6 @@ public class RuleBase {
/** Not used */
public String getSymbol() { return ""; }
-
}
}
diff --git a/container-search/src/main/java/com/yahoo/prelude/semantics/RuleBaseException.java b/container-search/src/main/java/com/yahoo/prelude/semantics/RuleBaseException.java
index d851c2648d5..5108777d938 100644
--- a/container-search/src/main/java/com/yahoo/prelude/semantics/RuleBaseException.java
+++ b/container-search/src/main/java/com/yahoo/prelude/semantics/RuleBaseException.java
@@ -6,7 +6,6 @@ package com.yahoo.prelude.semantics;
*
* @author bratseth
*/
-@SuppressWarnings("serial")
public class RuleBaseException extends RuntimeException {
public RuleBaseException(String message) {
@@ -14,7 +13,7 @@ public class RuleBaseException extends RuntimeException {
}
public RuleBaseException(String message,Exception cause) {
- super(message,cause);
+ super(message, cause);
}
}
diff --git a/container-search/src/main/java/com/yahoo/prelude/semantics/RuleImporter.java b/container-search/src/main/java/com/yahoo/prelude/semantics/RuleImporter.java
index 754d14ddcb4..ac643469ab6 100644
--- a/container-search/src/main/java/com/yahoo/prelude/semantics/RuleImporter.java
+++ b/container-search/src/main/java/com/yahoo/prelude/semantics/RuleImporter.java
@@ -28,13 +28,13 @@ public class RuleImporter {
* If this is set, imported rule bases are looked up in this config
* otherwise, they are looked up as files
*/
- private SemanticRulesConfig config = null;
+ private SemanticRulesConfig config;
/**
* Ignore requests to read automata files.
* Useful to validate rule bases without having automatas present
*/
- private boolean ignoreAutomatas = false;
+ private boolean ignoreAutomatas;
/**
* Ignore requests to include files.
@@ -61,8 +61,8 @@ public class RuleImporter {
}
public RuleImporter(SemanticRulesConfig config, boolean ignoreAutomatas) {
- this.config=config;
- this.ignoreAutomatas=ignoreAutomatas;
+ this.config = config;
+ this.ignoreAutomatas = ignoreAutomatas;
}
public RuleImporter(SemanticRulesConfig config, boolean ignoreAutomatas, boolean ignoreIncludes) {
@@ -79,7 +79,7 @@ public class RuleImporter {
* @throws ParseException if the file does not contain a valid semantic rule set
*/
public RuleBase importFile(String fileName) throws IOException, ParseException {
- return importFile(fileName,null);
+ return importFile(fileName, null);
}
/**
@@ -90,8 +90,8 @@ public class RuleImporter {
* @throws java.io.IOException if the file can not be read for some reason
* @throws ParseException if the file does not contain a valid semantic rule set
*/
- public RuleBase importFile(String fileName,String automataFile) throws IOException, ParseException {
- return importFile(fileName,automataFile,null);
+ public RuleBase importFile(String fileName, String automataFile) throws IOException, ParseException {
+ return importFile(fileName, automataFile, null);
}
/**
@@ -99,27 +99,26 @@ public class RuleImporter {
*
* @param fileName the rule file to use
* @param automataFile the automata file to use, or null to not use any
- * @param ruleBase an existing rule base to import these rules into, or null
- * to create a new
+ * @param ruleBase an existing rule base to import these rules into, or null to create a new
* @throws java.io.IOException if the file can not be read for some reason
* @throws ParseException if the file does not contain a valid semantic rule set
*/
- public RuleBase importFile(String fileName,String automataFile,RuleBase ruleBase) throws IOException, ParseException {
- ruleBase=privateImportFile(fileName,automataFile,ruleBase);
+ public RuleBase importFile(String fileName, String automataFile, RuleBase ruleBase) throws IOException, ParseException {
+ ruleBase = privateImportFile(fileName, automataFile, ruleBase);
ruleBase.initialize();
return ruleBase;
}
- public RuleBase privateImportFile(String fileName,String automataFile,RuleBase ruleBase) throws IOException, ParseException {
- BufferedReader reader=null;
+ public RuleBase privateImportFile(String fileName, String automataFile, RuleBase ruleBase) throws IOException, ParseException {
+ BufferedReader reader = null;
try {
- reader= IOUtils.createReader(fileName, "utf-8");
- File file=new File(fileName);
- String absoluteFileName=file.getAbsolutePath();
- if (ruleBase==null)
- ruleBase=new RuleBase();
+ reader = IOUtils.createReader(fileName, "utf-8");
+ File file = new File(fileName);
+ String absoluteFileName = file.getAbsolutePath();
+ if (ruleBase == null)
+ ruleBase = new RuleBase();
ruleBase.setName(stripLastName(file.getName()));
- privateImportFromReader(reader,absoluteFileName,automataFile,ruleBase);
+ privateImportFromReader(reader, absoluteFileName, automataFile, ruleBase);
return ruleBase;
}
finally {
@@ -129,14 +128,14 @@ public class RuleImporter {
/** Imports all the rule files (files ending by "sr") in the given directory */
public List<RuleBase> importDir(String ruleBaseDir) throws IOException, ParseException {
- File ruleBaseDirFile=new File(ruleBaseDir);
- if (!ruleBaseDirFile.exists())
+ File ruleBaseDirFile = new File(ruleBaseDir);
+ if ( ! ruleBaseDirFile.exists())
throw new IOException("Rule base dir '" + ruleBaseDirFile.getAbsolutePath() + "' does not exist");
- File[] files=ruleBaseDirFile.listFiles();
+ File[] files = ruleBaseDirFile.listFiles();
Arrays.sort(files);
- List<RuleBase> ruleBases=new java.util.ArrayList<>();
+ List<RuleBase> ruleBases = new java.util.ArrayList<>();
for (File file : files) {
- if (!file.getName().endsWith(".sr")) continue;
+ if ( ! file.getName().endsWith(".sr")) continue;
RuleBase base = importFile(file.getAbsolutePath());
ruleBases.add(base);
}
@@ -144,50 +143,50 @@ public class RuleImporter {
}
/** Read and include a rule base in another */
- public void include(String ruleBaseName,RuleBase ruleBase) throws java.io.IOException, ParseException {
+ public void include(String ruleBaseName, RuleBase ruleBase) throws java.io.IOException, ParseException {
if (ignoreIncludes) return;
RuleBase include;
- if (config==null) {
- include=privateImportFromDirectory(ruleBaseName,ruleBase);
+ if (config == null) {
+ include = privateImportFromDirectory(ruleBaseName, ruleBase);
}
else {
- include=privateImportFromConfig(ruleBaseName);
+ include = privateImportFromConfig(ruleBaseName);
}
ruleBase.include(include);
}
/** Returns an unitialized rule base */
- private RuleBase privateImportFromDirectory(String ruleBaseName,RuleBase ruleBase) throws IOException, ParseException {
+ private RuleBase privateImportFromDirectory(String ruleBaseName, RuleBase ruleBase) throws IOException, ParseException {
RuleBase include = new RuleBase();
- String includeDir=new File(ruleBase.getSource()).getParentFile().getAbsolutePath();
+ String includeDir = new File(ruleBase.getSource()).getParentFile().getAbsolutePath();
if (!ruleBaseName.endsWith(".sr"))
- ruleBaseName=ruleBaseName + ".sr";
- File importFile=new File(includeDir,ruleBaseName);
- if (!importFile.exists())
+ ruleBaseName = ruleBaseName + ".sr";
+ File importFile = new File(includeDir, ruleBaseName);
+ if ( ! importFile.exists())
throw new IOException("No file named '" + shortenPath(importFile.getPath()) + "'");
- return privateImportFile(importFile.getPath(),null,include);
+ return privateImportFile(importFile.getPath(), null, include);
}
/** Returns an unitialized rule base */
private RuleBase privateImportFromConfig(String ruleBaseName) throws IOException, ParseException {
- SemanticRulesConfig.Rulebase ruleBaseConfig=findRuleBaseConfig(config,ruleBaseName);
- if (ruleBaseConfig==null)
- ruleBaseConfig=findRuleBaseConfig(config,stripLastName(ruleBaseName));
- if (ruleBaseConfig==null)
+ SemanticRulesConfig.Rulebase ruleBaseConfig = findRuleBaseConfig(config,ruleBaseName);
+ if (ruleBaseConfig == null)
+ ruleBaseConfig = findRuleBaseConfig(config, stripLastName(ruleBaseName));
+ if (ruleBaseConfig == null)
throw new ParseException("Could not find included rule base '" + ruleBaseName + "'");
return privateImportConfig(ruleBaseConfig);
}
- private SemanticRulesConfig.Rulebase findRuleBaseConfig(SemanticRulesConfig config,String ruleBaseName) {
+ private SemanticRulesConfig.Rulebase findRuleBaseConfig(SemanticRulesConfig config, String ruleBaseName) {
for (Object aRulebase : config.rulebase()) {
- SemanticRulesConfig.Rulebase ruleBaseConfig = (SemanticRulesConfig.Rulebase) aRulebase;
+ SemanticRulesConfig.Rulebase ruleBaseConfig = (SemanticRulesConfig.Rulebase)aRulebase;
if (ruleBaseConfig.name().equals(ruleBaseName))
return ruleBaseConfig;
}
return null;
}
- public void setAutomata(RuleBase base,String automata) {
+ public void setAutomata(RuleBase base, String automata) {
if (ignoreAutomatas)
base.setUsesAutomata(true); // Stop it from failing on automata condition references
else
@@ -195,9 +194,9 @@ public class RuleImporter {
}
static String stripLastName(String fileName) {
- int lastDotIndex=fileName.lastIndexOf(".");
- if (lastDotIndex<0) return fileName;
- return fileName.substring(0,lastDotIndex);
+ int lastDotIndex = fileName.lastIndexOf(".");
+ if (lastDotIndex < 0) return fileName;
+ return fileName.substring(0, lastDotIndex);
}
public RuleBase importString(String string, String automataFile) throws IOException, ParseException {
@@ -217,22 +216,23 @@ public class RuleImporter {
}
public RuleBase importConfig(SemanticRulesConfig.Rulebase ruleBaseConfig) throws IOException, ParseException {
- RuleBase ruleBase=privateImportConfig(ruleBaseConfig);
+ RuleBase ruleBase = privateImportConfig(ruleBaseConfig);
ruleBase.initialize();
return ruleBase;
}
/** Imports an unitialized rule base */
- public RuleBase privateImportConfig(SemanticRulesConfig.Rulebase ruleBaseConfig) throws IOException, ParseException {
- if (config==null) throw new IllegalStateException("Must initialize with config if importing from config");
+ public RuleBase privateImportConfig(SemanticRulesConfig.Rulebase ruleBaseConfig) throws ParseException {
+ if (config == null) throw new IllegalStateException("Must initialize with config if importing from config");
RuleBase ruleBase = new RuleBase();
ruleBase.setName(ruleBaseConfig.name());
- return privateImportFromReader(new StringReader(ruleBaseConfig.rules()),"semantic-rules.cfg",
- ruleBaseConfig.automata(),ruleBase);
+ return privateImportFromReader(new StringReader(ruleBaseConfig.rules()),
+ "semantic-rules.cfg",
+ ruleBaseConfig.automata(),ruleBase);
}
- public RuleBase importFromReader(Reader reader,String sourceInfo,String automataFile) throws ParseException {
- return importFromReader(reader,sourceInfo,automataFile,null);
+ public RuleBase importFromReader(Reader reader, String sourceInfo, String automataFile) throws ParseException {
+ return importFromReader(reader, sourceInfo, automataFile, null);
}
/**
@@ -245,7 +245,7 @@ public class RuleImporter {
* @throws ParseException if the reader contains illegal rule syntax
*/
public RuleBase importFromReader(Reader reader, String sourceName, String automataFile, RuleBase ruleBase) throws ParseException {
- ruleBase=privateImportFromReader(reader, sourceName, automataFile,ruleBase);
+ ruleBase = privateImportFromReader(reader, sourceName, automataFile, ruleBase);
ruleBase.initialize();
return ruleBase;
}
@@ -253,19 +253,19 @@ public class RuleImporter {
/** Returns an unitialized rule base */
public RuleBase privateImportFromReader(Reader reader, String sourceName, String automataFile, RuleBase ruleBase) throws ParseException {
try {
- if (ruleBase==null) {
- ruleBase=new RuleBase();
+ if (ruleBase == null) {
+ ruleBase = new RuleBase();
if (sourceName == null)
sourceName = "anonymous";
ruleBase.setName(sourceName);
}
- ruleBase.setSource(sourceName.replace('\\','/'));
+ ruleBase.setSource(sourceName.replace('\\', '/'));
new SemanticsParser(reader).semanticRules(ruleBase, this);
- if (automataFile!=null && !automataFile.isEmpty())
- ruleBase.setAutomataFile(automataFile.replace('\\','/'));
+ if (automataFile != null && !automataFile.isEmpty())
+ ruleBase.setAutomataFile(automataFile.replace('\\', '/'));
return ruleBase;
} catch (Throwable t) { // also catches token mgr errors
- ParseException p=new ParseException("Could not parse '" + shortenPath(sourceName) + "'");
+ ParseException p = new ParseException("Could not parse '" + shortenPath(sourceName) + "'");
p.initCause(t);
throw p;
}
@@ -277,8 +277,8 @@ public class RuleImporter {
* (if rules/ is present, these rules are read from an applicatino package)
*/
private static String shortenPath(String path) {
- int rulesIndex=path.indexOf("rules/");
- if (rulesIndex<0) return path;
+ int rulesIndex = path.indexOf("rules/");
+ if (rulesIndex < 0) return path;
return path.substring(rulesIndex);
}
diff --git a/container-search/src/main/java/com/yahoo/prelude/semantics/engine/Evaluation.java b/container-search/src/main/java/com/yahoo/prelude/semantics/engine/Evaluation.java
index dafc31c66fa..989f3040cc7 100644
--- a/container-search/src/main/java/com/yahoo/prelude/semantics/engine/Evaluation.java
+++ b/container-search/src/main/java/com/yahoo/prelude/semantics/engine/Evaluation.java
@@ -21,7 +21,7 @@ public class Evaluation {
// TODO: Retrofit query into the namespace construct
private ParameterNameSpace parameterNameSpace = null;
- private Query query;
+ private final Query query;
/** The current index into the flattened item list */
private int currentIndex = 0;
@@ -30,7 +30,7 @@ public class Evaluation {
private List<FlattenedItem> flattenedItems;
/** The rule evaluation context, can be reset once the rule is evaluated */
- private RuleEvaluation ruleEvaluation;
+ private final RuleEvaluation ruleEvaluation;
/**
* The amount of context information to collect about this evaluation.
@@ -41,7 +41,7 @@ public class Evaluation {
private String traceIndentation = "";
/** See RuleEngine */
- private Set<Integer> matchDigests = new HashSet<>();
+ private final Set<Integer> matchDigests = new HashSet<>();
/** The previous size of this query (see RuleEngine), set on matches only */
private int previousQuerySize = 0;
@@ -244,15 +244,16 @@ public class Evaluation {
* @param desiredParentType the desired type of the composite which contains item when this returns
*/
public void insertItem(Item item, CompositeItem parent, int index, TermType desiredParentType) {
- if (parent == null) { // TODO: Accommodate for termtype in this case too
- query.getModel().getQueryTree().setRoot(item);
-
+ if (isEmpty(parent)) {
+ CompositeItem newParent = (CompositeItem)desiredParentType.createItemClass();
+ newParent.addItem(item);
+ query.getModel().getQueryTree().setRoot(newParent);
return;
}
- if (parent.getItemCount()>0 && parent instanceof QueryTree && parent.getItem(0) instanceof CompositeItem) {
+ if (parent.getItemCount() > 0 && parent instanceof QueryTree && parent.getItem(0) instanceof CompositeItem) {
// combine with the existing root instead
- parent=(CompositeItem)parent.getItem(0);
+ parent = (CompositeItem)parent.getItem(0);
if (index == 1) { // that means adding it after the existing root
index = parent.getItemCount();
}
@@ -260,11 +261,25 @@ public class Evaluation {
if (( desiredParentType == TermType.DEFAULT || desiredParentType.hasItemClass(parent.getClass()) )
&& equalIndexNameIfParentIsPhrase(item, parent)) {
- addItem(parent,index,item,desiredParentType);
+ addItem(parent, index, item, desiredParentType);
}
- else {
+ else if (incompatible(desiredParentType, parent)) {
insertIncompatibleItem(item, parent, query, desiredParentType);
}
+ else {
+ insertIncompatibleItemAsParent(item, parent, query, desiredParentType);
+ }
+ }
+
+ private boolean isEmpty(Item item) {
+ if (item == null) return true;
+ if (item instanceof QueryTree && ((QueryTree) item).isEmpty()) return true;
+ return false;
+ }
+
+ /** Returns true if the desired type cannot have childCandidate as a child */
+ private boolean incompatible(TermType desiredParentType, Item childCandidate) {
+ return desiredParentType == TermType.EQUIV && childCandidate.getItemType() != Item.ItemType.EQUIV;
}
private void addItem(CompositeItem parent, int index, Item item, TermType desiredParentType) {
@@ -273,27 +288,27 @@ public class Evaluation {
parent.setItem(0, item);
}
else if (index<=1 && !(parent.getItem(0) instanceof CompositeItem)) { // Case 2: The positive must become a composite
- CompositeItem positiveComposite=(CompositeItem)desiredParentType.createItemClass();
+ CompositeItem positiveComposite = (CompositeItem)desiredParentType.createItemClass();
positiveComposite.addItem(parent.getItem(0));
- positiveComposite.addItem(index,item);
- parent.setItem(0,positiveComposite);
+ positiveComposite.addItem(index, item);
+ parent.setItem(0, positiveComposite);
}
else if (parent.getItem(0)!=null && parent.getItem(0) instanceof CompositeItem // Case 3: Add to the positive composite
- && index<=((CompositeItem)parent.getItem(0)).getItemCount()) {
- ((CompositeItem)parent.getItem(0)).addItem(index,item);
+ && index <= ((CompositeItem)parent.getItem(0)).getItemCount()) {
+ ((CompositeItem)parent.getItem(0)).addItem(index, item);
}
else { // Case 4: Add negative
- parent.addItem(index,item);
+ parent.addItem(index, item);
}
}
- else if (parent.getItemCount()>0 && parent instanceof QueryTree) {
- CompositeItem composite=(CompositeItem)desiredParentType.createItemClass();
+ else if (parent.getItemCount() > 0 && parent instanceof QueryTree) {
+ CompositeItem composite = (CompositeItem)desiredParentType.createItemClass();
composite.addItem(parent.getItem(0));
- composite.addItem(index,item);
- parent.setItem(0,composite);
+ composite.addItem(index, item);
+ parent.setItem(0, composite);
}
else {
- parent.addItem(index,item);
+ parent.addItem(index, item);
}
}
@@ -305,32 +320,39 @@ public class Evaluation {
return ((PhraseItem)parent).getIndexName().equals(((IndexedItem)item).getIndexName());
}
- private void insertIncompatibleItem(Item item,CompositeItem parent,Query query,TermType desiredParentType) {
+ private void insertIncompatibleItem(Item item, CompositeItem parent, Query query, TermType desiredParentType) {
+ CompositeItem newParent;
+ if (desiredParentType == TermType.DEFAULT)
+ newParent = new AndItem();
+ else
+ newParent = (CompositeItem)desiredParentType.createItemClass();
+
+ newParent.addItem(item);
+ parent.addItem(newParent);
+ }
+
+ private void insertIncompatibleItemAsParent(Item item, CompositeItem parent, Query query, TermType desiredParentType) {
// Create new parent
CompositeItem newParent;
- if (desiredParentType==TermType.DEFAULT)
- newParent=new AndItem();
+ if (desiredParentType == TermType.DEFAULT)
+ newParent = new AndItem();
else
- newParent=(CompositeItem)desiredParentType.createItemClass();
+ newParent = (CompositeItem)desiredParentType.createItemClass();
// Save previous parent parent
- CompositeItem parentsParent=parent.getParent();
+ CompositeItem parentsParent = parent.getParent();
// Add items to new parent
newParent.addItem(parent);
newParent.addItem(item);
// Insert new parent as root or child of old parents parent
- if (parentsParent==null) {
+ if (parentsParent == null) {
query.getModel().getQueryTree().setRoot(newParent);
}
else {
- int parentIndex=0;
- if (parentsParent!=null) {
- parentIndex=parentsParent.getItemIndex(parent);
- }
- parentsParent.setItem(parentIndex,newParent);
+ parentsParent.setItem(parentsParent.getItemIndex(parent), newParent);
}
}
@@ -338,19 +360,19 @@ public class Evaluation {
if (first instanceof NullItem) {
return second;
} else if (first instanceof NotItem) {
- NotItem notItem=(NotItem)first;
- if (termType==TermType.NOT) {
+ NotItem notItem = (NotItem)first;
+ if (termType == TermType.NOT) {
notItem.addNegativeItem(second);
}
else {
- Item newPositive=combineItems(notItem.getPositiveItem(),second,termType);
+ Item newPositive = combineItems(notItem.getPositiveItem(), second, termType);
notItem.setPositiveItem(newPositive);
}
return notItem;
}
else if (first instanceof CompositeItem) {
- CompositeItem composite=(CompositeItem)first;
- CompositeItem combined=createType(termType);
+ CompositeItem composite = (CompositeItem)first;
+ CompositeItem combined = createType(termType);
if (combined.getClass().equals(composite.getClass())) {
composite.addItem(second);
return composite;
@@ -362,7 +384,7 @@ public class Evaluation {
}
}
else if (first instanceof TermItem) {
- CompositeItem combined=createType(termType);
+ CompositeItem combined = createType(termType);
combined.addItem(first);
combined.addItem(second);
return combined;
@@ -373,25 +395,15 @@ public class Evaluation {
}
private CompositeItem createType(TermType termType) {
- if (termType==TermType.DEFAULT) {
- if (query.getModel().getType().equals(Query.Type.ANY))
+ if (termType == TermType.DEFAULT) {
+ if (query.getModel().getType() == Query.Type.ANY)
return new OrItem();
else
return new AndItem();
}
- else if (termType==TermType.AND) {
- return new AndItem();
- }
- else if (termType==TermType.OR) {
- return new OrItem();
- }
- else if (termType==TermType.RANK) {
- return new RankItem();
- }
- else if (termType==TermType.NOT) {
- return new NotItem();
+ else {
+ return (CompositeItem)termType.createItemClass();
}
- throw new IllegalArgumentException("Programing error, this method should be updated with add in RankType");
}
private void flatten(Item item,int position,List<FlattenedItem> toList) {
diff --git a/container-search/src/main/java/com/yahoo/prelude/semantics/engine/Match.java b/container-search/src/main/java/com/yahoo/prelude/semantics/engine/Match.java
index 9e7c761deba..a87338879fe 100644
--- a/container-search/src/main/java/com/yahoo/prelude/semantics/engine/Match.java
+++ b/container-search/src/main/java/com/yahoo/prelude/semantics/engine/Match.java
@@ -6,6 +6,8 @@ import com.yahoo.prelude.query.Item;
import com.yahoo.prelude.query.TermItem;
import com.yahoo.prelude.query.WordItem;
+import java.util.Objects;
+
/**
* A match
*
@@ -14,15 +16,15 @@ import com.yahoo.prelude.query.WordItem;
public class Match {
/** The start position of this match */
- private int position;
+ private final int position;
- private TermItem item;
+ private final TermItem item;
/** The string to replace the match by, usually item.getIndexedString() */
- private String replaceValue;
+ private final String replaceValue;
/** The parent of the matched item */
- private CompositeItem parent=null;
+ private final CompositeItem parent;
/**
* Creates a match
@@ -56,23 +58,25 @@ public class Match {
*/
public CompositeItem getParent() { return parent; }
- public int hashCode() {
- return
- 17*item.getIndexedString().hashCode()+
- 33*item.getIndexName().hashCode();
- }
-
/** Returns a new item representing this match */
public Item toItem(String label) {
- return new WordItem(getReplaceValue(),label);
+ var newItem = new WordItem(getReplaceValue(), label);
+ newItem.setWeight(item.getWeight());
+ return newItem;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(item.getIndexedString(), item.getIndexName());
}
+ @Override
public boolean equals(Object o) {
if (! (o instanceof Match)) return false;
- Match other=(Match)o;
- if (other.position!=position) return false;
- if (!other.item.equals(item)) return false;
+ Match other = (Match)o;
+ if (other.position != position) return false;
+ if ( ! other.item.equals(item)) return false;
return true;
}
diff --git a/container-search/src/main/java/com/yahoo/prelude/semantics/engine/ReferencedMatches.java b/container-search/src/main/java/com/yahoo/prelude/semantics/engine/ReferencedMatches.java
index 274528f1fb3..f21f861a801 100644
--- a/container-search/src/main/java/com/yahoo/prelude/semantics/engine/ReferencedMatches.java
+++ b/container-search/src/main/java/com/yahoo/prelude/semantics/engine/ReferencedMatches.java
@@ -14,12 +14,12 @@ import com.yahoo.prelude.query.PhraseItem;
*/
public class ReferencedMatches {
- private String contextName;
+ private final String contextName;
- private List<Match> matches=new java.util.ArrayList<>(1);
+ private final List<Match> matches = new java.util.ArrayList<>(1);
public ReferencedMatches(String contextName) {
- this.contextName=contextName;
+ this.contextName = contextName;
}
public void addMatch(Match match) {
@@ -38,12 +38,12 @@ public class ReferencedMatches {
* @param label the label of the matches
*/
public Item toItem(String label) {
- if (matches.size()==0) return null;
- if (matches.size()==1) return matches.get(0).toItem(label);
+ if (matches.size() == 0) return null;
+ if (matches.size() == 1) return matches.get(0).toItem(label);
- PhraseItem phrase=new PhraseItem(); // TODO: Somehow allow AND items instead here
+ PhraseItem phrase = new PhraseItem(); // TODO: Somehow allow AND items instead here
phrase.setIndexName(label);
- for (Iterator<Match> i=matches.iterator(); i.hasNext(); ) {
+ for (Iterator<Match> i = matches.iterator(); i.hasNext(); ) {
phrase.addItem(i.next().toItem(label));
}
return phrase;
diff --git a/container-search/src/main/java/com/yahoo/prelude/semantics/engine/RuleEvaluation.java b/container-search/src/main/java/com/yahoo/prelude/semantics/engine/RuleEvaluation.java
index 29a781b1e68..09fcb6a424f 100644
--- a/container-search/src/main/java/com/yahoo/prelude/semantics/engine/RuleEvaluation.java
+++ b/container-search/src/main/java/com/yahoo/prelude/semantics/engine/RuleEvaluation.java
@@ -10,7 +10,7 @@ import com.yahoo.prelude.semantics.rule.ProductionRule;
import java.util.*;
/**
- * A particular evalutation of a particular rule.
+ * A particular evaluation of a particular rule.
*
* @author bratseth
*/
@@ -23,7 +23,7 @@ public class RuleEvaluation {
// Remember that whenever state is added to this class, you
// must consider whether/how to make that state backtrackable
- // by savinginformation in choicepoint.state
+ // by saving information in choicepoint.state
/** The items to match in this evaluation */
private List<FlattenedItem> items;
@@ -41,12 +41,12 @@ public class RuleEvaluation {
private String currentContext;
/** A list of referencedMatches */
- private List<ReferencedMatches> referencedMatchesList = new java.util.ArrayList<>();
+ private final List<ReferencedMatches> referencedMatchesList = new java.util.ArrayList<>();
- private List<Match> nonreferencedMatches = new java.util.ArrayList<>();
+ private final List<Match> nonreferencedMatches = new java.util.ArrayList<>();
/** The evaluation owning this */
- private Evaluation evaluation;
+ private final Evaluation evaluation;
/** The choice points saved in this evaluation */
private Stack<Choicepoint> choicepoints = null;
@@ -82,7 +82,7 @@ public class RuleEvaluation {
choicepoints.clear();
}
- public void setMatchReferences(Set<String> matchReferences) { this.matchReferences=matchReferences; }
+ public void setMatchReferences(Set<String> matchReferences) { this.matchReferences = matchReferences; }
/**
* <p>Calculates an id which is unique for each match (the totality of the matched terms)
@@ -96,23 +96,23 @@ public class RuleEvaluation {
* we add other kinds of conditions.</p>
*/
int calculateMatchDigest(ProductionRule rule) {
- int matchDigest=rule.hashCode();
- int matchCounter=1;
- for (Iterator<ReferencedMatches> i=referencedMatchesList.iterator(); i.hasNext(); ) {
- ReferencedMatches matches=i.next();
- int termCounter=0;
- for (Iterator<Match> j=matches.matchIterator(); j.hasNext(); ) {
- Match match=j.next();
- matchDigest=7*matchDigest*matchCounter+
- 71*termCounter+
- match.hashCode();
+ int matchDigest = rule.hashCode();
+ int matchCounter = 1;
+ for (Iterator<ReferencedMatches> i = referencedMatchesList.iterator(); i.hasNext(); ) {
+ ReferencedMatches matches = i.next();
+ int termCounter = 0;
+ for (Iterator<Match> j = matches.matchIterator(); j.hasNext(); ) {
+ Match match = j.next();
+ matchDigest = 7 * matchDigest * matchCounter+
+ 71 * termCounter +
+ match.hashCode();
termCounter++;
}
matchCounter++;
}
- for (Iterator<Match> i=nonreferencedMatches.iterator(); i.hasNext(); ) {
- Match match=i.next();
- matchDigest=7*matchDigest*matchCounter+match.hashCode();
+ for (Iterator<Match> i = nonreferencedMatches.iterator(); i.hasNext(); ) {
+ Match match = i.next();
+ matchDigest = 7 * matchDigest * matchCounter + match.hashCode();
matchCounter++;
}
return matchDigest;
@@ -139,7 +139,7 @@ public class RuleEvaluation {
/** Sets the current position */
public void setPosition(int position) {
- this.position=position;
+ this.position = position;
}
/** Returns the total number of items to match in this evaluation */
@@ -151,21 +151,21 @@ public class RuleEvaluation {
public Object getValue() { return value; }
/** Sets the last value returned by a condition in this evaluatiino, or null */
- public void setValue(Object value) { this.value=value; }
+ public void setValue(Object value) { this.value = value; }
/** Returns whether we are evaluating inside a condition which inverts the truth value */
public boolean isInNegation() { return inNegation; }
/** sets whether we are evaluating inside a condition which inverts the truth value */
- public void setInNegation(boolean inNegation) { this.inNegation=inNegation; }
+ public void setInNegation(boolean inNegation) { this.inNegation = inNegation; }
/** Returns the current position into the terms this evaluates over */
public int getPosition() { return position; }
/** Sets a new current label and returns the previous one */
public String setCurrentLabel(String currentLabel) {
- String oldLabel=currentLabel;
- this.currentLabel=currentLabel;
+ String oldLabel = currentLabel;
+ this.currentLabel = currentLabel;
return oldLabel;
}
@@ -179,8 +179,8 @@ public class RuleEvaluation {
public FlattenedItem next() {
position++;
- if (position>=items.size()) {
- position=items.size();
+ if (position >= items.size()) {
+ position = items.size();
return null;
}
@@ -189,17 +189,16 @@ public class RuleEvaluation {
// TODO: Simplistic yet. Nedd to support context nesting etc.
public void entering(String context) {
- if (context==null) return;
- if (matchReferences!=null && matchReferences.contains(context))
- currentContext=context;
-
+ if (context == null) return;
+ if (matchReferences != null && matchReferences.contains(context))
+ currentContext = context;
}
public void leaving(String context) {
- if (context==null) return;
- if (currentContext==null) return;
+ if (context == null) return;
+ if (currentContext == null) return;
if (currentContext.equals(context))
- currentContext=null;
+ currentContext = null;
}
/**
@@ -269,7 +268,7 @@ public class RuleEvaluation {
* @param termType the kind of item to index, this decides the resulting structure
*/
public void insertItem(Item item, CompositeItem parent, int index, TermType termType) {
- evaluation.insertItem(item,parent,index,termType);
+ evaluation.insertItem(item, parent, index, termType);
}
/** Returns a read-only view of the items of this */
@@ -282,7 +281,7 @@ public class RuleEvaluation {
}
public void trace(int level,String string) {
- evaluation.trace(level,string);
+ evaluation.trace(level, string);
}
public int getTraceLevel() {
@@ -304,24 +303,24 @@ public class RuleEvaluation {
* @param create true to create this choicepoint if it is missing
* @return the choicepoint, or null if not present, and create is false
*/
- public Choicepoint getChoicepoint(Condition condition,boolean create) {
- if (choicepoints==null) {
- if (!create) return null;
- choicepoints=new java.util.Stack<>();
+ public Choicepoint getChoicepoint(Condition condition, boolean create) {
+ if (choicepoints == null) {
+ if ( ! create) return null;
+ choicepoints = new java.util.Stack<>();
}
Choicepoint choicepoint=lookupChoicepoint(condition);
- if (choicepoint==null) {
- if (!create) return null;
- choicepoint=new Choicepoint(this,condition);
+ if (choicepoint == null) {
+ if ( ! create) return null;
+ choicepoint = new Choicepoint(this, condition);
choicepoints.push(choicepoint);
}
return choicepoint;
}
private Choicepoint lookupChoicepoint(Condition condition) {
- for (Iterator<Choicepoint> i=choicepoints.iterator(); i.hasNext(); ) {
- Choicepoint choicepoint=i.next();
- if (condition==choicepoint.getCondition())
+ for (Iterator<Choicepoint> i = choicepoints.iterator(); i.hasNext(); ) {
+ Choicepoint choicepoint = i.next();
+ if (condition == choicepoint.getCondition())
return choicepoint;
}
return null;
@@ -337,8 +336,8 @@ public class RuleEvaluation {
/** Remove all the terms recognized by this match */
public void removeMatches(ReferencedMatches matches) {
- for (Iterator<Match> i=matches.matchIterator(); i.hasNext(); ) {
- Match match=i.next();
+ for (Iterator<Match> i = matches.matchIterator(); i.hasNext(); ) {
+ Match match = i.next();
removeItemByIdentity(match.getItem());
}
}
diff --git a/container-search/src/main/java/com/yahoo/prelude/semantics/rule/LiteralPhraseProduction.java b/container-search/src/main/java/com/yahoo/prelude/semantics/rule/LiteralPhraseProduction.java
index cad753a570b..d06c4144fbc 100644
--- a/container-search/src/main/java/com/yahoo/prelude/semantics/rule/LiteralPhraseProduction.java
+++ b/container-search/src/main/java/com/yahoo/prelude/semantics/rule/LiteralPhraseProduction.java
@@ -19,7 +19,7 @@ import com.yahoo.protect.Validator;
*/
public class LiteralPhraseProduction extends TermProduction {
- private List<String> terms=new ArrayList<>();
+ private final List<String> terms = new ArrayList<>();
/** Creates a new produced literal phrase */
public LiteralPhraseProduction() {
@@ -45,20 +45,20 @@ public class LiteralPhraseProduction extends TermProduction {
public List<String> getTerms() { return Collections.unmodifiableList(terms); }
public void produce(RuleEvaluation e,int offset) {
- PhraseItem newPhrase=new PhraseItem();
+ PhraseItem newPhrase = new PhraseItem();
newPhrase.setIndexName(getLabel());
for (String term : terms)
newPhrase.addItem(new WordItem(term));
if (replacing) {
- Match matched=e.getNonreferencedMatch(0);
- insertMatch(e,matched,newPhrase,offset);
+ Match matched = e.getNonreferencedMatch(0);
+ insertMatch(e, matched, newPhrase, offset);
}
else {
newPhrase.setWeight(getWeight());
- if (e.getTraceLevel()>=6)
- e.trace(6,"Adding '" + newPhrase + "'");
- e.addItem(newPhrase,getTermType());
+ if (e.getTraceLevel() >= 6)
+ e.trace(6, "Adding '" + newPhrase + "'");
+ e.addItem(newPhrase, getTermType());
}
}
@@ -67,8 +67,8 @@ public class LiteralPhraseProduction extends TermProduction {
}
private String getSpaceSeparated(List<String> terms) {
- StringBuilder builder=new StringBuilder();
- for (Iterator<String> i=terms.iterator(); i.hasNext(); ) {
+ StringBuilder builder = new StringBuilder();
+ for (Iterator<String> i = terms.iterator(); i.hasNext(); ) {
builder.append(i.next());
if (i.hasNext())
builder.append(" ");
diff --git a/container-search/src/main/java/com/yahoo/prelude/semantics/rule/LiteralTermProduction.java b/container-search/src/main/java/com/yahoo/prelude/semantics/rule/LiteralTermProduction.java
index 69a4a87c04e..66d1df690bb 100644
--- a/container-search/src/main/java/com/yahoo/prelude/semantics/rule/LiteralTermProduction.java
+++ b/container-search/src/main/java/com/yahoo/prelude/semantics/rule/LiteralTermProduction.java
@@ -44,31 +44,32 @@ public class LiteralTermProduction extends TermProduction {
* @param literal this term word
* @param termType the type of term to produce
*/
- public LiteralTermProduction(String label,String literal, TermType termType) {
- super(label,termType);
+ public LiteralTermProduction(String label, String literal, TermType termType) {
+ super(label, termType);
setLiteral(literal);
}
/** The literal term value, never null */
public void setLiteral(String literal) {
- Validator.ensureNotNull("A produced term",literal);
+ Validator.ensureNotNull("A produced term", literal);
this.literal=literal;
}
/** Returns the term word produced, never null */
public String getLiteral() { return literal; }
- public void produce(RuleEvaluation e,int offset) {
- WordItem newItem=new WordItem(literal,getLabel());
+ public void produce(RuleEvaluation e, int offset) {
+ WordItem newItem = new WordItem(literal, getLabel());
if (replacing) {
- Match matched=e.getNonreferencedMatch(0);
- insertMatch(e,matched,newItem,offset);
+ Match matched = e.getNonreferencedMatch(0);
+ newItem.setWeight(matched.getItem().getWeight());
+ insertMatch(e, matched, newItem, offset);
}
else {
newItem.setWeight(getWeight());
- if (e.getTraceLevel()>=6)
- e.trace(6,"Adding '" + newItem + "'");
- e.addItem(newItem,getTermType());
+ if (e.getTraceLevel() >= 6)
+ e.trace(6, "Adding '" + newItem + "'");
+ e.addItem(newItem, getTermType());
}
}
diff --git a/container-search/src/main/java/com/yahoo/prelude/semantics/rule/Production.java b/container-search/src/main/java/com/yahoo/prelude/semantics/rule/Production.java
index 3073e2f7727..716622a4849 100644
--- a/container-search/src/main/java/com/yahoo/prelude/semantics/rule/Production.java
+++ b/container-search/src/main/java/com/yahoo/prelude/semantics/rule/Production.java
@@ -33,7 +33,7 @@ public abstract class Production {
public void setPosition(int position) { this.position = position; }
/** Sets the weight of this production as a percentage (default is 100) */
- public void setWeight(int weight) { this.weight=weight; }
+ public void setWeight(int weight) { this.weight = weight; }
/** Returns the weight of this production as a percentage (default is 100) */
public int getWeight() { return weight; }
diff --git a/container-search/src/main/java/com/yahoo/prelude/semantics/rule/ProductionList.java b/container-search/src/main/java/com/yahoo/prelude/semantics/rule/ProductionList.java
index 5b973228b1f..49d15710cd7 100644
--- a/container-search/src/main/java/com/yahoo/prelude/semantics/rule/ProductionList.java
+++ b/container-search/src/main/java/com/yahoo/prelude/semantics/rule/ProductionList.java
@@ -15,10 +15,10 @@ import com.yahoo.prelude.semantics.engine.RuleEvaluation;
*/
public class ProductionList {
- private List<Production> productions =new java.util.ArrayList<>();
+ private final List<Production> productions = new java.util.ArrayList<>();
/** True to replace by the production, false to add it */
- private boolean replacing=true;
+ private boolean replacing = true;
public void addProduction(Production term) {
term.setReplacing(replacing);
@@ -28,12 +28,12 @@ public class ProductionList {
/** True to replace, false to add, default true */
void setReplacing(boolean replacing) {
- for (Iterator<Production> i=productions.iterator(); i.hasNext(); ) {
- Production production=i.next();
+ for (Iterator<Production> i = productions.iterator(); i.hasNext(); ) {
+ Production production = i.next();
production.setReplacing(replacing);
}
- this.replacing=replacing;
+ this.replacing = replacing;
}
/** Returns an unmodifiable view of the productions in this */
@@ -42,22 +42,22 @@ public class ProductionList {
public int getTermCount() { return productions.size(); }
void addMatchReferences(Set<String> matchReferences) {
- for (Iterator<Production> i=productions.iterator(); i.hasNext(); ) {
- Production term=i.next();
+ for (Iterator<Production> i = productions.iterator(); i.hasNext(); ) {
+ Production term = i.next();
term.addMatchReferences(matchReferences);
}
}
public void produce(RuleEvaluation e) {
- for (int i=0; i<productions.size(); i++) {
- productions.get(i).produce(e,i);
+ for (int i = 0; i < productions.size(); i++) {
+ productions.get(i).produce(e, i);
}
}
public String toString() {
- StringBuilder buffer=new StringBuilder();
- for (Iterator<Production> i=productions.iterator(); i.hasNext(); ) {
- buffer.append(i.next().toString());
+ StringBuilder buffer = new StringBuilder();
+ for (Iterator<Production> i = productions.iterator(); i.hasNext(); ) {
+ buffer.append(i.next());
if (i.hasNext())
buffer.append(" ");
}
diff --git a/container-search/src/main/java/com/yahoo/prelude/semantics/rule/ReferenceTermProduction.java b/container-search/src/main/java/com/yahoo/prelude/semantics/rule/ReferenceTermProduction.java
index d4593a5dbb9..e1bb8ff102d 100644
--- a/container-search/src/main/java/com/yahoo/prelude/semantics/rule/ReferenceTermProduction.java
+++ b/container-search/src/main/java/com/yahoo/prelude/semantics/rule/ReferenceTermProduction.java
@@ -48,7 +48,7 @@ public class ReferenceTermProduction extends TermProduction {
* @param label the label of the produced term
* @param reference the label of the condition this should take it's value from
*/
- public ReferenceTermProduction(String label,String reference) {
+ public ReferenceTermProduction(String label, String reference) {
super(label);
setReference(reference);
}
@@ -60,15 +60,15 @@ public class ReferenceTermProduction extends TermProduction {
* @param reference the label of the condition this should take it's value from
* @param termType the type of term to produce
*/
- public ReferenceTermProduction(String label,String reference, TermType termType) {
- super(label,termType);
+ public ReferenceTermProduction(String label, String reference, TermType termType) {
+ super(label, termType);
setReference(reference);
}
/** The label of the condition this should take its value from, never null */
public void setReference(String reference) {
Validator.ensureNotNull("reference name of a produced reference term",reference);
- this.reference =reference;
+ this.reference = reference;
}
/** Returns the label of the condition this should take its value from, never null */
@@ -78,29 +78,29 @@ public class ReferenceTermProduction extends TermProduction {
matchReferences.add(reference);
}
- public void produce(RuleEvaluation e,int offset) {
- ReferencedMatches referencedMatches=e.getReferencedMatches(reference);
- if (referencedMatches==null)
+ public void produce(RuleEvaluation e, int offset) {
+ ReferencedMatches referencedMatches = e.getReferencedMatches(reference);
+ if (referencedMatches == null)
throw new EvaluationException("Referred match '" + reference + "' not found");
if (replacing) {
- replaceMatches(e,referencedMatches);
+ replaceMatches(e, referencedMatches);
}
else {
- addMatches(e,referencedMatches);
+ addMatches(e, referencedMatches);
}
}
- public void replaceMatches(RuleEvaluation e,ReferencedMatches referencedMatches) {
- Item referencedItem=referencedMatches.toItem(getLabel());
- if (referencedItem==null) return;
+ public void replaceMatches(RuleEvaluation e, ReferencedMatches referencedMatches) {
+ Item referencedItem = referencedMatches.toItem(getLabel());
+ if (referencedItem == null) return;
e.removeMatches(referencedMatches);
- insertMatch(e, referencedMatches.matchIterator().next(),referencedItem,0);
+ insertMatch(e, referencedMatches.matchIterator().next(), referencedItem, 0);
}
- private void addMatches(RuleEvaluation e,ReferencedMatches referencedMatches) {
- Item referencedItem=referencedMatches.toItem(getLabel());
- if (referencedItem==null) return;
- e.addItem(referencedItem,getTermType());
+ private void addMatches(RuleEvaluation e, ReferencedMatches referencedMatches) {
+ Item referencedItem = referencedMatches.toItem(getLabel());
+ if (referencedItem == null) return;
+ e.addItem(referencedItem, getTermType());
}
public String toInnerTermString() {
diff --git a/container-search/src/main/java/com/yahoo/prelude/semantics/rule/ReplacingProductionRule.java b/container-search/src/main/java/com/yahoo/prelude/semantics/rule/ReplacingProductionRule.java
index dfa423ec889..70151ea479b 100644
--- a/container-search/src/main/java/com/yahoo/prelude/semantics/rule/ReplacingProductionRule.java
+++ b/container-search/src/main/java/com/yahoo/prelude/semantics/rule/ReplacingProductionRule.java
@@ -14,7 +14,7 @@ public class ReplacingProductionRule extends ProductionRule {
/** Carries out the production of this rule */
public void produce(RuleEvaluation e) {
removeNonreferencedMatches(e);
- if (e.getTraceLevel()>=5) {
+ if (e.getTraceLevel() >= 5) {
e.trace(5,"Removed terms to get '" + e.getEvaluation().getQuery().getModel().getQueryTree().getRoot() + "', will add terms");
}
super.produce(e);
@@ -22,16 +22,16 @@ public class ReplacingProductionRule extends ProductionRule {
/** Remove items until there's only one item left */
private void removeNonreferencedMatches(RuleEvaluation e) {
- int itemCount=e.getEvaluation().getQuerySize();
+ int itemCount = e.getEvaluation().getQuerySize();
// Remove items backwards to ease index handling
- for (int i=e.getNonreferencedMatchCount()-1; i>=0; i--) {
+ for (int i = e.getNonreferencedMatchCount() - 1; i >= 0; i--) {
// Ensure we don't produce an empty query
- if (getProduction().getTermCount()==0 && itemCount==1)
+ if (getProduction().getTermCount() == 0 && itemCount == 1)
break;
itemCount--;
- Match match=e.getNonreferencedMatch(i);
+ Match match = e.getNonreferencedMatch(i);
match.getItem().getParent().removeItem(match.getPosition());
}
}
diff --git a/container-search/src/main/java/com/yahoo/prelude/semantics/rule/TermProduction.java b/container-search/src/main/java/com/yahoo/prelude/semantics/rule/TermProduction.java
index 847014ff646..d1a74a991da 100644
--- a/container-search/src/main/java/com/yahoo/prelude/semantics/rule/TermProduction.java
+++ b/container-search/src/main/java/com/yahoo/prelude/semantics/rule/TermProduction.java
@@ -51,7 +51,7 @@ public abstract class TermProduction extends Production {
/** Sets the term type to produce */
public void setTermType(TermType termType) {
- Validator.ensureNotNull("Type of produced Term",termType);
+ Validator.ensureNotNull("Type of produced Term", termType);
this.termType = termType;
}
@@ -60,19 +60,21 @@ public abstract class TermProduction extends Production {
* TODO: Move to ruleevaluation
*/
protected void insertMatch(RuleEvaluation e, Match matched, Item newItem, int offset) {
- newItem.setWeight(getWeight());
- int insertPosition=matched.getPosition()+offset;
+ if (getWeight() != 100)
+ newItem.setWeight(getWeight());
+ int insertPosition = matched.getPosition()+offset;
// This check is necessary (?) because earlier items may have been removed
// after we recorded the match position. It is sort of hackish. A cleaner
// solution would be to update the match position on changes
- if (insertPosition>matched.getParent().getItemCount()) {
- insertPosition=matched.getParent().getItemCount();
+ if (insertPosition > matched.getParent().getItemCount()) {
+ insertPosition = matched.getParent().getItemCount();
}
- e.insertItem(newItem,matched.getParent(),insertPosition,getTermType());
- if (e.getTraceLevel()>=6)
- e.trace(6,"Inserted item '" + newItem + "' at position " + insertPosition + " producing " + e.getEvaluation().getQuery().getModel().getQueryTree());
+ e.insertItem(newItem, matched.getParent(), insertPosition,getTermType());
+ if (e.getTraceLevel() >= 6)
+ e.trace(6, "Inserted item '" + newItem + "' at position " + insertPosition + " producing " +
+ e.getEvaluation().getQuery().getModel().getQueryTree());
}
protected String getLabelString() {
diff --git a/container-search/src/main/java/com/yahoo/prelude/statistics/StatisticsSearcher.java b/container-search/src/main/java/com/yahoo/prelude/statistics/StatisticsSearcher.java
index ebea2f95af7..24b631f1773 100644
--- a/container-search/src/main/java/com/yahoo/prelude/statistics/StatisticsSearcher.java
+++ b/container-search/src/main/java/com/yahoo/prelude/statistics/StatisticsSearcher.java
@@ -5,9 +5,9 @@ import com.yahoo.component.chain.dependencies.Before;
import com.yahoo.concurrent.CopyOnWriteHashMap;
import com.yahoo.container.protect.Error;
import com.yahoo.jdisc.Metric;
-import com.yahoo.log.LogLevel;
-import com.yahoo.metrics.simple.MetricSettings;
+import com.yahoo.jdisc.http.HttpRequest;
import com.yahoo.metrics.simple.MetricReceiver;
+import com.yahoo.metrics.simple.MetricSettings;
import com.yahoo.processing.request.CompoundName;
import com.yahoo.search.Query;
import com.yahoo.search.Result;
@@ -29,7 +29,18 @@ import java.util.PriorityQueue;
import java.util.Queue;
import java.util.logging.Level;
-import static com.yahoo.container.protect.Error.*;
+import static com.yahoo.container.protect.Error.BACKEND_COMMUNICATION_ERROR;
+import static com.yahoo.container.protect.Error.EMPTY_DOCUMENTS;
+import static com.yahoo.container.protect.Error.ERROR_IN_PLUGIN;
+import static com.yahoo.container.protect.Error.ILLEGAL_QUERY;
+import static com.yahoo.container.protect.Error.INTERNAL_SERVER_ERROR;
+import static com.yahoo.container.protect.Error.INVALID_QUERY_PARAMETER;
+import static com.yahoo.container.protect.Error.INVALID_QUERY_TRANSFORMATION;
+import static com.yahoo.container.protect.Error.NO_BACKENDS_IN_SERVICE;
+import static com.yahoo.container.protect.Error.RESULT_HAS_ERRORS;
+import static com.yahoo.container.protect.Error.SERVER_IS_MISCONFIGURED;
+import static com.yahoo.container.protect.Error.TIMEOUT;
+import static com.yahoo.container.protect.Error.UNSPECIFIED;
/**
@@ -55,39 +66,40 @@ public class StatisticsSearcher extends Searcher {
private static final String FAILED_QUERIES_METRIC = "failed_queries";
private static final String MEAN_QUERY_LATENCY_METRIC = "mean_query_latency";
private static final String QUERY_LATENCY_METRIC = "query_latency";
- private static final String QUERY_OFFSET_METRIC = "query_hit_offset";
+ private static final String QUERY_HIT_OFFSET_METRIC = "query_hit_offset";
private static final String QUERIES_METRIC = "queries";
- private static final String ACTIVE_QUERIES_METRIC = "active_queries";
private static final String PEAK_QPS_METRIC = "peak_qps";
private static final String DOCS_COVERED_METRIC = "documents_covered";
private static final String DOCS_TOTAL_METRIC = "documents_total";
- private static final String DEGRADED_METRIC = "degraded_queries";
+ private static final String DEGRADED_QUERIES_METRIC = "degraded_queries";
private static final String RELEVANCE_AT_1_METRIC = "relevance.at_1";
private static final String RELEVANCE_AT_3_METRIC = "relevance.at_3";
private static final String RELEVANCE_AT_10_METRIC = "relevance.at_10";
- private final Counter queries; // basic counter
- private final Counter failedQueries; // basic counter
- private final Counter nullQueries; // basic counter
- private final Counter illegalQueries; // basic counter
- private final Value queryLatency; // mean pr 5 min
+ private final Counter queriesCounter; // basic counter
+ private final Counter failedQueriesCounter; // basic counter
+ private final Counter nullQueriesCounter; // basic counter
+ private final Counter illegalQueriesCounter; // basic counter
+ private final Value meanQueryLatency; // mean pr 5 min
private final Value queryLatencyBuckets;
private final Value maxQueryLatency; // separate to avoid name mangling
@SuppressWarnings("unused") // all the work is done by the callback
private final Value peakQPS; // peak 1s QPS
- private final Counter emptyResults; // number of results containing no concrete hits
+ private final Counter emptyResultsCounter; // number of results containing no concrete hits
private final Value hitsPerQuery; // mean number of hits per query
+ private final Value totalHitsPerQuery;
private final PeakQpsReporter peakQpsReporter;
// Naming of enums are reflected directly in metric dimensions and should not be changed as they are public API
private enum DegradedReason { match_phase, adaptive_timeout, timeout, non_ideal_state }
- private Metric metric;
- private Map<String, Metric.Context> statePageOnlyContexts = new CopyOnWriteHashMap<>();
- private Map<String, Map<DegradedReason, Metric.Context>> degradedReasonContexts = new CopyOnWriteHashMap<>();
- private Map<String, Map<String, Metric.Context>> relevanceContexts = new CopyOnWriteHashMap<>();
- private java.util.Timer scheduler = new java.util.Timer(true);
+ private final Metric metric;
+ private final Map<String, Metric.Context> chainContexts = new CopyOnWriteHashMap<>();
+ private final Map<String, Metric.Context> statePageOnlyContexts = new CopyOnWriteHashMap<>();
+ private final Map<String, Map<DegradedReason, Metric.Context>> degradedReasonContexts = new CopyOnWriteHashMap<>();
+ private final Map<String, Map<String, Metric.Context>> relevanceContexts = new CopyOnWriteHashMap<>();
+ private final java.util.Timer scheduler = new java.util.Timer(true);
private class PeakQpsReporter extends java.util.TimerTask {
private long prevMaxQPSTime = System.currentTimeMillis();
@@ -127,17 +139,21 @@ public class StatisticsSearcher extends Searcher {
this.peakQpsReporter = new PeakQpsReporter();
this.metric = metric;
- queries = new Counter(QUERIES_METRIC, manager, false);
- failedQueries = new Counter(FAILED_QUERIES_METRIC, manager, false);
- nullQueries = new Counter("null_queries", manager, false);
- illegalQueries = new Counter("illegal_queries", manager, false);
- queryLatency = new Value(MEAN_QUERY_LATENCY_METRIC, manager, new Value.Parameters().setLogRaw(false).setLogMean(true).setNameExtension(false));
+ queriesCounter = new Counter(QUERIES_METRIC, manager, false);
+ failedQueriesCounter = new Counter(FAILED_QUERIES_METRIC, manager, false);
+ nullQueriesCounter = new Counter("null_queries", manager, false);
+ illegalQueriesCounter = new Counter("illegal_queries", manager, false);
+ meanQueryLatency = new Value(MEAN_QUERY_LATENCY_METRIC, manager, new Value.Parameters().setLogRaw(false).setLogMean(true).setNameExtension(false));
maxQueryLatency = new Value(MAX_QUERY_LATENCY_METRIC, manager, new Value.Parameters().setLogRaw(false).setLogMax(true).setNameExtension(false));
queryLatencyBuckets = Value.buildValue(QUERY_LATENCY_METRIC, manager, null);
peakQPS = new Value(PEAK_QPS_METRIC, manager, new Value.Parameters().setLogRaw(false).setLogMax(true).setNameExtension(false));
hitsPerQuery = new Value(HITS_PER_QUERY_METRIC, manager, new Value.Parameters().setLogRaw(false).setLogMean(true).setNameExtension(false));
- emptyResults = new Counter(EMPTY_RESULTS_METRIC, manager, false);
+ totalHitsPerQuery = new Value(TOTALHITS_PER_QUERY_METRIC, manager, new Value.Parameters().setLogRaw(false).setLogMean(true).setNameExtension(false));
+
+ emptyResultsCounter = new Counter(EMPTY_RESULTS_METRIC, manager, false);
metricReceiver.declareGauge(QUERY_LATENCY_METRIC, Optional.empty(), new MetricSettings.Builder().histogram(true).build());
+ metricReceiver.declareGauge(HITS_PER_QUERY_METRIC, Optional.empty(), new MetricSettings.Builder().histogram(true).build());
+ metricReceiver.declareGauge(TOTALHITS_PER_QUERY_METRIC, Optional.empty(), new MetricSettings.Builder().histogram(true).build());
scheduler.schedule(peakQpsReporter, 1000, 1000);
}
@@ -152,11 +168,15 @@ public class StatisticsSearcher extends Searcher {
peakQpsReporter.countQuery();
}
- private Metric.Context getChainMetricContext(String chainName, String endpoint) {
- Map<String, String> dimensions = new HashMap<>();
- dimensions.put("chain", chainName);
- dimensions.put("endpoint", endpoint);
- return this.metric.createContext(dimensions);
+ private Metric.Context getChainMetricContext(String chainName) {
+ Metric.Context context = chainContexts.get(chainName);
+ if (context == null) {
+ Map<String, String> dimensions = new HashMap<>();
+ dimensions.put("chain", chainName);
+ context = this.metric.createContext(dimensions);
+ chainContexts.put(chainName, context);
+ }
+ return context;
}
private Metric.Context getDegradedMetricContext(String chainName, Coverage coverage) {
@@ -223,11 +243,11 @@ public class StatisticsSearcher extends Searcher {
return execution.search(query);
}
- Metric.Context metricContext = getChainMetricContext(execution.chain().getId().stringValue(), query.getHttpRequest().getHeader("Host"));
+ Metric.Context metricContext = getChainMetricContext(execution.chain().getId().stringValue());
incrQueryCount(metricContext);
logQuery(query);
- long start = System.currentTimeMillis(); // Start time, in millisecs.
+ long start_ns = getStartNanoTime(query);
qps(metricContext);
Result result;
//handle exceptions thrown below in searchers
@@ -238,14 +258,14 @@ public class StatisticsSearcher extends Searcher {
throw e;
}
- long end = System.currentTimeMillis(); // Start time, in millisecs.
- long latency = end - start;
- if (latency >= 0) {
- addLatency(latency, metricContext);
+ long end_ns = System.nanoTime(); // End time, in nanoseconds
+ long latency_ns = end_ns - start_ns;
+ if (latency_ns >= 0) {
+ addLatency(latency_ns, metricContext);
} else {
- getLogger().log(LogLevel.WARNING,
- "Apparently negative latency measure, start: " + start
- + ", end: " + end + ", for query: " + query.toString());
+ getLogger().log(Level.WARNING,
+ "Apparently negative latency measure, start: " + start_ns
+ + ", end: " + end_ns + ", for query: " + query.toString());
}
if (result.hits().getError() != null) {
incrErrorCount(result, metricContext);
@@ -255,18 +275,22 @@ public class StatisticsSearcher extends Searcher {
if (queryCoverage != null) {
if (queryCoverage.isDegraded()) {
Metric.Context degradedContext = getDegradedMetricContext(execution.chain().getId().stringValue(), queryCoverage);
- metric.add(DEGRADED_METRIC, 1, degradedContext);
+ metric.add(DEGRADED_QUERIES_METRIC, 1, degradedContext);
}
metric.add(DOCS_COVERED_METRIC, queryCoverage.getDocs(), metricContext);
metric.add(DOCS_TOTAL_METRIC, queryCoverage.getActive(), metricContext);
}
int hitCount = result.getConcreteHitCount();
- hitsPerQuery.put((double) hitCount);
+ hitsPerQuery.put(hitCount);
metric.set(HITS_PER_QUERY_METRIC, (double) hitCount, metricContext);
- metric.set(TOTALHITS_PER_QUERY_METRIC, (double) result.getTotalHitCount(), metricContext);
- metric.set(QUERY_OFFSET_METRIC, (double) (query.getHits() + query.getOffset()), metricContext);
+
+ long totalHitCount = result.getTotalHitCount();
+ totalHitsPerQuery.put(totalHitCount);
+ metric.set(TOTALHITS_PER_QUERY_METRIC, (double) totalHitCount, metricContext);
+
+ metric.set(QUERY_HIT_OFFSET_METRIC, (double) (query.getHits() + query.getOffset()), metricContext);
if (hitCount == 0) {
- emptyResults.increment();
+ emptyResultsCounter.increment();
metric.add(EMPTY_RESULTS_METRIC, 1, metricContext);
}
@@ -283,9 +307,10 @@ public class StatisticsSearcher extends Searcher {
}
}
- private void addLatency(long latency, Metric.Context metricContext) {
+ private void addLatency(long latency_ns, Metric.Context metricContext) {
+ double latency = 0.000001 * latency_ns;
//myStats.addLatency(latency);
- queryLatency.put(latency);
+ meanQueryLatency.put(latency);
metric.set(QUERY_LATENCY_METRIC, latency, metricContext);
metric.set(MEAN_QUERY_LATENCY_METRIC, latency, metricContext);
maxQueryLatency.put(latency);
@@ -295,20 +320,20 @@ public class StatisticsSearcher extends Searcher {
private void incrQueryCount(Metric.Context metricContext) {
//myStats.incrQueryCnt();
- queries.increment();
+ queriesCounter.increment();
metric.add(QUERIES_METRIC, 1, metricContext);
}
private void incrErrorCount(Result result, Metric.Context metricContext) {
- failedQueries.increment();
+ failedQueriesCounter.increment();
metric.add(FAILED_QUERIES_METRIC, 1, metricContext);
if (result == null) // the chain threw an exception
metric.add("error.unhandled_exception", 1, metricContext);
else if (result.hits().getErrorHit().hasOnlyErrorCode(Error.NULL_QUERY.code))
- nullQueries.increment();
+ nullQueriesCounter.increment();
else if (result.hits().getErrorHit().hasOnlyErrorCode(Error.ILLEGAL_QUERY.code))
- illegalQueries.increment();
+ illegalQueriesCounter.increment();
}
/**
@@ -414,5 +439,15 @@ public class StatisticsSearcher extends Searcher {
}
}
+ /**
+ * Returns the relative start time from request was received by jdisc
+ */
+ private static long getStartNanoTime(Query query) {
+ return Optional.ofNullable(query.getHttpRequest())
+ .flatMap(httpRequest -> Optional.ofNullable(httpRequest.getJDiscRequest()))
+ .map(HttpRequest::relativeCreatedAtNanoTime)
+ .orElseGet(System::nanoTime);
+ }
+
}
diff --git a/container-search/src/main/java/com/yahoo/search/Query.java b/container-search/src/main/java/com/yahoo/search/Query.java
index 1e3f11f4f78..08ebd74da5a 100644
--- a/container-search/src/main/java/com/yahoo/search/Query.java
+++ b/container-search/src/main/java/com/yahoo/search/Query.java
@@ -7,10 +7,9 @@ import com.yahoo.collections.Tuple2;
import com.yahoo.component.Version;
import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.fs4.MapEncoder;
-import com.yahoo.log.LogLevel;
+import com.yahoo.language.process.Embedder;
import com.yahoo.prelude.fastsearch.DocumentDatabase;
import com.yahoo.prelude.query.Highlight;
-import com.yahoo.prelude.query.QueryException;
import com.yahoo.prelude.query.textualrepresentation.TextualQueryRepresentation;
import com.yahoo.processing.request.CompoundName;
import com.yahoo.search.dispatch.Dispatcher;
@@ -57,6 +56,8 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
import java.util.logging.Logger;
/**
@@ -182,7 +183,7 @@ public class Query extends com.yahoo.processing.Request implements Cloneable {
//---------------- Tracing ----------------------------------------------------
- private static Logger log = Logger.getLogger(Query.class.getName());
+ private static final Logger log = Logger.getLogger(Query.class.getName());
/** The time this query was created */
private long startTime;
@@ -201,7 +202,7 @@ public class Query extends com.yahoo.processing.Request implements Cloneable {
public static final CompoundName TIMEOUT = new CompoundName("timeout");
- private static QueryProfileType argumentType;
+ private static final QueryProfileType argumentType;
static {
argumentType = new QueryProfileType("native");
argumentType.setBuiltin(true);
@@ -227,7 +228,7 @@ public class Query extends com.yahoo.processing.Request implements Cloneable {
public static QueryProfileType getArgumentType() { return argumentType; }
/** The aliases of query properties */
- private static Map<String, CompoundName> propertyAliases;
+ private static final Map<String, CompoundName> propertyAliases;
static {
Map<String,CompoundName> propertyAliasesBuilder = new HashMap<>();
addAliases(Query.getArgumentType(), propertyAliasesBuilder);
@@ -317,7 +318,7 @@ public class Query extends com.yahoo.processing.Request implements Cloneable {
* Creates a query from a request
*
* @param request the HTTP request from which this is created
- * @param queryProfile the query profile to use for this query, or null if none.
+ * @param queryProfile the query profile to use for this query, or null if none
*/
public Query(HttpRequest request, CompiledQueryProfile queryProfile) {
this(request, request.propertyMap(), queryProfile);
@@ -326,27 +327,39 @@ public class Query extends com.yahoo.processing.Request implements Cloneable {
/**
* Creates a query from a request
*
- * @param request the HTTP request from which this is created.
- * @param requestMap the property map of the query.
- * @param queryProfile the query profile to use for this query, or null if none.
+ * @param request the HTTP request from which this is created
+ * @param requestMap the property map of the query
+ * @param queryProfile the query profile to use for this query, or null if none
*/
public Query(HttpRequest request, Map<String, String> requestMap, CompiledQueryProfile queryProfile) {
super(new QueryPropertyAliases(propertyAliases));
this.httpRequest = request;
- init(requestMap, queryProfile);
+ init(requestMap, queryProfile, Embedder.throwsOnUse);
+ }
+
+ // TODO: Deprecate most constructors above here
+
+ private Query(Builder builder) {
+ this(builder.getRequest(), builder.getRequestMap(), builder.getQueryProfile(), builder.getEmbedder());
+ }
+
+ private Query(HttpRequest request, Map<String, String> requestMap, CompiledQueryProfile queryProfile, Embedder embedder) {
+ super(new QueryPropertyAliases(propertyAliases));
+ this.httpRequest = request;
+ init(requestMap, queryProfile, embedder);
}
- private void init(Map<String, String> requestMap, CompiledQueryProfile queryProfile) {
- startTime = System.currentTimeMillis();
+ private void init(Map<String, String> requestMap, CompiledQueryProfile queryProfile, Embedder embedder) {
+ startTime = httpRequest.getJDiscRequest().creationTime(TimeUnit.MILLISECONDS);
if (queryProfile != null) {
// Move all request parameters to the query profile just to validate that the parameter settings are legal
- Properties queryProfileProperties = new QueryProfileProperties(queryProfile);
+ Properties queryProfileProperties = new QueryProfileProperties(queryProfile, embedder);
properties().chain(queryProfileProperties);
// TODO: Just checking legality rather than actually setting would be faster
setPropertiesFromRequestMap(requestMap, properties(), true); // Adds errors to the query for illegal set attempts
// Create the full chain
- properties().chain(new QueryProperties(this, queryProfile.getRegistry())).
+ properties().chain(new QueryProperties(this, queryProfile.getRegistry(), embedder)).
chain(new ModelObjectMap()).
chain(new RequestContextProperties(requestMap)).
chain(queryProfileProperties).
@@ -365,7 +378,7 @@ public class Query extends com.yahoo.processing.Request implements Cloneable {
}
else { // bypass these complications if there is no query profile to get values from and validate against
properties().
- chain(new QueryProperties(this, CompiledQueryProfileRegistry.empty)).
+ chain(new QueryProperties(this, CompiledQueryProfileRegistry.empty, embedder)).
chain(new PropertyMap()).
chain(new DefaultProperties());
setPropertiesFromRequestMap(requestMap, properties(), false);
@@ -413,11 +426,7 @@ public class Query extends com.yahoo.processing.Request implements Cloneable {
if (field.getType() == FieldType.genericQueryProfileType) { // Generic map
CompoundName fullName = prefix.append(field.getName());
for (Map.Entry<String, Object> entry : originalProperties.listProperties(fullName, context).entrySet()) {
- try {
- properties().set(fullName.append(entry.getKey()), entry.getValue(), context);
- } catch (IllegalArgumentException e) {
- throw new QueryException("Invalid request parameter", e);
- }
+ properties().set(fullName.append(entry.getKey()), entry.getValue(), context);
}
}
else if (field.getType() instanceof QueryProfileFieldType) { // Nested arguments
@@ -427,11 +436,7 @@ public class Query extends com.yahoo.processing.Request implements Cloneable {
CompoundName fullName = prefix.append(field.getName());
Object value = originalProperties.get(fullName, context);
if (value != null) {
- try {
- properties().set(fullName, value, context);
- } catch (IllegalArgumentException e) {
- throw new QueryException("Invalid request parameter", e);
- }
+ properties().set(fullName, value, context);
}
}
}
@@ -440,13 +445,8 @@ public class Query extends com.yahoo.processing.Request implements Cloneable {
/** Calls properties.set on all entries in requestMap */
private void setPropertiesFromRequestMap(Map<String, String> requestMap, Properties properties, boolean ignoreSelect) {
for (var entry : requestMap.entrySet()) {
- try {
- if (ignoreSelect && entry.getKey().equals(Select.SELECT)) continue;
- properties.set(entry.getKey(), entry.getValue(), requestMap);
- }
- catch (IllegalArgumentException e) {
- throw new QueryException("Invalid request parameter", e);
- }
+ if (ignoreSelect && entry.getKey().equals(Select.SELECT)) continue;
+ properties.set(entry.getKey(), entry.getValue(), requestMap);
}
}
@@ -663,7 +663,7 @@ public class Query extends com.yahoo.processing.Request implements Cloneable {
// getQueryTree isn't exception safe
try {
queryTree = model.getQueryTree().toString();
- } catch (Exception e) {
+ } catch (Exception | StackOverflowError e) {
queryTree = "[Could not parse user input: " + model.getQueryString() + "]";
}
return "query '" + queryTree + "'";
@@ -675,7 +675,7 @@ public class Query extends com.yahoo.processing.Request implements Cloneable {
// getQueryTree isn't exception safe
try {
queryTree = model.getQueryTree().toString();
- } catch (Exception e) {
+ } catch (Exception | StackOverflowError e) {
queryTree = "Could not parse user input: " + model.getQueryString();
}
return "query=[" + queryTree + "]" + " offset=" + getOffset() + " hits=" + getHits() + "]";
@@ -726,7 +726,7 @@ public class Query extends com.yahoo.processing.Request implements Cloneable {
if (includeQuery)
message += ": [" + queryTreeText() + "]";
- log.log(LogLevel.DEBUG,message);
+ log.log(Level.FINE,message);
// Pass 0 as traceLevel as the trace level check is already done above,
// and it is not propagated to trace until execution has started
@@ -995,6 +995,7 @@ public class Query extends com.yahoo.processing.Request implements Cloneable {
clone.properties().setParentQuery(clone);
assert (clone.properties().getParentQuery() == clone);
+ clone.setTimeout(getTimeout());
clone.setTraceLevel(getTraceLevel());
clone.setExplainLevel(getExplainLevel());
clone.setHits(getHits());
@@ -1124,4 +1125,59 @@ public class Query extends com.yahoo.processing.Request implements Cloneable {
getRanking().prepare();
}
+ public static class Builder {
+
+ private HttpRequest request = null;
+ private Map<String, String> requestMap = null;
+ private CompiledQueryProfile queryProfile = null;
+ private Embedder embedder = Embedder.throwsOnUse;
+
+ public Builder setRequest(String query) {
+ request = HttpRequest.createTestRequest(query, com.yahoo.jdisc.http.HttpRequest.Method.GET);
+ return this;
+ }
+
+ public Builder setRequest(HttpRequest request) {
+ this.request = request;
+ return this;
+ }
+
+ public HttpRequest getRequest() {
+ if (request == null)
+ return HttpRequest.createTestRequest("", com.yahoo.jdisc.http.HttpRequest.Method.GET);
+ return request;
+ }
+
+ /** Sets the request mao to use explicitly. If not set, the request map will be getRequest().propertyMap() */
+ public Builder setRequestMap(Map<String, String> requestMap) {
+ this.requestMap = requestMap;
+ return this;
+ }
+
+ public Map<String, String> getRequestMap() {
+ if (requestMap == null)
+ return getRequest().propertyMap();
+ return requestMap;
+ }
+
+ public Builder setQueryProfile(CompiledQueryProfile queryProfile) {
+ this.queryProfile = queryProfile;
+ return this;
+ }
+
+ /** Returns the query profile of this query, or null if none. */
+ public CompiledQueryProfile getQueryProfile() { return queryProfile; }
+
+ public Builder setEmbedder(Embedder embedder) {
+ this.embedder = embedder;
+ return this;
+ }
+
+ public Embedder getEmbedder() { return embedder; }
+
+ /** Creates a new query from this builder. No properties are required to before calling this. */
+ public Query build() { return new Query(this); }
+
+ }
+
}
diff --git a/container-search/src/main/java/com/yahoo/search/Result.java b/container-search/src/main/java/com/yahoo/search/Result.java
index 4080b09f40b..2e9018217bb 100644
--- a/container-search/src/main/java/com/yahoo/search/Result.java
+++ b/container-search/src/main/java/com/yahoo/search/Result.java
@@ -21,6 +21,8 @@ import java.util.Iterator;
* result items, as well as further HitGroups, making up a <i>composite</i> structure. This allows the hits of a result
* to be hierarchically organized. A Hit is polymorphic and may contain any kind of information deemed
* an approriate partial answer to the Query.
+ * <p>
+ * Do not cache this as it holds references to objects that should be garbage collected.
*
* @author bratseth
*/
@@ -51,7 +53,7 @@ public final class Result extends com.yahoo.processing.Response implements Clone
* Headers containing "envelope" meta information to be returned with this result.
* Used for HTTP getHeaders when the return protocol is HTTP.
*/
- private ListMap<String,String> headers = null;
+ private ListMap<String, String> headers = null;
/** Creates a new Result where the top level hit group has id "toplevel" */
public Result(Query query) {
@@ -66,7 +68,6 @@ public final class Result extends com.yahoo.processing.Response implements Clone
* @param query the query which produced this result
* @param hits the hit container which this will return from {@link #hits()}
*/
- @SuppressWarnings("deprecation")
public Result(Query query, HitGroup hits) {
super(query);
if (query==null) throw new NullPointerException("The query reference in a result cannot be null");
@@ -89,7 +90,6 @@ public final class Result extends com.yahoo.processing.Response implements Clone
* with a result. It should <b>always</b> be called when adding
* hits from a result, but there is no constraints on the order of the calls.
*/
- @SuppressWarnings("deprecation")
public void mergeWith(Result result) {
totalHitCount += result.getTotalHitCount();
deepHitCount += result.getDeepHitCount();
diff --git a/container-search/src/main/java/com/yahoo/search/Searcher.java b/container-search/src/main/java/com/yahoo/search/Searcher.java
index 5fefe9d2468..cd6b7167f08 100644
--- a/container-search/src/main/java/com/yahoo/search/Searcher.java
+++ b/container-search/src/main/java/com/yahoo/search/Searcher.java
@@ -73,6 +73,7 @@ public abstract class Searcher extends Processor {
// Note to developers: If you think you should add something here you are probably wrong
// Create a subclass containing the new method instead.
+
private final Logger logger = Logger.getLogger(getClass().getName());
public Searcher() {}
diff --git a/container-search/src/main/java/com/yahoo/search/cluster/BaseNodeMonitor.java b/container-search/src/main/java/com/yahoo/search/cluster/BaseNodeMonitor.java
index 0d491d2f0c1..8eee7c11d3e 100644
--- a/container-search/src/main/java/com/yahoo/search/cluster/BaseNodeMonitor.java
+++ b/container-search/src/main/java/com/yahoo/search/cluster/BaseNodeMonitor.java
@@ -44,7 +44,7 @@ public abstract class BaseNodeMonitor<T> {
protected MonitorConfiguration configuration;
/** Is the node we monitor part of an internal Vespa cluster or not */
- private boolean internal;
+ private final boolean internal;
public BaseNodeMonitor(boolean internal) {
this.internal=internal;
diff --git a/container-search/src/main/java/com/yahoo/search/cluster/ClusterMonitor.java b/container-search/src/main/java/com/yahoo/search/cluster/ClusterMonitor.java
index 15cf4995b77..c9b8aeee417 100644
--- a/container-search/src/main/java/com/yahoo/search/cluster/ClusterMonitor.java
+++ b/container-search/src/main/java/com/yahoo/search/cluster/ClusterMonitor.java
@@ -25,9 +25,9 @@ import java.util.logging.Logger;
*/
public class ClusterMonitor<T> {
- private final MonitorConfiguration configuration = new MonitorConfiguration();
+ private static final Logger log = Logger.getLogger(ClusterMonitor.class.getName());
- private static Logger log = Logger.getLogger(ClusterMonitor.class.getName());
+ private final MonitorConfiguration configuration = new MonitorConfiguration();
private final NodeManager<T> nodeManager;
@@ -62,6 +62,8 @@ public class ClusterMonitor<T> {
/** Returns the configuration of this cluster monitor */
public MonitorConfiguration getConfiguration() { return configuration; }
+ public boolean isClosed() { return closed.get(); }
+
/**
* Adds a new node for monitoring.
* The object representing the node must
diff --git a/container-search/src/main/java/com/yahoo/search/cluster/ClusterSearcher.java b/container-search/src/main/java/com/yahoo/search/cluster/ClusterSearcher.java
index 2d05168731a..db79c610dc7 100644
--- a/container-search/src/main/java/com/yahoo/search/cluster/ClusterSearcher.java
+++ b/container-search/src/main/java/com/yahoo/search/cluster/ClusterSearcher.java
@@ -3,7 +3,7 @@ package com.yahoo.search.cluster;
import com.yahoo.component.ComponentId;
import com.yahoo.container.protect.Error;
-import com.yahoo.log.LogLevel;
+import java.util.logging.Level;
import com.yahoo.prelude.Ping;
import com.yahoo.prelude.Pong;
import com.yahoo.yolean.Exceptions;
@@ -71,7 +71,7 @@ public abstract class ClusterSearcher<T> extends PingableSearcher implements Nod
/** Pinging a node, called from ClusterMonitor */
@Override
public final void ping(ClusterMonitor<T> clusterMonitor, T p, Executor executor) {
- log(LogLevel.FINE, "Sending ping to: ", p);
+ log(Level.FINE, "Sending ping to: ", p);
Pinger pinger = new Pinger(p);
FutureTask<Pong> future = new FutureTask<>(pinger);
@@ -97,10 +97,10 @@ public abstract class ClusterSearcher<T> extends PingableSearcher implements Nod
if (pong.badResponse()) {
clusterMonitor.failed(p, pong.error().get());
- log(LogLevel.FINE, "Failed ping - ", pong);
+ log(Level.FINE, "Failed ping - ", pong);
} else {
clusterMonitor.responded(p);
- log(LogLevel.FINE, "Answered ping - ", p);
+ log(Level.FINE, "Answered ping - ", p);
}
if (logThrowable != null) {
@@ -173,7 +173,7 @@ public abstract class ClusterSearcher<T> extends PingableSearcher implements Nod
if (result.hits().getError().getCode() == Error.TIMEOUT.code)
return result; // Retry is unlikely to help
- log(LogLevel.FINER, "No result, checking for timeout.");
+ log(Level.FINER, "No result, checking for timeout.");
tries++;
connection = nodes.select(code, tries);
} while (tries < nodes.getNodeCount());
@@ -211,7 +211,7 @@ public abstract class ClusterSearcher<T> extends PingableSearcher implements Nod
try {
result = search(query, execution, connection);
} catch (RuntimeException e) { //TODO: Exceptions should not be used to signal backend communication errors
- log(LogLevel.WARNING, "An exception occurred while invoking backend searcher.", e);
+ log(Level.WARNING, "An exception occurred while invoking backend searcher.", e);
result = new Result(query, ErrorMessage.createBackendCommunicationError("Failed calling " + connection +
" in " + this + " for " + query +
": " + Exceptions.toMessageString(e)));
@@ -220,14 +220,6 @@ public abstract class ClusterSearcher<T> extends PingableSearcher implements Nod
if (result == null)
result = new Result(query, ErrorMessage.createBackendCommunicationError("No result returned in " + this +
" from " + connection + " for " + query));
-
- if (result.hits().getError() != null) {
- log(LogLevel.FINE, "FAILED: ", query);
- } else if ( ! result.isCached()) {
- log(LogLevel.FINE, "WORKING: ", query);
- } else {
- log(LogLevel.FINE, "CACHE HIT: ", query);
- }
return result;
}
@@ -263,13 +255,6 @@ public abstract class ClusterSearcher<T> extends PingableSearcher implements Nod
result.hits().addError(ErrorMessage.createBackendCommunicationError("Error filling " + result + " from " + connection + ": " +
Exceptions.toMessageString(e)));
}
- if (result.hits().getError() != null) {
- log(LogLevel.FINE, "FAILED: ", result.getQuery());
- } else if ( ! result.isCached()) {
- log(LogLevel.FINE, "WORKING: ", result.getQuery());
- } else {
- log(LogLevel.FINE, "CACHE HIT: " + result.getQuery());
- }
}
/**
diff --git a/container-search/src/main/java/com/yahoo/search/cluster/Hasher.java b/container-search/src/main/java/com/yahoo/search/cluster/Hasher.java
index 46752b0bedb..6c83e1c64e3 100644
--- a/container-search/src/main/java/com/yahoo/search/cluster/Hasher.java
+++ b/container-search/src/main/java/com/yahoo/search/cluster/Hasher.java
@@ -11,6 +11,7 @@ package com.yahoo.search.cluster;
public class Hasher<T> {
public static class NodeFactor<T> {
+
private final T node;
/**
diff --git a/container-search/src/main/java/com/yahoo/search/cluster/MonitorConfiguration.java b/container-search/src/main/java/com/yahoo/search/cluster/MonitorConfiguration.java
index 21e5fe3bc7f..95f51b374d6 100644
--- a/container-search/src/main/java/com/yahoo/search/cluster/MonitorConfiguration.java
+++ b/container-search/src/main/java/com/yahoo/search/cluster/MonitorConfiguration.java
@@ -8,62 +8,44 @@ package com.yahoo.search.cluster;
*/
public class MonitorConfiguration {
- /**
- * The interval in ms between consecutive checks of the monitored
- * nodes
- */
- private long checkInterval=1000;
+ /** The interval in ms between consecutive checks of the monitored nodes */
+ private long checkInterval = 1000;
- /**
- * The number of milliseconds to attempt to complete a request
- * before giving up
- */
+ /** The number of milliseconds to attempt to complete a request before giving up */
private final long requestTimeout = 980;
- /**
- * The number of milliseconds a node is allowed to fail before we
- * mark it as not working
- */
- private long failLimit=5000;
+ /** The number of milliseconds a node is allowed to fail before we mark it as not working */
+ private long failLimit = 5000;
- /**
- * Sets the interval between each ping of idle or failing nodes
- * Default is 1000ms
- */
- public void setCheckInterval(long intervalMs) {
- this.checkInterval=intervalMs;
- }
+ /** Sets the interval between each ping of idle or failing nodes. Default is 1000 ms. */
+ @Deprecated // TODO: Remove on Vespa 8
+ public void setCheckInterval(long intervalMs) { this.checkInterval = intervalMs; }
- /**
- * Returns the interval between each ping of idle or failing nodes
- * Default is 1000ms
- */
- public long getCheckInterval() {
- return checkInterval;
- }
+ /** Returns the interval between each ping of idle or failing nodes. Default is 1000 ms. */
+ public long getCheckInterval() { return checkInterval; }
/**
- * Sets the number of times a failed node must respond before it is put
- * back in service. Default is 3.
- * @deprecated Will go away in Vespa 8
+ * Sets the number of times a failed node must respond before it is put back in service. Default is 3.
+ *
+ * @deprecated will go away in Vespa 8
*/
- @Deprecated
+ @Deprecated // TODO: Remove on Vespa 8
public void setResponseAfterFailLimit(int responseAfterFailLimit) { }
/**
- * Sets the number of ms a node (failing or working) is allowed to
- * stay idle before it is pinged. Default is 3000
+ * Sets the number of ms a node (failing or working) is allowed to stay idle before it is pinged. Default is 3000.
+ *
* @deprecated Will go away in Vespa 8
*/
- @Deprecated
+ @Deprecated // TODO: Remove on Vespa 8
public void setIdleLimit(int idleLimit) { }
/**
- * Gets the number of ms a node (failing or working)
- * is allowed to stay idle before it is pinged. Default is 3000
+ * Gets the number of ms a node (failing or working) is allowed to stay idle before it is pinged. Default is 3000.
+ *
* @deprecated Will go away in Vespa 8
*/
- @Deprecated
+ @Deprecated // TODO: Remove on Vespa 8
public long getIdleLimit() {
return 3000;
}
@@ -78,6 +60,7 @@ public class MonitorConfiguration {
* Sets the number of milliseconds a node is allowed to fail before we
* mark it as not working
*/
+ @Deprecated // TODO: Remove on Vespa 8
public void setFailLimit(long failLimit) { this.failLimit=failLimit; }
/**
@@ -91,25 +74,27 @@ public class MonitorConfiguration {
* in quarantine. Once in quarantine it won't be put back in
* productuion before quarantineTime has expired even if it is
* working. Default is 3
+ *
* @deprecated Will go away in Vespa 8
*/
- @Deprecated
+ @Deprecated // TODO: Remove on Vespa 8
public void setFailQuarantineLimit(int failQuarantineLimit) { }
/**
- * The number of ms an unstable node is quarantined. Default is
- * 100*60*60
+ * The number of ms an unstable node is quarantined. Default is 100*60*60
+ *
* @deprecated Will go away in Vespa 8
*/
- @Deprecated
+ @Deprecated // TODO: Remove on Vespa 8
public void setQuarantineTime(long quarantineTime) { }
+ @Override
public String toString() {
return "monitor configuration [" +
- "checkInterval: " + checkInterval +
- " requestTimeout " + requestTimeout +
- " failLimit " + failLimit +
- "]";
+ "checkInterval: " + checkInterval +
+ " requestTimeout " + requestTimeout +
+ " failLimit " + failLimit +
+ "]";
}
}
diff --git a/container-search/src/main/java/com/yahoo/search/cluster/NodeManager.java b/container-search/src/main/java/com/yahoo/search/cluster/NodeManager.java
index 481f1e1b5a5..836c71089c1 100644
--- a/container-search/src/main/java/com/yahoo/search/cluster/NodeManager.java
+++ b/container-search/src/main/java/com/yahoo/search/cluster/NodeManager.java
@@ -7,7 +7,7 @@ import java.util.concurrent.Executor;
* Must be implemented by a node collection which wants
* it's node state monitored by a ClusterMonitor
*
- * @author bratseth
+ * @author bratseth
*/
public interface NodeManager<T> {
@@ -20,9 +20,10 @@ public interface NodeManager<T> {
/**
* Called when a node should be pinged.
* This *must* lead to either a call to NodeMonitor.failed or NodeMonitor.responded
+ *
* @deprecated Use ping(ClusterMonitor clusterMonitor, T node, Executor executor) instead.
*/
- @Deprecated
+ @Deprecated // TODO: Remove on Vespa 8
default void ping(T node, Executor executor) {
throw new IllegalStateException("If you have not overrriden ping(ClusterMonitor<T> clusterMonitor, T node, Executor executor), you should at least have overriden this method.");
}
diff --git a/container-search/src/main/java/com/yahoo/search/cluster/TrafficNodeMonitor.java b/container-search/src/main/java/com/yahoo/search/cluster/TrafficNodeMonitor.java
index ccf3e863ff3..d17f6bfbaa8 100644
--- a/container-search/src/main/java/com/yahoo/search/cluster/TrafficNodeMonitor.java
+++ b/container-search/src/main/java/com/yahoo/search/cluster/TrafficNodeMonitor.java
@@ -52,8 +52,8 @@ public class TrafficNodeMonitor<T> extends BaseNodeMonitor<T> {
* Called when a response is received from this node.
*/
public void responded() {
- respondedAt=now();
- succeededAt=respondedAt;
+ respondedAt = now();
+ succeededAt = respondedAt;
setWorking(true,"Responds correctly");
}
@@ -69,20 +69,20 @@ public class TrafficNodeMonitor<T> extends BaseNodeMonitor<T> {
atStartUp = false;
if (this.isWorking == working) return; // Old news
- if (explanation==null) {
- explanation="";
+ if (explanation == null) {
+ explanation = "";
} else {
- explanation=": " + explanation;
+ explanation = ": " + explanation;
}
if (working) {
log.info("Putting " + node + " in service" + explanation);
} else {
log.warning("Taking " + node + " out of service" + explanation);
- failedAt=now();
+ failedAt = now();
}
- this.isWorking=working;
+ this.isWorking = working;
}
}
diff --git a/container-search/src/main/java/com/yahoo/search/dispatch/CloseableInvoker.java b/container-search/src/main/java/com/yahoo/search/dispatch/CloseableInvoker.java
index 515d6249fd8..9329f4a6819 100644
--- a/container-search/src/main/java/com/yahoo/search/dispatch/CloseableInvoker.java
+++ b/container-search/src/main/java/com/yahoo/search/dispatch/CloseableInvoker.java
@@ -12,6 +12,7 @@ import java.util.function.BiConsumer;
* @author ollivir
*/
public abstract class CloseableInvoker implements Closeable {
+
protected abstract void release();
private BiConsumer<Boolean, Long> teardown = null;
@@ -35,4 +36,5 @@ public abstract class CloseableInvoker implements Closeable {
}
release();
}
+
}
diff --git a/container-search/src/main/java/com/yahoo/search/dispatch/Dispatcher.java b/container-search/src/main/java/com/yahoo/search/dispatch/Dispatcher.java
index abe6fffba39..dd3c760d2f4 100644
--- a/container-search/src/main/java/com/yahoo/search/dispatch/Dispatcher.java
+++ b/container-search/src/main/java/com/yahoo/search/dispatch/Dispatcher.java
@@ -4,6 +4,7 @@ package com.yahoo.search.dispatch;
import com.google.inject.Inject;
import com.yahoo.component.AbstractComponent;
import com.yahoo.component.ComponentId;
+import com.yahoo.compress.Compressor;
import com.yahoo.container.handler.VipStatus;
import com.yahoo.jdisc.Metric;
import com.yahoo.prelude.fastsearch.VespaBackEndSearcher;
@@ -30,6 +31,7 @@ import java.util.List;
import java.util.Optional;
import java.util.OptionalInt;
import java.util.Set;
+import java.util.stream.Collectors;
/**
* A dispatcher communicates with search nodes to perform queries and fill hits.
@@ -48,6 +50,7 @@ public class Dispatcher extends AbstractComponent {
public static final String DISPATCH = "dispatch";
private static final String INTERNAL = "internal";
private static final String PROTOBUF = "protobuf";
+ private static final String TOP_K_PROBABILITY = "topKProbability";
private static final String INTERNAL_METRIC = "dispatch_internal";
@@ -56,9 +59,12 @@ public class Dispatcher extends AbstractComponent {
/** If enabled, search queries will use protobuf rpc */
public static final CompoundName dispatchProtobuf = CompoundName.fromComponents(DISPATCH, PROTOBUF);
+ /** If set will control computation of how many hits will be fetched from each partition.*/
+ public static final CompoundName topKProbability = CompoundName.fromComponents(DISPATCH, TOP_K_PROBABILITY);
+
/** A model of the search cluster this dispatches to */
private final SearchCluster searchCluster;
- private final ClusterMonitor clusterMonitor;
+ private final ClusterMonitor<Node> clusterMonitor;
private final LoadBalancer loadBalancer;
@@ -77,6 +83,7 @@ public class Dispatcher extends AbstractComponent {
argumentType.setBuiltin(true);
argumentType.addField(new FieldDescription(INTERNAL, FieldType.booleanType));
argumentType.addField(new FieldDescription(PROTOBUF, FieldType.booleanType));
+ argumentType.addField(new FieldDescription(TOP_K_PROBABILITY, FieldType.doubleType));
argumentType.freeze();
}
@@ -99,7 +106,7 @@ public class Dispatcher extends AbstractComponent {
}
/* Protected for simple mocking in tests. Beware that searchCluster is shutdown on in deconstruct() */
- protected Dispatcher(ClusterMonitor clusterMonitor,
+ protected Dispatcher(ClusterMonitor<Node> clusterMonitor,
SearchCluster searchCluster,
DispatchConfig dispatchConfig,
InvokerFactory invokerFactory,
@@ -116,21 +123,30 @@ public class Dispatcher extends AbstractComponent {
this.metricContext = metric.createContext(null);
this.maxHitsPerNode = dispatchConfig.maxHitsPerNode();
searchCluster.addMonitoring(clusterMonitor);
+ Thread warmup = new Thread(() -> warmup(dispatchConfig.warmuptime()));
+ warmup.start();
try {
while ( ! searchCluster.hasInformationAboutAllNodes()) {
Thread.sleep(1);
}
+ warmup.join();
} catch (InterruptedException e) {}
- /*
- * No we have information from all nodes and a ping iteration has completed.
- * Instead of waiting until next ping interval to update coverage and group state,
- * we should compute the state ourselves, so that when the dispatcher is ready the state
- * of its groups are also known.
- */
+ // Now we have information from all nodes and a ping iteration has completed.
+ // Instead of waiting until next ping interval to update coverage and group state,
+ // we should compute the state ourselves, so that when the dispatcher is ready the state
+ // of its groups are also known.
searchCluster.pingIterationCompleted();
}
+ /**
+ * Will run important code in order to trigger JIT compilation and avoid cold start issues.
+ * Currently warms up lz4 compression code.
+ */
+ private static long warmup(double seconds) {
+ return new Compressor().warmup(seconds);
+ }
+
/** Returns the search cluster this dispatches to */
public SearchCluster searchCluster() {
return searchCluster;
@@ -138,7 +154,7 @@ public class Dispatcher extends AbstractComponent {
@Override
public void deconstruct() {
- /* The clustermonitor must be shutdown first as it uses the invokerfactory through the searchCluster. */
+ // The clustermonitor must be shutdown first as it uses the invokerfactory through the searchCluster.
clusterMonitor.shutdown();
invokerFactory.release();
}
@@ -168,8 +184,8 @@ public class Dispatcher extends AbstractComponent {
if (nodes.isEmpty()) return Optional.empty();
query.trace(false, 2, "Dispatching with search path ", searchPath);
- return invokerFactory.createSearchInvoker(searcher, query,
- OptionalInt.empty(),
+ return invokerFactory.createSearchInvoker(searcher,
+ query,
nodes,
true,
maxHitsPerNode);
@@ -185,8 +201,7 @@ public class Dispatcher extends AbstractComponent {
query.trace(false, 2, "Dispatching to ", node);
return invokerFactory.createSearchInvoker(searcher,
query,
- OptionalInt.empty(),
- Arrays.asList(node),
+ List.of(node),
true,
maxHitsPerNode)
.orElseThrow(() -> new IllegalStateException("Could not dispatch directly to " + node));
@@ -195,7 +210,7 @@ public class Dispatcher extends AbstractComponent {
int covered = searchCluster.groupsWithSufficientCoverage();
int groups = searchCluster.orderedGroups().size();
int max = Integer.min(Integer.min(covered + 1, groups), MAX_GROUP_SELECTION_ATTEMPTS);
- Set<Integer> rejected = null;
+ Set<Integer> rejected = rejectGroupBlockingFeed(searchCluster.orderedGroups());
for (int i = 0; i < max; i++) {
Optional<Group> groupInCluster = loadBalancer.takeGroup(rejected);
if (groupInCluster.isEmpty()) break; // No groups available
@@ -204,7 +219,6 @@ public class Dispatcher extends AbstractComponent {
boolean acceptIncompleteCoverage = (i == max - 1);
Optional<SearchInvoker> invoker = invokerFactory.createSearchInvoker(searcher,
query,
- OptionalInt.of(group.id()),
group.nodes(),
acceptIncompleteCoverage,
maxHitsPerNode);
@@ -224,4 +238,21 @@ public class Dispatcher extends AbstractComponent {
throw new IllegalStateException("No suitable groups to dispatch query. Rejected: " + rejected);
}
+ /**
+ * We want to avoid groups blocking feed because their data may be out of date.
+ * If there is a single group blocking feed, we want to reject it.
+ * If multiple groups are blocking feed we should use them anyway as we may not have remaining
+ * capacity otherwise. Same if there are no other groups.
+ *
+ * @return a modifiable set containing the single group to reject, or null otherwise
+ */
+ private Set<Integer> rejectGroupBlockingFeed(List<Group> groups) {
+ if (groups.size() == 1) return null;
+ List<Group> groupsRejectingFeed = groups.stream().filter(Group::isBlockingWrites).collect(Collectors.toList());
+ if (groupsRejectingFeed.size() != 1) return null;
+ Set<Integer> rejected = new HashSet<>();
+ rejected.add(groupsRejectingFeed.get(0).id());
+ return rejected;
+ }
+
}
diff --git a/container-search/src/main/java/com/yahoo/search/dispatch/FillInvoker.java b/container-search/src/main/java/com/yahoo/search/dispatch/FillInvoker.java
index dd4c4494ac5..8b7714aaf3b 100644
--- a/container-search/src/main/java/com/yahoo/search/dispatch/FillInvoker.java
+++ b/container-search/src/main/java/com/yahoo/search/dispatch/FillInvoker.java
@@ -10,7 +10,8 @@ import com.yahoo.search.Result;
* @author ollivir
*/
public abstract class FillInvoker extends CloseableInvoker {
- /** Retrieve document summaries for the unfilled hits in the given {@link Result} */
+
+ /** Retrieves document summaries for the unfilled hits in the given {@link Result} */
public void fill(Result result, String summaryClass) {
sendFillRequest(result, summaryClass);
getFillResults(result, summaryClass);
@@ -19,4 +20,5 @@ public abstract class FillInvoker extends CloseableInvoker {
protected abstract void getFillResults(Result result, String summaryClass);
protected abstract void sendFillRequest(Result result, String summaryClass);
+
}
diff --git a/container-search/src/main/java/com/yahoo/search/dispatch/InterleavedSearchInvoker.java b/container-search/src/main/java/com/yahoo/search/dispatch/InterleavedSearchInvoker.java
index cec3e94d551..fb04c8299e9 100644
--- a/container-search/src/main/java/com/yahoo/search/dispatch/InterleavedSearchInvoker.java
+++ b/container-search/src/main/java/com/yahoo/search/dispatch/InterleavedSearchInvoker.java
@@ -3,6 +3,7 @@ package com.yahoo.search.dispatch;
import com.yahoo.search.Query;
import com.yahoo.search.Result;
+import com.yahoo.search.dispatch.searchcluster.Group;
import com.yahoo.search.dispatch.searchcluster.SearchCluster;
import com.yahoo.search.result.Coverage;
import com.yahoo.search.result.ErrorMessage;
@@ -41,6 +42,7 @@ public class InterleavedSearchInvoker extends SearchInvoker implements ResponseM
private final Set<SearchInvoker> invokers;
private final SearchCluster searchCluster;
+ private final Group group;
private final LinkedBlockingQueue<SearchInvoker> availableForProcessing;
private final Set<Integer> alreadyFailedNodes;
private Query query;
@@ -59,11 +61,15 @@ public class InterleavedSearchInvoker extends SearchInvoker implements ResponseM
private boolean timedOut = false;
private boolean degradedByMatchPhase = false;
- public InterleavedSearchInvoker(Collection<SearchInvoker> invokers, SearchCluster searchCluster, Set<Integer> alreadyFailedNodes) {
+ public InterleavedSearchInvoker(Collection<SearchInvoker> invokers,
+ SearchCluster searchCluster,
+ Group group,
+ Set<Integer> alreadyFailedNodes) {
super(Optional.empty());
this.invokers = Collections.newSetFromMap(new IdentityHashMap<>());
this.invokers.addAll(invokers);
this.searchCluster = searchCluster;
+ this.group = group;
this.availableForProcessing = newQueue();
this.alreadyFailedNodes = alreadyFailedNodes;
}
@@ -74,23 +80,33 @@ public class InterleavedSearchInvoker extends SearchInvoker implements ResponseM
* will be adjusted accordingly.
*/
@Override
- protected void sendSearchRequest(Query query) throws IOException {
+ protected Object sendSearchRequest(Query query, Object unusedContext) throws IOException {
this.query = query;
invokers.forEach(invoker -> invoker.setMonitor(this));
deadline = currentTime() + query.getTimeLeft();
int originalHits = query.getHits();
int originalOffset = query.getOffset();
- query.setHits(query.getHits() + query.getOffset());
+ int neededHits = originalHits + originalOffset;
+ int q = neededHits;
+ if (group.isBalanced() && !group.isSparse()) {
+ Double topkProbabilityOverrride = query.properties().getDouble(Dispatcher.topKProbability);
+ q = (topkProbabilityOverrride != null)
+ ? searchCluster.estimateHitsToFetch(neededHits, invokers.size(), topkProbabilityOverrride)
+ : searchCluster.estimateHitsToFetch(neededHits, invokers.size());
+ }
+ query.setHits(q);
query.setOffset(0);
+ Object context = null;
for (SearchInvoker invoker : invokers) {
- invoker.sendSearchRequest(query);
+ context = invoker.sendSearchRequest(query, context);
askedNodes++;
}
query.setHits(originalHits);
query.setOffset(originalOffset);
+ return null;
}
@Override
@@ -98,6 +114,8 @@ public class InterleavedSearchInvoker extends SearchInvoker implements ResponseM
InvokerResult result = new InvokerResult(query, query.getHits());
List<LeanHit> merged = Collections.emptyList();
long nextTimeout = query.getTimeLeft();
+ boolean extraDebug = (query.getOffset() == 0) && (query.getHits() == 7) && log.isLoggable(java.util.logging.Level.FINE);
+ List<InvokerResult> processed = new ArrayList<>();
try {
while (!invokers.isEmpty() && nextTimeout >= 0) {
SearchInvoker invoker = availableForProcessing.poll(nextTimeout, TimeUnit.MILLISECONDS);
@@ -105,7 +123,11 @@ public class InterleavedSearchInvoker extends SearchInvoker implements ResponseM
log.fine(() -> "Search timed out with " + askedNodes + " requests made, " + answeredNodes + " responses received");
break;
} else {
- merged = mergeResult(result.getResult(), invoker.getSearchResult(execution), merged);
+ InvokerResult toMerge = invoker.getSearchResult(execution);
+ if (extraDebug) {
+ processed.add(toMerge);
+ }
+ merged = mergeResult(result.getResult(), toMerge, merged);
ejectInvoker(invoker);
}
nextTimeout = nextTimeout();
@@ -116,6 +138,33 @@ public class InterleavedSearchInvoker extends SearchInvoker implements ResponseM
insertNetworkErrors(result.getResult());
result.getResult().setCoverage(createCoverage());
+
+ if (extraDebug && merged.size() > 0) {
+ int firstPartId = merged.get(0).getPartId();
+ for (int index = 1; index < merged.size(); index++) {
+ if (merged.get(index).getPartId() != firstPartId) {
+ extraDebug = false;
+ log.fine("merged["+index+"/"+merged.size()+"] from partId "+merged.get(index).getPartId()+", first "+firstPartId);
+ break;
+ }
+ }
+ }
+ if (extraDebug) {
+ log.fine("Interleaved "+processed.size()+" results");
+ for (int pIdx = 0; pIdx < processed.size(); ++pIdx) {
+ var p = processed.get(pIdx);
+ log.fine("InvokerResult "+pIdx+" total hits "+p.getResult().getTotalHitCount());
+ var lean = p.getLeanHits();
+ for (int idx = 0; idx < lean.size(); ++idx) {
+ var hit = lean.get(idx);
+ log.fine("lean hit "+idx+" relevance "+hit.getRelevance()+" partid "+hit.getPartId());
+ }
+ }
+ for (int mIdx = 0; mIdx < merged.size(); ++mIdx) {
+ var hit = merged.get(mIdx);
+ log.fine("merged hit "+mIdx+" relevance "+hit.getRelevance()+" partid "+hit.getPartId());
+ }
+ }
int needed = query.getOffset() + query.getHits();
for (int index = query.getOffset(); (index < merged.size()) && (index < needed); index++) {
result.getLeanHits().add(merged.get(index));
@@ -213,15 +262,15 @@ public class InterleavedSearchInvoker extends SearchInvoker implements ResponseM
int indexCurrent = 0;
int indexPartial = 0;
while (indexCurrent < current.size() && indexPartial < partial.size() && merged.size() < needed) {
- LeanHit incommingHit = partial.get(indexPartial);
+ LeanHit incomingHit = partial.get(indexPartial);
LeanHit currentHit = current.get(indexCurrent);
- int cmpRes = currentHit.compareTo(incommingHit);
+ int cmpRes = currentHit.compareTo(incomingHit);
if (cmpRes < 0) {
merged.add(currentHit);
indexCurrent++;
} else if (cmpRes > 0) {
- merged.add(incommingHit);
+ merged.add(incomingHit);
indexPartial++;
} else { // Duplicates
merged.add(currentHit);
@@ -230,10 +279,12 @@ public class InterleavedSearchInvoker extends SearchInvoker implements ResponseM
}
}
while ((indexCurrent < current.size()) && (merged.size() < needed)) {
- merged.add(current.get(indexCurrent++));
+ LeanHit currentHit = current.get(indexCurrent++);
+ merged.add(currentHit);
}
while ((indexPartial < partial.size()) && (merged.size() < needed)) {
- merged.add(partial.get(indexPartial++));
+ LeanHit incomingHit = partial.get(indexPartial++);
+ merged.add(incomingHit);
}
return merged;
}
@@ -321,4 +372,8 @@ public class InterleavedSearchInvoker extends SearchInvoker implements ResponseM
protected LinkedBlockingQueue<SearchInvoker> newQueue() {
return new LinkedBlockingQueue<>();
}
+
+ // For testing
+ Collection<SearchInvoker> invokers() { return invokers; }
+
}
diff --git a/container-search/src/main/java/com/yahoo/search/dispatch/InvokerFactory.java b/container-search/src/main/java/com/yahoo/search/dispatch/InvokerFactory.java
index 1c3a90ac6ab..e602afadcfb 100644
--- a/container-search/src/main/java/com/yahoo/search/dispatch/InvokerFactory.java
+++ b/container-search/src/main/java/com/yahoo/search/dispatch/InvokerFactory.java
@@ -4,6 +4,7 @@ package com.yahoo.search.dispatch;
import com.yahoo.prelude.fastsearch.VespaBackEndSearcher;
import com.yahoo.search.Query;
import com.yahoo.search.Result;
+import com.yahoo.search.dispatch.searchcluster.Group;
import com.yahoo.search.dispatch.searchcluster.Node;
import com.yahoo.search.dispatch.searchcluster.SearchCluster;
import com.yahoo.search.result.Coverage;
@@ -13,7 +14,6 @@ import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
-import java.util.OptionalInt;
import java.util.Set;
/**
@@ -35,23 +35,22 @@ public abstract class InvokerFactory {
public abstract FillInvoker createFillInvoker(VespaBackEndSearcher searcher, Result result);
/**
- * Create a {@link SearchInvoker} for a list of content nodes.
+ * Creates a {@link SearchInvoker} for a list of content nodes.
*
* @param searcher the searcher processing the query
* @param query the search query being processed
- * @param groupId the id of the node group to which the nodes belong
- * @param nodes pre-selected list of content nodes
+ * @param nodes pre-selected list of content nodes, all in a group or a subset of a group
* @param acceptIncompleteCoverage if some of the nodes are unavailable and this parameter is
* false, verify that the remaining set of nodes has sufficient coverage
- * @return Optional containing the SearchInvoker or empty if some node in the
+ * @return the invoker or empty if some node in the
* list is invalid and the remaining coverage is not sufficient
*/
- public Optional<SearchInvoker> createSearchInvoker(VespaBackEndSearcher searcher,
- Query query,
- OptionalInt groupId,
- List<Node> nodes,
- boolean acceptIncompleteCoverage,
- int maxHits) {
+ Optional<SearchInvoker> createSearchInvoker(VespaBackEndSearcher searcher,
+ Query query,
+ List<Node> nodes,
+ boolean acceptIncompleteCoverage,
+ int maxHits) {
+ Group group = searchCluster.group(nodes.get(0).group()).get(); // Nodes must be of the same group
List<SearchInvoker> invokers = new ArrayList<>(nodes.size());
Set<Integer> failed = null;
for (Node node : nodes) {
@@ -79,10 +78,10 @@ public abstract class InvokerFactory {
success.add(node);
}
}
- if ( ! searchCluster.isPartialGroupCoverageSufficient(groupId, success) && !acceptIncompleteCoverage) {
+ if ( ! searchCluster.isPartialGroupCoverageSufficient(success) && !acceptIncompleteCoverage) {
return Optional.empty();
}
- if(invokers.size() == 0) {
+ if (invokers.isEmpty()) {
return Optional.of(createCoverageErrorInvoker(nodes, failed));
}
}
@@ -90,7 +89,7 @@ public abstract class InvokerFactory {
if (invokers.size() == 1 && failed == null) {
return Optional.of(invokers.get(0));
} else {
- return Optional.of(new InterleavedSearchInvoker(invokers, searchCluster, failed));
+ return Optional.of(new InterleavedSearchInvoker(invokers, searchCluster, group, failed));
}
}
diff --git a/container-search/src/main/java/com/yahoo/search/dispatch/InvokerResult.java b/container-search/src/main/java/com/yahoo/search/dispatch/InvokerResult.java
index 94c347a6927..2723429c0cf 100644
--- a/container-search/src/main/java/com/yahoo/search/dispatch/InvokerResult.java
+++ b/container-search/src/main/java/com/yahoo/search/dispatch/InvokerResult.java
@@ -12,14 +12,19 @@ import java.util.List;
/**
* Wraps a Result and a flat, skinny hit list
+ *
+ * @author baldersheim
*/
public class InvokerResult {
+
private final Result result;
private final List<LeanHit> leanHits;
+
public InvokerResult(Result result) {
this.result = result;
this.leanHits = Collections.emptyList();
}
+
public InvokerResult(Query query, int expectedHits) {
result = new Result(query);
leanHits = new ArrayList<>(expectedHits);
@@ -32,6 +37,7 @@ public class InvokerResult {
public List<LeanHit> getLeanHits() {
return leanHits;
}
+
void complete() {
Query query = result.getQuery();
Sorting sorting = query.getRanking().getSorting();
@@ -47,4 +53,5 @@ public class InvokerResult {
}
leanHits.clear();
}
+
}
diff --git a/container-search/src/main/java/com/yahoo/search/dispatch/LeanHit.java b/container-search/src/main/java/com/yahoo/search/dispatch/LeanHit.java
index 86f1999d8b4..df8fb2f29fa 100644
--- a/container-search/src/main/java/com/yahoo/search/dispatch/LeanHit.java
+++ b/container-search/src/main/java/com/yahoo/search/dispatch/LeanHit.java
@@ -4,7 +4,11 @@ package com.yahoo.search.dispatch;
import java.util.Arrays;
+/**
+ * @author baldersheim
+ */
public class LeanHit implements Comparable<LeanHit> {
+
private final byte [] gid;
private final double relevance;
private final byte [] sortData;
@@ -12,19 +16,16 @@ public class LeanHit implements Comparable<LeanHit> {
private final int distributionKey;
public LeanHit(byte [] gid, int partId, int distributionKey, double relevance) {
- this.gid = gid;
- this.relevance = Double.isNaN(relevance) ? Double.NEGATIVE_INFINITY : relevance;
- this.sortData = null;
- this.partId = partId;
- this.distributionKey = distributionKey;
+ this(gid, partId, distributionKey, relevance, null);
}
- public LeanHit(byte [] gid, int partId, int distributionKey, byte [] sortData) {
+ public LeanHit(byte [] gid, int partId, int distributionKey, double relevance, byte [] sortData) {
this.gid = gid;
- this.relevance = 0.0;
+ this.relevance = Double.isNaN(relevance) ? Double.NEGATIVE_INFINITY : relevance;
this.sortData = sortData;
this.partId = partId;
this.distributionKey = distributionKey;
}
+
public double getRelevance() { return relevance; }
public byte [] getGid() { return gid; }
public byte [] getSortData() { return sortData; }
@@ -53,4 +54,5 @@ public class LeanHit implements Comparable<LeanHit> {
int vr = (int) right[i] & 0xFF;
return vl - vr;
}
+
}
diff --git a/container-search/src/main/java/com/yahoo/search/dispatch/LoadBalancer.java b/container-search/src/main/java/com/yahoo/search/dispatch/LoadBalancer.java
index 210ab5777d2..ebde2ffc611 100644
--- a/container-search/src/main/java/com/yahoo/search/dispatch/LoadBalancer.java
+++ b/container-search/src/main/java/com/yahoo/search/dispatch/LoadBalancer.java
@@ -43,8 +43,8 @@ public class LoadBalancer {
}
/**
- * Select and allocate the search cluster group which is to be used for the next search query. Callers <b>must</b> call
- * {@link #releaseGroup} symmetrically for each taken allocation.
+ * Select and allocate the search cluster group which is to be used for the next search query.
+ * Callers <b>must</b> call {@link #releaseGroup} symmetrically for each taken allocation.
*
* @param rejectedGroups if not null, the load balancer will only return groups with IDs not in the set
* @return the node group to target, or <i>empty</i> if the internal dispatch logic cannot be used
@@ -76,7 +76,7 @@ public class LoadBalancer {
synchronized (this) {
for (GroupStatus sched : scoreboard) {
if (sched.group.id() == group.id()) {
- sched.release(success, (double) searchTimeMs / 1000.0);
+ sched.release(success, searchTimeMs / 1000.0);
break;
}
}
@@ -134,6 +134,7 @@ public class LoadBalancer {
}
private static class RoundRobinScheduler implements GroupScheduler {
+
private int needle = 0;
private final List<GroupStatus> scoreboard;
@@ -204,6 +205,7 @@ public class LoadBalancer {
}
static class AdaptiveScheduler implements GroupScheduler {
+
private final Random random;
private final List<GroupStatus> scoreboard;
@@ -251,4 +253,5 @@ public class LoadBalancer {
return selectGroup(needle, false, rejectedGroups);
}
}
+
}
diff --git a/container-search/src/main/java/com/yahoo/search/dispatch/ResponseMonitor.java b/container-search/src/main/java/com/yahoo/search/dispatch/ResponseMonitor.java
index c2e81d43677..3ebd21fa18a 100644
--- a/container-search/src/main/java/com/yahoo/search/dispatch/ResponseMonitor.java
+++ b/container-search/src/main/java/com/yahoo/search/dispatch/ResponseMonitor.java
@@ -9,5 +9,7 @@ package com.yahoo.search.dispatch;
* @author ollivir
*/
public interface ResponseMonitor<T> {
+
void responseAvailable(T from);
+
}
diff --git a/container-search/src/main/java/com/yahoo/search/dispatch/SearchErrorInvoker.java b/container-search/src/main/java/com/yahoo/search/dispatch/SearchErrorInvoker.java
index a32931c43c8..7dbc2e98759 100644
--- a/container-search/src/main/java/com/yahoo/search/dispatch/SearchErrorInvoker.java
+++ b/container-search/src/main/java/com/yahoo/search/dispatch/SearchErrorInvoker.java
@@ -35,11 +35,12 @@ public class SearchErrorInvoker extends SearchInvoker {
}
@Override
- protected void sendSearchRequest(Query query) throws IOException {
+ protected Object sendSearchRequest(Query query, Object context) throws IOException {
this.query = query;
- if(monitor != null) {
+ if (monitor != null) {
monitor.responseAvailable(this);
}
+ return context;
}
@Override
@@ -60,4 +61,5 @@ public class SearchErrorInvoker extends SearchInvoker {
protected void setMonitor(ResponseMonitor<SearchInvoker> monitor) {
this.monitor = monitor;
}
+
}
diff --git a/container-search/src/main/java/com/yahoo/search/dispatch/SearchInvoker.java b/container-search/src/main/java/com/yahoo/search/dispatch/SearchInvoker.java
index 45ed1b87746..b33e91189cc 100644
--- a/container-search/src/main/java/com/yahoo/search/dispatch/SearchInvoker.java
+++ b/container-search/src/main/java/com/yahoo/search/dispatch/SearchInvoker.java
@@ -32,14 +32,21 @@ public abstract class SearchInvoker extends CloseableInvoker {
* for correct result windowing.
*/
public Result search(Query query, Execution execution) throws IOException {
- sendSearchRequest(query);
+ sendSearchRequest(query, null);
InvokerResult result = getSearchResult(execution);
setFinalStatus(result.getResult().hits().getError() == null);
result.complete();
return result.getResult();
}
- protected abstract void sendSearchRequest(Query query) throws IOException;
+ /**
+ *
+ * @param query the query to send
+ * @param context a context object that can be used to pass context among different
+ * invokers, e.g for reuse of preserialized data.
+ * @return an object that can be passed to the next invocation of sendSearchRequest
+ */
+ protected abstract Object sendSearchRequest(Query query, Object context) throws IOException;
protected abstract InvokerResult getSearchResult(Execution execution) throws IOException;
diff --git a/container-search/src/main/java/com/yahoo/search/dispatch/SearchPath.java b/container-search/src/main/java/com/yahoo/search/dispatch/SearchPath.java
index 1e0153761c9..b29c3297aea 100644
--- a/container-search/src/main/java/com/yahoo/search/dispatch/SearchPath.java
+++ b/container-search/src/main/java/com/yahoo/search/dispatch/SearchPath.java
@@ -1,7 +1,6 @@
// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.search.dispatch;
-import com.google.common.collect.ImmutableCollection;
import com.yahoo.collections.Pair;
import com.yahoo.search.dispatch.searchcluster.Group;
import com.yahoo.search.dispatch.searchcluster.Node;
@@ -13,6 +12,7 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
+import java.util.Random;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -28,15 +28,11 @@ import java.util.stream.IntStream;
public class SearchPath {
/**
- * Parse the search path and select nodes from the given cluster based on it.
+ * Parses the search path and select nodes from the given cluster based on it.
*
- * @param searchPath
- * unparsed search path expression (see: model.searchPath in Search
- * API reference)
- * @param cluster
- * the search cluster from which nodes are selected
- * @throws InvalidSearchPathException
- * if the searchPath is malformed
+ * @param searchPath unparsed search path expression (see: model.searchPath in Search API reference)
+ * @param cluster the search cluster from which nodes are selected
+ * @throws InvalidSearchPathException if the searchPath is malformed
* @return list of nodes chosen with the search path, or an empty list in which
* case some other node selection logic should be used
*/
@@ -45,7 +41,7 @@ public class SearchPath {
if (sp.isPresent()) {
return sp.get().mapToNodes(cluster);
} else {
- return Collections.emptyList();
+ return List.of();
}
}
@@ -68,17 +64,18 @@ public class SearchPath {
}
}
- private final List<NodeSelection> nodes;
- private final Integer group;
+ private final List<Selection> nodes;
+ private final List<Selection> groups;
+ private static final Random random = new Random();
- private SearchPath(List<NodeSelection> nodes, Integer group) {
+ private SearchPath(List<Selection> nodes, List<Selection> groups) {
this.nodes = nodes;
- this.group = group;
+ this.groups = groups;
}
private List<Node> mapToNodes(SearchCluster cluster) {
if (cluster.groups().isEmpty()) {
- return Collections.emptyList();
+ return List.of();
}
Group selectedGroup = selectGroup(cluster);
@@ -89,7 +86,7 @@ public class SearchPath {
List<Node> groupNodes = selectedGroup.nodes();
Set<Integer> wanted = new HashSet<>();
int max = groupNodes.size();
- for (NodeSelection node : nodes) {
+ for (Selection node : nodes) {
wanted.addAll(node.matches(max));
}
List<Node> ret = new ArrayList<>();
@@ -100,41 +97,52 @@ public class SearchPath {
}
private boolean isEmpty() {
- return nodes.isEmpty() && group == null;
+ return nodes.isEmpty() && groups.isEmpty();
}
- private Group selectGroup(SearchCluster cluster) {
- if (group != null) {
- Optional<Group> specificGroup = cluster.group(group);
- if (specificGroup.isPresent()) {
- return specificGroup.get();
+ private Group selectRandomGroupWithSufficientCoverage(SearchCluster cluster, List<Integer> groupIds) {
+ while ( groupIds.size() > 1 ) {
+ int index = random.nextInt(groupIds.size());
+ int groupId = groupIds.get(index);
+ Optional<Group> group = cluster.group(groupId);
+ if (group.isPresent()) {
+ if (group.get().hasSufficientCoverage()) {
+ return group.get();
+ } else {
+ groupIds.remove(index);
+ }
} else {
- throw new InvalidSearchPathException("Invalid searchPath, cluster does not have " + (group + 1) + " groups");
+ throw new InvalidSearchPathException("Invalid searchPath, cluster does not have " + (groupId + 1) + " groups");
}
}
+ return cluster.group(groupIds.get(0)).get();
+ }
- // pick "anything": try to find the first working
- ImmutableCollection<Group> groups = cluster.groups().values();
- for (Group g : groups) {
- if (g.hasSufficientCoverage()) {
- return g;
+ private Group selectGroup(SearchCluster cluster) {
+ if ( ! groups.isEmpty()) {
+ List<Integer> potentialGroups = new ArrayList<>();
+ for (Selection groupSelection : groups) {
+ for (int group = groupSelection.from; group < groupSelection.to; group++) {
+ potentialGroups.add(group);
+ }
}
+ return selectRandomGroupWithSufficientCoverage(cluster, potentialGroups);
}
- // fallback: first
- return groups.iterator().next();
+ // pick any working group
+ return selectRandomGroupWithSufficientCoverage(cluster, new ArrayList<>(cluster.groups().keySet()));
}
private static SearchPath parseElement(String element) {
- Pair<String, String> nodesAndGroup = halveAt('/', element);
- List<NodeSelection> nodes = parseNodes(nodesAndGroup.getFirst());
- Integer group = parseGroup(nodesAndGroup.getSecond());
+ Pair<String, String> nodesAndGroups = halveAt('/', element);
+ List<Selection> nodes = parseSelection(nodesAndGroups.getFirst());
+ List<Selection> groups = parseSelection(nodesAndGroups.getSecond());
- return new SearchPath(nodes, group);
+ return new SearchPath(nodes, groups);
}
- private static List<NodeSelection> parseNodes(String nodes) {
- List<NodeSelection> ret = new ArrayList<>();
+ private static List<Selection> parseSelection(String nodes) {
+ List<Selection> ret = new ArrayList<>();
while (nodes.length() > 0) {
if (nodes.startsWith("[")) {
nodes = parseNodeRange(nodes, ret);
@@ -148,8 +156,8 @@ public class SearchPath {
return ret;
}
- // an asterisk or an empty string followed by a comma or the end of the string
- private static final Pattern NODE_WILDCARD = Pattern.compile("^\\*?(?:,|$)");
+ // An asterisk or forward slash or an empty string followed by a comma or the end of the string
+ private static final Pattern NODE_WILDCARD = Pattern.compile("^\\*?(?:,|$|/$)");
private static boolean isWildcard(String node) {
return NODE_WILDCARD.matcher(node).lookingAt();
@@ -157,40 +165,30 @@ public class SearchPath {
private static final Pattern NODE_RANGE = Pattern.compile("^\\[(\\d+),(\\d+)>(?:,|$)");
- private static String parseNodeRange(String nodes, List<NodeSelection> into) {
+ private static String parseNodeRange(String nodes, List<Selection> into) {
Matcher m = NODE_RANGE.matcher(nodes);
if (m.find()) {
String ret = nodes.substring(m.end());
- Integer start = Integer.parseInt(m.group(1));
- Integer end = Integer.parseInt(m.group(2));
+ int start = Integer.parseInt(m.group(1));
+ int end = Integer.parseInt(m.group(2));
if (start > end) {
throw new InvalidSearchPathException("Invalid range");
}
- into.add(new NodeSelection(start, end));
+ into.add(new Selection(start, end));
return ret;
} else {
throw new InvalidSearchPathException("Invalid range expression");
}
}
- private static String parseNodeNum(String nodes, List<NodeSelection> into) {
+ private static String parseNodeNum(String nodes, List<Selection> into) {
Pair<String, String> numAndRest = halveAt(',', nodes);
int nodeNum = Integer.parseInt(numAndRest.getFirst());
- into.add(new NodeSelection(nodeNum, nodeNum + 1));
+ into.add(new Selection(nodeNum, nodeNum + 1));
return numAndRest.getSecond();
}
- private static Integer parseGroup(String group) {
- if (group.isEmpty()) {
- return null;
- }
- if ("/".equals(group) || "*".equals(group)) { // anything goes
- return null;
- }
- return Integer.parseInt(group);
- }
-
private static Pair<String, String> halveAt(char divider, String string) {
int pos = string.indexOf(divider);
if (pos >= 0) {
@@ -199,11 +197,9 @@ public class SearchPath {
return new Pair<>(string, "");
}
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder();
+ private static void selectionToString(StringBuilder sb, List<Selection> nodes) {
boolean first = true;
- for (NodeSelection p : nodes) {
+ for (Selection p : nodes) {
if (first) {
first = false;
} else {
@@ -211,17 +207,24 @@ public class SearchPath {
}
sb.append(p.toString());
}
- if (group != null) {
- sb.append('/').append(group);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ selectionToString(sb, nodes);
+ if ( ! groups.isEmpty()) {
+ sb.append('/');
+ selectionToString(sb, groups);
}
return sb.toString();
}
- private static class NodeSelection {
+ private static class Selection {
private final int from;
private final int to;
- NodeSelection(int from, int to) {
+ Selection(int from, int to) {
this.from = from;
this.to = to;
}
@@ -230,7 +233,7 @@ public class SearchPath {
if (from >= max) {
return Collections.emptyList();
}
- int end = (to > max) ? max : to;
+ int end = Math.min(to, max);
return IntStream.range(from, end).boxed().collect(Collectors.toList());
}
diff --git a/container-search/src/main/java/com/yahoo/search/dispatch/TopKEstimator.java b/container-search/src/main/java/com/yahoo/search/dispatch/TopKEstimator.java
new file mode 100644
index 00000000000..315dfdd4320
--- /dev/null
+++ b/container-search/src/main/java/com/yahoo/search/dispatch/TopKEstimator.java
@@ -0,0 +1,79 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.search.dispatch;
+
+import org.apache.commons.math3.distribution.TDistribution;
+
+/**
+ * Use StudentT distribution and estimate how many hits you need from each partition
+ * to to get the globally top-k documents with the desired probability
+ *
+ * @author baldersheim
+ */
+public class TopKEstimator {
+
+ private final TDistribution studentT;
+ private final double defaultP;
+ private final boolean estimate;
+ private final double skewFactor;
+ private final double [] defaultCumulativeProbability;
+ private final static int MIN_N = 2;
+
+ private static boolean needEstimate(double p) {
+ return (0.0 < p) && (p < 1.0);
+ }
+
+ TopKEstimator(double freedom, double defaultProbability) {
+ this(freedom, defaultProbability, 0.0);
+ }
+
+ public TopKEstimator(double freedom, double defaultProbability, double skewFactor) {
+ this.studentT = new TDistribution(null, freedom);
+ defaultP = defaultProbability;
+ estimate = needEstimate(defaultP);
+ this.skewFactor = skewFactor;
+ defaultCumulativeProbability = new double[64];
+ for (int i=0; i < defaultCumulativeProbability.length; i++) {
+ defaultCumulativeProbability[i] = computeCumulativeProbability(i+MIN_N, defaultP);
+ }
+ }
+
+ private double inverseCumulativeProbability(int n, double p) {
+ if (p == defaultP && (n >= MIN_N) && (n < defaultCumulativeProbability.length + MIN_N)) {
+ return defaultCumulativeProbability[n - MIN_N];
+ }
+ return computeCumulativeProbability(n, p);
+ }
+
+ private double computeCumulativeProbability(int n, double p) {
+ double p_inverse = 1 - (1 - p)/computeN(n);
+ return studentT.inverseCumulativeProbability(p_inverse);
+ }
+
+ private double computeN(double n) {
+ double p_max = (1 + skewFactor)/n;
+ return Math.max(1, 1/p_max);
+ }
+
+ double estimateExactK(double k, int n_i, double p) {
+ double n = computeN(n_i);
+ double variance = k * 1/n * (1 - 1/n);
+ return k/n + inverseCumulativeProbability(n_i, p) * Math.sqrt(variance);
+ }
+
+ double estimateExactK(double k, int n) {
+ return estimateExactK(k, n, defaultP);
+ }
+
+ public int estimateK(int k, int n) {
+ return (estimate && (n >= MIN_N))
+ ? Math.min(k, (int)Math.ceil(estimateExactK(k, n, defaultP)))
+ : k;
+ }
+
+ public int estimateK(int k, int n, double p) {
+ return (needEstimate(p) && (n >= MIN_N))
+ ? Math.min(k, (int)Math.ceil(estimateExactK(k, n, p)))
+ : k;
+ }
+}
+
diff --git a/container-search/src/main/java/com/yahoo/search/dispatch/rpc/Client.java b/container-search/src/main/java/com/yahoo/search/dispatch/rpc/Client.java
index bc0a38617ee..53cbee114b9 100644
--- a/container-search/src/main/java/com/yahoo/search/dispatch/rpc/Client.java
+++ b/container-search/src/main/java/com/yahoo/search/dispatch/rpc/Client.java
@@ -16,6 +16,7 @@ interface Client {
/** Creates a connection to a particular node in this */
NodeConnection createConnection(String hostname, int port);
+ void close();
interface ResponseReceiver {
void receive(ResponseOrError<ProtobufResponse> response);
@@ -87,7 +88,7 @@ interface Client {
RpcFillInvoker.GetDocsumsResponseReceiver responseReceiver, double timeoutSeconds);
void request(String rpcMethod, CompressionType compression, int uncompressedLength, byte[] compressedPayload,
- ResponseReceiver responseReceiver, double timeoutSeconds);
+ ResponseReceiver responseReceiver, double timeoutSeconds);
/** Closes this connection */
void close();
diff --git a/container-search/src/main/java/com/yahoo/search/dispatch/rpc/ProtobufSerialization.java b/container-search/src/main/java/com/yahoo/search/dispatch/rpc/ProtobufSerialization.java
index ae2258c4546..250524fadf2 100644
--- a/container-search/src/main/java/com/yahoo/search/dispatch/rpc/ProtobufSerialization.java
+++ b/container-search/src/main/java/com/yahoo/search/dispatch/rpc/ProtobufSerialization.java
@@ -101,7 +101,7 @@ public class ProtobufSerialization {
mergeToSearchRequestFromSorting(ranking.getSorting(), builder);
}
if (ranking.getLocation() != null) {
- builder.setGeoLocation(ranking.getLocation().toString());
+ builder.setGeoLocation(ranking.getLocation().backendString());
}
var featureMap = ranking.getFeatures().asMap();
@@ -144,7 +144,7 @@ public class ProtobufSerialization {
builder.setRankProfile(ranking.getProfile());
if (ranking.getLocation() != null) {
- builder.setGeoLocation(ranking.getLocation().toString());
+ builder.setGeoLocation(ranking.getLocation().backendString());
}
if (includeQueryData) {
mergeQueryDataToDocsumRequest(query, builder);
@@ -196,7 +196,7 @@ public class ProtobufSerialization {
result.getResult().setTotalHitCount(protobuf.getTotalHitCount());
result.getResult().setCoverage(convertToCoverage(protobuf));
- var haveGrouping = protobuf.getGroupingBlob() != null && !protobuf.getGroupingBlob().isEmpty();
+ var haveGrouping = ! protobuf.getGroupingBlob().isEmpty();
if (haveGrouping) {
BufferSerializer buf = new BufferSerializer(new GrowableByteBuffer(protobuf.getGroupingBlob().asReadOnlyByteBuffer()));
int cnt = buf.getInt(null);
@@ -214,12 +214,12 @@ public class ProtobufSerialization {
for (var replyHit : protobuf.getHitsList()) {
LeanHit hit = (replyHit.getSortData().isEmpty())
? new LeanHit(replyHit.getGlobalId().toByteArray(), partId, distKey, replyHit.getRelevance())
- : new LeanHit(replyHit.getGlobalId().toByteArray(), partId, distKey, replyHit.getSortData().toByteArray());
+ : new LeanHit(replyHit.getGlobalId().toByteArray(), partId, distKey, replyHit.getRelevance(), replyHit.getSortData().toByteArray());
result.getLeanHits().add(hit);
}
var slimeTrace = protobuf.getSlimeTrace();
- if (slimeTrace != null && !slimeTrace.isEmpty()) {
+ if ( ! slimeTrace.isEmpty()) {
var traces = new Value.ArrayValue();
traces.add(new SlimeAdapter(BinaryFormat.decode(slimeTrace.toByteArray()).get()));
query.trace(traces, query.getTraceLevel());
diff --git a/container-search/src/main/java/com/yahoo/search/dispatch/rpc/RpcClient.java b/container-search/src/main/java/com/yahoo/search/dispatch/rpc/RpcClient.java
index a2821892358..70d94c5a8a6 100644
--- a/container-search/src/main/java/com/yahoo/search/dispatch/rpc/RpcClient.java
+++ b/container-search/src/main/java/com/yahoo/search/dispatch/rpc/RpcClient.java
@@ -25,8 +25,13 @@ class RpcClient implements Client {
private final Supervisor supervisor;
- public RpcClient(int transportThreads) {
- supervisor = new Supervisor(new Transport(transportThreads));
+ public RpcClient(String name, int transportThreads) {
+ supervisor = new Supervisor(new Transport(name, transportThreads));
+ }
+
+ @Override
+ public void close() {
+ supervisor.transport().shutdown().join();
}
@Override
@@ -44,13 +49,14 @@ class RpcClient implements Client {
// The current shared connection. This will be recycled when it becomes invalid.
// All access to this must be synchronized
- private Target target = null;
+ private Target target;
public RpcNodeConnection(String hostname, int port, Supervisor supervisor) {
this.supervisor = supervisor;
this.hostname = hostname;
this.port = port;
description = "rpc node connection to " + hostname + ":" + port;
+ target = supervisor.connect(new Spec(hostname, port));
}
@Override
@@ -79,17 +85,16 @@ class RpcClient implements Client {
private void invokeAsync(Request req, double timeout, RequestWaiter waiter) {
// TODO: Consider replacing this by a watcher on the target
synchronized(this) { // ensure we have exactly 1 valid connection across threads
- if (target == null || ! target.isValid())
+ if (! target.isValid()) {
target = supervisor.connect(new Spec(hostname, port));
+ }
}
target.invokeAsync(req, timeout, waiter);
}
@Override
public void close() {
- if (target != null) {
- target.close();
- }
+ target.close();
}
@Override
diff --git a/container-search/src/main/java/com/yahoo/search/dispatch/rpc/RpcFillInvoker.java b/container-search/src/main/java/com/yahoo/search/dispatch/rpc/RpcFillInvoker.java
index 9b661368972..0e8759f740e 100644
--- a/container-search/src/main/java/com/yahoo/search/dispatch/rpc/RpcFillInvoker.java
+++ b/container-search/src/main/java/com/yahoo/search/dispatch/rpc/RpcFillInvoker.java
@@ -137,7 +137,7 @@ public class RpcFillInvoker extends FillInvoker {
root.setString("ranking", rankProfile);
}
if (location != null) {
- root.setString("location", location.toString());
+ root.setString("location", location.backendString());
}
Cursor gids = root.setArray("gids");
for (FastHit hit : hits) {
diff --git a/container-search/src/main/java/com/yahoo/search/dispatch/rpc/RpcPing.java b/container-search/src/main/java/com/yahoo/search/dispatch/rpc/RpcPing.java
index 5e04f1d7a3e..26abe92a6f1 100644
--- a/container-search/src/main/java/com/yahoo/search/dispatch/rpc/RpcPing.java
+++ b/container-search/src/main/java/com/yahoo/search/dispatch/rpc/RpcPing.java
@@ -22,6 +22,7 @@ public class RpcPing implements Pinger, Client.ResponseReceiver {
private static final Logger log = Logger.getLogger(RpcPing.class.getName());
private static final String RPC_METHOD = "vespa.searchprotocol.ping";
private static final CompressionType PING_COMPRESSION = CompressionType.NONE;
+ private static final boolean triggeredClassLoading = ErrorMessage.createBackendCommunicationError("TriggerClassLoading") instanceof ErrorMessage;
private final Node node;
private final RpcResourcePool resourcePool;
@@ -86,6 +87,7 @@ public class RpcPing implements Pinger, Client.ResponseReceiver {
@Override
public void receive(ResponseOrError<ProtobufResponse> response) {
+ if (clusterMonitor.isClosed() && ! triggeredClassLoading) return;
if (node.isLastReceivedPong(pingSequenceId)) {
pongHandler.handle(toPong(response));
} else {
diff --git a/container-search/src/main/java/com/yahoo/search/dispatch/rpc/RpcPingFactory.java b/container-search/src/main/java/com/yahoo/search/dispatch/rpc/RpcPingFactory.java
index ac8f0a59c20..01e3ec3ca2b 100644
--- a/container-search/src/main/java/com/yahoo/search/dispatch/rpc/RpcPingFactory.java
+++ b/container-search/src/main/java/com/yahoo/search/dispatch/rpc/RpcPingFactory.java
@@ -1,3 +1,4 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.search.dispatch.rpc;
import com.yahoo.search.cluster.ClusterMonitor;
@@ -7,12 +8,16 @@ import com.yahoo.search.dispatch.searchcluster.Pinger;
import com.yahoo.search.dispatch.searchcluster.PongHandler;
public class RpcPingFactory implements PingFactory {
+
private final RpcResourcePool rpcResourcePool;
+
public RpcPingFactory(RpcResourcePool rpcResourcePool) {
this.rpcResourcePool = rpcResourcePool;
}
+
@Override
public Pinger createPinger(Node node, ClusterMonitor<Node> monitor, PongHandler pongHandler) {
return new RpcPing(node, monitor, rpcResourcePool, pongHandler);
}
+
}
diff --git a/container-search/src/main/java/com/yahoo/search/dispatch/rpc/RpcProtobufFillInvoker.java b/container-search/src/main/java/com/yahoo/search/dispatch/rpc/RpcProtobufFillInvoker.java
index 341b9b2bce3..8a17be8102e 100644
--- a/container-search/src/main/java/com/yahoo/search/dispatch/rpc/RpcProtobufFillInvoker.java
+++ b/container-search/src/main/java/com/yahoo/search/dispatch/rpc/RpcProtobufFillInvoker.java
@@ -38,6 +38,7 @@ import java.util.logging.Logger;
* @author ollivir
*/
public class RpcProtobufFillInvoker extends FillInvoker {
+
private static final String RPC_METHOD = "vespa.searchprotocol.getDocsums";
private static final Logger log = Logger.getLogger(RpcProtobufFillInvoker.class.getName());
diff --git a/container-search/src/main/java/com/yahoo/search/dispatch/rpc/RpcResourcePool.java b/container-search/src/main/java/com/yahoo/search/dispatch/rpc/RpcResourcePool.java
index 065489ef9a0..c3d072b8db6 100644
--- a/container-search/src/main/java/com/yahoo/search/dispatch/rpc/RpcResourcePool.java
+++ b/container-search/src/main/java/com/yahoo/search/dispatch/rpc/RpcResourcePool.java
@@ -4,7 +4,6 @@ package com.yahoo.search.dispatch.rpc;
import com.google.common.collect.ImmutableMap;
import com.google.inject.Inject;
import com.yahoo.component.AbstractComponent;
-import com.yahoo.component.ComponentId;
import com.yahoo.compress.CompressionType;
import com.yahoo.compress.Compressor;
import com.yahoo.compress.Compressor.Compression;
@@ -27,6 +26,7 @@ import java.util.Random;
* @author ollivir
*/
public class RpcResourcePool extends AbstractComponent {
+
/** The compression method which will be used with rpc dispatch. "lz4" (default) and "none" is supported. */
public final static CompoundName dispatchCompression = new CompoundName("dispatch.compression");
@@ -35,17 +35,19 @@ public class RpcResourcePool extends AbstractComponent {
/** Connections to the search nodes this talks to, indexed by node id ("partid") */
private final ImmutableMap<Integer, NodeConnectionPool> nodeConnectionPools;
+ private final RpcClient client;
RpcResourcePool(Map<Integer, NodeConnection> nodeConnections) {
var builder = new ImmutableMap.Builder<Integer, NodeConnectionPool>();
nodeConnections.forEach((key, connection) -> builder.put(key, new NodeConnectionPool(Collections.singletonList(connection))));
this.nodeConnectionPools = builder.build();
+ client = null;
}
@Inject
public RpcResourcePool(DispatchConfig dispatchConfig) {
super();
- var client = new RpcClient(dispatchConfig.numJrtTransportThreads());
+ client = new RpcClient("dispatch-client", dispatchConfig.numJrtTransportThreads());
// Create rpc node connection pools indexed by the node distribution key
var builder = new ImmutableMap.Builder<Integer, NodeConnectionPool>();
@@ -82,6 +84,9 @@ public class RpcResourcePool extends AbstractComponent {
public void deconstruct() {
super.deconstruct();
nodeConnectionPools.values().forEach(NodeConnectionPool::release);
+ if (client != null) {
+ client.close();
+ }
}
private class NodeConnectionPool {
diff --git a/container-search/src/main/java/com/yahoo/search/dispatch/rpc/RpcSearchInvoker.java b/container-search/src/main/java/com/yahoo/search/dispatch/rpc/RpcSearchInvoker.java
index 76240e55c98..20b11efb470 100644
--- a/container-search/src/main/java/com/yahoo/search/dispatch/rpc/RpcSearchInvoker.java
+++ b/container-search/src/main/java/com/yahoo/search/dispatch/rpc/RpcSearchInvoker.java
@@ -45,21 +45,36 @@ public class RpcSearchInvoker extends SearchInvoker implements Client.ResponseRe
}
@Override
- protected void sendSearchRequest(Query query) {
+ protected Object sendSearchRequest(Query query, Object incomingContext) {
this.query = query;
Client.NodeConnection nodeConnection = resourcePool.getConnection(node.key());
if (nodeConnection == null) {
responses.add(Client.ResponseOrError.fromError("Could not send search to unknown node " + node.key()));
responseAvailable();
- return;
+ return incomingContext;
}
query.trace(false, 5, "Sending search request with jrt/protobuf to node with dist key ", node.key());
- var payload = ProtobufSerialization.serializeSearchRequest(query, Math.min(query.getHits(), maxHits), searcher.getServerId());
+ RpcContext context = getContext(incomingContext);
double timeoutSeconds = ((double) query.getTimeLeft() - 3.0) / 1000.0;
- Compressor.Compression compressionResult = resourcePool.compress(query, payload);
- nodeConnection.request(RPC_METHOD, compressionResult.type(), payload.length, compressionResult.data(), this, timeoutSeconds);
+ nodeConnection.request(RPC_METHOD,
+ context.compressedPayload.type(),
+ context.compressedPayload.uncompressedSize(),
+ context.compressedPayload.data(),
+ this,
+ timeoutSeconds);
+ return context;
+ }
+
+ private RpcContext getContext(Object incomingContext) {
+ if (incomingContext instanceof RpcContext)
+ return (RpcContext)incomingContext;
+
+ return new RpcContext(resourcePool, query,
+ ProtobufSerialization.serializeSearchRequest(query,
+ Math.min(query.getHits(), maxHits),
+ searcher.getServerId()));
}
@Override
@@ -87,9 +102,7 @@ public class RpcSearchInvoker extends SearchInvoker implements Client.ResponseRe
ProtobufResponse protobufResponse = response.response().get();
CompressionType compression = CompressionType.valueOf(protobufResponse.compression());
byte[] payload = resourcePool.compressor().decompress(protobufResponse.compressedPayload(), compression, protobufResponse.uncompressedSize());
- var result = ProtobufSerialization.deserializeToSearchResult(payload, query, searcher, node.pathIndex(), node.key());
-
- return result;
+ return ProtobufSerialization.deserializeToSearchResult(payload, query, searcher, node.pathIndex(), node.key());
}
@Override
@@ -106,4 +119,14 @@ public class RpcSearchInvoker extends SearchInvoker implements Client.ResponseRe
return searcher.getName();
}
+ static class RpcContext {
+
+ final Compressor.Compression compressedPayload;
+
+ RpcContext(RpcResourcePool resourcePool, Query query, byte[] payload) {
+ compressedPayload = resourcePool.compress(query, payload);
+ }
+
+ }
+
}
diff --git a/container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/Group.java b/container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/Group.java
index 0e4e87b9a6a..727fb64faef 100644
--- a/container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/Group.java
+++ b/container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/Group.java
@@ -6,6 +6,7 @@ import com.google.common.collect.ImmutableList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
+import java.util.logging.Logger;
/**
* A group in a search cluster. This class is multithread safe.
@@ -15,12 +16,17 @@ import java.util.concurrent.atomic.AtomicLong;
*/
public class Group {
+ private static final Logger log = Logger.getLogger(Group.class.getName());
+ private final static double maxContentSkew = 0.10; // If documents on a node is more than 10% off from the average the group is unbalanced
+ private final static int minDocsPerNodeToRequireLowSkew = 100;
+
private final int id;
private final ImmutableList<Node> nodes;
-
private final AtomicBoolean hasSufficientCoverage = new AtomicBoolean(true);
private final AtomicBoolean hasFullCoverage = new AtomicBoolean(true);
private final AtomicLong activeDocuments = new AtomicLong(0);
+ private final AtomicBoolean isBlockingWrites = new AtomicBoolean(false);
+ private final AtomicBoolean isBalanced = new AtomicBoolean(true);
public Group(int id, List<Node> nodes) {
this.id = id;
@@ -52,38 +58,52 @@ public class Group {
}
public int workingNodes() {
- int nodesUp = 0;
- for (Node node : nodes) {
- if (node.isWorking() == Boolean.TRUE) {
- nodesUp++;
- }
- }
- return nodesUp;
+ return (int) nodes.stream().filter(node -> node.isWorking() == Boolean.TRUE).count();
}
- void aggregateActiveDocuments() {
- long activeDocumentsInGroup = 0;
- for (Node node : nodes) {
- if (node.isWorking() == Boolean.TRUE) {
- activeDocumentsInGroup += node.getActiveDocuments();
+ public void aggregateNodeValues() {
+ long activeDocs = nodes.stream().filter(node -> node.isWorking() == Boolean.TRUE).mapToLong(Node::getActiveDocuments).sum();
+ activeDocuments.set(activeDocs);
+ isBlockingWrites.set(nodes.stream().anyMatch(Node::isBlockingWrites));
+ int numWorkingNodes = workingNodes();
+ if (numWorkingNodes > 0) {
+ long average = activeDocs / numWorkingNodes;
+ long skew = nodes.stream().filter(node -> node.isWorking() == Boolean.TRUE).mapToLong(node -> Math.abs(node.getActiveDocuments() - average)).sum();
+ boolean balanced = skew <= activeDocs * maxContentSkew;
+ if (!isBalanced.get() || balanced != isBalanced.get()) {
+ if (!isSparse())
+ log.info("Content is " + (balanced ? "" : "not ") + "well balanced. Current deviation = " +
+ skew * 100 / activeDocs + " %. activeDocs = " + activeDocs + ", skew = " + skew +
+ ", average = " + average);
+ isBalanced.set(balanced);
}
+ } else {
+ isBalanced.set(true);
}
- activeDocuments.set(activeDocumentsInGroup);
-
}
- /** Returns the active documents on this node. If unknown, 0 is returned. */
- long getActiveDocuments() {
- return this.activeDocuments.get();
+ /** Returns the active documents on this group. If unknown, 0 is returned. */
+ long activeDocuments() { return activeDocuments.get(); }
+
+ /** Returns whether any node in this group is currently blocking write operations */
+ public boolean isBlockingWrites() { return isBlockingWrites.get(); }
+
+ /** Returns whether the nodes in the group have about the same number of documents */
+ public boolean isBalanced() { return isBalanced.get(); }
+
+ /** Returns whether this group has too few documents per node to expect it to be balanced */
+ public boolean isSparse() {
+ if (nodes.isEmpty()) return false;
+ return activeDocuments.get() / nodes.size() < minDocsPerNodeToRequireLowSkew;
}
- public boolean isFullCoverageStatusChanged(boolean hasFullCoverageNow) {
+ public boolean fullCoverageStatusChanged(boolean hasFullCoverageNow) {
boolean previousState = hasFullCoverage.getAndSet(hasFullCoverageNow);
return previousState != hasFullCoverageNow;
}
@Override
- public String toString() { return "search group " + id; }
+ public String toString() { return "group " + id; }
@Override
public int hashCode() { return id; }
diff --git a/container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/Node.java b/container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/Node.java
index e93b633f09d..9807a978647 100644
--- a/container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/Node.java
+++ b/container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/Node.java
@@ -23,6 +23,7 @@ public class Node {
private final AtomicLong activeDocuments = new AtomicLong(0);
private final AtomicLong pingSequence = new AtomicLong(0);
private final AtomicLong lastPong = new AtomicLong(0);
+ private final AtomicBoolean isBlockingWrites = new AtomicBoolean(false);
public Node(int key, String hostname, int group) {
this.key = key;
@@ -70,14 +71,14 @@ public class Node {
}
/** Updates the active documents on this node */
- void setActiveDocuments(long activeDocuments) {
- this.activeDocuments.set(activeDocuments);
- }
+ public void setActiveDocuments(long activeDocuments) { this.activeDocuments.set(activeDocuments); }
/** Returns the active documents on this node. If unknown, 0 is returned. */
- long getActiveDocuments() {
- return activeDocuments.get();
- }
+ long getActiveDocuments() { return activeDocuments.get(); }
+
+ public void setBlockingWrites(boolean isBlockingWrites) { this.isBlockingWrites.set(isBlockingWrites); }
+
+ boolean isBlockingWrites() { return isBlockingWrites.get(); }
@Override
public int hashCode() { return Objects.hash(hostname, key, pathIndex, group); }
diff --git a/container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/PingFactory.java b/container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/PingFactory.java
index 2e07d8d61e6..3b9e9573367 100644
--- a/container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/PingFactory.java
+++ b/container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/PingFactory.java
@@ -3,7 +3,9 @@ package com.yahoo.search.dispatch.searchcluster;
import com.yahoo.search.cluster.ClusterMonitor;
-
+/**
+ * @author ollivir
+ */
public interface PingFactory {
Pinger createPinger(Node node, ClusterMonitor<Node> monitor, PongHandler pongHandler);
diff --git a/container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/Pinger.java b/container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/Pinger.java
index b4a7ccbf98c..681a7d0af2c 100644
--- a/container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/Pinger.java
+++ b/container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/Pinger.java
@@ -8,5 +8,7 @@ package com.yahoo.search.dispatch.searchcluster;
* @author baldersheim
*/
public interface Pinger {
+
void ping();
+
}
diff --git a/container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/PongHandler.java b/container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/PongHandler.java
index c0579b5d36e..c39426e9d76 100644
--- a/container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/PongHandler.java
+++ b/container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/PongHandler.java
@@ -1,3 +1,4 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.search.dispatch.searchcluster;
import com.yahoo.prelude.Pong;
@@ -8,5 +9,7 @@ import com.yahoo.prelude.Pong;
* @author baldersheim
*/
public interface PongHandler {
+
void handle(Pong pong);
+
}
diff --git a/container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/SearchCluster.java b/container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/SearchCluster.java
index 06e3934335a..b5fbede4701 100644
--- a/container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/SearchCluster.java
+++ b/container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/SearchCluster.java
@@ -5,18 +5,19 @@ import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
+import com.google.common.math.Quantiles;
import com.yahoo.container.handler.VipStatus;
import com.yahoo.net.HostName;
import com.yahoo.prelude.Pong;
import com.yahoo.search.cluster.ClusterMonitor;
import com.yahoo.search.cluster.NodeManager;
+import com.yahoo.search.dispatch.TopKEstimator;
import com.yahoo.vespa.config.search.DispatchConfig;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
-import java.util.OptionalInt;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import java.util.stream.Collectors;
@@ -38,7 +39,9 @@ public class SearchCluster implements NodeManager<Node> {
private final ImmutableList<Group> orderedGroups;
private final VipStatus vipStatus;
private final PingFactory pingFactory;
+ private final TopKEstimator hitEstimator;
private long nextLogTime = 0;
+ private static final double SKEW_FACTOR = 0.05;
/**
* A search node on this local machine having the entire corpus, which we therefore
@@ -76,12 +79,13 @@ public class SearchCluster implements NodeManager<Node> {
for (Node node : nodes)
nodesByHostBuilder.put(node.hostname(), node);
this.nodesByHost = nodesByHostBuilder.build();
+ hitEstimator = new TopKEstimator(30.0, dispatchConfig.topKProbability(), SKEW_FACTOR);
this.localCorpusDispatchTarget = findLocalCorpusDispatchTarget(HostName.getLocalhost(), nodesByHost, groups);
}
- public void addMonitoring(ClusterMonitor clusterMonitor) {
- for (var group : orderedGroups) {
+ public void addMonitoring(ClusterMonitor<Node> clusterMonitor) {
+ for (var group : orderedGroups()) {
for (var node : group.nodes())
clusterMonitor.add(node, true);
}
@@ -129,22 +133,25 @@ public class SearchCluster implements NodeManager<Node> {
/** Returns the n'th (zero-indexed) group in the cluster if possible */
public Optional<Group> group(int n) {
- if (orderedGroups.size() > n) {
- return Optional.of(orderedGroups.get(n));
+ if (orderedGroups().size() > n) {
+ return Optional.of(orderedGroups().get(n));
} else {
return Optional.empty();
}
}
- /** Returns the number of nodes per group - size()/groups.size() */
- public int groupSize() {
- if (groups.size() == 0) return size();
- return size() / groups.size();
+ /**
+ * Returns the wanted number of nodes per group - size()/groups.size().
+ * The actual node count for a given group may differ due to node retirements.
+ */
+ public int wantedGroupSize() {
+ if (groups().size() == 0) return size();
+ return size() / groups().size();
}
public int groupsWithSufficientCoverage() {
int covered = 0;
- for (Group g : orderedGroups) {
+ for (Group g : orderedGroups()) {
if (g.hasSufficientCoverage()) {
covered++;
}
@@ -160,7 +167,7 @@ public class SearchCluster implements NodeManager<Node> {
if ( localCorpusDispatchTarget.isEmpty()) return Optional.empty();
// Only use direct dispatch if the local group has sufficient coverage
- Group localSearchGroup = groups.get(localCorpusDispatchTarget.get().group());
+ Group localSearchGroup = groups().get(localCorpusDispatchTarget.get().group());
if ( ! localSearchGroup.hasSufficientCoverage()) return Optional.empty();
// Only use direct dispatch if the local search node is not down
@@ -199,7 +206,10 @@ public class SearchCluster implements NodeManager<Node> {
setInRotationOnlyIf(hasWorkingNodes());
}
else if (usesLocalCorpusIn(node)) { // follow the status of this node
- setInRotationOnlyIf(nodeIsWorking);
+ // Do not take this out of rotation if we're a combined cluster of size 1,
+ // as that can't be helpful, and leads to a deadlock where this node is never taken back in servic e
+ if (nodeIsWorking || size() > 1)
+ setInRotationOnlyIf(nodeIsWorking);
}
}
@@ -219,6 +229,13 @@ public class SearchCluster implements NodeManager<Node> {
vipStatus.removeFromRotation(clusterId);
}
+ public int estimateHitsToFetch(int wantedHits, int numPartitions) {
+ return hitEstimator.estimateK(wantedHits, numPartitions);
+ }
+ public int estimateHitsToFetch(int wantedHits, int numPartitions, double topKProbability) {
+ return hitEstimator.estimateK(wantedHits, numPartitions, topKProbability);
+ }
+
public boolean hasInformationAboutAllNodes() {
return nodesByHost.values().stream().allMatch(node -> node.isWorking() != null);
}
@@ -236,12 +253,15 @@ public class SearchCluster implements NodeManager<Node> {
}
private static class PongCallback implements PongHandler {
+
private final ClusterMonitor<Node> clusterMonitor;
private final Node node;
+
PongCallback(Node node, ClusterMonitor<Node> clusterMonitor) {
this.node = node;
this.clusterMonitor = clusterMonitor;
}
+
@Override
public void handle(Pong pong) {
if (pong.badResponse()) {
@@ -249,10 +269,12 @@ public class SearchCluster implements NodeManager<Node> {
} else {
if (pong.activeDocuments().isPresent()) {
node.setActiveDocuments(pong.activeDocuments().get());
+ node.setBlockingWrites(pong.isBlockingWrites());
}
clusterMonitor.responded(node);
}
}
+
}
/** Used by the cluster monitor to manage node status */
@@ -263,41 +285,35 @@ public class SearchCluster implements NodeManager<Node> {
}
private void pingIterationCompletedSingleGroup() {
- Group group = groups.values().iterator().next();
- group.aggregateActiveDocuments();
+ Group group = groups().values().iterator().next();
+ group.aggregateNodeValues();
// With just one group sufficient coverage may not be the same as full coverage, as the
// group will always be marked sufficient for use.
updateSufficientCoverage(group, true);
- boolean fullCoverage = isGroupCoverageSufficient(group.workingNodes(), group.nodes().size(), group.getActiveDocuments(),
- group.getActiveDocuments());
- trackGroupCoverageChanges(0, group, fullCoverage, group.getActiveDocuments());
+ boolean sufficientCoverage = isGroupCoverageSufficient(group.activeDocuments(),
+ group.activeDocuments());
+ trackGroupCoverageChanges(group, sufficientCoverage, group.activeDocuments());
}
private void pingIterationCompletedMultipleGroups() {
- int numGroups = orderedGroups.size();
- // Update active documents per group and use it to decide if the group should be active
-
- long[] activeDocumentsInGroup = new long[numGroups];
- long sumOfActiveDocuments = 0;
- for(int i = 0; i < numGroups; i++) {
- Group group = orderedGroups.get(i);
- group.aggregateActiveDocuments();
- activeDocumentsInGroup[i] = group.getActiveDocuments();
- sumOfActiveDocuments += activeDocumentsInGroup[i];
- }
-
+ orderedGroups().forEach(Group::aggregateNodeValues);
+ long medianDocuments = medianDocumentsPerGroup();
boolean anyGroupsSufficientCoverage = false;
- for (int i = 0; i < numGroups; i++) {
- Group group = orderedGroups.get(i);
- long activeDocuments = activeDocumentsInGroup[i];
- long averageDocumentsInOtherGroups = (sumOfActiveDocuments - activeDocuments) / (numGroups - 1);
- boolean sufficientCoverage = isGroupCoverageSufficient(group.workingNodes(), group.nodes().size(), activeDocuments, averageDocumentsInOtherGroups);
+ for (Group group : orderedGroups()) {
+ boolean sufficientCoverage = isGroupCoverageSufficient(group.activeDocuments(),
+ medianDocuments);
anyGroupsSufficientCoverage = anyGroupsSufficientCoverage || sufficientCoverage;
updateSufficientCoverage(group, sufficientCoverage);
- trackGroupCoverageChanges(i, group, sufficientCoverage, averageDocumentsInOtherGroups);
+ trackGroupCoverageChanges(group, sufficientCoverage, medianDocuments);
}
}
+ private long medianDocumentsPerGroup() {
+ if (orderedGroups().isEmpty()) return 0;
+ var activeDocuments = orderedGroups().stream().map(Group::activeDocuments).collect(Collectors.toList());
+ return (long)Quantiles.median().compute(activeDocuments);
+ }
+
/**
* Update statistics after a round of issuing pings.
* Note that this doesn't wait for pings to return, so it will typically accumulate data from
@@ -305,7 +321,7 @@ public class SearchCluster implements NodeManager<Node> {
*/
@Override
public void pingIterationCompleted() {
- int numGroups = orderedGroups.size();
+ int numGroups = orderedGroups().size();
if (numGroups == 1) {
pingIterationCompletedSingleGroup();
} else {
@@ -313,77 +329,42 @@ public class SearchCluster implements NodeManager<Node> {
}
}
- private boolean isGroupCoverageSufficient(int workingNodes, int nodesInGroup, long activeDocuments, long averageDocumentsInOtherGroups) {
- boolean sufficientCoverage = true;
-
- if (averageDocumentsInOtherGroups > 0) {
- double coverage = 100.0 * (double) activeDocuments / averageDocumentsInOtherGroups;
- sufficientCoverage = coverage >= dispatchConfig.minActivedocsPercentage();
- }
- if (sufficientCoverage) {
- sufficientCoverage = isGroupNodeCoverageSufficient(workingNodes, nodesInGroup);
- }
- return sufficientCoverage;
- }
-
- private boolean isGroupNodeCoverageSufficient(int workingNodes, int nodesInGroup) {
- int nodesAllowedDown = dispatchConfig.maxNodesDownPerGroup()
- + (int) (((double) nodesInGroup * (100.0 - dispatchConfig.minGroupCoverage())) / 100.0);
- return workingNodes + nodesAllowedDown >= nodesInGroup;
+ private boolean isGroupCoverageSufficient(long activeDocuments, long medianDocuments) {
+ double documentCoverage = 100.0 * (double) activeDocuments / medianDocuments;
+ if (medianDocuments > 0 && documentCoverage < dispatchConfig.minActivedocsPercentage())
+ return false;
+ return true;
}
/**
* Calculate whether a subset of nodes in a group has enough coverage
*/
- public boolean isPartialGroupCoverageSufficient(OptionalInt knownGroupId, List<Node> nodes) {
- if (orderedGroups.size() == 1) {
- boolean sufficient = nodes.size() >= groupSize() - dispatchConfig.maxNodesDownPerGroup();
- return sufficient;
- }
-
- if (knownGroupId.isEmpty()) {
- return false;
- }
- int groupId = knownGroupId.getAsInt();
- Group group = groups.get(groupId);
- if (group == null) {
- return false;
- }
- int nodesInGroup = group.nodes().size();
- long sumOfActiveDocuments = 0;
- int otherGroups = 0;
- for (Group g : orderedGroups) {
- if (g.id() != groupId) {
- sumOfActiveDocuments += g.getActiveDocuments();
- otherGroups++;
- }
- }
- long activeDocuments = 0;
- for (Node n : nodes) {
- activeDocuments += n.getActiveDocuments();
- }
- long averageDocumentsInOtherGroups = sumOfActiveDocuments / otherGroups;
- return isGroupCoverageSufficient(nodes.size(), nodesInGroup, activeDocuments, averageDocumentsInOtherGroups);
+ public boolean isPartialGroupCoverageSufficient(List<Node> nodes) {
+ if (orderedGroups().size() == 1)
+ return true;
+ long activeDocuments = nodes.stream().mapToLong(Node::getActiveDocuments).sum();
+ return isGroupCoverageSufficient(activeDocuments, medianDocumentsPerGroup());
}
- private void trackGroupCoverageChanges(int index, Group group, boolean fullCoverage, long averageDocuments) {
+ private void trackGroupCoverageChanges(Group group, boolean fullCoverage, long medianDocuments) {
if ( ! hasInformationAboutAllNodes()) return; // Be silent until we know what we are talking about.
- boolean changed = group.isFullCoverageStatusChanged(fullCoverage);
+ boolean changed = group.fullCoverageStatusChanged(fullCoverage);
if (changed || (!fullCoverage && System.currentTimeMillis() > nextLogTime)) {
nextLogTime = System.currentTimeMillis() + 30 * 1000;
- int requiredNodes = groupSize() - dispatchConfig.maxNodesDownPerGroup();
if (fullCoverage) {
- log.info(() -> String.format("Group %d is now good again (%d/%d active docs, coverage %d/%d)",
- index, group.getActiveDocuments(), averageDocuments, group.workingNodes(), groupSize()));
+ log.info("Cluster " + clusterId + ": " + group + " has full coverage. " +
+ "Active documents: " + group.activeDocuments() + "/" + medianDocuments + ", " +
+ "working nodes: " + group.workingNodes() + "/" + group.nodes().size());
} else {
- StringBuilder missing = new StringBuilder();
+ StringBuilder unresponsive = new StringBuilder();
for (var node : group.nodes()) {
- if (node.isWorking() != Boolean.TRUE) {
- missing.append('\n').append(node.toString());
- }
+ if (node.isWorking() != Boolean.TRUE)
+ unresponsive.append('\n').append(node);
}
- log.warning(() -> String.format("Coverage of group %d is only %d/%d (requires %d) (%d/%d active docs) Failed nodes are:%s",
- index, group.workingNodes(), groupSize(), requiredNodes, group.getActiveDocuments(), averageDocuments, missing.toString()));
+ log.warning("Cluster " + clusterId + ": " + group + " has reduced coverage: " +
+ "Active documents: " + group.activeDocuments() + "/" + medianDocuments + ", " +
+ "working nodes: " + group.workingNodes() + "/" + group.nodes().size() +
+ ", unresponsive nodes: " + (unresponsive.toString().isEmpty() ? " none" : unresponsive));
}
}
}
diff --git a/container-search/src/main/java/com/yahoo/search/federation/FederationResult.java b/container-search/src/main/java/com/yahoo/search/federation/FederationResult.java
index 5f1cfccf549..6243dc694c2 100644
--- a/container-search/src/main/java/com/yahoo/search/federation/FederationResult.java
+++ b/container-search/src/main/java/com/yahoo/search/federation/FederationResult.java
@@ -39,8 +39,8 @@ class FederationResult {
}
/**
- * Wait on each target for that targets timeout
- * On the worst case this is the same as waiting for the max target timeout,
+ * Wait on each target for that targets timeout.
+ * In the worst case this is the same as waiting for the max target timeout,
* in the average case it may be much better because lower timeout sources do not get to
* drive the timeout above their own timeout value.
* When this completes, results can be accessed from the TargetResults with no blocking
diff --git a/container-search/src/main/java/com/yahoo/search/federation/FederationSearcher.java b/container-search/src/main/java/com/yahoo/search/federation/FederationSearcher.java
index d0b3189a79c..8d0e4944ab8 100644
--- a/container-search/src/main/java/com/yahoo/search/federation/FederationSearcher.java
+++ b/container-search/src/main/java/com/yahoo/search/federation/FederationSearcher.java
@@ -14,6 +14,7 @@ import com.yahoo.concurrent.CopyOnWriteHashMap;
import com.yahoo.errorhandling.Results;
import com.yahoo.errorhandling.Results.Builder;
import com.yahoo.prelude.IndexFacts;
+import com.yahoo.processing.IllegalInputException;
import com.yahoo.processing.request.CompoundName;
import com.yahoo.search.Query;
import com.yahoo.search.Result;
@@ -57,7 +58,6 @@ import java.util.logging.Logger;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.yahoo.collections.CollectionUtil.first;
-import static com.yahoo.container.util.Util.quote;
import static com.yahoo.search.federation.StrictContractsConfig.PropagateSourceProperties;
/**
@@ -78,7 +78,6 @@ public class FederationSearcher extends ForkingSearcher {
/** The name of the query property containing the source name added to the query to each source by this */
public final static CompoundName SOURCENAME = new CompoundName("sourceName");
public final static CompoundName PROVIDERNAME = new CompoundName("providerName");
-
/** Logging field name constants */
public static final String LOG_COUNT_PREFIX = "count_";
@@ -105,7 +104,7 @@ public class FederationSearcher extends ForkingSearcher {
ComponentRegistry<TargetSelector> targetSelectors) {
if (selectorId.isEmpty()) return null;
return checkNotNull(targetSelectors.getComponent(selectorId),
- "Missing target selector with id" + quote(selectorId));
+ "Missing target selector with id '" + selectorId + "'");
}
// for testing
@@ -139,7 +138,7 @@ public class FederationSearcher extends ForkingSearcher {
}
}
- //Allow source groups to use by default.
+ // Allow source groups to use by default.
if (target.useByDefault())
builder.useTargetByDefault(target.id());
}
@@ -304,7 +303,6 @@ public class FederationSearcher extends ForkingSearcher {
Object value = getSourceOrProviderProperty(original, key, sourceName, providerName, window.get(key));
if (value != null)
outgoing.properties().set(key, value);
- if (value != null) System.out.println("Setting " + key + " = " + value);
}
}
}
@@ -340,9 +338,8 @@ public class FederationSearcher extends ForkingSearcher {
private List<String> allSourceRefDescriptions() {
List<String> descriptions = new ArrayList<>();
- for (com.yahoo.search.federation.sourceref.Target target : searchChainResolver.allTopLevelTargets()) {
+ for (com.yahoo.search.federation.sourceref.Target target : searchChainResolver.allTopLevelTargets())
descriptions.add(target.searchRefDescription());
- }
return descriptions;
}
@@ -355,7 +352,7 @@ public class FederationSearcher extends ForkingSearcher {
}
private void warnIfUnresolvedSearchChains(List<UnresolvedSearchChainException> missingTargets,
- HitGroup errorHitGroup) {
+ HitGroup errorHitGroup) {
if (!missingTargets.isEmpty()) {
errorHitGroup.addError(missingSearchChainsErrorMessage(missingTargets));
}
@@ -493,9 +490,9 @@ public class FederationSearcher extends ForkingSearcher {
* TODO This is probably a dirty hack for bug 4711376. There are probably better ways.
* But I will leave that to trd-processing@
*
- * @param group The merging hitgroup to be updated if necessary
- * @param orderer The per provider hit orderer.
- * @return The hitorderer chosen
+ * @param group the merging hitgroup to be updated if necessary
+ * @param orderer the per provider hit orderer
+ * @return he hitorderer chosen
*/
private HitOrderer dirtyCopyIfModifiedOrderer(HitGroup group, HitOrderer orderer) {
if (orderer != null) {
@@ -546,8 +543,8 @@ public class FederationSearcher extends ForkingSearcher {
private ComponentSpecification asSourceSpec(String source) {
try {
return new ComponentSpecification(source);
- } catch(Exception e) {
- throw new IllegalArgumentException("The source ref '" + source + "' used for federation is not valid.", e);
+ } catch (Exception e) {
+ throw new IllegalInputException("The source ref '" + source + "' used for federation is not valid.", e);
}
}
diff --git a/container-search/src/main/java/com/yahoo/search/federation/sourceref/SearchChainInvocationSpec.java b/container-search/src/main/java/com/yahoo/search/federation/sourceref/SearchChainInvocationSpec.java
index 6cb8d2ef174..59b4e521a56 100644
--- a/container-search/src/main/java/com/yahoo/search/federation/sourceref/SearchChainInvocationSpec.java
+++ b/container-search/src/main/java/com/yahoo/search/federation/sourceref/SearchChainInvocationSpec.java
@@ -9,7 +9,7 @@ import java.util.List;
import java.util.Objects;
/**
- * Specifices which search chain should be run and how it should be run.
+ * Specifies which search chain should be run and how it should be run.
* This is a value object.
*
* @author Tony Vaagenes
diff --git a/container-search/src/main/java/com/yahoo/search/federation/sourceref/SearchChainResolver.java b/container-search/src/main/java/com/yahoo/search/federation/sourceref/SearchChainResolver.java
index 97ceee96dfc..6626c1b3cc4 100644
--- a/container-search/src/main/java/com/yahoo/search/federation/sourceref/SearchChainResolver.java
+++ b/container-search/src/main/java/com/yahoo/search/federation/sourceref/SearchChainResolver.java
@@ -46,14 +46,12 @@ public class SearchChainResolver {
public static class Builder {
- private SortedSet<Target> defaultTargets = new TreeSet<>();
+ private final SortedSet<Target> defaultTargets = new TreeSet<>();
- private final ComponentRegistry<Target> targets = new ComponentRegistry<Target>() {
+ private final ComponentRegistry<Target> targets = new ComponentRegistry<>() {
@Override
public void freeze() {
- for (Target target : allComponents()) {
- target.freeze();
- }
+ allComponents().forEach(Target::freeze);
super.freeze();
}
};
@@ -70,10 +68,16 @@ public class SearchChainResolver {
return addSearchChain(searchChainId, new FederationOptions(), documentTypes);
}
- public Builder addSearchChain(ComponentId searchChainId, FederationOptions federationOptions,
+ public Builder addSearchChain(ComponentId searchChainId,
+ FederationOptions federationOptions,
List<String> documentTypes) {
registerTarget(new SingleTarget(searchChainId,
- new SearchChainInvocationSpec(searchChainId, null, null, federationOptions, documentTypes), false));
+ new SearchChainInvocationSpec(searchChainId,
+ null,
+ null,
+ federationOptions,
+ documentTypes),
+ false));
return this;
}
@@ -86,9 +90,8 @@ public class SearchChainResolver {
}
public Builder addSourceForProvider(ComponentId sourceId, ComponentId providerId, ComponentId searchChainId,
- boolean isDefaultProviderForSource, FederationOptions federationOptions,
- List<String> documentTypes) {
-
+ boolean isDefaultProviderForSource, FederationOptions federationOptions,
+ List<String> documentTypes) {
SearchChainInvocationSpec searchChainInvocationSpec =
new SearchChainInvocationSpec(searchChainId, sourceId, providerId, federationOptions, documentTypes);
@@ -129,7 +132,6 @@ public class SearchChainResolver {
this.defaultTargets = Collections.unmodifiableSortedSet(defaultTargets);
}
-
public SearchChainInvocationSpec resolve(ComponentSpecification sourceRef, Properties sourceToProviderMap)
throws UnresolvedSearchChainException {
@@ -158,4 +160,5 @@ public class SearchChainResolver {
public SortedSet<Target> defaultTargets() {
return defaultTargets;
}
+
}
diff --git a/container-search/src/main/java/com/yahoo/search/federation/sourceref/SingleTarget.java b/container-search/src/main/java/com/yahoo/search/federation/sourceref/SingleTarget.java
index 9c7e1024518..4613c73c4b4 100644
--- a/container-search/src/main/java/com/yahoo/search/federation/sourceref/SingleTarget.java
+++ b/container-search/src/main/java/com/yahoo/search/federation/sourceref/SingleTarget.java
@@ -5,8 +5,6 @@ import com.yahoo.component.ComponentId;
import com.yahoo.processing.request.Properties;
/**
- * TODO: What is this?
- *
* @author Tony Vaagenes
*/
public class SingleTarget extends Target {
diff --git a/container-search/src/main/java/com/yahoo/search/federation/sourceref/SourceRefResolver.java b/container-search/src/main/java/com/yahoo/search/federation/sourceref/SourceRefResolver.java
index ee98d033440..516ee9f968f 100644
--- a/container-search/src/main/java/com/yahoo/search/federation/sourceref/SourceRefResolver.java
+++ b/container-search/src/main/java/com/yahoo/search/federation/sourceref/SourceRefResolver.java
@@ -1,17 +1,15 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.search.federation.sourceref;
-import static com.yahoo.container.util.Util.quote;
+import com.yahoo.component.ComponentSpecification;
+import com.yahoo.prelude.IndexFacts;
+import com.yahoo.processing.request.Properties;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
-import com.yahoo.component.ComponentSpecification;
-import com.yahoo.prelude.IndexFacts;
-import com.yahoo.processing.request.Properties;
-
/**
* Maps a source reference to search chain invocation specs.
*
@@ -24,13 +22,12 @@ public class SourceRefResolver {
public SourceRefResolver(SearchChainResolver searchChainResolver) {
this.searchChainResolver = searchChainResolver;
}
+
public Set<SearchChainInvocationSpec> resolve(ComponentSpecification sourceRef,
Properties sourceToProviderMap,
- IndexFacts indexFacts)
- throws UnresolvedSearchChainException {
-
+ IndexFacts indexFacts) throws UnresolvedSearchChainException {
try {
- return new LinkedHashSet<>(Arrays.asList(searchChainResolver.resolve(sourceRef, sourceToProviderMap)));
+ return new LinkedHashSet<>(List.of(searchChainResolver.resolve(sourceRef, sourceToProviderMap)));
} catch (UnresolvedSourceRefException e) {
return resolveClustersWithDocument(sourceRef, sourceToProviderMap, indexFacts);
}
@@ -65,9 +62,9 @@ public class SourceRefResolver {
return searchChainResolver.resolve(new ComponentSpecification(cluster), sourceToProviderMap);
}
catch (UnresolvedSearchChainException e) {
- throw new UnresolvedSearchChainException("Failed to resolve cluster search chain " + quote(cluster) +
- " when using source ref " + quote(sourceRef) +
- " as a document name.");
+ throw new UnresolvedSearchChainException("Failed to resolve cluster search chain '" + cluster +
+ "' when using source ref '" + sourceRef +
+ "' as a document name.");
}
}
diff --git a/container-search/src/main/java/com/yahoo/search/federation/sourceref/Target.java b/container-search/src/main/java/com/yahoo/search/federation/sourceref/Target.java
index f23e24525bb..1b11e588f11 100644
--- a/container-search/src/main/java/com/yahoo/search/federation/sourceref/Target.java
+++ b/container-search/src/main/java/com/yahoo/search/federation/sourceref/Target.java
@@ -6,8 +6,6 @@ import com.yahoo.component.ComponentId;
import com.yahoo.processing.request.Properties;
/**
- * TODO: What's this?
- *
* @author Tony Vaagenes
*/
public abstract class Target extends AbstractComponent {
diff --git a/container-search/src/main/java/com/yahoo/search/federation/sourceref/UnresolvedProviderException.java b/container-search/src/main/java/com/yahoo/search/federation/sourceref/UnresolvedProviderException.java
index 5075b05454b..2ae923714c7 100644
--- a/container-search/src/main/java/com/yahoo/search/federation/sourceref/UnresolvedProviderException.java
+++ b/container-search/src/main/java/com/yahoo/search/federation/sourceref/UnresolvedProviderException.java
@@ -4,8 +4,6 @@ package com.yahoo.search.federation.sourceref;
import com.yahoo.component.ComponentId;
import com.yahoo.component.ComponentSpecification;
-import static com.yahoo.container.util.Util.quote;
-
/**
* @author Tony Vaagenes
*/
@@ -17,6 +15,6 @@ class UnresolvedProviderException extends UnresolvedSearchChainException {
static UnresolvedSearchChainException createForMissingProvider(ComponentId source,
ComponentSpecification provider) {
- return new UnresolvedProviderException("No provider " + quote(provider) + " for source " + quote(source) + ".");
+ return new UnresolvedProviderException("No provider '" + provider + "' for source '" + source + "'.");
}
}
diff --git a/container-search/src/main/java/com/yahoo/search/federation/sourceref/UnresolvedSourceRefException.java b/container-search/src/main/java/com/yahoo/search/federation/sourceref/UnresolvedSourceRefException.java
index 233e92c1699..6f0549eaf93 100644
--- a/container-search/src/main/java/com/yahoo/search/federation/sourceref/UnresolvedSourceRefException.java
+++ b/container-search/src/main/java/com/yahoo/search/federation/sourceref/UnresolvedSourceRefException.java
@@ -3,8 +3,6 @@ package com.yahoo.search.federation.sourceref;
import com.yahoo.component.ComponentSpecification;
-import static com.yahoo.container.util.Util.quote;
-
/**
* @author Tony Vaagenes
*/
@@ -16,6 +14,6 @@ class UnresolvedSourceRefException extends UnresolvedSearchChainException {
static UnresolvedSearchChainException createForMissingSourceRef(ComponentSpecification source) {
- return new UnresolvedSourceRefException("Could not resolve source ref " + quote(source) + ".");
+ return new UnresolvedSourceRefException("Could not resolve source ref '" + source + "'.");
}
}
diff --git a/container-search/src/main/java/com/yahoo/search/grouping/Continuation.java b/container-search/src/main/java/com/yahoo/search/grouping/Continuation.java
index d7ee3fedfc9..b74101fb83d 100644
--- a/container-search/src/main/java/com/yahoo/search/grouping/Continuation.java
+++ b/container-search/src/main/java/com/yahoo/search/grouping/Continuation.java
@@ -8,7 +8,7 @@ import com.yahoo.search.grouping.vespa.ContinuationDecoder;
* subsequently be sent back along with the original request to navigate across a large result set. It is an opaque
* data object that is not intended to be human readable.</p>
*
- * <p>To render a Cookie within a result set, you simply need to call {@link #toString()}.</p>
+ * <p>To render a continuation within a result set, you simply need to call {@link #toString()}.</p>
*
* @author Simon Thoresen Hult
*/
@@ -18,8 +18,8 @@ public abstract class Continuation {
public static final String PREV_PAGE = "prev";
public static final String THIS_PAGE = "this";
- public static Continuation fromString(String str) {
- return ContinuationDecoder.decode(str);
+ public static Continuation fromString(String string) {
+ return ContinuationDecoder.decode(string);
}
/** Returns a deep copy of this */
diff --git a/container-search/src/main/java/com/yahoo/search/grouping/GroupingQueryParser.java b/container-search/src/main/java/com/yahoo/search/grouping/GroupingQueryParser.java
index 9924a05bb46..b9e0825ab03 100644
--- a/container-search/src/main/java/com/yahoo/search/grouping/GroupingQueryParser.java
+++ b/container-search/src/main/java/com/yahoo/search/grouping/GroupingQueryParser.java
@@ -12,8 +12,14 @@ import com.yahoo.search.grouping.request.GroupingOperation;
import com.yahoo.search.query.Select;
import com.yahoo.search.searchchain.Execution;
import com.yahoo.search.searchchain.PhaseNames;
+import com.yahoo.processing.IllegalInputException;
-import java.util.*;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.TimeZone;
/**
* This searcher is responsible for turning the "select" parameter into a corresponding {@link GroupingRequest}. It will
@@ -35,19 +41,23 @@ public class GroupingQueryParser extends Searcher {
@Override
public Result search(Query query, Execution execution) {
- String reqParam = query.properties().getString(PARAM_REQUEST);
- if (reqParam == null) {
+ try {
+ String reqParam = query.properties().getString(PARAM_REQUEST);
+ if (reqParam == null) return execution.search(query);
+
+ List<Continuation> continuations = getContinuations(query.properties().getString(PARAM_CONTINUE));
+ TimeZone zone = getTimeZone(query.properties().getString(PARAM_TIMEZONE, "utc"));
+ for (GroupingOperation op : GroupingOperation.fromStringAsList(reqParam)) {
+ GroupingRequest grpRequest = GroupingRequest.newInstance(query);
+ grpRequest.setRootOperation(op);
+ grpRequest.setTimeZone(zone);
+ grpRequest.continuations().addAll(continuations);
+ }
return execution.search(query);
}
- List<Continuation> continuations = getContinuations(query.properties().getString(PARAM_CONTINUE));
- TimeZone zone = getTimeZone(query.properties().getString(PARAM_TIMEZONE, "utc"));
- for (GroupingOperation op : GroupingOperation.fromStringAsList(reqParam)) {
- GroupingRequest grpRequest = GroupingRequest.newInstance(query);
- grpRequest.setRootOperation(op);
- grpRequest.setTimeZone(zone);
- grpRequest.continuations().addAll(continuations);
+ catch (IllegalArgumentException e) {
+ throw new IllegalInputException(e);
}
- return execution.search(query);
}
private List<Continuation> getContinuations(String param) {
diff --git a/container-search/src/main/java/com/yahoo/search/grouping/GroupingRequest.java b/container-search/src/main/java/com/yahoo/search/grouping/GroupingRequest.java
index 13c23234910..8e57434a049 100644
--- a/container-search/src/main/java/com/yahoo/search/grouping/GroupingRequest.java
+++ b/container-search/src/main/java/com/yahoo/search/grouping/GroupingRequest.java
@@ -2,7 +2,6 @@
package com.yahoo.search.grouping;
import com.yahoo.net.URI;
-import com.yahoo.processing.request.CompoundName;
import com.yahoo.search.Query;
import com.yahoo.search.Result;
import com.yahoo.search.grouping.request.GroupingOperation;
@@ -12,7 +11,9 @@ import com.yahoo.search.grouping.result.RootId;
import com.yahoo.search.query.Select;
import com.yahoo.search.result.Hit;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.TimeZone;
/**
* An instance of this class represents one of many grouping requests that are attached to a {@link Query}. Use the
diff --git a/container-search/src/main/java/com/yahoo/search/grouping/GroupingValidator.java b/container-search/src/main/java/com/yahoo/search/grouping/GroupingValidator.java
index 06b030dbc78..cd8578cd728 100644
--- a/container-search/src/main/java/com/yahoo/search/grouping/GroupingValidator.java
+++ b/container-search/src/main/java/com/yahoo/search/grouping/GroupingValidator.java
@@ -5,6 +5,7 @@ import com.google.inject.Inject;
import com.yahoo.component.chain.dependencies.After;
import com.yahoo.component.chain.dependencies.Before;
import com.yahoo.component.chain.dependencies.Provides;
+import com.yahoo.processing.IllegalInputException;
import com.yahoo.search.grouping.request.AttributeMapLookupValue;
import com.yahoo.vespa.config.search.AttributesConfig;
import com.yahoo.container.QrSearchersConfig;
@@ -80,14 +81,14 @@ public class GroupingValidator extends Searcher {
AttributesConfig.Attribute keyAttribute = attributes.get(keyAttributeName);
AttributesConfig.Attribute keySourceAttribute = attributes.get(keySourceAttributeName);
if (!keySourceAttribute.datatype().equals(keyAttribute.datatype())) {
- throw new IllegalArgumentException("Grouping request references key source attribute '" +
- keySourceAttributeName + "' with data type '" + keySourceAttribute.datatype() +
- "' that is different than data type '" + keyAttribute.datatype() + "' of key attribute '" +
- keyAttributeName + "'");
+ throw new IllegalInputException("Grouping request references key source attribute '" +
+ keySourceAttributeName + "' with data type '" + keySourceAttribute.datatype() +
+ "' that is different than data type '" + keyAttribute.datatype() + "' of key attribute '" +
+ keyAttributeName + "'");
}
if (!keySourceAttribute.collectiontype().equals(AttributesConfig.Attribute.Collectiontype.Enum.SINGLE)) {
- throw new IllegalArgumentException("Grouping request references key source attribute '" +
- keySourceAttributeName + "' which is not of single value type");
+ throw new IllegalInputException("Grouping request references key source attribute '" +
+ keySourceAttributeName + "' which is not of single value type");
}
}
diff --git a/container-search/src/main/java/com/yahoo/search/grouping/UniqueGroupingSearcher.java b/container-search/src/main/java/com/yahoo/search/grouping/UniqueGroupingSearcher.java
index 8280917ca2f..3abfa3a6531 100644
--- a/container-search/src/main/java/com/yahoo/search/grouping/UniqueGroupingSearcher.java
+++ b/container-search/src/main/java/com/yahoo/search/grouping/UniqueGroupingSearcher.java
@@ -3,7 +3,7 @@ package com.yahoo.search.grouping;
import com.yahoo.component.chain.dependencies.After;
import com.yahoo.component.chain.dependencies.Before;
-import com.yahoo.log.LogLevel;
+import java.util.logging.Level;
import com.yahoo.processing.request.CompoundName;
import com.yahoo.search.Query;
import com.yahoo.search.Result;
@@ -110,7 +110,7 @@ public class UniqueGroupingSearcher extends Searcher {
if (null == root) {
String msg = "Result group not found for deduping grouping request, returning empty result.";
query.trace(msg, 3);
- log.log(LogLevel.WARNING, msg);
+ log.log(Level.WARNING, msg);
throw new IllegalStateException("Failed to produce deduped result set.");
}
result.hits().remove(root.getId().toString()); // hide our tracks
diff --git a/container-search/src/main/java/com/yahoo/search/grouping/request/AddFunction.java b/container-search/src/main/java/com/yahoo/search/grouping/request/AddFunction.java
index 420861d2f6c..60805aacd5f 100644
--- a/container-search/src/main/java/com/yahoo/search/grouping/request/AddFunction.java
+++ b/container-search/src/main/java/com/yahoo/search/grouping/request/AddFunction.java
@@ -38,9 +38,9 @@ public class AddFunction extends FunctionNode {
/**
* Constructs a new instance of this class from a list of arguments.
*
- * @param args The arguments to pass to the constructor.
- * @return The created instance.
- * @throws IllegalArgumentException Thrown if the number of arguments is less than 2.
+ * @param args the arguments to pass to the constructor.
+ * @return the created instance.
+ * @throws IllegalArgumentException thrown if the number of arguments is less than 2.
*/
public static AddFunction newInstance(List<GroupingExpression> args) {
if (args.size() < 2) {
diff --git a/container-search/src/main/java/com/yahoo/search/grouping/request/BucketResolver.java b/container-search/src/main/java/com/yahoo/search/grouping/request/BucketResolver.java
index c36c8af5c34..6c6b20973ab 100644
--- a/container-search/src/main/java/com/yahoo/search/grouping/request/BucketResolver.java
+++ b/container-search/src/main/java/com/yahoo/search/grouping/request/BucketResolver.java
@@ -24,9 +24,9 @@ public class BucketResolver {
* Pushes the given expression onto this bucket resolver. Once all buckets have been pushed using this method, call
* {@link #resolve(GroupingExpression)} to retrieve to combined grouping expression.
*
- * @param val The expression to push.
- * @param inclusive Whether or not the value is inclusive or not.
- * @throws IllegalArgumentException Thrown if the expression is incompatible.
+ * @param val the expression to push
+ * @param inclusive whether or not the value is inclusive or not
+ * @throws IllegalArgumentException thrown if the expression is incompatible
*/
public BucketResolver push(ConstantValue<?> val, boolean inclusive) {
if (prev == null) {
diff --git a/container-search/src/main/java/com/yahoo/search/grouping/request/GroupingOperation.java b/container-search/src/main/java/com/yahoo/search/grouping/request/GroupingOperation.java
index c825f3c61de..499ed610d34 100644
--- a/container-search/src/main/java/com/yahoo/search/grouping/request/GroupingOperation.java
+++ b/container-search/src/main/java/com/yahoo/search/grouping/request/GroupingOperation.java
@@ -8,7 +8,12 @@ import com.yahoo.search.grouping.request.parser.GroupingParserInput;
import com.yahoo.search.grouping.request.parser.ParseException;
import com.yahoo.search.grouping.request.parser.TokenMgrException;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
/**
* This class represents a single node in a grouping operation tree. You may manually construct this tree, or you may
@@ -228,8 +233,8 @@ public abstract class GroupingOperation extends GroupingNode {
* method verifies the input level against the operation type, and recursively resolves the level of all argument
* expressions.
*
- * @param level The level of the input data.
- * @throws IllegalArgumentException Thrown if a contained expression is invalid for the given level.
+ * @param level the level of the input data
+ * @throws IllegalArgumentException thrown if a contained expression is invalid for the given level
*/
public void resolveLevel(int level) {
if (groupBy != null) {
@@ -322,12 +327,7 @@ public abstract class GroupingOperation extends GroupingNode {
return this;
}
- /**
- * Return the accuracy of this.
- *
- * @return The accuracy value.
- * @see #setAccuracy(double)
- */
+ /** Return the accuracy of this. */
public double getAccuracy() {
return accuracy;
}
@@ -335,8 +335,8 @@ public abstract class GroupingOperation extends GroupingNode {
/**
* Adds an expression to the order-by clause of this operation.
*
- * @param exp The expressions to add to this.
- * @return This, to allow chaining.
+ * @param exp the expressions to add to this
+ * @return this, to allow chaining
*/
public GroupingOperation addOrderBy(GroupingExpression exp) {
orderBy.add(exp);
@@ -346,11 +346,11 @@ public abstract class GroupingOperation extends GroupingNode {
/**
* Convenience method to call {@link #addOrderBy(GroupingExpression)} for each element in the given list.
*
- * @param lst The list of expressions to add.
- * @return This, to allow chaining.
+ * @param list the list of expressions to add
+ * @return this, to allow chaining
*/
- public GroupingOperation addOrderBy(List<GroupingExpression> lst) {
- for (GroupingExpression exp : lst) {
+ public GroupingOperation addOrderBy(List<GroupingExpression> list) {
+ for (GroupingExpression exp : list) {
addOrderBy(exp);
}
return this;
@@ -359,7 +359,7 @@ public abstract class GroupingOperation extends GroupingNode {
/**
* Returns the number of expressions in the order-by clause of this.
*
- * @return The expression count.
+ * @return the expression count
*/
public int getNumOrderBy() {
return orderBy.size();
@@ -368,9 +368,9 @@ public abstract class GroupingOperation extends GroupingNode {
/**
* Returns the group-by expression at the given index.
*
- * @param i The index of the expression to return.
- * @return The expression at the given index.
- * @throws IndexOutOfBoundsException If the index is out of range.
+ * @param i the index of the expression to return
+ * @return the expression at the given index
+ * @throws IndexOutOfBoundsException if the index is out of range
*/
public GroupingExpression getOrderBy(int i) {
return orderBy.get(i);
@@ -379,7 +379,7 @@ public abstract class GroupingOperation extends GroupingNode {
/**
* Returns an immutable view to the order-by clause of this.
*
- * @return The expression list.
+ * @return the expression list
*/
public List<GroupingExpression> getOrderBy() {
return Collections.unmodifiableList(orderBy);
@@ -388,8 +388,8 @@ public abstract class GroupingOperation extends GroupingNode {
/**
* Adds an expression to the output clause of this operation.
*
- * @param exp The expressions to add to this.
- * @return This, to allow chaining.
+ * @param exp the expressions to add to this
+ * @return this, to allow chaining
*/
public GroupingOperation addOutput(GroupingExpression exp) {
outputs.add(exp);
@@ -399,8 +399,8 @@ public abstract class GroupingOperation extends GroupingNode {
/**
* Convenience method to call {@link #addOutput(GroupingExpression)} for each element in the given list.
*
- * @param lst The list of expressions to add.
- * @return This, to allow chaining.
+ * @param lst the list of expressions to add
+ * @return this, to allow chaining
*/
public GroupingOperation addOutputs(List<GroupingExpression> lst) {
for (GroupingExpression exp : lst) {
@@ -412,7 +412,7 @@ public abstract class GroupingOperation extends GroupingNode {
/**
* Returns the number of expressions in the output clause of this.
*
- * @return The expression count.
+ * @return the expression count
*/
public int getNumOutputs() {
return outputs.size();
@@ -421,9 +421,9 @@ public abstract class GroupingOperation extends GroupingNode {
/**
* Returns the output expression at the given index.
*
- * @param i The index of the expression to return.
- * @return The expression at the given index.
- * @throws IndexOutOfBoundsException If the index is out of range.
+ * @param i the index of the expression to return
+ * @return the expression at the given index
+ * @throws IndexOutOfBoundsException If the index is out of range
*/
public GroupingExpression getOutput(int i) {
return outputs.get(i);
@@ -432,7 +432,7 @@ public abstract class GroupingOperation extends GroupingNode {
/**
* Returns an immutable view to the output clause of this.
*
- * @return The expression list.
+ * @return the expression list
*/
public List<GroupingExpression> getOutputs() {
return Collections.unmodifiableList(outputs);
@@ -443,8 +443,8 @@ public abstract class GroupingOperation extends GroupingNode {
* during expression evaluation to give the dispatch-node more data to consider when selecting the N groups that are
* to be evaluated further.
*
- * @param precision The precision to set.
- * @return This, to allow chaining.
+ * @param precision the precision to set
+ * @return this, to allow chaining
* @see #setMax(int)
*/
public GroupingOperation setPrecision(int precision) {
@@ -452,11 +452,7 @@ public abstract class GroupingOperation extends GroupingNode {
return this;
}
- /**
- * Returns the precision clause of this.
- *
- * @return The precision.
- */
+ /** Returns the precision clause of this. */
public int getPrecision() {
return precision;
}
@@ -464,11 +460,11 @@ public abstract class GroupingOperation extends GroupingNode {
/**
* Assigns a string as the where clause of this operation.
*
- * @param str The string to assign to this.
- * @return This, to allow chaining.
+ * @param string the string to assign to this
+ * @return this, to allow chaining
*/
- public GroupingOperation setWhere(String str) {
- where = str;
+ public GroupingOperation setWhere(String string) {
+ where = string;
return this;
}
@@ -590,9 +586,9 @@ public abstract class GroupingOperation extends GroupingNode {
* Convenience method to call {@link #fromStringAsList(String)} and assert that the list contains exactly one
* grouping operation.
*
- * @param str The string to parse.
- * @return A grouping operation that corresponds to the string.
- * @throws IllegalArgumentException Thrown if the string could not be parsed as a single operation.
+ * @param str the string to parse
+ * @return a grouping operation that corresponds to the string
+ * @throws IllegalArgumentException thrown if the string could not be parsed as a single operation
*/
public static GroupingOperation fromString(String str) {
List<GroupingOperation> lst = fromStringAsList(str);
@@ -606,15 +602,15 @@ public abstract class GroupingOperation extends GroupingNode {
* Parses the given string as a list of grouping operations. This method never returns null, it either returns a
* list of valid grouping requests or it throws an exception.
*
- * @param str The string to parse.
- * @return A list of grouping operations that corresponds to the string.
- * @throws IllegalArgumentException Thrown if the string could not be parsed.
+ * @param string the string to parse
+ * @return a list of grouping operations that corresponds to the string
+ * @throws IllegalArgumentException thrown if the string could not be parsed
*/
- public static List<GroupingOperation> fromStringAsList(String str) {
- if (str == null || str.trim().length() == 0) {
+ public static List<GroupingOperation> fromStringAsList(String string) {
+ if (string == null || string.trim().length() == 0) {
return Collections.emptyList();
}
- GroupingParserInput input = new GroupingParserInput(str);
+ GroupingParserInput input = new GroupingParserInput(string);
try {
return new GroupingParser(input).requestList();
} catch (ParseException | TokenMgrException e) {
diff --git a/container-search/src/main/java/com/yahoo/search/grouping/request/MinFunction.java b/container-search/src/main/java/com/yahoo/search/grouping/request/MinFunction.java
index 40fbda5a98a..5a39612b200 100644
--- a/container-search/src/main/java/com/yahoo/search/grouping/request/MinFunction.java
+++ b/container-search/src/main/java/com/yahoo/search/grouping/request/MinFunction.java
@@ -1,12 +1,11 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.search.grouping.request;
-import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
- * This class represents a min-function in a {@link GroupingExpression}. It evaluates to a number that equals the
+ * A min-function in a {@link GroupingExpression}. It evaluates to a number that equals the
* smallest of the results of all arguments.
*
* @author Simon Thoresen Hult
@@ -16,9 +15,9 @@ public class MinFunction extends FunctionNode {
/**
* Constructs a new instance of this class.
*
- * @param arg1 The first compulsory argument, must evaluate to a number.
- * @param arg2 The second compulsory argument, must evaluate to a number.
- * @param argN The optional arguments, must evaluate to a number.
+ * @param arg1 the first compulsory argument, must evaluate to a number
+ * @param arg2 the second compulsory argument, must evaluate to a number
+ * @param argN the optional arguments, must evaluate to a number
*/
public MinFunction(GroupingExpression arg1, GroupingExpression arg2, GroupingExpression... argN) {
this(null, null, asList(arg1, arg2, argN));
diff --git a/container-search/src/main/java/com/yahoo/search/grouping/vespa/GroupingExecutor.java b/container-search/src/main/java/com/yahoo/search/grouping/vespa/GroupingExecutor.java
index 7c2e774f68b..d2d89e41879 100644
--- a/container-search/src/main/java/com/yahoo/search/grouping/vespa/GroupingExecutor.java
+++ b/container-search/src/main/java/com/yahoo/search/grouping/vespa/GroupingExecutor.java
@@ -13,7 +13,7 @@ import java.util.logging.Logger;
import com.yahoo.component.ComponentId;
import com.yahoo.component.chain.dependencies.After;
import com.yahoo.component.chain.dependencies.Provides;
-import com.yahoo.log.LogLevel;
+import java.util.logging.Level;
import com.yahoo.prelude.fastsearch.GroupingListHit;
import com.yahoo.prelude.query.Item;
import com.yahoo.prelude.query.QueryCanonicalizer;
@@ -225,9 +225,9 @@ public class GroupingExecutor extends Searcher {
if (passList.isEmpty()) {
throw new RuntimeException("No grouping request for pass " + pass + ", bug!");
}
- if (log.isLoggable(LogLevel.DEBUG)) {
+ if (log.isLoggable(Level.FINE)) {
for (Grouping grouping : passList) {
- log.log(LogLevel.DEBUG, "Pass(" + pass + "), Grouping(" + grouping.getId() + "): " + grouping);
+ log.log(Level.FINE, "Pass(" + pass + "), Grouping(" + grouping.getId() + "): " + grouping);
}
}
Item passRoot;
@@ -263,9 +263,9 @@ public class GroupingExecutor extends Searcher {
ret = passResult;
}
}
- if (log.isLoggable(LogLevel.DEBUG)) {
+ if (log.isLoggable(Level.FINE)) {
for (Grouping grouping : groupingMap.values()) {
- log.log(LogLevel.DEBUG, "Result Grouping(" + grouping.getId() + "): " + grouping);
+ log.log(Level.FINE, "Result Grouping(" + grouping.getId() + "): " + grouping);
}
}
return ret;
diff --git a/container-search/src/main/java/com/yahoo/search/grouping/vespa/IntegerDecoder.java b/container-search/src/main/java/com/yahoo/search/grouping/vespa/IntegerDecoder.java
index e1a222d6bc0..15781060d7f 100644
--- a/container-search/src/main/java/com/yahoo/search/grouping/vespa/IntegerDecoder.java
+++ b/container-search/src/main/java/com/yahoo/search/grouping/vespa/IntegerDecoder.java
@@ -1,6 +1,8 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.search.grouping.vespa;
+import java.util.Arrays;
+
/**
* @author Simon Thoresen Hult
*/
@@ -33,7 +35,8 @@ class IntegerDecoder {
if (c >= CHAR_MIN && c <= CHAR_MAX) {
return (0xF & (c - CHAR_MIN));
} else {
- throw new NumberFormatException(String.valueOf(c));
+ throw new NumberFormatException("Expected a char in " + Arrays.toString(IntegerEncoder.CHARS) +
+ " but was '" + c + "'");
}
}
}
diff --git a/container-search/src/main/java/com/yahoo/search/handler/HttpSearchResponse.java b/container-search/src/main/java/com/yahoo/search/handler/HttpSearchResponse.java
index d636d3bc925..ba034271a4c 100644
--- a/container-search/src/main/java/com/yahoo/search/handler/HttpSearchResponse.java
+++ b/container-search/src/main/java/com/yahoo/search/handler/HttpSearchResponse.java
@@ -1,20 +1,15 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.search.handler;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-
import com.google.common.util.concurrent.ListenableFuture;
import com.yahoo.collections.ListMap;
-import com.yahoo.container.jdisc.ExtendedResponse;
import com.yahoo.container.handler.Coverage;
import com.yahoo.container.handler.Timing;
+import com.yahoo.container.jdisc.ExtendedResponse;
import com.yahoo.container.logging.AccessLogEntry;
import com.yahoo.container.logging.HitCounts;
import com.yahoo.jdisc.HeaderFields;
+import com.yahoo.jdisc.Metric;
import com.yahoo.jdisc.handler.CompletionHandler;
import com.yahoo.jdisc.handler.ContentChannel;
import com.yahoo.processing.execution.Execution.Trace.LogValue;
@@ -25,6 +20,12 @@ import com.yahoo.search.Result;
import com.yahoo.search.query.context.QueryContext;
import com.yahoo.yolean.trace.TraceNode;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
/**
* Wrap the result of a query as an HTTP response.
*
@@ -35,20 +36,21 @@ public class HttpSearchResponse extends ExtendedResponse {
private final Result result;
private final Query query;
private final Renderer<Result> rendererCopy;
+ private final Metric metric;
private final Timing timing;
private final HitCounts hitCounts;
private final TraceNode trace;
- public HttpSearchResponse(int status, Result result, Query query, Renderer renderer) {
- this(status, result, query, renderer, null);
+ public HttpSearchResponse(int status, Result result, Query query, Renderer<Result> renderer) {
+ this(status, result, query, renderer, null, null);
}
- HttpSearchResponse(int status, Result result, Query query, Renderer renderer, TraceNode trace) {
+ HttpSearchResponse(int status, Result result, Query query, Renderer<Result> renderer, TraceNode trace, Metric metric) {
super(status);
this.query = query;
this.result = result;
this.rendererCopy = renderer;
-
+ this.metric = metric;
this.timing = SearchResponse.createTiming(query, result);
this.hitCounts = SearchResponse.createHitCounts(query, result);
this.trace = trace;
@@ -98,7 +100,11 @@ public class HttpSearchResponse extends ExtendedResponse {
}
try {
try {
- waitableRender(output);
+ long nanoStart = System.nanoTime();
+ ListenableFuture<Boolean> promise = waitableRender(output);
+ if (metric != null) {
+ promise.addListener(new RendererLatencyReporter(nanoStart), Runnable::run);
+ }
} finally {
if (!(rendererCopy instanceof AsynchronousSectionedRenderer)) {
output.flush();
@@ -173,9 +179,23 @@ public class HttpSearchResponse extends ExtendedResponse {
@Override
public Iterable<LogValue> getLogValues() {
QueryContext context = query.getContext(false);
- return context == null
- ? Collections::emptyIterator
- : context::logValueIterator;
+ return context == null ? Collections::emptyIterator : context::logValueIterator;
+ }
+
+ private class RendererLatencyReporter implements Runnable {
+
+ final long nanoStart;
+
+ RendererLatencyReporter(long nanoStart) { this.nanoStart = nanoStart; }
+
+ @Override
+ public void run() {
+ long latencyNanos = System.nanoTime() - nanoStart;
+ Metric.Context ctx = metric.createContext(Map.of(
+ SearchHandler.RENDERER_DIMENSION, rendererCopy.getClassName(),
+ SearchHandler.MIME_DIMENSION, rendererCopy.getMimeType()));
+ metric.set(SearchHandler.RENDER_LATENCY_METRIC, latencyNanos, ctx);
+ }
}
}
diff --git a/container-search/src/main/java/com/yahoo/search/handler/SearchHandler.java b/container-search/src/main/java/com/yahoo/search/handler/SearchHandler.java
index dad106570ab..c15aef44f3d 100644
--- a/container-search/src/main/java/com/yahoo/search/handler/SearchHandler.java
+++ b/container-search/src/main/java/com/yahoo/search/handler/SearchHandler.java
@@ -10,45 +10,52 @@ import com.yahoo.component.provider.ComponentRegistry;
import com.yahoo.container.QrSearchersConfig;
import com.yahoo.container.core.ChainsConfig;
import com.yahoo.container.core.ContainerHttpConfig;
+import com.yahoo.container.handler.threadpool.ContainerThreadPool;
+import com.yahoo.container.jdisc.AclMapping;
+import com.yahoo.container.jdisc.HttpMethodAclMapping;
import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.container.jdisc.HttpResponse;
import com.yahoo.container.jdisc.LoggingRequestHandler;
+import com.yahoo.container.jdisc.RequestHandlerSpec;
import com.yahoo.container.jdisc.VespaHeaders;
import com.yahoo.container.logging.AccessLog;
import com.yahoo.io.IOUtils;
import com.yahoo.jdisc.Metric;
+import com.yahoo.jdisc.Request;
import com.yahoo.language.Linguistics;
-import com.yahoo.log.LogLevel;
+import com.yahoo.language.process.Embedder;
import com.yahoo.net.HostName;
import com.yahoo.net.UriTools;
-import com.yahoo.prelude.query.QueryException;
import com.yahoo.prelude.query.parser.ParseException;
+import com.yahoo.processing.IllegalInputException;
import com.yahoo.processing.rendering.Renderer;
import com.yahoo.processing.request.CompoundName;
-import com.yahoo.search.query.ranking.SoftTimeout;
-import com.yahoo.search.searchchain.ExecutionFactory;
-import com.yahoo.slime.Inspector;
-import com.yahoo.slime.ObjectTraverser;
-import com.yahoo.slime.SlimeUtils;
-import com.yahoo.yolean.Exceptions;
import com.yahoo.search.Query;
import com.yahoo.search.Result;
import com.yahoo.search.Searcher;
import com.yahoo.search.config.IndexInfoConfig;
+import com.yahoo.search.query.context.QueryContext;
import com.yahoo.search.query.profile.compiled.CompiledQueryProfile;
import com.yahoo.search.query.profile.compiled.CompiledQueryProfileRegistry;
import com.yahoo.search.query.profile.config.QueryProfileConfigurer;
import com.yahoo.search.query.profile.config.QueryProfilesConfig;
import com.yahoo.search.query.properties.DefaultProperties;
+import com.yahoo.search.query.ranking.SoftTimeout;
import com.yahoo.search.result.ErrorMessage;
import com.yahoo.search.searchchain.Execution;
+import com.yahoo.search.searchchain.ExecutionFactory;
import com.yahoo.search.searchchain.SearchChainRegistry;
import com.yahoo.search.statistics.ElapsedTime;
+import com.yahoo.slime.Inspector;
+import com.yahoo.slime.ObjectTraverser;
+import com.yahoo.slime.SlimeUtils;
import com.yahoo.statistics.Callback;
import com.yahoo.statistics.Handle;
import com.yahoo.statistics.Statistics;
import com.yahoo.statistics.Value;
import com.yahoo.vespa.configdefinition.SpecialtokensConfig;
+import com.yahoo.yolean.Exceptions;
+import com.yahoo.yolean.trace.TraceNode;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@@ -69,6 +76,8 @@ import java.util.logging.Logger;
*/
public class SearchHandler extends LoggingRequestHandler {
+ private static final Logger log = Logger.getLogger(SearchHandler.class.getName());
+
private final AtomicInteger requestsInFlight = new AtomicInteger(0);
// max number of threads for the executor for this handler
@@ -77,15 +86,15 @@ public class SearchHandler extends LoggingRequestHandler {
private static final CompoundName DETAILED_TIMING_LOGGING = new CompoundName("trace.timingDetails");
private static final CompoundName FORCE_TIMESTAMPS = new CompoundName("trace.timestamps");
-
/** Event name for number of connections to the search subsystem */
private static final String SEARCH_CONNECTIONS = "search_connections";
+ static final String RENDER_LATENCY_METRIC = "jdisc.render.latency";
+ static final String MIME_DIMENSION = "mime";
+ static final String RENDERER_DIMENSION = "renderer";
private static final String JSON_CONTENT_TYPE = "application/json";
- private static Logger log = Logger.getLogger(SearchHandler.class.getName());
-
- private Value searchConnections;
+ private final Value searchConnections;
public static final String defaultSearchChainName = "default";
private static final String fallbackSearchChain = "vespa";
@@ -97,10 +106,15 @@ public class SearchHandler extends LoggingRequestHandler {
private final String selfHostname = HostName.getLocalhost();
+ private final Embedder embedder;
+
private final ExecutionFactory executionFactory;
private final AtomicLong numRequestsLeftToTrace;
+ private final static RequestHandlerSpec REQUEST_HANDLER_SPEC = RequestHandlerSpec.builder()
+ .withAclMapping(SearchHandler.aclRequestMapper()).build();
+
private final class MeanConnections implements Callback {
@Override
@@ -117,43 +131,101 @@ public class SearchHandler extends LoggingRequestHandler {
@Inject
public SearchHandler(Statistics statistics,
Metric metric,
+ ContainerThreadPool threadpool,
+ CompiledQueryProfileRegistry queryProfileRegistry,
+ ContainerHttpConfig config,
+ Embedder embedder,
+ ExecutionFactory executionFactory) {
+ this(statistics, metric, threadpool.executor(), queryProfileRegistry, embedder, executionFactory,
+ config.numQueriesToTraceOnDebugAfterConstruction(),
+ config.hostResponseHeaderKey().equals("") ? Optional.empty() : Optional.of(config.hostResponseHeaderKey()));
+ }
+
+ /**
+ * @deprecated Use the @Inject annotated constructor instead.
+ */
+ @Deprecated // Vespa 8
+ public SearchHandler(Statistics statistics,
+ Metric metric,
+ ContainerThreadPool threadpool,
+ AccessLog ignored,
+ CompiledQueryProfileRegistry queryProfileRegistry,
+ ContainerHttpConfig config,
+ ExecutionFactory executionFactory) {
+ this(statistics, metric, threadpool.executor(), ignored, queryProfileRegistry, config, executionFactory);
+ }
+
+ /**
+ * @deprecated Use the @Inject annotated constructor instead.
+ */
+ @Deprecated // Vespa 8
+ public SearchHandler(Statistics statistics,
+ Metric metric,
Executor executor,
- AccessLog accessLog,
+ AccessLog ignored,
+ CompiledQueryProfileRegistry queryProfileRegistry,
+ ContainerHttpConfig containerHttpConfig,
+ ExecutionFactory executionFactory) {
+ this(statistics,
+ metric,
+ executor,
+ queryProfileRegistry,
+ Embedder.throwsOnUse,
+ executionFactory,
+ containerHttpConfig.numQueriesToTraceOnDebugAfterConstruction(),
+ containerHttpConfig.hostResponseHeaderKey().equals("") ?
+ Optional.empty() : Optional.of(containerHttpConfig.hostResponseHeaderKey()));
+ }
+
+ /**
+ * @deprecated Use the @Inject annotated constructor instead.
+ */
+ @Deprecated // Vespa 8
+ public SearchHandler(Statistics statistics,
+ Metric metric,
+ Executor executor,
+ AccessLog ignored,
QueryProfilesConfig queryProfileConfig,
ContainerHttpConfig containerHttpConfig,
ExecutionFactory executionFactory) {
this(statistics,
metric,
executor,
- accessLog,
QueryProfileConfigurer.createFromConfig(queryProfileConfig).compile(),
+ Embedder.throwsOnUse,
executionFactory,
containerHttpConfig.numQueriesToTraceOnDebugAfterConstruction(),
containerHttpConfig.hostResponseHeaderKey().equals("") ?
- Optional.empty() : Optional.of( containerHttpConfig.hostResponseHeaderKey()));
+ Optional.empty() : Optional.of( containerHttpConfig.hostResponseHeaderKey()));
}
+ /**
+ * @deprecated Use the @Inject annotated constructor instead.
+ */
+ @Deprecated // Vespa 8
public SearchHandler(Statistics statistics,
Metric metric,
Executor executor,
- AccessLog accessLog,
+ AccessLog ignored,
CompiledQueryProfileRegistry queryProfileRegistry,
ExecutionFactory executionFactory,
Optional<String> hostResponseHeaderKey) {
- this(statistics, metric, executor, accessLog, queryProfileRegistry, executionFactory, 0, hostResponseHeaderKey);
+ this(statistics, metric, executor, queryProfileRegistry, Embedder.throwsOnUse,
+ executionFactory, 0, hostResponseHeaderKey);
}
private SearchHandler(Statistics statistics,
Metric metric,
Executor executor,
- AccessLog accessLog,
CompiledQueryProfileRegistry queryProfileRegistry,
+ Embedder embedder,
ExecutionFactory executionFactory,
long numQueriesToTraceOnDebugAfterStartup,
Optional<String> hostResponseHeaderKey) {
- super(executor, accessLog, metric, true);
- log.log(LogLevel.DEBUG, "SearchHandler.init " + System.identityHashCode(this));
+ super(executor, metric, true);
+ log.log(Level.FINE, () -> "SearchHandler.init " + System.identityHashCode(this));
this.queryProfileRegistry = queryProfileRegistry;
+ this.embedder = embedder;
this.executionFactory = executionFactory;
this.maxThreads = examineExecutor(executor);
@@ -192,6 +264,8 @@ public class SearchHandler extends LoggingRequestHandler {
new ExecutionFactory(chainsConfig, indexInfo, clusters, searchers, specialtokens, linguistics, renderers));
}
+ Metric metric() { return metric; }
+
private static int examineExecutor(Executor executor) {
if (executor instanceof ThreadPoolExecutor) {
return ((ThreadPoolExecutor) executor).getMaximumPoolSize();
@@ -205,10 +279,8 @@ public class SearchHandler extends LoggingRequestHandler {
try {
try {
return handleBody(request);
- } catch (QueryException e) {
- return (e.getCause() instanceof IllegalArgumentException)
- ? invalidParameterResponse(request, e)
- : illegalQueryResponse(request, e);
+ } catch (IllegalInputException e) {
+ return illegalQueryResponse(request, e);
} catch (RuntimeException e) { // Make sure we generate a valid response even on unexpected errors
log.log(Level.WARNING, "Failed handling " + request, e);
return internalServerErrorResponse(request, e);
@@ -218,6 +290,9 @@ public class SearchHandler extends LoggingRequestHandler {
}
}
+ @Override
+ public Optional<Request.RequestType> getRequestType() { return Optional.of(Request.RequestType.READ); }
+
private int getHttpResponseStatus(com.yahoo.container.jdisc.HttpRequest httpRequest, Result result) {
boolean benchmarkOutput = VespaHeaders.benchmarkOutput(httpRequest);
if (benchmarkOutput) {
@@ -234,15 +309,11 @@ public class SearchHandler extends LoggingRequestHandler {
private HttpResponse errorResponse(HttpRequest request, ErrorMessage errorMessage) {
Query query = new Query();
Result result = new Result(query, errorMessage);
- Renderer renderer = getRendererCopy(ComponentSpecification.fromString(request.getProperty("format")));
+ Renderer<Result> renderer = getRendererCopy(ComponentSpecification.fromString(request.getProperty("format")));
return new HttpSearchResponse(getHttpResponseStatus(request, result), result, query, renderer);
}
- private HttpResponse invalidParameterResponse(HttpRequest request, RuntimeException e) {
- return errorResponse(request, ErrorMessage.createInvalidQueryParameter(Exceptions.toMessageString(e)));
- }
-
private HttpResponse illegalQueryResponse(HttpRequest request, RuntimeException e) {
return errorResponse(request, ErrorMessage.createIllegalQuery(Exceptions.toMessageString(e)));
}
@@ -251,7 +322,6 @@ public class SearchHandler extends LoggingRequestHandler {
return errorResponse(request, ErrorMessage.createInternalServerError(Exceptions.toMessageString(e)));
}
-
private HttpSearchResponse handleBody(HttpRequest request) {
Map<String, String> requestMap = requestMapFromRequest(request);
@@ -259,7 +329,11 @@ public class SearchHandler extends LoggingRequestHandler {
String queryProfileName = requestMap.getOrDefault("queryProfile", null);
CompiledQueryProfile queryProfile = queryProfileRegistry.findQueryProfile(queryProfileName);
- Query query = new Query(request, requestMap, queryProfile);
+ Query query = new Query.Builder().setRequest(request)
+ .setRequestMap(requestMap)
+ .setQueryProfile(queryProfile)
+ .setEmbedder(embedder)
+ .build();
boolean benchmarking = VespaHeaders.benchmarkOutput(request);
boolean benchmarkCoverage = VespaHeaders.benchmarkCoverage(benchmarking, request.getJDiscRequest().headers());
@@ -294,14 +368,13 @@ public class SearchHandler extends LoggingRequestHandler {
}
// Transform result to response
- Renderer renderer = toRendererCopy(query.getPresentation().getRenderer());
+ Renderer<Result> renderer = toRendererCopy(query.getPresentation().getRenderer());
HttpSearchResponse response = new HttpSearchResponse(getHttpResponseStatus(request, result),
result, query, renderer,
- log.isLoggable(Level.FINE)
- ? query.getContext(false).getTrace().traceNode()
- : null);
- if (hostResponseHeaderKey.isPresent())
- response.headers().add(hostResponseHeaderKey.get(), selfHostname);
+ extractTraceNode(query),
+ metric);
+ response.setRequestType(Request.RequestType.READ);
+ hostResponseHeaderKey.ifPresent(key -> response.headers().add(key, selfHostname));
if (benchmarking)
VespaHeaders.benchmarkOutput(response.headers(), benchmarkCoverage, response.getTiming(),
@@ -310,6 +383,19 @@ public class SearchHandler extends LoggingRequestHandler {
return response;
}
+ private static TraceNode extractTraceNode(Query query) {
+ if (log.isLoggable(Level.FINE)) {
+ QueryContext queryContext = query.getContext(false);
+ if (queryContext != null) {
+ Execution.Trace trace = queryContext.getTrace();
+ if (trace != null) {
+ return trace.traceNode();
+ }
+ }
+ }
+ return null;
+ }
+
private static int getErrors(Result result) {
return result.hits().getErrorHit() == null ? 0 : 1;
}
@@ -404,7 +490,7 @@ public class SearchHandler extends LoggingRequestHandler {
if (searchConnections != null) {
connectionStatistics();
} else {
- log.log(LogLevel.WARNING,
+ log.log(Level.WARNING,
"searchConnections is a null reference, probably a known race condition during startup.",
new IllegalStateException("searchConnections reference is null."));
}
@@ -413,21 +499,17 @@ public class SearchHandler extends LoggingRequestHandler {
} catch (ParseException e) {
ErrorMessage error = ErrorMessage.createIllegalQuery("Could not parse query [" + request + "]: "
+ Exceptions.toMessageString(e));
- log.log(LogLevel.DEBUG, error::getDetailedMessage);
+ log.log(Level.FINE, error::getDetailedMessage);
+ return new Result(query, error);
+ } catch (IllegalInputException e) {
+ ErrorMessage error = ErrorMessage.createBadRequest("Invalid request [" + request + "]: "
+ + Exceptions.toMessageString(e));
+ log.log(Level.FINE, error::getDetailedMessage);
return new Result(query, error);
} catch (IllegalArgumentException e) {
- if ("Comparison method violates its general contract!".equals(e.getMessage())) {
- // This is an error in application components or Vespa code
- log(request, query, e);
- return new Result(query, ErrorMessage.createUnspecifiedError("Failed searching: " +
- Exceptions.toMessageString(e), e));
- }
- else {
- ErrorMessage error = ErrorMessage.createBadRequest("Invalid search request [" + request + "]: "
- + Exceptions.toMessageString(e));
- log.log(LogLevel.DEBUG, error::getDetailedMessage);
- return new Result(query, error);
- }
+ log(request, query, e);
+ return new Result(query, ErrorMessage.createUnspecifiedError("Failed: " +
+ Exceptions.toMessageString(e), e));
} catch (LinkageError | StackOverflowError e) {
// LinkageError should have been an Exception in an OSGi world - typical bundle dependency issue problem
// StackOverflowError is recoverable
@@ -437,7 +519,7 @@ public class SearchHandler extends LoggingRequestHandler {
return new Result(query, error);
} catch (Exception e) {
log(request, query, e);
- return new Result(query, ErrorMessage.createUnspecifiedError("Failed searching: " +
+ return new Result(query, ErrorMessage.createUnspecifiedError("Failed: " +
Exceptions.toMessageString(e), e));
}
}
@@ -448,8 +530,8 @@ public class SearchHandler extends LoggingRequestHandler {
if (maxThreads > 3) {
// cast to long to avoid overflows if maxThreads is at no
// log value (maxint)
- final long maxThreadsAsLong = maxThreads;
- final long connectionsAsLong = connections;
+ long maxThreadsAsLong = maxThreads;
+ long connectionsAsLong = connections;
// only log when exactly crossing the limit to avoid
// spamming the log
if (connectionsAsLong < maxThreadsAsLong * 9L / 10L) {
@@ -472,10 +554,10 @@ public class SearchHandler extends LoggingRequestHandler {
private void log(String request, Query query, Throwable e) {
// Attempted workaround for missing stack traces
if (e.getStackTrace().length == 0) {
- log.log(LogLevel.ERROR, "Failed executing " + query.toDetailString() +
- " [" + request + "], received exception with no context", e);
+ log.log(Level.SEVERE, "Failed executing " + query.toDetailString() +
+ " [" + request + "], received exception with no context", e);
} else {
- log.log(LogLevel.ERROR, "Failed executing " + query.toDetailString() + " [" + request + "]", e);
+ log.log(Level.SEVERE, "Failed executing " + query.toDetailString() + " [" + request + "]", e);
}
}
@@ -491,12 +573,13 @@ public class SearchHandler extends LoggingRequestHandler {
if (query.getHits() > maxHits) {
return new Result(query, ErrorMessage.createIllegalQuery(query.getHits() +
- " hits requested, configured limit: " + maxHits + "."));
+ " hits requested, configured limit: " + maxHits +
+ ". See https://docs.vespa.ai/en/reference/query-api-reference.html#native-execution-parameters"));
} else if (query.getOffset() > maxOffset) {
- return new Result(query,
- ErrorMessage.createIllegalQuery("Offset of " + query.getOffset() +
- " requested, configured limit: " + maxOffset + "."));
+ return new Result(query, ErrorMessage.createIllegalQuery("Offset of " + query.getOffset() +
+ " requested, configured limit: " + maxOffset +
+ ". See https://docs.vespa.ai/en/reference/query-api-reference.html#native-execution-parameters"));
}
return null;
}
@@ -541,11 +624,12 @@ public class SearchHandler extends LoggingRequestHandler {
Inspector inspector;
try {
- byte[] byteArray = IOUtils.readBytes(request.getData(), 1 << 20);
+ // Use an 4k buffer, that should be plenty for most json requests to pass in a single chunk
+ byte[] byteArray = IOUtils.readBytes(request.getData(), 4096);
inspector = SlimeUtils.jsonToSlime(byteArray).get();
if (inspector.field("error_message").valid()) {
- throw new QueryException("Illegal query: " + inspector.field("error_message").asString() + " at: '" +
- new String(inspector.field("offending_input").asData(), StandardCharsets.UTF_8) + "'");
+ throw new IllegalInputException("Illegal query: " + inspector.field("error_message").asString() + " at: '" +
+ new String(inspector.field("offending_input").asData(), StandardCharsets.UTF_8) + "'");
}
} catch (IOException e) {
@@ -558,9 +642,9 @@ public class SearchHandler extends LoggingRequestHandler {
requestMap.putAll(request.propertyMap());
if (requestMap.containsKey("yql") && (requestMap.containsKey("select.where") || requestMap.containsKey("select.grouping")) )
- throw new QueryException("Illegal query: Query contains both yql and select parameter");
+ throw new IllegalInputException("Illegal query: Query contains both yql and select parameter");
if (requestMap.containsKey("query") && (requestMap.containsKey("select.where") || requestMap.containsKey("select.grouping")) )
- throw new QueryException("Illegal query: Query contains both query and select parameter");
+ throw new IllegalInputException("Illegal query: Query contains both query and select parameter");
return requestMap;
}
@@ -596,6 +680,17 @@ public class SearchHandler extends LoggingRequestHandler {
});
}
+ @Override
+ public RequestHandlerSpec requestHandlerSpec() {
+ return REQUEST_HANDLER_SPEC;
+ }
+
+ private static AclMapping aclRequestMapper() {
+ return HttpMethodAclMapping.standard()
+ .override(com.yahoo.jdisc.http.HttpRequest.Method.POST, AclMapping.Action.READ)
+ .build();
+ }
+
}
diff --git a/container-search/src/main/java/com/yahoo/search/handler/SearchResponse.java b/container-search/src/main/java/com/yahoo/search/handler/SearchResponse.java
index b4a469c569a..5b5ff0770c4 100644
--- a/container-search/src/main/java/com/yahoo/search/handler/SearchResponse.java
+++ b/container-search/src/main/java/com/yahoo/search/handler/SearchResponse.java
@@ -49,9 +49,11 @@ public class SearchResponse {
}
public static Timing createTiming(Query query, Result result) {
- return new Timing(result.getElapsedTime().firstFill(),
+ long summaryStartTime = result.getElapsedTime().firstFill();
+ long queryStartTime = result.getElapsedTime().first();
+ return new Timing(summaryStartTime,
0,
- result.getElapsedTime().first(),
+ queryStartTime == Long.MAX_VALUE ? 0 : queryStartTime,
query.getTimeout());
}
diff --git a/container-search/src/main/java/com/yahoo/search/pagetemplates/PageTemplateSearcher.java b/container-search/src/main/java/com/yahoo/search/pagetemplates/PageTemplateSearcher.java
index 0ec04bf99de..2074fce19bd 100644
--- a/container-search/src/main/java/com/yahoo/search/pagetemplates/PageTemplateSearcher.java
+++ b/container-search/src/main/java/com/yahoo/search/pagetemplates/PageTemplateSearcher.java
@@ -2,9 +2,9 @@
package com.yahoo.search.pagetemplates;
import com.google.inject.Inject;
-import com.yahoo.component.ComponentId;
import com.yahoo.component.chain.dependencies.Provides;
import com.yahoo.component.provider.ComponentRegistry;
+import com.yahoo.processing.IllegalInputException;
import com.yahoo.search.Query;
import com.yahoo.search.Result;
import com.yahoo.search.Searcher;
@@ -13,8 +13,6 @@ import com.yahoo.search.pagetemplates.config.PageTemplateConfigurer;
import com.yahoo.search.pagetemplates.engine.Organizer;
import com.yahoo.search.pagetemplates.engine.Resolution;
import com.yahoo.search.pagetemplates.engine.Resolver;
-import com.yahoo.search.pagetemplates.engine.resolvers.DeterministicResolver;
-import com.yahoo.search.pagetemplates.engine.resolvers.RandomResolver;
import com.yahoo.search.pagetemplates.engine.resolvers.ResolverRegistry;
import com.yahoo.search.pagetemplates.model.Choice;
import com.yahoo.search.pagetemplates.model.PageElement;
@@ -23,7 +21,13 @@ import com.yahoo.processing.request.CompoundName;
import com.yahoo.search.result.ErrorMessage;
import com.yahoo.search.searchchain.Execution;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.Map;
/**
* Enables page optimization templates.
@@ -107,7 +111,7 @@ public class PageTemplateSearcher extends Searcher {
@Override
public Result search(Query query, Execution execution) {
// Pre execution: Choose template and sources
- List<PageElement> pages=selectPageTemplates(query);
+ List<PageElement> pages = selectPageTemplates(query);
if (pages.isEmpty()) return execution.search(query); // Bypass if no page template chosen
addSources(pages,query);
@@ -115,12 +119,12 @@ public class PageTemplateSearcher extends Searcher {
query.properties().set(pagePageTemplateListName, pages);
// Execute
- Result result=execution.search(query);
+ Result result = execution.search(query);
// Post execution: Resolve choices and organize the result as dictated by the resolved template
- Choice pageTemplateChoice=Choice.createSingletons(pages);
- Resolution resolution=selectResolver(query).resolve(pageTemplateChoice,query,result);
- organizer.organize(pageTemplateChoice,resolution,result);
+ Choice pageTemplateChoice = Choice.createSingletons(pages);
+ Resolution resolution = selectResolver(query).resolve(pageTemplateChoice, query, result);
+ organizer.organize(pageTemplateChoice, resolution, result);
return result;
}
@@ -132,23 +136,23 @@ public class PageTemplateSearcher extends Searcher {
// Determine the list of page template ids
@SuppressWarnings("unchecked")
List<String> pageIds = (List<String>) query.properties().get(pageIdListName);
- if (pageIds==null) {
- String pageIdString=query.properties().getString(pageIdName,"").trim();
- if (pageIdString.length()>0)
- pageIds=Arrays.asList(pageIdString.split(" "));
+ if (pageIds == null) {
+ String pageIdString = query.properties().getString(pageIdName,"").trim();
+ if (pageIdString.length() > 0)
+ pageIds = Arrays.asList(pageIdString.split(" "));
}
// If none set, just return the default or null if none
- if (pageIds==null) {
+ if (pageIds == null) {
PageElement defaultPage=templateRegistry.getComponent("default");
- return (defaultPage==null ? Collections.<PageElement>emptyList() : Collections.singletonList(defaultPage));
+ return (defaultPage == null ? Collections.<PageElement>emptyList() : Collections.singletonList(defaultPage));
}
// Resolve the id list to page templates
- List<PageElement> pages=new ArrayList<>(pageIds.size());
+ List<PageElement> pages = new ArrayList<>(pageIds.size());
for (String pageId : pageIds) {
- PageTemplate page=templateRegistry.getComponent(pageId);
- if (page==null)
+ PageTemplate page = templateRegistry.getComponent(pageId);
+ if (page == null)
query.errors().add(ErrorMessage.createInvalidQueryParameter("Could not resolve requested page template '" +
pageId + "'"));
else
@@ -159,17 +163,17 @@ public class PageTemplateSearcher extends Searcher {
}
private Resolver selectResolver(Query query) {
- String resolverId=query.properties().getString(pageResolverName);
- if (resolverId==null) return resolverRegistry.defaultResolver();
- Resolver resolver=resolverRegistry.getComponent(resolverId);
- if (resolver==null) throw new IllegalArgumentException("No page template resolver '" + resolverId + "'");
+ String resolverId = query.properties().getString(pageResolverName);
+ if (resolverId == null) return resolverRegistry.defaultResolver();
+ Resolver resolver = resolverRegistry.getComponent(resolverId);
+ if (resolver == null) throw new IllegalInputException("No page template resolver '" + resolverId + "'");
return resolver;
}
/** Sets query.getModel().getSources() to the right value and add source parameters specified in templates */
- private void addSources(List<PageElement> pages,Query query) {
+ private void addSources(List<PageElement> pages, Query query) {
// Determine all wanted sources
- Set<Source> pageSources=new HashSet<>();
+ Set<Source> pageSources = new HashSet<>();
for (PageElement page : pages)
pageSources.addAll(((PageTemplate)page).getSources());
@@ -177,34 +181,34 @@ public class PageTemplateSearcher extends Searcher {
if (query.getModel().getSources().size() > 0) {
// Add properties if the source list is set explicitly, but do not modify otherwise
- addParametersForIncludedSources(pageSources,query);
+ addParametersForIncludedSources(pageSources, query);
return;
}
if (pageSources.contains(Source.any)) {
- IntentModel intentModel=IntentModel.getFrom(query);
- if (intentModel!=null) {
+ IntentModel intentModel = IntentModel.getFrom(query);
+ if (intentModel != null) {
query.getModel().getSources().addAll(intentModel.getSourceNames());
- addPageTemplateSources(pageSources,query);
+ addPageTemplateSources(pageSources, query);
}
// otherwise leave empty to search all
}
else { // Let the page templates decide
- addPageTemplateSources(pageSources,query);
+ addPageTemplateSources(pageSources, query);
}
}
private void addPageTemplateSources(Set<Source> pageSources,Query query) {
for (Source pageSource : pageSources) {
- if (pageSource==Source.any) continue;
+ if (pageSource == Source.any) continue;
query.getModel().getSources().add(pageSource.getName());
addParameters(pageSource,query);
}
}
- private void addParametersForIncludedSources(Set<Source> sources,Query query) {
+ private void addParametersForIncludedSources(Set<Source> sources, Query query) {
for (Source source : sources) {
- if (source.parameters().size()>0 && query.getModel().getSources().contains(source.getName()))
+ if (source.parameters().size() > 0 && query.getModel().getSources().contains(source.getName()))
addParameters(source,query);
}
}
@@ -220,8 +224,8 @@ public class PageTemplateSearcher extends Searcher {
* is not supported. (Same parameter sets in multiple templates is supported,
* and will be just one entry in this set).
*/
- private void addErrorIfSameSourceMultipleTimes(List<PageElement> pages,Set<Source> sources,Query query) {
- Set<String> sourceNames=new HashSet<>();
+ private void addErrorIfSameSourceMultipleTimes(List<PageElement> pages, Set<Source> sources, Query query) {
+ Set<String> sourceNames = new HashSet<>();
for (Source source : sources) {
if (sourceNames.contains(source.getName()))
query.errors().add(ErrorMessage.createInvalidQueryParameter(
diff --git a/container-search/src/main/java/com/yahoo/search/pagetemplates/PlaceholderMappingVisitor.java b/container-search/src/main/java/com/yahoo/search/pagetemplates/PlaceholderMappingVisitor.java
index c29e9615fe8..c37ea5667c0 100644
--- a/container-search/src/main/java/com/yahoo/search/pagetemplates/PlaceholderMappingVisitor.java
+++ b/container-search/src/main/java/com/yahoo/search/pagetemplates/PlaceholderMappingVisitor.java
@@ -19,16 +19,16 @@ import java.util.Map;
*/
class PlaceholderMappingVisitor extends PageTemplateVisitor {
- private Map<String, MapChoice> placeholderIdToChoice=new LinkedHashMap<>();
+ private final Map<String, MapChoice> placeholderIdToChoice = new LinkedHashMap<>();
@Override
public void visit(MapChoice mapChoice) {
- List<String> placeholderIds=mapChoice.placeholderIds();
+ List<String> placeholderIds = mapChoice.placeholderIds();
for (String placeholderId : placeholderIds) {
- MapChoice existingChoice=placeholderIdToChoice.put(placeholderId,mapChoice);
- if (existingChoice!=null)
+ MapChoice existingChoice = placeholderIdToChoice.put(placeholderId,mapChoice);
+ if (existingChoice != null)
throw new IllegalArgumentException("placeholder id '" + placeholderId + "' is referenced by both " +
- mapChoice + " and " + existingChoice + ": Only one reference is allowed");
+ mapChoice + " and " + existingChoice + ": Only one reference is allowed");
}
}
diff --git a/container-search/src/main/java/com/yahoo/search/pagetemplates/PlaceholderReferenceCreatingVisitor.java b/container-search/src/main/java/com/yahoo/search/pagetemplates/PlaceholderReferenceCreatingVisitor.java
index e8870f4f11a..5ef507201cb 100644
--- a/container-search/src/main/java/com/yahoo/search/pagetemplates/PlaceholderReferenceCreatingVisitor.java
+++ b/container-search/src/main/java/com/yahoo/search/pagetemplates/PlaceholderReferenceCreatingVisitor.java
@@ -1,9 +1,10 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.search.pagetemplates;
-import com.yahoo.search.pagetemplates.model.*;
+import com.yahoo.search.pagetemplates.model.MapChoice;
+import com.yahoo.search.pagetemplates.model.PageTemplateVisitor;
+import com.yahoo.search.pagetemplates.model.Placeholder;
-import java.util.HashMap;
import java.util.Map;
/**
@@ -14,16 +15,16 @@ import java.util.Map;
*/
class PlaceholderReferenceCreatingVisitor extends PageTemplateVisitor {
- private Map<String, MapChoice> placeholderIdToChoice=new HashMap<>();
+ private final Map<String, MapChoice> placeholderIdToChoice;
public PlaceholderReferenceCreatingVisitor(Map<String, MapChoice> placeholderIdToChoice) {
- this.placeholderIdToChoice=placeholderIdToChoice;
+ this.placeholderIdToChoice = placeholderIdToChoice;
}
@Override
public void visit(Placeholder placeholder) {
- MapChoice choice=placeholderIdToChoice.get(placeholder.getId());
- if (choice==null)
+ MapChoice choice = placeholderIdToChoice.get(placeholder.getId());
+ if (choice == null)
throw new IllegalArgumentException(placeholder + " is not referenced by any choice");
placeholder.setValueContainer(choice);
}
diff --git a/container-search/src/main/java/com/yahoo/search/pagetemplates/config/PageTemplateXMLReader.java b/container-search/src/main/java/com/yahoo/search/pagetemplates/config/PageTemplateXMLReader.java
index beffd12b22a..0359432a819 100644
--- a/container-search/src/main/java/com/yahoo/search/pagetemplates/config/PageTemplateXMLReader.java
+++ b/container-search/src/main/java/com/yahoo/search/pagetemplates/config/PageTemplateXMLReader.java
@@ -38,18 +38,18 @@ public class PageTemplateXMLReader {
* @throws RuntimeException if <code>directory</code> is not a readable directory, or if there is some error in the XML
*/
public PageTemplateRegistry read(String directory) {
- List<NamedReader> pageReaders=new ArrayList<>();
+ List<NamedReader> pageReaders = new ArrayList<>();
try {
- File dir=new File(directory);
- if ( !dir.isDirectory() ) throw new IllegalArgumentException("Could not read page templates: '" +
- directory + "' is not a valid directory.");
+ File dir = new File(directory);
+ if ( ! dir.isDirectory() ) throw new IllegalArgumentException("Could not read page templates: '" +
+ directory + "' is not a valid directory.");
for (File file : sortFiles(dir)) {
if ( ! file.getName().endsWith(".xml")) continue;
- pageReaders.add(new NamedReader(file.getName(),new FileReader(file)));
+ pageReaders.add(new NamedReader(file.getName(), new FileReader(file)));
}
- return read(pageReaders,true);
+ return read(pageReaders, true);
}
catch (IOException e) {
throw new IllegalArgumentException("Could not read page templates from '" + directory + "'",e);
@@ -67,18 +67,18 @@ public class PageTemplateXMLReader {
* @throws RuntimeException if <code>fileName</code> is not a readable file, or if there is some error in the XML
*/
public PageTemplate readFile(String fileName) {
- NamedReader pageReader=null;
+ NamedReader pageReader = null;
try {
- File file=new File(fileName);
- pageReader=new NamedReader(fileName,new FileReader(file));
- String firstName=file.getName().substring(0,file.getName().length()-4);
- return read(Collections.singletonList(pageReader),true).getComponent(firstName);
+ File file = new File(fileName);
+ pageReader = new NamedReader(fileName,new FileReader(file));
+ String firstName = file.getName().substring(0, file.getName().length() - 4);
+ return read(Collections.singletonList(pageReader), true).getComponent(firstName);
}
catch (IOException e) {
- throw new IllegalArgumentException("Could not read the page template '" + fileName + "'",e);
+ throw new IllegalArgumentException("Could not read the page template '" + fileName + "'", e);
}
finally {
- if (pageReader!=null)
+ if (pageReader != null)
try { pageReader.close(); } catch (IOException e) { }
}
}
@@ -130,11 +130,11 @@ public class PageTemplateXMLReader {
}
/** Throws an exception if the name is not corresponding to the id */
- private void validateFileName(final String actualName,ComponentId id,String artifactName) {
- String expectedCanonicalFileName=id.toFileName();
- String fileName=new File(actualName).getName();
- fileName=stripXmlEnding(fileName);
- String canonicalFileName=ComponentId.fromFileName(fileName).toFileName();
+ private void validateFileName(String actualName, ComponentId id, String artifactName) {
+ String expectedCanonicalFileName = id.toFileName();
+ String fileName = new File(actualName).getName();
+ fileName = stripXmlEnding(fileName);
+ String canonicalFileName = ComponentId.fromFileName(fileName).toFileName();
if ( ! canonicalFileName.equals(expectedCanonicalFileName))
throw new IllegalArgumentException("The file name of " + artifactName + " '" + id +
"' must be '" + expectedCanonicalFileName + ".xml' but was '" + actualName + "'");
@@ -144,14 +144,14 @@ public class PageTemplateXMLReader {
if (!fileName.endsWith(".xml"))
throw new IllegalArgumentException("'" + fileName + "' should have a .xml ending");
else
- return fileName.substring(0,fileName.length()-4);
+ return fileName.substring(0, fileName.length() - 4);
}
private void readPages() {
for (Map.Entry<ComponentId,Element> pageElement : pageElementsByPageId.entrySet()) {
try {
- PageTemplate page=registry.getComponent(pageElement.getValue().getAttribute("id"));
- readPageContent(pageElement.getValue(),page);
+ PageTemplate page = registry.getComponent(pageElement.getValue().getAttribute("id"));
+ readPageContent(pageElement.getValue(), page);
}
catch (IllegalArgumentException e) {
throw new IllegalArgumentException("Could not read page template '" + pageElement.getKey() + "'",e);
@@ -159,16 +159,16 @@ public class PageTemplateXMLReader {
}
}
- private void readPageContent(Element pageElement,PageTemplate page) {
+ private void readPageContent(Element pageElement, PageTemplate page) {
if (page.isFrozen()) return; // Already read
- Section rootSection=new Section(page.getId().toString());
- readSection(pageElement,rootSection);
+ Section rootSection = new Section(page.getId().toString());
+ readSection(pageElement, rootSection);
page.setSection(rootSection);
page.freeze();
}
/** Fills a section with attributes and sub-elements from a "section" or "page" element */
- private Section readSection(Element sectionElement,Section section) {
+ private Section readSection(Element sectionElement, Section section) {
section.setLayout(Layout.fromString(sectionElement.getAttribute("layout")));
section.setRegion(sectionElement.getAttribute("region"));
section.setOrder(Sorting.fromString(sectionElement.getAttribute("order")));
@@ -198,10 +198,10 @@ public class PageTemplateXMLReader {
/** Reads the direct descendant elements of an include */
private List<PageElement> readInclude(Element element) {
- PageTemplate included=registry.getComponent(element.getAttribute("idref"));
- if (included==null)
+ PageTemplate included = registry.getComponent(element.getAttribute("idref"));
+ if (included == null)
throw new IllegalArgumentException("Could not find page template '" + element.getAttribute("idref"));
- readPageContent(pageElementsByPageId.get(included.getId()),included);
+ readPageContent(pageElementsByPageId.get(included.getId()), included);
return included.getSection().elements(Section.class);
}
@@ -223,9 +223,9 @@ public class PageTemplateXMLReader {
}
private List<Source> readSourceAttribute(Element sectionElement) {
- List<Source> sources=new ArrayList<>();
- String sourceAttributeString=sectionElement.getAttribute("source");
- if (sourceAttributeString!=null) {
+ List<Source> sources = new ArrayList<>();
+ String sourceAttributeString = sectionElement.getAttribute("source");
+ if (sourceAttributeString != null) {
for (String sourceName : sourceAttributeString.split(" ")) {
if (sourceName.isEmpty()) continue;
if ("*".equals(sourceName))
diff --git a/container-search/src/main/java/com/yahoo/search/pagetemplates/engine/Organizer.java b/container-search/src/main/java/com/yahoo/search/pagetemplates/engine/Organizer.java
index 3e6e82a5584..051103cba06 100644
--- a/container-search/src/main/java/com/yahoo/search/pagetemplates/engine/Organizer.java
+++ b/container-search/src/main/java/com/yahoo/search/pagetemplates/engine/Organizer.java
@@ -9,9 +9,7 @@ import com.yahoo.search.query.Sorting;
import com.yahoo.search.result.*;
import java.util.ArrayList;
-import java.util.Iterator;
import java.util.List;
-import java.util.Map;
/**
* Reorganizes and prunes a result as prescribed by a resolved template.
@@ -29,13 +27,13 @@ public class Organizer {
* @param result the result to organize
*/
public void organize(Choice templateChoice, Resolution resolution, Result result) {
- PageTemplate template=(PageTemplate)templateChoice.get(resolution.getResolution(templateChoice)).get(0);
- SectionHitGroup sectionGroup =toGroup(template.getSection(),resolution,result);
- ErrorHit errors=result.hits().getErrorHit();
+ PageTemplate template = (PageTemplate)templateChoice.get(resolution.getResolution(templateChoice)).get(0);
+ SectionHitGroup sectionGroup = toGroup(template.getSection(), resolution, result);
+ ErrorHit errors = result.hits().getErrorHit();
// transfer state from existing hit
sectionGroup.setQuery(result.hits().getQuery());
- if (errors!=null && errors instanceof DefaultErrorHit)
+ if (errors instanceof DefaultErrorHit)
sectionGroup.add((DefaultErrorHit)errors);
result.hits().forEachField((name, value) -> sectionGroup.setField(name, value));
result.setHits(sectionGroup);
diff --git a/container-search/src/main/java/com/yahoo/search/pagetemplates/engine/Resolution.java b/container-search/src/main/java/com/yahoo/search/pagetemplates/engine/Resolution.java
index e0a3821e10c..f36ebe56e21 100644
--- a/container-search/src/main/java/com/yahoo/search/pagetemplates/engine/Resolution.java
+++ b/container-search/src/main/java/com/yahoo/search/pagetemplates/engine/Resolution.java
@@ -40,10 +40,10 @@ public class Resolution {
* been resolved in this
*/
public int getResolution(Choice choice) {
- if (choice.alternatives().size()==1) return 0;
+ if (choice.alternatives().size() == 1) return 0;
if (choice.isEmpty()) throw new IllegalArgumentException("Cannot return a resolution of empty " + choice);
- Integer resolution=choiceResolutions.get(choice);
- if (resolution==null) throw new IllegalArgumentException(this + " has no resolution of " + choice);
+ Integer resolution = choiceResolutions.get(choice);
+ if (resolution == null) throw new IllegalArgumentException(this + " has no resolution of " + choice);
return resolution;
}
diff --git a/container-search/src/main/java/com/yahoo/search/pagetemplates/result/PageTemplatesXmlRenderer.java b/container-search/src/main/java/com/yahoo/search/pagetemplates/result/PageTemplatesXmlRenderer.java
index f2e2e1b034d..47914792da8 100644
--- a/container-search/src/main/java/com/yahoo/search/pagetemplates/result/PageTemplatesXmlRenderer.java
+++ b/container-search/src/main/java/com/yahoo/search/pagetemplates/result/PageTemplatesXmlRenderer.java
@@ -271,10 +271,10 @@ public class PageTemplatesXmlRenderer extends AsynchronousSectionedRenderer<Resu
private Result getResult() {
try {
- return (Result) getResponse();
+ return (Result)getResponse();
} catch (ClassCastException e) {
- throw new IllegalArgumentException("PageTemplatesXmlRenderer attempted used outside a search context, got a " +
- getResponse().getClass().getName());
+ throw new IllegalStateException("PageTemplatesXmlRenderer attempted used outside a search context, got a " +
+ getResponse().getClass().getName());
}
}
diff --git a/container-search/src/main/java/com/yahoo/search/query/Model.java b/container-search/src/main/java/com/yahoo/search/query/Model.java
index f06aab09a3d..637873aa375 100644
--- a/container-search/src/main/java/com/yahoo/search/query/Model.java
+++ b/container-search/src/main/java/com/yahoo/search/query/Model.java
@@ -7,6 +7,7 @@ import com.yahoo.language.LocaleFactory;
import com.yahoo.prelude.query.CompositeItem;
import com.yahoo.prelude.query.Item;
import com.yahoo.prelude.query.TaggableItem;
+import com.yahoo.processing.IllegalInputException;
import com.yahoo.processing.request.CompoundName;
import com.yahoo.search.Query;
import com.yahoo.search.query.parser.Parsable;
@@ -236,10 +237,14 @@ public class Model implements Cloneable {
*/
public QueryTree getQueryTree() {
if (queryTree == null) {
- Parser parser = ParserFactory.newInstance(type, ParserEnvironment.fromExecutionContext(execution.context()));
- queryTree = parser.parse(Parsable.fromQueryModel(this));
- if (parent.getTraceLevel() >= 2) {
- parent.trace("Query parsed to: " + parent.yqlRepresentation(), 2);
+ try {
+ Parser parser = ParserFactory.newInstance(type, ParserEnvironment.fromExecutionContext(execution.context()));
+ queryTree = parser.parse(Parsable.fromQueryModel(this));
+ if (parent.getTraceLevel() >= 2)
+ parent.trace("Query parsed to: " + parent.yqlRepresentation(), 2);
+ }
+ catch (IllegalArgumentException e) {
+ throw new IllegalInputException("Failed parsing query", e);
}
}
return queryTree;
diff --git a/container-search/src/main/java/com/yahoo/search/query/ParameterParser.java b/container-search/src/main/java/com/yahoo/search/query/ParameterParser.java
index d358fa06977..c0f0ee80730 100644
--- a/container-search/src/main/java/com/yahoo/search/query/ParameterParser.java
+++ b/container-search/src/main/java/com/yahoo/search/query/ParameterParser.java
@@ -1,7 +1,7 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.search.query;
-import static com.yahoo.container.util.Util.quote;
+import com.yahoo.processing.IllegalInputException;
/**
* Wrapper class to avoid code duplication of common parsing requirements.
@@ -25,13 +25,8 @@ public class ParameterParser {
* representation cannot be parsed as a number followed optionally by time unit
*/
public static Long asMilliSeconds(Object value, Long defaultValue) {
- if (value == null) {
- return defaultValue;
- }
- if (value instanceof Number) {
- Number n = (Number) value;
- return Long.valueOf(n.longValue() * 1000L);
- }
+ if (value == null) return defaultValue;
+ if (value instanceof Number) return ((Number)value).longValue() * 1000L;
return parseTime(value.toString());
}
@@ -43,7 +38,7 @@ public class ParameterParser {
double multiplier = parseUnit(time.substring(unitOffset));
return (long) (measure * multiplier);
} catch (RuntimeException e) {
- throw new IllegalArgumentException("Error parsing " + quote(time), e);
+ throw new IllegalInputException("Error parsing '" + time + "'", e);
}
}
@@ -58,7 +53,7 @@ public class ParameterParser {
}
}
if (unitOffset == 0) {
- throw new NumberFormatException("Invalid number " + quote(time));
+ throw new IllegalInputException("Invalid number '" + time + "'");
}
return unitOffset;
}
diff --git a/container-search/src/main/java/com/yahoo/search/query/Presentation.java b/container-search/src/main/java/com/yahoo/search/query/Presentation.java
index 13196488e98..db2fbf525e0 100644
--- a/container-search/src/main/java/com/yahoo/search/query/Presentation.java
+++ b/container-search/src/main/java/com/yahoo/search/query/Presentation.java
@@ -23,7 +23,7 @@ import java.util.Set;
public class Presentation implements Cloneable {
/** The type representing the property arguments consumed by this */
- private static QueryProfileType argumentType;
+ private static final QueryProfileType argumentType;
public static final String PRESENTATION = "presentation";
public static final String BOLDING = "bolding";
@@ -48,7 +48,7 @@ public class Presentation implements Cloneable {
public static QueryProfileType getArgumentType() { return argumentType; }
/** How the result should be highlighted */
- private Highlight highlight= null;
+ private Highlight highlight = null;
/** The terms to highlight in the result (only used by BoldingSearcher, may be removed later). */
private List<IndexedItem> boldingData = null;
@@ -128,15 +128,15 @@ public class Presentation implements Cloneable {
return clone;
}
catch (CloneNotSupportedException e) {
- throw new RuntimeException("Someone inserted a noncloneable superclass",e);
+ throw new RuntimeException("Someone inserted a noncloneable superclass", e);
}
}
@Override
public boolean equals(Object o) {
- if (o == null || !(o instanceof Presentation)) return false;
+ if ( ! (o instanceof Presentation)) return false;
Presentation p = (Presentation) o;
- return QueryHelper.equals(bolding,p.bolding) && QueryHelper.equals(summary,p.summary);
+ return QueryHelper.equals(bolding, p.bolding) && QueryHelper.equals(summary, p.summary);
}
@Override
diff --git a/container-search/src/main/java/com/yahoo/search/query/Properties.java b/container-search/src/main/java/com/yahoo/search/query/Properties.java
index a1a70b4c3ba..a0cd4137e9f 100644
--- a/container-search/src/main/java/com/yahoo/search/query/Properties.java
+++ b/container-search/src/main/java/com/yahoo/search/query/Properties.java
@@ -30,8 +30,9 @@ public abstract class Properties extends com.yahoo.processing.request.Properties
return (Properties)super.clone();
}
- /** The query owning this property object.
- * Only guaranteed to work if this instance is accessible as query.properties()
+ /**
+ * Returns the query owning this property object.
+ * Only guaranteed to work if this instance is accessible as query.properties()
*/
public Query getParentQuery() {
if (chained() == null) {
@@ -48,4 +49,5 @@ public abstract class Properties extends com.yahoo.processing.request.Properties
if (chained() != null)
chained().setParentQuery(query);
}
+
}
diff --git a/container-search/src/main/java/com/yahoo/search/query/Ranking.java b/container-search/src/main/java/com/yahoo/search/query/Ranking.java
index 830a3f4ef81..0aa3f12ca68 100644
--- a/container-search/src/main/java/com/yahoo/search/query/Ranking.java
+++ b/container-search/src/main/java/com/yahoo/search/query/Ranking.java
@@ -39,6 +39,7 @@ public class Ranking implements Cloneable {
public static final String LIST_FEATURES = "listFeatures";
public static final String FRESHNESS = "freshness";
public static final String QUERYCACHE = "queryCache";
+ public static final String RERANKCOUNT = "rerankCount";
public static final String MATCH_PHASE = "matchPhase";
public static final String DIVERSITY = "diversity";
public static final String SOFTTIMEOUT = "softtimeout";
@@ -55,7 +56,8 @@ public class Ranking implements Cloneable {
argumentType.addField(new FieldDescription(SORTING, "string", "sorting sortspec"));
argumentType.addField(new FieldDescription(LIST_FEATURES, "string", RANKFEATURES.toString()));
argumentType.addField(new FieldDescription(FRESHNESS, "string", "datetime"));
- argumentType.addField(new FieldDescription(QUERYCACHE, "string"));
+ argumentType.addField(new FieldDescription(QUERYCACHE, "boolean"));
+ argumentType.addField(new FieldDescription(RERANKCOUNT, "integer"));
argumentType.addField(new FieldDescription(MATCH_PHASE, new QueryProfileFieldType(MatchPhase.getArgumentType()), "matchPhase"));
argumentType.addField(new FieldDescription(DIVERSITY, new QueryProfileFieldType(Diversity.getArgumentType())));
argumentType.addField(new FieldDescription(SOFTTIMEOUT, new QueryProfileFieldType(SoftTimeout.getArgumentType())));
@@ -67,7 +69,7 @@ public class Ranking implements Cloneable {
}
public static QueryProfileType getArgumentType() { return argumentType; }
- private Query parent;
+ private final Query parent;
/** The location of the query is used for distance ranking */
private Location location = null;
@@ -85,6 +87,8 @@ public class Ranking implements Cloneable {
private boolean queryCache = false;
+ private Integer rerankCount = null;
+
private RankProperties rankProperties = new RankProperties();
private RankFeatures rankFeatures = new RankFeatures();
@@ -140,6 +144,15 @@ public class Ranking implements Cloneable {
public boolean getQueryCache() { return queryCache; }
+ /**
+ * Sets the number of hits for which the second-phase function will be evaluated.
+ * When set, this overrides the setting in the rank profile.
+ */
+ public void setRerankCount(int rerankCount) { this.rerankCount = rerankCount; }
+
+ /** Returns the rerank-count that will be used, or null if not set */
+ public Integer getRerankCount() { return rerankCount; }
+
/** Returns the location of this query, or null if none */
public Location getLocation() { return location; }
@@ -258,6 +271,8 @@ public class Ranking implements Cloneable {
matching.prepare(rankProperties);
softTimeout.prepare(rankProperties);
prepareNow(freshness);
+ if (rerankCount != null)
+ rankProperties.put("vespa.hitcollector.heapsize", rerankCount);
}
private void prepareNow(Freshness freshness) {
diff --git a/container-search/src/main/java/com/yahoo/search/query/Select.java b/container-search/src/main/java/com/yahoo/search/query/Select.java
index cb662dcd671..a7e491f5269 100644
--- a/container-search/src/main/java/com/yahoo/search/query/Select.java
+++ b/container-search/src/main/java/com/yahoo/search/query/Select.java
@@ -1,7 +1,6 @@
// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.search.query;
-import com.yahoo.processing.request.CompoundName;
import com.yahoo.search.Query;
import com.yahoo.search.grouping.GroupingRequest;
import com.yahoo.search.query.parser.ParserEnvironment;
@@ -12,13 +11,12 @@ import com.yahoo.search.yql.VespaGroupingStep;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.LinkedHashSet;
import java.util.List;
import java.util.Objects;
/**
- * The parameters defining the where-clause and groping of a query
+ * The parameters defining the where-clause and grouping of a query
*
* @author henrhoi
*/
@@ -26,7 +24,6 @@ public class Select implements Cloneable {
/** The type representing the property arguments consumed by this */
private static final QueryProfileType argumentType;
- private static final CompoundName argumentTypeName;
public static final String SELECT = "select";
public static final String WHERE = "where";
@@ -46,7 +43,6 @@ public class Select implements Cloneable {
argumentType.addField(new FieldDescription(WHERE, "string"));
argumentType.addField(new FieldDescription(GROUPING, "string"));
argumentType.freeze();
- argumentTypeName = new CompoundName(argumentType.getId().getName());
}
public static QueryProfileType getArgumentType() { return argumentType; }
@@ -57,12 +53,13 @@ public class Select implements Cloneable {
}
public Select(String where, String grouping, Query query) {
- this(where, grouping, query, Collections.emptyList());
+ this(where, grouping, null, query, Collections.emptyList());
}
- private Select(String where, String grouping, Query query, List<GroupingRequest> groupingRequests) {
+ private Select(String where, String grouping, String groupingExpressionString, Query query, List<GroupingRequest> groupingRequests) {
this.where = Objects.requireNonNull(where, "A Select must have a where string (possibly the empty string)");
this.grouping = Objects.requireNonNull(grouping, "A Select must have a select string (possibly the empty string)");
+ this.groupingExpressionString = groupingExpressionString;
this.parent = Objects.requireNonNull(query, "A Select must have a parent query");
this.groupingRequests = deepCopy(groupingRequests, this);
}
@@ -78,7 +75,7 @@ public class Select implements Cloneable {
* Sets the document selection criterion of the query.
*
* @param where the documents to select as a JSON string on the format specified in
- * <a href="https://docs.vespa.ai/documentation/reference/select-reference.html">the select reference doc</a>
+ * <a href="https://docs.vespa.ai/en/reference/select-reference.html">the select reference doc</a>
*/
public void setWhereString(String where) {
this.where = where;
@@ -95,7 +92,7 @@ public class Select implements Cloneable {
* Sets the grouping operation of the query.
*
* @param grouping the grouping to perform as a JSON string on the format specified in
- * <a href="https://docs.vespa.ai/documentation/reference/select-reference.html">the select reference doc</a>
+ * <a href="https://docs.vespa.ai/en/reference/select-reference.html">the select reference doc</a>
*/
public void setGroupingString(String grouping) {
groupingRequests.clear();
@@ -131,16 +128,16 @@ public class Select implements Cloneable {
@Override
public String toString() {
- return "where: [" + where + "], grouping: [" + grouping+ "]";
+ return "where: [" + where + "], grouping: [" + grouping + "]";
}
@Override
public Object clone() {
- return new Select(where, grouping, parent, groupingRequests);
+ return new Select(where, grouping, groupingExpressionString, parent, groupingRequests);
}
public Select cloneFor(Query parent) {
- return new Select(where, grouping, parent, groupingRequests);
+ return new Select(where, grouping, groupingExpressionString, parent, groupingRequests);
}
}
diff --git a/container-search/src/main/java/com/yahoo/search/query/SelectParser.java b/container-search/src/main/java/com/yahoo/search/query/SelectParser.java
index 775dca7c444..f94c6300e83 100644
--- a/container-search/src/main/java/com/yahoo/search/query/SelectParser.java
+++ b/container-search/src/main/java/com/yahoo/search/query/SelectParser.java
@@ -2,10 +2,14 @@
package com.yahoo.search.query;
import com.google.common.base.Preconditions;
+import com.yahoo.processing.IllegalInputException;
import com.yahoo.collections.LazyMap;
+import com.yahoo.geo.DistanceParser;
+import com.yahoo.geo.ParsedDegree;
import com.yahoo.language.Language;
import com.yahoo.language.process.Normalizer;
import com.yahoo.prelude.IndexFacts;
+import com.yahoo.prelude.Location;
import com.yahoo.prelude.query.AndItem;
import com.yahoo.prelude.query.BoolItem;
import com.yahoo.prelude.query.CompositeItem;
@@ -15,14 +19,15 @@ import com.yahoo.prelude.query.ExactStringItem;
import com.yahoo.prelude.query.IntItem;
import com.yahoo.prelude.query.Item;
import com.yahoo.prelude.query.Limit;
+import com.yahoo.prelude.query.GeoLocationItem;
import com.yahoo.prelude.query.NearItem;
+import com.yahoo.prelude.query.NearestNeighborItem;
import com.yahoo.prelude.query.NotItem;
import com.yahoo.prelude.query.ONearItem;
import com.yahoo.prelude.query.OrItem;
import com.yahoo.prelude.query.PhraseItem;
import com.yahoo.prelude.query.PredicateQueryItem;
import com.yahoo.prelude.query.PrefixItem;
-import com.yahoo.prelude.query.QueryException;
import com.yahoo.prelude.query.RangeItem;
import com.yahoo.prelude.query.RankItem;
import com.yahoo.prelude.query.RegExpItem;
@@ -46,6 +51,7 @@ import com.yahoo.slime.ArrayTraverser;
import com.yahoo.slime.Inspector;
import com.yahoo.slime.ObjectTraverser;
import com.yahoo.slime.SlimeUtils;
+import com.yahoo.slime.Type;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
@@ -60,6 +66,59 @@ import static com.yahoo.slime.Type.LONG;
import static com.yahoo.slime.Type.OBJECT;
import static com.yahoo.slime.Type.STRING;
+import static com.yahoo.search.yql.YqlParser.ACCENT_DROP;
+import static com.yahoo.search.yql.YqlParser.ALTERNATIVES;
+import static com.yahoo.search.yql.YqlParser.AND_SEGMENTING;
+import static com.yahoo.search.yql.YqlParser.ANNOTATIONS;
+import static com.yahoo.search.yql.YqlParser.APPROXIMATE;
+import static com.yahoo.search.yql.YqlParser.ASCENDING_HITS_ORDER;
+import static com.yahoo.search.yql.YqlParser.CONNECTION_ID;
+import static com.yahoo.search.yql.YqlParser.CONNECTION_WEIGHT;
+import static com.yahoo.search.yql.YqlParser.CONNECTIVITY;
+import static com.yahoo.search.yql.YqlParser.DEFAULT_TARGET_NUM_HITS;
+import static com.yahoo.search.yql.YqlParser.DESCENDING_HITS_ORDER;
+import static com.yahoo.search.yql.YqlParser.DISTANCE;
+import static com.yahoo.search.yql.YqlParser.DISTANCE_THRESHOLD;
+import static com.yahoo.search.yql.YqlParser.DOT_PRODUCT;
+import static com.yahoo.search.yql.YqlParser.EQUIV;
+import static com.yahoo.search.yql.YqlParser.FILTER;
+import static com.yahoo.search.yql.YqlParser.GEO_LOCATION;
+import static com.yahoo.search.yql.YqlParser.HIT_LIMIT;
+import static com.yahoo.search.yql.YqlParser.HNSW_EXPLORE_ADDITIONAL_HITS;
+import static com.yahoo.search.yql.YqlParser.IMPLICIT_TRANSFORMS;
+import static com.yahoo.search.yql.YqlParser.LABEL;
+import static com.yahoo.search.yql.YqlParser.NEAR;
+import static com.yahoo.search.yql.YqlParser.NEAREST_NEIGHBOR;
+import static com.yahoo.search.yql.YqlParser.NFKC;
+import static com.yahoo.search.yql.YqlParser.NORMALIZE_CASE;
+import static com.yahoo.search.yql.YqlParser.ONEAR;
+import static com.yahoo.search.yql.YqlParser.ORIGIN;
+import static com.yahoo.search.yql.YqlParser.ORIGIN_LENGTH;
+import static com.yahoo.search.yql.YqlParser.ORIGIN_OFFSET;
+import static com.yahoo.search.yql.YqlParser.ORIGIN_ORIGINAL;
+import static com.yahoo.search.yql.YqlParser.PHRASE;
+import static com.yahoo.search.yql.YqlParser.PREDICATE;
+import static com.yahoo.search.yql.YqlParser.PREFIX;
+import static com.yahoo.search.yql.YqlParser.RANGE;
+import static com.yahoo.search.yql.YqlParser.RANK;
+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.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_HITS;
+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.USE_POSITION_DATA;
+import static com.yahoo.search.yql.YqlParser.USER_INPUT_LANGUAGE;
+import static com.yahoo.search.yql.YqlParser.WAND;
+import static com.yahoo.search.yql.YqlParser.WEAK_AND;
+import static com.yahoo.search.yql.YqlParser.WEIGHT;
+import static com.yahoo.search.yql.YqlParser.WEIGHTED_SET;
+
/**
* The Select query language.
*
@@ -69,6 +128,14 @@ import static com.yahoo.slime.Type.STRING;
*/
public class SelectParser implements Parser {
+ private static final String AND = "and";
+ private static final String AND_NOT = "and_not";
+ private static final String CALL = "call";
+ private static final String CONTAINS = "contains";
+ private static final String EQ = "equals";
+ private static final String MATCHES = "matches";
+ private static final String OR = "or";
+
Parsable query;
private final IndexFacts indexFacts;
private final Map<Integer, TaggableItem> identifiedItems = LazyMap.newHashMap();
@@ -76,61 +143,7 @@ public class SelectParser implements Parser {
private final Normalizer normalizer;
private IndexFacts.Session indexFactsSession;
- // YQL parameters and functions
- private static final String DESCENDING_HITS_ORDER = "descending";
- private static final String ASCENDING_HITS_ORDER = "ascending";
- private static final Integer DEFAULT_TARGET_NUM_HITS = 10;
- private static final String ORIGIN_LENGTH = "length";
- private static final String ORIGIN_OFFSET = "offset";
- private static final String ORIGIN = "origin";
- private static final String ORIGIN_ORIGINAL = "original";
- private static final String CONNECTION_ID = "id";
- private static final String CONNECTION_WEIGHT = "weight";
- private static final String CONNECTIVITY = "connectivity";
- private static final String ANNOTATIONS = "annotations";
- private static final String NFKC = "nfkc";
- private static final String USER_INPUT_LANGUAGE = "language";
- private static final String ACCENT_DROP = "accentDrop";
- private static final String ALTERNATIVES = "alternatives";
- private static final String AND_SEGMENTING = "andSegmenting";
- private static final String DISTANCE = "distance";
- private static final String DOT_PRODUCT = "dotProduct";
- private static final String EQUIV = "equiv";
- private static final String FILTER = "filter";
- private static final String HIT_LIMIT = "hitLimit";
- private static final String IMPLICIT_TRANSFORMS = "implicitTransforms";
- private static final String LABEL = "label";
- private static final String NEAR = "near";
- private static final String NORMALIZE_CASE = "normalizeCase";
- private static final String ONEAR = "onear";
- private static final String PHRASE = "phrase";
- private static final String PREDICATE = "predicate";
- private static final String PREFIX = "prefix";
- private static final String RANKED = "ranked";
- private static final String RANK = "rank";
- private static final String SAME_ELEMENT = "sameElement";
- private static final String SCORE_THRESHOLD = "scoreThreshold";
- private static final String SIGNIFICANCE = "significance";
- private static final String STEM = "stem";
- private static final String SUBSTRING = "substring";
- private static final String SUFFIX = "suffix";
- private static final String TARGET_NUM_HITS = "targetNumHits";
- private static final String THRESHOLD_BOOST_FACTOR = "thresholdBoostFactor";
- private static final String UNIQUE_ID = "id";
- private static final String USE_POSITION_DATA = "usePositionData";
- private static final String WAND = "wand";
- private static final String WEAK_AND = "weakAnd";
- private static final String WEIGHTED_SET = "weightedSet";
- private static final String WEIGHT = "weight";
- private static final String AND = "and";
- private static final String AND_NOT = "and_not";
- private static final String OR = "or";
- private static final String EQ = "equals";
- private static final String RANGE = "range";
- private static final String CONTAINS = "contains";
- private static final String MATCHES = "matches";
- private static final String CALL = "call";
- private static final List<String> FUNCTION_CALLS = Arrays.asList(WAND, WEIGHTED_SET, DOT_PRODUCT, PREDICATE, RANK, WEAK_AND);
+ private static final List<String> FUNCTION_CALLS = Arrays.asList(WAND, WEIGHTED_SET, DOT_PRODUCT, GEO_LOCATION, NEAREST_NEIGHBOR, PREDICATE, RANK, WEAK_AND);
public SelectParser(ParserEnvironment environment) {
indexFacts = environment.getIndexFacts();
@@ -148,15 +161,20 @@ public class SelectParser implements Parser {
}
private QueryTree buildTree() {
- Inspector inspector = SlimeUtils.jsonToSlime(this.query.getSelect().getWhereString().getBytes()).get();
+ Inspector inspector = SlimeUtils.jsonToSlime(this.query.getSelect().getWhereString()).get();
if (inspector.field("error_message").valid()) {
- throw new QueryException("Illegal query: " + inspector.field("error_message").asString() +
+ throw new IllegalInputException("Illegal query: " + inspector.field("error_message").asString() +
" at: '" + new String(inspector.field("offending_input").asData(), StandardCharsets.UTF_8) + "'");
}
- Item root = walkJson(inspector);
- connectItems();
- return new QueryTree(root);
+ try {
+ Item root = walkJson(inspector);
+ connectItems();
+ return new QueryTree(root);
+ }
+ catch (IllegalArgumentException e) {
+ throw new IllegalInputException("Illegal JSON query", e);
+ }
}
private Item walkJson(Inspector inspector) {
@@ -208,10 +226,10 @@ public class SelectParser implements Parser {
/** Translates a list of grouping requests on JSON form to a list in the grouping language form */
private List<String> toGroupingRequests(String groupingJson) {
- Inspector inspector = SlimeUtils.jsonToSlime(groupingJson.getBytes()).get();
+ Inspector inspector = SlimeUtils.jsonToSlime(groupingJson).get();
if (inspector.field("error_message").valid()) {
- throw new QueryException("Illegal query: " + inspector.field("error_message").asString() +
- " at: '" + new String(inspector.field("offending_input").asData(), StandardCharsets.UTF_8) + "'");
+ throw new IllegalInputException("Illegal query: " + inspector.field("error_message").asString() +
+ " at: '" + new String(inspector.field("offending_input").asData(), StandardCharsets.UTF_8) + "'");
}
List<String> operations = new ArrayList<>();
@@ -259,6 +277,10 @@ public class SelectParser implements Parser {
return buildWeightedSet(key, value);
case DOT_PRODUCT:
return buildDotProduct(key, value);
+ case GEO_LOCATION:
+ return buildGeoLocation(key, value);
+ case NEAREST_NEIGHBOR:
+ return buildNearestNeighbor(key, value);
case PREDICATE:
return buildPredicate(key, value);
case RANK:
@@ -266,7 +288,7 @@ public class SelectParser implements Parser {
case WEAK_AND:
return buildWeakAnd(key, value);
default:
- throw newUnexpectedArgumentException(key, DOT_PRODUCT, RANK, WAND, WEAK_AND, WEIGHTED_SET, PREDICATE);
+ throw newUnexpectedArgumentException(key, DOT_PRODUCT, NEAREST_NEIGHBOR, RANK, WAND, WEAK_AND, WEIGHTED_SET, PREDICATE);
}
}
@@ -403,17 +425,97 @@ public class SelectParser implements Parser {
return orItem;
}
+ private Item buildGeoLocation(String key, Inspector value) {
+ HashMap<Integer, Inspector> children = childMap(value);
+ Preconditions.checkArgument(children.size() == 4, "Expected 4 arguments, got %s.", children.size());
+ String field = children.get(0).asString();
+ var arg1 = children.get(1);
+ var arg2 = children.get(2);
+ var arg3 = children.get(3);
+ var loc = new Location();
+ if (arg3.type() != Type.STRING) {
+ throw new IllegalArgumentException("Invalid geoLocation radius type "+arg3.type()+" for "+arg3);
+ }
+ double radius = DistanceParser.parse(arg3.asString());
+ if (arg1.type() == Type.STRING && arg2.type() == Type.STRING) {
+ var c1input = children.get(1).asString();
+ var c2input = children.get(2).asString();
+ var coord_1 = ParsedDegree.fromString(c1input, true, false);
+ var coord_2 = ParsedDegree.fromString(c2input, false, true);
+ if (coord_1.isLatitude && coord_2.isLongitude) {
+ loc.setGeoCircle(coord_1.degrees, coord_2.degrees, radius);
+ } else if (coord_2.isLatitude && coord_1.isLongitude) {
+ loc.setGeoCircle(coord_2.degrees, coord_1.degrees, radius);
+ } else {
+ throw new IllegalArgumentException("Invalid geoLocation coordinates '"+c1input+"' and '"+c2input+"'");
+ }
+ } else if (arg1.type() == Type.DOUBLE && arg2.type() == Type.DOUBLE) {
+ loc.setGeoCircle(arg1.asDouble(), arg2.asDouble(), radius);
+ } else {
+ throw new IllegalArgumentException("Invalid geoLocation coordinate types "+arg1.type()+" and "+arg2.type());
+ }
+ var item = new GeoLocationItem(loc, field);
+ Inspector annotations = getAnnotations(value);
+ if (annotations != null){
+ annotations.traverse((ObjectTraverser) (annotation_name, annotation_value) -> {
+ if (LABEL.equals(annotation_name)) {
+ item.setLabel(annotation_value.asString());
+ }
+ });
+ }
+ return item;
+ }
+
+ private Item buildNearestNeighbor(String key, Inspector value) {
+ HashMap<Integer, Inspector> children = childMap(value);
+ Preconditions.checkArgument(children.size() == 2, "Expected 2 arguments, got %s.", children.size());
+ String field = children.get(0).asString();
+ String property = children.get(1).asString();
+ NearestNeighborItem item = new NearestNeighborItem(field, property);
+ Inspector annotations = getAnnotations(value);
+ if (annotations != null){
+ annotations.traverse((ObjectTraverser) (annotation_name, annotation_value) -> {
+ if (TARGET_HITS.equals(annotation_name)){
+ item.setTargetNumHits((int)(annotation_value.asDouble()));
+ }
+ if (TARGET_NUM_HITS.equals(annotation_name)){
+ item.setTargetNumHits((int)(annotation_value.asDouble()));
+ }
+ if (DISTANCE_THRESHOLD.equals(annotation_name)) {
+ double distanceThreshold = annotation_value.asDouble();
+ item.setDistanceThreshold(distanceThreshold);
+ }
+ if (HNSW_EXPLORE_ADDITIONAL_HITS.equals(annotation_name)) {
+ int hnswExploreAdditionalHits = (int)(annotation_value.asDouble());
+ item.setHnswExploreAdditionalHits(hnswExploreAdditionalHits);
+ }
+ if (APPROXIMATE.equals(annotation_name)) {
+ boolean allowApproximate = annotation_value.asBool();
+ item.setAllowApproximate(allowApproximate);
+ }
+ if (LABEL.equals(annotation_name)) {
+ item.setLabel(annotation_value.asString());
+ }
+ });
+ }
+ return item;
+ }
+
+ @SuppressWarnings("deprecation")
private CompositeItem buildWeakAnd(String key, Inspector value) {
WeakAndItem weakAnd = new WeakAndItem();
addItemsFromInspector(weakAnd, value);
Inspector annotations = getAnnotations(value);
- if (annotations != null){
+ if (annotations != null) {
annotations.traverse((ObjectTraverser) (annotation_name, annotation_value) -> {
- if (TARGET_NUM_HITS.equals(annotation_name)){
+ if (TARGET_HITS.equals(annotation_name)){
weakAnd.setN((int)(annotation_value.asDouble()));
}
- if (SCORE_THRESHOLD.equals(annotation_name)){
+ if (TARGET_NUM_HITS.equals(annotation_name)) {
+ weakAnd.setN((int)(annotation_value.asDouble()));
+ }
+ if (SCORE_THRESHOLD.equals(annotation_name)) {
weakAnd.setScoreThreshold((int)(annotation_value.asDouble()));
}
});
@@ -662,7 +764,10 @@ public class SelectParser implements Parser {
HashMap<Integer, Inspector> children = childMap(value);
Preconditions.checkArgument(children.size() == 2, "Expected 2 arguments, got %s.", children.size());
- Integer target_num_hits= getIntegerAnnotation(TARGET_NUM_HITS, annotations, DEFAULT_TARGET_NUM_HITS);
+ Integer target_num_hits= getIntegerAnnotation(TARGET_HITS, annotations, null);
+ if (target_num_hits == null) {
+ target_num_hits= getIntegerAnnotation(TARGET_NUM_HITS, annotations, DEFAULT_TARGET_NUM_HITS);
+ }
WandItem out = new WandItem(children.get(0).asString(), target_num_hits);
@@ -1097,9 +1202,10 @@ public class SelectParser implements Parser {
private void connectItems() {
for (ConnectedItem entry : connectedItems) {
TaggableItem to = identifiedItems.get(entry.toId);
- Preconditions.checkNotNull(to,
- "Item '%s' was specified to connect to item with ID %s, which does not "
- + "exist in the query.", entry.fromItem, entry.toId);
+ if (to == null)
+ throw new IllegalArgumentException("Item '" + entry.fromItem +
+ "' was specified to connect to item with ID " + entry.toId +
+ ", which does not exist in the query.");
entry.fromItem.setConnectivity((Item) to, entry.weight);
}
}
diff --git a/container-search/src/main/java/com/yahoo/search/query/Sorting.java b/container-search/src/main/java/com/yahoo/search/query/Sorting.java
index 6518dcd5b6d..0a0ae65d524 100644
--- a/container-search/src/main/java/com/yahoo/search/query/Sorting.java
+++ b/container-search/src/main/java/com/yahoo/search/query/Sorting.java
@@ -3,6 +3,7 @@ package com.yahoo.search.query;
import com.ibm.icu.text.Collator;
import com.ibm.icu.util.ULocale;
+import com.yahoo.processing.IllegalInputException;
import com.yahoo.text.Utf8;
import java.nio.ByteBuffer;
@@ -88,7 +89,7 @@ public class Sorting implements Cloneable {
} else if (STRENGTH_IDENTICAL.equalsIgnoreCase(s)) {
strength = UcaSorter.Strength.IDENTICAL;
} else {
- throw new IllegalArgumentException("Unknown collation strength: '" + s + "'");
+ throw new IllegalInputException("Unknown collation strength: '" + s + "'");
}
sorter = new UcaSorter(sortString.substring(startPar+1, commaPos), sortString.substring(commaPos+1, commaopt), strength);
} else {
@@ -99,9 +100,9 @@ public class Sorting implements Cloneable {
}
} else {
if (funcName.isEmpty()) {
- throw new IllegalArgumentException("No sort function specified");
+ throw new IllegalInputException("No sort function specified");
} else {
- throw new IllegalArgumentException("Unknown sort function '" + funcName + "'");
+ throw new IllegalInputException("Unknown sort function '" + funcName + "'");
}
}
} else {
@@ -196,7 +197,7 @@ public class Sorting implements Cloneable {
if (legalAttributeName.matcher(fieldName).matches()) {
this.fieldName = fieldName;
} else {
- throw new IllegalArgumentException("Illegal attribute name '" + fieldName + "' for sorting. Requires '" + legalAttributeName.pattern() + "'");
+ throw new IllegalInputException("Illegal attribute name '" + fieldName + "' for sorting. Requires '" + legalAttributeName.pattern() + "'");
}
}
diff --git a/container-search/src/main/java/com/yahoo/search/query/context/QueryContext.java b/container-search/src/main/java/com/yahoo/search/query/context/QueryContext.java
index 1ba30275dc1..4fb9e1e9afa 100644
--- a/container-search/src/main/java/com/yahoo/search/query/context/QueryContext.java
+++ b/container-search/src/main/java/com/yahoo/search/query/context/QueryContext.java
@@ -38,10 +38,10 @@ public class QueryContext implements Cloneable {
owner.getModel().getExecution().trace().trace(message,traceLevel);
}
/**
- * Adds a key-value which will be logged to the access log for this query (by doing toString() on the value
+ * Adds a key-value which will be logged to the access log for this query (by doing toString() on the value).
* Multiple values may be set to the same key. A value cannot be removed once set.
*/
- public void logValue(String key,Object value) {
+ public void logValue(String key, Object value) {
owner.getModel().getExecution().trace().logValue(key, value.toString());
}
diff --git a/container-search/src/main/java/com/yahoo/search/query/parser/ParserEnvironment.java b/container-search/src/main/java/com/yahoo/search/query/parser/ParserEnvironment.java
index 9e53f9d8ea9..df96d314455 100644
--- a/container-search/src/main/java/com/yahoo/search/query/parser/ParserEnvironment.java
+++ b/container-search/src/main/java/com/yahoo/search/query/parser/ParserEnvironment.java
@@ -4,8 +4,7 @@ package com.yahoo.search.query.parser;
import com.yahoo.language.Linguistics;
import com.yahoo.language.simple.SimpleLinguistics;
import com.yahoo.prelude.IndexFacts;
-import com.yahoo.prelude.query.parser.SpecialTokenRegistry;
-import com.yahoo.prelude.query.parser.SpecialTokens;
+import com.yahoo.language.process.SpecialTokens;
import com.yahoo.search.Searcher;
import com.yahoo.search.searchchain.Execution;
@@ -19,7 +18,7 @@ public final class ParserEnvironment {
private IndexFacts indexFacts = new IndexFacts();
private Linguistics linguistics = new SimpleLinguistics();
- private SpecialTokens specialTokens = new SpecialTokens();
+ private SpecialTokens specialTokens = SpecialTokens.empty();
public IndexFacts getIndexFacts() {
return indexFacts;
diff --git a/container-search/src/main/java/com/yahoo/search/query/profile/AllReferencesQueryProfileVisitor.java b/container-search/src/main/java/com/yahoo/search/query/profile/AllReferencesQueryProfileVisitor.java
deleted file mode 100644
index eda8bf78b68..00000000000
--- a/container-search/src/main/java/com/yahoo/search/query/profile/AllReferencesQueryProfileVisitor.java
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.search.query.profile;
-
-import com.yahoo.processing.request.CompoundName;
-import com.yahoo.search.query.profile.types.FieldDescription;
-import com.yahoo.search.query.profile.types.QueryProfileFieldType;
-import com.yahoo.search.query.profile.types.QueryProfileType;
-
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * @author bratseth
- */
-final class AllReferencesQueryProfileVisitor extends PrefixQueryProfileVisitor {
-
- /** A map of query profile types */
- private Set<CompoundName> references = new HashSet<>();
-
- public AllReferencesQueryProfileVisitor(CompoundName prefix) {
- super(prefix);
- }
-
- @Override
- public void onValue(String name, Object value,
- DimensionBinding binding,
- QueryProfile owner,
- DimensionValues variant) {}
-
- @Override
- public void onQueryProfileInsidePrefix(QueryProfile profile,
- DimensionBinding binding,
- QueryProfile owner,
- DimensionValues variant) {
- references.add(currentPrefix);
- }
-
- /** Returns the values resulting from this visiting */
- public Set<CompoundName> getResult() { return references; }
-
- /** Returns false - we are not done until we have seen all */
- public boolean isDone() { return false; }
-
-}
diff --git a/container-search/src/main/java/com/yahoo/search/query/profile/AllTypesQueryProfileVisitor.java b/container-search/src/main/java/com/yahoo/search/query/profile/AllTypesQueryProfileVisitor.java
deleted file mode 100644
index 6bf17d70c70..00000000000
--- a/container-search/src/main/java/com/yahoo/search/query/profile/AllTypesQueryProfileVisitor.java
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.search.query.profile;
-
-import com.yahoo.processing.request.CompoundName;
-import com.yahoo.search.query.profile.types.FieldDescription;
-import com.yahoo.search.query.profile.types.QueryProfileFieldType;
-import com.yahoo.search.query.profile.types.QueryProfileType;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * @author bratseth
- */
-final class AllTypesQueryProfileVisitor extends PrefixQueryProfileVisitor {
-
- /** A map of query profile types */
- private Map<CompoundName, QueryProfileType> types = new HashMap<>();
-
- public AllTypesQueryProfileVisitor(CompoundName prefix) {
- super(prefix);
- }
-
- @Override
- public void onValue(String name, Object value,
- DimensionBinding binding,
- QueryProfile owner,
- DimensionValues variant) {}
-
-
- @Override
- public void onQueryProfileInsidePrefix(QueryProfile profile,
- DimensionBinding binding,
- QueryProfile owner,
- DimensionValues variant) {
- if (profile.getType() != null)
- addReachableTypes(currentPrefix, profile.getType());
- }
-
- private void addReachableTypes(CompoundName name, QueryProfileType type) {
- types.putIfAbsent(name, type); // Types visited earlier has precedence: profile.type overrides profile.inherited.type
- for (FieldDescription fieldDescription : type.fields().values()) {
- if ( ! (fieldDescription.getType() instanceof QueryProfileFieldType)) continue;
- QueryProfileFieldType fieldType = (QueryProfileFieldType)fieldDescription.getType();
- if (fieldType.getQueryProfileType() !=null) {
- addReachableTypes(name.append(fieldDescription.getName()), fieldType.getQueryProfileType());
- }
- }
- }
-
- /** Returns the values resulting from this visiting */
- public Map<CompoundName, QueryProfileType> getResult() { return types; }
-
- /** Returns false - we are not done until we have seen all */
- public boolean isDone() { return false; }
-
-}
diff --git a/container-search/src/main/java/com/yahoo/search/query/profile/AllUnoverridableQueryProfileVisitor.java b/container-search/src/main/java/com/yahoo/search/query/profile/AllUnoverridableQueryProfileVisitor.java
deleted file mode 100644
index 4bae6823500..00000000000
--- a/container-search/src/main/java/com/yahoo/search/query/profile/AllUnoverridableQueryProfileVisitor.java
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.search.query.profile;
-
-import com.yahoo.processing.request.CompoundName;
-
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * @author bratseth
- */
-final class AllUnoverridableQueryProfileVisitor extends PrefixQueryProfileVisitor {
-
- /** A map of query profile types */
- private Set<CompoundName> unoverridables = new HashSet<>();
-
- public AllUnoverridableQueryProfileVisitor(CompoundName prefix) {
- super(prefix);
- }
-
- @Override
- public void onValue(String name, Object value,
- DimensionBinding binding,
- QueryProfile owner,
- DimensionValues variant) {
- addUnoverridable(name, currentPrefix.append(name), binding, owner);
- }
-
- @Override
- public void onQueryProfileInsidePrefix(QueryProfile profile,
- DimensionBinding binding,
- QueryProfile owner,
- DimensionValues variant) {
- addUnoverridable(currentPrefix.last(), currentPrefix, binding, owner);
- }
-
- private void addUnoverridable(String localName,
- CompoundName fullName,
- DimensionBinding binding,
- QueryProfile owner) {
- if (owner == null) return;
-
- Boolean isOverridable = owner.isLocalOverridable(localName, binding);
- if (isOverridable != null && ! isOverridable)
- unoverridables.add(fullName);
- }
-
- /** Returns the values resulting from this visiting */
- public Set<CompoundName> getResult() { return unoverridables; }
-
- /** Returns false - we are not done until we have seen all */
- public boolean isDone() { return false; }
-
-}
diff --git a/container-search/src/main/java/com/yahoo/search/query/profile/AllValuesQueryProfileVisitor.java b/container-search/src/main/java/com/yahoo/search/query/profile/AllValuesQueryProfileVisitor.java
index f27500085e1..b24bf1195eb 100644
--- a/container-search/src/main/java/com/yahoo/search/query/profile/AllValuesQueryProfileVisitor.java
+++ b/container-search/src/main/java/com/yahoo/search/query/profile/AllValuesQueryProfileVisitor.java
@@ -13,11 +13,11 @@ import java.util.Map;
*/
final class AllValuesQueryProfileVisitor extends PrefixQueryProfileVisitor {
- private Map<String, ValueWithSource> values = new HashMap<>();
+ private final Map<String, ValueWithSource> values = new HashMap<>();
/* Lists all values starting at prefix */
- public AllValuesQueryProfileVisitor(CompoundName prefix) {
- super(prefix);
+ public AllValuesQueryProfileVisitor(CompoundName prefix, CompoundNameChildCache pathCache) {
+ super(prefix, pathCache);
}
@Override
@@ -26,7 +26,7 @@ final class AllValuesQueryProfileVisitor extends PrefixQueryProfileVisitor {
DimensionBinding binding,
QueryProfile owner,
DimensionValues variant) {
- putValue(localName, value, owner, variant);
+ putValue(localName, value, null, owner, variant, binding);
}
@Override
@@ -34,17 +34,29 @@ final class AllValuesQueryProfileVisitor extends PrefixQueryProfileVisitor {
DimensionBinding binding,
QueryProfile owner,
DimensionValues variant) {
- putValue("", profile.getValue(), owner, variant);
+ putValue("", profile.getValue(), profile, owner, variant, binding);
}
- private void putValue(String key, Object value, QueryProfile owner, DimensionValues variant) {
- if (value == null) return;
- CompoundName fullName = currentPrefix.append(key);
- if (fullName.isEmpty()) return; // Avoid putting a non-leaf (subtree) root in the list
- if (values.containsKey(fullName.toString())) return; // The first value encountered has priority
+ private void putValue(String key,
+ Object value,
+ QueryProfile profile,
+ QueryProfile owner,
+ DimensionValues variant,
+ DimensionBinding binding) {
+ CompoundName fullName = cache.append(currentPrefix, key);
+
+ ValueWithSource existing = values.get(fullName.toString());
+
+ // The first value encountered has priority and values have priority over profiles
+ if (existing != null && (existing.value() != null || value == null)) return;
+
+ Boolean isOverridable = owner != null ? owner.isLocalOverridable(key, binding) : null;
values.put(fullName.toString(), new ValueWithSource(value,
owner == null ? "anonymous" : owner.getSource(),
+ isOverridable != null && ! isOverridable,
+ profile != null,
+ profile == null ? null : profile.getType(),
variant));
}
diff --git a/container-search/src/main/java/com/yahoo/search/query/profile/BackedOverridableQueryProfile.java b/container-search/src/main/java/com/yahoo/search/query/profile/BackedOverridableQueryProfile.java
index 11864e60cec..9eab6629a45 100644
--- a/container-search/src/main/java/com/yahoo/search/query/profile/BackedOverridableQueryProfile.java
+++ b/container-search/src/main/java/com/yahoo/search/query/profile/BackedOverridableQueryProfile.java
@@ -22,7 +22,7 @@ import java.util.Map;
public class BackedOverridableQueryProfile extends OverridableQueryProfile implements Cloneable {
/** The backing read only query profile, or null if this is not backed */
- private QueryProfile backingProfile;
+ private final QueryProfile backingProfile;
/**
* Creates an overridable profile from the given backing profile. The backing profile will never be
@@ -31,7 +31,7 @@ public class BackedOverridableQueryProfile extends OverridableQueryProfile imple
* @param backingProfile the backing profile, which is assumed read only, never null
*/
public BackedOverridableQueryProfile(QueryProfile backingProfile) {
- Validator.ensureNotNull("An overridable query profile must be backed by a real query profile",backingProfile);
+ Validator.ensureNotNull("An overridable query profile must be backed by a real query profile", backingProfile);
setType(backingProfile.getType());
this.backingProfile = backingProfile;
}
@@ -49,7 +49,7 @@ public class BackedOverridableQueryProfile extends OverridableQueryProfile imple
protected Object localLookup(String localName, DimensionBinding dimensionBinding) {
Object valueInThis = super.localLookup(localName, dimensionBinding);
if (valueInThis != null) return valueInThis;
- return backingProfile.localLookup(localName, dimensionBinding);
+ return backingProfile.localLookup(localName, dimensionBinding.createFor(backingProfile.getDimensions()));
}
protected Boolean isLocalInstanceOverridable(String localName) {
@@ -88,23 +88,23 @@ public class BackedOverridableQueryProfile extends OverridableQueryProfile imple
}
@Override
- protected void visitVariants(boolean allowContent,QueryProfileVisitor visitor,DimensionBinding dimensionBinding) {
+ protected void visitVariants(boolean allowContent, QueryProfileVisitor visitor, DimensionBinding dimensionBinding) {
super.visitVariants(allowContent, visitor, dimensionBinding);
if (visitor.isDone()) return;
- backingProfile.visitVariants(allowContent, visitor, dimensionBinding);
+ backingProfile.visitVariants(allowContent, visitor, dimensionBinding.createFor(backingProfile.getDimensions()));
}
@Override
- protected void visitInherited(boolean allowContent,QueryProfileVisitor visitor,DimensionBinding dimensionBinding, QueryProfile owner) {
- super.visitInherited(allowContent,visitor,dimensionBinding, owner);
+ protected void visitInherited(boolean allowContent, QueryProfileVisitor visitor, DimensionBinding dimensionBinding, QueryProfile owner) {
+ super.visitInherited(allowContent, visitor, dimensionBinding, owner);
if (visitor.isDone()) return;
- backingProfile.visitInherited(allowContent,visitor,dimensionBinding,owner);
+ backingProfile.visitInherited(allowContent, visitor, dimensionBinding.createFor(backingProfile.getDimensions()), owner);
}
/** Returns a value from the content of this: The value in this, or the value from the backing if not set in this */
protected Object getContent(String localKey) {
- Object value=super.getContent(localKey);
- if (value!=null) return value;
+ Object value = super.getContent(localKey);
+ if (value != null) return value;
return backingProfile.getContent(localKey);
}
@@ -113,19 +113,19 @@ public class BackedOverridableQueryProfile extends OverridableQueryProfile imple
* All the values in this, and all values in the backing where an overriding value is not set in this
*/
@Override
- protected Map<String,Object> getContent() {
- Map<String,Object> thisContent=super.getContent();
- Map<String,Object> backingContent=backingProfile.getContent();
+ protected Map<String, Object> getContent() {
+ Map<String,Object> thisContent = super.getContent();
+ Map<String,Object> backingContent = backingProfile.getContent();
if (thisContent.isEmpty()) return backingContent; // Shortcut
if (backingContent.isEmpty()) return thisContent; // Shortcut
- Map<String,Object> content=new HashMap<>(backingContent);
+ Map<String, Object> content = new HashMap<>(backingContent);
content.putAll(thisContent);
return content;
}
@Override
public String toString() {
- return "overridable wrapper of " + backingProfile.toString();
+ return "overridable wrapper of " + backingProfile;
}
@Override
diff --git a/container-search/src/main/java/com/yahoo/search/query/profile/CompoundNameChildCache.java b/container-search/src/main/java/com/yahoo/search/query/profile/CompoundNameChildCache.java
new file mode 100644
index 00000000000..4163e45ae61
--- /dev/null
+++ b/container-search/src/main/java/com/yahoo/search/query/profile/CompoundNameChildCache.java
@@ -0,0 +1,24 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.search.query.profile;
+
+import com.yahoo.processing.request.CompoundName;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Cache for compound names created through {@link CompoundName#append(String)}.
+ * Creating new {@link CompoundName}s can be expensive, and since they are immutable, they
+ * are safe to cache and reuse. Use this if you will create <em>a lot</em> of them, by appending suffixes.
+ *
+ * @author jonmv
+ */
+public final class CompoundNameChildCache {
+
+ private final Map<CompoundName, Map<String, CompoundName>> cache = new HashMap<>();
+
+ public CompoundName append(CompoundName prefix, String suffix) {
+ return cache.computeIfAbsent(prefix, __ -> new HashMap<>()).computeIfAbsent(suffix, prefix::append);
+ }
+
+}
diff --git a/container-search/src/main/java/com/yahoo/search/query/profile/DimensionBinding.java b/container-search/src/main/java/com/yahoo/search/query/profile/DimensionBinding.java
index 50bd2c58da8..43462e8f327 100644
--- a/container-search/src/main/java/com/yahoo/search/query/profile/DimensionBinding.java
+++ b/container-search/src/main/java/com/yahoo/search/query/profile/DimensionBinding.java
@@ -2,11 +2,9 @@
package com.yahoo.search.query.profile;
import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
/**
* An immutable, binding of a list of dimensions to dimension values
@@ -22,25 +20,25 @@ public class DimensionBinding {
private DimensionValues values;
/** The binding from those dimensions to values, and possibly other values */
- private Map<String, String> context; // TODO: This is not needed any more
+ private Map<String, String> context;
public static final DimensionBinding nullBinding =
- new DimensionBinding(Collections.unmodifiableList(Collections.emptyList()), DimensionValues.empty, null);
+ new DimensionBinding(List.of(), DimensionValues.empty, null);
public static final DimensionBinding invalidBinding =
- new DimensionBinding(Collections.unmodifiableList(Collections.emptyList()), DimensionValues.empty, null);
+ new DimensionBinding(List.of(), DimensionValues.empty, null);
/** Whether the value array contains only nulls */
- private boolean containsAllNulls;
+ private final boolean containsAllNulls;
// NOTE: Map must be ordered
- public static DimensionBinding createFrom(Map<String,String> values) {
+ public static DimensionBinding createFrom(Map<String, String> values) {
return createFrom(new ArrayList<>(values.keySet()), values);
}
/** Creates a binding from a variant and a context. Any of the arguments may be null. */
// NOTE: Map must be ordered
- public static DimensionBinding createFrom(List<String> dimensions, Map<String,String> context) {
+ public static DimensionBinding createFrom(List<String> dimensions, Map<String, String> context) {
if (dimensions == null || dimensions.size() == 0) {
if (context == null) return nullBinding;
if (dimensions == null) return new DimensionBinding(null, DimensionValues.empty, context); // Null, but must preserve context
@@ -51,7 +49,7 @@ public class DimensionBinding {
/** Creates a binding from a variant and a context. Any of the arguments may be null. */
public static DimensionBinding createFrom(List<String> dimensions, DimensionValues dimensionValues) {
- if (dimensionValues==null || dimensionValues == DimensionValues.empty) return nullBinding;
+ if (dimensionValues == null || dimensionValues == DimensionValues.empty) return nullBinding;
// If null, preserve raw material for creating a context later (in createFor)
if (dimensions == null) return new DimensionBinding(null, dimensionValues, null);
@@ -61,14 +59,13 @@ public class DimensionBinding {
/** Returns a binding for a (possibly) new set of variants. Variants may be null, but not bindings */
public DimensionBinding createFor(List<String> newDimensions) {
- if (newDimensions==null) return this; // Note: Not necessarily null - if no new variants then keep the existing binding
- // if (this.context==null && values.length==0) return nullBinding; // No data from which to create a non-null binding
- if (this.dimensions==newDimensions) return this; // Avoid creating a new object if the dimensions are the same
-
- Map<String,String> context=this.context;
- if (context==null)
- context=this.values.asContext(this.dimensions !=null ? this.dimensions : newDimensions);
- return new DimensionBinding(newDimensions,extractDimensionValues(newDimensions,context),context);
+ if (newDimensions == null) return this; // Note: Not necessarily null - if no new variants then keep the existing binding
+ if (this.dimensions == newDimensions) return this; // Avoid creating a new object if the dimensions are the same
+
+ Map<String,String> context = this.context;
+ if (context == null)
+ context = this.values.asContext(this.dimensions != null ? this.dimensions : newDimensions);
+ return new DimensionBinding(newDimensions, extractDimensionValues(newDimensions, context), context);
}
/**
@@ -76,20 +73,20 @@ public class DimensionBinding {
* The array will not be modified. The context is needed in order to convert this binding to another
* given another set of variant dimensions.
*/
- private DimensionBinding(List<String> dimensions, DimensionValues values, Map<String,String> context) {
- this.dimensions=dimensions;
- this.values=values;
+ private DimensionBinding(List<String> dimensions, DimensionValues values, Map<String, String> context) {
+ this.dimensions = dimensions;
+ this.values = values;
this.context = context;
- containsAllNulls=values.isEmpty();
+ containsAllNulls = values.isEmpty();
}
/** Returns a read-only list of the dimensions of this. This value is undefined if this isNull() */
public List<String> getDimensions() { return dimensions; }
/** Returns a context created from the dimensions and values of this */
- public Map<String,String> getContext() {
- if (context !=null) return context;
- context =values.asContext(dimensions);
+ public Map<String, String> getContext() {
+ if (context != null) return context;
+ context = values.asContext(dimensions);
return context;
}
@@ -102,7 +99,7 @@ public class DimensionBinding {
public DimensionValues getValues() { return values; }
/** Returns true only if this binding is null (contains no values for its dimensions (if any) */
- public boolean isNull() { return dimensions==null || containsAllNulls; }
+ public boolean isNull() { return dimensions == null || containsAllNulls; }
/**
* Returns an array of the dimension values corresponding to the dimensions of this from the given context,
@@ -110,10 +107,10 @@ public class DimensionBinding {
* Dimensions which are not set in this context get a null value.
*/
private static DimensionValues extractDimensionValues(List<String> dimensions, Map<String,String> context) {
- String[] dimensionValues=new String[dimensions.size()];
- if (context==null || context.size()==0) return DimensionValues.createFrom(dimensionValues);
- for (int i=0; i<dimensions.size(); i++)
- dimensionValues[i]=context.get(dimensions.get(i));
+ String[] dimensionValues = new String[dimensions.size()];
+ if (context == null || context.size() == 0) return DimensionValues.createFrom(dimensionValues);
+ for (int i = 0; i < dimensions.size(); i++)
+ dimensionValues[i] = context.get(dimensions.get(i));
return DimensionValues.createFrom(dimensionValues);
}
@@ -127,86 +124,57 @@ public class DimensionBinding {
*
* @return the combined binding, or the special invalidBinding if these two bindings are incompatible
*/
- public DimensionBinding combineWith(DimensionBinding binding) {
- List<String> combinedDimensions = combineDimensions(getDimensions(), binding.getDimensions());
- if (combinedDimensions == null) return invalidBinding;
-
- // not runtime, so assume we don't need to preserve values outside the dimensions
- Map<String, String> combinedValues = combineValues(getContext(), binding.getContext());
- if (combinedValues == null) return invalidBinding;
-
- return DimensionBinding.createFrom(combinedDimensions, combinedValues);
- }
-
- /** Returns the binding of this (dimension->value) as a map */
- private Map<String, String> asMap() {
- Map<String, String> map = new LinkedHashMap<>();
- for (int i = 0; i < Math.min(dimensions.size(), values.size()); i++) {
- if (values.getValues()[i] != null)
- map.put(dimensions.get(i), values.getValues()[i]);
- }
- return map;
- }
-
- /**
- * Returns a combined list of dimensions from two separate lists,
- * or null if they are incompatible.
- * This is to combine two lists to one such that the partial order in both is preserved
- * (or return null if impossible).
- */
- private List<String> combineDimensions(List<String> d1, List<String> d2) {
- List<String> combined = new ArrayList<>();
- int d1Index = 0, d2Index=0;
- while (d1Index < d1.size() && d2Index < d2.size()) {
- if (d1.get(d1Index).equals(d2.get(d2Index))) { // agreement on next element
- combined.add(d1.get(d1Index));
- d1Index++;
- d2Index++;
+ public DimensionBinding combineWith(DimensionBinding other) {
+ List<String> d1 = getDimensions();
+ List<String> d2 = other.getDimensions();
+ DimensionValues v1 = getValues();
+ DimensionValues v2 = other.getValues();
+ List<String> dimensions = new ArrayList<>();
+ List<String> values = new ArrayList<>();
+ int i1 = 0, i2 = 0;
+ while (i1 < d1.size() && i2 < d2.size()) {
+ if (d1.get(i1).equals(d2.get(i2))) { // agreement on next dimension
+ String s1 = v1.get(i1), s2 = v2.get(i2);
+ if (s1 == null)
+ values.add(s2);
+ else if (s2 == null || s1.equals(s2))
+ values.add(s1);
+ else
+ return invalidBinding; // disagreement on next value
+
+ dimensions.add(d1.get(i1));
+ i1++;
+ i2++;
}
- else if ( ! d2.contains(d1.get(d1Index))) { // next in d1 is independent from d2
- combined.add(d1.get(d1Index++));
+ else if ( ! d2.contains(d1.get(i1))) { // next dimension in d1 is independent from d2
+ dimensions.add(d1.get(i1));
+ values.add(v1.get(i1));
+ i1++;
}
- else if ( ! d1.contains(d2.get(d2Index))) { // next in d2 is independent from d1
- combined.add(d2.get(d2Index++));
+ else if ( ! d1.contains(d2.get(i2))) { // next dimension in d2 is independent from d1
+ dimensions.add(d2.get(i2));
+ values.add(v2.get(i2));
+ i2++;
}
else {
- return null; // no independent and no agreement
+ return invalidBinding; // not independent and no agreement
}
}
- if (d1Index < d1.size())
- combined.addAll(d1.subList(d1Index, d1.size()));
- else if (d2Index < d2.size())
- combined.addAll(d2.subList(d2Index, d2.size()));
-
- return combined;
- }
-
- /**
- * Returns a combined map of dimension values from two separate maps,
- * or null if they are incompatible.
- */
- private Map<String, String> combineValues(Map<String, String> m1, Map<String, String> m2) {
- Map<String, String> combinedValues = new LinkedHashMap<>(m1);
- for (Map.Entry<String, String> m2Entry : m2.entrySet()) {
- if (m2Entry.getValue() == null) continue;
- String m1Value = m1.get(m2Entry.getKey());
- if (m1Value != null && ! m1Value.equals(m2Entry.getValue()))
- return null; // conflicting values of a key
- combinedValues.put(m2Entry.getKey(), m2Entry.getValue());
+ while (i1 < d1.size()) {
+ dimensions.add(d1.get(i1));
+ values.add(v1.get(i1));
+ i1++;
+ }
+ while (i2 < d2.size()) {
+ dimensions.add(d2.get(i2));
+ values.add(v2.get(i2));
+ i2++;
}
- return combinedValues;
- }
- private boolean intersects(List<String> l1, List<String> l2) {
- for (String l1Item : l1)
- if (l2.contains(l1Item))
- return true;
- return false;
+ return DimensionBinding.createFrom(dimensions, DimensionValues.createFrom(values.toArray(new String[0])));
}
- /**
- * Returns true if <code>this == invalidBinding</code>
- */
+ /** Returns true if this == invalidBinding */
public boolean isInvalid() { return this == invalidBinding; }
@Override
@@ -226,7 +194,7 @@ public class DimensionBinding {
/** Two bindings are equal if they contain the same dimensions and the same non-null values */
@Override
public boolean equals(Object o) {
- if (o==this) return true;
+ if (o == this) return true;
if (! (o instanceof DimensionBinding)) return false;
DimensionBinding other = (DimensionBinding)o;
if ( ! this.dimensions.equals(other.dimensions)) return false;
@@ -236,7 +204,7 @@ public class DimensionBinding {
@Override
public int hashCode() {
- return dimensions.hashCode() + 17 * values.hashCode();
+ return Objects.hash(dimensions, values);
}
}
diff --git a/container-search/src/main/java/com/yahoo/search/query/profile/DimensionValues.java b/container-search/src/main/java/com/yahoo/search/query/profile/DimensionValues.java
index 9eb50c0f72e..f3c4548c491 100644
--- a/container-search/src/main/java/com/yahoo/search/query/profile/DimensionValues.java
+++ b/container-search/src/main/java/com/yahoo/search/query/profile/DimensionValues.java
@@ -21,7 +21,7 @@ public class DimensionValues implements Comparable<DimensionValues> {
public static final DimensionValues empty = new DimensionValues(new String[] {});
public static DimensionValues createFrom(String[] values) {
- if (values==null || values.length==0 || containsAllNulls(values)) return empty;
+ if (values == null || values.length == 0 || containsAllNulls(values)) return empty;
return new DimensionValues(values);
}
@@ -34,10 +34,10 @@ public class DimensionValues implements Comparable<DimensionValues> {
*/
private DimensionValues(String[] values) {
if (values == null) throw new NullPointerException("Dimension values cannot be null");
- this.values=Arrays.copyOf(values, values.length);
+ this.values = Arrays.copyOf(values, values.length);
}
- /** Returns true if this is has the same value every place it has a value as the givenValues. */
+ /** Returns true if this is has the same value every place it has a value as the given values. */
public boolean matches(DimensionValues givenValues) {
for (int i = 0; i < this.size() || i < givenValues.size() ; i++)
if ( ! matches(this.get(i), givenValues.get(i)))
@@ -73,14 +73,14 @@ public class DimensionValues implements Comparable<DimensionValues> {
/** Helper method which uses compareTo to return whether this is most specific */
public boolean isMoreSpecificThan(DimensionValues other) {
- return this.compareTo(other)<0;
+ return this.compareTo(other) < 0;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if ( ! (o instanceof DimensionValues)) return false;
- DimensionValues other = (DimensionValues)o;
+ DimensionValues other = (DimensionValues) o;
for (int i = 0; i < this.size() || i < other.size(); i++) {
if (get(i) == null) {
if (other.get(i) != null) return false;
diff --git a/container-search/src/main/java/com/yahoo/search/query/profile/ModelObjectMap.java b/container-search/src/main/java/com/yahoo/search/query/profile/ModelObjectMap.java
index 4dc9ade62e5..e32d2dc226d 100644
--- a/container-search/src/main/java/com/yahoo/search/query/profile/ModelObjectMap.java
+++ b/container-search/src/main/java/com/yahoo/search/query/profile/ModelObjectMap.java
@@ -22,8 +22,7 @@ public class ModelObjectMap extends PropertyMap {
*/
@Override
protected boolean shouldSet(CompoundName name, Object value) {
- if (value == null) return true;
- return ! FieldType.isLegalFieldValue(value);
+ return value != null && ! FieldType.isLegalFieldValue(value);
}
}
diff --git a/container-search/src/main/java/com/yahoo/search/query/profile/PrefixQueryProfileVisitor.java b/container-search/src/main/java/com/yahoo/search/query/profile/PrefixQueryProfileVisitor.java
index 690a48f8124..b53fc4f96f2 100644
--- a/container-search/src/main/java/com/yahoo/search/query/profile/PrefixQueryProfileVisitor.java
+++ b/container-search/src/main/java/com/yahoo/search/query/profile/PrefixQueryProfileVisitor.java
@@ -3,6 +3,11 @@ package com.yahoo.search.query.profile;
import com.yahoo.processing.request.CompoundName;
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.Map;
+
/**
* A query profile visitor which keeps track of name prefixes and can skip values outside a given prefix
*
@@ -10,18 +15,22 @@ import com.yahoo.processing.request.CompoundName;
*/
abstract class PrefixQueryProfileVisitor extends QueryProfileVisitor {
+ protected final CompoundNameChildCache cache;
+
/** Only call onValue/onQueryProfile for nodes having this prefix */
private final CompoundName prefix;
/** The current prefix, relative to prefix. */
protected CompoundName currentPrefix = CompoundName.empty;
+ private final Deque<CompoundName> currentPrefixes = new ArrayDeque<>();
private int prefixComponentIndex = -1;
- public PrefixQueryProfileVisitor(CompoundName prefix) {
+ public PrefixQueryProfileVisitor(CompoundName prefix, CompoundNameChildCache cache) {
if (prefix == null)
prefix = CompoundName.empty;
this.prefix = prefix;
+ this.cache = cache;
}
@Override
@@ -40,18 +49,19 @@ abstract class PrefixQueryProfileVisitor extends QueryProfileVisitor {
@Override
public final boolean enter(String name) {
- prefixComponentIndex++;
- if (prefixComponentIndex-1 < prefix.size()) return true; // we're in the given prefix, which should not be included in the name
- currentPrefix = currentPrefix.append(name);
+ if (prefixComponentIndex++ < prefix.size()) return true; // we're in the given prefix, which should not be included in the name
+ if ( ! name.isEmpty()) {
+ currentPrefixes.push(currentPrefix);
+ currentPrefix = cache.append(currentPrefix, name);
+ }
return true;
}
@Override
public final void leave(String name) {
- prefixComponentIndex--;
- if (prefixComponentIndex < prefix.size()) return; // we're in the given prefix, which should not be included in the name
- if ( ! name.isEmpty() && ! currentPrefix.isEmpty())
- currentPrefix = currentPrefix.first(currentPrefix.size() - 1);
+ if (--prefixComponentIndex < prefix.size()) return; // we're in the given prefix, which should not be included in the name
+ if ( ! name.isEmpty())
+ currentPrefix = currentPrefixes.pop();
}
/**
diff --git a/container-search/src/main/java/com/yahoo/search/query/profile/QueryProfile.java b/container-search/src/main/java/com/yahoo/search/query/profile/QueryProfile.java
index 7ae18f96d86..d3da2fd076a 100644
--- a/container-search/src/main/java/com/yahoo/search/query/profile/QueryProfile.java
+++ b/container-search/src/main/java/com/yahoo/search/query/profile/QueryProfile.java
@@ -4,6 +4,7 @@ package com.yahoo.search.query.profile;
import com.google.common.collect.ImmutableList;
import com.yahoo.component.ComponentId;
import com.yahoo.component.provider.FreezableSimpleComponent;
+import com.yahoo.processing.IllegalInputException;
import com.yahoo.processing.request.CompoundName;
import com.yahoo.processing.request.Properties;
import com.yahoo.search.query.profile.compiled.CompiledQueryProfile;
@@ -17,7 +18,6 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -146,7 +146,7 @@ public class QueryProfile extends FreezableSimpleComponent implements Cloneable
* Returns the content fields declared in this (i.e not including those inherited) as a read-only map.
* @throws IllegalStateException if this is frozen
*/
- public Map<String,Object> declaredContent() {
+ public Map<String, Object> declaredContent() {
ensureNotFrozen();
return content.unmodifiableMap();
}
@@ -202,6 +202,14 @@ public class QueryProfile extends FreezableSimpleComponent implements Cloneable
}
/**
+ * Sets the overridability of a field in this profile,
+ * this overrides the corresponding setting in the type (if any)
+ */
+ public final void setOverridable(String fieldName, boolean overridable, DimensionValues dimensionValues) {
+ setOverridable(new CompoundName(fieldName), overridable, DimensionBinding.createFrom(getDimensions(), dimensionValues));
+ }
+
+ /**
* Return all objects that start with the given prefix path using no context. Use "" to list all.
* <p>
* For example, if {a.d =&gt; "a.d-value" ,a.e =&gt; "a.e-value", b.d =&gt; "b.d-value", then calling listValues("a")
@@ -233,7 +241,7 @@ public class QueryProfile extends FreezableSimpleComponent implements Cloneable
* For example, if {a.d =&gt; "a.d-value" ,a.e =&gt; "a.e-value", b.d =&gt; "b.d-value", then calling listValues("a")
* will return {"d" =&gt; "a.d-value","e" =&gt; "a.e-value"}
*/
- public final Map<String, Object> listValues(CompoundName prefix, Map<String,String> context) {
+ public final Map<String, Object> listValues(CompoundName prefix, Map<String, String> context) {
return listValues(prefix, context, null);
}
@@ -257,42 +265,14 @@ public class QueryProfile extends FreezableSimpleComponent implements Cloneable
}
AllValuesQueryProfileVisitor visitValues(CompoundName prefix, Map<String, String> context) {
- DimensionBinding dimensionBinding = DimensionBinding.createFrom(getDimensions(), context);
-
- AllValuesQueryProfileVisitor visitor = new AllValuesQueryProfileVisitor(prefix);
- accept(visitor, dimensionBinding, null);
- return visitor;
- }
-
- /**
- * Lists types reachable from this, indexed by the prefix having that type.
- * If this is itself typed, this' type will be included with an empty prefix
- */
- public Map<CompoundName, QueryProfileType> listTypes(CompoundName prefix, Map<String, String> context) {
- DimensionBinding dimensionBinding = DimensionBinding.createFrom(getDimensions(), context);
- AllTypesQueryProfileVisitor visitor = new AllTypesQueryProfileVisitor(prefix);
- accept(visitor, dimensionBinding, null);
- return visitor.getResult();
- }
-
- /**
- * Lists references reachable from this.
- */
- Set<CompoundName> listReferences(CompoundName prefix, Map<String, String> context) {
- DimensionBinding dimensionBinding = DimensionBinding.createFrom(getDimensions(),context);
- AllReferencesQueryProfileVisitor visitor = new AllReferencesQueryProfileVisitor(prefix);
- accept(visitor,dimensionBinding,null);
- return visitor.getResult();
+ return visitValues(prefix, context, new CompoundNameChildCache());
}
- /**
- * Lists every entry (value or reference) reachable from this which is not overridable
- */
- Set<CompoundName> listUnoverridable(CompoundName prefix, Map<String, String> context) {
- DimensionBinding dimensionBinding = DimensionBinding.createFrom(getDimensions(),context);
- AllUnoverridableQueryProfileVisitor visitor = new AllUnoverridableQueryProfileVisitor(prefix);
- accept(visitor, dimensionBinding, null);
- return visitor.getResult();
+ AllValuesQueryProfileVisitor visitValues(CompoundName prefix, Map<String, String> context,
+ CompoundNameChildCache pathCache) {
+ AllValuesQueryProfileVisitor visitor = new AllValuesQueryProfileVisitor(prefix, pathCache);
+ accept(visitor, DimensionBinding.createFrom(getDimensions(), context), null);
+ return visitor;
}
/**
@@ -371,11 +351,11 @@ public class QueryProfile extends FreezableSimpleComponent implements Cloneable
* @throws IllegalArgumentException if the given name is illegal given the types of this or any nested query profile
* @throws IllegalStateException if this query profile is frozen
*/
- public final void set(CompoundName name, Object value, Map<String,String> context, QueryProfileRegistry registry) {
+ public final void set(CompoundName name, Object value, Map<String, String> context, QueryProfileRegistry registry) {
set(name, value, DimensionBinding.createFrom(getDimensions(), context), registry);
}
- public final void set(String name, Object value, Map<String,String> context, QueryProfileRegistry registry) {
+ public final void set(String name, Object value, Map<String, String> context, QueryProfileRegistry registry) {
set(new CompoundName(name), value, DimensionBinding.createFrom(getDimensions(), context), registry);
}
@@ -396,7 +376,7 @@ public class QueryProfile extends FreezableSimpleComponent implements Cloneable
* @throws IllegalArgumentException if the given name is illegal given the types of this or any nested query profile
* @throws IllegalStateException if this query profile is frozen
*/
- public final void set(String name,Object value, DimensionValues dimensionValues, QueryProfileRegistry registry) {
+ public final void set(String name, Object value, DimensionValues dimensionValues, QueryProfileRegistry registry) {
set(new CompoundName(name), value, DimensionBinding.createFrom(getDimensions(), dimensionValues), registry);
}
@@ -482,7 +462,7 @@ public class QueryProfile extends FreezableSimpleComponent implements Cloneable
setNode(name, value, null, binding, registry);
}
catch (IllegalArgumentException e) {
- throw new IllegalArgumentException("Could not set '" + name + "' to '" + value + "'", e);
+ throw new IllegalInputException("Could not set '" + name + "' to '" + value + "'", e);
}
}
@@ -509,9 +489,14 @@ public class QueryProfile extends FreezableSimpleComponent implements Cloneable
*/
Boolean isLocalOverridable(String localName, DimensionBinding binding) {
if (localLookup(localName, binding) == null) return null; // Not set
+ if ( ! binding.isNull() && getVariants() != null) {
+ Boolean variantIsOverriable = getVariants().isOverridable(localName, binding.getValues());
+ if (variantIsOverriable != null)
+ return variantIsOverriable;
+ }
Boolean isLocalInstanceOverridable = isLocalInstanceOverridable(localName);
if (isLocalInstanceOverridable != null)
- return isLocalInstanceOverridable.booleanValue();
+ return isLocalInstanceOverridable;
if (type != null) return type.isOverridable(localName);
return true;
}
@@ -564,7 +549,7 @@ public class QueryProfile extends FreezableSimpleComponent implements Cloneable
if (visitor.isDone()) return;
if (allowContent) {
- visitContent(visitor,dimensionBinding);
+ visitContent(visitor, dimensionBinding);
if (visitor.isDone()) return;
}
@@ -601,7 +586,7 @@ public class QueryProfile extends FreezableSimpleComponent implements Cloneable
visitor.acceptValue(contentKey, getContent(contentKey), dimensionBinding, this, null);
}
else { // get all content in this
- for (Map.Entry<String,Object> entry : getContent().entrySet()) {
+ for (Map.Entry<String, Object> entry : getContent().entrySet()) {
visitor.acceptValue(entry.getKey(), entry.getValue(), dimensionBinding, this, null);
if (visitor.isDone()) return;
}
@@ -614,12 +599,12 @@ public class QueryProfile extends FreezableSimpleComponent implements Cloneable
}
/** Returns all the content from this as an unmodifiable map */
- protected Map<String,Object> getContent() {
+ protected Map<String, Object> getContent() {
return content.unmodifiableMap();
}
/** Sets the value of a node in <i>this</i> profile - the local name given must not be nested (contain dots) */
- protected QueryProfile setLocalNode(String localName, Object value,QueryProfileType parentType,
+ protected QueryProfile setLocalNode(String localName, Object value, QueryProfileType parentType,
DimensionBinding dimensionBinding, QueryProfileRegistry registry) {
if (parentType != null && type == null && ! isFrozen())
type = parentType;
@@ -637,9 +622,10 @@ public class QueryProfile extends FreezableSimpleComponent implements Cloneable
static Object combineValues(Object newValue, Object existingValue) {
if (newValue instanceof QueryProfile) {
QueryProfile newProfile = (QueryProfile)newValue;
- if ( existingValue == null || ! (existingValue instanceof QueryProfile)) {
- if (!isModifiable(newProfile))
+ if ( ! (existingValue instanceof QueryProfile)) {
+ if ( ! isModifiable(newProfile)) {
newProfile = new BackedOverridableQueryProfile(newProfile); // Make the query profile reference overridable
+ }
newProfile.value = existingValue;
return newProfile;
}
@@ -717,15 +703,15 @@ public class QueryProfile extends FreezableSimpleComponent implements Cloneable
FieldDescription fieldDescription = type.getField(localName);
if (fieldDescription == null) {
if (type.isStrict())
- throw new IllegalArgumentException("'" + localName + "' is not declared in " + type + ", and the type is strict");
+ throw new IllegalInputException("'" + localName + "' is not declared in " + type + ", and the type is strict");
return value;
}
if (registry == null && (fieldDescription.getType() instanceof QueryProfileFieldType))
- throw new IllegalArgumentException("A registry was not passed: Query profile references is not supported");
+ throw new IllegalInputException("A registry was not passed: Query profile references is not supported");
Object convertedValue = fieldDescription.getType().convertFrom(value, registry);
if (convertedValue == null)
- throw new IllegalArgumentException("'" + value + "' is not a " + fieldDescription.getType().toInstanceDescription());
+ throw new IllegalInputException("'" + value + "' is not a " + fieldDescription.getType().toInstanceDescription());
return convertedValue;
}
@@ -759,14 +745,19 @@ public class QueryProfile extends FreezableSimpleComponent implements Cloneable
* Sets the overridability of a field in this profile,
* this overrides the corresponding setting in the type (if any)
*/
- private void setOverridable(CompoundName fieldName,boolean overridable,DimensionBinding dimensionBinding) {
+ private void setOverridable(CompoundName fieldName, boolean overridable, DimensionBinding dimensionBinding) {
QueryProfile parent = lookupParentExact(fieldName, true, dimensionBinding);
- if (parent.overridable == null)
- parent.overridable = new HashMap<>();
- parent.overridable.put(fieldName.last(), overridable);
+ if (dimensionBinding.isNull()) {
+ if (parent.overridable == null)
+ parent.overridable = new HashMap<>();
+ parent.overridable.put(fieldName.last(), overridable);
+ }
+ else {
+ variants.setOverridable(fieldName.last(), overridable, dimensionBinding.getValues());
+ }
}
- /** Sets a value to a (possibly non-local) node. The parent query profile holding the value is returned */
+ /** Sets a value to a (possibly non-local) node. */
private void setNode(CompoundName name, Object value, QueryProfileType parentType,
DimensionBinding dimensionBinding, QueryProfileRegistry registry) {
ensureNotFrozen();
@@ -840,13 +831,10 @@ public class QueryProfile extends FreezableSimpleComponent implements Cloneable
value = convertToSubstitutionString(value);
if (dimensionBinding.isNull()) {
- Object combinedValue;
- if (value instanceof QueryProfile)
- combinedValue = combineValues(value, content == null ? null : content.get(localName));
- else
- combinedValue = combineValues(value, localLookup(localName, dimensionBinding));
-
- if (combinedValue!=null)
+ Object combinedValue = value instanceof QueryProfile
+ ? combineValues(value, content == null ? null : content.get(localName))
+ : combineValues(value, localLookup(localName, dimensionBinding));
+ if (combinedValue != null)
content.put(localName, combinedValue);
}
else {
diff --git a/container-search/src/main/java/com/yahoo/search/query/profile/QueryProfileCompiler.java b/container-search/src/main/java/com/yahoo/search/query/profile/QueryProfileCompiler.java
index cffe941b912..29a997a75dd 100644
--- a/container-search/src/main/java/com/yahoo/search/query/profile/QueryProfileCompiler.java
+++ b/container-search/src/main/java/com/yahoo/search/query/profile/QueryProfileCompiler.java
@@ -2,15 +2,23 @@
package com.yahoo.search.query.profile;
import com.yahoo.processing.request.CompoundName;
+import com.yahoo.search.query.profile.compiled.Binding;
import com.yahoo.search.query.profile.compiled.CompiledQueryProfile;
import com.yahoo.search.query.profile.compiled.CompiledQueryProfileRegistry;
import com.yahoo.search.query.profile.compiled.DimensionalMap;
import com.yahoo.search.query.profile.compiled.ValueWithSource;
import com.yahoo.search.query.profile.types.QueryProfileType;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.function.BiConsumer;
import java.util.logging.Logger;
/**
@@ -24,34 +32,38 @@ public class QueryProfileCompiler {
public static CompiledQueryProfileRegistry compile(QueryProfileRegistry input) {
CompiledQueryProfileRegistry output = new CompiledQueryProfileRegistry(input.getTypeRegistry());
- for (QueryProfile inputProfile : input.allComponents()) {
+ for (QueryProfile inputProfile : input.allComponents())
output.register(compile(inputProfile, output));
- }
return output;
}
public static CompiledQueryProfile compile(QueryProfile in, CompiledQueryProfileRegistry registry) {
try {
- DimensionalMap.Builder<CompoundName, ValueWithSource> values = new DimensionalMap.Builder<>();
- DimensionalMap.Builder<CompoundName, QueryProfileType> types = new DimensionalMap.Builder<>();
- DimensionalMap.Builder<CompoundName, Object> references = new DimensionalMap.Builder<>();
- DimensionalMap.Builder<CompoundName, Object> unoverridables = new DimensionalMap.Builder<>();
+ DimensionalMap.Builder<ValueWithSource> values = new DimensionalMap.Builder<>();
+ DimensionalMap.Builder<QueryProfileType> types = new DimensionalMap.Builder<>();
+ DimensionalMap.Builder<Object> references = new DimensionalMap.Builder<>();
+ DimensionalMap.Builder<Object> unoverridables = new DimensionalMap.Builder<>();
// Resolve values for each existing variant and combine into a single data structure
Set<DimensionBindingForPath> variants = collectVariants(CompoundName.empty, in, DimensionBinding.nullBinding);
variants.add(new DimensionBindingForPath(DimensionBinding.nullBinding, CompoundName.empty)); // if this contains no variants
- log.fine(() -> "Compiling " + in.toString() + " having " + variants.size() + " variants");
- for (DimensionBindingForPath variant : variants) {
- log.finer(() -> " Compiling variant " + variant);
- for (Map.Entry<String, ValueWithSource> entry : in.visitValues(variant.path(), variant.binding().getContext()).valuesWithSource().entrySet()) {
- values.put(variant.path().append(entry.getKey()), variant.binding(), entry.getValue());
+ log.fine(() -> "Compiling " + in + " having " + variants.size() + " variants");
+
+ CompoundNameChildCache pathCache = new CompoundNameChildCache();
+ Map<DimensionBinding, Binding> bindingCache = new HashMap<>();
+ for (var variant : variants) {
+ log.finer(() -> "Compiling variant " + variant);
+ Binding variantBinding = bindingCache.computeIfAbsent(variant.binding(), Binding::createFrom);
+ for (var entry : in.visitValues(variant.path(), variant.binding().getContext(), pathCache).valuesWithSource().entrySet()) {
+ CompoundName fullName = pathCache.append(variant.path, entry.getKey());
+ values.put(fullName, variantBinding, entry.getValue());
+ if (entry.getValue().isUnoverridable())
+ unoverridables.put(fullName, variantBinding, Boolean.TRUE);
+ if (entry.getValue().isQueryProfile())
+ references.put(fullName, variantBinding, Boolean.TRUE);
+ if (entry.getValue().queryProfileType() != null)
+ types.put(fullName, variantBinding, entry.getValue().queryProfileType());
}
- for (Map.Entry<CompoundName, QueryProfileType> entry : in.listTypes(variant.path(), variant.binding().getContext()).entrySet())
- types.put(variant.path().append(entry.getKey()), variant.binding(), entry.getValue());
- for (CompoundName reference : in.listReferences(variant.path(), variant.binding().getContext()))
- references.put(variant.path().append(reference), variant.binding(), Boolean.TRUE); // Used as a set; value is ignored
- for (CompoundName name : in.listUnoverridable(variant.path(), variant.binding().getContext()))
- unoverridables.put(variant.path().append(name), variant.binding(), Boolean.TRUE); // Used as a set; value is ignored
}
return new CompiledQueryProfile(in.getId(), in.getType(),
@@ -71,6 +83,7 @@ public class QueryProfileCompiler {
*/
private static Set<DimensionBindingForPath> collectVariants(CompoundName path, QueryProfile profile, DimensionBinding currentVariant) {
Set<DimensionBindingForPath> variants = new HashSet<>();
+
variants.addAll(collectVariantsFromValues(path, profile.getContent(), currentVariant));
variants.addAll(collectVariantsInThis(path, profile, currentVariant));
if (profile instanceof BackedOverridableQueryProfile)
@@ -93,16 +106,31 @@ public class QueryProfileCompiler {
* I.e if we have the variants [-,b=b1], [a=a1,-], [a=a2,-],
* this returns the variants [a=a1,b=b1], [a=a2,b=b1]
*
- * This is necessary because left-specified values takes precedence, such that resolving [a=a1,b=b1] would
- * lead us to the compiled profile [a=a1,-], which may contain default values for properties where
+ * This is necessary because left-specified values takes precedence, and resolving [a=a1,b=b1] would
+ * otherwise lead us to the compiled profile [a=a1,-], which may contain default values for properties where
* we should have preferred variant values in [-,b=b1].
*/
private static Set<DimensionBindingForPath> wildcardExpanded(Set<DimensionBindingForPath> variants) {
Set<DimensionBindingForPath> expanded = new HashSet<>();
- for (var variant : variants) {
- if (hasWildcardBeforeEnd(variant.binding()))
- expanded.addAll(wildcardExpanded(variant, variants));
- }
+
+ PathTree trie = new PathTree();
+ for (var variant : variants)
+ trie.add(variant);
+
+ // Visit all variant prefixes, grouped on path, and all their unique child bindings.
+ trie.forEachPrefixAndChildren((prefixes, childBindings) -> {
+ Set<DimensionBinding> processed = new HashSet<>();
+ for (DimensionBindingForPath prefix : prefixes)
+ if (processed.add(prefix.binding())) // Only compute once for similar bindings, since path is equal.
+ if (hasWildcardBeforeEnd(prefix.binding()))
+ for (DimensionBinding childBinding : childBindings)
+ if (childBinding != prefix.binding()) {
+ DimensionBinding combined = prefix.binding().combineWith(childBinding);
+ if ( ! combined.isInvalid())
+ expanded.add(new DimensionBindingForPath(combined, prefix.path()));
+ }
+ });
+
return expanded;
}
@@ -114,20 +142,6 @@ public class QueryProfileCompiler {
return false;
}
- private static Set<DimensionBindingForPath> wildcardExpanded(DimensionBindingForPath variantToExpand,
- Set<DimensionBindingForPath> variants) {
- Set<DimensionBindingForPath> expanded = new HashSet<>();
- for (var variant : variants) {
- if (variant.binding().isNull()) continue;
- DimensionBinding combined = variantToExpand.binding().combineWith(variant.binding());
- if ( ! combined.isInvalid() ) {
- expanded.add(new DimensionBindingForPath(combined, variantToExpand.path()));
- }
- }
- return expanded;
- }
-
-
/** Generates a set of all the (legal) combinations of the variants in the given sets */
private static Set<DimensionBindingForPath> combined(Set<DimensionBindingForPath> v1s,
Set<DimensionBindingForPath> v2s) {
@@ -135,7 +149,7 @@ public class QueryProfileCompiler {
for (DimensionBindingForPath v1 : v1s) {
if (v1.binding().isNull()) continue;
for (DimensionBindingForPath v2 : v2s) {
- if (v1.binding().isNull()) continue;
+ if (v2.binding().isNull()) continue;
DimensionBinding combined = v1.binding().combineWith(v2.binding());
if ( combined.isInvalid() ) continue;
@@ -157,6 +171,7 @@ public class QueryProfileCompiler {
if (combinedVariant.isInvalid()) continue; // values at this point in the graph are unreachable
+ variants.add(new DimensionBindingForPath(combinedVariant, path));
variants.addAll(collectVariantsFromValues(path, variant.values(), combinedVariant));
for (QueryProfile variantInheritedProfile : variant.inherited())
variants.addAll(collectVariants(path, variantInheritedProfile, combinedVariant));
@@ -169,9 +184,6 @@ public class QueryProfileCompiler {
Map<String, Object> values,
DimensionBinding currentVariant) {
Set<DimensionBindingForPath> variants = new HashSet<>();
- if ( ! values.isEmpty())
- variants.add(new DimensionBindingForPath(currentVariant, path)); // there are actual values for this variant
-
for (Map.Entry<String, Object> entry : values.entrySet()) {
if (entry.getValue() instanceof QueryProfile)
variants.addAll(collectVariants(path.append(entry.getKey()), (QueryProfile)entry.getValue(), currentVariant));
@@ -202,7 +214,7 @@ public class QueryProfileCompiler {
@Override
public int hashCode() {
- return binding.hashCode() + 17*path.hashCode();
+ return binding.hashCode() + 17 * path.hashCode();
}
@Override
@@ -212,4 +224,61 @@ public class QueryProfileCompiler {
}
+
+ /**
+ * Simple trie for CompoundName paths.
+ *
+ * @author jonmv
+ */
+ static class PathTree {
+
+ private final Node root = new Node(0);
+
+ void add(DimensionBindingForPath entry) {
+ root.add(entry);
+ }
+
+ /** Performs action on sets of path prefixes against all their (common) children. */
+ void forEachPrefixAndChildren(BiConsumer<Collection<DimensionBindingForPath>, Collection<DimensionBinding>> action) {
+ root.visit(action);
+ }
+
+ private static class Node {
+
+ private final int depth;
+ private final SortedMap<String, Node> children = new TreeMap<>();
+ private final List<DimensionBindingForPath> elements = new ArrayList<>();
+
+ private Node(int depth) {
+ this.depth = depth;
+ }
+
+ /** Performs action on the elements of this against all child element bindings, then returns the union of these two sets. */
+ Set<DimensionBinding> visit(BiConsumer<Collection<DimensionBindingForPath>, Collection<DimensionBinding>> action) {
+ Set<DimensionBinding> allChildren = new HashSet<>();
+ for (Node child : children.values())
+ allChildren.addAll(child.visit(action));
+
+ for (DimensionBindingForPath element : elements)
+ if ( ! element.binding().isNull())
+ allChildren.add(element.binding());
+
+ action.accept(elements, allChildren);
+
+ return allChildren;
+ }
+
+ void add(DimensionBindingForPath entry) {
+ if (depth == entry.path().size())
+ elements.add(entry);
+ else
+ children.computeIfAbsent(entry.path().get(depth),
+ __ -> new Node(depth + 1)).add(entry);
+ }
+
+ }
+
+ }
+
+
}
diff --git a/container-search/src/main/java/com/yahoo/search/query/profile/QueryProfileProperties.java b/container-search/src/main/java/com/yahoo/search/query/profile/QueryProfileProperties.java
index 05e3c4fe9a0..53be827073c 100644
--- a/container-search/src/main/java/com/yahoo/search/query/profile/QueryProfileProperties.java
+++ b/container-search/src/main/java/com/yahoo/search/query/profile/QueryProfileProperties.java
@@ -2,12 +2,15 @@
package com.yahoo.search.query.profile;
import com.yahoo.collections.Pair;
+import com.yahoo.language.process.Embedder;
+import com.yahoo.processing.IllegalInputException;
import com.yahoo.processing.request.CompoundName;
import com.yahoo.processing.request.properties.PropertyMap;
import com.yahoo.protect.Validator;
import com.yahoo.search.query.Properties;
import com.yahoo.search.query.profile.compiled.CompiledQueryProfile;
import com.yahoo.search.query.profile.compiled.DimensionalValue;
+import com.yahoo.search.query.profile.types.ConversionContext;
import com.yahoo.search.query.profile.types.FieldDescription;
import com.yahoo.search.query.profile.types.QueryProfileFieldType;
import com.yahoo.search.query.profile.types.QueryProfileType;
@@ -27,18 +30,29 @@ import java.util.Map;
public class QueryProfileProperties extends Properties {
private final CompiledQueryProfile profile;
+ private final Embedder embedder;
// Note: The priority order is: values has precedence over references
/** Values which has been overridden at runtime, or null if none */
private Map<CompoundName, Object> values = null;
- /** Query profile references which has been overridden at runtime, or null if none. Earlier values has precedence */
+
+ /**
+ * Query profile references which has been overridden at runtime, possibly to the null value to clear values,
+ * or null if none (i.e this is lazy).
+ * Earlier values has precedence
+ */
private List<Pair<CompoundName, CompiledQueryProfile>> references = null;
- /** Creates an instance from a profile, throws an exception if the given profile is null */
public QueryProfileProperties(CompiledQueryProfile profile) {
+ this(profile, Embedder.throwsOnUse);
+ }
+
+ /** Creates an instance from a profile, throws an exception if the given profile is null */
+ public QueryProfileProperties(CompiledQueryProfile profile, Embedder embedder) {
Validator.ensureNotNull("The profile wrapped by this cannot be null", profile);
this.profile = profile;
+ this.embedder = embedder;
}
/** Returns the query profile backing this, or null if none */
@@ -49,20 +63,21 @@ public class QueryProfileProperties extends Properties {
public Object get(CompoundName name, Map<String, String> context,
com.yahoo.processing.request.Properties substitution) {
name = unalias(name, context);
- Object value = null;
- if (values != null)
- value = values.get(name);
- if (value == null) {
- Pair<CompoundName, CompiledQueryProfile> reference = findReference(name);
- if (reference != null)
- return reference.getSecond().get(name.rest(reference.getFirst().size()), context, substitution); // yes; even if null
+ if (values != null && values.containsKey(name))
+ return values.get(name); // Returns this value, even if null
+
+ Pair<CompoundName, CompiledQueryProfile> reference = findReference(name);
+ if (reference != null) {
+ if (reference.getSecond() == null)
+ return null; // cleared
+ else
+ return reference.getSecond().get(name.rest(reference.getFirst().size()), context, substitution); // even if null
}
- if (value == null)
- value = profile.get(name, context, substitution);
- if (value == null)
- value = super.get(name, context, substitution);
- return value;
+ Object value = profile.get(name, context, substitution);
+ if (value != null)
+ return value;
+ return super.get(name, context, substitution);
}
/**
@@ -88,28 +103,34 @@ public class QueryProfileProperties extends Properties {
// Check types
if ( ! profile.getTypes().isEmpty()) {
- QueryProfileType type = null;
+ QueryProfileType type;
+ QueryProfileType explicitTypeFromField = null;
for (int i = 0; i < name.size(); i++) {
- if (type == null) // We're on the first iteration, or no type is explicitly specified
+ if (explicitTypeFromField != null)
+ type = explicitTypeFromField;
+ else
type = profile.getType(name.first(i), context);
if (type == null) continue;
+
String localName = name.get(i);
FieldDescription fieldDescription = type.getField(localName);
if (fieldDescription == null && type.isStrict())
- throw new IllegalArgumentException("'" + localName + "' is not declared in " + type + ", and the type is strict");
+ throw new IllegalInputException("'" + localName + "' is not declared in " + type + ", and the type is strict");
// TODO: In addition to strictness, check legality along the way
if (fieldDescription != null) {
if (i == name.size() - 1) { // at the end of the path, check the assignment type
- value = fieldDescription.getType().convertFrom(value, profile.getRegistry());
+ value = fieldDescription.getType().convertFrom(value, new ConversionContext(profile.getRegistry(),
+ embedder,
+ context));
if (value == null)
- throw new IllegalArgumentException("'" + value + "' is not a " +
- fieldDescription.getType().toInstanceDescription());
+ throw new IllegalInputException("'" + value + "' is not a " +
+ fieldDescription.getType().toInstanceDescription());
}
else if (fieldDescription.getType() instanceof QueryProfileFieldType) {
// If a type is specified, use that instead of the type implied by the name
- type = ((QueryProfileFieldType) fieldDescription.getType()).getQueryProfileType();
+ explicitTypeFromField = ((QueryProfileFieldType) fieldDescription.getType()).getQueryProfileType();
}
}
@@ -118,12 +139,12 @@ public class QueryProfileProperties extends Properties {
if (value instanceof String && value.toString().startsWith("ref:")) {
if (profile.getRegistry() == null)
- throw new IllegalArgumentException("Runtime query profile references does not work when the " +
+ throw new IllegalInputException("Runtime query profile references does not work when the " +
"QueryProfileProperties are constructed without a registry");
String queryProfileId = value.toString().substring(4);
value = profile.getRegistry().findQueryProfile(queryProfileId);
if (value == null)
- throw new IllegalArgumentException("Query profile '" + queryProfileId + "' is not found");
+ throw new IllegalInputException("Query profile '" + queryProfileId + "' is not found");
}
if (value instanceof CompiledQueryProfile) { // this will be due to one of the two clauses above
@@ -138,17 +159,31 @@ public class QueryProfileProperties extends Properties {
}
}
catch (IllegalArgumentException e) {
- throw new IllegalArgumentException("Could not set '" + name + "' to '" + value + "'", e);
+ throw new IllegalInputException("Could not set '" + name + "' to '" + value + "'", e);
}
}
@Override
- public Map<String, Object> listProperties(CompoundName path, Map<String,String> context,
+ public void clearAll(CompoundName name, Map<String, String> context) {
+ if (references == null)
+ references = new ArrayList<>();
+ references.add(new Pair<>(name, null));
+
+ if (values != null)
+ values.keySet().removeIf(key -> key.hasPrefix(name));
+ }
+
+ @Override
+ public Map<String, Object> listProperties(CompoundName path, Map<String, String> context,
com.yahoo.processing.request.Properties substitution) {
path = unalias(path, context);
if (context == null) context = Collections.emptyMap();
- Map<String, Object> properties = profile.listValues(path, context, substitution);
+ Map<String, Object> properties = new HashMap<>();
+ for (var entry : profile.listValues(path, context, substitution).entrySet()) {
+ if (references != null && containsNullParentOf(path, references)) continue;
+ properties.put(entry.getKey(), entry.getValue());
+ }
properties.putAll(super.listProperties(path, context, substitution));
if (references != null) {
@@ -165,8 +200,14 @@ public class QueryProfileProperties extends Properties {
pathInReference = path.rest(refEntry.getFirst().size());
prefixToReferenceKeys = CompoundName.empty;
}
- for (Map.Entry<String, Object> valueEntry : refEntry.getSecond().listValues(pathInReference, context, substitution).entrySet()) {
- properties.put(prefixToReferenceKeys.append(new CompoundName(valueEntry.getKey())).toString(), valueEntry.getValue());
+ if (refEntry.getSecond() == null) {
+ if (refEntry.getFirst().hasPrefix(path))
+ properties.put(prefixToReferenceKeys.toString(), null);
+ }
+ else {
+ for (Map.Entry<String, Object> valueEntry : refEntry.getSecond().listValues(pathInReference, context, substitution).entrySet()) {
+ properties.put(prefixToReferenceKeys.append(new CompoundName(valueEntry.getKey())).toString(), valueEntry.getValue());
+ }
}
}
@@ -241,6 +282,12 @@ public class QueryProfileProperties extends Properties {
return null;
}
+ private boolean containsNullParentOf(CompoundName path, List<Pair<CompoundName, CompiledQueryProfile>> properties) {
+ if (properties.contains(new Pair<>(path, (CompiledQueryProfile)null))) return true;
+ if (path.size() > 0 && containsNullParentOf(path.first(path.size() - 1), properties)) return true;
+ return false;
+ }
+
CompoundName unalias(CompoundName name, Map<String,String> context) {
if (profile.getTypes().isEmpty()) return name;
diff --git a/container-search/src/main/java/com/yahoo/search/query/profile/QueryProfileRegistry.java b/container-search/src/main/java/com/yahoo/search/query/profile/QueryProfileRegistry.java
index 0363b50815b..eb7a6d19d91 100644
--- a/container-search/src/main/java/com/yahoo/search/query/profile/QueryProfileRegistry.java
+++ b/container-search/src/main/java/com/yahoo/search/query/profile/QueryProfileRegistry.java
@@ -62,7 +62,6 @@ public class QueryProfileRegistry extends ComponentRegistry<QueryProfile> {
int slashIndex=id.getName().lastIndexOf("/");
if (slashIndex<1) return null;
String parentName=id.getName().substring(0,slashIndex);
- if (parentName.equals("")) return null;
ComponentSpecification parentId=new ComponentSpecification(parentName,id.getVersionSpecification());
diff --git a/container-search/src/main/java/com/yahoo/search/query/profile/QueryProfileVariant.java b/container-search/src/main/java/com/yahoo/search/query/profile/QueryProfileVariant.java
index 3f70ff98373..855befad658 100644
--- a/container-search/src/main/java/com/yahoo/search/query/profile/QueryProfileVariant.java
+++ b/container-search/src/main/java/com/yahoo/search/query/profile/QueryProfileVariant.java
@@ -16,13 +16,15 @@ public class QueryProfileVariant implements Cloneable, Comparable<QueryProfileVa
private List<QueryProfile> inherited = null;
- private DimensionValues dimensionValues;
+ private final DimensionValues dimensionValues;
private Map<String, Object> values;
+ private final Map<String, Boolean> overridable = new HashMap<>();
+
private boolean frozen = false;
- private QueryProfile owner;
+ private final QueryProfile owner;
public QueryProfileVariant(DimensionValues dimensionValues, QueryProfile owner) {
this.dimensionValues = dimensionValues;
@@ -35,7 +37,7 @@ public class QueryProfileVariant implements Cloneable, Comparable<QueryProfileVa
* Returns the live reference to the values of this. This may be modified
* if this is not frozen.
*/
- public Map<String,Object> values() {
+ public Map<String, Object> values() {
if (values == null) {
if (frozen)
return Collections.emptyMap();
@@ -59,20 +61,26 @@ public class QueryProfileVariant implements Cloneable, Comparable<QueryProfileVa
return inherited;
}
- public void set(String key, Object newValue) {
+ public Object set(String key, Object newValue) {
if (values == null)
values = new HashMap<>();
Object oldValue = values.get(key);
- if (oldValue == null) {
- values.put(key, newValue);
- } else {
- Object combinedOrNull = QueryProfile.combineValues(newValue, oldValue);
- if (combinedOrNull != null) {
- values.put(key, combinedOrNull);
- }
- }
+ Object combinedOrNull = QueryProfile.combineValues(newValue, oldValue);
+ if (combinedOrNull instanceof BackedOverridableQueryProfile) // Use the owner's, not the referenced dimensions
+ ((QueryProfile) combinedOrNull).setDimensions(owner.getDimensions().toArray(new String[0]));
+ if (combinedOrNull != null)
+ values.put(key, combinedOrNull);
+ return combinedOrNull;
+ }
+
+ public void setOverridable(String key, boolean overridable) {
+ this.overridable.put(key, overridable);
+ }
+
+ public Boolean isOverridable(String key) {
+ return overridable.get(key);
}
public void inherit(QueryProfile profile) {
@@ -138,6 +146,7 @@ public class QueryProfileVariant implements Cloneable, Comparable<QueryProfileVa
frozen=true;
}
+ @Override
public QueryProfileVariant clone() {
if (frozen) return this;
try {
@@ -156,7 +165,7 @@ public class QueryProfileVariant implements Cloneable, Comparable<QueryProfileVa
@Override
public String toString() {
- return "query profile variant for " + dimensionValues;
+ return "query profile variant of " + owner + " for " + dimensionValues;
}
}
diff --git a/container-search/src/main/java/com/yahoo/search/query/profile/QueryProfileVariants.java b/container-search/src/main/java/com/yahoo/search/query/profile/QueryProfileVariants.java
index 8dedda800ea..431947b0816 100644
--- a/container-search/src/main/java/com/yahoo/search/query/profile/QueryProfileVariants.java
+++ b/container-search/src/main/java/com/yahoo/search/query/profile/QueryProfileVariants.java
@@ -27,10 +27,10 @@ public class QueryProfileVariants implements Freezable, Cloneable {
private boolean frozen = false;
/** Properties indexed by name, to support fast lookup of single values */
- private Map<String,FieldValues> fieldValuesByName = new HashMap<>();
+ private Map<String, FieldValues> fieldValuesByName = new HashMap<>();
/** The inherited profiles for various dimensions settings - a set of fieldvalues of List&lt;QueryProfile&gt; */
- private FieldValues inheritedProfiles =new FieldValues();
+ private FieldValues inheritedProfiles = new FieldValues();
/**
* Field and inherited profiles sorted by specificity used for all-value visiting.
@@ -43,10 +43,10 @@ public class QueryProfileVariants implements Freezable, Cloneable {
* Order matters - more specific values to the left in this list are more significant than more specific values
* to the right
*/
- private List<String> dimensions;
+ private final List<String> dimensions;
/** The query profile this variants of */
- private QueryProfile owner;
+ private final QueryProfile owner;
/**
* Creates a set of virtual query profiles which may return varying values over the set of dimensions given.
@@ -144,7 +144,7 @@ public class QueryProfileVariants implements Freezable, Cloneable {
if (visitor.isDone()) return;
fieldIndex++;
}
- else if (inheritedProfileValue != null) { // Inherited is most specific at this point
+ else { // Inherited is most specific at this point
if (inheritedProfileValue.matches(dimensionBinding.getValues())) {
@SuppressWarnings("unchecked")
List<QueryProfile> inheritedProfileList = (List<QueryProfile>)inheritedProfileValue.getValue();
@@ -219,7 +219,7 @@ public class QueryProfileVariants implements Freezable, Cloneable {
ensureNotFrozen();
// Update variant
- getVariant(dimensionValues, true).set(fieldName, value);
+ Object combinedValue = getVariant(dimensionValues, true).set(fieldName, value);
// Update per-variable optimized structure
FieldValues fieldValues = fieldValuesByName.get(fieldName);
@@ -228,12 +228,24 @@ public class QueryProfileVariants implements Freezable, Cloneable {
fieldValuesByName.put(fieldName, fieldValues);
}
- Object combinedValue = QueryProfile.combineValues(value, fieldValues.getExact(dimensionValues));
if (combinedValue != null)
fieldValues.put(dimensionValues, combinedValue);
}
/**
+ * Makes a value unoverridable in a given context.
+ */
+ public void setOverridable(String fieldName, boolean overridable, DimensionValues dimensionValues) {
+ getVariant(dimensionValues, true).setOverridable(fieldName, overridable);
+ }
+
+ public Boolean isOverridable(String fieldName, DimensionValues dimensionValues) {
+ QueryProfileVariant variant = getVariant(dimensionValues, false);
+ if (variant == null) return null;
+ return variant.isOverridable(fieldName);
+ }
+
+ /**
* Returns the dimensions over which the virtual profiles in this may return different values.
* Each dimension is a name for which a key-value may be supplied in the context properties
* on lookup time to influence the value returned.
@@ -257,6 +269,7 @@ public class QueryProfileVariants implements Freezable, Cloneable {
return Collections.unmodifiableList(variants);
}
+ @Override
public QueryProfileVariants clone() {
try {
if (frozen) return this;
@@ -304,9 +317,12 @@ public class QueryProfileVariants implements Freezable, Cloneable {
return variant;
}
+ @Override
+ public String toString() { return "variants of " + owner; }
+
public static class FieldValues implements Freezable, Cloneable {
- private List<FieldValue> resolutionList=null;
+ private List<FieldValue> resolutionList = null;
private boolean frozen = false;
@@ -420,7 +436,7 @@ public class QueryProfileVariants implements Freezable, Cloneable {
public static class FieldValue implements Comparable<FieldValue>, Cloneable {
- private DimensionValues dimensionValues;
+ private final DimensionValues dimensionValues;
private Object value;
public FieldValue(DimensionValues dimensionValues, Object value) {
@@ -458,7 +474,7 @@ public class QueryProfileVariants implements Freezable, Cloneable {
}
/** Clone by filling in the value from the given variants */
- public FieldValue clone(String fieldName,List<QueryProfileVariant> clonedVariants) {
+ public FieldValue clone(String fieldName, List<QueryProfileVariant> clonedVariants) {
try {
FieldValue clone = (FieldValue)super.clone();
if (this.value instanceof QueryProfile)
@@ -471,6 +487,7 @@ public class QueryProfileVariants implements Freezable, Cloneable {
}
}
+ @Override
public FieldValue clone() {
try {
FieldValue clone = (FieldValue)super.clone();
@@ -490,6 +507,9 @@ public class QueryProfileVariants implements Freezable, Cloneable {
return null;
}
+ @Override
+ public String toString() { return "field value " + value + " for " + dimensionValues; }
+
}
}
diff --git a/container-search/src/main/java/com/yahoo/search/query/profile/compiled/Binding.java b/container-search/src/main/java/com/yahoo/search/query/profile/compiled/Binding.java
index 2774bd4ebf2..46430a3041a 100644
--- a/container-search/src/main/java/com/yahoo/search/query/profile/compiled/Binding.java
+++ b/container-search/src/main/java/com/yahoo/search/query/profile/compiled/Binding.java
@@ -33,8 +33,7 @@ public class Binding implements Comparable<Binding> {
private final int hashCode;
- @SuppressWarnings("unchecked")
- public static final Binding nullBinding = new Binding(Integer.MAX_VALUE, Collections.<String,String>emptyMap());
+ public static final Binding nullBinding = new Binding(Integer.MAX_VALUE, Map.of());
public static Binding createFrom(DimensionBinding dimensionBinding) {
if (dimensionBinding.getDimensions().size() > maxDimensions)
@@ -57,27 +56,65 @@ public class Binding implements Comparable<Binding> {
return new Binding(generality, context);
}
- private Binding(int generality, Map<String, String> binding) {
+ /** Creates a binding from a map containing the exact bindings this will have */
+ private Binding(int generality, Map<String, String> bindings) {
this.generality = generality;
// Map -> arrays to limit memory consumption and speed up evaluation
- dimensions = new String[binding.size()];
- dimensionValues = new String[binding.size()];
+ dimensions = new String[bindings.size()];
+ dimensionValues = new String[bindings.size()];
int i = 0;
- int bindingHash = 0;
- for (Map.Entry<String,String> entry : binding.entrySet()) {
+ for (Map.Entry<String,String> entry : bindings.entrySet()) {
dimensions[i] = entry.getKey();
dimensionValues[i] = entry.getValue();
- bindingHash += i * entry.getKey().hashCode() + 11 * i * entry.getValue().hashCode();
i++;
}
- this.hashCode = bindingHash;
+ this.hashCode = Arrays.hashCode(dimensions) + 11 * Arrays.hashCode(dimensionValues);
+ }
+
+ Binding(DimensionalValue.BindingSpec spec, Map<String, String> bindings) {
+ this.generality = 0; // Not used here
+
+ // Map -> arrays to limit memory consumption and speed up evaluation
+ dimensions = spec.dimensions();
+ dimensionValues = new String[spec.dimensions().length];
+ for (int i = 0; i < dimensions.length; i++) {
+ dimensionValues[i] = bindings.get(dimensions[i]);
+ }
+ this.hashCode = Arrays.hashCode(dimensions) + 11 * Arrays.hashCode(dimensionValues);
+ }
+
+ /**
+ * Returns whether this binding is a proper generalization of the given binding:
+ * Meaning it contains a proper subset of the given bindings.
+ */
+ public boolean generalizes(Binding other) {
+ if ( this.dimensions.length >= other.dimensions.length) return false;
+ for (int i = 0; i < this.dimensions.length; i++) {
+ int otherIndexOfDimension = this.indexOf(dimensions[i], other.dimensions);
+ if (otherIndexOfDimension < 0) return false;
+ if ( ! this.dimensionValues[i].equals(other.dimensionValues[otherIndexOfDimension])) return false;
+ }
+ return true;
+ }
+
+ private int indexOf(String value, String[] array) {
+ for (int i = 0; i < array.length; i++) {
+ if (array[i].equals(value))
+ return i;
+ }
+ return -1;
}
/** Returns true only if this binding is null (contains no values for its dimensions (if any) */
public boolean isNull() { return dimensions.length == 0; }
+ /** Do not change the returtned array */
+ String[] dimensions() { return dimensions; }
+
+ String[] dimensionValues() { return dimensionValues; }
+
@Override
public String toString() {
StringBuilder b = new StringBuilder("Binding[");
@@ -106,7 +143,7 @@ public class Binding implements Comparable<Binding> {
* Returns true if all the dimension values in this have the same values
* in the given context.
*/
- public boolean matches(Map<String,String> context) {
+ public boolean matches(Map<String, String> context) {
for (int i = 0; i < dimensions.length; i++) {
if ( ! dimensionValues[i].equals(context.get(dimensions[i]))) return false;
}
@@ -116,7 +153,7 @@ public class Binding implements Comparable<Binding> {
/**
* Implements a partial ordering where more specific bindings come before less specific ones,
* taking both the number of bindings and their positions into account (earlier dimensions
- * take precedence over later ones.
+ * take precedence over later ones).
* <p>
* The order is not well defined for bindings in different dimensional spaces.
*/
diff --git a/container-search/src/main/java/com/yahoo/search/query/profile/compiled/CompiledQueryProfile.java b/container-search/src/main/java/com/yahoo/search/query/profile/compiled/CompiledQueryProfile.java
index 644d366e7d0..2439908183c 100644
--- a/container-search/src/main/java/com/yahoo/search/query/profile/compiled/CompiledQueryProfile.java
+++ b/container-search/src/main/java/com/yahoo/search/query/profile/compiled/CompiledQueryProfile.java
@@ -30,26 +30,26 @@ public class CompiledQueryProfile extends AbstractComponent implements Cloneable
private final QueryProfileType type;
/** The values of this */
- private final DimensionalMap<CompoundName, ValueWithSource> entries;
+ private final DimensionalMap<ValueWithSource> entries;
/** Keys which have a type in this */
- private final DimensionalMap<CompoundName, QueryProfileType> types;
+ private final DimensionalMap<QueryProfileType> types;
/** Keys which are (typed or untyped) references to other query profiles in this. Used as a set. */
- private final DimensionalMap<CompoundName, Object> references;
+ private final DimensionalMap<Object> references;
/** Values which are not overridable in this. Used as a set. */
- private final DimensionalMap<CompoundName, Object> unoverridables;
+ private final DimensionalMap<Object> unoverridables;
/**
* Creates a new query profile from an id.
*/
public CompiledQueryProfile(ComponentId id,
QueryProfileType type,
- DimensionalMap<CompoundName, ValueWithSource> entries,
- DimensionalMap<CompoundName, QueryProfileType> types,
- DimensionalMap<CompoundName, Object> references,
- DimensionalMap<CompoundName, Object> unoverridables,
+ DimensionalMap<ValueWithSource> entries,
+ DimensionalMap<QueryProfileType> types,
+ DimensionalMap<Object> references,
+ DimensionalMap<Object> unoverridables,
CompiledQueryProfileRegistry registry) {
super(id);
this.registry = registry;
@@ -91,10 +91,10 @@ public class CompiledQueryProfile extends AbstractComponent implements Cloneable
}
/** Returns the types reachable from this, or an empty map (never null) if none */
- public DimensionalMap<CompoundName, QueryProfileType> getTypes() { return types; }
+ public DimensionalMap<QueryProfileType> getTypes() { return types; }
/** Returns the references reachable from this, or an empty map (never null) if none */
- public DimensionalMap<CompoundName, Object> getReferences() { return references; }
+ public DimensionalMap<Object> getReferences() { return references; }
/**
* Return all objects that start with the given prefix path using no context. Use "" to list all.
@@ -102,7 +102,7 @@ public class CompiledQueryProfile extends AbstractComponent implements Cloneable
* For example, if {a.d =&gt; "a.d-value" ,a.e =&gt; "a.e-value", b.d =&gt; "b.d-value", then calling listValues("a")
* will return {"d" =&gt; "a.d-value","e" =&gt; "a.e-value"}
*/
- public final Map<String, Object> listValues(CompoundName prefix) { return listValues(prefix, Collections.<String,String>emptyMap()); }
+ public final Map<String, Object> listValues(CompoundName prefix) { return listValues(prefix, Collections.emptyMap()); }
public final Map<String, Object> listValues(String prefix) { return listValues(new CompoundName(prefix)); }
/**
@@ -134,7 +134,6 @@ public class CompiledQueryProfile extends AbstractComponent implements Cloneable
public Map<String, Object> listValues(CompoundName prefix, Map<String, String> context, Properties substitution) {
Map<String, Object> values = new HashMap<>();
for (Map.Entry<CompoundName, DimensionalValue<ValueWithSource>> entry : entries.entrySet()) {
- if ( entry.getKey().size() <= prefix.size()) continue;
if ( ! entry.getKey().hasPrefix(prefix)) continue;
ValueWithSource valueWithSource = entry.getValue().get(context);
@@ -160,6 +159,7 @@ public class CompiledQueryProfile extends AbstractComponent implements Cloneable
ValueWithSource valueWithSource = entry.getValue().get(context);
if (valueWithSource == null) continue;
+ if (valueWithSource.value() == null) continue;
valueWithSource = valueWithSource.withValue(substitute(valueWithSource.value(), context, substitution));
CompoundName suffixName = entry.getKey().rest(prefix.size());
@@ -183,6 +183,11 @@ public class CompiledQueryProfile extends AbstractComponent implements Cloneable
return substitute(value.value(), context, substitution);
}
+ /** Returns all the entries from the profile **/
+ public final DimensionalMap<ValueWithSource> getEntries() {
+ return this.entries;
+ }
+
private Object substitute(Object value, Map<String, String> context, Properties substitution) {
if (value == null) return value;
if (substitution == null) return value;
diff --git a/container-search/src/main/java/com/yahoo/search/query/profile/compiled/CompiledQueryProfileRegistry.java b/container-search/src/main/java/com/yahoo/search/query/profile/compiled/CompiledQueryProfileRegistry.java
index 7ab05d0cd1e..744c6eb6933 100644
--- a/container-search/src/main/java/com/yahoo/search/query/profile/compiled/CompiledQueryProfileRegistry.java
+++ b/container-search/src/main/java/com/yahoo/search/query/profile/compiled/CompiledQueryProfileRegistry.java
@@ -1,9 +1,14 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.search.query.profile.compiled;
+import com.google.inject.Inject;
import com.yahoo.component.ComponentSpecification;
import com.yahoo.component.provider.ComponentRegistry;
-import com.yahoo.search.query.profile.types.QueryProfileType;
+import com.yahoo.search.query.profile.QueryProfile;
+import com.yahoo.search.query.profile.QueryProfileCompiler;
+import com.yahoo.search.query.profile.QueryProfileRegistry;
+import com.yahoo.search.query.profile.config.QueryProfileConfigurer;
+import com.yahoo.search.query.profile.config.QueryProfilesConfig;
import com.yahoo.search.query.profile.types.QueryProfileTypeRegistry;
/**
@@ -18,6 +23,15 @@ public class CompiledQueryProfileRegistry extends ComponentRegistry<CompiledQuer
private final QueryProfileTypeRegistry typeRegistry;
+ @Inject
+ public CompiledQueryProfileRegistry(QueryProfilesConfig config) {
+ QueryProfileRegistry registry = QueryProfileConfigurer.createFromConfig(config);
+ typeRegistry = registry.getTypeRegistry();
+ for (QueryProfile inputProfile : registry.allComponents()) {
+ register(QueryProfileCompiler.compile(inputProfile, this));
+ }
+ }
+
/** Creates a compiled query profile registry with no types */
public CompiledQueryProfileRegistry() {
this(QueryProfileTypeRegistry.emptyFrozen());
@@ -28,7 +42,7 @@ public class CompiledQueryProfileRegistry extends ComponentRegistry<CompiledQuer
}
/** Registers a type by its id */
- public void register(CompiledQueryProfile profile) {
+ public final void register(CompiledQueryProfile profile) {
super.register(profile.getId(), profile);
}
diff --git a/container-search/src/main/java/com/yahoo/search/query/profile/compiled/DimensionalMap.java b/container-search/src/main/java/com/yahoo/search/query/profile/compiled/DimensionalMap.java
index 2e8f5dcf91c..6dc5f61c1f6 100644
--- a/container-search/src/main/java/com/yahoo/search/query/profile/compiled/DimensionalMap.java
+++ b/container-search/src/main/java/com/yahoo/search/query/profile/compiled/DimensionalMap.java
@@ -2,6 +2,7 @@
package com.yahoo.search.query.profile.compiled;
import com.google.common.collect.ImmutableMap;
+import com.yahoo.processing.request.CompoundName;
import com.yahoo.search.query.profile.DimensionBinding;
import java.util.HashMap;
@@ -16,23 +17,23 @@ import java.util.Set;
*
* @author bratseth
*/
-public class DimensionalMap<KEY, VALUE> {
+public class DimensionalMap<VALUE> {
- private final Map<KEY, DimensionalValue<VALUE>> values;
+ private final Map<CompoundName, DimensionalValue<VALUE>> values;
- private DimensionalMap(Map<KEY, DimensionalValue<VALUE>> values) {
+ private DimensionalMap(Map<CompoundName, DimensionalValue<VALUE>> values) {
this.values = ImmutableMap.copyOf(values);
}
/** Returns the value for this key matching a context, or null if none */
- public VALUE get(KEY key, Map<String, String> context) {
+ public VALUE get(CompoundName key, Map<String, String> context) {
DimensionalValue<VALUE> variants = values.get(key);
if (variants == null) return null;
return variants.get(context);
}
/** Returns the set of dimensional entries across all contexts. */
- public Set<Map.Entry<KEY, DimensionalValue<VALUE>>> entrySet() {
+ public Set<Map.Entry<CompoundName, DimensionalValue<VALUE>>> entrySet() {
return values.entrySet();
}
@@ -41,23 +42,18 @@ public class DimensionalMap<KEY, VALUE> {
return values.isEmpty();
}
- public static class Builder<KEY, VALUE> {
+ public static class Builder<VALUE> {
- private Map<KEY, DimensionalValue.Builder<VALUE>> entries = new HashMap<>();
+ private final Map<CompoundName, DimensionalValue.Builder<VALUE>> entries = new HashMap<>();
- // TODO: DimensionBinding -> Binding?
- public void put(KEY key, DimensionBinding binding, VALUE value) {
- DimensionalValue.Builder<VALUE> entry = entries.get(key);
- if (entry == null) {
- entry = new DimensionalValue.Builder<>();
- entries.put(key, entry);
- }
- entry.add(value, binding);
+ public void put(CompoundName key, Binding binding, VALUE value) {
+ entries.computeIfAbsent(key, __ -> new DimensionalValue.Builder<>())
+ .add(value, binding);
}
- public DimensionalMap<KEY, VALUE> build() {
- Map<KEY, DimensionalValue<VALUE>> map = new HashMap<>();
- for (Map.Entry<KEY, DimensionalValue.Builder<VALUE>> entry : entries.entrySet()) {
+ public DimensionalMap<VALUE> build() {
+ Map<CompoundName, DimensionalValue<VALUE>> map = new HashMap<>();
+ for (Map.Entry<CompoundName, DimensionalValue.Builder<VALUE>> entry : entries.entrySet()) {
map.put(entry.getKey(), entry.getValue().build(entries));
}
return new DimensionalMap<>(map);
diff --git a/container-search/src/main/java/com/yahoo/search/query/profile/compiled/DimensionalValue.java b/container-search/src/main/java/com/yahoo/search/query/profile/compiled/DimensionalValue.java
index b5481059ac0..afe07b09d41 100644
--- a/container-search/src/main/java/com/yahoo/search/query/profile/compiled/DimensionalValue.java
+++ b/container-search/src/main/java/com/yahoo/search/query/profile/compiled/DimensionalValue.java
@@ -6,11 +6,13 @@ import com.yahoo.search.query.profile.DimensionBinding;
import com.yahoo.search.query.profile.SubstituteString;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.Set;
/**
@@ -20,20 +22,22 @@ import java.util.Set;
*/
public class DimensionalValue<VALUE> {
- private final List<Value<VALUE>> values;
+ private final Map<Binding, VALUE> indexedVariants;
+ private final List<BindingSpec> bindingSpecs;
- /** Create a set of variants which is a single value regardless of dimensions */
- public DimensionalValue(Value<VALUE> value) {
- this.values = Collections.singletonList(value);
- }
+ private DimensionalValue(List<Value<VALUE>> variants) {
+ Collections.sort(variants);
- public DimensionalValue(List<Value<VALUE>> valueVariants) {
- if (valueVariants.size() == 1) { // special cased for efficiency
- this.values = Collections.singletonList(valueVariants.get(0));
- }
- else {
- this.values = new ArrayList<>(valueVariants);
- Collections.sort(this.values);
+ // If there are inconsistent definitions of the same property, we should pick the first in the sort order
+ this.indexedVariants = new HashMap<>();
+ for (Value<VALUE> variant : variants)
+ indexedVariants.putIfAbsent(variant.binding(), variant.value());
+
+ this.bindingSpecs = new ArrayList<>();
+ for (Value<VALUE> variant : variants) {
+ BindingSpec spec = new BindingSpec(variant.binding());
+ if ( ! bindingSpecs.contains(spec))
+ bindingSpecs.add(spec);
}
}
@@ -41,27 +45,30 @@ public class DimensionalValue<VALUE> {
public VALUE get(Map<String, String> context) {
if (context == null)
context = Collections.emptyMap();
- for (Value<VALUE> value : values) {
- if (value.matches(context))
- return value.value();
+
+ for (BindingSpec spec : bindingSpecs) {
+ if ( ! spec.matches(context)) continue;
+ VALUE value = indexedVariants.get(new Binding(spec, context));
+ if (value != null)
+ return value;
}
return null;
}
- public boolean isEmpty() { return values.isEmpty(); }
+ public boolean isEmpty() { return indexedVariants.isEmpty(); }
@Override
public String toString() {
- return values.toString();
+ return indexedVariants.toString();
}
public static class Builder<VALUE> {
- /** The minimal set of variants needed to capture all values at this key */
- private Map<VALUE, Value.Builder<VALUE>> buildableVariants = new HashMap<>();
+ /** The variants of the value of this key */
+ private final Map<VALUE, Value.Builder<VALUE>> buildableVariants = new HashMap<>();
/** Returns the value for the given binding, or null if none */
- public VALUE valueFor(DimensionBinding variantBinding) {
+ public VALUE valueFor(Binding variantBinding) {
for (var entry : buildableVariants.entrySet()) {
if (entry.getValue().variants.contains(variantBinding))
return entry.getKey();
@@ -69,34 +76,36 @@ public class DimensionalValue<VALUE> {
return null;
}
- public void add(VALUE value, DimensionBinding variantBinding) {
+ public void add(VALUE value, Binding variantBinding) {
// Note: We know we can index by the value because its possible types are constrained
- // to what query profiles allow: String, primitives and query profiles
- Value.Builder variant = buildableVariants.get(value);
- if (variant == null) {
- variant = new Value.Builder<>(value);
- buildableVariants.put(value, variant);
- }
- variant.addVariant(variantBinding);
+ // to what query profiles allow: String, primitives and query profiles (wrapped as a ValueWithSource)
+ buildableVariants.computeIfAbsent(value, Value.Builder::new)
+ .addVariant(variantBinding, value);
}
- public DimensionalValue<VALUE> build(Map<?, DimensionalValue.Builder<VALUE>> entries) {
- List<Value> variants = new ArrayList<>();
- for (Value.Builder buildableVariant : buildableVariants.values()) {
+ public DimensionalValue<VALUE> build(Map<CompoundName, DimensionalValue.Builder<VALUE>> entries) {
+ List<Value<VALUE>> variants = new ArrayList<>();
+ if (buildableVariants.size() == 1) {
+ // Compact size 1 as it is common and easy to do. To compact size > 1 we would need to
+ // compact within generic intervals having the same value
+ for (Value.Builder<VALUE> buildableVariant : buildableVariants.values())
+ buildableVariant.compact();
+ }
+ for (Value.Builder<VALUE> buildableVariant : buildableVariants.values()) {
variants.addAll(buildableVariant.build(entries));
}
- return new DimensionalValue(variants);
+ return new DimensionalValue<>(variants);
}
}
/** A value for a particular binding */
- private static class Value<VALUE> implements Comparable<Value> {
+ private static class Value<VALUE> implements Comparable<Value<VALUE>> {
- private VALUE value = null;
+ private final VALUE value;
/** The minimal binding this holds for */
- private Binding binding = null;
+ private final Binding binding;
public Value(VALUE value, Binding binding) {
this.value = value;
@@ -126,40 +135,78 @@ public class DimensionalValue<VALUE> {
return " value '" + value + "' for " + binding;
}
- /**
- * A single value with the minimal set of dimension combinations it holds for.
- */
private static class Builder<VALUE> {
- private final VALUE value;
+ private VALUE value;
/**
* The set of bindings this value is for.
* Some of these are more general versions of others.
* We need to keep both to allow interleaving a different value with medium generality.
*/
- private Set<DimensionBinding> variants = new HashSet<>();
+ private List<Binding> variants = new ArrayList<>();
public Builder(VALUE value) {
this.value = value;
}
/** Add a binding this holds for */
- public void addVariant(DimensionBinding binding) {
+ @SuppressWarnings("unchecked")
+ public void addVariant(Binding binding, VALUE newValue) {
variants.add(binding);
+
+ // We're combining values for efficiency, so remove incorrect provenance info
+ if (value instanceof ValueWithSource) {
+ ValueWithSource v1 = (ValueWithSource)value;
+ ValueWithSource v2 = (ValueWithSource)newValue;
+
+ if (v1.source() != null && ! v1.source().equals(v2.source()))
+ v1 = v1.withSource(null);
+
+ // We could keep the more general variant here (when matching), but that situation is rare
+ if (v1.variant().isPresent() && ! v1.variant().equals(v2.variant()))
+ v1 = v1.withVariant(Optional.empty());
+
+ value = (VALUE)v1;
+ }
+ }
+
+ /** Remove variants that are specializations of other variants in this */
+ void compact() {
+ Collections.sort(variants);
+ List<Binding> compacted = new ArrayList<>();
+
+ if (variants.get(variants.size() - 1).dimensions().length == 0) { // Shortcut
+ variants = List.of(variants.get(variants.size() - 1));
+ }
+ else {
+ for (int i = variants.size() - 1; i >= 0; i--) {
+ if ( ! containsGeneralizationOf(variants.get(i), compacted))
+ compacted.add(variants.get(i));
+ }
+ Collections.reverse(compacted);
+ variants = compacted;
+ }
+ }
+
+ private boolean containsGeneralizationOf(Binding binding, List<Binding> bindings) {
+ for (Binding candidate : bindings) {
+ if (candidate.generalizes(binding))
+ return true;
+ }
+ return false;
}
/** Build a separate value object for each dimension combination which has this value */
public List<Value<VALUE>> build(Map<CompoundName, DimensionalValue.Builder<VALUE>> entries) {
- // Shortcut for efficiency of the normal case
- if (variants.size() == 1) {
- return Collections.singletonList(new Value<>(substituteIfRelative(value, variants.iterator().next(), entries),
- Binding.createFrom(variants.iterator().next())));
+ if (variants.size() == 1) { // Shortcut
+ return List.of(new Value<>(substituteIfRelative(value, variants.iterator().next(), entries),
+ variants.iterator().next()));
}
List<Value<VALUE>> values = new ArrayList<>(variants.size());
- for (DimensionBinding variant : variants) {
- values.add(new Value<>(substituteIfRelative(value, variant, entries), Binding.createFrom(variant)));
+ for (Binding variant : variants) {
+ values.add(new Value<>(substituteIfRelative(value, variant, entries), variant));
}
return values;
}
@@ -171,7 +218,7 @@ public class DimensionalValue<VALUE> {
// TODO: Move this
@SuppressWarnings("unchecked")
private VALUE substituteIfRelative(VALUE value,
- DimensionBinding variant,
+ Binding variant,
Map<CompoundName, DimensionalValue.Builder<VALUE>> entries) {
if (value instanceof ValueWithSource && ((ValueWithSource)value).value() instanceof SubstituteString) {
ValueWithSource valueWithSource = (ValueWithSource)value;
@@ -185,7 +232,7 @@ public class DimensionalValue<VALUE> {
if (substituteValues == null)
throw new IllegalArgumentException("Could not resolve local substitution '" +
relativeComponent.fieldName() + "' in variant " +
- variant);
+ Arrays.toString(variant.dimensionValues()));
ValueWithSource resolved = (ValueWithSource)substituteValues.valueFor(variant);
resolvedComponents.add(new SubstituteString.StringComponent(resolved.value().toString()));
}
@@ -212,4 +259,38 @@ public class DimensionalValue<VALUE> {
}
+ /** A list of dimensions for which there exist one or more bindings in this */
+ static class BindingSpec {
+
+ /** The dimensions of this. Unenforced invariant: Content never changes. */
+ private final String[] dimensions;
+
+ public BindingSpec(Binding binding) {
+ this.dimensions = binding.dimensions();
+ }
+
+ /** Do not change the returned array */
+ String[] dimensions() { return dimensions; }
+
+ /** Returns whether this context contains all the keys of this */
+ public boolean matches(Map<String, String> context) {
+ for (int i = 0; i < dimensions.length; i++)
+ if ( ! context.containsKey(dimensions[i])) return false;
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(dimensions);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) return true;
+ if ( ! (other instanceof BindingSpec)) return false;
+ return Arrays.equals(((BindingSpec)other).dimensions, this.dimensions);
+ }
+
+ }
+
}
diff --git a/container-search/src/main/java/com/yahoo/search/query/profile/compiled/ValueWithSource.java b/container-search/src/main/java/com/yahoo/search/query/profile/compiled/ValueWithSource.java
index 925d20903c6..53a29b74813 100644
--- a/container-search/src/main/java/com/yahoo/search/query/profile/compiled/ValueWithSource.java
+++ b/container-search/src/main/java/com/yahoo/search/query/profile/compiled/ValueWithSource.java
@@ -2,7 +2,9 @@
package com.yahoo.search.query.profile.compiled;
import com.yahoo.search.query.profile.DimensionValues;
+import com.yahoo.search.query.profile.types.QueryProfileType;
+import java.util.Objects;
import java.util.Optional;
/**
@@ -14,35 +16,91 @@ public class ValueWithSource {
private final Object value;
- /** The source of the query profile having a value */
private final String source;
+ private final boolean isUnoverridable;
+
+ private final boolean isQueryProfile;
+
+ private final QueryProfileType type;
+
/** The dimension values specifying a variant in that profile, or null if it is not in a variant */
private final DimensionValues variant;
- public ValueWithSource(Object value, String source, DimensionValues variant) {
+ public ValueWithSource(Object value,
+ String source,
+ boolean isUnoverridable, boolean isQueryProfile, QueryProfileType type,
+ DimensionValues variant) {
this.value = value;
this.source = source;
+ this.isUnoverridable = isUnoverridable;
+ this.isQueryProfile = isQueryProfile;
+ this.type = type;
this.variant = variant;
}
+ /**
+ * Returns the value at this key, or null if none
+ * (in which case this is references a query profile which has no value set).
+ */
public Object value() { return value; }
+ /** Returns the source of the query profile having a value */
public String source() { return source; }
+ /** Returns true if this value cannot be overridden in queries */
+ public boolean isUnoverridable() { return isUnoverridable; }
+
+ /**
+ * Returns true if this key references a query profile (i.e a non-leaf).
+ * In this case the value may or may not be null, as non-leafs may have values.
+ */
+ public boolean isQueryProfile() { return isQueryProfile; }
+
+ /** Returns tye type of this if it refers to a query profile (not a leaf value), and it has a type */
+ public QueryProfileType queryProfileType() { return type; }
+
public ValueWithSource withValue(Object value) {
- return new ValueWithSource(value, source, variant);
+ return new ValueWithSource(value, source, isUnoverridable, isQueryProfile, type, variant);
+ }
+
+ public ValueWithSource withSource(String source) {
+ return new ValueWithSource(value, source, isUnoverridable, isQueryProfile, type, variant);
+ }
+
+ public ValueWithSource withVariant(Optional<DimensionValues> variant) {
+ return new ValueWithSource(value, source, isUnoverridable, isQueryProfile, type, variant.orElse(null));
}
/** Returns the variant having this value, or empty if it's not in a variant */
public Optional<DimensionValues> variant() { return Optional.ofNullable(variant); }
@Override
+ public int hashCode() {
+ // Value is always a value object. Don't include source in identity.
+ return Objects.hash(value, isUnoverridable, type);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) return true;
+ if ( ! (o instanceof ValueWithSource)) return false;
+
+ ValueWithSource other = (ValueWithSource)o;
+ if ( ! Objects.equals(this.value, other.value)) return false;
+ if ( ! Objects.equals(this.isUnoverridable, other.isUnoverridable)) return false;
+ if ( ! Objects.equals(this.type, other.type)) return false;
+ return true;
+ }
+
+ @Override
public String toString() {
- return value +
- " (from query profile '" + source + "'" +
+ if (source == null && variant == null) return value.toString();
+
+ return value + " (" +
+ ( source != null ? "from query profile '" + source + "'" : "") +
( variant != null ? " variant " + variant : "") +
- ")";
+ ")";
}
}
diff --git a/container-search/src/main/java/com/yahoo/search/query/profile/config/QueryProfileConfigurer.java b/container-search/src/main/java/com/yahoo/search/query/profile/config/QueryProfileConfigurer.java
index d3c232f84c5..423348454d0 100644
--- a/container-search/src/main/java/com/yahoo/search/query/profile/config/QueryProfileConfigurer.java
+++ b/container-search/src/main/java/com/yahoo/search/query/profile/config/QueryProfileConfigurer.java
@@ -121,16 +121,20 @@ public class QueryProfileConfigurer implements ConfigSubscriber.SingleSubscriber
QueryProfile referenced = registry.getComponent(referenceConfig.value());
if (referenced == null)
throw new IllegalArgumentException("Query profile '" + referenceConfig.value() + "' referenced as '" +
- referenceConfig.name() + "' in " + profile + " was not found");
+ referenceConfig.name() + "' in " + profile + " was not found");
profile.set(referenceConfig.name(), referenced, registry);
if (referenceConfig.overridable() != null && !referenceConfig.overridable().isEmpty())
- profile.setOverridable(referenceConfig.name(), BooleanParser.parseBoolean(referenceConfig.overridable()), null);
+ profile.setOverridable(referenceConfig.name(),
+ BooleanParser.parseBoolean(referenceConfig.overridable()),
+ DimensionValues.empty);
}
for (QueryProfilesConfig.Queryprofile.Property propertyConfig : config.property()) {
profile.set(propertyConfig.name(), propertyConfig.value(), registry);
if (propertyConfig.overridable() != null && ! propertyConfig.overridable().isEmpty())
- profile.setOverridable(propertyConfig.name(), BooleanParser.parseBoolean(propertyConfig.overridable()), null);
+ profile.setOverridable(propertyConfig.name(),
+ BooleanParser.parseBoolean(propertyConfig.overridable()),
+ DimensionValues.empty);
}
for (QueryProfilesConfig.Queryprofile.Queryprofilevariant variantConfig : config.queryprofilevariant()) {
@@ -152,15 +156,24 @@ public class QueryProfileConfigurer implements ConfigSubscriber.SingleSubscriber
}
for (QueryProfilesConfig.Queryprofile.Queryprofilevariant.Reference referenceConfig : variantConfig.reference()) {
- QueryProfile referenced=registry.getComponent(referenceConfig.value());
+ QueryProfile referenced = registry.getComponent(referenceConfig.value());
if (referenced == null)
throw new IllegalArgumentException("Query profile '" + referenceConfig.value() + "' referenced as '" +
- referenceConfig.name() + "' in " + profile + " for '" + forDimensionValues + "' was not found");
+ referenceConfig.name() + "' in " + profile +
+ " for '" + forDimensionValues + "' was not found");
profile.set(referenceConfig.name(), referenced, forDimensionValues, registry);
+ if ( ! referenceConfig.overridable().isEmpty())
+ profile.setOverridable(referenceConfig.name(),
+ Boolean.parseBoolean(referenceConfig.overridable()),
+ forDimensionValues);
}
for (QueryProfilesConfig.Queryprofile.Queryprofilevariant.Property propertyConfig : variantConfig.property()) {
profile.set(propertyConfig.name(), propertyConfig.value(), forDimensionValues, registry);
+ if ( ! propertyConfig.overridable().isEmpty())
+ profile.setOverridable(propertyConfig.name(),
+ Boolean.parseBoolean(propertyConfig.overridable()),
+ forDimensionValues);
}
}
diff --git a/container-search/src/main/java/com/yahoo/search/query/profile/config/QueryProfileXMLReader.java b/container-search/src/main/java/com/yahoo/search/query/profile/config/QueryProfileXMLReader.java
index 1b1cdce5890..a64f2087d1f 100644
--- a/container-search/src/main/java/com/yahoo/search/query/profile/config/QueryProfileXMLReader.java
+++ b/container-search/src/main/java/com/yahoo/search/query/profile/config/QueryProfileXMLReader.java
@@ -41,21 +41,21 @@ public class QueryProfileXMLReader {
try {
File dir = new File(directory);
if ( ! dir.isDirectory() ) throw new IllegalArgumentException("Could not read query profiles: '" +
- directory + "' is not a valid directory.");
+ directory + "' is not a valid directory.");
for (File file : sortFiles(dir)) {
if ( ! file.getName().endsWith(".xml")) continue;
- queryProfileReaders.add(new NamedReader(file.getName(),new FileReader(file)));
+ queryProfileReaders.add(new NamedReader(file.getName(), new FileReader(file)));
}
File typeDir=new File(dir,"types");
if (typeDir.isDirectory()) {
for (File file : sortFiles(typeDir)) {
if ( ! file.getName().endsWith(".xml")) continue;
- queryProfileTypeReaders.add(new NamedReader(file.getName(),new FileReader(file)));
+ queryProfileTypeReaders.add(new NamedReader(file.getName(), new FileReader(file)));
}
}
- return read(queryProfileTypeReaders,queryProfileReaders);
+ return read(queryProfileTypeReaders, queryProfileReaders);
}
catch (IOException e) {
throw new IllegalArgumentException("Could not read query profiles from '" + directory + "'", e);
@@ -235,7 +235,7 @@ public class QueryProfileXMLReader {
QueryProfile profile = registry.getComponent(new ComponentSpecification(element.getAttribute("id")).toId());
try {
readInherited(element, profile, registry,null, profile.toString());
- readFields(element, profile, registry,null, profile.toString());
+ readFields(element, profile, registry,DimensionValues.empty, profile.toString());
readVariants(element, profile, registry);
}
catch (RuntimeException e) {
@@ -268,10 +268,9 @@ public class QueryProfileXMLReader {
if (name == null || name.equals(""))
throw new IllegalArgumentException("A field in " + sourceDescription + " has no 'name' attribute");
try {
- Boolean overridable = getBooleanAttribute("overridable",null,field);
+ Boolean overridable = getBooleanAttribute("overridable", null, field);
if (overridable != null)
- profile.setOverridable(name, overridable, null);
-
+ profile.setOverridable(name, overridable, dimensionValues.asContext(profile.getDimensions()));
Object fieldValue = readFieldValue(field, name, sourceDescription, registry);
if (fieldValue instanceof QueryProfile)
references.add(new KeyValue(name, fieldValue));
@@ -358,8 +357,8 @@ public class QueryProfileXMLReader {
private static class KeyValue {
- private String key;
- private Object value;
+ private final String key;
+ private final Object value;
public KeyValue(String key, Object value) {
this.key = key;
diff --git a/container-search/src/main/java/com/yahoo/search/query/profile/types/ConversionContext.java b/container-search/src/main/java/com/yahoo/search/query/profile/types/ConversionContext.java
new file mode 100644
index 00000000000..e5b9eb1c1cd
--- /dev/null
+++ b/container-search/src/main/java/com/yahoo/search/query/profile/types/ConversionContext.java
@@ -0,0 +1,40 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.search.query.profile.types;
+
+import com.yahoo.language.Language;
+import com.yahoo.language.process.Embedder;
+import com.yahoo.search.query.profile.compiled.CompiledQueryProfileRegistry;
+
+import java.util.Map;
+
+/**
+ * @author bratseth
+ */
+public class ConversionContext {
+
+ private final CompiledQueryProfileRegistry registry;
+ private final Embedder embedder;
+ private final Language language;
+
+ public ConversionContext(CompiledQueryProfileRegistry registry, Embedder embedder, Map<String, String> context) {
+ this.registry = registry;
+ this.embedder = embedder;
+ this.language = context.containsKey("language") ? Language.fromLanguageTag(context.get("language"))
+ : Language.UNKNOWN;
+ }
+
+ /** Returns the profile registry, or null if none */
+ CompiledQueryProfileRegistry getRegistry() {return registry;}
+
+ /** Returns the configured encoder, never null */
+ Embedder getEncoder() { return embedder; }
+
+ /** Returns the language, which is never null but may be UNKNOWN */
+ Language getLanguage() { return language; }
+
+ /** Returns an empty context */
+ public static ConversionContext empty() {
+ return new ConversionContext(null, Embedder.throwsOnUse, Map.of());
+ }
+
+}
diff --git a/container-search/src/main/java/com/yahoo/search/query/profile/types/FieldDescription.java b/container-search/src/main/java/com/yahoo/search/query/profile/types/FieldDescription.java
index 6c30f1a8b05..7f8836ef2c1 100644
--- a/container-search/src/main/java/com/yahoo/search/query/profile/types/FieldDescription.java
+++ b/container-search/src/main/java/com/yahoo/search/query/profile/types/FieldDescription.java
@@ -33,7 +33,7 @@ public class FieldDescription implements Comparable<FieldDescription> {
}
public FieldDescription(String name, String type) {
- this(name,FieldType.fromString(type,null));
+ this(name,FieldType.fromString(type, null));
}
public FieldDescription(String name, FieldType type, boolean mandatory) {
@@ -60,7 +60,7 @@ public class FieldDescription implements Comparable<FieldDescription> {
* @param overridable whether this can be overridden when first set in a profile. Default: true
*/
public FieldDescription(String name, String typeString, String aliases, boolean mandatory, boolean overridable) {
- this(name,FieldType.fromString(typeString,null),aliases,mandatory,overridable);
+ this(name,FieldType.fromString(typeString, null), aliases, mandatory, overridable);
}
public FieldDescription(String name, FieldType type, boolean mandatory, boolean overridable) {
@@ -97,7 +97,8 @@ public class FieldDescription implements Comparable<FieldDescription> {
this.type = type;
// Forbidden until we can figure out the right semantics
- if (name.isCompound() && ! aliases.isEmpty()) throw new IllegalArgumentException("Aliases are not allowed with compound names");
+ if (name.isCompound() && ! aliases.isEmpty())
+ throw new IllegalArgumentException("Aliases are not allowed with compound names");
this.aliases = ImmutableList.copyOf(aliases);
this.mandatory = mandatory;
diff --git a/container-search/src/main/java/com/yahoo/search/query/profile/types/FieldType.java b/container-search/src/main/java/com/yahoo/search/query/profile/types/FieldType.java
index 3bfd33668e6..7a06f9ef534 100644
--- a/container-search/src/main/java/com/yahoo/search/query/profile/types/FieldType.java
+++ b/container-search/src/main/java/com/yahoo/search/query/profile/types/FieldType.java
@@ -3,7 +3,6 @@ package com.yahoo.search.query.profile.types;
import com.yahoo.search.query.profile.QueryProfile;
import com.yahoo.search.query.profile.QueryProfileRegistry;
-import com.yahoo.search.query.profile.compiled.CompiledQueryProfileRegistry;
import com.yahoo.search.yql.YqlQuery;
import com.yahoo.tensor.Tensor;
import com.yahoo.tensor.TensorType;
@@ -41,7 +40,7 @@ public abstract class FieldType {
public abstract Object convertFrom(Object o, QueryProfileRegistry registry);
/** Converts the given type to an instance of this type, if possible. Returns null if not possible. */
- public abstract Object convertFrom(Object o, CompiledQueryProfileRegistry registry);
+ public abstract Object convertFrom(Object o, ConversionContext context);
/**
* Returns this type as a tensor type: The true tensor type is this is a tensor field an an empty type -
@@ -77,7 +76,7 @@ public abstract class FieldType {
if ("query-profile".equals(typeString))
return genericQueryProfileType;
if (typeString.startsWith("query-profile:"))
- return QueryProfileFieldType.fromString(typeString.substring("query-profile:".length()),registry);
+ return QueryProfileFieldType.fromString(typeString.substring("query-profile:".length()), registry);
throw new IllegalArgumentException("Unknown type '" + typeString + "'");
}
diff --git a/container-search/src/main/java/com/yahoo/search/query/profile/types/PrimitiveFieldType.java b/container-search/src/main/java/com/yahoo/search/query/profile/types/PrimitiveFieldType.java
index 1e904e4f970..f9d8950908b 100644
--- a/container-search/src/main/java/com/yahoo/search/query/profile/types/PrimitiveFieldType.java
+++ b/container-search/src/main/java/com/yahoo/search/query/profile/types/PrimitiveFieldType.java
@@ -2,7 +2,6 @@
package com.yahoo.search.query.profile.types;
import com.yahoo.search.query.profile.QueryProfileRegistry;
-import com.yahoo.search.query.profile.compiled.CompiledQueryProfileRegistry;
import static com.yahoo.text.Lowercase.toLowerCase;
@@ -37,7 +36,7 @@ public class PrimitiveFieldType extends FieldType {
}
@Override
- public Object convertFrom(Object object, CompiledQueryProfileRegistry registry) {
+ public Object convertFrom(Object object, ConversionContext context) {
return convertFrom(object, (QueryProfileRegistry)null);
}
diff --git a/container-search/src/main/java/com/yahoo/search/query/profile/types/QueryFieldType.java b/container-search/src/main/java/com/yahoo/search/query/profile/types/QueryFieldType.java
index 1797a2bd59f..cbae6402039 100644
--- a/container-search/src/main/java/com/yahoo/search/query/profile/types/QueryFieldType.java
+++ b/container-search/src/main/java/com/yahoo/search/query/profile/types/QueryFieldType.java
@@ -2,7 +2,6 @@
package com.yahoo.search.query.profile.types;
import com.yahoo.search.query.profile.QueryProfileRegistry;
-import com.yahoo.search.query.profile.compiled.CompiledQueryProfileRegistry;
import com.yahoo.search.yql.YqlQuery;
/**
@@ -32,7 +31,7 @@ public class QueryFieldType extends FieldType {
}
@Override
- public Object convertFrom(Object o, CompiledQueryProfileRegistry registry) {
+ public Object convertFrom(Object o, ConversionContext context) {
return convertFrom(o, (QueryProfileRegistry)null);
}
diff --git a/container-search/src/main/java/com/yahoo/search/query/profile/types/QueryProfileFieldType.java b/container-search/src/main/java/com/yahoo/search/query/profile/types/QueryProfileFieldType.java
index fda2d27e682..ff12224823f 100644
--- a/container-search/src/main/java/com/yahoo/search/query/profile/types/QueryProfileFieldType.java
+++ b/container-search/src/main/java/com/yahoo/search/query/profile/types/QueryProfileFieldType.java
@@ -4,7 +4,6 @@ package com.yahoo.search.query.profile.types;
import com.yahoo.search.query.profile.QueryProfile;
import com.yahoo.search.query.profile.QueryProfileRegistry;
import com.yahoo.search.query.profile.compiled.CompiledQueryProfile;
-import com.yahoo.search.query.profile.compiled.CompiledQueryProfileRegistry;
/**
* Represents a query profile field type which is a reference to a query profile.
@@ -57,11 +56,11 @@ public class QueryProfileFieldType extends FieldType {
}
@Override
- public CompiledQueryProfile convertFrom(Object object, CompiledQueryProfileRegistry registry) {
+ public CompiledQueryProfile convertFrom(Object object, ConversionContext context) {
String profileId = object.toString();
if (profileId.startsWith("ref:"))
profileId = profileId.substring("ref:".length());
- CompiledQueryProfile profile = registry.getComponent(profileId);
+ CompiledQueryProfile profile = context.getRegistry().getComponent(profileId);
if (profile == null) return null;
if (type != null && ! type.equals(profile.getType())) return null;
return profile;
diff --git a/container-search/src/main/java/com/yahoo/search/query/profile/types/QueryProfileType.java b/container-search/src/main/java/com/yahoo/search/query/profile/types/QueryProfileType.java
index c02aada2062..e4396894595 100644
--- a/container-search/src/main/java/com/yahoo/search/query/profile/types/QueryProfileType.java
+++ b/container-search/src/main/java/com/yahoo/search/query/profile/types/QueryProfileType.java
@@ -177,11 +177,12 @@ public class QueryProfileType extends FreezableSimpleComponent {
public void freeze() {
if (isFrozen()) return;
- // Flatten the inheritance hierarchy into this to facilitate faster lookup
+ // Flatten for faster lookup
for (QueryProfileType inheritedType : inherited) {
for (FieldDescription field : inheritedType.fields().values())
- if ( ! fields.containsKey(field.getName()))
- fields.put(field.getName(),field);
+ if ( ! fields.containsKey(field.getName())) {
+ fields.put(field.getName(), field);
+ }
}
fields = ImmutableMap.copyOf(fields);
inherited = ImmutableList.copyOf(inherited);
@@ -354,9 +355,10 @@ public class QueryProfileType extends FreezableSimpleComponent {
if (inherited().size() == 0) return Collections.unmodifiableMap(fields);
// Collapse inherited
- Map<String, FieldDescription> allFields = new HashMap<>(fields);
+ Map<String, FieldDescription> allFields = new HashMap<>();
for (QueryProfileType inheritedType : inherited)
allFields.putAll(inheritedType.fields());
+ allFields.putAll(fields);
return Collections.unmodifiableMap(allFields);
}
diff --git a/container-search/src/main/java/com/yahoo/search/query/profile/types/TensorFieldType.java b/container-search/src/main/java/com/yahoo/search/query/profile/types/TensorFieldType.java
index 9699a72cb31..cd21f0b3a61 100644
--- a/container-search/src/main/java/com/yahoo/search/query/profile/types/TensorFieldType.java
+++ b/container-search/src/main/java/com/yahoo/search/query/profile/types/TensorFieldType.java
@@ -1,8 +1,9 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.search.query.profile.types;
+import com.yahoo.language.Language;
+import com.yahoo.language.process.Embedder;
import com.yahoo.search.query.profile.QueryProfileRegistry;
-import com.yahoo.search.query.profile.compiled.CompiledQueryProfileRegistry;
import com.yahoo.tensor.Tensor;
import com.yahoo.tensor.TensorType;
@@ -38,14 +39,26 @@ public class TensorFieldType extends FieldType {
@Override
public Object convertFrom(Object o, QueryProfileRegistry registry) {
+ return convertFrom(o, ConversionContext.empty());
+ }
+
+ @Override
+ public Object convertFrom(Object o, ConversionContext context) {
+ return convertFrom(o, context.getEncoder(), context.getLanguage());
+ }
+
+ private Object convertFrom(Object o, Embedder embedder, Language language) {
if (o instanceof Tensor) return o;
+ if (o instanceof String && ((String)o).startsWith("embed(")) return encode((String)o, embedder, language);
if (o instanceof String) return Tensor.from(type, (String)o);
return null;
}
- @Override
- public Object convertFrom(Object o, CompiledQueryProfileRegistry registry) {
- return convertFrom(o, (QueryProfileRegistry)null);
+ private Tensor encode(String s, Embedder embedder, Language language) {
+ if ( ! s.endsWith(")"))
+ throw new IllegalArgumentException("Expected any string enclosed in embed(), but the argument does not end by ')'");
+ String text = s.substring("embed(".length(), s.length() - 1);
+ return embedder.embed(text, language, type);
}
public static TensorFieldType fromTypeString(String s) {
diff --git a/container-search/src/main/java/com/yahoo/search/query/properties/PropertyAliases.java b/container-search/src/main/java/com/yahoo/search/query/properties/PropertyAliases.java
index a4a82d27f8e..83e8dd530ad 100644
--- a/container-search/src/main/java/com/yahoo/search/query/properties/PropertyAliases.java
+++ b/container-search/src/main/java/com/yahoo/search/query/properties/PropertyAliases.java
@@ -37,6 +37,8 @@ public class PropertyAliases extends Properties {
* @return the real name if an alias or the input name itself
*/
protected CompoundName unalias(CompoundName nameOrAlias) {
+ if (aliases.isEmpty()) return nameOrAlias;
+ if (nameOrAlias.size() > 1) return nameOrAlias; // aliases are simple names
CompoundName properName = aliases.get(nameOrAlias.getLowerCasedName());
return (properName != null) ? properName : nameOrAlias;
}
diff --git a/container-search/src/main/java/com/yahoo/search/query/properties/PropertyMap.java b/container-search/src/main/java/com/yahoo/search/query/properties/PropertyMap.java
index 4f30331e738..643e215daef 100644
--- a/container-search/src/main/java/com/yahoo/search/query/properties/PropertyMap.java
+++ b/container-search/src/main/java/com/yahoo/search/query/properties/PropertyMap.java
@@ -26,7 +26,10 @@ public class PropertyMap extends Properties {
/** The properties of this */
private Map<CompoundName, Object> properties = new LinkedHashMap<>();
- public void set(CompoundName name, Object value, Map<String,String> context) {
+ public void set(CompoundName name, Object value, Map<String, String> context) {
+ if (value == null) // Both clear and forward
+ properties.remove(name);
+
if (shouldSet(name, value))
properties.put(name, value);
else
diff --git a/container-search/src/main/java/com/yahoo/search/query/properties/QueryProperties.java b/container-search/src/main/java/com/yahoo/search/query/properties/QueryProperties.java
index dfe6c2af44b..3a426656185 100644
--- a/container-search/src/main/java/com/yahoo/search/query/properties/QueryProperties.java
+++ b/container-search/src/main/java/com/yahoo/search/query/properties/QueryProperties.java
@@ -1,6 +1,8 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.search.query.properties;
+import com.yahoo.language.process.Embedder;
+import com.yahoo.processing.IllegalInputException;
import com.yahoo.processing.request.CompoundName;
import com.yahoo.search.Query;
@@ -10,6 +12,7 @@ import com.yahoo.search.query.Properties;
import com.yahoo.search.query.Ranking;
import com.yahoo.search.query.Select;
import com.yahoo.search.query.profile.compiled.CompiledQueryProfileRegistry;
+import com.yahoo.search.query.profile.types.ConversionContext;
import com.yahoo.search.query.profile.types.FieldDescription;
import com.yahoo.search.query.profile.types.QueryProfileType;
import com.yahoo.search.query.ranking.Diversity;
@@ -31,10 +34,12 @@ public class QueryProperties extends Properties {
private Query query;
private final CompiledQueryProfileRegistry profileRegistry;
+ private final Embedder embedder;
- public QueryProperties(Query query, CompiledQueryProfileRegistry profileRegistry) {
+ public QueryProperties(Query query, CompiledQueryProfileRegistry profileRegistry, Embedder embedder) {
this.query = query;
this.profileRegistry = profileRegistry;
+ this.embedder = embedder;
}
public void setParentQuery(Query query) {
@@ -67,9 +72,10 @@ public class QueryProperties extends Properties {
if (key.last().equals(Ranking.SORTING)) return ranking.getSorting();
if (key.last().equals(Ranking.FRESHNESS)) return ranking.getFreshness();
if (key.last().equals(Ranking.QUERYCACHE)) return ranking.getQueryCache();
+ if (key.last().equals(Ranking.RERANKCOUNT)) return ranking.getRerankCount();
if (key.last().equals(Ranking.LIST_FEATURES)) return ranking.getListFeatures();
}
- else if (key.size()>=3 && key.get(1).equals(Ranking.MATCH_PHASE)) {
+ else if (key.size() >= 3 && key.get(1).equals(Ranking.MATCH_PHASE)) {
if (key.size() == 3) {
MatchPhase matchPhase = ranking.getMatchPhase();
if (key.last().equals(MatchPhase.ATTRIBUTE)) return matchPhase.getAttribute();
@@ -144,7 +150,6 @@ public class QueryProperties extends Properties {
return super.get(key, context, substitution);
}
- @SuppressWarnings("deprecation")
@Override
public void set(CompoundName key, Object value, Map<String,String> context) {
// Note: The defaults here are never used
@@ -172,7 +177,7 @@ public class QueryProperties extends Properties {
else if (key.last().equals(Model.RESTRICT))
model.setRestrict(asString(value,""));
else
- throwIllegalParameter(key.last(),Model.MODEL);
+ throwIllegalParameter(key.last(), Model.MODEL);
}
else if (key.first().equals(Ranking.RANKING)) {
Ranking ranking = query.getRanking();
@@ -187,57 +192,85 @@ public class QueryProperties extends Properties {
ranking.setFreshness(asString(value, ""));
else if (key.last().equals(Ranking.QUERYCACHE))
ranking.setQueryCache(asBoolean(value, false));
+ else if (key.last().equals(Ranking.RERANKCOUNT))
+ ranking.setRerankCount(asInteger(value, null));
else if (key.last().equals(Ranking.LIST_FEATURES))
ranking.setListFeatures(asBoolean(value,false));
+ else
+ throwIllegalParameter(key.last(), Ranking.RANKING);
}
- else if (key.size()>=3 && key.get(1).equals(Ranking.MATCH_PHASE)) {
+ else if (key.size() >= 3 && key.get(1).equals(Ranking.MATCH_PHASE)) {
if (key.size() == 3) {
MatchPhase matchPhase = ranking.getMatchPhase();
- if (key.last().equals(MatchPhase.ATTRIBUTE)) {
+ if (key.last().equals(MatchPhase.ATTRIBUTE))
matchPhase.setAttribute(asString(value, null));
- } else if (key.last().equals(MatchPhase.ASCENDING)) {
+ else if (key.last().equals(MatchPhase.ASCENDING))
matchPhase.setAscending(asBoolean(value, false));
- } else if (key.last().equals(MatchPhase.MAX_HITS)) {
+ else if (key.last().equals(MatchPhase.MAX_HITS))
matchPhase.setMaxHits(asLong(value, null));
- } else if (key.last().equals(MatchPhase.MAX_FILTER_COVERAGE)) {
+ else if (key.last().equals(MatchPhase.MAX_FILTER_COVERAGE))
matchPhase.setMaxFilterCoverage(asDouble(value, 0.2));
- }
+ else
+ throwIllegalParameter(key.rest().toString(), Ranking.MATCH_PHASE);
} else if (key.size() > 3 && key.get(2).equals(Ranking.DIVERSITY)) {
Diversity diversity = ranking.getMatchPhase().getDiversity();
if (key.last().equals(Diversity.ATTRIBUTE)) {
diversity.setAttribute(asString(value, null));
- } else if (key.last().equals(Diversity.MINGROUPS)) {
+ }
+ else if (key.last().equals(Diversity.MINGROUPS)) {
diversity.setMinGroups(asLong(value, null));
- } else if ((key.size() > 4) && key.get(3).equals(Diversity.CUTOFF)) {
- if (key.last().equals(Diversity.FACTOR)) {
+ }
+ else if ((key.size() > 4) && key.get(3).equals(Diversity.CUTOFF)) {
+ if (key.last().equals(Diversity.FACTOR))
diversity.setCutoffFactor(asDouble(value, 10.0));
- } else if (key.last().equals(Diversity.STRATEGY)) {
+ else if (key.last().equals(Diversity.STRATEGY))
diversity.setCutoffStrategy(asString(value, "loose"));
- }
+ else
+ throwIllegalParameter(key.rest().toString(), Diversity.CUTOFF);
+ }
+ else {
+ throwIllegalParameter(key.rest().toString(), Ranking.DIVERSITY);
}
}
}
else if (key.size() == 3 && key.get(1).equals(Ranking.SOFTTIMEOUT)) {
SoftTimeout soft = ranking.getSoftTimeout();
- if (key.last().equals(SoftTimeout.ENABLE)) soft.setEnable(asBoolean(value, true));
- if (key.last().equals(SoftTimeout.FACTOR)) soft.setFactor(asDouble(value, null));
- if (key.last().equals(SoftTimeout.TAILCOST)) soft.setTailcost(asDouble(value, null));
+ if (key.last().equals(SoftTimeout.ENABLE))
+ soft.setEnable(asBoolean(value, true));
+ else if (key.last().equals(SoftTimeout.FACTOR))
+ soft.setFactor(asDouble(value, null));
+ else if (key.last().equals(SoftTimeout.TAILCOST))
+ soft.setTailcost(asDouble(value, null));
+ else
+ throwIllegalParameter(key.rest().toString(), Ranking.SOFTTIMEOUT);
}
else if (key.size() == 3 && key.get(1).equals(Ranking.MATCHING)) {
Matching matching = ranking.getMatching();
- if (key.last().equals(Matching.TERMWISELIMIT)) matching.setTermwiselimit(asDouble(value, 1.0));
- if (key.last().equals(Matching.NUMTHREADSPERSEARCH)) matching.setNumThreadsPerSearch(asInteger(value, 1));
- if (key.last().equals(Matching.NUMSEARCHPARTITIIONS)) matching.setNumSearchPartitions(asInteger(value, 1));
- if (key.last().equals(Matching.MINHITSPERTHREAD)) matching.setMinHitsPerThread(asInteger(value, 0));
+ if (key.last().equals(Matching.TERMWISELIMIT))
+ matching.setTermwiselimit(asDouble(value, 1.0));
+ else if (key.last().equals(Matching.NUMTHREADSPERSEARCH))
+ matching.setNumThreadsPerSearch(asInteger(value, 1));
+ else if (key.last().equals(Matching.NUMSEARCHPARTITIIONS))
+ matching.setNumSearchPartitions(asInteger(value, 1));
+ else if (key.last().equals(Matching.MINHITSPERTHREAD))
+ matching.setMinHitsPerThread(asInteger(value, 0));
+ else
+ throwIllegalParameter(key.rest().toString(), Ranking.MATCHING);
}
else if (key.size() > 2) {
String restKey = key.rest().rest().toString();
if (key.get(1).equals(Ranking.FEATURES))
- setRankingFeature(query, restKey, toSpecifiedType(restKey, value, profileRegistry.getTypeRegistry().getComponent("features")));
+ setRankingFeature(query, restKey, toSpecifiedType(restKey,
+ value,
+ profileRegistry.getTypeRegistry().getComponent("features"),
+ context));
else if (key.get(1).equals(Ranking.PROPERTIES))
- ranking.getProperties().put(restKey, toSpecifiedType(restKey, value, profileRegistry.getTypeRegistry().getComponent("properties")));
+ ranking.getProperties().put(restKey, toSpecifiedType(restKey,
+ value,
+ profileRegistry.getTypeRegistry().getComponent("properties"),
+ context));
else
- throwIllegalParameter(key.rest().toString(),Ranking.RANKING);
+ throwIllegalParameter(key.rest().toString(), Ranking.RANKING);
}
}
else if (key.size() == 2 && key.first().equals(Presentation.PRESENTATION)) {
@@ -259,20 +292,27 @@ public class QueryProperties extends Properties {
query.getSelect().setGroupingExpressionString(asString(value, ""));
}
else if (key.size() == 2) {
- if (key.last().equals(Select.WHERE)) {
+ if (key.last().equals(Select.WHERE))
query.getSelect().setWhereString(asString(value, ""));
- } else if (key.last().equals(Select.GROUPING)) {
+ else if (key.last().equals(Select.GROUPING))
query.getSelect().setGroupingString(asString(value, ""));
- }
+ else
+ throwIllegalParameter(key.rest().toString(), Select.SELECT);
}
else {
throwIllegalParameter(key.last(), Select.SELECT);
}
}
else if (key.first().equals("rankfeature") || key.first().equals("featureoverride") ) { // featureoverride is deprecated
- setRankingFeature(query, key.rest().toString(), toSpecifiedType(key.rest().toString(), value, profileRegistry.getTypeRegistry().getComponent("features")));
+ setRankingFeature(query, key.rest().toString(), toSpecifiedType(key.rest().toString(),
+ value,
+ profileRegistry.getTypeRegistry().getComponent("features"),
+ context));
} else if (key.first().equals("rankproperty")) {
- query.getRanking().getProperties().put(key.rest().toString(), toSpecifiedType(key.rest().toString(), value, profileRegistry.getTypeRegistry().getComponent("properties")));
+ query.getRanking().getProperties().put(key.rest().toString(), toSpecifiedType(key.rest().toString(),
+ value,
+ profileRegistry.getTypeRegistry().getComponent("properties"),
+ context));
} else if (key.size()==1) {
if (key.equals(Query.HITS))
query.setHits(asInteger(value,10));
@@ -294,10 +334,10 @@ public class QueryProperties extends Properties {
super.set(key,value,context);
}
catch (Exception e) { // Make sure error messages are informative. This should be moved out of this properties implementation
- if (e.getMessage().startsWith("Could not set"))
+ if (e.getMessage() != null && e.getMessage().startsWith("Could not set"))
throw e;
else
- throw new IllegalArgumentException("Could not set '" + key + "' to '" + value + "'", e);
+ throw new IllegalInputException("Could not set '" + key + "' to '" + value + "'", e);
}
}
@@ -335,17 +375,17 @@ public class QueryProperties extends Properties {
}
}
- private Object toSpecifiedType(String key, Object value, QueryProfileType type) {
+ private Object toSpecifiedType(String key, Object value, QueryProfileType type, Map<String,String> context) {
if ( ! ( value instanceof String)) return value; // already typed
if (type == null) return value; // no type info -> keep as string
FieldDescription field = type.getField(key);
if (field == null) return value; // ditto
- return field.getType().convertFrom(value, profileRegistry);
+ return field.getType().convertFrom(value, new ConversionContext(profileRegistry, embedder, context));
}
private void throwIllegalParameter(String key,String namespace) {
- throw new IllegalArgumentException("'" + key + "' is not a valid property in '" + namespace +
- "'. See the search api for valid keys starting by '" + namespace + "'.");
+ throw new IllegalInputException("'" + key + "' is not a valid property in '" + namespace +
+ "'. See the query api for valid keys starting by '" + namespace + "'.");
}
@Override
diff --git a/container-search/src/main/java/com/yahoo/search/query/ranking/MatchPhase.java b/container-search/src/main/java/com/yahoo/search/query/ranking/MatchPhase.java
index 794247863bf..72a6533e946 100644
--- a/container-search/src/main/java/com/yahoo/search/query/ranking/MatchPhase.java
+++ b/container-search/src/main/java/com/yahoo/search/query/ranking/MatchPhase.java
@@ -1,6 +1,7 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.search.query.ranking;
+import com.yahoo.processing.IllegalInputException;
import com.yahoo.processing.request.CompoundName;
import com.yahoo.search.query.Ranking;
import com.yahoo.search.query.profile.types.FieldDescription;
@@ -84,8 +85,9 @@ public class MatchPhase implements Cloneable {
public void setMaxFilterCoverage(double maxFilterCoverage) {
if ((maxFilterCoverage < 0.0) || (maxFilterCoverage > 1.0)) {
- throw new IllegalArgumentException("maxFilterCoverage must be in the range [0.0, 1.0]. It is " + maxFilterCoverage);
+ throw new IllegalInputException("maxFilterCoverage must be in the range [0.0, 1.0]. It is " + maxFilterCoverage);
}
+
this.maxFilterCoverage = maxFilterCoverage;
}
diff --git a/container-search/src/main/java/com/yahoo/search/query/ranking/Matching.java b/container-search/src/main/java/com/yahoo/search/query/ranking/Matching.java
index fb3f2acfadd..14e4e006b39 100644
--- a/container-search/src/main/java/com/yahoo/search/query/ranking/Matching.java
+++ b/container-search/src/main/java/com/yahoo/search/query/ranking/Matching.java
@@ -1,6 +1,7 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.search.query.ranking;
+import com.yahoo.processing.IllegalInputException;
import com.yahoo.search.query.Ranking;
import com.yahoo.search.query.profile.types.FieldDescription;
import com.yahoo.search.query.profile.types.QueryProfileType;
@@ -59,7 +60,7 @@ public class Matching implements Cloneable {
public void setTermwiselimit(double value) {
if ((value < 0.0) || (value > 1.0)) {
- throw new IllegalArgumentException("termwiselimit must be in the range [0.0, 1.0]. It is " + value);
+ throw new IllegalInputException("termwiselimit must be in the range [0.0, 1.0]. It is " + value);
}
termwiseLimit = value;
}
diff --git a/container-search/src/main/java/com/yahoo/search/query/ranking/SoftTimeout.java b/container-search/src/main/java/com/yahoo/search/query/ranking/SoftTimeout.java
index 0d47ef77ce5..43c26692221 100644
--- a/container-search/src/main/java/com/yahoo/search/query/ranking/SoftTimeout.java
+++ b/container-search/src/main/java/com/yahoo/search/query/ranking/SoftTimeout.java
@@ -1,6 +1,7 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.search.query.ranking;
+import com.yahoo.processing.IllegalInputException;
import com.yahoo.processing.request.CompoundName;
import com.yahoo.search.query.Ranking;
import com.yahoo.search.query.profile.types.FieldDescription;
@@ -54,7 +55,7 @@ public class SoftTimeout implements Cloneable {
/** Override the adaptive factor determined on the content nodes */
public void setFactor(double factor) {
if ((factor < 0.0) || (factor > 1.0)) {
- throw new IllegalArgumentException("factor must be in the range [0.0, 1.0], got " + factor);
+ throw new IllegalInputException("factor must be in the range [0.0, 1.0], got " + factor);
}
this.factor = factor;
}
@@ -64,7 +65,7 @@ public class SoftTimeout implements Cloneable {
/** Override the tail cost factor determined on the content nodes */
public void setTailcost(double tailcost) {
if ((tailcost < 0.0) || (tailcost > 1.0)) {
- throw new IllegalArgumentException("tailcost must be in the range [0.0, 1.0], got " + tailcost);
+ throw new IllegalInputException("tailcost must be in the range [0.0, 1.0], got " + tailcost);
}
this.tailcost = tailcost;
}
diff --git a/container-search/src/main/java/com/yahoo/search/query/rewrite/RewriterUtils.java b/container-search/src/main/java/com/yahoo/search/query/rewrite/RewriterUtils.java
index b73ef1298ee..af73e905f20 100644
--- a/container-search/src/main/java/com/yahoo/search/query/rewrite/RewriterUtils.java
+++ b/container-search/src/main/java/com/yahoo/search/query/rewrite/RewriterUtils.java
@@ -2,7 +2,7 @@
package com.yahoo.search.query.rewrite;
import com.yahoo.fsa.FSA;
-import com.yahoo.log.LogLevel;
+import java.util.logging.Level;
import com.yahoo.search.Query;
import com.yahoo.search.intent.model.IntentModel;
import com.yahoo.search.intent.model.InterpretationNode;
@@ -289,7 +289,7 @@ public class RewriterUtils {
* @param msg Log message
*/
public static void log(Logger logger, String msg) {
- logger.log(LogLevel.DEBUG, logger.getName() + ": " + msg);
+ logger.log(Level.FINE, () -> logger.getName() + ": " + msg);
}
/**
@@ -303,7 +303,7 @@ public class RewriterUtils {
if(query!=null) {
query.trace(logger.getName() + ": " + msg, true, TRACELEVEL);
}
- logger.log(LogLevel.DEBUG, logger.getName() + ": " + msg);
+ logger.log(Level.FINE, () -> logger.getName() + ": " + msg);
}
/**
diff --git a/container-search/src/main/java/com/yahoo/search/query/rewrite/rewriters/GenericExpansionRewriter.java b/container-search/src/main/java/com/yahoo/search/query/rewrite/rewriters/GenericExpansionRewriter.java
index 794217e688f..9a2374da4c9 100644
--- a/container-search/src/main/java/com/yahoo/search/query/rewrite/rewriters/GenericExpansionRewriter.java
+++ b/container-search/src/main/java/com/yahoo/search/query/rewrite/rewriters/GenericExpansionRewriter.java
@@ -76,9 +76,8 @@ public class GenericExpansionRewriter extends QueryRewriteSearcher {
HashMap<String, File> fileList) {
logger = Logger.getLogger(GenericExpansionRewriter.class.getName());
FSA fsa = (FSA)rewriterDicts.get(GENERIC_EXPAND_DICT);
- if(fsa==null) {
- RewriterUtils.error(logger, "Error retrieving FSA dictionary: " +
- GENERIC_EXPAND_DICT);
+ if (fsa==null) {
+ RewriterUtils.error(logger, "Error retrieving FSA dictionary: " + GENERIC_EXPAND_DICT);
return false;
}
// Create Phrase Matcher
diff --git a/container-search/src/main/java/com/yahoo/search/query/textserialize/item/ItemContext.java b/container-search/src/main/java/com/yahoo/search/query/textserialize/item/ItemContext.java
index e739608387d..b6c886c91d7 100644
--- a/container-search/src/main/java/com/yahoo/search/query/textserialize/item/ItemContext.java
+++ b/container-search/src/main/java/com/yahoo/search/query/textserialize/item/ItemContext.java
@@ -12,6 +12,7 @@ import java.util.Map;
* @author Tony Vaagenes
*/
public class ItemContext {
+
private class Connectivity {
final String id;
final double strength;
@@ -43,7 +44,8 @@ public class ItemContext {
private Item getItem(String id) {
Item item = itemById.get(id);
if (item == null)
- throw new IllegalArgumentException("No item with id '" + id + "'.");
+ throw new IllegalArgumentException("No item with id '" + id + "'");
return item;
}
+
}
diff --git a/container-search/src/main/java/com/yahoo/search/query/textserialize/item/TermConverter.java b/container-search/src/main/java/com/yahoo/search/query/textserialize/item/TermConverter.java
index 04c01d7acc1..503987af027 100644
--- a/container-search/src/main/java/com/yahoo/search/query/textserialize/item/TermConverter.java
+++ b/container-search/src/main/java/com/yahoo/search/query/textserialize/item/TermConverter.java
@@ -10,6 +10,7 @@ import com.yahoo.search.query.textserialize.serializer.ItemIdMapper;
* @author Tony Vaagenes
*/
public abstract class TermConverter implements ItemFormConverter {
+
@Override
public Object formToItem(String name, ItemArguments arguments, ItemContext context) {
ensureOnlyOneChild(arguments);
@@ -22,7 +23,6 @@ public abstract class TermConverter implements ItemFormConverter {
abstract TermItem newTermItem(String word);
-
private void ensureOnlyOneChild(ItemArguments arguments) {
if (arguments.children.size() != 1) {
throw new IllegalArgumentException("Expected exactly one argument, got '" +
@@ -50,4 +50,5 @@ public abstract class TermConverter implements ItemFormConverter {
}
protected abstract String getValue(TermItem item);
+
}
diff --git a/container-search/src/main/java/com/yahoo/search/query/textserialize/item/TypeCheck.java b/container-search/src/main/java/com/yahoo/search/query/textserialize/item/TypeCheck.java
index b7843a300dc..70eba9d8b50 100644
--- a/container-search/src/main/java/com/yahoo/search/query/textserialize/item/TypeCheck.java
+++ b/container-search/src/main/java/com/yahoo/search/query/textserialize/item/TypeCheck.java
@@ -7,6 +7,7 @@ import com.yahoo.protect.Validator;
* @author Tony Vaagenes
*/
public class TypeCheck {
+
public static void ensureInstanceOf(Object object, Class<?> c) {
Validator.ensureInstanceOf(expectationString(c.getName(), object.getClass().getSimpleName()),
object, c);
@@ -24,4 +25,5 @@ public class TypeCheck {
private static String expectationString(String expected, String got) {
return "Expected " + expected + ", but got " + got;
}
+
}
diff --git a/container-search/src/main/java/com/yahoo/search/query/textserialize/serializer/Serializer.java b/container-search/src/main/java/com/yahoo/search/query/textserialize/serializer/Serializer.java
index d4e499bab79..d849e744184 100644
--- a/container-search/src/main/java/com/yahoo/search/query/textserialize/serializer/Serializer.java
+++ b/container-search/src/main/java/com/yahoo/search/query/textserialize/serializer/Serializer.java
@@ -14,6 +14,7 @@ import static com.yahoo.search.query.textserialize.item.ListUtil.first;
* @author Tony Vaagenes
*/
class Serializer {
+
static String serialize(Object child, ItemIdMapper itemIdMapper) {
if (child instanceof DispatchForm) {
return ((DispatchForm) child).serialize(itemIdMapper);
@@ -76,4 +77,5 @@ class Serializer {
static String serializeItem(Item item, ItemIdMapper itemIdMapper) {
return ItemExecutorRegistry.getByType(item.getItemType()).itemToForm(item, itemIdMapper).serialize(itemIdMapper);
}
+
}
diff --git a/container-search/src/main/java/com/yahoo/search/querytransform/BooleanAttributeParser.java b/container-search/src/main/java/com/yahoo/search/querytransform/BooleanAttributeParser.java
index fd76cc4397a..532de1270da 100644
--- a/container-search/src/main/java/com/yahoo/search/querytransform/BooleanAttributeParser.java
+++ b/container-search/src/main/java/com/yahoo/search/querytransform/BooleanAttributeParser.java
@@ -14,10 +14,10 @@ import java.math.BigInteger;
* a 64-bit hex number <code>0x1234</code> or a list of bits <code>[0, 2, 43,
* 22, ...]</code>.
*
- * @author <a href="mailto:magnarn@yahoo-inc.com">Magnar Nedland</a>
- * @since 5.1.15
+ * @author Magnar Nedland
*/
abstract class BooleanAttributeParser extends SimpleMapParser {
+
private boolean isMap = true;
@Override
diff --git a/container-search/src/main/java/com/yahoo/search/querytransform/BooleanSearcher.java b/container-search/src/main/java/com/yahoo/search/querytransform/BooleanSearcher.java
index f77301f587c..de01773c27c 100644
--- a/container-search/src/main/java/com/yahoo/search/querytransform/BooleanSearcher.java
+++ b/container-search/src/main/java/com/yahoo/search/querytransform/BooleanSearcher.java
@@ -4,6 +4,7 @@ package com.yahoo.search.querytransform;
import com.yahoo.component.chain.dependencies.After;
import com.yahoo.component.chain.dependencies.Provides;
import com.yahoo.prelude.query.PredicateQueryItem;
+import com.yahoo.processing.IllegalInputException;
import com.yahoo.processing.request.CompoundName;
import com.yahoo.search.Query;
import com.yahoo.search.Result;
@@ -26,6 +27,7 @@ import static com.yahoo.yolean.Exceptions.toMessageString;
@After({ STEMMING, ACCENT_REMOVAL })
@Provides(BooleanSearcher.PREDICATE)
public class BooleanSearcher extends Searcher {
+
private static final CompoundName FIELD = new CompoundName("boolean.field");
private static final CompoundName ATTRIBUTES = new CompoundName("boolean.attributes");
private static final CompoundName RANGE_ATTRIBUTES = new CompoundName("boolean.rangeAttributes");
@@ -61,7 +63,11 @@ public class BooleanSearcher extends Searcher {
} catch (TokenMgrException e) {
return new Result(query, ErrorMessage.createInvalidQueryParameter(toMessageString(e)));
}
- } else {
+ catch (IllegalArgumentException e) {
+ throw new IllegalInputException("Failed boolean search on field '" + fieldName + "'", e);
+ }
+ }
+ else {
if (query.isTraceable(5)) {
query.trace("BooleanSearcher: Nothing added to query", false, 5);
}
@@ -79,7 +85,9 @@ public class BooleanSearcher extends Searcher {
}
static public class PredicateValueAttributeParser extends BooleanAttributeParser {
- private PredicateQueryItem item;
+
+ private final PredicateQueryItem item;
+
public PredicateValueAttributeParser(PredicateQueryItem item) {
this.item = item;
}
@@ -93,10 +101,13 @@ public class BooleanSearcher extends Searcher {
protected void addAttribute(String attribute, String value, BigInteger subQueryMask) {
item.addFeature(attribute, value, subQueryMask.longValue());
}
+
}
static private class PredicateRangeAttributeParser extends BooleanAttributeParser {
- private PredicateQueryItem item;
+
+ private final PredicateQueryItem item;
+
public PredicateRangeAttributeParser(PredicateQueryItem item) {
this.item = item;
}
@@ -110,5 +121,7 @@ public class BooleanSearcher extends Searcher {
protected void addAttribute(String attribute, String value, BigInteger subQueryMask) {
item.addRangeFeature(attribute, Long.parseLong(value), subQueryMask.longValue());
}
+
}
+
}
diff --git a/container-search/src/main/java/com/yahoo/search/querytransform/NGramSearcher.java b/container-search/src/main/java/com/yahoo/search/querytransform/NGramSearcher.java
index 399ff6194c8..90bcebb53ad 100644
--- a/container-search/src/main/java/com/yahoo/search/querytransform/NGramSearcher.java
+++ b/container-search/src/main/java/com/yahoo/search/querytransform/NGramSearcher.java
@@ -107,7 +107,7 @@ public class NGramSearcher extends Searcher {
*/
protected Item splitToGrams(Item term, String text, int gramSize, Query query) {
String index = ((HasIndexItem)term).getIndexName();
- CompositeItem gramsItem = createGramRoot(query);
+ CompositeItem gramsItem = createGramRoot((HasIndexItem)term, query);
gramsItem.setIndexName(index);
Substring origin = ((BlockItem)term).getOrigin();
for (Iterator<GramSplitter.Gram> i = getGramSplitter().split(text,gramSize); i.hasNext(); ) {
@@ -130,12 +130,20 @@ public class NGramSearcher extends Searcher {
* called by {@link #splitToGrams}. This hook is provided to make it easy to create a subclass which
* matches grams using a different composite item, e.g an OrItem.
* <p>
- * This default implementation return new AndItem();
+ * This default implementation returns createGramRoot(query).
*
+ * @param term the term item this gram root is replacing in the query tree,
+ * typically used to access the index name of the term when that is required by the new gram root
+ * (such as in PhraseItem)
* @param query the input query, to make it possible to return a different composite item type
* depending on the query content
* @return the composite item to add the gram items to in {@link #splitToGrams}
*/
+ protected CompositeItem createGramRoot(HasIndexItem term, Query query) {
+ return createGramRoot(query);
+ }
+
+ /** Creates the root of the query subtree without access to the term being replaced. */
protected CompositeItem createGramRoot(Query query) {
return new AndItem();
}
diff --git a/container-search/src/main/java/com/yahoo/search/querytransform/VespaLowercasingSearcher.java b/container-search/src/main/java/com/yahoo/search/querytransform/VespaLowercasingSearcher.java
index 25488aa7bbc..3aa9e59003d 100644
--- a/container-search/src/main/java/com/yahoo/search/querytransform/VespaLowercasingSearcher.java
+++ b/container-search/src/main/java/com/yahoo/search/querytransform/VespaLowercasingSearcher.java
@@ -31,8 +31,7 @@ public class VespaLowercasingSearcher extends LowercasingSearcher {
public boolean shouldLowercase(WordItem word, IndexFacts.Session indexFacts) {
if (word.isLowercased()) return false;
- Index index = indexFacts.getIndex(word.getIndexName());
- return index.isLowercase() || index.isAttribute();
+ return indexFacts.getIndex(word.getIndexName()).isLowercase();
}
@Override
@@ -41,8 +40,7 @@ public class VespaLowercasingSearcher extends LowercasingSearcher {
StringBuilder sb = new StringBuilder();
sb.append(commonPath).append(".").append(word.getIndexName());
- Index index = indexFacts.getIndex(sb.toString());
- return index.isLowercase() || index.isAttribute();
+ return indexFacts.getIndex(sb.toString()).isLowercase();
}
}
diff --git a/container-search/src/main/java/com/yahoo/search/querytransform/WandSearcher.java b/container-search/src/main/java/com/yahoo/search/querytransform/WandSearcher.java
index 0b1387a16a2..4745bd23642 100644
--- a/container-search/src/main/java/com/yahoo/search/querytransform/WandSearcher.java
+++ b/container-search/src/main/java/com/yahoo/search/querytransform/WandSearcher.java
@@ -3,18 +3,25 @@ package com.yahoo.search.querytransform;
import com.yahoo.prelude.Index;
import com.yahoo.prelude.IndexFacts;
-import com.yahoo.prelude.query.*;
+import com.yahoo.prelude.query.CompositeItem;
+import com.yahoo.prelude.query.DotProductItem;
+import com.yahoo.prelude.query.Item;
+import com.yahoo.prelude.query.OrItem;
+import com.yahoo.prelude.query.WandItem;
+import com.yahoo.prelude.query.WeakAndItem;
+import com.yahoo.prelude.query.WordItem;
+import com.yahoo.processing.IllegalInputException;
import com.yahoo.processing.request.CompoundName;
import com.yahoo.search.Query;
import com.yahoo.search.Result;
import com.yahoo.search.Searcher;
import com.yahoo.search.result.ErrorMessage;
import com.yahoo.search.searchchain.Execution;
-import com.yahoo.text.MapParser;
import java.util.LinkedHashMap;
import java.util.Map;
+import com.yahoo.text.SimpleMapParser;
import com.yahoo.yolean.Exceptions;
/**
@@ -64,7 +71,7 @@ public class WandSearcher extends Searcher {
private static final CompoundName WAND_THRESHOLD_BOOST_FACTOR = new CompoundName("wand.thresholdBoostFactor");
private final String fieldName;
private final WandType wandType;
- private final Map<String, Integer> tokens;
+ private final Map<Object, Integer> tokens;
private final int heapSize;
private final double scoreThreshold;
private final double thresholdBoostFactor;
@@ -74,8 +81,14 @@ public class WandSearcher extends Searcher {
if (fieldName != null) {
String tokens = query.properties().getString(WAND_TOKENS);
if (tokens != null) {
- wandType = resolveWandType(execution.context().getIndexFacts().newSession(query), query);
- this.tokens = new IntegerMapParser().parse(tokens, new LinkedHashMap<>());
+ IndexFacts.Session indexFacts = execution.context().getIndexFacts().newSession(query);
+ Index index = indexFacts.getIndex(fieldName);
+ wandType = resolveWandType(index, indexFacts, query);
+ if (index.isNumerical() && (wandType == WandType.DOT_PRODUCT || wandType == WandType.PARALLEL)) {
+ this.tokens = new LongIntegerMapParser().parse(tokens, new LinkedHashMap<>(200));
+ } else {
+ this.tokens = new MapObjectIntegerParser().parse(tokens, new LinkedHashMap<>(200));
+ }
heapSize = resolveHeapSize(query);
scoreThreshold = resolveScoreThreshold(query);
thresholdBoostFactor = resolveThresholdBoostFactor(query);
@@ -89,10 +102,9 @@ public class WandSearcher extends Searcher {
thresholdBoostFactor = 1;
}
- private WandType resolveWandType(IndexFacts.Session indexFacts, Query query) {
- Index index = indexFacts.getIndex(fieldName);
+ private WandType resolveWandType(Index index, IndexFacts.Session indexFacts, Query query) {
if (index.isNull()) {
- throw new IllegalArgumentException("Field '" + fieldName + "' was not found in " + indexFacts);
+ throw new IllegalInputException("Field '" + fieldName + "' was not found in " + indexFacts);
} else {
return WandType.create(query.properties().getString(WAND_TYPE, "vespa"));
}
@@ -100,15 +112,15 @@ public class WandSearcher extends Searcher {
private int resolveHeapSize(Query query) {
String defaultHeapSize = "100";
- return Integer.valueOf(query.properties().getString(WAND_HEAP_SIZE, defaultHeapSize));
+ return Integer.parseInt(query.properties().getString(WAND_HEAP_SIZE, defaultHeapSize));
}
private double resolveScoreThreshold(Query query) {
- return Double.valueOf(query.properties().getString(WAND_SCORE_THRESHOLD, "0"));
+ return Double.parseDouble(query.properties().getString(WAND_SCORE_THRESHOLD, "0"));
}
private double resolveThresholdBoostFactor(Query query) {
- return Double.valueOf(query.properties().getString(WAND_THRESHOLD_BOOST_FACTOR, "1"));
+ return Double.parseDouble(query.properties().getString(WAND_THRESHOLD_BOOST_FACTOR, "1"));
}
public boolean hasValidData() {
@@ -119,7 +131,7 @@ public class WandSearcher extends Searcher {
return fieldName;
}
- public Map<String, Integer> getTokens() {
+ public Map<Object, Integer> getTokens() {
return tokens;
}
@@ -161,17 +173,17 @@ public class WandSearcher extends Searcher {
} else if (inputs.getWandType().equals(WandType.OR)) {
return populate(new OrItem(), inputs.getFieldName(), inputs.getTokens());
} else if (inputs.getWandType().equals(WandType.PARALLEL)) {
- return populate(new WandItem(inputs.getFieldName(), inputs.getHeapSize()),
- inputs.getScoreThreshold(), inputs.getThresholdBoostFactor(), inputs.getTokens());
+ return populate(new WandItem(inputs.getFieldName(), inputs.getHeapSize(), inputs.getTokens()),
+ inputs.getScoreThreshold(), inputs.getThresholdBoostFactor());
} else if (inputs.getWandType().equals(WandType.DOT_PRODUCT)) {
- return populate(new DotProductItem(inputs.getFieldName()), inputs.getTokens());
+ return new DotProductItem(inputs.getFieldName(), inputs.getTokens());
}
- throw new IllegalArgumentException("Unknown type '" + inputs.getWandType() + "'");
+ throw new IllegalInputException("Unknown type '" + inputs.getWandType() + "'");
}
- private CompositeItem populate(CompositeItem parent, String fieldName, Map<String,Integer> tokens) {
- for (Map.Entry<String,Integer> entry : tokens.entrySet()) {
- WordItem wordItem = new WordItem(entry.getKey(), fieldName);
+ private CompositeItem populate(CompositeItem parent, String fieldName, Map<Object,Integer> tokens) {
+ for (Map.Entry<Object,Integer> entry : tokens.entrySet()) {
+ WordItem wordItem = new WordItem(entry.getKey().toString(), fieldName);
wordItem.setWeight(entry.getValue());
wordItem.setStemmed(true);
wordItem.setNormalizable(false);
@@ -180,25 +192,34 @@ public class WandSearcher extends Searcher {
return parent;
}
- private WeightedSetItem populate(WeightedSetItem item, Map<String,Integer> tokens) {
- for (Map.Entry<String,Integer> entry : tokens.entrySet()) {
- item.addToken(entry.getKey(), entry.getValue());
- }
- return item;
- }
-
- private WandItem populate(WandItem item, double scoreThreshold, double thresholdBoostFactor, Map<String,Integer> tokens) {
- populate(item, tokens);
+ private WandItem populate(WandItem item, double scoreThreshold, double thresholdBoostFactor) {
item.setScoreThreshold(scoreThreshold);
item.setThresholdBoostFactor(thresholdBoostFactor);
return item;
}
- private static class IntegerMapParser extends MapParser<Integer> {
+ private static class MapObjectIntegerParser extends SimpleMapParser {
+ protected Map<Object, Integer> map;
+
+ public Map<Object,Integer> parse(String string, Map<Object,Integer> map) {
+ this.map = map;
+ parse(string);
+ return this.map;
+ }
+
@Override
- protected Integer parseValue(String s) {
- return Integer.parseInt(s);
+ protected void handleKeyValue(String key, String value) {
+ map.put(key, Integer.parseInt(value));
}
}
+ private static class LongIntegerMapParser extends MapObjectIntegerParser {
+
+ @Override
+ protected void handleKeyValue(String key, String value) {
+ map.put(Long.parseLong(key), Integer.parseInt(value));
+ }
+
+ }
+
}
diff --git a/container-search/src/main/java/com/yahoo/search/querytransform/WeakAndReplacementSearcher.java b/container-search/src/main/java/com/yahoo/search/querytransform/WeakAndReplacementSearcher.java
new file mode 100644
index 00000000000..3a392fcbda4
--- /dev/null
+++ b/container-search/src/main/java/com/yahoo/search/querytransform/WeakAndReplacementSearcher.java
@@ -0,0 +1,68 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.search.querytransform;
+
+import com.yahoo.prelude.query.*;
+import com.yahoo.processing.request.CompoundName;
+import com.yahoo.search.Query;
+import com.yahoo.search.Result;
+import com.yahoo.search.Searcher;
+import com.yahoo.search.searchchain.Execution;
+
+/**
+ * Recursively replaces all instances of OrItems with WeakAndItems if the query property weakand.replace is true.
+ * Otherwise a noop searcher.
+ *
+ * @author karowan
+ */
+public class WeakAndReplacementSearcher extends Searcher {
+ private static final CompoundName WEAKAND_REPLACE = new CompoundName("weakAnd.replace");
+
+ @Override public Result search(Query query, Execution execution) {
+ if (!query.properties().getBoolean(WEAKAND_REPLACE)) {
+ return execution.search(query);
+ }
+ replaceOrItems(query);
+ return execution.search(query);
+ }
+
+ /**
+ * Extracts the queryTree root and the wand.hits property to send to the recursive replacement function
+ * @param query the search query
+ */
+ private void replaceOrItems(Query query) {
+ Item root = query.getModel().getQueryTree().getRoot();
+ int hits = query.properties().getInteger("wand.hits", WeakAndItem.defaultN);
+ query.getModel().getQueryTree().setRoot(replaceOrItems(root, hits));
+ if (root != query.getModel().getQueryTree().getRoot())
+ query.trace("Replaced OR by WeakAnd", true, 2);
+ }
+
+
+ /**
+ * Recursively iterates over an Item to replace all instances of OrItems with WeakAndItems
+ * @param item the current item in the replacement iteration
+ * @param hits the wand.hits property from the request which is assigned to the N value of the new WeakAndItem
+ * @return the original item or a WeakAndItem replacement of an OrItem
+ */
+ private Item replaceOrItems(Item item, int hits) {
+ if (!(item instanceof CompositeItem)) {
+ return item;
+ }
+ CompositeItem compositeItem = (CompositeItem) item;
+ if (compositeItem instanceof OrItem) {
+ WeakAndItem newItem = new WeakAndItem(hits);
+ newItem.setWeight(compositeItem.getWeight());
+ compositeItem.items().forEach(newItem::addItem);
+ compositeItem = newItem;
+ }
+ for (int i = 0; i < compositeItem.getItemCount(); i++) {
+ Item subItem = compositeItem.getItem(i);
+ Item replacedItem = replaceOrItems(subItem, hits);
+ if (replacedItem != subItem) {
+ compositeItem.setItem(i, replacedItem);
+ }
+ }
+ return compositeItem;
+ }
+
+}
diff --git a/container-search/src/main/java/com/yahoo/search/rendering/JsonRenderer.java b/container-search/src/main/java/com/yahoo/search/rendering/JsonRenderer.java
index 31f8194b3b7..ee0e7f4fe0e 100644
--- a/container-search/src/main/java/com/yahoo/search/rendering/JsonRenderer.java
+++ b/container-search/src/main/java/com/yahoo/search/rendering/JsonRenderer.java
@@ -45,8 +45,7 @@ import com.yahoo.search.result.Hit;
import com.yahoo.search.result.HitGroup;
import com.yahoo.search.result.NanNumber;
import com.yahoo.tensor.Tensor;
-import org.json.JSONArray;
-import org.json.JSONObject;
+import com.yahoo.tensor.serialization.JsonFormat;
import java.io.IOException;
import java.io.OutputStream;
@@ -60,13 +59,14 @@ import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Collections;
import java.util.Deque;
-import java.util.Iterator;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.function.LongSupplier;
+import static com.fasterxml.jackson.databind.SerializationFeature.FLUSH_AFTER_WRITE_VALUE;
+
/**
* JSON renderer for search results.
*
@@ -78,6 +78,7 @@ public class JsonRenderer extends AsynchronousSectionedRenderer<Result> {
private static final CompoundName DEBUG_RENDERING_KEY = new CompoundName("renderer.json.debug");
private static final CompoundName JSON_CALLBACK = new CompoundName("jsoncallback");
+ private static final CompoundName TENSOR_FORMAT = new CompoundName("format.tensors");
// if this must be optimized, simply use com.fasterxml.jackson.core.SerializableString
private static final String BUCKET_LIMITS = "limits";
@@ -127,6 +128,8 @@ public class JsonRenderer extends AsynchronousSectionedRenderer<Result> {
private LongSupplier timeSource;
private OutputStream stream;
+ private boolean tensorShortFormRendering = false;
+
public JsonRenderer() {
this(null);
}
@@ -149,7 +152,7 @@ public class JsonRenderer extends AsynchronousSectionedRenderer<Result> {
* @return an object mapper for the internal JsonFactory
*/
protected static ObjectMapper createJsonCodec() {
- return new ObjectMapper();
+ return new ObjectMapper().disable(FLUSH_AFTER_WRITE_VALUE);
}
@Override
@@ -166,6 +169,7 @@ public class JsonRenderer extends AsynchronousSectionedRenderer<Result> {
public void beginResponse(OutputStream stream) throws IOException {
beginJsonCallback(stream);
debugRendering = getDebugRendering(getResult().getQuery());
+ tensorShortFormRendering = getTensorShortFormRendering(getResult().getQuery());
setGenerator(generatorFactory.createGenerator(stream, JsonEncoding.UTF8), debugRendering);
renderedChildren = new ArrayDeque<>();
generator.writeStartObject();
@@ -200,6 +204,12 @@ public class JsonRenderer extends AsynchronousSectionedRenderer<Result> {
return q != null && q.properties().getBoolean(DEBUG_RENDERING_KEY, false);
}
+ private boolean getTensorShortFormRendering(Query q) {
+ if (q == null || q.properties().get(TENSOR_FORMAT) == null)
+ return false;
+ return q.properties().getString(TENSOR_FORMAT).equalsIgnoreCase("short");
+ }
+
protected void renderTrace(Trace trace) throws IOException {
if (!trace.traceNode().children().iterator().hasNext()) return;
if (getResult().getQuery().getTraceLevel() == 0) return;
@@ -285,8 +295,6 @@ public class JsonRenderer extends AsynchronousSectionedRenderer<Result> {
generator.writeEndObject();
}
generator.writeEndArray();
-
-
}
protected void renderCoverage() throws IOException {
@@ -510,7 +518,7 @@ public class JsonRenderer extends AsynchronousSectionedRenderer<Result> {
}
protected FieldConsumer createFieldConsumer(JsonGenerator generator, boolean debugRendering) {
- return new FieldConsumer(generator, debugRendering);
+ return new FieldConsumer(generator, debugRendering, tensorShortFormRendering);
}
/**
@@ -529,12 +537,18 @@ public class JsonRenderer extends AsynchronousSectionedRenderer<Result> {
private final JsonGenerator generator;
private final boolean debugRendering;
+ private final boolean tensorShortForm;
private MutableBoolean hasFieldsField;
public FieldConsumer(JsonGenerator generator, boolean debugRendering) {
+ this(generator, debugRendering, false);
+ }
+
+ public FieldConsumer(JsonGenerator generator, boolean debugRendering, boolean tensorShortForm) {
this.generator = generator;
this.debugRendering = debugRendering;
+ this.tensorShortForm = tensorShortForm;
}
/**
@@ -659,7 +673,7 @@ public class JsonRenderer extends AsynchronousSectionedRenderer<Result> {
} else if (field instanceof Tensor) {
renderTensor(Optional.of((Tensor)field));
} else if (field instanceof FeatureData) {
- generator.writeRawValue(((FeatureData)field).toJson());
+ generator.writeRawValue(((FeatureData)field).toJson(tensorShortForm));
} else if (field instanceof Inspectable) {
renderInspectorDirect(((Inspectable)field).inspect());
} else if (field instanceof JsonProducer) {
@@ -671,14 +685,6 @@ public class JsonRenderer extends AsynchronousSectionedRenderer<Result> {
} else if (field instanceof FieldValue) {
// the null below is the field which has already been written
((FieldValue) field).serialize(null, new JsonWriter(generator));
- } else if (field instanceof JSONArray || field instanceof JSONObject) {
- // org.json returns null if the object would not result in syntactically correct JSON
- String s = field.toString();
- if (s == null) {
- generator.writeNull();
- } else {
- generator.writeRawValue(s);
- }
} else {
generator.writeString(field.toString());
}
@@ -705,26 +711,18 @@ public class JsonRenderer extends AsynchronousSectionedRenderer<Result> {
}
private void renderTensor(Optional<Tensor> tensor) throws IOException {
- generator.writeStartObject();
- generator.writeArrayFieldStart("cells");
- if (tensor.isPresent()) {
- for (Iterator<Tensor.Cell> i = tensor.get().cellIterator(); i.hasNext(); ) {
- Tensor.Cell cell = i.next();
-
- generator.writeStartObject();
-
- generator.writeObjectFieldStart("address");
- for (int d = 0; d < cell.getKey().size(); d++)
- generator.writeObjectField(tensor.get().type().dimensions().get(d).name(), cell.getKey().label(d));
- generator.writeEndObject();
-
- generator.writeObjectField("value", cell.getValue());
-
- generator.writeEndObject();
- }
+ if (tensor.isEmpty()) {
+ generator.writeStartObject();
+ generator.writeArrayFieldStart("cells");
+ generator.writeEndArray();
+ generator.writeEndObject();
+ return;
+ }
+ if (tensorShortForm) {
+ generator.writeRawValue(new String(JsonFormat.encodeShortForm(tensor.get()), StandardCharsets.UTF_8));
+ } else {
+ generator.writeRawValue(new String(JsonFormat.encode(tensor.get()), StandardCharsets.UTF_8));
}
- generator.writeEndArray();
- generator.writeEndObject();
}
}
diff --git a/container-search/src/main/java/com/yahoo/search/rendering/RendererRegistry.java b/container-search/src/main/java/com/yahoo/search/rendering/RendererRegistry.java
index 783045babf4..9540bc20bc5 100644
--- a/container-search/src/main/java/com/yahoo/search/rendering/RendererRegistry.java
+++ b/container-search/src/main/java/com/yahoo/search/rendering/RendererRegistry.java
@@ -4,6 +4,7 @@ package com.yahoo.search.rendering;
import com.yahoo.component.ComponentId;
import com.yahoo.component.ComponentSpecification;
import com.yahoo.component.provider.ComponentRegistry;
+import com.yahoo.processing.IllegalInputException;
import com.yahoo.processing.rendering.Renderer;
import com.yahoo.search.Result;
import com.yahoo.search.pagetemplates.result.PageTemplatesXmlRenderer;
@@ -103,8 +104,8 @@ public final class RendererRegistry extends ComponentRegistry<com.yahoo.processi
com.yahoo.processing.rendering.Renderer<Result> renderer = getComponent(format);
if (renderer == null)
- throw new IllegalArgumentException("No renderer with id or alias '" + format + "'. " +
- "Available renderers are: [" + rendererNames() + "].");
+ throw new IllegalInputException("No renderer with id or alias '" + format + "'. " +
+ "Available renderers are: [" + rendererNames() + "].");
return renderer;
}
diff --git a/container-search/src/main/java/com/yahoo/search/rendering/SyncDefaultRenderer.java b/container-search/src/main/java/com/yahoo/search/rendering/SyncDefaultRenderer.java
index 9d0e110a6dd..d79122ac9c4 100644
--- a/container-search/src/main/java/com/yahoo/search/rendering/SyncDefaultRenderer.java
+++ b/container-search/src/main/java/com/yahoo/search/rendering/SyncDefaultRenderer.java
@@ -2,7 +2,7 @@
package com.yahoo.search.rendering;
import com.yahoo.concurrent.CopyOnWriteHashMap;
-import com.yahoo.log.LogLevel;
+import java.util.logging.Level;
import com.yahoo.net.URI;
import com.yahoo.prelude.fastsearch.GroupingListHit;
import com.yahoo.prelude.hitfield.HitField;
@@ -395,7 +395,7 @@ public final class SyncDefaultRenderer extends Renderer {
if (e instanceof IOException) {
throw (IOException) e;
} else {
- log.log(LogLevel.WARNING, "Exception thrown when rendering the result:", e);
+ log.log(Level.WARNING, "Exception thrown when rendering the result:", e);
}
}
diff --git a/container-search/src/main/java/com/yahoo/search/rendering/XmlRenderer.java b/container-search/src/main/java/com/yahoo/search/rendering/XmlRenderer.java
index 62ee16993fd..f3b0cd3a61e 100644
--- a/container-search/src/main/java/com/yahoo/search/rendering/XmlRenderer.java
+++ b/container-search/src/main/java/com/yahoo/search/rendering/XmlRenderer.java
@@ -352,8 +352,8 @@ public final class XmlRenderer extends AsynchronousSectionedRenderer<Result> {
try {
return (Result) getResponse();
} catch (ClassCastException e) {
- throw new IllegalArgumentException("XmlRenderer attempted used outside a search context, got a " +
- getResponse().getClass().getName());
+ throw new IllegalStateException("XmlRenderer attempted used outside a search context, got a " +
+ getResponse().getClass().getName());
}
}
diff --git a/container-search/src/main/java/com/yahoo/search/result/FeatureData.java b/container-search/src/main/java/com/yahoo/search/result/FeatureData.java
index 4895db04462..7673352576d 100644
--- a/container-search/src/main/java/com/yahoo/search/result/FeatureData.java
+++ b/container-search/src/main/java/com/yahoo/search/result/FeatureData.java
@@ -14,7 +14,9 @@ import com.yahoo.tensor.serialization.TypedBinaryFormat;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
+import java.util.HashMap;
import java.util.HashSet;
+import java.util.Map;
import java.util.Optional;
import java.util.Set;
@@ -31,6 +33,10 @@ public class FeatureData implements Inspectable, JsonProducer {
private Set<String> featureNames = null;
+ /** Cached decoded values */
+ private Map<String, Double> decodedDoubles = null;
+ private Map<String, Tensor> decodedTensors = null;
+
private String jsonForm = null;
public FeatureData(Inspector value) {
@@ -56,9 +62,17 @@ public class FeatureData implements Inspectable, JsonProducer {
return jsonForm;
}
+ public String toJson(boolean tensorShortForm) {
+ if (this == empty) return "{}";
+ if (jsonForm != null) return jsonForm;
+
+ jsonForm = JsonRender.render(value, new Encoder(new StringBuilder(), true, tensorShortForm)).toString();
+ return jsonForm;
+ }
+
@Override
public StringBuilder writeJson(StringBuilder target) {
- return JsonRender.render(value, new Encoder(target, true));
+ return JsonRender.render(value, new Encoder(target, true, false));
}
/**
@@ -68,6 +82,19 @@ public class FeatureData implements Inspectable, JsonProducer {
* (that is, if it is a tensor with nonzero rank)
*/
public Double getDouble(String featureName) {
+ if (decodedDoubles == null)
+ decodedDoubles = new HashMap<>();
+
+ Double value = decodedDoubles.get(featureName);
+ if (value != null) return value;
+
+ value = decodeDouble(featureName);
+ if (value != null)
+ decodedDoubles.put(featureName, value);
+ return value;
+ }
+
+ private Double decodeDouble(String featureName) {
Inspector featureValue = getInspector(featureName);
if ( ! featureValue.valid()) return null;
@@ -83,6 +110,19 @@ public class FeatureData implements Inspectable, JsonProducer {
* This will return any feature value: Scalars are returned as a rank 0 tensor.
*/
public Tensor getTensor(String featureName) {
+ if (decodedTensors == null)
+ decodedTensors = new HashMap<>();
+
+ Tensor value = decodedTensors.get(featureName);
+ if (value != null) return value;
+
+ value = decodeTensor(featureName);
+ if (value != null)
+ decodedTensors.put(featureName, value);
+ return value;
+ }
+
+ private Tensor decodeTensor(String featureName) {
Inspector featureValue = getInspector(featureName);
if ( ! featureValue.valid()) return null;
@@ -130,15 +170,19 @@ public class FeatureData implements Inspectable, JsonProducer {
/** A JSON encoder which encodes DATA as a tensor */
private static class Encoder extends JsonRender.StringEncoder {
- Encoder(StringBuilder out, boolean compact) {
+ private final boolean tensorShortForm;
+
+ Encoder(StringBuilder out, boolean compact, boolean tensorShortForm) {
super(out, compact);
+ this.tensorShortForm = tensorShortForm;
}
@Override
public void encodeDATA(byte[] value) {
// This could be done more efficiently ...
- target().append(new String(JsonFormat.encodeWithType(TypedBinaryFormat.decode(Optional.empty(), GrowableByteBuffer.wrap(value))),
- StandardCharsets.UTF_8));
+ Tensor tensor = TypedBinaryFormat.decode(Optional.empty(), GrowableByteBuffer.wrap(value));
+ byte[] encodedTensor = tensorShortForm ? JsonFormat.encodeShortForm(tensor) : JsonFormat.encodeWithType(tensor);
+ target().append(new String(encodedTensor, StandardCharsets.UTF_8));
}
}
diff --git a/container-search/src/main/java/com/yahoo/search/result/Hit.java b/container-search/src/main/java/com/yahoo/search/result/Hit.java
index fc416c0d930..d224ec2c7ab 100644
--- a/container-search/src/main/java/com/yahoo/search/result/Hit.java
+++ b/container-search/src/main/java/com/yahoo/search/result/Hit.java
@@ -35,6 +35,8 @@ import java.util.function.BiConsumer;
* done of a lightweight version of the hits, which is cheaper if a significant
* number of hits are filtered out.</p>
*
+ * <p>Do not cache this as it holds references to objects that should be garbage collected.</p>
+ *
* @author bratseth
*/
public class Hit extends ListenableFreezableClass implements Data, Comparable<Hit>, Cloneable {
@@ -45,8 +47,8 @@ public class Hit extends ListenableFreezableClass implements Data, Comparable<Hi
private static final String DOCUMENT_ID = "documentid";
/** A collection of string keyed object properties. */
- private Map<String,Object> fields = null;
- private Map<String,Object> unmodifiableFieldMap = null;
+ private Map<String, Object> fields = null;
+ private Map<String, Object> unmodifiableFieldMap = null;
/** Meta data describing how a given searcher should treat this hit. */
// TODO: The case for this is to allow multiple levels of federation searcher routing.
@@ -473,7 +475,7 @@ public class Hit extends ListenableFreezableClass implements Data, Comparable<Hi
private Map<String, Object> getFieldMap(int minSize) {
if (fields == null) {
// Compensate for loadfactor and then some, rounded up....
- fields = new LinkedHashMap<>(2*minSize);
+ fields = new LinkedHashMap<>(2 * minSize);
}
return fields;
}
@@ -505,7 +507,7 @@ public class Hit extends ListenableFreezableClass implements Data, Comparable<Hi
}
/** Returns the types of this as a modifiable set. Modifications to this set are directly reflected in this hit */
- //TODO This shoudld not be exposed as a modifiable set
+ // TODO: This should not be exposed as a modifiable set
public Set<String> types() {
if (types == null)
types = new ArraySet<>(1);
@@ -682,7 +684,7 @@ public class Hit extends ListenableFreezableClass implements Data, Comparable<Hi
* Called for fields which are available as UTF-8 instead of accept(String, Object).
*
* @param fieldName the name of the field
- * @param utf8Data raw utf-8 data. The reciver <b>must not</b> modify this data
+ * @param utf8Data raw utf-8 data. The receiver <b>must not</b> modify this data
* @param offset the start index in the utf8Data array of the data to accept
* @param length the length starting from offset in the utf8Data array of the data to accept
*/
diff --git a/container-search/src/main/java/com/yahoo/search/searchchain/Execution.java b/container-search/src/main/java/com/yahoo/search/searchchain/Execution.java
index 84fe88d0292..fac0d35d509 100644
--- a/container-search/src/main/java/com/yahoo/search/searchchain/Execution.java
+++ b/container-search/src/main/java/com/yahoo/search/searchchain/Execution.java
@@ -6,7 +6,7 @@ import com.yahoo.language.Linguistics;
import com.yahoo.prelude.IndexFacts;
import com.yahoo.prelude.Ping;
import com.yahoo.prelude.Pong;
-import com.yahoo.prelude.query.parser.SpecialTokenRegistry;
+import com.yahoo.language.process.SpecialTokenRegistry;
import com.yahoo.processing.Processor;
import com.yahoo.processing.Request;
import com.yahoo.processing.Response;
@@ -17,8 +17,6 @@ import com.yahoo.search.cluster.PingableSearcher;
import com.yahoo.search.rendering.RendererRegistry;
import com.yahoo.search.statistics.TimeTracker;
-import java.util.logging.Logger;
-
/**
* <p>An execution of a search chain. This keeps track of the call state for an execution (in the calling thread)
* of the searchers of a search chain.</p>
@@ -111,7 +109,7 @@ public class Execution extends com.yahoo.processing.execution.Execution {
public Context(SearchChainRegistry searchChainRegistry, IndexFacts indexFacts,
SpecialTokenRegistry tokenRegistry, RendererRegistry rendererRegistry, Linguistics linguistics)
{
- owner=null;
+ owner = null;
// The next time something is added here, compose into wrapper objects. Many arguments...
// Four methods need to be updated when adding something:
@@ -525,7 +523,6 @@ public class Execution extends com.yahoo.processing.execution.Execution {
*
* @param result the result to fill
*/
- @SuppressWarnings("deprecation")
public void fillAttributes(Result result) {
fill(result, ATTRIBUTEPREFETCH);
}
diff --git a/container-search/src/main/java/com/yahoo/search/searchchain/ExecutionFactory.java b/container-search/src/main/java/com/yahoo/search/searchchain/ExecutionFactory.java
index 31b6d06f78e..a813229c984 100644
--- a/container-search/src/main/java/com/yahoo/search/searchchain/ExecutionFactory.java
+++ b/container-search/src/main/java/com/yahoo/search/searchchain/ExecutionFactory.java
@@ -13,7 +13,7 @@ import com.yahoo.language.Linguistics;
import com.yahoo.language.simple.SimpleLinguistics;
import com.yahoo.prelude.IndexFacts;
import com.yahoo.prelude.IndexModel;
-import com.yahoo.prelude.query.parser.SpecialTokenRegistry;
+import com.yahoo.language.process.SpecialTokenRegistry;
import com.yahoo.processing.rendering.Renderer;
import com.yahoo.search.Searcher;
import com.yahoo.search.config.IndexInfoConfig;
diff --git a/container-search/src/main/java/com/yahoo/search/searchchain/PhaseNames.java b/container-search/src/main/java/com/yahoo/search/searchchain/PhaseNames.java
index ec79979387b..d2809e8fe33 100644
--- a/container-search/src/main/java/com/yahoo/search/searchchain/PhaseNames.java
+++ b/container-search/src/main/java/com/yahoo/search/searchchain/PhaseNames.java
@@ -10,6 +10,7 @@ package com.yahoo.search.searchchain;
* @author Steinar Knutsen
*/
public final class PhaseNames {
+
private PhaseNames() {
}
diff --git a/container-search/src/main/java/com/yahoo/search/searchchain/model/VespaSearchers.java b/container-search/src/main/java/com/yahoo/search/searchchain/model/VespaSearchers.java
index cf4f5f360ad..2f680a8f3bd 100644
--- a/container-search/src/main/java/com/yahoo/search/searchchain/model/VespaSearchers.java
+++ b/container-search/src/main/java/com/yahoo/search/searchchain/model/VespaSearchers.java
@@ -23,6 +23,7 @@ import java.util.Set;
*/
@SuppressWarnings({"rawtypes", "unchecked"})
public class VespaSearchers {
+
public static final Collection<ChainedComponentModel> vespaSearcherModels =
toSearcherModels(
com.yahoo.prelude.querytransform.PhrasingSearcher.class,
@@ -33,8 +34,8 @@ public class VespaSearchers {
com.yahoo.prelude.searcher.BlendingSearcher.class,
com.yahoo.prelude.searcher.PosSearcher.class,
com.yahoo.prelude.semantics.SemanticSearcher.class,
- com.yahoo.search.grouping.GroupingQueryParser.class);
-
+ com.yahoo.search.grouping.GroupingQueryParser.class,
+ com.yahoo.search.querytransform.WeakAndReplacementSearcher.class);
public static final Collection<ChainedComponentModel> nativeSearcherModels;
@@ -59,8 +60,9 @@ public class VespaSearchers {
private static FederationSearcherModel federationSearcherModel() {
return new FederationSearcherModel(new ComponentSpecification("federation"),
- Dependencies.emptyDependencies(),
- Collections.emptyList(), true);
+ Dependencies.emptyDependencies(),
+ Collections.emptyList(),
+ true);
}
private static boolean allAdded(Collection<ChainedComponentModel> searcherModels, Set<ComponentId> componentIds) {
@@ -81,4 +83,5 @@ public class VespaSearchers {
}
return searcherModels;
}
+
}
diff --git a/container-search/src/main/java/com/yahoo/search/searchchain/model/federation/FederationSearcherModel.java b/container-search/src/main/java/com/yahoo/search/searchchain/model/federation/FederationSearcherModel.java
index e1c95e9478e..01dccee5c7f 100644
--- a/container-search/src/main/java/com/yahoo/search/searchchain/model/federation/FederationSearcherModel.java
+++ b/container-search/src/main/java/com/yahoo/search/searchchain/model/federation/FederationSearcherModel.java
@@ -5,7 +5,6 @@ import java.util.List;
import com.google.common.collect.ImmutableList;
import com.yahoo.container.bundle.BundleInstantiationSpecification;
-import net.jcip.annotations.Immutable;
import com.yahoo.component.ComponentSpecification;
import com.yahoo.component.chain.dependencies.Dependencies;
@@ -17,24 +16,9 @@ import com.yahoo.search.federation.FederationSearcher;
*
* @author Tony Vaagenes
*/
-@Immutable
public class FederationSearcherModel extends ChainedComponentModel {
- /**
- * Specifies one or more search chains that can be addressed
- * as a single source.
- */
- public static class TargetSpec {
- public final ComponentSpecification sourceSpec;
- public final FederationOptions federationOptions;
-
- public TargetSpec(ComponentSpecification sourceSpec, FederationOptions federationOptions) {
- this.sourceSpec = sourceSpec;
- this.federationOptions = federationOptions;
- }
- }
-
- private static ComponentSpecification federationSearcherComponentSpecification =
+ private static final ComponentSpecification federationSearcherComponentSpecification =
new ComponentSpecification(FederationSearcher.class.getName());
public final List<TargetSpec> targets;
@@ -48,4 +32,16 @@ public class FederationSearcherModel extends ChainedComponentModel {
this.targets = ImmutableList.copyOf(targets);
}
+ /** Specifies one or more search chains that can be addressed as a single source. */
+ public static class TargetSpec {
+
+ public final ComponentSpecification sourceSpec;
+ public final FederationOptions federationOptions;
+
+ public TargetSpec(ComponentSpecification sourceSpec, FederationOptions federationOptions) {
+ this.sourceSpec = sourceSpec;
+ this.federationOptions = federationOptions;
+ }
+ }
+
}
diff --git a/container-search/src/main/java/com/yahoo/search/searchchain/model/federation/LocalProviderSpec.java b/container-search/src/main/java/com/yahoo/search/searchchain/model/federation/LocalProviderSpec.java
index 4c36ca9b4da..160f917f6c6 100644
--- a/container-search/src/main/java/com/yahoo/search/searchchain/model/federation/LocalProviderSpec.java
+++ b/container-search/src/main/java/com/yahoo/search/searchchain/model/federation/LocalProviderSpec.java
@@ -10,6 +10,7 @@ import com.yahoo.search.Searcher;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
+import java.util.Objects;
import net.jcip.annotations.Immutable;
@@ -33,6 +34,7 @@ public class LocalProviderSpec {
com.yahoo.search.querytransform.RangeQueryOptimizer.class,
com.yahoo.search.querytransform.SortingDegrader.class,
com.yahoo.prelude.searcher.ValidateSortingSearcher.class,
+ com.yahoo.search.searchers.QueryValidator.class,
com.yahoo.prelude.cluster.ClusterSearcher.class,
com.yahoo.search.grouping.GroupingValidator.class,
com.yahoo.search.grouping.vespa.GroupingExecutor.class,
@@ -48,15 +50,8 @@ public class LocalProviderSpec {
public final String clusterName;
- // TODO: make this final
- public Integer cacheSize;
-
- public LocalProviderSpec(String clusterName, Integer cacheSize) {
- this.clusterName = clusterName;
- this.cacheSize = cacheSize;
-
- if (clusterName == null)
- throw new IllegalArgumentException("Missing cluster name.");
+ public LocalProviderSpec(String clusterName) {
+ this.clusterName = Objects.requireNonNull(clusterName, "Cluster name cannot be null");
}
public static boolean includesType(String type) {
diff --git a/container-search/src/main/java/com/yahoo/search/searchchain/testutil/DocumentSourceSearcher.java b/container-search/src/main/java/com/yahoo/search/searchchain/testutil/DocumentSourceSearcher.java
index d39a488626b..e346a766738 100644
--- a/container-search/src/main/java/com/yahoo/search/searchchain/testutil/DocumentSourceSearcher.java
+++ b/container-search/src/main/java/com/yahoo/search/searchchain/testutil/DocumentSourceSearcher.java
@@ -97,11 +97,10 @@ public class DocumentSourceSearcher extends Searcher {
public Result search(Query query, Execution execution) {
queryCount++;
Result r = unFilledResults.get(getQueryKeyClone(query));
- if (r == null) {
+ if (r == null)
r = defaultFilledResult.clone();
- } else {
+ else
r = r.clone();
- }
r.setQuery(query);
r.hits().trim(query.getOffset(), query.getHits());
@@ -182,11 +181,8 @@ public class DocumentSourceSearcher extends Searcher {
* reset. For testing - not reliable if multiple threads makes
* queries simultaneously
*/
- public int getQueryCount() {
- return queryCount;
- }
+ public int getQueryCount() { return queryCount; }
+
+ public void resetQueryCount() { queryCount = 0; }
- public void resetQueryCount() {
- queryCount=0;
- }
}
diff --git a/container-search/src/main/java/com/yahoo/search/searchers/InputCheckingSearcher.java b/container-search/src/main/java/com/yahoo/search/searchers/InputCheckingSearcher.java
index 018f4fc7aa9..d8391fe08f3 100644
--- a/container-search/src/main/java/com/yahoo/search/searchers/InputCheckingSearcher.java
+++ b/container-search/src/main/java/com/yahoo/search/searchers/InputCheckingSearcher.java
@@ -11,7 +11,9 @@ import java.util.ListIterator;
import java.util.Map;
import java.util.logging.Logger;
-import com.yahoo.log.LogLevel;
+import java.util.logging.Level;
+
+import com.yahoo.component.chain.dependencies.Before;
import com.yahoo.metrics.simple.Counter;
import com.yahoo.metrics.simple.MetricReceiver;
import com.yahoo.prelude.query.CompositeItem;
@@ -19,18 +21,22 @@ import com.yahoo.prelude.query.Item;
import com.yahoo.prelude.query.PhraseItem;
import com.yahoo.prelude.query.TermItem;
import com.yahoo.prelude.query.WordItem;
+import com.yahoo.processing.IllegalInputException;
import com.yahoo.search.Query;
import com.yahoo.search.Result;
import com.yahoo.search.Searcher;
import com.yahoo.search.result.ErrorMessage;
import com.yahoo.search.searchchain.Execution;
+import com.yahoo.search.searchchain.PhaseNames;
+import com.yahoo.yolean.Exceptions;
/**
- * Check whether the query tree seems to be "well formed". In other words, run heurestics against
+ * Check whether the query tree seems to be "well formed". In other words, run heuristics against
* the input data to see whether the query should sent to the search backend.
*
* @author Steinar Knutsen
*/
+@Before(PhaseNames.BACKEND)
public class InputCheckingSearcher extends Searcher {
private final Counter utfRejections;
@@ -50,10 +56,8 @@ public class InputCheckingSearcher extends Searcher {
public Result search(Query query, Execution execution) {
try {
checkQuery(query);
- } catch (IllegalArgumentException e) {
- if (log.isLoggable(LogLevel.DEBUG)) {
- log.log(LogLevel.DEBUG, "Rejected query \"" + query.toString() + "\" on cause of: " + e.getMessage());
- }
+ } catch (IllegalInputException e) {
+ log.log(Level.FINE, () -> "Rejected query '" + query.toString() + "' on cause of: " + Exceptions.toMessageString(e));
return new Result(query, ErrorMessage.createIllegalQuery(e.getMessage()));
}
return execution.search(query);
@@ -92,8 +96,9 @@ public class InputCheckingSearcher extends Searcher {
repeatedCount++;
if (repeatedCount >= MAX_REPEATED_CONSECUTIVE_TERMS_IN_PHRASE) {
repeatedConsecutiveTermsInPhraseRejections.add();
- throw new IllegalArgumentException("More than " + MAX_REPEATED_CONSECUTIVE_TERMS_IN_PHRASE +
- " ocurrences of term '" + current + "' in a row detected in phrase : " + phrase.toString());
+ throw new IllegalInputException("More than " + MAX_REPEATED_CONSECUTIVE_TERMS_IN_PHRASE +
+ " occurrences of term '" + current +
+ "' in a row detected in phrase : " + phrase.toString());
}
} else {
repeatedCount = 0;
@@ -125,8 +130,8 @@ public class InputCheckingSearcher extends Searcher {
if (count != null) {
if (count.get() >= MAX_REPEATED_TERMS_IN_PHRASE) {
repeatedTermsInPhraseRejections.add();
- throw new IllegalArgumentException("Phrase contains more than " + MAX_REPEATED_TERMS_IN_PHRASE +
- " occurrences of term '" + current + "' in phrase : " + phrase.toString());
+ throw new IllegalInputException("Phrase contains more than " + MAX_REPEATED_TERMS_IN_PHRASE +
+ " occurrences of term '" + current + "' in phrase : " + phrase.toString());
}
count.inc();
} else {
@@ -169,8 +174,8 @@ public class InputCheckingSearcher extends Searcher {
return;
}
utfRejections.add();
- throw new IllegalArgumentException("The user input has been determined to be double encoded UTF-8."
- + " Please investigate whether this is a false positive.");
+ throw new IllegalInputException("The user input has been determined to be double encoded UTF-8."
+ + " Please investigate whether this is a false positive.");
}
private int countSingleCharacterUserTerms(Item queryItem) {
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
new file mode 100644
index 00000000000..024f231f524
--- /dev/null
+++ b/container-search/src/main/java/com/yahoo/search/searchers/QueryValidator.java
@@ -0,0 +1,59 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.search.searchers;
+
+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.ToolBox;
+import com.yahoo.search.Query;
+import com.yahoo.search.Result;
+import com.yahoo.search.Searcher;
+import com.yahoo.search.searchchain.Execution;
+import com.yahoo.search.searchchain.PhaseNames;
+
+import static com.yahoo.search.grouping.GroupingQueryParser.SELECT_PARAMETER_PARSING;
+
+/**
+ * Validation of query operators against the schema which is searched
+ *
+ * @author bratseth
+ */
+@After(SELECT_PARAMETER_PARSING)
+@Before(PhaseNames.BACKEND)
+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());
+ return execution.search(query);
+ }
+
+ private static class ItemValidator extends ToolBox.QueryVisitor {
+
+ IndexFacts.Session session;
+
+ public ItemValidator(IndexFacts.Session session) {
+ this.session = 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");
+ }
+ return true;
+ }
+
+ @Override
+ public void onExit() { }
+
+ }
+
+}
diff --git a/container-search/src/main/java/com/yahoo/search/searchers/ValidateNearestNeighborSearcher.java b/container-search/src/main/java/com/yahoo/search/searchers/ValidateNearestNeighborSearcher.java
index 8cae081cada..d22dd2e6af6 100644
--- a/container-search/src/main/java/com/yahoo/search/searchers/ValidateNearestNeighborSearcher.java
+++ b/container-search/src/main/java/com/yahoo/search/searchers/ValidateNearestNeighborSearcher.java
@@ -4,48 +4,46 @@ package com.yahoo.search.searchers;
import com.google.common.annotations.Beta;
-import com.yahoo.container.QrSearchersConfig;
import com.yahoo.prelude.query.Item;
import com.yahoo.prelude.query.NearestNeighborItem;
-import com.yahoo.prelude.query.QueryCanonicalizer;
import com.yahoo.prelude.query.ToolBox;
import com.yahoo.search.Query;
import com.yahoo.search.Result;
import com.yahoo.search.Searcher;
+import com.yahoo.search.grouping.vespa.GroupingExecutor;
import com.yahoo.search.query.ranking.RankProperties;
import com.yahoo.search.result.ErrorMessage;
import com.yahoo.search.searchchain.Execution;
import com.yahoo.tensor.Tensor;
import com.yahoo.tensor.TensorType;
import com.yahoo.vespa.config.search.AttributesConfig;
-import com.yahoo.yolean.chain.After;
+import com.yahoo.yolean.chain.Before;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
-// This depends on tensors in query.getRanking which are moved to rank.properties during query.prepare()
-// Query.prepare is done at the same time as canonicalization (by GroupingExecutor), so use that constraint.
-@After(QueryCanonicalizer.queryCanonicalization)
-
/**
* Validates any NearestNeighborItem query items.
*
* @author arnej
*/
-@Beta
+@Before(GroupingExecutor.COMPONENT_NAME) // Must happen before query.prepare()
public class ValidateNearestNeighborSearcher extends Searcher {
- private Map<String, TensorType> validAttributes = new HashMap<>();
+ private final Map<String, List<TensorType>> validAttributes = new HashMap<>();
public ValidateNearestNeighborSearcher(AttributesConfig attributesConfig) {
for (AttributesConfig.Attribute a : attributesConfig.attribute()) {
- TensorType tt = null;
+ if (! validAttributes.containsKey(a.name())) {
+ validAttributes.put(a.name(), new ArrayList<TensorType>());
+ }
if (a.datatype() == AttributesConfig.Attribute.Datatype.TENSOR) {
- tt = TensorType.fromSpec(a.tensortype());
+ TensorType tt = TensorType.fromSpec(a.tensortype());
+ validAttributes.get(a.name()).add(tt);
}
- validAttributes.put(a.name(), tt);
}
}
@@ -56,7 +54,7 @@ public class ValidateNearestNeighborSearcher extends Searcher {
}
private Optional<ErrorMessage> validate(Query query) {
- NNVisitor visitor = new NNVisitor(query.getRanking().getProperties(), validAttributes);
+ NNVisitor visitor = new NNVisitor(query.getRanking().getProperties(), validAttributes, query);
ToolBox.visit(visitor, query.getModel().getQueryTree().getRoot());
return visitor.errorMessage;
}
@@ -65,26 +63,24 @@ public class ValidateNearestNeighborSearcher extends Searcher {
public Optional<ErrorMessage> errorMessage = Optional.empty();
- private RankProperties rankProperties;
- private Map<String, TensorType> validAttributes;
+ private final Map<String, List<TensorType>> validAttributes;
+ private final Query query;
- public NNVisitor(RankProperties rankProperties, Map<String, TensorType> validAttributes) {
- this.rankProperties = rankProperties;
+ public NNVisitor(RankProperties rankProperties, Map<String, List<TensorType>> validAttributes, Query query) {
this.validAttributes = validAttributes;
+ this.query = query;
}
@Override
public boolean visit(Item item) {
if (item instanceof NearestNeighborItem) {
- validate((NearestNeighborItem) item);
+ String error = validate((NearestNeighborItem)item);
+ if (error != null)
+ errorMessage = Optional.of(ErrorMessage.createIllegalQuery(error));
}
return true;
}
- private void setError(String description) {
- errorMessage = Optional.of(ErrorMessage.createIllegalQuery(description));
- }
-
private static boolean isCompatible(TensorType lhs, TensorType rhs) {
return lhs.dimensions().equals(rhs.dimensions());
}
@@ -98,50 +94,36 @@ public class ValidateNearestNeighborSearcher extends Searcher {
return true;
}
- private void validate(NearestNeighborItem item) {
- int target = item.getTargetNumHits();
- if (target < 1) {
- setError(item.toString() + " has invalid targetNumHits");
- return;
- }
- String qprop = item.getQueryTensorName();
- List<Object> rankPropValList = rankProperties.asMap().get(qprop);
- if (rankPropValList == null) {
- setError(item.toString() + " query tensor not found");
- return;
- }
- if (rankPropValList.size() != 1) {
- setError(item.toString() + " query tensor does not have a single value");
- return;
- }
- Object rankPropValue = rankPropValList.get(0);
- if (! (rankPropValue instanceof Tensor)) {
- setError(item.toString() + " query tensor should be a tensor, was: "+
- (rankPropValue == null ? "null" : rankPropValue.getClass().toString()));
- return;
+ /** Returns an error message if this is invalid, or null if it is valid */
+ private String validate(NearestNeighborItem item) {
+ if (item.getTargetNumHits() < 1)
+ return item + " has invalid targetHits " + item.getTargetNumHits() + ": Must be >= 1";
+
+ String queryFeatureName = "query(" + item.getQueryTensorName() + ")";
+ Optional<Tensor> queryTensor = query.getRanking().getFeatures().getTensor(queryFeatureName);
+ if (queryTensor.isEmpty())
+ return item + " requires a tensor rank feature " + queryFeatureName + " but this is not present";
+
+ if ( ! validAttributes.containsKey(item.getIndexName())) {
+ return item + " field is not an attribute";
}
- Tensor qTensor = (Tensor)rankPropValue;
- TensorType qTensorType = qTensor.type();
-
- String field = item.getIndexName();
- if (validAttributes.containsKey(field)) {
- TensorType fTensorType = validAttributes.get(field);
- if (fTensorType == null) {
- setError(item.toString() + " field is not a tensor");
- return;
+ List<TensorType> allTensorTypes = validAttributes.get(item.getIndexName());
+ for (TensorType fieldType : allTensorTypes) {
+ if (isDenseVector(fieldType) && isCompatible(fieldType, queryTensor.get().type())) {
+ return null;
}
- if (! isCompatible(fTensorType, qTensorType)) {
- setError(item.toString() + " field type "+fTensorType+" does not match query tensor type "+qTensorType);
- return;
+ }
+ for (TensorType fieldType : allTensorTypes) {
+ if (isDenseVector(fieldType) && ! isCompatible(fieldType, queryTensor.get().type())) {
+ return item + " field type " + fieldType + " does not match query type " + queryTensor.get().type();
}
- if (! isDenseVector(fTensorType)) {
- setError(item.toString() + " tensor type "+fTensorType+" is not a dense vector");
- return;
+ }
+ for (TensorType fieldType : allTensorTypes) {
+ if (! isDenseVector(fieldType)) {
+ return item + " tensor type " + fieldType + " is not a dense vector";
}
- } else {
- setError(item.toString() + " field is not an attribute");
- return;
}
+ return item + " field is not a tensor";
}
@Override
diff --git a/container-search/src/main/java/com/yahoo/search/statistics/PeakQpsSearcher.java b/container-search/src/main/java/com/yahoo/search/statistics/PeakQpsSearcher.java
index 108e46fb68e..6d4fcdaf8fb 100644
--- a/container-search/src/main/java/com/yahoo/search/statistics/PeakQpsSearcher.java
+++ b/container-search/src/main/java/com/yahoo/search/statistics/PeakQpsSearcher.java
@@ -137,8 +137,8 @@ public class PeakQpsSearcher extends Searcher {
useMetaHit = false;
propertyName = null;
} else {
- throw new IllegalArgumentException("Config definition out of sync with implementation." +
- " No way to create output for method " + method + ".");
+ throw new IllegalStateException("Config definition out of sync with implementation." +
+ " No way to create output for method " + method + ".");
}
}
diff --git a/container-search/src/main/java/com/yahoo/search/statistics/TimingSearcher.java b/container-search/src/main/java/com/yahoo/search/statistics/TimingSearcher.java
index 0e1acd66600..f344872955a 100644
--- a/container-search/src/main/java/com/yahoo/search/statistics/TimingSearcher.java
+++ b/container-search/src/main/java/com/yahoo/search/statistics/TimingSearcher.java
@@ -14,16 +14,15 @@ import com.yahoo.search.statistics.TimeTracker.Activity;
import com.yahoo.statistics.Statistics;
import com.yahoo.statistics.Value;
-
/**
* A searcher which is intended to be useful as a general probe for
* measuring time consumption a search chain.
*
- *
- * @author <a href="mailto:steinar@yahoo-inc.com">Steinar Knutsen</a>
+ * @author Steinar Knutsen
*/
@Before("rawQuery")
public class TimingSearcher extends PingableSearcher {
+
private Value measurements;
private final boolean measurePing;
private final boolean measureSearch;
@@ -140,5 +139,4 @@ public class TimingSearcher extends PingableSearcher {
super.deconstruct();
}
-
}
diff --git a/container-search/src/main/java/com/yahoo/search/yql/FieldFiller.java b/container-search/src/main/java/com/yahoo/search/yql/FieldFiller.java
index df9f2af0cce..b2791875d27 100644
--- a/container-search/src/main/java/com/yahoo/search/yql/FieldFiller.java
+++ b/container-search/src/main/java/com/yahoo/search/yql/FieldFiller.java
@@ -128,8 +128,7 @@ public class FieldFiller extends Searcher {
Set<String> summaryFields = result.getQuery().getPresentation().getSummaryFields();
- if (summaryFields.isEmpty() ||
- summaryClass == null ||
+ if (summaryFields.isEmpty() || summaryClass == null ||
result.getQuery().properties().getBoolean(FIELD_FILLER_DISABLE)) {
return;
}
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 8b2438e3ec2..209340e0a99 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
@@ -5,6 +5,7 @@ import com.google.common.annotations.Beta;
import com.google.inject.Inject;
import com.yahoo.language.Linguistics;
import com.yahoo.language.simple.SimpleLinguistics;
+import com.yahoo.processing.IllegalInputException;
import com.yahoo.search.Query;
import com.yahoo.search.Result;
import com.yahoo.search.Searcher;
@@ -28,6 +29,7 @@ import java.util.logging.Logger;
*
* @author Steinar Knutsen
*/
+// TODO: The query model should do this
@Beta
@Provides(MinimalQueryInserter.EXTERNAL_YQL)
@Before(PhaseNames.TRANSFORMED_QUERY)
@@ -40,19 +42,22 @@ public class MinimalQueryInserter extends Searcher {
private static final CompoundName MAX_HITS = new CompoundName("maxHits");
private static final CompoundName MAX_OFFSET = new CompoundName("maxOffset");
- private static Logger log = Logger.getLogger(MinimalQueryInserter.class.getName());
+ private static final Logger log = Logger.getLogger(MinimalQueryInserter.class.getName());
@Inject
public MinimalQueryInserter(Linguistics linguistics) {
// Warmup is needed to avoid a large 400ms init cost during first execution of yql code.
warmup(linguistics);
}
+
public MinimalQueryInserter() {
this(new SimpleLinguistics());
}
+
static boolean warmup() {
return warmup(new SimpleLinguistics());
}
+
private static boolean warmup(Linguistics linguistics) {
Query query = new Query("search/?yql=select%20*%20from%20sources%20where%20title%20contains%20'xyz';");
Result result = insertQuery(query, new ParserEnvironment().setLinguistics(linguistics));
@@ -67,6 +72,18 @@ public class MinimalQueryInserter extends Searcher {
return true;
}
+ @Override
+ public Result search(Query query, Execution execution) {
+ try {
+ if (query.properties().get(YQL) == null) return execution.search(query);
+ Result result = insertQuery(query, ParserEnvironment.fromExecutionContext(execution.context()));
+ return (result == null) ? execution.search(query) : result;
+ }
+ catch (IllegalArgumentException e) {
+ throw new IllegalInputException("Illegal YQL query", e);
+ }
+ }
+
private static Result insertQuery(Query query, ParserEnvironment env) {
YqlParser parser = (YqlParser) ParserFactory.newInstance(Query.Type.YQL, env);
parser.setQueryParser(false);
@@ -82,12 +99,16 @@ public class MinimalQueryInserter extends Searcher {
int maxHits = query.properties().getInteger(MAX_HITS);
int maxOffset = query.properties().getInteger(MAX_OFFSET);
if (parser.getOffset() > maxOffset) {
- return new Result(query, ErrorMessage.createInvalidQueryParameter("Requested offset " + parser.getOffset()
- + ", but the max offset allowed is " + maxOffset + "."));
+ return new Result(query,
+ ErrorMessage.createInvalidQueryParameter("Requested offset " + parser.getOffset() +
+ ", but the max offset allowed is " +
+ maxOffset + "."));
}
if (parser.getHits() > maxHits) {
- return new Result(query, ErrorMessage.createInvalidQueryParameter("Requested " + parser.getHits()
- + " hits returned, but max hits allowed is " + maxHits + "."));
+ return new Result(query,
+ ErrorMessage.createInvalidQueryParameter("Requested " + parser.getHits() +
+ " hits returned, but max hits allowed is " +
+ maxHits + "."));
}
}
query.getModel().getQueryTree().setRoot(newTree.getRoot());
@@ -116,12 +137,4 @@ public class MinimalQueryInserter extends Searcher {
return null;
}
- @Override
- public Result search(Query query, Execution execution) {
- if (query.properties().get(YQL) == null) return execution.search(query);
-
- Result result = insertQuery(query, ParserEnvironment.fromExecutionContext(execution.context()));
- return (result == null) ? execution.search(query) : result;
- }
-
}
diff --git a/container-search/src/main/java/com/yahoo/search/yql/NullItemException.java b/container-search/src/main/java/com/yahoo/search/yql/NullItemException.java
index b451b9c85c5..8502af76858 100644
--- a/container-search/src/main/java/com/yahoo/search/yql/NullItemException.java
+++ b/container-search/src/main/java/com/yahoo/search/yql/NullItemException.java
@@ -4,11 +4,12 @@ package com.yahoo.search.yql;
/**
* Used to communicate a NullItem has been encountered in the query tree.
*
- * @author <a href="mailto:steinar@yahoo-inc.com">Steinar Knutsen</a>
+ * @author Steinar Knutsen
*/
-@SuppressWarnings("serial")
public class NullItemException extends RuntimeException {
+
public NullItemException(String message) {
super(message);
}
+
}
diff --git a/container-search/src/main/java/com/yahoo/search/yql/OperatorNode.java b/container-search/src/main/java/com/yahoo/search/yql/OperatorNode.java
index 431f159db01..f529de32fe9 100644
--- a/container-search/src/main/java/com/yahoo/search/yql/OperatorNode.java
+++ b/container-search/src/main/java/com/yahoo/search/yql/OperatorNode.java
@@ -13,9 +13,7 @@ import java.util.Map;
/**
* Represents a use of an operator against concrete arguments. The types of arguments depend on the operator.
- * <p>
* The extension point of this scheme is the Operator rather than new types of Nodes.
- * <p>
* Operators SHOULD take a fixed number of arguments -- wrap variable argument counts in Lists.
*/
final class OperatorNode<T extends Operator> {
@@ -164,7 +162,7 @@ final class OperatorNode<T extends Operator> {
}
// we are aware only of types used in our logical operator trees -- OperatorNode, List, and constant values
- private static final Function<Object, Object> COPY = new Function<Object, Object>() {
+ private static final Function<Object, Object> COPY = new Function<>() {
@Override
public Object apply(Object input) {
if (input instanceof List) {
diff --git a/container-search/src/main/java/com/yahoo/search/yql/ProgramCompileException.java b/container-search/src/main/java/com/yahoo/search/yql/ProgramCompileException.java
index 46dfb780e2d..32ab9d682e3 100644
--- a/container-search/src/main/java/com/yahoo/search/yql/ProgramCompileException.java
+++ b/container-search/src/main/java/com/yahoo/search/yql/ProgramCompileException.java
@@ -1,7 +1,9 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.search.yql;
-class ProgramCompileException extends RuntimeException {
+import com.yahoo.processing.IllegalInputException;
+
+class ProgramCompileException extends IllegalInputException {
private Location sourceLocation;
@@ -9,27 +11,6 @@ class ProgramCompileException extends RuntimeException {
super(message);
}
- public ProgramCompileException(String message, Object... args) {
- super(formatMessage(message, args));
- }
-
- private static String formatMessage(String message, Object... args) {
- return args == null ? message : String.format(message, args);
- }
-
- public ProgramCompileException(String message, Throwable cause) {
- super(message, cause);
- }
-
- public ProgramCompileException(Throwable cause) {
- super(cause);
- }
-
- public ProgramCompileException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
- super(message, cause, enableSuppression, writableStackTrace);
- }
-
-
public ProgramCompileException(Location sourceLocation, String message, Object... args) {
super(String.format("%s %s", sourceLocation != null ? sourceLocation : "", args == null ? message : String.format(message, args)));
this.sourceLocation = sourceLocation;
diff --git a/container-search/src/main/java/com/yahoo/search/yql/ProgramParser.java b/container-search/src/main/java/com/yahoo/search/yql/ProgramParser.java
index a270d935a97..d3f07bae428 100644
--- a/container-search/src/main/java/com/yahoo/search/yql/ProgramParser.java
+++ b/container-search/src/main/java/com/yahoo/search/yql/ProgramParser.java
@@ -14,76 +14,46 @@ import com.yahoo.search.yql.yqlplusParser.AnnotateExpressionContext;
import com.yahoo.search.yql.yqlplusParser.ArgumentContext;
import com.yahoo.search.yql.yqlplusParser.ArgumentsContext;
import com.yahoo.search.yql.yqlplusParser.ArrayLiteralContext;
-import com.yahoo.search.yql.yqlplusParser.ArrayTypeContext;
import com.yahoo.search.yql.yqlplusParser.Call_sourceContext;
import com.yahoo.search.yql.yqlplusParser.ConstantArrayContext;
import com.yahoo.search.yql.yqlplusParser.ConstantExpressionContext;
import com.yahoo.search.yql.yqlplusParser.ConstantMapExpressionContext;
import com.yahoo.search.yql.yqlplusParser.ConstantPropertyNameAndValueContext;
-import com.yahoo.search.yql.yqlplusParser.Delete_statementContext;
import com.yahoo.search.yql.yqlplusParser.DereferencedExpressionContext;
import com.yahoo.search.yql.yqlplusParser.EqualityExpressionContext;
import com.yahoo.search.yql.yqlplusParser.ExpressionContext;
-import com.yahoo.search.yql.yqlplusParser.FallbackContext;
import com.yahoo.search.yql.yqlplusParser.Field_defContext;
-import com.yahoo.search.yql.yqlplusParser.Field_names_specContext;
-import com.yahoo.search.yql.yqlplusParser.Field_values_group_specContext;
-import com.yahoo.search.yql.yqlplusParser.Field_values_specContext;
import com.yahoo.search.yql.yqlplusParser.IdentContext;
-import com.yahoo.search.yql.yqlplusParser.Import_listContext;
-import com.yahoo.search.yql.yqlplusParser.Import_statementContext;
import com.yahoo.search.yql.yqlplusParser.InNotInTargetContext;
-import com.yahoo.search.yql.yqlplusParser.Insert_sourceContext;
-import com.yahoo.search.yql.yqlplusParser.Insert_statementContext;
-import com.yahoo.search.yql.yqlplusParser.Insert_valuesContext;
-import com.yahoo.search.yql.yqlplusParser.JoinExpressionContext;
-import com.yahoo.search.yql.yqlplusParser.Join_exprContext;
import com.yahoo.search.yql.yqlplusParser.LimitContext;
import com.yahoo.search.yql.yqlplusParser.Literal_elementContext;
import com.yahoo.search.yql.yqlplusParser.Literal_listContext;
import com.yahoo.search.yql.yqlplusParser.LogicalANDExpressionContext;
import com.yahoo.search.yql.yqlplusParser.LogicalORExpressionContext;
import com.yahoo.search.yql.yqlplusParser.MapExpressionContext;
-import com.yahoo.search.yql.yqlplusParser.MapTypeContext;
-import com.yahoo.search.yql.yqlplusParser.Merge_componentContext;
-import com.yahoo.search.yql.yqlplusParser.Merge_statementContext;
-import com.yahoo.search.yql.yqlplusParser.ModuleIdContext;
-import com.yahoo.search.yql.yqlplusParser.ModuleNameContext;
import com.yahoo.search.yql.yqlplusParser.MultiplicativeExpressionContext;
import com.yahoo.search.yql.yqlplusParser.Namespaced_nameContext;
-import com.yahoo.search.yql.yqlplusParser.Next_statementContext;
import com.yahoo.search.yql.yqlplusParser.OffsetContext;
import com.yahoo.search.yql.yqlplusParser.OrderbyContext;
import com.yahoo.search.yql.yqlplusParser.Orderby_fieldContext;
import com.yahoo.search.yql.yqlplusParser.Output_specContext;
-import com.yahoo.search.yql.yqlplusParser.Paged_clauseContext;
-import com.yahoo.search.yql.yqlplusParser.ParamsContext;
import com.yahoo.search.yql.yqlplusParser.Pipeline_stepContext;
-import com.yahoo.search.yql.yqlplusParser.Procedure_argumentContext;
-import com.yahoo.search.yql.yqlplusParser.Program_arglistContext;
import com.yahoo.search.yql.yqlplusParser.Project_specContext;
import com.yahoo.search.yql.yqlplusParser.ProgramContext;
import com.yahoo.search.yql.yqlplusParser.PropertyNameAndValueContext;
import com.yahoo.search.yql.yqlplusParser.Query_statementContext;
import com.yahoo.search.yql.yqlplusParser.RelationalExpressionContext;
import com.yahoo.search.yql.yqlplusParser.RelationalOpContext;
-import com.yahoo.search.yql.yqlplusParser.Returning_specContext;
import com.yahoo.search.yql.yqlplusParser.Scalar_literalContext;
-import com.yahoo.search.yql.yqlplusParser.Select_source_joinContext;
import com.yahoo.search.yql.yqlplusParser.Select_source_multiContext;
import com.yahoo.search.yql.yqlplusParser.Select_statementContext;
-import com.yahoo.search.yql.yqlplusParser.Selectvar_statementContext;
import com.yahoo.search.yql.yqlplusParser.Sequence_sourceContext;
import com.yahoo.search.yql.yqlplusParser.Source_listContext;
import com.yahoo.search.yql.yqlplusParser.Source_specContext;
import com.yahoo.search.yql.yqlplusParser.Source_statementContext;
import com.yahoo.search.yql.yqlplusParser.StatementContext;
import com.yahoo.search.yql.yqlplusParser.TimeoutContext;
-import com.yahoo.search.yql.yqlplusParser.TypenameContext;
import com.yahoo.search.yql.yqlplusParser.UnaryExpressionContext;
-import com.yahoo.search.yql.yqlplusParser.Update_statementContext;
-import com.yahoo.search.yql.yqlplusParser.Update_valuesContext;
-import com.yahoo.search.yql.yqlplusParser.ViewContext;
import com.yahoo.search.yql.yqlplusParser.WhereContext;
import org.antlr.v4.runtime.BaseErrorListener;
@@ -126,7 +96,6 @@ final class ProgramParser {
return prepareParser(file.getAbsoluteFile().toString(), new CaseInsensitiveFileStream(file.getAbsolutePath()));
}
-
private yqlplusParser prepareParser(String programName, CharStream input) {
yqlplusLexer lexer = new yqlplusLexer(input);
lexer.removeErrorListeners();
@@ -139,7 +108,7 @@ final class ProgramParser {
int charPositionInLine,
@NotNull String msg,
@Nullable RecognitionException e) {
- throw new ProgramCompileException(new Location(programName, line, charPositionInLine), msg);
+ throw new ProgramCompileException(new Location(programName, line, charPositionInLine), "%s", msg);
}
});
@@ -156,7 +125,7 @@ final class ProgramParser {
int charPositionInLine,
@NotNull String msg,
@Nullable RecognitionException e) {
- throw new ProgramCompileException(new Location(programName, line, charPositionInLine), msg);
+ throw new ProgramCompileException(new Location(programName, line, charPositionInLine), "%s", msg);
}
});
@@ -168,41 +137,18 @@ final class ProgramParser {
try {
return parser.program();
} catch (RecognitionException e) {
- //Retry parsing using full LL mode
+ // Retry parsing using full LL mode
parser.reset();
parser.getInterpreter().setPredictionMode(PredictionMode.LL);
return parser.program();
}
}
- public OperatorNode<StatementOperator> parse(String programName, InputStream program) throws IOException, RecognitionException {
- yqlplusParser parser = prepareParser(programName, program);
- return convertProgram(parseProgram(parser), parser, programName);
- }
-
public OperatorNode<StatementOperator> parse(String programName, String program) throws IOException, RecognitionException {
yqlplusParser parser = prepareParser(programName, program);
return convertProgram(parseProgram(parser), parser, programName);
}
- public OperatorNode<StatementOperator> parse(File input) throws IOException, RecognitionException {
- yqlplusParser parser = prepareParser(input);
- return convertProgram(parseProgram(parser), parser, input.getAbsoluteFile().toString());
- }
-
- public OperatorNode<ExpressionOperator> parseExpression(String input) throws IOException, RecognitionException {
- return convertExpr(prepareParser("<expression>", input).expression(false).getRuleContext(), new Scope());
- }
-
- public OperatorNode<ExpressionOperator> parseExpression(String input, Set<String> visibleAliases) throws IOException, RecognitionException {
- Scope scope = new Scope();
- final Location loc = new Location("<expression>", -1, -1);
- for (String alias : visibleAliases) {
- scope.defineDataSource(loc, alias);
- }
- return convertExpr(prepareParser("<expression>", input).expression(false).getRuleContext(), scope);
- }
-
private Location toLocation(Scope scope, ParseTree node) {
Token start;
if (node instanceof ParserRuleContext) {
@@ -212,8 +158,7 @@ final class ProgramParser {
} else {
throw new ProgramCompileException("Location is not available for type " + node.getClass());
}
- Location location = new Location(scope != null? scope.programName: "<string>", start.getLine(), start.getCharPositionInLine());
- return location;
+ return new Location(scope != null? scope.programName: "<string>", start.getLine(), start.getCharPositionInLine());
}
private List<String> readName(Namespaced_nameContext node) {
@@ -230,14 +175,6 @@ final class ProgramParser {
private final List<String> binding;
- Binding(String moduleName, String exportName) {
- this.binding = ImmutableList.of(moduleName, exportName);
- }
-
- Binding(String moduleName) {
- this.binding = ImmutableList.of(moduleName);
- }
-
Binding(List<String> binding) {
this.binding = binding;
}
@@ -263,13 +200,6 @@ final class ProgramParser {
final yqlplusParser parser;
final String programName;
- Scope() {
- this.parser = null;
- this.programName = null;
- this.root = this;
- this.parent = null;
- }
-
Scope(yqlplusParser parser, String programName) {
this.parser = parser;
this.programName = programName;
@@ -288,15 +218,10 @@ final class ProgramParser {
return parser;
}
- public String getProgramName() {
- return programName;
- }
-
public Set<String> getCursors() {
return cursors;
}
-
boolean isBound(String name) {
// bindings live only in the 'root' node
return root.bindings.containsKey(name);
@@ -329,13 +254,6 @@ final class ProgramParser {
root.bindings.put(symbolName, new Binding(binding));
}
- public void bindModuleSymbol(Location loc, List<String> moduleName, String exportName, String symbolName) {
- ImmutableList.Builder<String> builder = ImmutableList.builder();
- builder.addAll(moduleName);
- builder.add(exportName);
- bindModule(loc, builder.build(), symbolName);
- }
-
public void defineDataSource(Location loc, String name) {
if (isCursor(name)) {
throw new ProgramCompileException(loc, "Alias '%s' is already used.", name);
@@ -376,13 +294,12 @@ final class ProgramParser {
}
}
- private OperatorNode<SequenceOperator> convertSelectOrInsertOrUpdateOrDelete(ParseTree node, Scope scopeParent) {
+ private OperatorNode<SequenceOperator> convertSelect(ParseTree node, Scope scopeParent) {
- Preconditions.checkArgument(node instanceof Select_statementContext || node instanceof Insert_statementContext ||
- node instanceof Update_statementContext || node instanceof Delete_statementContext);
+ Preconditions.checkArgument(node instanceof Select_statementContext);
- // SELECT^ select_field_spec select_source where? orderby? limit? offset? timeout? fallback?
- // select is the only place to define where/orderby/limit/offset and joins
+ // SELECT^ select_field_spec select_source where? orderby? limit? offset? timeout?
+ // select is the only place to define where/orderby/limit/offset
Scope scope = scopeParent.child();
ProjectionBuilder proj = null;
OperatorNode<SequenceOperator> source = null;
@@ -391,29 +308,18 @@ final class ProgramParser {
OperatorNode<ExpressionOperator> offset = null;
OperatorNode<ExpressionOperator> limit = null;
OperatorNode<ExpressionOperator> timeout = null;
- OperatorNode<SequenceOperator> fallback = null;
- OperatorNode<SequenceOperator> insertValues = null;
- OperatorNode<ExpressionOperator> updateValues = null;
-
- ParseTree sourceNode;
- if (node instanceof Select_statementContext ) {
- sourceNode = node.getChild(2) != null ? node.getChild(2).getChild(0):null;
- } else {
- sourceNode = node.getChild(1);
- }
+ ParseTree sourceNode = node.getChild(2) != null ? node.getChild(2).getChild(0):null;
if (sourceNode != null) {
switch (getParseTreeIndex(sourceNode)) {
// ALL_SOURCE and MULTI_SOURCE are how FROM SOURCES
// *|source_name,... are parsed
- // They can't be used directly with the JOIN syntax at this time
- case yqlplusParser.RULE_select_source_all: {
+ case yqlplusParser.RULE_select_source_all:
Location location = toLocation(scope, sourceNode.getChild(2));
source = OperatorNode.create(location, SequenceOperator.ALL);
source.putAnnotation("alias", "row");
scope.defineDataSource(location, "row");
- }
break;
case yqlplusParser.RULE_select_source_multi:
Source_listContext multiSourceContext = ((Select_source_multiContext) sourceNode).source_list();
@@ -421,22 +327,8 @@ final class ProgramParser {
source.putAnnotation("alias", "row");
scope.defineDataSource(toLocation(scope, multiSourceContext), "row");
break;
- case yqlplusParser.RULE_select_source_join:
+ case yqlplusParser.RULE_select_source_from:
source = convertSource((ParserRuleContext) sourceNode.getChild(1), scope);
- List<Join_exprContext> joinContexts = ((Select_source_joinContext)sourceNode).join_expr();
- for (Join_exprContext joinContext:joinContexts) {
- source = convertJoin(joinContext, source, scope);
- }
- break;
- case yqlplusParser.RULE_insert_source:
- Insert_sourceContext insertSourceContext = (Insert_sourceContext) sourceNode;
- source = convertSource((ParserRuleContext)insertSourceContext.getChild(1), scope);
- break;
- case yqlplusParser.RULE_delete_source:
- source = convertSource((ParserRuleContext)sourceNode.getChild(1), scope);
- break;
- case yqlplusParser.RULE_update_source:
- source = convertSource((ParserRuleContext)sourceNode.getChild(0), scope);
break;
}
} else {
@@ -451,9 +343,6 @@ final class ProgramParser {
proj = readProjection(((Project_specContext) child.getChild(0)).field_def(), scope);
}
break;
- case yqlplusParser.RULE_returning_spec:
- proj = readProjection(((Returning_specContext) child).select_field_spec().project_spec().field_def(), scope);
- break;
case yqlplusParser.RULE_where:
filter = convertExpr(((WhereContext) child).expression(), scope);
break;
@@ -475,23 +364,6 @@ final class ProgramParser {
case yqlplusParser.RULE_timeout:
timeout = convertExpr(((TimeoutContext) child).fixed_or_parameter(), scope);
break;
- case yqlplusParser.RULE_fallback:
- fallback = convertQuery(((FallbackContext) child).select_statement(), scope);
- break;
- case yqlplusParser.RULE_insert_values:
- if (child.getChild(0) instanceof yqlplusParser.Query_statementContext) {
- insertValues = convertQuery(child.getChild(0).getChild(0), scope);
- } else {
- insertValues = readBatchValues(((Insert_valuesContext) child).field_names_spec(), ((Insert_valuesContext)child).field_values_group_spec(), scope);
- }
- break;
- case yqlplusParser.RULE_update_values:
- if (getParseTreeIndex(child.getChild(0)) == yqlplusParser.RULE_field_def) {
- updateValues = readValues(((Update_valuesContext)child).field_def(), scope);
- } else {
- updateValues = readValues((Field_names_specContext)child.getChild(0), (Field_values_specContext)child.getChild(2), scope);
- }
- break;
}
}
// now assemble the logical plan
@@ -500,26 +372,6 @@ final class ProgramParser {
if (filter != null) {
result = OperatorNode.create(SequenceOperator.FILTER, result, filter);
}
- // insert values
- if (insertValues != null) {
- result = OperatorNode.create(SequenceOperator.INSERT, result, insertValues);
- }
- // update
- if (updateValues != null) {
- if (filter != null) {
- result = OperatorNode.create(SequenceOperator.UPDATE, source, updateValues, filter);
- } else {
- result = OperatorNode.create(SequenceOperator.UPDATE_ALL, source, updateValues);
- }
- }
- // delete
- if (getParseTreeIndex(node) == yqlplusParser.RULE_delete_statement) {
- if (filter != null) {
- result = OperatorNode.create(SequenceOperator.DELETE, source, filter);
- } else {
- result = OperatorNode.create(SequenceOperator.DELETE_ALL, source);
- }
- }
// then sort (or project and sort)
boolean projectBeforeSort = false;
if (orderby != null) {
@@ -558,30 +410,9 @@ final class ProgramParser {
if (timeout != null) {
result = OperatorNode.create(SequenceOperator.TIMEOUT, result, timeout);
}
- // if there's a fallback, emit a fallback node
- if (fallback != null) {
- result = OperatorNode.create(SequenceOperator.FALLBACK, result, fallback);
- }
return result;
}
- private OperatorNode<ExpressionOperator> readValues(List<Field_defContext> fieldDefs, Scope scope) {
- List<String> fieldNames;
- List<OperatorNode<ExpressionOperator>> fieldValues;
- int numPairs = fieldDefs.size();
- fieldNames = Lists.newArrayListWithExpectedSize(numPairs);
- fieldValues = Lists.newArrayListWithExpectedSize(numPairs);
- for (int j = 0; j < numPairs; j++) {
- ParseTree startNode = fieldDefs.get(j);
- while(startNode.getChildCount() < 3) {
- startNode = startNode.getChild(0);
- }
- fieldNames.add((String) convertExpr(startNode.getChild(0), scope).getArgument(1));
- fieldValues.add(convertExpr(startNode.getChild(2), scope));
- }
- return OperatorNode.create(ExpressionOperator.MAP, fieldNames, fieldValues);
- }
-
private OperatorNode<SequenceOperator> readMultiSource(Scope scope, Source_listContext multiSource) {
List<List<String>> sourceNameList = Lists.newArrayList();
List<Namespaced_nameContext> nameSpaces = multiSource.namespaced_name();
@@ -591,9 +422,7 @@ final class ProgramParser {
}
return OperatorNode.create(toLocation(scope, multiSource), SequenceOperator.MULTISOURCE, sourceNameList);
}
-// pipeline_step
-// : namespaced_name arguments[false]?
-// ;
+
private OperatorNode<SequenceOperator> convertPipe(Query_statementContext queryStatementContext, List<Pipeline_stepContext> nodes, Scope scope) {
OperatorNode<SequenceOperator> result = convertQuery(queryStatementContext.getChild(0), scope.getRoot());
for (Pipeline_stepContext step:nodes) {
@@ -603,7 +432,7 @@ final class ProgramParser {
} else {
List<String> name = readName(step.namespaced_name());
List<OperatorNode<ExpressionOperator>> args = ImmutableList.of();
- //LPAREN (argument[$in_select] (COMMA argument[$in_select])*) RPAREN
+ // LPAREN (argument[$in_select] (COMMA argument[$in_select])*) RPAREN
if (step.getChildCount() > 1) {
ArgumentsContext arguments = step.arguments();
if (arguments.getChildCount() > 2) {
@@ -621,56 +450,25 @@ final class ProgramParser {
return result;
}
- private OperatorNode<SequenceOperator> convertMerge(List<Merge_componentContext> mergeComponentList, Scope scope) {
- Preconditions.checkArgument(mergeComponentList != null);
- List<OperatorNode<SequenceOperator>> sources = Lists.newArrayListWithExpectedSize(mergeComponentList.size());
- for (Merge_componentContext mergeComponent:mergeComponentList) {
- Select_statementContext selectContext = mergeComponent.select_statement();
- Source_statementContext sourceContext = mergeComponent.source_statement();
- if (selectContext != null) {
- sources.add(convertQuery(selectContext, scope.getRoot()));
- } else {
- sources.add(convertQuery(sourceContext, scope.getRoot()));
- }
- }
- return OperatorNode.create(SequenceOperator.MERGE, sources);
- }
-
private OperatorNode<SequenceOperator> convertQuery(ParseTree node, Scope scope) {
- if (node instanceof Select_statementContext
- || node instanceof Insert_statementContext
- || node instanceof Update_statementContext
- || node instanceof Delete_statementContext) {
- return convertSelectOrInsertOrUpdateOrDelete(node, scope.getRoot());
- } else if (node instanceof Source_statementContext) { //for pipe
+ if (node instanceof Select_statementContext) {
+ return convertSelect(node, scope.getRoot());
+ } else if (node instanceof Source_statementContext) { // for pipe
Source_statementContext sourceStatementContext = (Source_statementContext)node;
return convertPipe(sourceStatementContext.query_statement(), sourceStatementContext.pipeline_step(), scope);
- } else if (node instanceof Merge_statementContext) {
- return convertMerge(((Merge_statementContext)node).merge_component(), scope);
} else {
throw new IllegalArgumentException("Unexpected argument type to convertQueryStatement: " + node.toStringTree());
}
}
- private OperatorNode<SequenceOperator> convertJoin(Join_exprContext node, OperatorNode<SequenceOperator> left, Scope scope) {
- Source_specContext sourceSpec = node.source_spec();
- OperatorNode<SequenceOperator> right = convertSource(sourceSpec, scope);
- JoinExpressionContext joinContext = node.joinExpression();
- OperatorNode<ExpressionOperator> joinExpression = readBinOp(ExpressionOperator.valueOf("EQ"), joinContext.getChild(0), joinContext.getChild(2), scope);
- if (joinExpression.getOperator() != ExpressionOperator.EQ) {
- throw new ProgramCompileException(joinExpression.getLocation(), "Unexpected join expression type: %s (expected EQ)", joinExpression.getOperator());
- }
- return OperatorNode.create(toLocation(scope, sourceSpec), node.join_spec().LEFT() != null ? SequenceOperator.LEFT_JOIN : SequenceOperator.JOIN, left, right, joinExpression);
- }
-
private String assignAlias(String alias, ParserRuleContext node, Scope scope) {
if (alias == null) {
alias = "source";
}
- if (node != null && node instanceof yqlplusParser.Alias_defContext) {
- //alias_def : (AS? ID);
+ if (node instanceof yqlplusParser.Alias_defContext) {
+ // alias_def : (AS? ID);
ParseTree idChild = node;
if (node.getChildCount() > 1) {
idChild = node.getChild(1);
@@ -690,20 +488,14 @@ final class ProgramParser {
scope.defineDataSource(null, candidate);
return alias;
}
- }
-
- private OperatorNode<SequenceOperator> convertSource(ParserRuleContext sourceSpecNode, Scope scope) {
+ }
+ private OperatorNode<SequenceOperator> convertSource(ParserRuleContext sourceSpecNode, Scope scope) {
// DataSources
String alias;
OperatorNode<SequenceOperator> result;
ParserRuleContext dataSourceNode = sourceSpecNode;
ParserRuleContext aliasContext = null;
- //data_source
- //: call_source
- //| LPAREN source_statement RPAREN
- //| sequence_source
- //;
if (sourceSpecNode instanceof Source_specContext) {
dataSourceNode = (ParserRuleContext)sourceSpecNode.getChild(0);
if (sourceSpecNode.getChildCount() == 2) {
@@ -717,7 +509,6 @@ final class ProgramParser {
}
}
switch (getParseTreeIndex(dataSourceNode)) {
- case yqlplusParser.RULE_write_data_source:
case yqlplusParser.RULE_call_source: {
List<String> names = readName(dataSourceNode.getChild(Namespaced_nameContext.class, 0));
alias = assignAlias(names.get(names.size() - 1), aliasContext, scope);
@@ -763,60 +554,9 @@ final class ProgramParser {
return result;
}
- private OperatorNode<TypeOperator> decodeType(Scope scope, TypenameContext type) {
-
- TypeOperator op;
- ParseTree typeNode = type.getChild(0);
- switch (getParseTreeIndex(typeNode)) {
- case yqlplusParser.TYPE_BOOLEAN:
- op = TypeOperator.BOOLEAN;
- break;
- case yqlplusParser.TYPE_BYTE:
- op = TypeOperator.BYTE;
- break;
- case yqlplusParser.TYPE_DOUBLE:
- op = TypeOperator.DOUBLE;
- break;
- case yqlplusParser.TYPE_INT16:
- op = TypeOperator.INT16;
- break;
- case yqlplusParser.TYPE_INT32:
- op = TypeOperator.INT32;
- break;
- case yqlplusParser.TYPE_INT64:
- op = TypeOperator.INT64;
- break;
- case yqlplusParser.TYPE_STRING:
- op = TypeOperator.STRING;
- break;
- case yqlplusParser.TYPE_TIMESTAMP:
- op = TypeOperator.TIMESTAMP;
- break;
- case yqlplusParser.RULE_arrayType:
- return OperatorNode.create(toLocation(scope, typeNode), TypeOperator.ARRAY, decodeType(scope, ((ArrayTypeContext)typeNode).getChild(TypenameContext.class, 0)));
- case yqlplusParser.RULE_mapType:
- return OperatorNode.create(toLocation(scope, typeNode), TypeOperator.MAP, decodeType(scope, ((MapTypeContext)typeNode).getChild(TypenameContext.class, 0)));
- default:
- throw new ProgramCompileException("Unknown type " + typeNode.getText());
- }
- return OperatorNode.create(toLocation(scope, typeNode), op);
- }
-
- private List<String> createBindingName(ParseTree node) {
- if (node instanceof ModuleNameContext) {
- if (((ModuleNameContext)node).namespaced_name() != null) {
- return readName(((ModuleNameContext)node).namespaced_name());
- } else if (((ModuleNameContext)node).literalString() != null) {
- return ImmutableList.of(((ModuleNameContext)node).literalString().STRING().getText());
- }
- } else if (node instanceof ModuleIdContext) {
- return ImmutableList.of(node.getText());
- }
- throw new ProgramCompileException("Wrong context");
- }
-
- private OperatorNode<StatementOperator> convertProgram(
- ParserRuleContext program, yqlplusParser parser, String programName) {
+ private OperatorNode<StatementOperator> convertProgram(ParserRuleContext program,
+ yqlplusParser parser,
+ String programName) {
Scope scope = new Scope(parser, programName);
List<OperatorNode<StatementOperator>> stmts = Lists.newArrayList();
int output = 0;
@@ -825,148 +565,37 @@ final class ProgramParser {
continue;
}
ParserRuleContext ruleContext = (ParserRuleContext) node;
- switch (ruleContext.getRuleIndex()) {
- case yqlplusParser.RULE_params: {
- // ^(ARGUMENT ident typeref expression?)
- ParamsContext paramsContext = (ParamsContext) ruleContext;
- Program_arglistContext program_arglistContext = paramsContext.program_arglist();
- if (program_arglistContext != null) {
- List<Procedure_argumentContext> argList = program_arglistContext.procedure_argument();
- for (Procedure_argumentContext procedureArgumentContext : argList) {
- String name = procedureArgumentContext.ident().getText();
- OperatorNode<TypeOperator> type = decodeType(scope, procedureArgumentContext.getChild(TypenameContext.class, 0));
- OperatorNode<ExpressionOperator> defaultValue = OperatorNode.create(ExpressionOperator.NULL);
- if (procedureArgumentContext.expression() != null) {
- defaultValue = convertExpr(procedureArgumentContext.expression(), scope);
- }
- scope.defineVariable(toLocation(scope, procedureArgumentContext), name);
- stmts.add(OperatorNode.create(StatementOperator.ARGUMENT, name, type, defaultValue));
- }
- }
- break;
- }
- case yqlplusParser.RULE_import_statement: {
- Import_statementContext importContext = (Import_statementContext) ruleContext;
- if (null == importContext.import_list()) {
- List<String> name = createBindingName(node.getChild(1));
- String target;
- Location location = toLocation(scope, node.getChild(1));
- if (node.getChildCount() == 2) {
- target = name.get(0);
- } else if (node.getChildCount() == 4) {
- target = node.getChild(3).getText();
- } else {
- throw new ProgramCompileException("Unknown node count for IMPORT: " + node.toStringTree());
- }
- scope.bindModule(location, name, target);
- } else {
- // | FROM moduleName IMPORT import_list -> ^(IMPORT_FROM
- // moduleName import_list+)
- Import_listContext importListContext = importContext.import_list();
- List<String> name = createBindingName(importContext.moduleName());
- Location location = toLocation(scope, importContext.moduleName());
- List<ModuleIdContext> moduleIds = importListContext.moduleId();
- List<String> symbols = Lists.newArrayListWithExpectedSize(moduleIds.size());
- for (ModuleIdContext cnode : moduleIds) {
- symbols.add(cnode.ID().getText());
- }
- for (String sym : symbols) {
- scope.bindModuleSymbol(location, name, sym, sym);
- }
- }
- break;
- }
+ if (ruleContext.getRuleIndex() != yqlplusParser.RULE_statement)
+ throw new ProgramCompileException("Unknown program element: " + node.getText());
- // DDL
- case yqlplusParser.RULE_ddl:
- ruleContext = (ParserRuleContext)ruleContext.getChild(0);
- break;
- case yqlplusParser.RULE_view: {
- // view and projection expansion now has to be done by the
- // execution engine
- // since views/projections, in order to be useful, have to
- // support being used from outside the same program
- ViewContext viewContext = (ViewContext) ruleContext;
- Location loc = toLocation(scope, viewContext);
- scope.getRoot().defineView(loc, viewContext.ID().getText());
- stmts.add(OperatorNode.create(loc, StatementOperator.DEFINE_VIEW, viewContext.ID().getText(), convertQuery(viewContext.source_statement(), scope.getRoot())));
- break;
+ // ^(STATEMENT_QUERY source_statement paged_clause? output_spec?)
+ StatementContext statementContext = (StatementContext) ruleContext;
+ Source_statementContext source_statement = statementContext.output_statement().source_statement();
+ OperatorNode<SequenceOperator> query;
+ if (source_statement.getChildCount() == 1) {
+ query = convertQuery( source_statement.query_statement().getChild(0), scope);
+ } else {
+ query = convertQuery(source_statement, scope);
}
- case yqlplusParser.RULE_statement: {
- // ^(STATEMENT_QUERY source_statement paged_clause?
- // output_spec?)
- StatementContext statementContext = (StatementContext) ruleContext;
- switch (getParseTreeIndex(ruleContext.getChild(0))) {
- case yqlplusParser.RULE_selectvar_statement: {
- // ^(STATEMENT_SELECTVAR ident source_statement)
- Selectvar_statementContext selectVarContext = (Selectvar_statementContext) ruleContext.getChild(0);
- String variable = selectVarContext.ident().getText();
- OperatorNode<SequenceOperator> query = convertQuery(selectVarContext.source_statement(), scope);
- Location location = toLocation(scope, selectVarContext.ident());
- scope.defineVariable(location, variable);
- stmts.add(OperatorNode.create(location, StatementOperator.EXECUTE, query, variable));
- break;
+ String variable = "result" + (++output);
+ boolean isCountVariable = false;
+ ParseTree outputStatement = node.getChild(0);
+ Location location = toLocation(scope, outputStatement);
+ for (int i = 1; i < outputStatement.getChildCount(); ++i) {
+ ParseTree child = outputStatement.getChild(i);
+ if ( getParseTreeIndex(child) != yqlplusParser.RULE_output_spec)
+ throw new ProgramCompileException( "Unknown statement attribute: " + child.toStringTree());
+
+ Output_specContext outputSpecContext = (Output_specContext) child;
+ variable = outputSpecContext.ident().getText();
+ if (outputSpecContext.COUNT() != null) {
+ isCountVariable = true;
}
- case yqlplusParser.RULE_next_statement: {
- // NEXT^ literalString OUTPUT! AS! ident
- Next_statementContext nextStateContext = (Next_statementContext) ruleContext.getChild(0);
- String continuationValue = StringUnescaper.unquote(nextStateContext.literalString().getText());
- String variable = nextStateContext.ident().getText();
- Location location = toLocation(scope, node);
- OperatorNode<SequenceOperator> next = OperatorNode.create(location, SequenceOperator.NEXT, continuationValue);
- stmts.add(OperatorNode.create(location, StatementOperator.EXECUTE, next, variable));
- stmts.add(OperatorNode.create(location, StatementOperator.OUTPUT, variable));
- scope.defineVariable(location, variable);
- break;
- }
- case yqlplusParser.RULE_output_statement:
- Source_statementContext source_statement = statementContext.output_statement().source_statement();
- OperatorNode<SequenceOperator> query;
- if (source_statement.getChildCount() == 1) {
- query = convertQuery( source_statement.query_statement().getChild(0), scope);
- } else {
- query = convertQuery(source_statement, scope);
- }
- String variable = "result" + (++output);
- boolean isCountVariable = false;
- OperatorNode<ExpressionOperator> pageSize = null;
- ParseTree outputStatement = node.getChild(0);
- Location location = toLocation(scope, outputStatement);
- for (int i = 1; i < outputStatement.getChildCount(); ++i) {
- ParseTree child = outputStatement.getChild(i);
- switch (getParseTreeIndex(child)) {
- case yqlplusParser.RULE_paged_clause:
- Paged_clauseContext pagedContext = (Paged_clauseContext) child;
- pageSize = convertExpr(pagedContext.fixed_or_parameter(), scope);
- break;
- case yqlplusParser.RULE_output_spec:
- Output_specContext outputSpecContext = (Output_specContext) child;
- variable = outputSpecContext.ident().getText();
- if (outputSpecContext.COUNT() != null) {
- isCountVariable = true;
- }
- break;
- default:
- throw new ProgramCompileException( "Unknown statement attribute: " + child.toStringTree());
- }
- }
- scope.defineVariable(location, variable);
- if (pageSize != null) {
- query = OperatorNode.create(SequenceOperator.PAGE, query, pageSize);
- }
- stmts.add(OperatorNode.create(location, StatementOperator.EXECUTE, query, variable));
- stmts.add(OperatorNode.create(location, isCountVariable ? StatementOperator.COUNT:StatementOperator.OUTPUT, variable));
- }
- break;
- }
- default:
- throw new ProgramCompileException("Unknown program element: " + node.getText());
}
+ scope.defineVariable(location, variable);
+ stmts.add(OperatorNode.create(location, StatementOperator.EXECUTE, query, variable));
+ stmts.add(OperatorNode.create(location, isCountVariable ? StatementOperator.COUNT:StatementOperator.OUTPUT, variable));
}
- // traverse the tree, find all of the namespaced calls not covered by
- // imports so we can
- // define "implicit" import statements for them (to make engine
- // implementation easier)
return OperatorNode.create(StatementOperator.PROGRAM, stmts);
}
@@ -982,19 +611,19 @@ final class ProgramParser {
private ProjectionBuilder readProjection(List<Field_defContext> fieldDefs, Scope scope) {
if (null == fieldDefs)
- throw new ProgramCompileException("Null fieldDefs");
+ throw new ProgramCompileException("Null fieldDefs");
ProjectionBuilder proj = new ProjectionBuilder();
for (Field_defContext rulenode : fieldDefs) {
// FIELD
- // expression alias_def?
- OperatorNode<ExpressionOperator> expr = convertExpr((ExpressionContext)rulenode.getChild(0), scope);
+ // expression alias_def?
+ OperatorNode<ExpressionOperator> expr = convertExpr(rulenode.getChild(0), scope);
- String aliasName = null;
- if (rulenode.getChildCount() > 1) {
- // ^(ALIAS ID)
- aliasName = rulenode.alias_def().ID().getText();
- }
- proj.addField(aliasName, expr);
+ String aliasName = null;
+ if (rulenode.getChildCount() > 1) {
+ // ^(ALIAS ID)
+ aliasName = rulenode.alias_def().ID().getText();
+ }
+ proj.addField(aliasName, expr);
// no grammar for the other rule types at this time
}
return proj;
@@ -1009,359 +638,345 @@ final class ProgramParser {
}
public OperatorNode<ExpressionOperator> convertExpr(ParseTree parseTree, Scope scope) {
- switch (getParseTreeIndex(parseTree)) {
- case yqlplusParser.RULE_vespa_grouping: {
- ParseTree firstChild = parseTree.getChild(0);
- if (getParseTreeIndex(firstChild) == yqlplusParser.RULE_annotation) {
- ParseTree secondChild = parseTree.getChild(1);
- OperatorNode<ExpressionOperator> annotation = convertExpr(((AnnotationContext) firstChild)
- .constantMapExpression(), scope);
- OperatorNode<ExpressionOperator> expr = OperatorNode.create(toLocation(scope, secondChild),
- ExpressionOperator.VESPA_GROUPING, secondChild.getText());
- List<String> names = (List<String>) annotation.getArgument(0);
- List<OperatorNode<ExpressionOperator>> annotates = (List<OperatorNode<ExpressionOperator>>) annotation
- .getArgument(1);
- for (int i = 0; i < names.size(); ++i) {
- expr.putAnnotation(names.get(i), readConstantExpression(annotates.get(i)));
- }
- return expr;
- } else {
- return OperatorNode.create(toLocation(scope, firstChild), ExpressionOperator.VESPA_GROUPING,
- firstChild.getText());
- }
- }
- case yqlplusParser.RULE_nullOperator:
- return OperatorNode.create(ExpressionOperator.NULL);
- case yqlplusParser.RULE_argument:
- return convertExpr(parseTree.getChild(0), scope);
- case yqlplusParser.RULE_fixed_or_parameter: {
- ParseTree firstChild = parseTree.getChild(0);
- if (getParseTreeIndex(firstChild) == yqlplusParser.INT) {
- return OperatorNode.create(toLocation(scope, firstChild), ExpressionOperator.LITERAL, Integer.valueOf(firstChild.getText()));
- } else {
- return convertExpr(firstChild, scope);
- }
- }
- case yqlplusParser.RULE_constantMapExpression: {
- List<ConstantPropertyNameAndValueContext> propertyList = ((ConstantMapExpressionContext) parseTree).constantPropertyNameAndValue();
- List<String> names = Lists.newArrayListWithExpectedSize(propertyList.size());
- List<OperatorNode<ExpressionOperator>> exprs = Lists.newArrayListWithExpectedSize(propertyList.size());
- for (ConstantPropertyNameAndValueContext child : propertyList) {
- // : propertyName ':' expression[$expression::namespace] ->
- // ^(PROPERTY propertyName expression)
- names.add(StringUnescaper.unquote(child.getChild(0).getText()));
- exprs.add(convertExpr(child.getChild(2), scope));
+ switch (getParseTreeIndex(parseTree)) {
+ case yqlplusParser.RULE_vespa_grouping: {
+ ParseTree firstChild = parseTree.getChild(0);
+ if (getParseTreeIndex(firstChild) == yqlplusParser.RULE_annotation) {
+ ParseTree secondChild = parseTree.getChild(1);
+ OperatorNode<ExpressionOperator> annotation = convertExpr(((AnnotationContext) firstChild)
+ .constantMapExpression(), scope);
+ OperatorNode<ExpressionOperator> expr = OperatorNode.create(toLocation(scope, secondChild),
+ ExpressionOperator.VESPA_GROUPING, secondChild.getText());
+ List<String> names = annotation.getArgument(0);
+ List<OperatorNode<ExpressionOperator>> annotates = annotation.getArgument(1);
+ for (int i = 0; i < names.size(); ++i) {
+ expr.putAnnotation(names.get(i), readConstantExpression(annotates.get(i)));
+ }
+ return expr;
+ } else {
+ return OperatorNode.create(toLocation(scope, firstChild), ExpressionOperator.VESPA_GROUPING,
+ firstChild.getText());
+ }
}
- return OperatorNode.create(toLocation(scope, parseTree),ExpressionOperator.MAP, names, exprs);
- }
- case yqlplusParser.RULE_mapExpression: {
- List<PropertyNameAndValueContext> propertyList = ((MapExpressionContext)parseTree).propertyNameAndValue();
- List<String> names = Lists.newArrayListWithExpectedSize(propertyList.size());
- List<OperatorNode<ExpressionOperator>> exprs = Lists.newArrayListWithCapacity(propertyList.size());
- for (PropertyNameAndValueContext child : propertyList) {
- // : propertyName ':' expression[$expression::namespace] ->
- // ^(PROPERTY propertyName expression)
- names.add(StringUnescaper.unquote(child.getChild(0).getText()));
- exprs.add(convertExpr(child.getChild(2), scope));
- }
- return OperatorNode.create(toLocation(scope, parseTree),ExpressionOperator.MAP, names, exprs);
- }
- case yqlplusParser.RULE_constantArray: {
- List<ConstantExpressionContext> expressionList = ((ConstantArrayContext)parseTree).constantExpression();
- List<OperatorNode<ExpressionOperator>> values = Lists.newArrayListWithExpectedSize(expressionList.size());
- for (ConstantExpressionContext expr : expressionList) {
- values.add(convertExpr(expr, scope));
+ case yqlplusParser.RULE_nullOperator:
+ return OperatorNode.create(ExpressionOperator.NULL);
+ case yqlplusParser.RULE_argument:
+ return convertExpr(parseTree.getChild(0), scope);
+ case yqlplusParser.RULE_fixed_or_parameter: {
+ ParseTree firstChild = parseTree.getChild(0);
+ if (getParseTreeIndex(firstChild) == yqlplusParser.INT) {
+ return OperatorNode.create(toLocation(scope, firstChild), ExpressionOperator.LITERAL, Integer.valueOf(firstChild.getText()));
+ } else {
+ return convertExpr(firstChild, scope);
+ }
+ }
+ case yqlplusParser.RULE_constantMapExpression: {
+ List<ConstantPropertyNameAndValueContext> propertyList = ((ConstantMapExpressionContext) parseTree).constantPropertyNameAndValue();
+ List<String> names = Lists.newArrayListWithExpectedSize(propertyList.size());
+ List<OperatorNode<ExpressionOperator>> exprs = Lists.newArrayListWithExpectedSize(propertyList.size());
+ for (ConstantPropertyNameAndValueContext child : propertyList) {
+ // : propertyName ':' expression[$expression::namespace] ->
+ // ^(PROPERTY propertyName expression)
+ names.add(StringUnescaper.unquote(child.getChild(0).getText()));
+ exprs.add(convertExpr(child.getChild(2), scope));
+ }
+ return OperatorNode.create(toLocation(scope, parseTree),ExpressionOperator.MAP, names, exprs);
+ }
+ case yqlplusParser.RULE_mapExpression: {
+ List<PropertyNameAndValueContext> propertyList = ((MapExpressionContext)parseTree).propertyNameAndValue();
+ List<String> names = Lists.newArrayListWithExpectedSize(propertyList.size());
+ List<OperatorNode<ExpressionOperator>> exprs = Lists.newArrayListWithCapacity(propertyList.size());
+ for (PropertyNameAndValueContext child : propertyList) {
+ // : propertyName ':' expression[$expression::namespace] ->
+ // ^(PROPERTY propertyName expression)
+ names.add(StringUnescaper.unquote(child.getChild(0).getText()));
+ exprs.add(convertExpr(child.getChild(2), scope));
+ }
+ return OperatorNode.create(toLocation(scope, parseTree),ExpressionOperator.MAP, names, exprs);
+ }
+ case yqlplusParser.RULE_constantArray: {
+ List<ConstantExpressionContext> expressionList = ((ConstantArrayContext)parseTree).constantExpression();
+ List<OperatorNode<ExpressionOperator>> values = Lists.newArrayListWithExpectedSize(expressionList.size());
+ for (ConstantExpressionContext expr : expressionList) {
+ values.add(convertExpr(expr, scope));
+ }
+ return OperatorNode.create(toLocation(scope, expressionList.isEmpty()? parseTree:expressionList.get(0)), ExpressionOperator.ARRAY, values);
+ }
+ case yqlplusParser.RULE_arrayLiteral: {
+ List<ExpressionContext> expressionList = ((ArrayLiteralContext) parseTree).expression();
+ List<OperatorNode<ExpressionOperator>> values = Lists.newArrayListWithExpectedSize(expressionList.size());
+ for (ExpressionContext expr : expressionList) {
+ values.add(convertExpr(expr, scope));
+ }
+ return OperatorNode.create(toLocation(scope, expressionList.isEmpty()? parseTree:expressionList.get(0)), ExpressionOperator.ARRAY, values);
+ }
+ // dereferencedExpression: primaryExpression(indexref[in_select]| propertyref)*
+ case yqlplusParser.RULE_dereferencedExpression: {
+ DereferencedExpressionContext dereferencedExpression = (DereferencedExpressionContext) parseTree;
+ Iterator<ParseTree> it = dereferencedExpression.children.iterator();
+ OperatorNode<ExpressionOperator> result = convertExpr(it.next(), scope);
+ while (it.hasNext()) {
+ ParseTree defTree = it.next();
+ if (getParseTreeIndex(defTree) == yqlplusParser.RULE_propertyref) {
+ // DOT nm=ID
+ result = OperatorNode.create(toLocation(scope, parseTree), ExpressionOperator.PROPREF, result, defTree.getChild(1).getText());
+ } else {
+ // indexref
+ result = OperatorNode.create(toLocation(scope, parseTree), ExpressionOperator.INDEX, result, convertExpr(defTree.getChild(1), scope));
+ }
+ }
+ return result;
}
- return OperatorNode.create(toLocation(scope, expressionList.isEmpty()? parseTree:expressionList.get(0)), ExpressionOperator.ARRAY, values);
+ case yqlplusParser.RULE_primaryExpression: {
+ // ^(CALL namespaced_name arguments)
+ ParseTree firstChild = parseTree.getChild(0);
+ switch (getParseTreeIndex(firstChild)) {
+ case yqlplusParser.RULE_fieldref: {
+ return convertExpr(firstChild, scope);
+ }
+ case yqlplusParser.RULE_callExpresion: {
+ List<ArgumentContext> args = ((ArgumentsContext) firstChild.getChild(1)).argument();
+ List<OperatorNode<ExpressionOperator>> arguments = Lists.newArrayListWithExpectedSize(args.size());
+ for (ArgumentContext argContext : args) {
+ arguments.add(convertExpr(argContext.expression(),scope));
+ }
+ return OperatorNode.create(toLocation(scope, parseTree), ExpressionOperator.CALL, scope.resolvePath(readName((Namespaced_nameContext) firstChild.getChild(0))), arguments);
+ }
+ case yqlplusParser.RULE_parameter:
+ // external variable reference
+ return OperatorNode.create(toLocation(scope, firstChild), ExpressionOperator.VARREF, firstChild.getChild(1).getText());
+ case yqlplusParser.RULE_scalar_literal:
+ case yqlplusParser.RULE_arrayLiteral:
+ case yqlplusParser.RULE_mapExpression:
+ return convertExpr(firstChild, scope);
+ case yqlplusParser.LPAREN:
+ return convertExpr(parseTree.getChild(1), scope);
+ }
+ break;
+ }
+ case yqlplusParser.RULE_parameter: {
+ // external variable reference
+ ParserRuleContext parameterContext = (ParserRuleContext) parseTree;
+ IdentContext identContext = parameterContext.getRuleContext(IdentContext.class, 0);
+ return OperatorNode.create(toLocation(scope, identContext), ExpressionOperator.VARREF, identContext.getText());
+ }
+ case yqlplusParser.RULE_annotateExpression: {
+ //annotation logicalORExpression
+ AnnotationContext annotateExpressionContext = ((AnnotateExpressionContext)parseTree).annotation();
+ OperatorNode<ExpressionOperator> annotation = convertExpr(annotateExpressionContext.constantMapExpression(), scope);
+ OperatorNode<ExpressionOperator> expr = convertExpr(parseTree.getChild(1), scope);
+ List<String> names = annotation.getArgument(0);
+ List<OperatorNode<ExpressionOperator>> annotates = annotation.getArgument(1);
+ for (int i = 0; i < names.size(); ++i) {
+ expr.putAnnotation(names.get(i), readConstantExpression(annotates.get(i)));
+ }
+ return expr;
+ }
+ case yqlplusParser.RULE_expression: {
+ return convertExpr(parseTree.getChild(0), scope);
+ }
+ case yqlplusParser.RULE_logicalANDExpression:
+ LogicalANDExpressionContext andExpressionContext = (LogicalANDExpressionContext) parseTree;
+ return readConjOp(ExpressionOperator.AND, andExpressionContext.equalityExpression(), scope);
+ case yqlplusParser.RULE_logicalORExpression: {
+ int childCount = parseTree.getChildCount();
+ LogicalORExpressionContext logicalORExpressionContext = (LogicalORExpressionContext) parseTree;
+ if (childCount > 1) {
+ return readConjOrOp(ExpressionOperator.OR, logicalORExpressionContext, scope);
+ } else {
+ List<EqualityExpressionContext> equalityExpressionList = ((LogicalANDExpressionContext) parseTree.getChild(0)).equalityExpression();
+ if (equalityExpressionList.size() > 1) {
+ return readConjOp(ExpressionOperator.AND, equalityExpressionList, scope);
+ } else {
+ return convertExpr(equalityExpressionList.get(0), scope);
+ }
+ }
+ }
+ case yqlplusParser.RULE_equalityExpression: {
+ EqualityExpressionContext equalityExpression = (EqualityExpressionContext) parseTree;
+ RelationalExpressionContext relationalExpressionContext = equalityExpression.relationalExpression(0);
+ OperatorNode<ExpressionOperator> expr = convertExpr(relationalExpressionContext, scope);
+ InNotInTargetContext inNotInTarget = equalityExpression.inNotInTarget();
+ int childCount = equalityExpression.getChildCount();
+ if (childCount == 1) {
+ return expr;
+ }
+ if (inNotInTarget != null) {
+ Literal_listContext literalListContext = inNotInTarget.literal_list();
+ boolean isIN = equalityExpression.IN() != null;
+ if (literalListContext == null) {
+ Select_statementContext selectStatementContext = inNotInTarget.select_statement();
+ OperatorNode<SequenceOperator> query = convertQuery(selectStatementContext, scope);
+ return OperatorNode.create(expr.getLocation(),isIN ? ExpressionOperator.IN_QUERY: ExpressionOperator.NOT_IN_QUERY, expr, query);
+ } else {
+ // we need to identify the type of the target; if it's a
+ // scalar we need to wrap it in a CREATE_ARRAY
+ // if it's already a CREATE ARRAY then it's fine, otherwise
+ // we need to know the variable type
+ // return readBinOp(node.getType() == yqlplusParser.IN ?
+ // ExpressionOperator.IN : ExpressionOperator.NOT_IN, node,
+ // scope);
+ return readBinOp(isIN ? ExpressionOperator.IN: ExpressionOperator.NOT_IN, equalityExpression.getChild(0), literalListContext, scope);
+ }
+
+ } else {
+ ParseTree firstChild = equalityExpression.getChild(1);
+ if (equalityExpression.getChildCount() == 2) {
+ switch (getParseTreeIndex(firstChild)) {
+ case yqlplusParser.IS_NULL:
+ return readUnOp(ExpressionOperator.IS_NULL, relationalExpressionContext, scope);
+ case yqlplusParser.IS_NOT_NULL:
+ return readUnOp(ExpressionOperator.IS_NOT_NULL, relationalExpressionContext, scope);
+ }
+ } else {
+ switch (getParseTreeIndex(firstChild.getChild(0))) {
+ case yqlplusParser.EQ:
+ return readBinOp(ExpressionOperator.EQ, equalityExpression.getChild(0), equalityExpression.getChild(2), scope);
+ case yqlplusParser.NEQ:
+ return readBinOp(ExpressionOperator.NEQ, equalityExpression.getChild(0), equalityExpression.getChild(2), scope);
+ case yqlplusParser.LIKE:
+ return readBinOp(ExpressionOperator.LIKE, equalityExpression.getChild(0), equalityExpression.getChild(2), scope);
+ case yqlplusParser.NOTLIKE:
+ return readBinOp(ExpressionOperator.NOT_LIKE, equalityExpression.getChild(0), equalityExpression.getChild(2), scope);
+ case yqlplusParser.MATCHES:
+ return readBinOp(ExpressionOperator.MATCHES, equalityExpression.getChild(0), equalityExpression.getChild(2), scope);
+ case yqlplusParser.NOTMATCHES:
+ return readBinOp(ExpressionOperator.NOT_MATCHES, equalityExpression.getChild(0), equalityExpression.getChild(2), scope);
+ case yqlplusParser.CONTAINS:
+ return readBinOp(ExpressionOperator.CONTAINS, equalityExpression.getChild(0), equalityExpression.getChild(2), scope);
+ }
+ }
+
+ }
+ break;
+ }
+ case yqlplusParser.RULE_relationalExpression: {
+ RelationalExpressionContext relationalExpressionContext = (RelationalExpressionContext) parseTree;
+ RelationalOpContext opContext = relationalExpressionContext.relationalOp();
+ if (opContext != null) {
+ switch (getParseTreeIndex(relationalExpressionContext.relationalOp().getChild(0))) {
+ case yqlplusParser.LT:
+ return readBinOp(ExpressionOperator.LT, parseTree, scope);
+ case yqlplusParser.LTEQ:
+ return readBinOp(ExpressionOperator.LTEQ, parseTree, scope);
+ case yqlplusParser.GT:
+ return readBinOp(ExpressionOperator.GT, parseTree, scope);
+ case yqlplusParser.GTEQ:
+ return readBinOp(ExpressionOperator.GTEQ, parseTree, scope);
+ }
+ } else {
+ return convertExpr(relationalExpressionContext.additiveExpression(0), scope);
+ }
+ }
+ break;
+ case yqlplusParser.RULE_additiveExpression:
+ case yqlplusParser.RULE_multiplicativeExpression: {
+ if (parseTree.getChildCount() > 1) {
+ String opStr = parseTree.getChild(1).getText();
+ switch (opStr) {
+ case "+":
+ return readBinOp(ExpressionOperator.ADD, parseTree, scope);
+ case "-":
+ return readBinOp(ExpressionOperator.SUB, parseTree, scope);
+ case "/":
+ return readBinOp(ExpressionOperator.DIV, parseTree, scope);
+ case "*":
+ return readBinOp(ExpressionOperator.MULT, parseTree, scope);
+ case "%":
+ return readBinOp(ExpressionOperator.MOD, parseTree, scope);
+ default:
+ if (parseTree.getChild(0) instanceof UnaryExpressionContext) {
+ return convertExpr(parseTree.getChild(0), scope);
+ } else {
+ throw new ProgramCompileException(toLocation(scope, parseTree), "Unknown expression type: " + parseTree.toStringTree());
+ }
+ }
+ } else {
+ if (parseTree.getChild(0) instanceof UnaryExpressionContext) {
+ return convertExpr(parseTree.getChild(0), scope);
+ } else if (parseTree.getChild(0) instanceof MultiplicativeExpressionContext) {
+ return convertExpr(parseTree.getChild(0), scope);
+ } else {
+ throw new ProgramCompileException(toLocation(scope, parseTree), "Unknown expression type: " + parseTree.getText());
+ }
+ }
+ }
+ case yqlplusParser.RULE_unaryExpression: {
+ if (1 == parseTree.getChildCount()) {
+ return convertExpr(parseTree.getChild(0), scope);
+ } else if (2 == parseTree.getChildCount()) {
+ if ("-".equals(parseTree.getChild(0).getText())) {
+ return readUnOp(ExpressionOperator.NEGATE, parseTree, scope);
+ } else if ("!".equals(parseTree.getChild(0).getText())) {
+ return readUnOp(ExpressionOperator.NOT, parseTree, scope);
+ }
+ throw new ProgramCompileException(toLocation(scope, parseTree),"Unknown unary operator " + parseTree.getText());
+ } else {
+ throw new ProgramCompileException(toLocation(scope, parseTree),"Unknown child count " + parseTree.getChildCount() + " of " + parseTree.getText());
+ }
+ }
+ case yqlplusParser.RULE_fieldref: {
+ // all in-scope data sources should be defined in scope
+ // the 'first' field in a namespaced reference must be:
+ // - a field name if (and only if) there is exactly one data source
+ // in scope OR
+ // - an alias name, which will be followed by a field name
+ // ^(FIELDREF<FieldReference>[$expression::namespace]
+ // namespaced_name)
+ List<String> path = readName((Namespaced_nameContext) parseTree.getChild(0));
+ Location loc = toLocation(scope, parseTree.getChild(0));
+ String alias = path.get(0);
+ OperatorNode<ExpressionOperator> result = null;
+ int start = 0;
+ if (scope.isCursor(alias)) {
+ if (path.size() > 1) {
+ result = OperatorNode.create(loc, ExpressionOperator.READ_FIELD, alias, path.get(1));
+ start = 2;
+ } else {
+ result = OperatorNode.create(loc, ExpressionOperator.READ_RECORD, alias);
+ start = 1;
+ }
+ } else if (scope.isBound(alias)) {
+ return OperatorNode.create(loc, ExpressionOperator.READ_MODULE, scope.getBinding(alias).toPathWith(path.subList(1, path.size())));
+ } else if (scope.getCursors().size() == 1) {
+ alias = scope.getCursors().iterator().next();
+ result = OperatorNode.create(loc, ExpressionOperator.READ_FIELD, alias, path.get(0));
+ start = 1;
+ } else {
+ // ah ha, we can't end up with a 'loose' UDF call because it
+ // won't be a module or known alias
+ // so we need not support implicit imports for constants used in
+ // UDFs
+ throw new ProgramCompileException(loc, "Unknown field or alias '%s'", alias);
+ }
+ for (int idx = start; idx < path.size(); ++idx) {
+ result = OperatorNode.create(loc, ExpressionOperator.PROPREF, result, path.get(idx));
+ }
+ return result;
+ }
+ case yqlplusParser.RULE_scalar_literal:
+ return OperatorNode.create(toLocation(scope, parseTree), ExpressionOperator.LITERAL, convertLiteral((Scalar_literalContext) parseTree));
+ case yqlplusParser.RULE_constantExpression:
+ return convertExpr(parseTree.getChild(0), scope);
+ case yqlplusParser.RULE_literal_list:
+ if (getParseTreeIndex(parseTree.getChild(1)) == yqlplusParser.RULE_array_parameter) {
+ return convertExpr(parseTree.getChild(1), scope);
+ } else {
+ List<Literal_elementContext> elements = ((Literal_listContext) parseTree).literal_element();
+ ParseTree firldElement = elements.get(0).getChild(0);
+ if (elements.size() == 1 && scope.getParser().isArrayParameter(firldElement)) {
+ return convertExpr(firldElement, scope);
+ } else {
+ List<OperatorNode<ExpressionOperator>> values = Lists.newArrayListWithExpectedSize(elements.size());
+ for (Literal_elementContext child : elements) {
+ values.add(convertExpr(child.getChild(0), scope));
+ }
+ return OperatorNode.create(toLocation(scope, elements.get(0)),ExpressionOperator.ARRAY, values);
+ }
+ }
}
- case yqlplusParser.RULE_arrayLiteral: {
- List<ExpressionContext> expressionList = ((ArrayLiteralContext) parseTree).expression();
- List<OperatorNode<ExpressionOperator>> values = Lists.newArrayListWithExpectedSize(expressionList.size());
- for (ExpressionContext expr : expressionList) {
- values.add(convertExpr(expr, scope));
- }
- return OperatorNode.create(toLocation(scope, expressionList.isEmpty()? parseTree:expressionList.get(0)), ExpressionOperator.ARRAY, values);
- }
- //dereferencedExpression: primaryExpression(indexref[in_select]| propertyref)*
- case yqlplusParser.RULE_dereferencedExpression: {
- DereferencedExpressionContext dereferencedExpression = (DereferencedExpressionContext) parseTree;
- Iterator<ParseTree> it = dereferencedExpression.children.iterator();
- OperatorNode<ExpressionOperator> result = convertExpr(it.next(), scope);
- while (it.hasNext()) {
- ParseTree defTree = it.next();
- if (getParseTreeIndex(defTree) == yqlplusParser.RULE_propertyref) {
- //DOT nm=ID
- result = OperatorNode.create(toLocation(scope, parseTree), ExpressionOperator.PROPREF, result, defTree.getChild(1).getText());
- } else {
- //indexref
- result = OperatorNode.create(toLocation(scope, parseTree), ExpressionOperator.INDEX, result, convertExpr(defTree.getChild(1), scope));
- }
- }
- return result;
- }
- case yqlplusParser.RULE_primaryExpression: {
- // ^(CALL namespaced_name arguments)
- ParseTree firstChild = parseTree.getChild(0);
- switch (getParseTreeIndex(firstChild)) {
- case yqlplusParser.RULE_fieldref: {
- return convertExpr(firstChild, scope);
- }
- case yqlplusParser.RULE_callExpresion: {
- List<ArgumentContext> args = ((ArgumentsContext) firstChild.getChild(1)).argument();
- List<OperatorNode<ExpressionOperator>> arguments = Lists.newArrayListWithExpectedSize(args.size());
- for (ArgumentContext argContext : args) {
- arguments.add(convertExpr(argContext.expression(),scope));
- }
- return OperatorNode.create(toLocation(scope, parseTree), ExpressionOperator.CALL, scope.resolvePath(readName((Namespaced_nameContext) firstChild.getChild(0))), arguments);
- }
- // TODO add processing this is not implemented in V3
- // case yqlplusParser.APPLY:
-
- case yqlplusParser.RULE_parameter:
- // external variable reference
- return OperatorNode.create(toLocation(scope, firstChild), ExpressionOperator.VARREF, firstChild.getChild(1).getText());
- case yqlplusParser.RULE_scalar_literal:
- case yqlplusParser.RULE_arrayLiteral:
- case yqlplusParser.RULE_mapExpression:
- return convertExpr(firstChild, scope);
- case yqlplusParser.LPAREN:
- return convertExpr(parseTree.getChild(1), scope);
- }
- break;
- }
-
- // TODO: Temporarily disable CAST - think through how types are named
- // case yqlplusParser.CAST: {
- //
- // return new Cast()
- // }
- // return new CastExpression(payload);
- case yqlplusParser.RULE_parameter: {
- // external variable reference
- ParserRuleContext parameterContext = (ParserRuleContext) parseTree;
- IdentContext identContext = parameterContext.getRuleContext(IdentContext.class, 0);
- return OperatorNode.create(toLocation(scope, identContext), ExpressionOperator.VARREF, identContext.getText());
- }
- case yqlplusParser.RULE_annotateExpression: {
- //annotation logicalORExpression
- AnnotationContext annotateExpressionContext = ((AnnotateExpressionContext)parseTree).annotation();
- OperatorNode<ExpressionOperator> annotation = convertExpr(annotateExpressionContext.constantMapExpression(), scope);
- OperatorNode<ExpressionOperator> expr = convertExpr(parseTree.getChild(1), scope);
- List<String> names = (List<String>) annotation.getArgument(0);
- List<OperatorNode<ExpressionOperator>> annotates = (List<OperatorNode<ExpressionOperator>>) annotation.getArgument(1);
- for (int i = 0; i < names.size(); ++i) {
- expr.putAnnotation(names.get(i), readConstantExpression(annotates.get(i)));
- }
- return expr;
- }
- case yqlplusParser.RULE_expression: {
- return convertExpr(parseTree.getChild(0), scope);
- }
- case yqlplusParser.RULE_logicalANDExpression:
- LogicalANDExpressionContext andExpressionContext = (LogicalANDExpressionContext) parseTree;
- return readConjOp(ExpressionOperator.AND, andExpressionContext.equalityExpression(), scope);
- case yqlplusParser.RULE_logicalORExpression: {
- int childCount = parseTree.getChildCount();
- LogicalORExpressionContext logicalORExpressionContext = (LogicalORExpressionContext) parseTree;
- if (childCount > 1) {
- return readConjOrOp(ExpressionOperator.OR, logicalORExpressionContext, scope);
- } else {
- List<EqualityExpressionContext> equalityExpressionList = ((LogicalANDExpressionContext) parseTree.getChild(0)).equalityExpression();
- if (equalityExpressionList.size() > 1) {
- return readConjOp(ExpressionOperator.AND, equalityExpressionList, scope);
- } else {
- return convertExpr(equalityExpressionList.get(0), scope);
- }
- }
- }
- case yqlplusParser.RULE_equalityExpression: {
- EqualityExpressionContext equalityExpression = (EqualityExpressionContext) parseTree;
- RelationalExpressionContext relationalExpressionContext = equalityExpression.relationalExpression(0);
- OperatorNode<ExpressionOperator> expr = convertExpr(relationalExpressionContext, scope);
- InNotInTargetContext inNotInTarget = equalityExpression.inNotInTarget();
- int childCount = equalityExpression.getChildCount();
- if (childCount == 1) {
- return expr;
- }
- if (inNotInTarget != null) {
- Literal_listContext literalListContext = inNotInTarget.literal_list();
- boolean isIN = equalityExpression.IN() != null;
- if (literalListContext == null) {
- Select_statementContext selectStatementContext = inNotInTarget.select_statement();
- OperatorNode<SequenceOperator> query = convertQuery(selectStatementContext, scope);
- return OperatorNode.create(expr.getLocation(),isIN ? ExpressionOperator.IN_QUERY: ExpressionOperator.NOT_IN_QUERY, expr, query);
- } else {
- // we need to identify the type of the target; if it's a
- // scalar we need to wrap it in a CREATE_ARRAY
- // if it's already a CREATE ARRAY then it's fine, otherwise
- // we need to know the variable type
- // return readBinOp(node.getType() == yqlplusParser.IN ?
- // ExpressionOperator.IN : ExpressionOperator.NOT_IN, node,
- // scope);
- return readBinOp(isIN ? ExpressionOperator.IN: ExpressionOperator.NOT_IN, equalityExpression.getChild(0), literalListContext, scope);
- }
-
- } else {
- ParseTree firstChild = equalityExpression.getChild(1);
- if (equalityExpression.getChildCount() == 2) {
- switch (getParseTreeIndex(firstChild)) {
- case yqlplusParser.IS_NULL:
- return readUnOp(ExpressionOperator.IS_NULL, relationalExpressionContext, scope);
- case yqlplusParser.IS_NOT_NULL:
- return readUnOp(ExpressionOperator.IS_NOT_NULL, relationalExpressionContext, scope);
- }
- } else {
- switch (getParseTreeIndex(firstChild.getChild(0))) {
- case yqlplusParser.EQ:
- return readBinOp(ExpressionOperator.EQ, equalityExpression.getChild(0), equalityExpression.getChild(2), scope);
- case yqlplusParser.NEQ:
- return readBinOp(ExpressionOperator.NEQ, equalityExpression.getChild(0), equalityExpression.getChild(2), scope);
- case yqlplusParser.LIKE:
- return readBinOp(ExpressionOperator.LIKE, equalityExpression.getChild(0), equalityExpression.getChild(2), scope);
- case yqlplusParser.NOTLIKE:
- return readBinOp(ExpressionOperator.NOT_LIKE, equalityExpression.getChild(0), equalityExpression.getChild(2), scope);
- case yqlplusParser.MATCHES:
- return readBinOp(ExpressionOperator.MATCHES, equalityExpression.getChild(0), equalityExpression.getChild(2), scope);
- case yqlplusParser.NOTMATCHES:
- return readBinOp(ExpressionOperator.NOT_MATCHES, equalityExpression.getChild(0), equalityExpression.getChild(2), scope);
- case yqlplusParser.CONTAINS:
- return readBinOp(ExpressionOperator.CONTAINS, equalityExpression.getChild(0), equalityExpression.getChild(2), scope);
- }
- }
-
- }
- break;
- }
- case yqlplusParser.RULE_relationalExpression: {
- RelationalExpressionContext relationalExpressionContext = (RelationalExpressionContext) parseTree;
- RelationalOpContext opContext = relationalExpressionContext.relationalOp();
- if (opContext != null) {
- switch (getParseTreeIndex(relationalExpressionContext.relationalOp().getChild(0))) {
- case yqlplusParser.LT:
- return readBinOp(ExpressionOperator.LT, parseTree, scope);
- case yqlplusParser.LTEQ:
- return readBinOp(ExpressionOperator.LTEQ, parseTree, scope);
- case yqlplusParser.GT:
- return readBinOp(ExpressionOperator.GT, parseTree, scope);
- case yqlplusParser.GTEQ:
- return readBinOp(ExpressionOperator.GTEQ, parseTree, scope);
- }
- } else {
- return convertExpr(relationalExpressionContext.additiveExpression(0), scope);
- }
- }
- break;
- case yqlplusParser.RULE_additiveExpression:
- case yqlplusParser.RULE_multiplicativeExpression: {
- if (parseTree.getChildCount() > 1) {
- String opStr = parseTree.getChild(1).getText();
- switch (opStr) {
- case "+":
- return readBinOp(ExpressionOperator.ADD, parseTree, scope);
- case "-":
- return readBinOp(ExpressionOperator.SUB, parseTree, scope);
- case "/":
- return readBinOp(ExpressionOperator.DIV, parseTree, scope);
- case "*":
- return readBinOp(ExpressionOperator.MULT, parseTree, scope);
- case "%":
- return readBinOp(ExpressionOperator.MOD, parseTree, scope);
- default:
- if (parseTree.getChild(0) instanceof UnaryExpressionContext) {
- return convertExpr(parseTree.getChild(0), scope);
- } else {
- throw new ProgramCompileException(toLocation(scope, parseTree), "Unknown expression type: " + parseTree.toStringTree());
- }
- }
- } else {
- if (parseTree.getChild(0) instanceof UnaryExpressionContext) {
- return convertExpr(parseTree.getChild(0), scope);
- } else if (parseTree.getChild(0) instanceof MultiplicativeExpressionContext) {
- return convertExpr(parseTree.getChild(0), scope);
- } else {
- throw new ProgramCompileException(toLocation(scope, parseTree), "Unknown expression type: " + parseTree.getText());
- }
- }
- }
- case yqlplusParser.RULE_unaryExpression: {
- if (1 == parseTree.getChildCount()) {
- return convertExpr(parseTree.getChild(0), scope);
- } else if (2 == parseTree.getChildCount()) {
- if ("-".equals(parseTree.getChild(0).getText())) {
- return readUnOp(ExpressionOperator.NEGATE, parseTree, scope);
- } else if ("!".equals(parseTree.getChild(0).getText())) {
- return readUnOp(ExpressionOperator.NOT, parseTree, scope);
- }
- throw new ProgramCompileException(toLocation(scope, parseTree),"Unknown unary operator " + parseTree.getText());
- } else {
- throw new ProgramCompileException(toLocation(scope, parseTree),"Unknown child count " + parseTree.getChildCount() + " of " + parseTree.getText());
- }
- }
- case yqlplusParser.RULE_fieldref:
- case yqlplusParser.RULE_joinDereferencedExpression: {
- // all in-scope data sources should be defined in scope
- // the 'first' field in a namespaced reference must be:
- // - a field name if (and only if) there is exactly one data source
- // in scope OR
- // - an alias name, which will be followed by a field name
- // ^(FIELDREF<FieldReference>[$expression::namespace]
- // namespaced_name)
- List<String> path = readName((Namespaced_nameContext) parseTree.getChild(0));
- Location loc = toLocation(scope, parseTree.getChild(0));
- String alias = path.get(0);
- OperatorNode<ExpressionOperator> result = null;
- int start = 0;
- if (scope.isCursor(alias)) {
- if (path.size() > 1) {
- result = OperatorNode.create(loc, ExpressionOperator.READ_FIELD, alias, path.get(1));
- start = 2;
- } else {
- result = OperatorNode.create(loc, ExpressionOperator.READ_RECORD, alias);
- start = 1;
- }
- } else if (scope.isBound(alias)) {
- return OperatorNode.create(loc, ExpressionOperator.READ_MODULE, scope.getBinding(alias).toPathWith(path.subList(1, path.size())));
- } else if (scope.getCursors().size() == 1) {
- alias = scope.getCursors().iterator().next();
- result = OperatorNode.create(loc, ExpressionOperator.READ_FIELD, alias, path.get(0));
- start = 1;
- } else {
- // ah ha, we can't end up with a 'loose' UDF call because it
- // won't be a module or known alias
- // so we need not support implicit imports for constants used in
- // UDFs
- throw new ProgramCompileException(loc, "Unknown field or alias '%s'", alias);
- }
- for (int idx = start; idx < path.size(); ++idx) {
- result = OperatorNode.create(loc, ExpressionOperator.PROPREF, result, path.get(idx));
- }
- return result;
- }
- case yqlplusParser.RULE_scalar_literal:
- return OperatorNode.create(toLocation(scope, parseTree), ExpressionOperator.LITERAL, convertLiteral((Scalar_literalContext) parseTree));
- case yqlplusParser.RULE_insert_values:
- return readValues((Insert_valuesContext) parseTree, scope);
- case yqlplusParser.RULE_constantExpression:
- return convertExpr(parseTree.getChild(0), scope);
- case yqlplusParser.RULE_literal_list:
- if (getParseTreeIndex(parseTree.getChild(1)) == yqlplusParser.RULE_array_parameter) {
- return convertExpr(parseTree.getChild(1), scope);
- } else {
- List<Literal_elementContext> elements = ((Literal_listContext) parseTree).literal_element();
- ParseTree firldElement = elements.get(0).getChild(0);
- if (elements.size() == 1 && scope.getParser().isArrayParameter(firldElement)) {
- return convertExpr(firldElement, scope);
- } else {
- List<OperatorNode<ExpressionOperator>> values = Lists.newArrayListWithExpectedSize(elements.size());
- for (Literal_elementContext child : elements) {
- values.add(convertExpr(child.getChild(0), scope));
- }
- return OperatorNode.create(toLocation(scope, elements.get(0)),ExpressionOperator.ARRAY, values);
- }
- }
- }
- throw new ProgramCompileException(toLocation(scope, parseTree),
- "Unknown expression type: " + parseTree.getText());
+ throw new ProgramCompileException(toLocation(scope, parseTree),
+ "Unknown expression type: " + parseTree.getText());
}
public Object convertLiteral(Scalar_literalContext literal) {
@@ -1375,9 +990,9 @@ final class ProgramParser {
case yqlplusParser.STRING:
return StringUnescaper.unquote(text);
case yqlplusParser.TRUE:
- return Boolean.valueOf(true);
+ return true;
case yqlplusParser.FALSE:
- return Boolean.valueOf(false);
+ return false;
case yqlplusParser.LONG_INT:
return Long.parseLong(text.substring(0, text.length()-1));
default:
@@ -1391,21 +1006,24 @@ final class ProgramParser {
return node.getArgument(0);
case MAP: {
ImmutableMap.Builder<String, Object> map = ImmutableMap.builder();
- List<String> names = (List<String>) node.getArgument(0);
- List<OperatorNode<ExpressionOperator>> exprs = (List<OperatorNode<ExpressionOperator>>) node.getArgument(1);
+ List<String> names = node.getArgument(0);
+ List<OperatorNode<ExpressionOperator>> exprs = node.getArgument(1);
for (int i = 0; i < names.size(); ++i) {
map.put(names.get(i), readConstantExpression(exprs.get(i)));
}
return map.build();
}
case ARRAY: {
- List<OperatorNode<ExpressionOperator>> exprs = (List<OperatorNode<ExpressionOperator>>) node.getArgument(0);
+ List<OperatorNode<ExpressionOperator>> exprs = node.getArgument(0);
ImmutableList.Builder<Object> lst = ImmutableList.builder();
for (OperatorNode<ExpressionOperator> expr : exprs) {
lst.add(readConstantExpression(expr));
}
return lst.build();
}
+ case VARREF: {
+ return node; // must be dereferenced in YqlParser when we have userQuery
+ }
default:
throw new ProgramCompileException(node.getLocation(), "Internal error: Unknown constant expression type: " + node.getOperator());
}
@@ -1460,77 +1078,7 @@ final class ProgramParser {
}
}
- private OperatorNode<ExpressionOperator> readValues(Field_names_specContext nameDefs, Field_values_specContext values, Scope scope) {
- List<Field_defContext> fieldDefs = nameDefs.field_def();
- List<ExpressionContext> valueDefs = values.expression();
- assert fieldDefs.size() == valueDefs.size();
- List<String> fieldNames;
- List<OperatorNode<ExpressionOperator>> fieldValues;
- int numPairs = fieldDefs.size();
- fieldNames = Lists.newArrayListWithExpectedSize(numPairs);
- fieldValues = Lists.newArrayListWithExpectedSize(numPairs);
- for (int i = 0; i < numPairs; i++) {
- fieldNames.add((String) convertExpr(fieldDefs.get(i).expression(), scope).getArgument(1));
- fieldValues.add(convertExpr(valueDefs.get(i), scope));
- }
- return OperatorNode.create(ExpressionOperator.MAP, fieldNames, fieldValues);
- }
-
- private OperatorNode<ExpressionOperator> readValues(ParserRuleContext node, Scope scope) {
- List<String> fieldNames;
- List<OperatorNode<ExpressionOperator>> fieldValues;
- if (node.getRuleIndex() == yqlplusParser.RULE_field_def) {
- Field_defContext fieldDefContext = (Field_defContext)node;
- //TODO double check
- fieldNames = Lists.newArrayListWithExpectedSize(node.getChildCount());
- fieldValues = Lists.newArrayListWithExpectedSize(node.getChildCount());
- for (int i = 0; i < node.getChildCount(); i++) {
- fieldNames.add((String) convertExpr(node.getChild(i).getChild(0).getChild(0), scope).getArgument(1));
- fieldValues.add(convertExpr(node.getChild(i).getChild(0).getChild(1), scope));
- }
- } else {
- assert node.getChildCount() % 2 == 0;
- int numPairs = node.getChildCount() / 2;
- fieldNames = Lists.newArrayListWithExpectedSize(numPairs);
- fieldValues = Lists.newArrayListWithExpectedSize(numPairs);
- for (int i = 0; i < numPairs; i++) {
- fieldNames.add((String) convertExpr(node.getChild(i).getChild(0), scope).getArgument(1));
- fieldValues.add(convertExpr(node.getChild(numPairs + i), scope));
- }
- }
- return OperatorNode.create(ExpressionOperator.MAP, fieldNames, fieldValues);
- }
-
- /*
- * Converts node list
- *
- * a_name, b_name, c_name, a_value_1, b_value_1, c_value_1, a_value_2, b_value_2, c_value2, a_value_3, b_value_3, c_value_3
- *
- * into corresponding constant sequence:
- *
- * [ { a_name : a_value_1, b_name : b_value_1, c_name : c_value_1 }, ... ]
- *
- */
- private OperatorNode<SequenceOperator> readBatchValues(Field_names_specContext nameDefs, List<Field_values_group_specContext> valueGroups, Scope scope) {
- List<Field_defContext> nameContexts = nameDefs.field_def();
- List<String> fieldNames = Lists.newArrayList();
- for (Field_defContext nameContext:nameContexts) {
- fieldNames.add((String) convertExpr(nameContext.getChild(0), scope).getArgument(1));
- }
- List<OperatorNode> records = Lists.newArrayList();
- for (Field_values_group_specContext valueGorup:valueGroups) {
- List<ExpressionContext> expressionList = valueGorup.expression();
- List<OperatorNode<ExpressionOperator>> fieldValues = Lists.newArrayListWithExpectedSize(expressionList.size());
- for (ExpressionContext expressionContext:expressionList) {
- fieldValues.add(convertExpr(expressionContext, scope));
- }
- records.add(OperatorNode.create(ExpressionOperator.MAP, fieldNames, fieldValues));
- }
- // Return constant sequence of records with the given name/values
- return OperatorNode.create(SequenceOperator.EVALUATE, OperatorNode.create(ExpressionOperator.ARRAY, records));
- }
-
- /*
+ /**
* Scans the given node for READ_FIELD expressions.
*
* TODO: Search recursively and consider additional operators
@@ -1555,4 +1103,5 @@ final class ProgramParser {
}
return readFieldList;
}
+
}
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 6eef1252998..9e6701572dc 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
@@ -16,6 +16,7 @@ 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.GEO_LOCATION;
import static com.yahoo.search.yql.YqlParser.HIT_LIMIT;
import static com.yahoo.search.yql.YqlParser.IMPLICIT_TRANSFORMS;
import static com.yahoo.search.yql.YqlParser.LABEL;
@@ -72,6 +73,7 @@ import com.yahoo.prelude.query.ExactStringItem;
import com.yahoo.prelude.query.IndexedItem;
import com.yahoo.prelude.query.IntItem;
import com.yahoo.prelude.query.Item;
+import com.yahoo.prelude.query.GeoLocationItem;
import com.yahoo.prelude.query.MarkerWordItem;
import com.yahoo.prelude.query.NearItem;
import com.yahoo.prelude.query.NearestNeighborItem;
@@ -140,7 +142,7 @@ public class VespaSerializer {
escape(((WordItem) current).getIndexedString(), destination).append('"');
} else {
throw new IllegalArgumentException("Serializing of " + current.getClass().getSimpleName()
- + " in segment AND expressions not implemented, please report this as a bug.");
+ + " in segment AND expressions not implemented, please report this as a bug.");
}
}
}
@@ -641,7 +643,7 @@ public class VespaSerializer {
WordAlternativesSerializer.serialize(destination, (WordAlternativesItem) current, false);
} else {
throw new IllegalArgumentException("Serializing of " + current.getClass().getSimpleName() +
- " in phrases not implemented, please report this as a bug.");
+ " in phrases not implemented, please report this as a bug.");
}
}
destination.append(')');
@@ -689,6 +691,26 @@ public class VespaSerializer {
}
+ private static class GeoLocationSerializer extends Serializer<GeoLocationItem> {
+ @Override
+ void onExit(StringBuilder destination, GeoLocationItem item) { }
+ @Override
+ boolean serialize(StringBuilder destination, GeoLocationItem item) {
+ String annotations = leafAnnotations(item);
+ if (annotations.length() > 0) {
+ destination.append("([{").append(annotations).append("}]");
+ }
+ destination.append(GEO_LOCATION).append('(');
+ destination.append(item.getIndexName()).append(", ");
+ var loc = item.getLocation();
+ destination.append(loc.degNS()).append(", ");
+ destination.append(loc.degEW()).append(", ");
+ destination.append('"').append(loc.degRadius()).append(" deg").append('"');
+ destination.append(')');
+ return false;
+ }
+ }
+
private static class NearestNeighborSerializer extends Serializer<NearestNeighborItem> {
@Override
@@ -701,7 +723,24 @@ public class VespaSerializer {
destination.append(leafAnnotations(item));
comma(destination, initLen);
int targetNumHits = item.getTargetNumHits();
- destination.append("\"targetNumHits\": ").append(targetNumHits);
+ annotationKey(destination, YqlParser.TARGET_NUM_HITS).append(targetNumHits);
+ double distanceThreshold = item.getDistanceThreshold();
+ if (distanceThreshold < Double.POSITIVE_INFINITY) {
+ comma(destination, initLen);
+ String key = YqlParser.DISTANCE_THRESHOLD;
+ annotationKey(destination, key).append(distanceThreshold);
+ }
+ int explore = item.getHnswExploreAdditionalHits();
+ if (explore != 0) {
+ comma(destination, initLen);
+ String key = YqlParser.HNSW_EXPLORE_ADDITIONAL_HITS;
+ annotationKey(destination, key).append(explore);
+ }
+ boolean allow_approx = item.getAllowApproximate();
+ if (! allow_approx) {
+ comma(destination, initLen);
+ annotationKey(destination, "approximate").append(allow_approx);
+ }
destination.append("}]");
destination.append(NEAREST_NEIGHBOR).append('(');
destination.append(item.getIndexName()).append(", ");
@@ -911,12 +950,13 @@ public class VespaSerializer {
}
+ @SuppressWarnings("deprecation")
private static class WeakAndSerializer extends Serializer<WeakAndItem> {
@Override
void onExit(StringBuilder destination, WeakAndItem item) {
destination.append(')');
- if (needsAnnotationBlock((WeakAndItem) item)) {
+ if (needsAnnotationBlock(item)) {
destination.append(')');
}
}
@@ -1125,7 +1165,7 @@ public class VespaSerializer {
Serializer doIt = dispatch.get(item.getClass());
if (doIt == null) {
- throw new IllegalArgumentException(item.getClass() + " not supported for YQL+ marshalling.");
+ throw new IllegalArgumentException(item.getClass() + " not supported for YQL marshalling.");
}
if (state.peekFirst() != null && state.peekFirst().subItems > 0) {
@@ -1152,6 +1192,7 @@ public class VespaSerializer {
dispatchBuilder.put(EquivItem.class, new EquivSerializer());
dispatchBuilder.put(ExactStringItem.class, new WordSerializer());
dispatchBuilder.put(IntItem.class, new NumberSerializer());
+ dispatchBuilder.put(GeoLocationItem.class, new GeoLocationSerializer());
dispatchBuilder.put(BoolItem.class, new BoolSerializer());
dispatchBuilder.put(MarkerWordItem.class, new WordSerializer()); // gotcha
dispatchBuilder.put(NearItem.class, new NearSerializer());
@@ -1347,6 +1388,11 @@ public class VespaSerializer {
}
}
+ private static StringBuilder annotationKey(StringBuilder annotation, String val) {
+ annotation.append("\"").append(val).append("\": ");
+ return annotation;
+ }
+
private static void comma(StringBuilder annotation, int initLen) {
if (annotation.length() > initLen) {
annotation.append(", ");
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 8d013e501e8..9eaea47ea1b 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
@@ -19,11 +19,14 @@ import com.google.common.annotations.Beta;
import com.google.common.base.Preconditions;
import com.yahoo.collections.LazyMap;
import com.yahoo.collections.LazySet;
+import com.yahoo.geo.DistanceParser;
+import com.yahoo.geo.ParsedDegree;
import com.yahoo.language.Language;
import com.yahoo.language.detect.Detector;
import com.yahoo.language.process.Normalizer;
import com.yahoo.language.process.Segmenter;
import com.yahoo.prelude.IndexFacts;
+import com.yahoo.prelude.Location;
import com.yahoo.prelude.query.AndItem;
import com.yahoo.prelude.query.AndSegmentItem;
import com.yahoo.prelude.query.BoolItem;
@@ -34,6 +37,7 @@ import com.yahoo.prelude.query.ExactStringItem;
import com.yahoo.prelude.query.IntItem;
import com.yahoo.prelude.query.Item;
import com.yahoo.prelude.query.Limit;
+import com.yahoo.prelude.query.GeoLocationItem;
import com.yahoo.prelude.query.NearItem;
import com.yahoo.prelude.query.NearestNeighborItem;
import com.yahoo.prelude.query.NotItem;
@@ -63,6 +67,7 @@ import com.yahoo.prelude.query.WeakAndItem;
import com.yahoo.prelude.query.WeightedSetItem;
import com.yahoo.prelude.query.WordAlternativesItem;
import com.yahoo.prelude.query.WordItem;
+import com.yahoo.processing.IllegalInputException;
import com.yahoo.search.Query;
import com.yahoo.search.grouping.Continuation;
import com.yahoo.search.grouping.request.GroupingOperation;
@@ -94,8 +99,8 @@ import com.yahoo.search.query.parser.ParserFactory;
*/
public class YqlParser implements Parser {
- private static final String DESCENDING_HITS_ORDER = "descending";
- private static final String ASCENDING_HITS_ORDER = "ascending";
+ public static final String DESCENDING_HITS_ORDER = "descending";
+ public static final String ASCENDING_HITS_ORDER = "ascending";
private enum SegmentWhen {
NEVER, POSSIBLY, ALWAYS;
@@ -107,12 +112,12 @@ public class YqlParser implements Parser {
private static final Integer DEFAULT_HITS = 10;
private static final Integer DEFAULT_OFFSET = 0;
- private static final Integer DEFAULT_TARGET_NUM_HITS = 10;
+ public static final Integer DEFAULT_TARGET_NUM_HITS = 10;
private static final String ACCENT_DROP_DESCRIPTION = "setting for whether to remove accents if field implies it";
- private static final String ANNOTATIONS = "annotations";
+ public static final String ANNOTATIONS = "annotations";
private static final String FILTER_DESCRIPTION = "term filter setting";
private static final String IMPLICIT_TRANSFORMS_DESCRIPTION = "setting for whether built-in query transformers should touch the term";
- private static final String NFKC = "nfkc";
+ public static final String NFKC = "nfkc";
private static final String NORMALIZE_CASE_DESCRIPTION = "setting for whether to do case normalization if field implies it";
private static final String ORIGIN_DESCRIPTION = "string origin for a term";
private static final String RANKED_DESCRIPTION = "setting for whether to use term for ranking";
@@ -121,7 +126,7 @@ public class YqlParser implements Parser {
private static final String USER_INPUT_ALLOW_EMPTY = "allowEmpty";
private static final String USER_INPUT_DEFAULT_INDEX = "defaultIndex";
private static final String USER_INPUT_GRAMMAR = "grammar";
- private static final String USER_INPUT_LANGUAGE = "language";
+ public static final String USER_INPUT_LANGUAGE = "language";
private static final String USER_INPUT_RAW = "raw";
private static final String USER_INPUT_SEGMENT = "segment";
private static final String USER_INPUT = "userInput";
@@ -134,52 +139,57 @@ public class YqlParser implements Parser {
public static final String SORTING_LOCALE = "locale";
public static final String SORTING_STRENGTH = "strength";
- static final String ACCENT_DROP = "accentDrop";
- static final String ALTERNATIVES = "alternatives";
- static final String AND_SEGMENTING = "andSegmenting";
- static final String BOUNDS = "bounds";
- static final String BOUNDS_LEFT_OPEN = "leftOpen";
- static final String BOUNDS_OPEN = "open";
- static final String BOUNDS_RIGHT_OPEN = "rightOpen";
- static final String CONNECTION_ID = "id";
- static final String CONNECTION_WEIGHT = "weight";
- static final String CONNECTIVITY = "connectivity";
- static final String DISTANCE = "distance";
- static final String DOT_PRODUCT = "dotProduct";
- static final String EQUIV = "equiv";
- static final String FILTER = "filter";
- static final String HIT_LIMIT = "hitLimit";
- static final String IMPLICIT_TRANSFORMS = "implicitTransforms";
- static final String LABEL = "label";
- static final String NEAR = "near";
- static final String NEAREST_NEIGHBOR = "nearestNeighbor";
- static final String NORMALIZE_CASE = "normalizeCase";
- static final String ONEAR = "onear";
- static final String ORIGIN_LENGTH = "length";
- static final String ORIGIN_OFFSET = "offset";
- static final String ORIGIN = "origin";
- static final String ORIGIN_ORIGINAL = "original";
- static final String PHRASE = "phrase";
- static final String PREDICATE = "predicate";
- static final String PREFIX = "prefix";
- static final String RANGE = "range";
- static final String RANKED = "ranked";
- static final String RANK = "rank";
- static final String SAME_ELEMENT = "sameElement";
- static final String SCORE_THRESHOLD = "scoreThreshold";
- static final String SIGNIFICANCE = "significance";
- static final String STEM = "stem";
- static final String SUBSTRING = "substring";
- static final String SUFFIX = "suffix";
- static final String TARGET_NUM_HITS = "targetNumHits";
- static final String THRESHOLD_BOOST_FACTOR = "thresholdBoostFactor";
- static final String UNIQUE_ID = "id";
- static final String USE_POSITION_DATA = "usePositionData";
- static final String WAND = "wand";
- static final String WEAK_AND = "weakAnd";
- static final String WEIGHTED_SET = "weightedSet";
- static final String WEIGHT = "weight";
- static final String URI = "uri";
+ public static final String ACCENT_DROP = "accentDrop";
+ public static final String ALTERNATIVES = "alternatives";
+ public static final String AND_SEGMENTING = "andSegmenting";
+ public static final String APPROXIMATE = "approximate";
+ public static final String BOUNDS = "bounds";
+ public static final String BOUNDS_LEFT_OPEN = "leftOpen";
+ public static final String BOUNDS_OPEN = "open";
+ public static final String BOUNDS_RIGHT_OPEN = "rightOpen";
+ public static final String CONNECTION_ID = "id";
+ public static final String CONNECTION_WEIGHT = "weight";
+ public static final String CONNECTIVITY = "connectivity";
+ public static final String DISTANCE = "distance";
+ public static final String DOT_PRODUCT = "dotProduct";
+ public static final String EQUIV = "equiv";
+ public static final String FILTER = "filter";
+ public static final String GEO_LOCATION = "geoLocation";
+ public static final String HIT_LIMIT = "hitLimit";
+ public static final String DISTANCE_THRESHOLD = "distanceThreshold";
+ public static final String HNSW_EXPLORE_ADDITIONAL_HITS = "hnsw.exploreAdditionalHits";
+ public static final String IMPLICIT_TRANSFORMS = "implicitTransforms";
+ public static final String LABEL = "label";
+ public static final String NEAR = "near";
+ public static final String NEAREST_NEIGHBOR = "nearestNeighbor";
+ public static final String NORMALIZE_CASE = "normalizeCase";
+ public static final String ONEAR = "onear";
+ public static final String ORIGIN_LENGTH = "length";
+ public static final String ORIGIN_OFFSET = "offset";
+ public static final String ORIGIN = "origin";
+ public static final String ORIGIN_ORIGINAL = "original";
+ public static final String PHRASE = "phrase";
+ public static final String PREDICATE = "predicate";
+ public static final String PREFIX = "prefix";
+ public static final String RANGE = "range";
+ public static final String RANKED = "ranked";
+ public static final String RANK = "rank";
+ public static final String SAME_ELEMENT = "sameElement";
+ public static final String SCORE_THRESHOLD = "scoreThreshold";
+ public static final String SIGNIFICANCE = "significance";
+ public static final String STEM = "stem";
+ public static final String SUBSTRING = "substring";
+ public static final String SUFFIX = "suffix";
+ public static final String TARGET_HITS = "targetHits";
+ public static final String TARGET_NUM_HITS = "targetNumHits";
+ public static final String THRESHOLD_BOOST_FACTOR = "thresholdBoostFactor";
+ public static final String UNIQUE_ID = "id";
+ public static final String USE_POSITION_DATA = "usePositionData";
+ public static final String WAND = "wand";
+ public static final String WEAK_AND = "weakAnd";
+ public static final String WEIGHTED_SET = "weightedSet";
+ public static final String WEIGHT = "weight";
+ public static final String URI = "uri";
private final IndexFacts indexFacts;
private final List<ConnectedItem> connectedItems = new ArrayList<>();
@@ -310,10 +320,10 @@ public class YqlParser implements Parser {
private void connectItems() {
for (ConnectedItem entry : connectedItems) {
TaggableItem to = identifiedItems.get(entry.toId);
- Preconditions.checkNotNull(to,
- "Item '%s' was specified to connect to item with ID %s, which does not "
- + "exist in the query.", entry.fromItem,
- entry.toId);
+ if (to == null)
+ throw new IllegalArgumentException("Item '" + entry.fromItem +
+ "' was specified to connect to item with ID " + entry.toId +
+ ", which does not exist in the query.");
entry.fromItem.setConnectivity((Item) to, entry.weight);
}
}
@@ -369,6 +379,8 @@ public class YqlParser implements Parser {
return buildWeightedSet(ast);
case DOT_PRODUCT:
return buildDotProduct(ast);
+ case GEO_LOCATION:
+ return buildGeoLocation(ast);
case NEAREST_NEIGHBOR:
return buildNearestNeighbor(ast);
case PREDICATE:
@@ -410,20 +422,60 @@ public class YqlParser implements Parser {
return fillWeightedSet(ast, args.get(1), new DotProductItem(getIndex(args.get(0))));
}
+ private Item buildGeoLocation(OperatorNode<ExpressionOperator> ast) {
+ List<OperatorNode<ExpressionOperator>> args = ast.getArgument(1);
+ Preconditions.checkArgument(args.size() == 4, "Expected 4 arguments, got %s.", args.size());
+ String field = fetchFieldRead(args.get(0));
+ var coord_1 = ParsedDegree.fromString(fetchFieldRead(args.get(1)), true, false);
+ var coord_2 = ParsedDegree.fromString(fetchFieldRead(args.get(2)), false, true);
+ double radius = DistanceParser.parse(fetchFieldRead(args.get(3)));
+ var loc = new Location();
+ if (coord_1.isLatitude && coord_2.isLongitude) {
+ loc.setGeoCircle(coord_1.degrees, coord_2.degrees, radius);
+ } else if (coord_2.isLatitude && coord_1.isLongitude) {
+ loc.setGeoCircle(coord_2.degrees, coord_1.degrees, radius);
+ } else {
+ throw new IllegalArgumentException("Invalid geoLocation coordinates '"+coord_1+"' and '"+coord_2+"'");
+ }
+ var item = new GeoLocationItem(loc, field);
+ String label = getAnnotation(ast, LABEL, String.class, null, "item label");
+ if (label != null) {
+ item.setLabel(label);
+ }
+ return item;
+ }
+
private Item buildNearestNeighbor(OperatorNode<ExpressionOperator> ast) {
List<OperatorNode<ExpressionOperator>> args = ast.getArgument(1);
Preconditions.checkArgument(args.size() == 2, "Expected 2 arguments, got %s.", args.size());
String field = fetchFieldRead(args.get(0));
String property = fetchFieldRead(args.get(1));
NearestNeighborItem item = new NearestNeighborItem(field, property);
- Integer targetNumHits = getAnnotation(ast, TARGET_NUM_HITS,
+ Integer targetNumHits = getAnnotation(ast, TARGET_HITS,
+ Integer.class, null, "desired minimum hits to produce");
+ if (targetNumHits == null) {
+ targetNumHits = getAnnotation(ast, TARGET_NUM_HITS,
Integer.class, null, "desired minimum hits to produce");
+ }
if (targetNumHits != null) {
item.setTargetNumHits(targetNumHits);
}
+ Double distanceThreshold = getAnnotation(ast, DISTANCE_THRESHOLD,
+ Double.class, null, "maximum distance allowed from query point");
+ if (distanceThreshold != null) {
+ item.setDistanceThreshold(distanceThreshold);
+ }
+ Integer hnswExploreAdditionalHits = getAnnotation(ast, HNSW_EXPLORE_ADDITIONAL_HITS,
+ Integer.class, null, "number of extra hits to explore for HNSW algorithm");
+ if (hnswExploreAdditionalHits != null) {
+ item.setHnswExploreAdditionalHits(hnswExploreAdditionalHits);
+ }
+ Boolean allowApproximate = getAnnotation(ast, APPROXIMATE,
+ Boolean.class, Boolean.TRUE, "allow approximate nearest neighbor search");
+ item.setAllowApproximate(allowApproximate);
String label = getAnnotation(ast, LABEL, String.class, null, "item label");
if (label != null) {
- item.setLabel(label);
+ item.setLabel(label);
}
return item;
}
@@ -494,9 +546,13 @@ public class YqlParser implements Parser {
List<OperatorNode<ExpressionOperator>> args = ast.getArgument(1);
Preconditions.checkArgument(args.size() == 2, "Expected 2 arguments, got %s.", args.size());
- 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"));
+ Integer targetNumHits = getAnnotation(ast, TARGET_HITS,
+ Integer.class, null, "desired number of hits to accumulate in wand");
+ if (targetNumHits == null) {
+ targetNumHits = getAnnotation(ast, TARGET_NUM_HITS,
+ Integer.class, DEFAULT_TARGET_NUM_HITS, "desired number of hits to accumulate in wand");
+ }
+ WandItem out = new WandItem(getIndex(args.get(0)), targetNumHits);
Double scoreThreshold = getAnnotation(ast, SCORE_THRESHOLD, Double.class, null,
"min score for hit inclusion");
if (scoreThreshold != null) {
@@ -733,7 +789,7 @@ public class YqlParser implements Parser {
try {
ast = new ProgramParser().parse("query", currentlyParsing.getQuery());
} catch (Exception e) {
- throw new IllegalArgumentException(e);
+ throw new IllegalInputException(e);
}
assertHasOperator(ast, StatementOperator.PROGRAM);
Preconditions.checkArgument(ast.getArguments().length == 1,
@@ -760,10 +816,11 @@ public class YqlParser implements Parser {
OperatorNode<ExpressionOperator> groupingAst = ast.<List<OperatorNode<ExpressionOperator>>> getArgument(2).get(0);
GroupingOperation groupingOperation = GroupingOperation.fromString(groupingAst.<String> getArgument(0));
VespaGroupingStep groupingStep = new VespaGroupingStep(groupingOperation);
- List<String> continuations = getAnnotation(groupingAst, "continuations", List.class,
+ List<Object> continuations = getAnnotation(groupingAst, "continuations", List.class,
Collections.emptyList(), "grouping continuations");
- for (String continuation : continuations) {
- groupingStep.continuations().add(Continuation.fromString(continuation));
+
+ for (Object continuation : continuations) {
+ groupingStep.continuations().add(Continuation.fromString(dereference(continuation)));
}
groupingSteps.add(groupingStep);
ast = ast.getArgument(0);
@@ -772,6 +829,18 @@ public class YqlParser implements Parser {
return ast;
}
+ private String dereference(Object constantOrVarref) {
+ if (constantOrVarref instanceof OperatorNode) {
+ OperatorNode<?> varref = (OperatorNode<?>)constantOrVarref;
+ Preconditions.checkState(userQuery != null,
+ "properties must be available when trying to fetch user input");
+ return userQuery.properties().getString(varref.getArgument(0, String.class));
+ }
+ else {
+ return constantOrVarref.toString();
+ }
+ }
+
private OperatorNode<?> fetchSorting(OperatorNode<?> ast) {
if (ast.getOperator() != SequenceOperator.SORT) return ast;
@@ -883,6 +952,8 @@ public class YqlParser implements Parser {
private static String fetchFieldRead(OperatorNode<ExpressionOperator> ast) {
switch (ast.getOperator()) {
+ case LITERAL:
+ return ast.getArgument(0).toString();
case READ_FIELD:
return ast.getArgument(1);
case PROPREF:
@@ -953,40 +1024,52 @@ public class YqlParser implements Parser {
private String fetchConditionIndex(OperatorNode<ExpressionOperator> ast) {
OperatorNode<ExpressionOperator> lhs = ast.getArgument(0);
OperatorNode<ExpressionOperator> rhs = ast.getArgument(1);
- if (lhs.getOperator() == ExpressionOperator.LITERAL || lhs.getOperator() == ExpressionOperator.NEGATE) {
+ if (isNumber(lhs))
return getIndex(rhs);
- }
- if (rhs.getOperator() == ExpressionOperator.LITERAL || rhs.getOperator() == ExpressionOperator.NEGATE) {
+ else if (isNumber(rhs))
return getIndex(lhs);
- }
- throw new IllegalArgumentException("Expected LITERAL and READ_FIELD/PROPREF, got " + lhs.getOperator() +
- " and " + rhs.getOperator() + ".");
+ else
+ throw new IllegalArgumentException("Expected LITERAL/VARREF and READ_FIELD/PROPREF, got " + lhs.getOperator() +
+ " and " + rhs.getOperator() + ".");
+ }
+
+ private boolean isNumber(OperatorNode<ExpressionOperator> ast) {
+ return ast.getOperator() == ExpressionOperator.NEGATE ||
+ ast.getOperator() == ExpressionOperator.LITERAL || ast.getOperator() == ExpressionOperator.VARREF;
}
- private static String getNumberAsString(OperatorNode<ExpressionOperator> ast) {
+ private String getNumberAsString(OperatorNode<ExpressionOperator> ast) {
String negative = "";
- OperatorNode<ExpressionOperator> currentAst = ast;
- if (currentAst.getOperator() == ExpressionOperator.NEGATE) {
+ if (ast.getOperator() == ExpressionOperator.NEGATE) {
negative = "-";
- currentAst = currentAst.getArgument(0);
+ ast = ast.getArgument(0);
+ }
+ switch (ast.getOperator()) {
+ case VARREF:
+ Preconditions.checkState(userQuery != null,
+ "properties must be available when trying to fetch user input");
+ return negative + userQuery.properties().getString(ast.getArgument(0, String.class));
+ case LITERAL:
+ return negative + ast.getArgument(0).toString();
+ default:
+ throw new IllegalArgumentException("Expected VARREF or LITERAL, got " + ast.getOperator());
}
- assertHasOperator(currentAst, ExpressionOperator.LITERAL);
- return negative + currentAst.getArgument(0).toString();
}
- private static String fetchConditionWord(OperatorNode<ExpressionOperator> ast) {
+ private String fetchConditionWord(OperatorNode<ExpressionOperator> ast) {
OperatorNode<ExpressionOperator> lhs = ast.getArgument(0);
OperatorNode<ExpressionOperator> rhs = ast.getArgument(1);
- if (lhs.getOperator() == ExpressionOperator.LITERAL || lhs.getOperator() == ExpressionOperator.NEGATE) {
+ if (isNumber(lhs)) {
assertFieldName(rhs);
return getNumberAsString(lhs);
}
- if (rhs.getOperator() == ExpressionOperator.LITERAL || rhs.getOperator() == ExpressionOperator.NEGATE) {
+ else if (isNumber(rhs)) {
assertFieldName(lhs);
return getNumberAsString(rhs);
}
- throw new IllegalArgumentException("Expected LITERAL/NEGATE and READ_FIELD/PROPREF, got "
- + lhs.getOperator() + " and " + rhs.getOperator() + ".");
+ else
+ throw new IllegalArgumentException("Expected LITERAL/NEGATE and READ_FIELD/PROPREF, got " +
+ lhs.getOperator() + " and " + rhs.getOperator() + ".");
}
private static boolean isIndexOnLeftHandSide(OperatorNode<ExpressionOperator> ast) {
@@ -1016,10 +1099,15 @@ public class YqlParser implements Parser {
return convertVarArgs(spec, 0, new OrItem());
}
+ @SuppressWarnings("deprecation")
private CompositeItem buildWeakAnd(OperatorNode<ExpressionOperator> spec) {
WeakAndItem weakAnd = new WeakAndItem();
- Integer targetNumHits = getAnnotation(spec, TARGET_NUM_HITS,
+ Integer targetNumHits = getAnnotation(spec, TARGET_HITS,
+ Integer.class, null, "desired minimum hits to produce");
+ if (targetNumHits == null) {
+ targetNumHits = getAnnotation(spec, TARGET_NUM_HITS,
Integer.class, null, "desired minimum hits to produce");
+ }
if (targetNumHits != null) {
weakAnd.setN(targetNumHits);
}
@@ -1202,7 +1290,7 @@ public class YqlParser implements Parser {
equiv.setIndexName(field);
for (OperatorNode<ExpressionOperator> arg : args) {
switch (arg.getOperator()) {
- case LITERAL:
+ case LITERAL: case VARREF:
equiv.addItem(instantiateWordItem(field, arg, equiv.getClass()));
break;
case CALL:
@@ -1211,7 +1299,7 @@ public class YqlParser implements Parser {
break;
default:
throw newUnexpectedArgumentException(arg.getOperator(),
- ExpressionOperator.CALL, ExpressionOperator.LITERAL);
+ ExpressionOperator.CALL, ExpressionOperator.LITERAL, ExpressionOperator.VARREF);
}
}
return leafStyleSettings(ast, equiv);
@@ -1272,7 +1360,8 @@ public class YqlParser implements Parser {
}
private Item instantiateWordItem(String field,
- OperatorNode<ExpressionOperator> ast, Class<?> parent,
+ OperatorNode<ExpressionOperator> ast,
+ Class<?> parent,
SegmentWhen segmentPolicy) {
String wordData = getStringContents(ast);
return instantiateWordItem(field, wordData, ast, parent, segmentPolicy, null, decideParsingLanguage(ast, wordData));
@@ -1288,7 +1377,8 @@ public class YqlParser implements Parser {
// which always expands first, but not using getIndex, which performs checks that doesn't always work
private Item instantiateWordItem(String field,
String rawWord,
- OperatorNode<ExpressionOperator> ast, Class<?> parent,
+ OperatorNode<ExpressionOperator> ast,
+ Class<?> parent,
SegmentWhen segmentPolicy,
Boolean exactMatch,
Language language) {
@@ -1682,7 +1772,15 @@ public class YqlParser implements Parser {
Object value = ast.getAnnotation(key);
for (Iterator<OperatorNode<?>> i = annotationStack.iterator(); value == null
&& considerParents && i.hasNext();) {
- value = i.next().getAnnotation(key);
+ OperatorNode node = i.next();
+ if (node.getOperator() == ExpressionOperator.VARREF) {
+ Preconditions.checkState(userQuery != null,
+ "properties must be available when trying to fetch user input");
+ value = userQuery.properties().getString(ast.getArgument(0, String.class));
+ }
+ else {
+ value = node.getAnnotation(key);
+ }
}
if (value == null) return defaultValue;
Preconditions.checkArgument(expectedClass.isInstance(value),
diff --git a/container-search/src/main/java/com/yahoo/text/interpretation/Annotations.java b/container-search/src/main/java/com/yahoo/text/interpretation/Annotations.java
index 5afe51d4415..f625bedca19 100644
--- a/container-search/src/main/java/com/yahoo/text/interpretation/Annotations.java
+++ b/container-search/src/main/java/com/yahoo/text/interpretation/Annotations.java
@@ -8,28 +8,26 @@ import java.util.Map;
/**
* An annotation is a description of a an area of text, with a given class. For example, an annotation for the
*
- * @author <a href="mailto:arnebef@yahoo-inc.com">Arne Bergene Fossaa</a>
+ * @author Arne Bergene Fossaa
*/
public class Annotations {
-
- private Span span;
+ private final Span span;
protected Map<String,Object> annotations;
-
/**
* Adds an annotation to the the the set of annotations.
*/
public void put(String key,Object o) {
- if(annotations == null) {
+ if (annotations == null) {
annotations = new HashMap<>();
}
annotations.put(key,o);
}
public Map<String,Object> getMap() {
- if(annotations == null) {
+ if (annotations == null) {
return Collections.emptyMap();
} else {
return annotations;
@@ -113,12 +111,11 @@ public class Annotations {
*/
public Boolean getBoolean(String key) {
Object o = getMap().get(key);
- if(o == null || !(o instanceof Boolean)) {
+ if ( ! (o instanceof Boolean)) {
return null;
} else {
- return (Boolean) o;
+ return (Boolean)o;
}
}
-
}
diff --git a/container-search/src/main/java/com/yahoo/text/interpretation/Interpretation.java b/container-search/src/main/java/com/yahoo/text/interpretation/Interpretation.java
index 9a9b8e81633..f662a0a4e7e 100644
--- a/container-search/src/main/java/com/yahoo/text/interpretation/Interpretation.java
+++ b/container-search/src/main/java/com/yahoo/text/interpretation/Interpretation.java
@@ -21,21 +21,19 @@ import java.util.Set;
* is not needed.
*
* @see Span
- * @author <a href="mailto:arnebef@yahoo-inc.com">Arne Bergene Fossaa</a>
+ * @author Arne Bergene Fossaa
*/
public class Interpretation {
- private Modification modification;
+ private final Modification modification;
private double probability;
- private Span rootSpan;
+ private final Span rootSpan;
public final static AnnotationClass INTERPRETATION_CLASS = new AnnotationClass("interpretation");
-
/**
* Creates a new interpretation and a new modification from the text,
* with the probability set to the default value(0.0).
-
*/
public Interpretation(String text) {
this(text,0.0);
@@ -48,7 +46,6 @@ public class Interpretation {
this(new Modification(text),probabilty);
}
-
/**
* Creates a new interpretation based on the modification, with the probability set to the default value(0.0).
*/
@@ -65,12 +62,10 @@ public class Interpretation {
setProbability(probability);
}
-
public Modification getModification() {
return modification;
}
-
/**
* The probability that this interpretation is correct.
* @return a value between 0.0 and 1.0 that gives the probability that this interpretation is correct
@@ -98,13 +93,12 @@ public class Interpretation {
/** Returns the root of the tree representation of the interpretation */
public Span root() { return rootSpan; }
-
// Wrapper methods for Span
/**
* Return the annotation with the given annotationclass (and create it if necessary).
- * @param annotationClass The class of the annotation
*
+ * @param annotationClass The class of the annotation
*/
public Annotations annotate(String annotationClass) {
return annotate(new AnnotationClass(annotationClass));
@@ -112,8 +106,8 @@ public class Interpretation {
/**
* Return the annotation with the given annotationclass (and create it if necessary).
- * @param annotationClass The class of the annotation
*
+ * @param annotationClass The class of the annotation
*/
public Annotations annotate(AnnotationClass annotationClass) {
return rootSpan.annotate(annotationClass);
@@ -124,6 +118,7 @@ public class Interpretation {
* exist, a new is created.
*
* A shortcut for annotate(annotationClass).put(key,value)
+ *
* @param annotationClass class of the annotation
* @param key key of the property to set on the annotation
* @param value value of the property to set on the annotation
@@ -137,6 +132,7 @@ public class Interpretation {
* exist, a new is created.
*
* A shortcut for annotate(annotationClass).put(key,value)
+ *
* @param annotationClass class of the annotation
* @param key key of the property to set on the annotation
* @param value value of the property to set on the annotation
@@ -147,6 +143,7 @@ public class Interpretation {
/**
* Returns the annotation with the given annotationClass (and create it if necessary).
+ *
* @param from start of the substring
* @param to end of the substring
* @param annotationClass class of the annotation
@@ -157,6 +154,7 @@ public class Interpretation {
/**
* Returns the annotation with the given annotationClass (and create it if necessary).
+ *
* @param from start of the substring
* @param to end of the substring
* @param annotationClass class of the annotation
@@ -169,7 +167,8 @@ public class Interpretation {
* Sets a key/value pair for an annotation of a substring. If an annotation of the class
* does not exist, a new is created.
*
- * A shortcut for annotate(from, to, annotationClass, key, value
+ * A shortcut for annotate(from, to, annotationClass, key, value)
+ *
* @param from start of the substring
* @param to end of the substring
* @param annotationClass class of the annotation
@@ -184,7 +183,8 @@ public class Interpretation {
* Sets a key/value pair for an annotation of a substring. If an annotation of the class
* does not exist, a new is created.
*
- * A shortcut for annotate(from, to, annotationClass, key, value
+ * A shortcut for annotate(from, to, annotationClass, key, value)
+ *
* @param from start of the substring
* @param to end of the substring
* @param annotationClass class of the annotation
@@ -219,12 +219,7 @@ public class Interpretation {
*/
public List<Annotations> getAll(AnnotationClass annotationClass) {
// TODO: This implementation is very inefficient because it unnecessarily collects for all classes
- Map<AnnotationClass,List<Annotations>> all = getAll();
- if(all.containsKey(annotationClass)){
- return all.get(annotationClass);
- } else {
- return Collections.emptyList();
- }
+ return getAll().getOrDefault(annotationClass, List.of());
}
/**
@@ -256,7 +251,7 @@ public class Interpretation {
/**
* Gets the value of a property set on an annotation.
* If the annotation or the key/value pair does not exists, null
- * is returned
+ * is returned.
*/
public Object get(String annotationClass,String key) {
return get(new AnnotationClass(annotationClass),key);
@@ -265,7 +260,7 @@ public class Interpretation {
/**
* Gets the value of a property set on an annotation.
* If the annotation or the key/value pair does not exists, null
- * is returned
+ * is returned.
*/
public Object get(AnnotationClass annotationClass,String key) {
Annotations annotations = get(annotationClass);
@@ -280,7 +275,7 @@ public class Interpretation {
* Equivalent to <code>get(from,to,new AnnotationClass(annotationClass))</code>
*/
public Annotations get(int from, int to, String annotationClass) {
- return get(from,to,new AnnotationClass(annotationClass));
+ return get(from, to, new AnnotationClass(annotationClass));
}
/**
@@ -310,7 +305,7 @@ public class Interpretation {
* @return the anno
*/
public Annotations get(int from, int to, AnnotationClass annotationClass ) {
- return rootSpan.getAnnotation(from,to,annotationClass);
+ return rootSpan.getAnnotation(from, to, annotationClass);
}
/**
@@ -320,9 +315,9 @@ public class Interpretation {
* is returned.
*
*/
- public Object get(int from,int to,String annotationClass,String key) {
- Annotations annotations = get(from,to,annotationClass);
- if(annotations != null) {
+ public Object get(int from, int to, String annotationClass, String key) {
+ Annotations annotations = get(from, to, annotationClass);
+ if (annotations != null) {
return annotations.get(key);
} else {
return null;
@@ -331,7 +326,6 @@ public class Interpretation {
/**
* Gets all the annotationclasses that describes the text.
-
*/
public Set<AnnotationClass> getClasses() {
return rootSpan.getClasses();
@@ -399,7 +393,7 @@ public class Interpretation {
}
}
sb.append("}");
-
}
}
+
}
diff --git a/container-search/src/main/java/com/yahoo/text/interpretation/Modification.java b/container-search/src/main/java/com/yahoo/text/interpretation/Modification.java
index e2fd5a5ec5c..28cf11c62b1 100644
--- a/container-search/src/main/java/com/yahoo/text/interpretation/Modification.java
+++ b/container-search/src/main/java/com/yahoo/text/interpretation/Modification.java
@@ -9,21 +9,14 @@ import java.util.HashMap;
* This class represents a possible rewrite of an original text. Reasons for rewrite may be due to possible
* spelling errors in the text or to query expansion.
*
- * @author <a href="mailto:arnebef@yahoo-inc.com">Arne Bergene Fossaa</a>
+ * @author Arne Bergene Fossaa
*/
public class Modification extends HashMap<String,Object>{
- /**
- *
- */
- private static final long serialVersionUID = -8522335044460396296L;
-
-
public final static AnnotationClass MODIFICATION_CLASS = new AnnotationClass("modification");
-
- private String text;
- private Annotations annotations;
+ private final String text;
+ private final Annotations annotations;
public Modification(String text) {
this.text = text;
diff --git a/container-search/src/main/java/com/yahoo/text/interpretation/Span.java b/container-search/src/main/java/com/yahoo/text/interpretation/Span.java
index 6ade5e323e7..90cc6231d48 100644
--- a/container-search/src/main/java/com/yahoo/text/interpretation/Span.java
+++ b/container-search/src/main/java/com/yahoo/text/interpretation/Span.java
@@ -22,7 +22,7 @@ import java.util.Set;
* <p>
* A span will usually be used indirectly through Interpretation.
*
- * @author <a href="mailto:arnebef@yahoo-inc.com">Arne Bergene Fossaa</a>
+ * @author Arne Bergene Fossaa
*/
public class Span {
@@ -33,7 +33,6 @@ public class Span {
private final int from;
private final int to;
-
/**
* Creates a new root span based on the modfication
*/
@@ -44,7 +43,7 @@ public class Span {
this.to = modification.getText().length();
}
- //This constructor is private to ensure that all child spans for a span is contained inside it.
+ // This constructor is private to ensure that all child spans for a span is contained inside it.
private Span(int from, int to, Span parent) {
this.parent = parent;
this.modification = parent.modification;
@@ -52,8 +51,6 @@ public class Span {
this.to = to;
}
-
-
/**
* Returns the text that this spans is
*/
@@ -61,12 +58,11 @@ public class Span {
return modification.getText().substring(from, to);
}
-
+ @Override
public String toString() {
return "SPAN: " + getText();
}
-
public Annotations annotate(AnnotationClass clazz) {
Annotations annotations = this.annotations.get(clazz);
if (!this.annotations.containsKey(clazz)) {
@@ -83,7 +79,6 @@ public class Span {
return addAnnotation(from, to, clazz);
}
-
/**
* Returns all annotations that are contained in either this subspan or in any of its subannotations
*/
@@ -124,7 +119,7 @@ public class Span {
* @throws RuntimeException if (from,to) is not contained in the span
*/
public Annotations getAnnotation(int from, int to, AnnotationClass clazz) {
- if(from < this.from || to > this.to) {
+ if (from < this.from || to > this.to) {
throw new RuntimeException("Trying to get a range that is outside this span");
}
if (this.parent != null) {
@@ -140,13 +135,12 @@ public class Span {
*/
public Set<AnnotationClass> getClasses() {
return getClasses(from, to);
-
}
/**
* Returns all AnnotationClasses that are defined for the range (from,to).
*
- * @throws RuntimeException if (from,to) is not contained in the span
+ * @throws RuntimeException if (from, to) is not contained in the span
*/
public Set<AnnotationClass> getClasses(int from, int to) {
if(from < this.from || to > this.to) {
@@ -161,8 +155,6 @@ public class Span {
}
}
-
-
/**
* Returns an unmodifiable list of all spans below this span that is a leaf node
*/
@@ -191,26 +183,25 @@ public class Span {
/** hack */
public int getFrom() { return from; }
+
/** hack */
public int getTo() { return to; }
- //Needed by addAnnotation
+ // Needed by addAnnotation
private List<Span> getRemovableSubSpan() {
return subSpans == null ?
Collections.<Span>emptyList() :
subSpans;
}
-
private void addSubSpan(Span span) {
- if(subSpans == null) {
+ if (subSpans == null) {
subSpans = new ArrayList<>();
}
subSpans.add(span);
}
-
- /*
+ /**
* How this works:
*
* First we check if any excisting subannotation can contain this annotation. If so, we leave it to them to add
@@ -222,7 +213,7 @@ public class Span {
*/
private Annotations addAnnotation(int from, int to, AnnotationClass clazz) {
if (equalsRange(from, to)) {
- //We simply add everything from the new span to this
+ // We simply add everything from the new span to this
if (annotations.containsKey(clazz)) {
return annotations.get(clazz);
} else {
@@ -232,7 +223,7 @@ public class Span {
}
}
- //We then check if any of the children intersects
+ // We then check if any of the children intersects
for (Span subSpan : getSubSpans()) {
if (subSpan.intersects(from, to)) {
throw new RuntimeException("Trying to add span that intersects already excisting span");
@@ -241,14 +232,13 @@ public class Span {
}
}
- //We now know that we have to add the new span to this span
+ // We now know that we have to add the new span to this span
Span span = new Span(from, to, this);
Annotations nAnnotations = new Annotations(span);
span.annotations.put(clazz,nAnnotations);
addSubSpan(span);
-
- //We then add any subannotation that is inside the span
+ // We then add any subannotation that is inside the span
Iterator<Span> subIterator = getRemovableSubSpan().iterator();
while (subIterator.hasNext()) {
@@ -256,7 +246,7 @@ public class Span {
if (subSpan.contains(from, to)) {
return subSpan.addAnnotation(from, to, clazz);
} else if (subSpan.isInside(from, to)) {
- //Overtake the subannotation
+ // Take over the subannotation
subSpan.parent = span;
span.addSubSpan(subSpan);
subIterator.remove();
@@ -265,7 +255,6 @@ public class Span {
return nAnnotations;
}
-
private boolean contains(int from, int to) {
return this.from <= from && this.to >= to;
}
@@ -274,12 +263,9 @@ public class Span {
return this.from >= from && this.to <= to;
}
-
private boolean intersects(int from, int to) {
return (this.from < from && this.to > from && this.to < to)
|| (this.from < to && this.to > to && this.from > from);
-
-
}
private boolean equalsRange(int from, int to) {
@@ -336,7 +322,7 @@ public class Span {
if (!contains(from, to)) {
return null;
}
- //First yourself, then the subs
+ // First yourself, then the subs
Annotations annotations = this.annotations.get(clazz);
for (Span subSpan : getSubSpans()) {
Annotations subAnnotations = subSpan.getBestAnnotation(from, to, clazz);
@@ -346,4 +332,5 @@ public class Span {
}
return annotations;
}
+
}
diff --git a/container-search/src/main/java/com/yahoo/vespa/streamingvisitors/TracingOptions.java b/container-search/src/main/java/com/yahoo/vespa/streamingvisitors/TracingOptions.java
index 3c96b00d628..b9e19d6a1b6 100644
--- a/container-search/src/main/java/com/yahoo/vespa/streamingvisitors/TracingOptions.java
+++ b/container-search/src/main/java/com/yahoo/vespa/streamingvisitors/TracingOptions.java
@@ -15,12 +15,12 @@ import java.util.concurrent.TimeUnit;
* Encapsulates all trace-related components and options used by the streaming search Searcher.
*
* Provides a DEFAULT static instance which has the following characteristics:
- * - Approximately 1 query every 2 seconds is traced
+ * - Approximately 1 query every second is traced
* - Trace level is set to 7 for traced queries
- * - Only emits traces for queries that have timed out and where the elapsed time is at least 5x
+ * - Only emits traces for queries that have timed out and where the elapsed time is at least 2x
* of the timeout specified in the query itself
* - Emits traces to the Vespa log
- * - Only 1 trace every 10 seconds may be emitted to the log
+ * - Only 2 traces every 10 seconds may be emitted to the log
*/
public class TracingOptions {
@@ -50,11 +50,11 @@ public class TracingOptions {
public static final TracingOptions DEFAULT;
public static final int DEFAULT_TRACE_LEVEL_OVERRIDE = 7; // TODO determine appropriate trace level
// Traces won't be exported unless the query has timed out with a duration that is > timeout * multiplier
- public static final double TRACE_TIMEOUT_MULTIPLIER_THRESHOLD = 5.0;
+ public static final double TRACE_TIMEOUT_MULTIPLIER_THRESHOLD = 2.0;
static {
- SamplingStrategy queryTraceSampler = ProbabilisticSampleRate.withSystemDefaults(0.5);
- SamplingStrategy logExportSampler = MaxSamplesPerPeriod.withSteadyClock(TimeUnit.SECONDS.toNanos(10), 1);
+ SamplingStrategy queryTraceSampler = ProbabilisticSampleRate.withSystemDefaults(1);
+ SamplingStrategy logExportSampler = MaxSamplesPerPeriod.withSteadyClock(TimeUnit.SECONDS.toNanos(10), 2);
TraceExporter traceExporter = new SamplingTraceExporter(new LoggingTraceExporter(), logExportSampler);
DEFAULT = new TracingOptions(queryTraceSampler, traceExporter, System::nanoTime,
DEFAULT_TRACE_LEVEL_OVERRIDE, TRACE_TIMEOUT_MULTIPLIER_THRESHOLD);
diff --git a/container-search/src/main/java/com/yahoo/vespa/streamingvisitors/VdsStreamingSearcher.java b/container-search/src/main/java/com/yahoo/vespa/streamingvisitors/VdsStreamingSearcher.java
index 4750bac551c..24dd25c5182 100644
--- a/container-search/src/main/java/com/yahoo/vespa/streamingvisitors/VdsStreamingSearcher.java
+++ b/container-search/src/main/java/com/yahoo/vespa/streamingvisitors/VdsStreamingSearcher.java
@@ -1,11 +1,15 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.streamingvisitors;
+import com.yahoo.container.core.documentapi.VespaDocumentAccess;
import com.yahoo.document.DocumentId;
import com.yahoo.document.select.parser.ParseException;
import com.yahoo.document.select.parser.TokenMgrException;
+import com.yahoo.documentapi.VisitorParameters;
+import com.yahoo.documentapi.VisitorSession;
+import com.yahoo.documentapi.messagebus.MessageBusDocumentAccess;
+import com.yahoo.documentapi.messagebus.loadtypes.LoadTypeSet;
import com.yahoo.fs4.DocsumPacket;
-import com.yahoo.log.LogLevel;
import com.yahoo.messagebus.routing.Route;
import com.yahoo.prelude.Ping;
import com.yahoo.prelude.Pong;
@@ -30,6 +34,7 @@ import java.math.BigInteger;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
import java.util.logging.Logger;
/**
@@ -53,15 +58,15 @@ public class VdsStreamingSearcher extends VespaBackEndSearcher {
private Route route;
/** The configId used to access the searchcluster. */
- private String searchClusterConfigId = null;
+ private String searchClusterName = null;
private String documentType;
/** The route to the storage cluster. */
private String storageClusterRouteSpec = null;
- private String getSearchClusterConfigId() { return searchClusterConfigId; }
+ private String getSearchClusterName() { return searchClusterName; }
private String getStorageClusterRouteSpec() { return storageClusterRouteSpec; }
- public final void setSearchClusterConfigId(String clusterName) {
- this.searchClusterConfigId = clusterName;
+ public final void setSearchClusterName(String clusterName) {
+ this.searchClusterName = clusterName;
}
public final void setDocumentType(String documentType) {
this.documentType = documentType;
@@ -71,16 +76,35 @@ public class VdsStreamingSearcher extends VespaBackEndSearcher {
this.storageClusterRouteSpec = storageClusterRouteSpec;
}
- private static class VdsVisitorFactory implements VisitorFactory {
+ private static class VespaVisitorFactory implements VdsVisitor.VisitorSessionFactory, VisitorFactory {
+
+ private final VespaDocumentAccess access;
+
+ private VespaVisitorFactory(VespaDocumentAccess access) {
+ this.access = access;
+ }
+
+ @Override
+ public VisitorSession createVisitorSession(VisitorParameters params) throws ParseException {
+ return access.createVisitorSession(params);
+ }
+
+ @Override
+ public LoadTypeSet getLoadTypeSet() {
+ return ((MessageBusDocumentAccess) access.delegate()).getParams().getLoadTypes();
+ }
+
@Override
public Visitor createVisitor(Query query, String searchCluster, Route route, String documentType, int traceLevelOverride) {
- return new VdsVisitor(query, searchCluster, route, documentType, traceLevelOverride);
+ return new VdsVisitor(query, searchCluster, route, documentType, this, traceLevelOverride);
}
+
}
- public VdsStreamingSearcher() {
- this(new VdsVisitorFactory());
+ public VdsStreamingSearcher(VespaDocumentAccess access) {
+ this(new VespaVisitorFactory(access));
}
+
VdsStreamingSearcher(VisitorFactory visitorFactory) {
this.visitorFactory = visitorFactory;
tracingOptions = TracingOptions.DEFAULT;
@@ -136,17 +160,21 @@ public class VdsStreamingSearcher extends VespaBackEndSearcher {
@Override
public Result doSearch2(Query query, Execution execution) {
+ if (query.getTimeLeft() <= 0) {
+ return new Result(query, ErrorMessage.createTimeout(String.format("No time left for searching (timeout=%d)", query.getTimeout())));
+ }
+
initializeMissingQueryFields(query);
if (documentSelectionQueryParameterCount(query) != 1) {
return new Result(query, ErrorMessage.createBackendCommunicationError("Streaming search needs one and " +
"only one of these query parameters to be set: streaming.userid, streaming.groupname, " +
"streaming.selection"));
}
- query.trace("Routing to search cluster " + getSearchClusterConfigId() + " and document type " + documentType, 4);
+ query.trace("Routing to search cluster " + getSearchClusterName() + " and document type " + documentType, 4);
long timeStartedNanos = tracingOptions.getClock().nanoTimeNow();
int effectiveTraceLevel = inferEffectiveQueryTraceLevel(query);
- Visitor visitor = visitorFactory.createVisitor(query, getSearchClusterConfigId(), route, documentType, effectiveTraceLevel);
+ Visitor visitor = visitorFactory.createVisitor(query, getSearchClusterName(), route, documentType, effectiveTraceLevel);
try {
visitor.doSearch();
} catch (ParseException e) {
@@ -163,7 +191,7 @@ public class VdsStreamingSearcher extends VespaBackEndSearcher {
query.toString(), elapsedMillis / 1000.0)));
}
return new Result(query, ErrorMessage.createTimeout(e.getMessage()));
- } catch (InterruptedException|IllegalArgumentException e) {
+ } catch (InterruptedException | IllegalArgumentException e) {
return new Result(query, ErrorMessage.createBackendCommunicationError(e.getMessage()));
}
@@ -300,9 +328,9 @@ public class VdsStreamingSearcher extends VespaBackEndSearcher {
String expUserId = query.properties().getString(streamingUserid);
String expGroupName = query.properties().getString(streamingGroupname);
- LogLevel logLevel = LogLevel.ERROR;
+ Level logLevel = Level.SEVERE;
if (skippedEarlierResult) {
- logLevel = LogLevel.DEBUG;
+ logLevel = Level.FINE;
}
DocumentId docId;
diff --git a/container-search/src/main/java/com/yahoo/vespa/streamingvisitors/VdsVisitor.java b/container-search/src/main/java/com/yahoo/vespa/streamingvisitors/VdsVisitor.java
index 795b62663d5..49fda880b44 100644
--- a/container-search/src/main/java/com/yahoo/vespa/streamingvisitors/VdsVisitor.java
+++ b/container-search/src/main/java/com/yahoo/vespa/streamingvisitors/VdsVisitor.java
@@ -17,7 +17,7 @@ import com.yahoo.documentapi.messagebus.protocol.DocumentSummaryMessage;
import com.yahoo.documentapi.messagebus.protocol.QueryResultMessage;
import com.yahoo.documentapi.messagebus.protocol.SearchResultMessage;
import com.yahoo.io.GrowableByteBuffer;
-import com.yahoo.log.LogLevel;
+import java.util.logging.Level;
import com.yahoo.messagebus.Message;
import com.yahoo.messagebus.Trace;
import com.yahoo.messagebus.routing.Route;
@@ -64,6 +64,8 @@ class VdsVisitor extends VisitorDataHandler implements Visitor {
private static final CompoundName streamingPriority=new CompoundName("streaming.priority");
private static final CompoundName streamingMaxbucketspervisitor=new CompoundName("streaming.maxbucketspervisitor");
+ protected static final int MAX_BUCKETS_PER_VISITOR = 1024;
+
private static final Logger log = Logger.getLogger(VdsVisitor.class.getName());
private final VisitorParameters params = new VisitorParameters("");
private List<SearchResult.Hit> hits = new ArrayList<>();
@@ -81,55 +83,6 @@ class VdsVisitor extends VisitorDataHandler implements Visitor {
LoadTypeSet getLoadTypeSet();
}
- private static class MessageBusVisitorSessionFactory implements VisitorSessionFactory {
- private static final Object initMonitor = new Object();
- private static final AtomicReference<MessageBusVisitorSessionFactory> instance = new AtomicReference<>();
-
- private final LoadTypeSet loadTypes;
- private final DocumentAccess access;
-
- private MessageBusVisitorSessionFactory() {
- loadTypes = new LoadTypeSet("client");
- access = new MessageBusDocumentAccess(new MessageBusParams(loadTypes));
- }
-
- @Override
- public VisitorSession createVisitorSession(VisitorParameters params) throws ParseException {
- return access.createVisitorSession(params);
- }
-
- @Override
- public LoadTypeSet getLoadTypeSet() {
- return loadTypes;
- }
-
- /**
- * Returns a single, shared instance of this class which is lazily created in a thread-safe
- * manner the first time this method is invoked.
- *
- * May throw any config-related exception if subscription fails.
- */
- static MessageBusVisitorSessionFactory sharedInstance() {
- var ref = instance.getAcquire();
- if (ref != null) {
- return ref;
- }
- synchronized (initMonitor) {
- ref = instance.getAcquire();
- if (ref != null) {
- return ref;
- }
- ref = new MessageBusVisitorSessionFactory();
- instance.setRelease(ref);
- }
- return ref;
- }
- }
-
- public VdsVisitor(Query query, String searchCluster, Route route, String documentType, int traceLevelOverride) {
- this(query, searchCluster, route, documentType, MessageBusVisitorSessionFactory.sharedInstance(), traceLevelOverride);
- }
-
public VdsVisitor(Query query, String searchCluster, Route route,
String documentType, VisitorSessionFactory visitorSessionFactory,
int traceLevelOverride)
@@ -142,9 +95,9 @@ class VdsVisitor extends VisitorDataHandler implements Visitor {
private int inferSessionTraceLevel(Query query) {
int implicitLevel = traceLevelOverride;
- if (log.isLoggable(LogLevel.SPAM)) {
+ if (log.isLoggable(Level.FINEST)) {
implicitLevel = 9;
- } else if (log.isLoggable(LogLevel.DEBUG)) {
+ } else if (log.isLoggable(Level.FINE)) {
implicitLevel = 7;
}
return Math.max(query.getTraceLevel(), implicitLevel);
@@ -199,7 +152,7 @@ class VdsVisitor extends VisitorDataHandler implements Visitor {
}
params.setMaxPending(Integer.MAX_VALUE);
- params.setMaxBucketsPerVisitor(Integer.MAX_VALUE);
+ params.setMaxBucketsPerVisitor(MAX_BUCKETS_PER_VISITOR);
params.setTraceLevel(inferSessionTraceLevel(query));
@@ -262,9 +215,8 @@ class VdsVisitor extends VisitorDataHandler implements Visitor {
static int getQueryFlags(Query query) {
int flags = 0;
- boolean requestCoverage=true; // Always request coverage information
+ boolean requestCoverage = true; // Always request coverage information
- flags |= 0; // was collapse
flags |= query.properties().getBoolean(Model.ESTIMATE) ? 0x00000080 : 0;
flags |= (query.getRanking().getFreshness() != null) ? 0x00002000 : 0;
flags |= requestCoverage ? 0x00008000 : 0;
@@ -329,26 +281,24 @@ class VdsVisitor extends VisitorDataHandler implements Visitor {
public void doSearch() throws InterruptedException, ParseException, TimeoutException {
VisitorSession session = visitorSessionFactory.createVisitorSession(params);
try {
- if ( !session.waitUntilDone(query.getTimeout())) {
- log.log(LogLevel.DEBUG, "Visitor returned from waitUntilDone without being completed for " + query + " with selection " + params.getDocumentSelection());
+ if ( ! session.waitUntilDone(query.getTimeout())) {
+ log.log(Level.FINE, () -> "Visitor returned from waitUntilDone without being completed for " + query + " with selection " + params.getDocumentSelection());
session.abort();
throw new TimeoutException("Query timed out in " + VdsStreamingSearcher.class.getName());
}
} finally {
session.destroy();
sessionTrace = session.getTrace();
- log.log(LogLevel.DEBUG, () -> sessionTrace.toString());
+ log.log(Level.FINE, () -> sessionTrace.toString());
query.trace(sessionTrace.toString(), false, 9);
}
if (params.getControlHandler().getResult().code == VisitorControlHandler.CompletionCode.SUCCESS) {
- if (log.isLoggable(LogLevel.DEBUG)) {
- log.log(LogLevel.DEBUG, "VdsVisitor completed successfully for " + query + " with selection " + params.getDocumentSelection());
- }
+ log.log(Level.FINE, () -> "VdsVisitor completed successfully for " + query + " with selection " + params.getDocumentSelection());
} else {
- throw new IllegalArgumentException("Query failed: " // TODO: Is it necessary to use a runtime exception?
- + params.getControlHandler().getResult().code + ": "
- + params.getControlHandler().getResult().message);
+ throw new IllegalArgumentException("Query failed: " +
+ params.getControlHandler().getResult().code + ": " +
+ params.getControlHandler().getResult().message);
}
}
@@ -384,8 +334,8 @@ class VdsVisitor extends VisitorDataHandler implements Visitor {
}
public void onSearchResult(SearchResult sr) {
- if (log.isLoggable(LogLevel.SPAM)) {
- log.log(LogLevel.SPAM, "Got SearchResult for query with selection " + params.getDocumentSelection());
+ if (log.isLoggable(Level.FINEST)) {
+ log.log(Level.FINEST, "Got SearchResult for query with selection " + params.getDocumentSelection());
}
handleSearchResult(sr);
}
@@ -393,8 +343,8 @@ class VdsVisitor extends VisitorDataHandler implements Visitor {
private void handleSearchResult(SearchResult sr) {
final int hitCountTotal = sr.getTotalHitCount();
final int hitCount = sr.getHitCount();
- if (log.isLoggable(LogLevel.DEBUG)) {
- log.log(LogLevel.DEBUG, "Got SearchResult with " + hitCountTotal + " in total and " + hitCount + " hits in real for query with selection " + params.getDocumentSelection());
+ if (log.isLoggable(Level.FINE)) {
+ log.log(Level.FINE, "Got SearchResult with " + hitCountTotal + " in total and " + hitCount + " hits in real for query with selection " + params.getDocumentSelection());
}
List<SearchResult.Hit> newHits = new ArrayList<>(hitCount);
@@ -412,20 +362,21 @@ class VdsVisitor extends VisitorDataHandler implements Visitor {
}
private void mergeGroupingMaps(Map<Integer, byte []> newGroupingMap) {
- if (log.isLoggable(LogLevel.SPAM)) {
- log.log(LogLevel.SPAM, "mergeGroupingMaps: newGroupingMap = " + newGroupingMap);
+ if (log.isLoggable(Level.FINEST)) {
+ log.log(Level.FINEST, "mergeGroupingMaps: newGroupingMap = " + newGroupingMap);
}
for(Integer key : newGroupingMap.keySet()) {
byte [] value = newGroupingMap.get(key);
Grouping newGrouping = new Grouping();
- if (log.isLoggable(LogLevel.SPAM)) {
- log.log(LogLevel.SPAM, "Received group with key " + key + " and size " + value.length);
+ if (log.isLoggable(Level.FINEST)) {
+ log.log(Level.FINEST, "Received group with key " + key + " and size " + value.length);
}
BufferSerializer buf = new BufferSerializer( new GrowableByteBuffer(ByteBuffer.wrap(value)) );
newGrouping.deserialize(buf);
if (buf.getBuf().hasRemaining()) {
- throw new IllegalArgumentException("Failed deserializing grouping. There are still data left. Position = " + buf.position() + ", limit = " + buf.getBuf().limit());
+ throw new IllegalArgumentException("Failed deserializing grouping. There is still data left. " +
+ "Position = " + buf.position() + ", limit = " + buf.getBuf().limit());
}
synchronized (groupingMap) {
@@ -440,16 +391,16 @@ class VdsVisitor extends VisitorDataHandler implements Visitor {
}
public void onDocumentSummary(DocumentSummary ds) {
- if (log.isLoggable(LogLevel.SPAM)) {
- log.log(LogLevel.SPAM, "Got DocumentSummary for query with selection " + params.getDocumentSelection());
+ if (log.isLoggable(Level.FINEST)) {
+ log.log(Level.FINEST, "Got DocumentSummary for query with selection " + params.getDocumentSelection());
}
handleSummary(ds);
}
private void handleSummary(DocumentSummary ds) {
int summaryCount = ds.getSummaryCount();
- if (log.isLoggable(LogLevel.DEBUG)) {
- log.log(LogLevel.DEBUG, "Got DocumentSummary with " + summaryCount + " summaries for query with selection " + params.getDocumentSelection());
+ if (log.isLoggable(Level.FINE)) {
+ log.log(Level.FINE, "Got DocumentSummary with " + summaryCount + " summaries for query with selection " + params.getDocumentSelection());
}
synchronized (summaryMap) {
for (int i = 0; i < summaryCount; i++) {
diff --git a/container-search/src/main/java/com/yahoo/vespa/streamingvisitors/Visitor.java b/container-search/src/main/java/com/yahoo/vespa/streamingvisitors/Visitor.java
index e8b83495c69..8065f71c1f0 100644
--- a/container-search/src/main/java/com/yahoo/vespa/streamingvisitors/Visitor.java
+++ b/container-search/src/main/java/com/yahoo/vespa/streamingvisitors/Visitor.java
@@ -15,7 +15,7 @@ import java.util.Map;
/**
* Visitor for performing searches and accessing results.
*
- * @author <a href="mailto:ulf@yahoo-inc.com">Ulf Carlin</a>
+ * @author Ulf Carlin
*/
interface Visitor {
diff --git a/container-search/src/main/java/com/yahoo/vespa/streamingvisitors/VisitorFactory.java b/container-search/src/main/java/com/yahoo/vespa/streamingvisitors/VisitorFactory.java
index 7ce323a2f2b..421a36dbc63 100644
--- a/container-search/src/main/java/com/yahoo/vespa/streamingvisitors/VisitorFactory.java
+++ b/container-search/src/main/java/com/yahoo/vespa/streamingvisitors/VisitorFactory.java
@@ -7,7 +7,7 @@ import com.yahoo.search.Query;
/**
* A factory that creates Visitors.
*
- * @author <a href="mailto:ulf@yahoo-inc.com">Ulf Carlin</a>
+ * @author Ulf Carlin
*/
interface VisitorFactory {
diff --git a/container-search/src/main/java/com/yahoo/vespa/streamingvisitors/tracing/LoggingTraceExporter.java b/container-search/src/main/java/com/yahoo/vespa/streamingvisitors/tracing/LoggingTraceExporter.java
index 0aaf301e071..230af8971c2 100644
--- a/container-search/src/main/java/com/yahoo/vespa/streamingvisitors/tracing/LoggingTraceExporter.java
+++ b/container-search/src/main/java/com/yahoo/vespa/streamingvisitors/tracing/LoggingTraceExporter.java
@@ -1,7 +1,7 @@
// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.streamingvisitors.tracing;
-import com.yahoo.log.LogLevel;
+import java.util.logging.Level;
import java.util.function.Supplier;
import java.util.logging.Logger;
@@ -17,7 +17,7 @@ public class LoggingTraceExporter implements TraceExporter {
public void maybeExport(Supplier<TraceDescription> traceDescriptionSupplier) {
var traceDescription = traceDescriptionSupplier.get();
if (traceDescription.getTrace() != null) {
- log.log(LogLevel.WARNING, String.format("%s: %s", traceDescription.getDescription(),
+ log.log(Level.WARNING, String.format("%s: %s", traceDescription.getDescription(),
traceDescription.getTrace().toString()));
}
}
diff --git a/container-search/src/main/javacc/com/yahoo/prelude/semantics/parser/SemanticsParser.jj b/container-search/src/main/javacc/com/yahoo/prelude/semantics/parser/SemanticsParser.jj
index 3a82ffffb47..24fae895364 100644
--- a/container-search/src/main/javacc/com/yahoo/prelude/semantics/parser/SemanticsParser.jj
+++ b/container-search/src/main/javacc/com/yahoo/prelude/semantics/parser/SemanticsParser.jj
@@ -310,6 +310,7 @@ TermType termType() :
<DOLLAR> { return TermType.RANK; } |
<PLUS> { return TermType.AND; } |
<DASH> { return TermType.NOT; } |
+ <EQUALS> { return TermType.EQUIV; } |
{ return TermType.DEFAULT; }
}
diff --git a/container-search/src/main/resources/configdefinitions/fs4.def b/container-search/src/main/resources/configdefinitions/container.search.fs4.def
index 9562cfa75bd..9562cfa75bd 100644
--- a/container-search/src/main/resources/configdefinitions/fs4.def
+++ b/container-search/src/main/resources/configdefinitions/container.search.fs4.def
diff --git a/container-search/src/main/resources/configdefinitions/qr-monitor.def b/container-search/src/main/resources/configdefinitions/prelude.cluster.qr-monitor.def
index 2c4ff3c6167..2c4ff3c6167 100644
--- a/container-search/src/main/resources/configdefinitions/qr-monitor.def
+++ b/container-search/src/main/resources/configdefinitions/prelude.cluster.qr-monitor.def
diff --git a/container-search/src/main/resources/configdefinitions/emulation.def b/container-search/src/main/resources/configdefinitions/prelude.emulation.def
index 70d2d4954a4..70d2d4954a4 100644
--- a/container-search/src/main/resources/configdefinitions/emulation.def
+++ b/container-search/src/main/resources/configdefinitions/prelude.emulation.def
diff --git a/container-search/src/main/resources/configdefinitions/documentdb-info.def b/container-search/src/main/resources/configdefinitions/prelude.fastsearch.documentdb-info.def
index 76096b4a6f7..76096b4a6f7 100644
--- a/container-search/src/main/resources/configdefinitions/documentdb-info.def
+++ b/container-search/src/main/resources/configdefinitions/prelude.fastsearch.documentdb-info.def
diff --git a/container-search/src/main/resources/configdefinitions/keyvalue.def b/container-search/src/main/resources/configdefinitions/prelude.searcher.keyvalue.def
index 95153708aa2..95153708aa2 100644
--- a/container-search/src/main/resources/configdefinitions/keyvalue.def
+++ b/container-search/src/main/resources/configdefinitions/prelude.searcher.keyvalue.def
diff --git a/container-search/src/main/resources/configdefinitions/qr-quotetable.def b/container-search/src/main/resources/configdefinitions/prelude.searcher.qr-quotetable.def
index 40979ad2a35..40979ad2a35 100644
--- a/container-search/src/main/resources/configdefinitions/qr-quotetable.def
+++ b/container-search/src/main/resources/configdefinitions/prelude.searcher.qr-quotetable.def
diff --git a/container-search/src/main/resources/configdefinitions/semantic-rules.def b/container-search/src/main/resources/configdefinitions/prelude.semantics.semantic-rules.def
index 5ac0cca7ff6..5ac0cca7ff6 100644
--- a/container-search/src/main/resources/configdefinitions/semantic-rules.def
+++ b/container-search/src/main/resources/configdefinitions/prelude.semantics.semantic-rules.def
diff --git a/container-search/src/main/resources/configdefinitions/cluster.def b/container-search/src/main/resources/configdefinitions/search.config.cluster.def
index 52eca7ef753..812073517bb 100644
--- a/container-search/src/main/resources/configdefinitions/cluster.def
+++ b/container-search/src/main/resources/configdefinitions/search.config.cluster.def
@@ -5,8 +5,7 @@ namespace=search.config
#Note: Use clusterName where possible instead
clusterId int default=0
-#Internal searcher cache. Size is measured in megabytes of raw packet
-#size. Hits larger than 1% of total cache size will not be cached.
+# Not used
cacheSize int default=1
#Timeout for internal searcher cache. Entries older than this number
diff --git a/container-search/src/main/resources/configdefinitions/index-info.def b/container-search/src/main/resources/configdefinitions/search.config.index-info.def
index f3b905d4d0a..f3b905d4d0a 100644
--- a/container-search/src/main/resources/configdefinitions/index-info.def
+++ b/container-search/src/main/resources/configdefinitions/search.config.index-info.def
diff --git a/container-search/src/main/resources/configdefinitions/qr-start.def b/container-search/src/main/resources/configdefinitions/search.config.qr-start.def
index 031877ada81..95e9d4575dd 100644
--- a/container-search/src/main/resources/configdefinitions/qr-start.def
+++ b/container-search/src/main/resources/configdefinitions/search.config.qr-start.def
@@ -20,6 +20,9 @@ jvm.minHeapsize int default=1536 restart
## Stack size (in kilobytes)
jvm.stacksize int default=512 restart
+## CompressedOOps size in megabytes
+jvm.compressedClassSpaceSize int default=32 restart
+
## Base value of maximum direct memory size (in megabytes)
jvm.baseMaxDirectMemorySize int default=75 restart
diff --git a/container-search/src/main/resources/configdefinitions/rate-limiting.def b/container-search/src/main/resources/configdefinitions/search.config.rate-limiting.def
index 23235617f8c..23235617f8c 100644
--- a/container-search/src/main/resources/configdefinitions/rate-limiting.def
+++ b/container-search/src/main/resources/configdefinitions/search.config.rate-limiting.def
diff --git a/container-search/src/main/resources/configdefinitions/federation.def b/container-search/src/main/resources/configdefinitions/search.federation.federation.def
index 36eb5d4b4c8..36eb5d4b4c8 100644
--- a/container-search/src/main/resources/configdefinitions/federation.def
+++ b/container-search/src/main/resources/configdefinitions/search.federation.federation.def
diff --git a/container-search/src/main/resources/configdefinitions/provider.def b/container-search/src/main/resources/configdefinitions/search.federation.provider.def
index f9ab305b114..f9ab305b114 100644
--- a/container-search/src/main/resources/configdefinitions/provider.def
+++ b/container-search/src/main/resources/configdefinitions/search.federation.provider.def
diff --git a/container-search/src/main/resources/configdefinitions/searchchain-forward.def b/container-search/src/main/resources/configdefinitions/search.federation.searchchain-forward.def
index 0e86490e120..0e86490e120 100644
--- a/container-search/src/main/resources/configdefinitions/searchchain-forward.def
+++ b/container-search/src/main/resources/configdefinitions/search.federation.searchchain-forward.def
diff --git a/container-search/src/main/resources/configdefinitions/strict-contracts.def b/container-search/src/main/resources/configdefinitions/search.federation.strict-contracts.def
index 5ceb37db8d1..5ceb37db8d1 100644
--- a/container-search/src/main/resources/configdefinitions/strict-contracts.def
+++ b/container-search/src/main/resources/configdefinitions/search.federation.strict-contracts.def
diff --git a/container-search/src/main/resources/configdefinitions/search-with-renderer-handler.def b/container-search/src/main/resources/configdefinitions/search.handler.search-with-renderer-handler.def
index a34e08a1c82..a34e08a1c82 100644
--- a/container-search/src/main/resources/configdefinitions/search-with-renderer-handler.def
+++ b/container-search/src/main/resources/configdefinitions/search.handler.search-with-renderer-handler.def
diff --git a/container-search/src/main/resources/configdefinitions/page-templates.def b/container-search/src/main/resources/configdefinitions/search.pagetemplates.page-templates.def
index 31ec7644d18..31ec7644d18 100644
--- a/container-search/src/main/resources/configdefinitions/page-templates.def
+++ b/container-search/src/main/resources/configdefinitions/search.pagetemplates.page-templates.def
diff --git a/container-search/src/main/resources/configdefinitions/resolvers.def b/container-search/src/main/resources/configdefinitions/search.pagetemplates.resolvers.def
index 6003fdf81f1..6003fdf81f1 100644
--- a/container-search/src/main/resources/configdefinitions/resolvers.def
+++ b/container-search/src/main/resources/configdefinitions/search.pagetemplates.resolvers.def
diff --git a/container-search/src/main/resources/configdefinitions/query-profiles.def b/container-search/src/main/resources/configdefinitions/search.query.profile.config.query-profiles.def
index 3c6d11e2944..869c75df0e9 100644
--- a/container-search/src/main/resources/configdefinitions/query-profiles.def
+++ b/container-search/src/main/resources/configdefinitions/search.query.profile.config.query-profiles.def
@@ -52,10 +52,14 @@ queryprofile[].queryprofilevariant[].inherit[] string
queryprofile[].queryprofilevariant[].property[].name string
# Content of profile variant
queryprofile[].queryprofilevariant[].property[].value string
+# Whether this property is overridable: "true", "false" or ""
+queryprofile[].queryprofilevariant[].property[].overridable string default=""
# Content of profile variant
queryprofile[].queryprofilevariant[].reference[].name string
# Content of profile variant
queryprofile[].queryprofilevariant[].reference[].value string
+# Whether this reference is overridable: "true", "false" or ""
+queryprofile[].queryprofilevariant[].reference[].overridable string default=""
# A query profile type defines the values of instance query profiles.
# The id follows the same rules as for query profiles
diff --git a/container-search/src/main/resources/configdefinitions/rewrites.def b/container-search/src/main/resources/configdefinitions/search.query.rewrite.rewrites.def
index ecca422342a..ecca422342a 100644
--- a/container-search/src/main/resources/configdefinitions/rewrites.def
+++ b/container-search/src/main/resources/configdefinitions/search.query.rewrite.rewrites.def
diff --git a/container-search/src/main/resources/configdefinitions/lowercasing.def b/container-search/src/main/resources/configdefinitions/search.querytransform.lowercasing.def
index b656c451e11..b656c451e11 100644
--- a/container-search/src/main/resources/configdefinitions/lowercasing.def
+++ b/container-search/src/main/resources/configdefinitions/search.querytransform.lowercasing.def
diff --git a/container-search/src/main/resources/configdefinitions/measure-qps.def b/container-search/src/main/resources/configdefinitions/search.statistics.measure-qps.def
index c8b38b9db6e..c8b38b9db6e 100644
--- a/container-search/src/main/resources/configdefinitions/measure-qps.def
+++ b/container-search/src/main/resources/configdefinitions/search.statistics.measure-qps.def
diff --git a/container-search/src/main/resources/configdefinitions/timing-searcher.def b/container-search/src/main/resources/configdefinitions/search.statistics.timing-searcher.def
index 7c2b698bdb0..7c2b698bdb0 100644
--- a/container-search/src/main/resources/configdefinitions/timing-searcher.def
+++ b/container-search/src/main/resources/configdefinitions/search.statistics.timing-searcher.def