1 //---------------------------------------------------------------------
2 // <copyright file="ScalarRestriction.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
8 //---------------------------------------------------------------------
10 using System.Collections.Generic;
11 using System.Data.Common.CommandTrees;
12 using System.Data.Common.CommandTrees.ExpressionBuilder;
13 using System.Data.Common.Utils;
14 using System.Data.Entity;
15 using System.Diagnostics;
19 namespace System.Data.Mapping.ViewGeneration.Structures
21 using DomainBoolExpr = System.Data.Common.Utils.Boolean.BoolExpr<System.Data.Common.Utils.Boolean.DomainConstraint<BoolLiteral, Constant>>;
24 /// A class that denotes the boolean expression: "scalarVar in values".
25 /// See the comments in <see cref="MemberRestriction"/> for complete and incomplete restriction objects.
27 internal class ScalarRestriction : MemberRestriction
31 /// Creates a scalar member restriction with the meaning "<paramref name="member"/> = <paramref name="value"/>".
32 /// This constructor is used for creating discriminator type conditions.
34 internal ScalarRestriction(MemberPath member, Constant value)
35 : base(new MemberProjectedSlot(member), value)
37 Debug.Assert(value is ScalarConstant || value.IsNull() || value.IsNotNull(), "value is expected to be ScalarConstant, NULL, or NOT_NULL.");
41 /// Creates a scalar member restriction with the meaning "<paramref name="member"/> in <paramref name="values"/>".
43 internal ScalarRestriction(MemberPath member, IEnumerable<Constant> values, IEnumerable<Constant> possibleValues)
44 : base(new MemberProjectedSlot(member), values, possibleValues)
48 /// Creates a scalar member restriction with the meaning "<paramref name="slot"/> in <paramref name="domain"/>".
50 internal ScalarRestriction(MemberProjectedSlot slot, Domain domain)
57 /// Fixes the range of the restriction in accordance with <paramref name="range"/>.
58 /// Member restriction must be complete for this operation.
60 internal override DomainBoolExpr FixRange(Set<Constant> range, MemberDomainMap memberDomainMap)
62 Debug.Assert(IsComplete, "Ranges are fixed only for complete scalar restrictions.");
63 IEnumerable<Constant> newPossibleValues = memberDomainMap.GetDomain(RestrictedMemberSlot.MemberPath);
64 BoolLiteral newLiteral = new ScalarRestriction(RestrictedMemberSlot, new Domain(range, newPossibleValues));
65 return newLiteral.GetDomainBoolExpression(memberDomainMap);
68 internal override BoolLiteral RemapBool(Dictionary<MemberPath, MemberPath> remap)
70 MemberProjectedSlot newVar = (MemberProjectedSlot)this.RestrictedMemberSlot.RemapSlot(remap);
71 return new ScalarRestriction(newVar, this.Domain);
74 internal override MemberRestriction CreateCompleteMemberRestriction(IEnumerable<Constant> possibleValues)
76 Debug.Assert(!this.IsComplete, "CreateCompleteMemberRestriction must be called only for incomplete restrictions.");
77 return new ScalarRestriction(this.RestrictedMemberSlot, new Domain(this.Domain.Values, possibleValues));
80 internal override StringBuilder AsEsql(StringBuilder builder, string blockAlias, bool skipIsNotNull)
82 return ToStringHelper(builder, blockAlias, skipIsNotNull, false);
85 internal override DbExpression AsCqt(DbExpression row, bool skipIsNotNull)
87 DbExpression cqt = null;
90 // negatedConstantAsCql action
91 (negated, domainValues) =>
93 Debug.Assert(cqt == null, "unexpected construction order - cqt must be null");
94 cqt = negated.AsCqt(row, domainValues, this.RestrictedMemberSlot.MemberPath, skipIsNotNull);
99 Debug.Assert(cqt == null, "unexpected construction order - cqt must be null");
100 Debug.Assert(domainValues.Count > 0, "domain must not be empty");
101 cqt = this.RestrictedMemberSlot.MemberPath.AsCqt(row);
102 if (domainValues.Count == 1)
105 cqt = cqt.Equal(domainValues.Single().AsCqt(row, this.RestrictedMemberSlot.MemberPath));
109 // Multiple values: build list of var = c1, var = c2, ..., then OR them all.
110 List<DbExpression> operands = domainValues.Select(c => (DbExpression)cqt.Equal(c.AsCqt(row, this.RestrictedMemberSlot.MemberPath))).ToList();
111 cqt = Helpers.BuildBalancedTreeInPlace(operands, (prev, next) => prev.Or(next));
114 // varIsNotNull action
117 // ( ... AND var IS NOT NULL)
118 DbExpression varIsNotNull = this.RestrictedMemberSlot.MemberPath.AsCqt(row).IsNull().Not();
119 cqt = cqt != null ? cqt.And(varIsNotNull) : varIsNotNull;
124 // (var IS NULL OR ...)
125 DbExpression varIsNull = this.RestrictedMemberSlot.MemberPath.AsCqt(row).IsNull();
126 cqt = cqt != null ? varIsNull.Or(cqt) : varIsNull;
133 internal override StringBuilder AsUserString(StringBuilder builder, string blockAlias, bool skipIsNotNull)
135 return ToStringHelper(builder, blockAlias, skipIsNotNull, true);
139 /// Common code for <see cref="AsEsql"/> and <see cref="AsUserString"/> methods.
141 private StringBuilder ToStringHelper(StringBuilder inputBuilder, string blockAlias, bool skipIsNotNull, bool userString)
143 // Due to the varIsNotNull and varIsNull actions, we cannot build incrementally.
144 // So we use a local StringBuilder - it should not be that inefficient (one extra copy).
145 StringBuilder builder = new StringBuilder();
148 // negatedConstantAsCql action
149 (negated, domainValues) =>
153 negated.AsUserString(builder, blockAlias, domainValues, RestrictedMemberSlot.MemberPath, skipIsNotNull);
157 negated.AsEsql(builder, blockAlias, domainValues, RestrictedMemberSlot.MemberPath, skipIsNotNull);
160 // varInDomain action
163 Debug.Assert(domainValues.Count > 0, "domain must not be empty");
164 this.RestrictedMemberSlot.MemberPath.AsEsql(builder, blockAlias);
165 if (domainValues.Count == 1)
168 builder.Append(" = ");
171 domainValues.Single().ToCompactString(builder);
175 domainValues.Single().AsEsql(builder, RestrictedMemberSlot.MemberPath, blockAlias);
181 builder.Append(" IN {");
183 foreach (Constant constant in domainValues)
187 builder.Append(", ");
191 constant.ToCompactString(builder);
195 constant.AsEsql(builder, RestrictedMemberSlot.MemberPath, blockAlias);
202 // varIsNotNull action
205 // (leftExpr AND var IS NOT NULL)
206 bool leftExprEmpty = builder.Length == 0;
207 builder.Insert(0, '(');
210 builder.Append(" AND ");
214 this.RestrictedMemberSlot.MemberPath.ToCompactString(builder, Strings.ViewGen_EntityInstanceToken);
215 builder.Append(" is not NULL)"); // plus the closing bracket
219 this.RestrictedMemberSlot.MemberPath.AsEsql(builder, blockAlias);
220 builder.Append(" IS NOT NULL)"); // plus the closing bracket
226 // (var IS NULL OR rightExpr)
227 bool rightExprEmpty = builder.Length == 0;
228 StringBuilder varIsNullBuilder = new StringBuilder();
231 varIsNullBuilder.Append('(');
235 this.RestrictedMemberSlot.MemberPath.ToCompactString(varIsNullBuilder, blockAlias);
236 varIsNullBuilder.Append(" is NULL");
240 this.RestrictedMemberSlot.MemberPath.AsEsql(varIsNullBuilder, blockAlias);
241 varIsNullBuilder.Append(" IS NULL");
245 varIsNullBuilder.Append(" OR ");
247 builder.Insert(0, varIsNullBuilder.ToString());
255 inputBuilder.Append(builder.ToString());
260 Action<NegatedConstant, IEnumerable<Constant>> negatedConstantAsCql,
261 Action<Set<Constant>> varInDomain,
266 Debug.Assert(this.RestrictedMemberSlot.MemberPath.IsScalarType(), "Expected scalar.");
268 // If domain values contain a negated constant, delegate Cql generation into that constant.
269 Debug.Assert(this.Domain.Values.Count(c => c is NegatedConstant) <= 1, "Multiple negated constants?");
270 NegatedConstant negated = (NegatedConstant)this.Domain.Values.FirstOrDefault(c => c is NegatedConstant);
273 negatedConstantAsCql(negated, this.Domain.Values);
275 else // We have only positive constants.
277 // 1. Generate "var in domain"
278 // 2. If var is not nullable, append "... and var is not null".
279 // This is needed for boolean _from variables that must never evaluate to null because view generation assumes 2-valued boolean logic.
280 // 3. If domain contains null, prepend "var is null or ...".
282 // A complete generation pattern:
283 // (var is null or ( var in domain and var is not null))
284 // ^^^^^^^^^^^^^^ ^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^
285 // generated by #3 generated by #1 generated by #2
287 // Copy the domain values for simplification changes.
288 Set<Constant> domainValues = new Set<Constant>(this.Domain.Values, Constant.EqualityComparer);
290 bool includeNull = false;
291 if (domainValues.Contains(Constant.Null))
294 domainValues.Remove(Constant.Null);
297 // Constraint counter-example could contain undefined cellconstant. E.g for booleans (for int its optimized out due to negated constants)
298 // we want to treat undefined as nulls.
299 if (domainValues.Contains(Constant.Undefined))
302 domainValues.Remove(Constant.Undefined);
305 bool excludeNull = !skipIsNotNull && this.RestrictedMemberSlot.MemberPath.IsNullable;
307 Debug.Assert(!includeNull || !excludeNull, "includeNull and excludeNull can't be true at the same time.");
309 // #1: Generate "var in domain"
310 if (domainValues.Count > 0)
312 varInDomain(domainValues);
315 // #2: Append "... and var is not null".
321 // #3: Prepend "var is null or ...".
330 #region String methods
331 internal override void ToCompactString(StringBuilder builder)
333 RestrictedMemberSlot.ToCompactString(builder);
334 builder.Append(" IN (");
335 StringUtil.ToCommaSeparatedStringSorted(builder, Domain.Values);