/// <summary> /// Add a Select clause to a query; may need to create a new subquery. /// </summary> /// <param name="select">Select clause to add.</param> /// <param name="inScope">Set of parameter names in scope.</param> /// <param name="currentCollection">Current input collection.</param> /// <param name="subqueryBinding">The subquery binding information.</param> /// <returns>A new query containing a select clause.</returns> public QueryUnderConstruction AddSelectClause(SqlSelectClause select, HashSet <ParameterExpression> inScope, Collection currentCollection, TranslationContext.SubqueryBinding subqueryBinding) { QueryUnderConstruction result = this; if (subqueryBinding.ShouldBeOnNewQuery) { result = this.PackageQuery(inScope, currentCollection); subqueryBinding.ShouldBeOnNewQuery = false; } // If result SelectClause is not null, or both result selectClause and select has Distinct // then it is unexpected since the SelectClause will be overwritten. if (!((result.selectClause != null && result.selectClause.HasDistinct && selectClause.HasDistinct) || result.selectClause == null)) { throw new DocumentQueryException("Internal error: attempting to overwrite SELECT clause"); } result.selectClause = select; foreach (Binding binding in subqueryBinding.TakeBindings()) { result.AddBinding(binding); } return(result); }
public QueryUnderConstruction(Func <string, ParameterExpression> aliasCreatorFunc, QueryUnderConstruction inputQuery) { this.fromParameters = new FromParameterBindings(); this.aliasCreatorFunc = aliasCreatorFunc; this.inputQuery = inputQuery; this.alias = new Lazy <ParameterExpression>(() => aliasCreatorFunc(QueryUnderConstruction.DefaultSubqueryRoot)); }
/// <summary> /// Create a new QueryUnderConstruction node that take the current query as its input /// </summary> /// <param name="inScope">The current context's parameters scope</param> /// <returns>The new query node</returns> public QueryUnderConstruction PackageQuery(HashSet <ParameterExpression> inScope) { QueryUnderConstruction result = new QueryUnderConstruction(this.aliasCreatorFunc); result.fromParameters.SetInputParameter(typeof(object), this.Alias.Name, inScope); result.inputQuery = this; return(result); }
public TranslationContext() { this.InScope = new HashSet <ParameterExpression>(); this.substitutions = new ParameterSubstitution(); this.methodStack = new List <MethodCallExpression>(); this.lambdaParametersStack = new List <ParameterExpression>(); this.collectionStack = new List <Collection>(); this.currentQuery = new QueryUnderConstruction(); }
/// <summary> /// Create a new QueryUnderConstruction node if indicated as neccesary by the subquery binding /// </summary> /// <returns>The current QueryUnderConstruction after the package query call if necessary</returns> public QueryUnderConstruction PackageCurrentQueryIfNeccessary() { if (this.CurrentSubqueryBinding.ShouldBeOnNewQuery) { this.currentQuery = this.currentQuery.PackageQuery(this.InScope); this.CurrentSubqueryBinding.ShouldBeOnNewQuery = false; } return(this.currentQuery); }
/// <summary> /// Verify that the current query is supported with non-subquery. /// As soon as the query has a TopSpec then after that, it cannot have a Distinct, OrderBy, or Where clause. /// That is because in those cases, the semantics of LINQ is Take() should be done first, whereas, due to the /// nature of SQL language, there is no equivalent in SQL as TOP clause in SQL is executed last. /// This limit will be lifted when we have the support sub query in LINQ translation. /// </summary> /// <param name="query"></param> private static void ValidateNonSubquerySupport(QueryUnderConstruction query) { while (query != null) { if (query.topSpec != null) { throw new DocumentQueryException("LINQ operations after a Take() is not supported yet."); } query = query.inputQuery; } }
public QueryUnderConstruction AddOrderByClause(SqlOrderbyClause orderBy, TranslationContext context) { QueryUnderConstruction result = context.PackageCurrentQueryIfNeccessary(); result.orderByClause = orderBy; foreach (Binding binding in context.CurrentSubqueryBinding.TakeBindings()) { result.AddBinding(binding); } return(result); }
/// <summary> /// Add a Where clause to a query; may need to create a new query. /// </summary> /// <param name="whereClause">Clause to add.</param> /// <param name="context">The translation context.</param> /// <returns>A new query containing the specified Where clause.</returns> public QueryUnderConstruction AddWhereClause(SqlWhereClause whereClause, TranslationContext context) { QueryUnderConstruction result = context.PackageCurrentQueryIfNeccessary(); whereClause = QueryUnderConstruction.CombineWithConjunction(result.whereClause, whereClause); result.whereClause = whereClause; foreach (Binding binding in context.CurrentSubqueryBinding.TakeBindings()) { result.AddBinding(binding); } return(result); }
public TranslationContext(CosmosLinqSerializerOptions linqSerializerOptions, IDictionary <object, string> parameters = null) { this.InScope = new HashSet <ParameterExpression>(); this.substitutions = new ParameterSubstitution(); this.methodStack = new List <MethodCallExpression>(); this.lambdaParametersStack = new List <ParameterExpression>(); this.collectionStack = new List <Collection>(); this.currentQuery = new QueryUnderConstruction(this.GetGenFreshParameterFunc()); this.subqueryBindingStack = new Stack <SubqueryBinding>(); this.linqSerializerOptions = linqSerializerOptions; this.parameters = parameters; this.memberNames = new MemberNames(linqSerializerOptions); }
/// <summary> /// Separate out the query branch, which makes up a subquery and is built on top of the parent query chain. /// E.g. Let the query chain at some point in time be q0 - q1 - q2. When a subquery is recognized, its expression is visited. /// Assume that adds 2 queries to the chain to q0 - q1 - q2 - q3 - q4. Invoking q4.GetSubquery(q2) would return q3 - q4 /// after it's isolated from the rest of the chain. /// </summary> /// <param name="queryBeforeVisit">The last query in the chain before the collection expression is visited</param> /// <returns>The subquery that has been decoupled from the parent query chain</returns> public QueryUnderConstruction GetSubquery(QueryUnderConstruction queryBeforeVisit) { QueryUnderConstruction parentQuery = null; for (QueryUnderConstruction query = this; query != queryBeforeVisit; query = query.inputQuery) { parentQuery = query; } parentQuery.inputQuery = null; return(this); }
/// <summary> /// Add a Where clause to a query; may need to create a new query. /// </summary> /// <param name="whereClause">Clause to add.</param> /// <param name="elementType">Type of element in input collection.</param> /// <param name="inScope">Set of parameter names in scope.</param> /// <returns>A new query containing the specified Where clause.</returns> public QueryUnderConstruction AddWhereClause(SqlWhereClause whereClause, Type elementType, HashSet <ParameterExpression> inScope) { QueryUnderConstruction.ValidateNonSubquerySupport(this); QueryUnderConstruction result = this; if (this.selectClause != null) { result = this.PackageQuery(inScope); } whereClause = QueryUnderConstruction.CombineWithConjunction(result.whereClause, whereClause); result.whereClause = whereClause; return(result); }
public QueryUnderConstruction AddLimitSpec(SqlLimitSpec limitSpec, TranslationContext context) { QueryUnderConstruction result = this; if (result.limitSpec != null) { result.limitSpec = (result.limitSpec.Limit < limitSpec.Limit) ? result.limitSpec : limitSpec; } else { result.limitSpec = limitSpec; } return(result); }
/// <summary> /// Find and flatten the prefix set of queries into a single query by substituting their expressions. /// </summary> /// <returns>The query that has been flatten</returns> public QueryUnderConstruction FlattenAsPossible() { // Flatten should be done when the current query can be translated without the need of using sub query // The cases that need to use sub query are: // 1. Select clause appears after Distinct // 2. There are any operations after Take // 3. There are nested Select, Where or OrderBy QueryUnderConstruction parentQuery = null; QueryUnderConstruction flattenQuery = null; bool seenSelect = false; bool seenAnyOp = false; for (QueryUnderConstruction query = this; query != null; query = query.inputQuery) { foreach (Binding binding in query.fromParameters.GetBindings()) { if ((binding.ParameterDefinition != null) && (binding.ParameterDefinition is SqlSubqueryCollection)) { flattenQuery = this; break; } } if (flattenQuery != null) { break; } if ((query.topSpec != null && seenAnyOp) || (query.selectClause != null && query.selectClause.HasDistinct && seenSelect)) { parentQuery.inputQuery = query.FlattenAsPossible(); flattenQuery = this; break; } seenSelect = seenSelect || ((query.selectClause != null) && !(query.selectClause.HasDistinct)); seenAnyOp = true; parentQuery = query; } if (flattenQuery == null) { flattenQuery = this.Flatten(); } return(flattenQuery); }
public QueryUnderConstruction AddOffsetSpec(SqlOffsetSpec offsetSpec, TranslationContext context) { QueryUnderConstruction result = context.PackageCurrentQueryIfNeccessary(); if (result.offsetSpec != null) { // Skip(A).Skip(B) => Skip(A + B) result.offsetSpec = SqlOffsetSpec.Create(result.offsetSpec.Offset + offsetSpec.Offset); } else { result.offsetSpec = offsetSpec; } return(result); }
private QueryUnderConstruction PackageQuery(HashSet <ParameterExpression> inScope) { QueryUnderConstruction result = new QueryUnderConstruction(); var inputParam = result.fromParameters.SetInputParameter(typeof(object), ParameterSubstitution.InputParameterName, inScope); inScope.Add(inputParam); result.inputQuery = this; if (this.selectClause == null) { var star = new SqlSelectStarSpec(); this.selectClause = new SqlSelectClause(star, null); } return(result); }
public QueryUnderConstruction AddTopSpec(SqlTopSpec topSpec) { QueryUnderConstruction result = this; if (result.topSpec != null) { // Set the topSpec to the one with minimum Count value result.topSpec = (result.topSpec.Count < topSpec.Count) ? result.topSpec : topSpec; } else { result.topSpec = topSpec; } return(result); }
/// <summary> /// Flatten subqueries into a single query by substituting their expressions in the current query. /// </summary> /// <returns>A flattened query.</returns> public QueryUnderConstruction Flatten() { // SELECT fo(y) FROM y IN (SELECT fi(x) FROM x WHERE gi(x)) WHERE go(y) // is translated by substituting fi(x) for y in the outer query // producing // SELECT fo(fi(x)) FROM x WHERE gi(x) AND (go(fi(x)) if (this.inputQuery == null) { // we are flat already if (this.selectClause == null) { this.selectClause = new SqlSelectClause(new SqlSelectStarSpec(), this.topSpec); } else { this.selectClause = new SqlSelectClause(this.selectClause.SelectSpec, this.topSpec, this.selectClause.HasDistinct); } return(this); } if (this.inputQuery.orderByClause != null && this.orderByClause != null) { throw new DocumentQueryException("Multiple OrderBy is not supported."); } var flatInput = this.inputQuery.Flatten(); var inputSelect = flatInput.selectClause; var inputwhere = flatInput.whereClause; SqlIdentifier replacement = new SqlIdentifier(this.InputParameter().Name); var composedSelect = Substitute(inputSelect, inputSelect.TopSpec ?? this.topSpec, replacement, this.selectClause); var composedWhere = Substitute(inputSelect.SelectSpec, replacement, this.whereClause); var composedOrderBy = Substitute(inputSelect.SelectSpec, replacement, this.orderByClause); var and = QueryUnderConstruction.CombineWithConjunction(inputwhere, composedWhere); QueryUnderConstruction result = new QueryUnderConstruction { selectClause = composedSelect, whereClause = and, inputQuery = null, fromParameters = flatInput.fromParameters, orderByClause = composedOrderBy ?? this.inputQuery.orderByClause, }; return(result); }
public QueryUnderConstruction AddOrderByClause(SqlOrderbyClause orderBy, TranslationContext context) { QueryUnderConstruction result = this; if (context.CurrentSubqueryBinding.ShouldBeOnNewQuery) { result = this.PackageQuery(context.InScope, context.PeekCollection()); context.CurrentSubqueryBinding.ShouldBeOnNewQuery = false; } result.orderByClause = orderBy; foreach (Binding binding in context.CurrentSubqueryBinding.TakeBindings()) { result.AddBinding(binding); } return(result); }
public QueryUnderConstruction AddOffsetSpec(SqlOffsetSpec offsetSpec, TranslationContext context) { QueryUnderConstruction result = context.PackageCurrentQueryIfNeccessary(); if (result.offsetSpec != null) { // Skip(A).Skip(B) => Skip(A + B) long offsetA = QueryUnderConstruction.GetOffsetCount(result.offsetSpec); long offsetB = QueryUnderConstruction.GetOffsetCount(offsetSpec); result.offsetSpec = SqlOffsetSpec.Create(SqlNumberLiteral.Create(offsetA + offsetB)); } else { result.offsetSpec = offsetSpec; } return(result); }
/// <summary> /// Add a Where clause to a query; may need to create a new query. /// </summary> /// <param name="whereClause">Clause to add.</param> /// <param name="context">The translation context.</param> /// <returns>A new query containing the specified Where clause.</returns> public QueryUnderConstruction AddWhereClause(SqlWhereClause whereClause, TranslationContext context) { QueryUnderConstruction result = this; if (this.ShouldBeOnNewQuery(LinqMethods.Where, 2)) { result = this.PackageQuery(context.InScope, context.PeekCollection()); context.CurrentSubqueryBinding.ShouldBeOnNewQuery = false; } whereClause = QueryUnderConstruction.CombineWithConjunction(result.whereClause, whereClause); result.whereClause = whereClause; foreach (Binding binding in context.CurrentSubqueryBinding.TakeBindings()) { result.AddBinding(binding); } return(result); }
public QueryUnderConstruction AddOrderByClause(SqlOrderbyClause orderBy, HashSet <ParameterExpression> inScope) { QueryUnderConstruction.ValidateNonSubquerySupport(this); QueryUnderConstruction result = this; if (this.selectClause != null) { result = this.PackageQuery(inScope); } if (result.orderByClause != null) { throw new DocumentQueryException("Multiple OrderBy is not supported."); } result.orderByClause = orderBy; return(result); }
public QueryUnderConstruction AddTopSpec(SqlTopSpec topSpec) { QueryUnderConstruction result = this; if (result.topSpec != null) { long accumulatedTopCount = QueryUnderConstruction.GetTopCount(result.topSpec); long currentTopCount = QueryUnderConstruction.GetTopCount(topSpec); if (currentTopCount < accumulatedTopCount) { result.topSpec = topSpec; } } else { result.topSpec = topSpec; } return(result); }
public QueryUnderConstruction AddTopSpec(SqlTopSpec topSpec, HashSet <ParameterExpression> inScope, Collection currentCollection) { QueryUnderConstruction result = this; if (result.topSpec != null) { // Set the topSpec to the one with minimum Count value result.topSpec = (this.topSpec.Count < topSpec.Count) ? this.topSpec : topSpec; } else { result.topSpec = topSpec; } if (result.topSpec.Count < 0) { result.topSpec = SqlTopSpec.Create(0); } return(result); }
public QueryUnderConstruction AddLimitSpec(SqlLimitSpec limitSpec, TranslationContext context) { QueryUnderConstruction result = this; if (result.limitSpec != null) { long accumulatedLimitCount = QueryUnderConstruction.GetLimitCount(result.limitSpec); long currentLimitCount = QueryUnderConstruction.GetLimitCount(limitSpec); if (currentLimitCount < accumulatedLimitCount) { result.limitSpec = limitSpec; } } else { result.limitSpec = limitSpec; } return(result); }
/// <summary> /// Add a Select clause to a query; may need to create a new subquery. /// </summary> /// <param name="select">Select clause to add.</param> /// <param name="context">The translation context.</param> /// <returns>A new query containing a select clause.</returns> public QueryUnderConstruction AddSelectClause(SqlSelectClause select, TranslationContext context) { QueryUnderConstruction result = context.PackageCurrentQueryIfNeccessary(); // If result SelectClause is not null, or both result selectClause and select has Distinct // then it is unexpected since the SelectClause will be overwritten. if (!((result.selectClause != null && result.selectClause.HasDistinct && selectClause.HasDistinct) || result.selectClause == null)) { throw new DocumentQueryException("Internal error: attempting to overwrite SELECT clause"); } result.selectClause = select; foreach (Binding binding in context.CurrentSubqueryBinding.TakeBindings()) { result.AddBinding(binding); } return(result); }
/// <summary> /// Add a Select clause to a query; may need to create a new subquery. /// </summary> /// <param name="select">Select clause to add.</param> /// <param name="inputElementType">Type of element in the input collection.</param> /// <param name="outputElementType">Type of element in output collection.</param> /// <param name="inScope">Set of parameter names in scope.</param> /// <returns>A new query containing a select clause.</returns> public QueryUnderConstruction AddSelectClause(SqlSelectClause select, Type inputElementType, Type outputElementType, HashSet <ParameterExpression> inScope) { if (select.HasDistinct) { QueryUnderConstruction.ValidateNonSubquerySupport(this); } QueryUnderConstruction result = this; if (this.selectClause != null) { result = this.PackageQuery(inScope); } if (result.selectClause != null) { throw new DocumentQueryException("Internal error: attempting to overwrite SELECT clause"); } result.selectClause = select; return(result); }
/// <summary> /// Find and flatten the prefix set of queries into a single query by substituting their expressions. /// </summary> /// <returns>The query that has been flatten</returns> public QueryUnderConstruction FlattenAsPossible() { // Flatten should be done when the current query can be translated without the need of using sub query // The cases that need to use sub query are: // 1. Select clause appears after Distinct // 2. There are any operations after Take that is not a pure Select. // 3. There are nested Select, Where or OrderBy QueryUnderConstruction parentQuery = null; QueryUnderConstruction flattenQuery = null; bool seenSelect = false; bool seenAnyNonSelectOp = false; for (QueryUnderConstruction query = this; query != null; query = query.inputQuery) { foreach (Binding binding in query.fromParameters.GetBindings()) { if ((binding.ParameterDefinition != null) && (binding.ParameterDefinition is SqlSubqueryCollection)) { flattenQuery = this; break; } } // In Select -> SelectMany cases, fromParameter substitution is not yet supported . // Therefore these are un-flattenable. if (query.inputQuery != null && (query.fromParameters.GetBindings().First().Parameter.Name == query.inputQuery.Alias.Name) && query.fromParameters.GetBindings().Any(b => b.ParameterDefinition != null)) { flattenQuery = this; break; } if (flattenQuery != null) { break; } if (((query.topSpec != null || query.offsetSpec != null || query.limitSpec != null) && seenAnyNonSelectOp) || (query.selectClause != null && query.selectClause.HasDistinct && seenSelect)) { parentQuery.inputQuery = query.FlattenAsPossible(); flattenQuery = this; break; } seenSelect = seenSelect || ((query.selectClause != null) && !(query.selectClause.HasDistinct)); seenAnyNonSelectOp |= (query.whereClause != null) || (query.orderByClause != null) || (query.topSpec != null) || (query.offsetSpec != null) || (query.fromParameters.GetBindings().Any(b => b.ParameterDefinition != null)) || ((query.selectClause != null) && ((query.selectClause.HasDistinct) || (this.HasSelectAggregate()))); parentQuery = query; } if (flattenQuery == null) { flattenQuery = this.Flatten(); } return(flattenQuery); }
/// <summary> /// Flatten subqueries into a single query by substituting their expressions in the current query. /// </summary> /// <returns>A flattened query.</returns> private QueryUnderConstruction Flatten() { // SELECT fo(y) FROM y IN (SELECT fi(x) FROM x WHERE gi(x)) WHERE go(y) // is translated by substituting fi(x) for y in the outer query // producing // SELECT fo(fi(x)) FROM x WHERE gi(x) AND (go(fi(x)) if (this.inputQuery == null) { // we are flat already if (this.selectClause == null) { // If selectClause doesn't exists, use SELECT v0 where v0 is the input parameter, instead of SELECT *. string parameterName = this.fromParameters.GetInputParameter().Name; SqlScalarExpression parameterExpression = SqlPropertyRefScalarExpression.Create(null, SqlIdentifier.Create(parameterName)); this.selectClause = SqlSelectClause.Create(SqlSelectValueSpec.Create(parameterExpression)); } else { this.selectClause = SqlSelectClause.Create(this.selectClause.SelectSpec, this.topSpec, this.selectClause.HasDistinct); } return(this); } QueryUnderConstruction flatInput = this.inputQuery.Flatten(); SqlSelectClause inputSelect = flatInput.selectClause; SqlWhereClause inputwhere = flatInput.whereClause; // Determine the paramName to be replaced in the current query // It should be the top input parameter name which is not binded in this collection. // That is because if it has been binded before, it has global scope and should not be replaced. string paramName = null; HashSet <string> inputQueryParams = new HashSet <string>(); foreach (Binding binding in this.inputQuery.fromParameters.GetBindings()) { inputQueryParams.Add(binding.Parameter.Name); } foreach (Binding binding in this.fromParameters.GetBindings()) { if (binding.ParameterDefinition == null || inputQueryParams.Contains(binding.Parameter.Name)) { paramName = binding.Parameter.Name; } } SqlIdentifier replacement = SqlIdentifier.Create(paramName); SqlSelectClause composedSelect = Substitute(inputSelect, inputSelect.TopSpec ?? this.topSpec, replacement, this.selectClause); SqlWhereClause composedWhere = Substitute(inputSelect.SelectSpec, replacement, this.whereClause); SqlOrderbyClause composedOrderBy = Substitute(inputSelect.SelectSpec, replacement, this.orderByClause); SqlWhereClause and = QueryUnderConstruction.CombineWithConjunction(inputwhere, composedWhere); FromParameterBindings fromParams = QueryUnderConstruction.CombineInputParameters(flatInput.fromParameters, this.fromParameters); SqlOffsetSpec offsetSpec; SqlLimitSpec limitSpec; if (flatInput.offsetSpec != null) { offsetSpec = flatInput.offsetSpec; limitSpec = flatInput.limitSpec; } else { offsetSpec = this.offsetSpec; limitSpec = this.limitSpec; } QueryUnderConstruction result = new QueryUnderConstruction(this.aliasCreatorFunc) { selectClause = composedSelect, whereClause = and, inputQuery = null, fromParameters = flatInput.fromParameters, orderByClause = composedOrderBy ?? this.inputQuery.orderByClause, offsetSpec = offsetSpec, limitSpec = limitSpec, alias = new Lazy <ParameterExpression>(() => this.Alias) }; return(result); }