9c671fe6b4994ebd0f0f9a26250b3a9b34e4bf79
[mono.git] / mcs / class / referencesource / System.Data.Entity / System / Data / Mapping / ViewGeneration / Structures / NegatedConstant.cs
1 //---------------------------------------------------------------------
2 // <copyright file="NegatedConstant.cs" company="Microsoft">
3 //      Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //
6 // @owner Microsoft
7 // @backupOwner Microsoft
8 //---------------------------------------------------------------------
9
10 namespace System.Data.Mapping.ViewGeneration.Structures
11 {
12     using System.Collections.Generic;
13     using System.Data.Common.CommandTrees;
14     using System.Data.Common.CommandTrees.ExpressionBuilder;
15     using System.Data.Common.Utils;
16     using System.Data.Entity;
17     using System.Diagnostics;
18     using System.Linq;
19     using System.Text;
20
21     /// <summary>
22     /// A class that represents NOT(elements), e.g., NOT(1, 2, NULL), i.e., all values other than null, 1 and 2
23     /// </summary>
24     internal sealed class NegatedConstant : Constant
25     {
26         #region Constructors
27         /// <summary>
28         /// Creates a negated constant with the <paramref name="values"/> in it.
29         /// </summary>
30         /// <param name="values">must have no <see cref=" NegatedConstant"/> items</param>
31         internal NegatedConstant(IEnumerable<Constant> values)
32         {
33             Debug.Assert(!values.Any(v => v is NegatedConstant), "Negated constant values must not contain another negated constant.");
34             m_negatedDomain = new Set<Constant>(values, Constant.EqualityComparer);
35         }
36         #endregion
37
38         #region Fields
39         /// <summary>
40         /// e.g., NOT(1, 2, Undefined)
41         /// </summary>
42         private readonly Set<Constant> m_negatedDomain;
43         #endregion
44
45         #region Properties
46         internal IEnumerable<Constant> Elements
47         {
48             get { return m_negatedDomain; }
49         }
50         #endregion
51
52         #region Methods
53         /// <summary>
54         /// Returns true if the negated constant contains <paramref name="constant"/>.
55         /// </summary>
56         internal bool Contains(Constant constant)
57         {
58             return m_negatedDomain.Contains(constant);
59         }
60
61         internal override bool IsNull()
62         {
63             return false;
64         }
65
66         internal override bool IsNotNull()
67         {
68             if (object.ReferenceEquals(this, Constant.NotNull))
69             {
70                 return true;
71             }
72             else
73             {
74                 return m_negatedDomain.Count == 1 && m_negatedDomain.Contains(Constant.Null);
75             }
76         }
77
78         internal override bool IsUndefined()
79         {
80             return false;
81         }
82
83         /// <summary>
84         /// Returns true if the negated constant contains <see cref="Constant.Null"/>.
85         /// </summary>
86         internal override bool HasNotNull()
87         {
88             return m_negatedDomain.Contains(Constant.Null);
89         }
90
91         public override int GetHashCode()
92         {
93             int result = 0;
94             foreach (Constant constant in m_negatedDomain)
95             {
96                 result ^= Constant.EqualityComparer.GetHashCode(constant);
97             }
98             return result;
99         }
100
101         protected override bool IsEqualTo(Constant right)
102         {
103             NegatedConstant rightNegatedConstant = right as NegatedConstant;
104             if (rightNegatedConstant == null)
105             {
106                 return false;
107             }
108
109             return m_negatedDomain.SetEquals(rightNegatedConstant.m_negatedDomain);
110         }
111
112         /// <summary>
113         /// Not supported in this class.
114         /// </summary>
115         internal override StringBuilder AsEsql(StringBuilder builder, MemberPath outputMember, string blockAlias)
116         {
117             Debug.Fail("Should not be called.");
118             return null; // To keep the compiler happy
119         }
120
121         /// <summary>
122         /// Not supported in this class.
123         /// </summary>
124         internal override DbExpression AsCqt(DbExpression row, MemberPath outputMember)
125         {
126             Debug.Fail("Should not be called.");
127             return null; // To keep the compiler happy
128         }
129
130         internal StringBuilder AsEsql(StringBuilder builder, string blockAlias, IEnumerable<Constant> constants, MemberPath outputMember, bool skipIsNotNull)
131         {
132             return ToStringHelper(builder, blockAlias, constants, outputMember, skipIsNotNull, false);
133         }
134
135         internal DbExpression AsCqt(DbExpression row, IEnumerable<Constant> constants, MemberPath outputMember, bool skipIsNotNull)
136         {
137             DbExpression cqt = null;
138
139             AsCql(
140                 // trueLiteral action
141                 () => cqt = DbExpressionBuilder.True,
142                 // varIsNotNull action
143                 () => cqt = outputMember.AsCqt(row).IsNull().Not(),
144                 // varNotEqualsTo action
145                 (constant) =>
146                 {
147                     DbExpression notEqualsExpr = outputMember.AsCqt(row).NotEqual(constant.AsCqt(row, outputMember));
148                     if (cqt != null)
149                     {
150                         cqt = cqt.And(notEqualsExpr);
151                     }
152                     else
153                     {
154                         cqt = notEqualsExpr;
155                     }
156                 },
157                 constants, outputMember, skipIsNotNull);
158
159             return cqt;
160         }
161
162         internal StringBuilder AsUserString(StringBuilder builder, string blockAlias, IEnumerable<Constant> constants, MemberPath outputMember, bool skipIsNotNull)
163         {
164             return ToStringHelper(builder, blockAlias, constants, outputMember, skipIsNotNull, true);
165         }
166
167         /// <summary>
168         /// Given a set of positive <paramref name="constants"/> generates a simplified negated constant Cql expression.
169         /// Examples:
170         ///     - 7, NOT(7, NULL) means NOT(NULL)
171         ///     - 7, 8, NOT(7, 8, 9, 10) means NOT(9, 10)
172         /// </summary>
173         private void AsCql(Action trueLiteral, Action varIsNotNull, Action<Constant> varNotEqualsTo, IEnumerable<Constant> constants, MemberPath outputMember, bool skipIsNotNull)
174         {
175             bool isNullable = outputMember.IsNullable;
176             // Remove all the constants from negated and then print "x <> C1 .. AND x <> C2 .. AND x <> C3 ..."
177             Set<Constant> negatedConstants = new Set<Constant>(this.Elements, Constant.EqualityComparer);
178             foreach (Constant constant in constants)
179             {
180                 if (constant.Equals(this)) { continue; }
181                 Debug.Assert(negatedConstants.Contains(constant), "Negated constant must contain all positive constants");
182                 negatedConstants.Remove(constant);
183             }
184
185             if (negatedConstants.Count == 0)
186             {
187                 // All constants cancel out - emit True.
188                 trueLiteral();
189             }
190             else
191             {
192                 bool hasNull = negatedConstants.Contains(Constant.Null);
193                 negatedConstants.Remove(Constant.Null);
194
195                 // We always add IS NOT NULL if the property is nullable (and we cannot skip IS NOT NULL).
196                 // Also, if the domain contains NOT NULL, we must add it.
197                 
198                 if (hasNull || (isNullable && !skipIsNotNull))
199                 {
200                     varIsNotNull();
201                 }
202
203                 foreach (Constant constant in negatedConstants)
204                 {
205                     varNotEqualsTo(constant);
206                 }
207             }
208         }
209
210         private StringBuilder ToStringHelper(StringBuilder builder, string blockAlias, IEnumerable<Constant> constants, MemberPath outputMember, bool skipIsNotNull, bool userString)
211         {
212             bool anyAdded = false;
213             AsCql(
214                 // trueLiteral action
215                 () => builder.Append("true"),
216                 // varIsNotNull action
217                 () =>
218                 {
219                     if (userString)
220                     {
221                         outputMember.ToCompactString(builder, blockAlias);
222                         builder.Append(" is not NULL");
223                     }
224                     else
225                     {
226                         outputMember.AsEsql(builder, blockAlias);
227                         builder.Append(" IS NOT NULL");
228                     }
229                     anyAdded = true;
230                 },
231                 // varNotEqualsTo action
232                 (constant) =>
233                 {
234                     if (anyAdded)
235                     {
236                         builder.Append(" AND ");
237                     }
238                     anyAdded = true;
239
240                     if (userString)
241                     {
242                         outputMember.ToCompactString(builder, blockAlias);
243                         builder.Append(" <>");
244                         constant.ToCompactString(builder);
245                     }
246                     else
247                     {
248                         outputMember.AsEsql(builder, blockAlias);
249                         builder.Append(" <>");
250                         constant.AsEsql(builder, outputMember, blockAlias);
251                     }
252                 },
253                 constants, outputMember, skipIsNotNull);
254             return builder;
255         }
256
257         internal override string ToUserString()
258         {
259             if (IsNotNull())
260             {
261                 return System.Data.Entity.Strings.ViewGen_NotNull;
262             }
263             else
264             {
265                 StringBuilder builder = new StringBuilder();
266                 bool isFirst = true;
267                 foreach (Constant constant in m_negatedDomain)
268                 {
269                     // Skip printing out Null if m_negatedDomain has other values
270                     if (m_negatedDomain.Count > 1 && constant.IsNull())
271                     {
272                         continue;
273                     }
274                     if (isFirst == false)
275                     {
276                         builder.Append(System.Data.Entity.Strings.ViewGen_CommaBlank);
277                     }
278                     isFirst = false;
279                     builder.Append(constant.ToUserString());
280                 }
281                 StringBuilder result = new StringBuilder();
282                 result.Append(Strings.ViewGen_NegatedCellConstant(builder.ToString()));
283                 return result.ToString();
284             }
285         }
286
287         internal override void ToCompactString(StringBuilder builder)
288         {
289             if (IsNotNull())
290             {
291                 builder.Append("NOT_NULL");
292             }
293             else
294             {
295                 builder.Append("NOT(");
296                 StringUtil.ToCommaSeparatedStringSorted(builder, m_negatedDomain);
297                 builder.Append(")");
298             }
299         }
300         #endregion
301     }
302 }