1 //---------------------------------------------------------------------
2 // <copyright file="GeneratedView.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
11 {
12     using System.Collections.Generic;
13     using System.Data.Common.CommandTrees;
14     using System.Data.Common.CommandTrees.Internal;
15     using System.Data.Common.EntitySql;
16     using System.Data.Common.Utils;
17     using System.Data.Entity.Util;
18     using System.Data.Mapping.ViewGeneration.Utils;
19     using System.Data.Metadata.Edm;
20     using System.Data.Query.InternalTrees;
21     using System.Data.Query.PlanCompiler;
22     using System.Diagnostics;
23     using System.Text;
24 
25     /// <summary>
26     /// Holds the view generated for a given OFTYPE(Extent, Type) combination.
27     /// </summary>
28     internal sealed class GeneratedView : InternalBase
29     {
30         #region Factory
31         /// <summary>
32         /// Creates generated view object for the combination of the <paramref name="extent"/> and the <paramref name="type"/>.
33         /// This constructor is used for regular cell-based view generation.
34         /// </summary>
CreateGeneratedView(EntitySetBase extent, EdmType type, DbQueryCommandTree commandTree, string eSQL, StorageMappingItemCollection mappingItemCollection, ConfigViewGenerator config)35         internal static GeneratedView CreateGeneratedView(EntitySetBase extent,
36                                                           EdmType type,
37                                                           DbQueryCommandTree commandTree,
38                                                           string eSQL,
39                                                           StorageMappingItemCollection mappingItemCollection,
40                                                           ConfigViewGenerator config)
41         {
42             // If config.GenerateEsql is specified, eSQL must be non-null.
43             // If config.GenerateEsql is false, commandTree is non-null except the case when loading pre-compiled eSQL views.
44             Debug.Assert(!config.GenerateEsql || !String.IsNullOrEmpty(eSQL), "eSQL must be specified");
45 
46             DiscriminatorMap discriminatorMap = null;
47             if (commandTree != null)
48             {
49                 commandTree = ViewSimplifier.SimplifyView(extent, commandTree);
50 
51                 // See if the view matches the "discriminated" pattern (allows simplification of generated store commands)
52                 if (extent.BuiltInTypeKind == BuiltInTypeKind.EntitySet)
53                 {
54                     if (DiscriminatorMap.TryCreateDiscriminatorMap((EntitySet)extent, commandTree.Query, out discriminatorMap))
55                     {
56                         Debug.Assert(discriminatorMap != null, "discriminatorMap == null after it has been created");
57                     }
58                 }
59             }
60 
61             return new GeneratedView(extent, type, commandTree, eSQL, discriminatorMap, mappingItemCollection, config);
62         }
63 
64         /// <summary>
65         /// Creates generated view object for the combination of the <paramref name="extent"/> and the <paramref name="type"/>.
66         /// This constructor is used for FK association sets only.
67         /// </summary>
CreateGeneratedViewForFKAssociationSet(EntitySetBase extent, EdmType type, DbQueryCommandTree commandTree, StorageMappingItemCollection mappingItemCollection, ConfigViewGenerator config)68         internal static GeneratedView CreateGeneratedViewForFKAssociationSet(EntitySetBase extent,
69                                                                              EdmType type,
70                                                                              DbQueryCommandTree commandTree,
71                                                                              StorageMappingItemCollection mappingItemCollection,
72                                                                              ConfigViewGenerator config)
73         {
74             return new GeneratedView(extent, type, commandTree, null, null, mappingItemCollection, config);
75         }
76 
77         /// <summary>
78         /// Creates generated view object for the combination of the <paramref name="setMapping"/>.Set and the <paramref name="type"/>.
79         /// This constructor is used for user-defined query views only.
80         /// </summary>
TryParseUserSpecifiedView(StorageSetMapping setMapping, EntityTypeBase type, string eSQL, bool includeSubtypes, StorageMappingItemCollection mappingItemCollection, ConfigViewGenerator config, IList<EdmSchemaError> errors, out GeneratedView generatedView)81         internal static bool TryParseUserSpecifiedView(StorageSetMapping setMapping,
82                                                        EntityTypeBase type,
83                                                        string eSQL,
84                                                        bool includeSubtypes,
85                                                        StorageMappingItemCollection mappingItemCollection,
86                                                        ConfigViewGenerator config,
87                                                        /*out*/ IList<EdmSchemaError> errors,
88                                                        out GeneratedView generatedView)
89         {
90             bool failed = false;
91 
92             DbQueryCommandTree commandTree;
93             DiscriminatorMap discriminatorMap;
94             Exception parserException;
95             if (!GeneratedView.TryParseView(eSQL, true, setMapping.Set, mappingItemCollection, config, out commandTree, out discriminatorMap, out parserException))
96             {
97                 EdmSchemaError error = new EdmSchemaError(System.Data.Entity.Strings.Mapping_Invalid_QueryView2(setMapping.Set.Name, parserException.Message),
98                                            (int)StorageMappingErrorCode.InvalidQueryView, EdmSchemaErrorSeverity.Error,
99                                            setMapping.EntityContainerMapping.SourceLocation, setMapping.StartLineNumber, setMapping.StartLinePosition, parserException);
100                 errors.Add(error);
101                 failed = true;
102             }
103             else
104             {
105                 Debug.Assert(commandTree != null, "commandTree not set after parsing the view");
106 
107                 // Verify that all expressions appearing in the view are supported.
108                 foreach (var error in ViewValidator.ValidateQueryView(commandTree, setMapping, type, includeSubtypes))
109                 {
110                     errors.Add(error);
111                     failed = true;
112                 }
113 
114                 // Verify that the result type of the query view is assignable to the element type of the entityset
115                 CollectionType queryResultType = (commandTree.Query.ResultType.EdmType) as CollectionType;
116                 if ((queryResultType == null) || (!setMapping.Set.ElementType.IsAssignableFrom(queryResultType.TypeUsage.EdmType)))
117                 {
118                     EdmSchemaError error = new EdmSchemaError(System.Data.Entity.Strings.Mapping_Invalid_QueryView_Type(setMapping.Set.Name),
119                                                (int)StorageMappingErrorCode.InvalidQueryViewResultType, EdmSchemaErrorSeverity.Error,
120                                                setMapping.EntityContainerMapping.SourceLocation, setMapping.StartLineNumber, setMapping.StartLinePosition);
121                     errors.Add(error);
122                     failed = true;
123                 }
124             }
125 
126             if (!failed)
127             {
128                 generatedView = new GeneratedView(setMapping.Set, type, commandTree, eSQL, discriminatorMap, mappingItemCollection, config);
129                 return true;
130             }
131             else
132             {
133                 generatedView = null;
134                 return false;
135             }
136         }
137 
GeneratedView(EntitySetBase extent, EdmType type, DbQueryCommandTree commandTree, string eSQL, DiscriminatorMap discriminatorMap, StorageMappingItemCollection mappingItemCollection, ConfigViewGenerator config)138         private GeneratedView(EntitySetBase extent,
139                               EdmType type,
140                               DbQueryCommandTree commandTree,
141                               string eSQL,
142                               DiscriminatorMap discriminatorMap,
143                               StorageMappingItemCollection mappingItemCollection,
144                               ConfigViewGenerator config)
145         {
146             // At least one of the commandTree or eSQL must be specified.
147             // Both are specified in the case of user-defined views.
148             Debug.Assert(commandTree != null || !String.IsNullOrEmpty(eSQL), "commandTree or eSQL must be specified");
149 
150             m_extent = extent;
151             m_type = type;
152             m_commandTree = commandTree;
153             m_eSQL = eSQL;
154             m_discriminatorMap = discriminatorMap;
155             m_mappingItemCollection = mappingItemCollection;
156             m_config = config;
157 
158             if (m_config.IsViewTracing)
159             {
160                 StringBuilder trace = new StringBuilder(1024);
161                 this.ToCompactString(trace);
162                 Helpers.FormatTraceLine("CQL view for {0}", trace.ToString());
163             }
164         }
165         #endregion
166 
167         #region Fields
168         private readonly EntitySetBase m_extent;
169         private readonly EdmType m_type;
170         private DbQueryCommandTree m_commandTree; //We cache CQTs for Update Views sicne that is the one update stack works of.
171         private readonly string m_eSQL;
172         private Node m_internalTreeNode;  //we cache IQTs for Query Views since that is the one query stack works of.
173         private DiscriminatorMap m_discriminatorMap;
174         private readonly StorageMappingItemCollection m_mappingItemCollection;
175         private readonly ConfigViewGenerator m_config;
176         #endregion
177 
178         #region Properties
179         internal string eSQL
180         {
181             get { return m_eSQL; }
182         }
183         #endregion
184 
185         #region Methods
GetCommandTree()186         internal DbQueryCommandTree GetCommandTree()
187         {
188             if (m_commandTree == null)
189             {
190                 Debug.Assert(!String.IsNullOrEmpty(m_eSQL), "m_eSQL must be initialized");
191 
192                 Exception parserException;
193                 if (TryParseView(m_eSQL, false, m_extent, m_mappingItemCollection, m_config, out m_commandTree, out m_discriminatorMap, out parserException))
194                 {
195                     Debug.Assert(m_commandTree != null, "m_commandTree not set after parsing the view");
196                     return m_commandTree;
197                 }
198                 else
199                 {
200                     throw new MappingException(System.Data.Entity.Strings.Mapping_Invalid_QueryView(m_extent.Name, parserException.Message));
201                 }
202             }
203             return m_commandTree;
204         }
205 
GetInternalTree(Command targetIqtCommand)206         internal Node GetInternalTree(Command targetIqtCommand)
207         {
208             Debug.Assert(m_extent.EntityContainer.DataSpace == DataSpace.CSpace, "Internal Tree should be asked only for query view");
209             if (m_internalTreeNode == null)
210             {
211                 DbQueryCommandTree tree = GetCommandTree();
212                 // Convert this into an ITree first
213                 Command itree = ITreeGenerator.Generate(tree, m_discriminatorMap);
214                 // Pull out the root physical project-op, and copy this itree into our own itree
215                 PlanCompiler.Assert(itree.Root.Op.OpType == OpType.PhysicalProject,
216                     "Expected a physical projectOp at the root of the tree - found " + itree.Root.Op.OpType);
217                 // #554756: VarVec enumerators are not cached on the shared Command instance.
218                 itree.DisableVarVecEnumCaching();
219                 m_internalTreeNode = itree.Root.Child0;
220             }
221             Debug.Assert(m_internalTreeNode != null, "m_internalTreeNode != null");
222             return OpCopier.Copy(targetIqtCommand, m_internalTreeNode);
223         }
224 
225         /// <summary>
226         /// Given an extent and its corresponding view, invokes the parser to check if the view definition is syntactically correct.
227         /// Iff parsing succeeds: <paramref name="commandTree"/> and <paramref name="discriminatorMap"/> are set to the parse result and method returns true,
228         /// otherwise if parser has thrown a catchable exception, it is returned via <paramref name="parserException"/> parameter,
229         /// otherwise exception is re-thrown.
230         /// </summary>
TryParseView(string eSQL, bool isUserSpecified, EntitySetBase extent, StorageMappingItemCollection mappingItemCollection, ConfigViewGenerator config, out DbQueryCommandTree commandTree, out DiscriminatorMap discriminatorMap, out Exception parserException)231         private static bool TryParseView(string eSQL,
232                                          bool isUserSpecified,
233                                          EntitySetBase extent,
234                                          StorageMappingItemCollection mappingItemCollection,
235                                          ConfigViewGenerator config,
236                                          out DbQueryCommandTree commandTree,
237                                          out DiscriminatorMap discriminatorMap,
238                                          out Exception parserException)
239         {
240             commandTree = null;
241             discriminatorMap = null;
242             parserException = null;
243 
244             // We do not catch any internal exceptions any more
245             config.StartSingleWatch(PerfType.ViewParsing);
246             try
247             {
248                 // If it is a user specified view, allow all queries. Otherwise parse the view in the restricted mode.
249                 ParserOptions.CompilationMode compilationMode = ParserOptions.CompilationMode.RestrictedViewGenerationMode;
250                 if (isUserSpecified)
251                 {
252                     compilationMode = ParserOptions.CompilationMode.UserViewGenerationMode;
253                 }
254 
255                 Debug.Assert(!String.IsNullOrEmpty(eSQL), "eSQL query is not specified");
256                 commandTree = (DbQueryCommandTree)ExternalCalls.CompileView(eSQL, mappingItemCollection, compilationMode);
257 
258                 if (!isUserSpecified || AppSettings.SimplifyUserSpecifiedViews)
259                 {
260                     commandTree = ViewSimplifier.SimplifyView(extent, commandTree);
261                 }
262 
263                 // See if the view matches the "discriminated" pattern (allows simplification of generated store commands)
264                 if (extent.BuiltInTypeKind == BuiltInTypeKind.EntitySet)
265                 {
266                     if (DiscriminatorMap.TryCreateDiscriminatorMap((EntitySet)extent, commandTree.Query, out discriminatorMap))
267                     {
268                         Debug.Assert(discriminatorMap != null, "discriminatorMap == null after it has been created");
269                     }
270                 }
271             }
272             catch (Exception e)
273             {
274                 // Catching all the exception types since Query parser seems to be throwing veriety of
275                 // exceptions - EntityException, ArgumentException, ArgumentNullException etc.
276                 if (EntityUtil.IsCatchableExceptionType(e))
277                 {
278                     parserException = e;
279                 }
280                 else
281                 {
282                     throw;
283                 }
284             }
285             finally
286             {
287                 config.StopSingleWatch(PerfType.ViewParsing);
288             }
289 
290             Debug.Assert(commandTree != null || parserException != null, "Either commandTree or parserException is expected.");
291             // Note: m_commandTree might have been initialized by a previous call to this method, so in consequent calls it might occur that
292             // both m_commandTree and parserException are not null - this would mean that the last parse attempt failed, but m_commandTree value is
293             // preserved from the previous call.
294 
295             return parserException == null;
296         }
297         #endregion
298 
299         #region String Methods
ToCompactString(StringBuilder builder)300         internal override void ToCompactString(StringBuilder builder)
301         {
302             bool ofTypeView = m_type != m_extent.ElementType;
303 
304             if (ofTypeView)
305             {
306                 builder.Append("OFTYPE(");
307             }
308             builder.AppendFormat("{0}.{1}", m_extent.EntityContainer.Name, m_extent.Name);
309             if (ofTypeView)
310             {
311                 builder.Append(", ").Append(m_type.Name).Append(')');
312             }
313             builder.AppendLine(" = ");
314 
315             if (!String.IsNullOrEmpty(m_eSQL))
316             {
317                 builder.Append(m_eSQL);
318             }
319             else
320             {
321                 builder.Append(m_commandTree.Print());
322             }
323         }
324         #endregion
325     }
326 }
327