Updates referencesource to .NET 4.7
[mono-project.git] / mcs / class / referencesource / System.Data.Entity / System / Data / Mapping / ViewGeneration / GeneratedView.cs
blobbec352a06e036756695599852dd17d136f933b92
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 //---------------------------------------------------------------------
10 namespace System.Data.Mapping.ViewGeneration
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;
25 /// <summary>
26 /// Holds the view generated for a given OFTYPE(Extent, Type) combination.
27 /// </summary>
28 internal sealed class GeneratedView : InternalBase
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>
35 internal static GeneratedView CreateGeneratedView(EntitySetBase extent,
36 EdmType type,
37 DbQueryCommandTree commandTree,
38 string eSQL,
39 StorageMappingItemCollection mappingItemCollection,
40 ConfigViewGenerator config)
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");
46 DiscriminatorMap discriminatorMap = null;
47 if (commandTree != null)
49 commandTree = ViewSimplifier.SimplifyView(extent, commandTree);
51 // See if the view matches the "discriminated" pattern (allows simplification of generated store commands)
52 if (extent.BuiltInTypeKind == BuiltInTypeKind.EntitySet)
54 if (DiscriminatorMap.TryCreateDiscriminatorMap((EntitySet)extent, commandTree.Query, out discriminatorMap))
56 Debug.Assert(discriminatorMap != null, "discriminatorMap == null after it has been created");
61 return new GeneratedView(extent, type, commandTree, eSQL, discriminatorMap, mappingItemCollection, config);
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>
68 internal static GeneratedView CreateGeneratedViewForFKAssociationSet(EntitySetBase extent,
69 EdmType type,
70 DbQueryCommandTree commandTree,
71 StorageMappingItemCollection mappingItemCollection,
72 ConfigViewGenerator config)
74 return new GeneratedView(extent, type, commandTree, null, null, mappingItemCollection, config);
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>
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)
90 bool failed = false;
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))
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;
103 else
105 Debug.Assert(commandTree != null, "commandTree not set after parsing the view");
107 // Verify that all expressions appearing in the view are supported.
108 foreach (var error in ViewValidator.ValidateQueryView(commandTree, setMapping, type, includeSubtypes))
110 errors.Add(error);
111 failed = true;
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)))
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;
126 if (!failed)
128 generatedView = new GeneratedView(setMapping.Set, type, commandTree, eSQL, discriminatorMap, mappingItemCollection, config);
129 return true;
131 else
133 generatedView = null;
134 return false;
138 private GeneratedView(EntitySetBase extent,
139 EdmType type,
140 DbQueryCommandTree commandTree,
141 string eSQL,
142 DiscriminatorMap discriminatorMap,
143 StorageMappingItemCollection mappingItemCollection,
144 ConfigViewGenerator config)
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");
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;
158 if (m_config.IsViewTracing)
160 StringBuilder trace = new StringBuilder(1024);
161 this.ToCompactString(trace);
162 Helpers.FormatTraceLine("CQL view for {0}", trace.ToString());
165 #endregion
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
178 #region Properties
179 internal string eSQL
181 get { return m_eSQL; }
183 #endregion
185 #region Methods
186 internal DbQueryCommandTree GetCommandTree()
188 if (m_commandTree == null)
190 Debug.Assert(!String.IsNullOrEmpty(m_eSQL), "m_eSQL must be initialized");
192 Exception parserException;
193 if (TryParseView(m_eSQL, false, m_extent, m_mappingItemCollection, m_config, out m_commandTree, out m_discriminatorMap, out parserException))
195 Debug.Assert(m_commandTree != null, "m_commandTree not set after parsing the view");
196 return m_commandTree;
198 else
200 throw new MappingException(System.Data.Entity.Strings.Mapping_Invalid_QueryView(m_extent.Name, parserException.Message));
203 return m_commandTree;
206 internal Node GetInternalTree(Command targetIqtCommand)
208 Debug.Assert(m_extent.EntityContainer.DataSpace == DataSpace.CSpace, "Internal Tree should be asked only for query view");
209 if (m_internalTreeNode == null)
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;
221 Debug.Assert(m_internalTreeNode != null, "m_internalTreeNode != null");
222 return OpCopier.Copy(targetIqtCommand, m_internalTreeNode);
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>
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)
240 commandTree = null;
241 discriminatorMap = null;
242 parserException = null;
244 // We do not catch any internal exceptions any more
245 config.StartSingleWatch(PerfType.ViewParsing);
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)
252 compilationMode = ParserOptions.CompilationMode.UserViewGenerationMode;
255 Debug.Assert(!String.IsNullOrEmpty(eSQL), "eSQL query is not specified");
256 commandTree = (DbQueryCommandTree)ExternalCalls.CompileView(eSQL, mappingItemCollection, compilationMode);
258 if (!isUserSpecified || AppSettings.SimplifyUserSpecifiedViews)
260 commandTree = ViewSimplifier.SimplifyView(extent, commandTree);
263 // See if the view matches the "discriminated" pattern (allows simplification of generated store commands)
264 if (extent.BuiltInTypeKind == BuiltInTypeKind.EntitySet)
266 if (DiscriminatorMap.TryCreateDiscriminatorMap((EntitySet)extent, commandTree.Query, out discriminatorMap))
268 Debug.Assert(discriminatorMap != null, "discriminatorMap == null after it has been created");
272 catch (Exception e)
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))
278 parserException = e;
280 else
282 throw;
285 finally
287 config.StopSingleWatch(PerfType.ViewParsing);
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.
295 return parserException == null;
297 #endregion
299 #region String Methods
300 internal override void ToCompactString(StringBuilder builder)
302 bool ofTypeView = m_type != m_extent.ElementType;
304 if (ofTypeView)
306 builder.Append("OFTYPE(");
308 builder.AppendFormat("{0}.{1}", m_extent.EntityContainer.Name, m_extent.Name);
309 if (ofTypeView)
311 builder.Append(", ").Append(m_type.Name).Append(')');
313 builder.AppendLine(" = ");
315 if (!String.IsNullOrEmpty(m_eSQL))
317 builder.Append(m_eSQL);
319 else
321 builder.Append(m_commandTree.Print());
324 #endregion