/* SPDX-License-Identifier: Apache-2.0 * * The OpenSearch Contributors require contributions made to * this file be licensed under the Apache-2.0 license or a * compatible open source license. */ /* * Modifications Copyright OpenSearch Contributors. See * GitHub history for details. * * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright * ownership. Elasticsearch B.V. licenses this file to you under * the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ using System; using System.Linq.Expressions; using System.Runtime.Serialization; namespace OpenSearch.Client { [DataContract] public class QueryContainerDescriptor : QueryContainer where T : class { private QueryContainer WrapInContainer( Func create, Action assign ) where TQuery : class, TQueryInterface, IQuery, new() where TQueryInterface : class, IQuery { // Invoke the create delegate before assigning container; the create delegate // may mutate the current QueryContainerDescriptor instance such that it // contains a query. See https://github.com/elastic/elasticsearch-net/issues/2875 var query = create.InvokeOrDefault(new TQuery()); var container = ContainedQuery == null ? this : new QueryContainerDescriptor(); IQueryContainer c = container; c.IsVerbatim = query.IsVerbatim; c.IsStrict = query.IsStrict; assign(query, container); container.ContainedQuery = query; //if query is writable (not conditionless or verbatim): return a container that holds the query if (query.IsWritable) return container; //query is conditionless but marked as strict, throw exception if (query.IsStrict) throw new ArgumentException("Query is conditionless but strict is turned on"); //query is conditionless return an empty container that can later be rewritten return null; } /// /// A query defined using a raw json string. /// The query must be enclosed within '{' and '}' /// /// The query dsl json public QueryContainer Raw(string rawJson) => WrapInContainer((RawQueryDescriptor descriptor) => descriptor.Raw(rawJson), (query, container) => container.RawQuery = query); /// /// A query that uses a query parser in order to parse its content. /// public QueryContainer QueryString(Func, IQueryStringQuery> selector) => WrapInContainer(selector, (query, container) => container.QueryString = query); /// /// A query that uses the SimpleQueryParser to parse its context. /// Unlike the regular query_string query, the simple_query_string query will /// never throw an exception, and discards invalid parts of the query. /// public QueryContainer SimpleQueryString(Func, ISimpleQueryStringQuery> selector) => WrapInContainer(selector, (query, container) => container.SimpleQueryString = query); /// /// A query that match on any (configurable) of the provided terms. /// This is a simpler syntax query for using a bool query with several term queries in the should clauses. /// public QueryContainer Terms(Func, ITermsQuery> selector) => WrapInContainer(selector, (query, container) => container.Terms = query); /// /// A fuzzy based query that uses similarity based on Levenshtein (edit distance) algorithm. /// Warning: this query is not very scalable with its default prefix length of 0. In this case, /// every term will be enumerated and cause an edit score calculation or max_expansions is not set. /// public QueryContainer Fuzzy(Func, IFuzzyQuery> selector) => WrapInContainer(selector, (query, container) => container.Fuzzy = query); public QueryContainer FuzzyNumeric(Func, IFuzzyQuery> selector) => WrapInContainer(selector, (query, container) => container.Fuzzy = query); public QueryContainer FuzzyDate(Func, IFuzzyQuery> selector) => WrapInContainer(selector, (query, container) => container.Fuzzy = query); /// /// The default match query is of type boolean. It means that the text provided is analyzed and the analysis /// process constructs a boolean query from the provided text. /// public QueryContainer Match(Func, IMatchQuery> selector) => WrapInContainer(selector, (query, container) => container.Match = query); /// /// The match_phrase query analyzes the match and creates a phrase query out of the analyzed text. /// public QueryContainer MatchPhrase(Func, IMatchPhraseQuery> selector) => WrapInContainer(selector, (query, container) => container.MatchPhrase = query); /// public QueryContainer MatchBoolPrefix(Func, IMatchBoolPrefixQuery> selector) => WrapInContainer(selector, (query, container) => container.MatchBoolPrefix = query); /// /// The match_phrase_prefix is the same as match_phrase, expect it allows for prefix matches on the last term /// in the text /// public QueryContainer MatchPhrasePrefix(Func, IMatchPhrasePrefixQuery> selector) => WrapInContainer(selector, (query, container) => container.MatchPhrasePrefix = query); /// /// The multi_match query builds further on top of the match query by allowing multiple fields to be specified. /// The idea here is to allow to more easily build a concise match type query over multiple fields instead of using a /// relatively more expressive query by using multiple match queries within a bool query. /// public QueryContainer MultiMatch(Func, IMultiMatchQuery> selector) => WrapInContainer(selector, (query, container) => container.MultiMatch = query); /// /// Nested query allows to query nested objects / docs (see nested mapping). The query is executed against the /// nested objects / docs as if they were indexed as separate docs (they are, internally) and resulting in the /// root parent doc (or parent nested mapping). /// public QueryContainer Nested(Func, INestedQuery> selector) => WrapInContainer(selector, (query, container) => container.Nested = query); /// /// A thin wrapper allowing fined grained control what should happen if a query is conditionless /// if you need to fallback to something other than a match_all query /// public QueryContainer Conditionless(Func, IConditionlessQuery> selector) { var query = selector(new ConditionlessQueryDescriptor()); return query?.Query ?? query?.Fallback; } /// /// Matches documents with fields that have terms within a certain numeric range. /// public QueryContainer Range(Func, INumericRangeQuery> selector) => WrapInContainer(selector, (query, container) => container.Range = query); public QueryContainer LongRange(Func, ILongRangeQuery> selector) => WrapInContainer(selector, (query, container) => container.Range = query); /// /// Matches documents with fields that have terms within a certain date range. /// public QueryContainer DateRange(Func, IDateRangeQuery> selector) => WrapInContainer(selector, (query, container) => container.Range = query); /// /// Matches documents with fields that have terms within a certain term range. /// public QueryContainer TermRange(Func, ITermRangeQuery> selector) => WrapInContainer(selector, (query, container) => container.Range = query); /// /// More like this query find documents that are like the provided text by running it against one or more fields. /// public QueryContainer MoreLikeThis(Func, IMoreLikeThisQuery> selector) => WrapInContainer(selector, (query, container) => container.MoreLikeThis = query); /// /// A geo_shape query that finds documents /// that have a geometry that matches for the given spatial relation and input shape /// public QueryContainer GeoShape(Func, IGeoShapeQuery> selector) => WrapInContainer(selector, (query, container) => container.GeoShape = query); /// /// Finds documents with shapes that either intersect, are within, or do not intersect a specified shape. /// public QueryContainer Shape(Func, IShapeQuery> selector) => WrapInContainer(selector, (query, container) => container.Shape = query); /// /// Matches documents with a geo_point type field that falls within a polygon of points /// public QueryContainer GeoPolygon(Func, IGeoPolygonQuery> selector) => WrapInContainer(selector, (query, container) => container.GeoPolygon = query); /// /// Matches documents with a geo_point type field to include only those /// that exist within a specific distance from a given geo_point /// public QueryContainer GeoDistance(Func, IGeoDistanceQuery> selector) => WrapInContainer(selector, (query, container) => container.GeoDistance = query); /// /// Matches documents with a geo_point type field to include only those that exist within a bounding box /// public QueryContainer GeoBoundingBox(Func, IGeoBoundingBoxQuery> selector) => WrapInContainer(selector, (query, container) => container.GeoBoundingBox = query); /// /// The has_child query works the same as the has_child filter, by automatically wrapping the filter with a /// constant_score. /// /// Type of the child public QueryContainer HasChild(Func, IHasChildQuery> selector) where TChild : class => WrapInContainer(selector, (query, container) => container.HasChild = query); /// /// The has_parent query works the same as the has_parent filter, by automatically wrapping the filter with a /// constant_score. /// /// Type of the parent public QueryContainer HasParent(Func, IHasParentQuery> selector) where TParent : class => WrapInContainer(selector, (query, container) => container.HasParent = query); public QueryContainer Knn(Func, IKnnQuery> selector) => WrapInContainer(selector, (query, container) => container.Knn = query); /// /// A query that generates the union of documents produced by its subqueries, and that scores each document /// with the maximum score for that document as produced by any subquery, plus a tie breaking increment for /// any additional matching subqueries. /// public QueryContainer DisMax(Func, IDisMaxQuery> selector) => WrapInContainer(selector, (query, container) => container.DisMax = query); /// public QueryContainer DistanceFeature(Func, IDistanceFeatureQuery> selector) => WrapInContainer(selector, (query, container) => container.DistanceFeature = query); /// /// A query that wraps a filter or another query and simply returns a constant score equal to the query boost /// for every document in the filter. Maps to Lucene ConstantScoreQuery. /// public QueryContainer ConstantScore(Func, IConstantScoreQuery> selector) => WrapInContainer(selector, (query, container) => container.ConstantScore = query); /// /// A query that matches documents matching boolean combinations of other queries. The bool query maps to /// Lucene BooleanQuery. /// It is built using one or more boolean clauses, each clause with a typed occurrence /// public QueryContainer Bool(Func, IBoolQuery> selector) => WrapInContainer(selector, (query, container) => container.Bool = query); /// /// A query that can be used to effectively demote results that match a given query. /// Unlike the "must_not" clause in bool query, this still selects documents that contain /// undesirable terms, but reduces their overall score. /// public QueryContainer Boosting(Func, IBoostingQuery> selector) => WrapInContainer(selector, (query, container) => container.Boosting = query); /// /// A query that matches all documents. Maps to Lucene MatchAllDocsQuery. /// public QueryContainer MatchAll(Func selector = null) => WrapInContainer(selector, (query, container) => container.MatchAll = query ?? new MatchAllQuery()); /// /// A query that matches no documents. This is the inverse of the match_all query. /// public QueryContainer MatchNone(Func selector = null) => WrapInContainer(selector, (query, container) => container.MatchNone = query ?? new MatchNoneQuery()); /// /// Matches documents that have fields that contain a term (not analyzed). /// The term query maps to Lucene TermQuery. /// public QueryContainer Term(Expression> field, object value, double? boost = null, string name = null) => Term(t => t.Field(field).Value(value).Boost(boost).Name(name)); /// /// Helper method to easily filter on join relations /// public QueryContainer HasRelationName(Expression> field, RelationName value) => Term(t => t.Field(field).Value(value)); /// Helper method to easily filter on join relations public QueryContainer HasRelationName(Expression> field) => Term(t => t.Field(field).Value(Infer.Relation())); /// /// Matches documents that have fields that contain a term (not analyzed). /// The term query maps to Lucene TermQuery. /// public QueryContainer Term(Field field, object value, double? boost = null, string name = null) => Term(t => t.Field(field).Value(value).Boost(boost).Name(name)); /// /// Matches documents that have fields that contain a term (not analyzed). /// The term query maps to Lucene TermQuery. /// public QueryContainer Term(Func, ITermQuery> selector) => WrapInContainer(selector, (query, container) => container.Term = query); /// /// Matches documents that have fields matching a wildcard expression (not analyzed). /// Supported wildcards are *, which matches any character sequence (including the empty one), and ?, /// which matches any single character. Note this query can be slow, as it needs to iterate /// over many terms. In order to prevent extremely slow wildcard queries, a wildcard term should /// not start with one of the wildcards * or ?. The wildcard query maps to Lucene WildcardQuery. /// public QueryContainer Wildcard(Expression> field, string value, double? boost = null, MultiTermQueryRewrite rewrite = null, string name = null ) => Wildcard(t => t.Field(field).Value(value).Rewrite(rewrite).Boost(boost).Name(name)); /// /// Matches documents that have fields matching a wildcard expression (not analyzed). /// Supported wildcards are *, which matches any character sequence (including the empty one), and ?, /// which matches any single character. Note this query can be slow, as it needs to iterate over many terms. /// In order to prevent extremely slow wildcard queries, a wildcard term should not start with /// one of the wildcards * or ?. The wildcard query maps to Lucene WildcardQuery. /// public QueryContainer Wildcard(Field field, string value, double? boost = null, MultiTermQueryRewrite rewrite = null, string name = null) => Wildcard(t => t.Field(field).Value(value).Rewrite(rewrite).Boost(boost).Name(name)); /// /// Matches documents that have fields matching a wildcard expression (not analyzed). /// Supported wildcards are *, which matches any character sequence (including the empty one), and ?, /// which matches any single character. Note this query can be slow, as it needs to iterate over many terms. /// In order to prevent extremely slow wildcard queries, a wildcard term should not start with /// one of the wildcards * or ?. The wildcard query maps to Lucene WildcardQuery. /// public QueryContainer Wildcard(Func, IWildcardQuery> selector) => WrapInContainer(selector, (query, container) => container.Wildcard = query); /// /// Matches documents that have fields containing terms with a specified prefix (not analyzed). /// The prefix query maps to Lucene PrefixQuery. /// public QueryContainer Prefix(Expression> field, string value, double? boost = null, MultiTermQueryRewrite rewrite = null, string name = null ) => Prefix(t => t.Field(field).Value(value).Boost(boost).Rewrite(rewrite).Name(name)); /// /// Matches documents that have fields containing terms with a specified prefix (not analyzed). /// The prefix query maps to Lucene PrefixQuery. /// public QueryContainer Prefix(Field field, string value, double? boost = null, MultiTermQueryRewrite rewrite = null, string name = null) => Prefix(t => t.Field(field).Value(value).Boost(boost).Rewrite(rewrite).Name(name)); /// /// Matches documents that have fields containing terms with a specified prefix (not analyzed). /// The prefix query maps to Lucene PrefixQuery. /// public QueryContainer Prefix(Func, IPrefixQuery> selector) => WrapInContainer(selector, (query, container) => container.Prefix = query); /// /// Matches documents that only have the provided ids. /// Note, this filter does not require the _id field to be indexed since /// it works using the _uid field. /// public QueryContainer Ids(Func selector) => WrapInContainer(selector, (query, container) => container.Ids = query); /// /// Allows fine-grained control over the order and proximity of matching terms. /// Matching rules are constructed from a small set of definitions, /// and the rules are then applied to terms from a particular field. /// The definitions produce sequences of minimal intervals that span terms in a body of text. /// These intervals can be further combined and filtered by parent sources. /// public QueryContainer Intervals(Func, IIntervalsQuery> selector) => WrapInContainer(selector, (query, container) => container.Intervals = query); /// public QueryContainer RankFeature(Func, IRankFeatureQuery> selector) => WrapInContainer(selector, (query, container) => container.RankFeature = query); /// /// Matches spans containing a term. The span term query maps to Lucene SpanTermQuery. /// public QueryContainer SpanTerm(Func, ISpanTermQuery> selector) => WrapInContainer(selector, (query, container) => container.SpanTerm = query); /// /// Matches spans near the beginning of a field. The span first query maps to Lucene SpanFirstQuery. /// public QueryContainer SpanFirst(Func, ISpanFirstQuery> selector) => WrapInContainer(selector, (query, container) => container.SpanFirst = query); /// /// Matches spans which are near one another. One can specify slop, the maximum number of /// intervening unmatched positions, as well as whether matches are required to be in-order. /// The span near query maps to Lucene SpanNearQuery. /// public QueryContainer SpanNear(Func, ISpanNearQuery> selector) => WrapInContainer(selector, (query, container) => container.SpanNear = query); /// /// Matches the union of its span clauses. /// The span or query maps to Lucene SpanOrQuery. /// public QueryContainer SpanOr(Func, ISpanOrQuery> selector) => WrapInContainer(selector, (query, container) => container.SpanOr = query); /// /// Removes matches which overlap with another span query. /// The span not query maps to Lucene SpanNotQuery. /// public QueryContainer SpanNot(Func, ISpanNotQuery> selector) => WrapInContainer(selector, (query, container) => container.SpanNot = query); /// /// Wrap a multi term query (one of fuzzy, prefix, term range or regexp query) /// as a span query so it can be nested. /// public QueryContainer SpanMultiTerm(Func, ISpanMultiTermQuery> selector) => WrapInContainer(selector, (query, container) => container.SpanMultiTerm = query); /// /// Returns matches which enclose another span query. /// The span containing query maps to Lucene SpanContainingQuery /// public QueryContainer SpanContaining(Func, ISpanContainingQuery> selector) => WrapInContainer(selector, (query, container) => container.SpanContaining = query); /// /// Returns Matches which are enclosed inside another span query. /// The span within query maps to Lucene SpanWithinQuery /// public QueryContainer SpanWithin(Func, ISpanWithinQuery> selector) => WrapInContainer(selector, (query, container) => container.SpanWithin = query); /// /// Wraps span queries to allow them to participate in composite single-field Span queries by 'lying' about their search field. /// That is, the masked span query will function as normal, but the field points back to the set field of the query. /// This can be used to support queries like SpanNearQuery or SpanOrQuery across different fields, /// which is not ordinarily permitted. /// public QueryContainer SpanFieldMasking(Func, ISpanFieldMaskingQuery> selector) => WrapInContainer(selector, (query, container) => container.SpanFieldMasking = query); /// /// Allows you to use regular expression term queries. /// "term queries" means that OpenSearch will apply the regexp to the terms produced /// by the tokenizer for that field, and not to the original text of the field. /// public QueryContainer Regexp(Func, IRegexpQuery> selector) => WrapInContainer(selector, (query, container) => container.Regexp = query); /// /// The function_score query allows you to modify the score of documents that are retrieved by a query. /// This can be useful if, for example, a score function is computationally expensive and it is /// sufficient to compute the score on a filtered set of documents. /// /// public QueryContainer FunctionScore(Func, IFunctionScoreQuery> selector) => WrapInContainer(selector, (query, container) => container.FunctionScore = query); public QueryContainer Script(Func, IScriptQuery> selector) => WrapInContainer(selector, (query, container) => container.Script = query); public QueryContainer ScriptScore(Func, IScriptScoreQuery> selector) => WrapInContainer(selector, (query, container) => container.ScriptScore = query); public QueryContainer Exists(Func, IExistsQuery> selector) => WrapInContainer(selector, (query, container) => container.Exists = query); /// /// Used to match queries stored in an index. /// The percolate query itself contains the document that will be used as query /// to match with the stored queries. /// public QueryContainer Percolate(Func, IPercolateQuery> selector) => WrapInContainer(selector, (query, container) => container.Percolate = query); /// /// Used to find child documents which belong to a particular parent. /// public QueryContainer ParentId(Func, IParentIdQuery> selector) => WrapInContainer(selector, (query, container) => container.ParentId = query); /// /// Returns any documents that match with at least one or more of the provided terms. /// The terms are not analyzed and thus must match exactly. The number of terms that must match varies /// per document and is either controlled by a minimum should match field or /// computed per document in a minimum should match script. /// public QueryContainer TermsSet(Func, ITermsSetQuery> selector) => WrapInContainer(selector, (query, container) => container.TermsSet = query); } }