Update Reference Sources to .NET Framework 4.6.1
[mono.git] / mcs / class / referencesource / System.Data.Entity / System / Data / Mapping / ViewGeneration / Structures / ScalarRestriction.cs
1 //---------------------------------------------------------------------
2 // <copyright file="ScalarRestriction.cs" company="Microsoft">
3 //      Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //
6 // @owner [....]
7 // @backupOwner [....]
8 //---------------------------------------------------------------------
9
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;
16 using System.Linq;
17 using System.Text;
18
19 namespace System.Data.Mapping.ViewGeneration.Structures
20 {
21     using DomainBoolExpr    = System.Data.Common.Utils.Boolean.BoolExpr<System.Data.Common.Utils.Boolean.DomainConstraint<BoolLiteral, Constant>>;
22
23     /// <summary>
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.
26     /// </summary>
27     internal class ScalarRestriction : MemberRestriction
28     {
29         #region Constructors
30         /// <summary>
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.
33         /// </summary>
34         internal ScalarRestriction(MemberPath member, Constant value)
35             : base(new MemberProjectedSlot(member), value)
36         {
37             Debug.Assert(value is ScalarConstant || value.IsNull() || value.IsNotNull(), "value is expected to be ScalarConstant, NULL, or NOT_NULL.");
38         }
39
40         /// <summary>
41         /// Creates a scalar member restriction with the meaning "<paramref name="member"/> in <paramref name="values"/>".
42         /// </summary>
43         internal ScalarRestriction(MemberPath member, IEnumerable<Constant> values, IEnumerable<Constant> possibleValues)
44             : base(new MemberProjectedSlot(member), values, possibleValues)
45         { }
46
47         /// <summary>
48         /// Creates a scalar member restriction with the meaning "<paramref name="slot"/> in <paramref name="domain"/>".
49         /// </summary>
50         internal ScalarRestriction(MemberProjectedSlot slot, Domain domain)
51             : base(slot, domain)
52         { }
53         #endregion
54
55         #region Methods
56         /// <summary>
57         /// Fixes the range of the restriction in accordance with <paramref name="range"/>.
58         /// Member restriction must be complete for this operation. 
59         /// </summary>
60         internal override DomainBoolExpr FixRange(Set<Constant> range, MemberDomainMap memberDomainMap)
61         {
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);
66         }
67
68         internal override BoolLiteral RemapBool(Dictionary<MemberPath, MemberPath> remap)
69         {
70             MemberProjectedSlot newVar = (MemberProjectedSlot)this.RestrictedMemberSlot.RemapSlot(remap);
71             return new ScalarRestriction(newVar, this.Domain);
72         }
73
74         internal override MemberRestriction CreateCompleteMemberRestriction(IEnumerable<Constant> possibleValues)
75         {
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));
78         }
79
80         internal override StringBuilder AsEsql(StringBuilder builder, string blockAlias, bool skipIsNotNull)
81         {
82             return ToStringHelper(builder, blockAlias, skipIsNotNull, false);
83         }
84
85         internal override DbExpression AsCqt(DbExpression row, bool skipIsNotNull)
86         {
87             DbExpression cqt = null;
88
89             AsCql(
90                 // negatedConstantAsCql action
91                 (negated, domainValues) =>
92                 {
93                     Debug.Assert(cqt == null, "unexpected construction order - cqt must be null");
94                     cqt = negated.AsCqt(row, domainValues, this.RestrictedMemberSlot.MemberPath, skipIsNotNull);
95                 },
96                 // varInDomain action
97                 (domainValues) =>
98                 {
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)
103                     {
104                         // Single value
105                         cqt = cqt.Equal(domainValues.Single().AsCqt(row, this.RestrictedMemberSlot.MemberPath));
106                     }
107                     else
108                     {
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));
112                     }
113                 },
114                 // varIsNotNull action
115                 () =>
116                 {
117                     // ( ... AND var IS NOT NULL)
118                     DbExpression varIsNotNull = this.RestrictedMemberSlot.MemberPath.AsCqt(row).IsNull().Not();
119                     cqt = cqt != null ? cqt.And(varIsNotNull) : varIsNotNull;
120                 },
121                 // varIsNull action
122                 () =>
123                 {
124                     // (var IS NULL OR ...)
125                     DbExpression varIsNull = this.RestrictedMemberSlot.MemberPath.AsCqt(row).IsNull();
126                     cqt = cqt != null ? varIsNull.Or(cqt) : varIsNull;
127                 },
128                 skipIsNotNull);
129
130             return cqt;
131         }
132
133         internal override StringBuilder AsUserString(StringBuilder builder, string blockAlias, bool skipIsNotNull)
134         {
135             return ToStringHelper(builder, blockAlias, skipIsNotNull, true);
136         }
137
138         /// <summary>
139         /// Common code for <see cref="AsEsql"/> and <see cref="AsUserString"/> methods.
140         /// </summary>
141         private StringBuilder ToStringHelper(StringBuilder inputBuilder, string blockAlias, bool skipIsNotNull, bool userString)
142         {
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();
146
147             AsCql(
148                 // negatedConstantAsCql action
149                 (negated, domainValues) =>
150                 {
151                     if (userString)
152                     {
153                         negated.AsUserString(builder, blockAlias, domainValues, RestrictedMemberSlot.MemberPath, skipIsNotNull);
154                     }
155                     else
156                     {
157                         negated.AsEsql(builder, blockAlias, domainValues, RestrictedMemberSlot.MemberPath, skipIsNotNull);
158                     }
159                 },
160                 // varInDomain action
161                 (domainValues) =>
162                 {
163                     Debug.Assert(domainValues.Count > 0, "domain must not be empty");
164                     this.RestrictedMemberSlot.MemberPath.AsEsql(builder, blockAlias);
165                     if (domainValues.Count == 1)
166                     {
167                         // Single value
168                         builder.Append(" = ");
169                         if (userString)
170                         {
171                             domainValues.Single().ToCompactString(builder);
172                         }
173                         else
174                         {
175                             domainValues.Single().AsEsql(builder, RestrictedMemberSlot.MemberPath, blockAlias);
176                         }
177                     }
178                     else
179                     {
180                         // Multiple values
181                         builder.Append(" IN {");
182                         bool first = true;
183                         foreach (Constant constant in domainValues)
184                         {
185                             if (!first)
186                             {
187                                 builder.Append(", ");
188                             }
189                             if (userString)
190                             {
191                                 constant.ToCompactString(builder);
192                             }
193                             else
194                             {
195                                 constant.AsEsql(builder, RestrictedMemberSlot.MemberPath, blockAlias);
196                             }
197                             first = false;
198                         }
199                         builder.Append('}');
200                     }
201                 },
202                 // varIsNotNull action
203                 () =>
204                 {
205                     // (leftExpr AND var IS NOT NULL)
206                     bool leftExprEmpty = builder.Length == 0;
207                     builder.Insert(0, '(');
208                     if (!leftExprEmpty)
209                     {
210                         builder.Append(" AND ");
211                     }
212                     if (userString)
213                     {
214                         this.RestrictedMemberSlot.MemberPath.ToCompactString(builder, Strings.ViewGen_EntityInstanceToken);
215                         builder.Append(" is not NULL)"); // plus the closing bracket
216                     }
217                     else
218                     {
219                         this.RestrictedMemberSlot.MemberPath.AsEsql(builder, blockAlias);
220                         builder.Append(" IS NOT NULL)"); // plus the closing bracket
221                     }
222                 },
223                 // varIsNull action
224                 () =>
225                 {
226                     // (var IS NULL OR rightExpr)
227                     bool rightExprEmpty = builder.Length == 0;
228                     StringBuilder varIsNullBuilder = new StringBuilder();
229                     if (!rightExprEmpty)
230                     {
231                         varIsNullBuilder.Append('(');
232                     }
233                     if (userString)
234                     {
235                         this.RestrictedMemberSlot.MemberPath.ToCompactString(varIsNullBuilder, blockAlias);
236                         varIsNullBuilder.Append(" is NULL");
237                     }
238                     else
239                     {
240                         this.RestrictedMemberSlot.MemberPath.AsEsql(varIsNullBuilder, blockAlias);
241                         varIsNullBuilder.Append(" IS NULL");
242                     }
243                     if (!rightExprEmpty)
244                     {
245                         varIsNullBuilder.Append(" OR ");
246                     }
247                     builder.Insert(0, varIsNullBuilder.ToString());
248                     if (!rightExprEmpty)
249                     {
250                         builder.Append(')');
251                     }
252                 },
253                 skipIsNotNull);
254
255             inputBuilder.Append(builder.ToString());
256             return inputBuilder;
257         }
258
259         private void AsCql(
260             Action<NegatedConstant, IEnumerable<Constant>> negatedConstantAsCql,
261             Action<Set<Constant>> varInDomain,
262             Action varIsNotNull,
263             Action varIsNull,
264             bool skipIsNotNull)
265         {
266             Debug.Assert(this.RestrictedMemberSlot.MemberPath.IsScalarType(), "Expected scalar.");
267
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);
271             if (negated != null)
272             {
273                 negatedConstantAsCql(negated, this.Domain.Values);
274             }
275             else // We have only positive constants.
276             {
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 ...".
281                 //
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
286
287                 // Copy the domain values for simplification changes.
288                 Set<Constant> domainValues = new Set<Constant>(this.Domain.Values, Constant.EqualityComparer);
289
290                 bool includeNull = false;
291                 if (domainValues.Contains(Constant.Null))
292                 {
293                     includeNull = true;
294                     domainValues.Remove(Constant.Null);
295                 }
296
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))
300                 {
301                     includeNull = true;
302                     domainValues.Remove(Constant.Undefined);
303                 }
304
305                 bool excludeNull = !skipIsNotNull && this.RestrictedMemberSlot.MemberPath.IsNullable;
306
307                 Debug.Assert(!includeNull || !excludeNull, "includeNull and excludeNull can't be true at the same time.");
308
309                 // #1: Generate "var in domain"
310                 if (domainValues.Count > 0)
311                 {
312                     varInDomain(domainValues);
313                 }
314
315                 // #2: Append "... and var is not null".
316                 if (excludeNull)
317                 {
318                     varIsNotNull();
319                 }
320
321                 // #3: Prepend "var is null or ...".
322                 if (includeNull)
323                 {
324                     varIsNull();
325                 }
326             }
327         }
328         #endregion
329
330         #region String methods
331         internal override void ToCompactString(StringBuilder builder)
332         {
333             RestrictedMemberSlot.ToCompactString(builder);
334             builder.Append(" IN (");
335             StringUtil.ToCommaSeparatedStringSorted(builder, Domain.Values);
336             builder.Append(")");
337         }
338         #endregion
339     }
340 }