1 //---------------------------------------------------------------------
2 // <copyright file="GeneratedView.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
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
;
26 /// Holds the view generated for a given OFTYPE(Extent, Type) combination.
28 internal sealed class GeneratedView
: InternalBase
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.
35 internal static GeneratedView
CreateGeneratedView(EntitySetBase extent
,
37 DbQueryCommandTree commandTree
,
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
);
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.
68 internal static GeneratedView
CreateGeneratedViewForFKAssociationSet(EntitySetBase extent
,
70 DbQueryCommandTree commandTree
,
71 StorageMappingItemCollection mappingItemCollection
,
72 ConfigViewGenerator config
)
74 return new GeneratedView(extent
, type
, commandTree
, null, null, mappingItemCollection
, config
);
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.
81 internal static bool TryParseUserSpecifiedView(StorageSetMapping setMapping
,
85 StorageMappingItemCollection mappingItemCollection
,
86 ConfigViewGenerator config
,
87 /*out*/ IList
<EdmSchemaError
> errors
,
88 out GeneratedView generatedView
)
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
);
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
))
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
);
128 generatedView
= new GeneratedView(setMapping
.Set
, type
, commandTree
, eSQL
, discriminatorMap
, mappingItemCollection
, config
);
133 generatedView
= null;
138 private GeneratedView(EntitySetBase extent
,
140 DbQueryCommandTree commandTree
,
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");
152 m_commandTree
= commandTree
;
154 m_discriminatorMap
= discriminatorMap
;
155 m_mappingItemCollection
= mappingItemCollection
;
158 if (m_config
.IsViewTracing
)
160 StringBuilder trace
= new StringBuilder(1024);
161 this.ToCompactString(trace
);
162 Helpers
.FormatTraceLine("CQL view for {0}", trace
.ToString());
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
;
181 get { return m_eSQL; }
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
;
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
);
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.
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
)
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
;
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");
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
))
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;
299 #region String Methods
300 internal override void ToCompactString(StringBuilder builder
)
302 bool ofTypeView
= m_type
!= m_extent
.ElementType
;
306 builder
.Append("OFTYPE(");
308 builder
.AppendFormat("{0}.{1}", m_extent
.EntityContainer
.Name
, m_extent
.Name
);
311 builder
.Append(", ").Append(m_type
.Name
).Append(')');
313 builder
.AppendLine(" = ");
315 if (!String
.IsNullOrEmpty(m_eSQL
))
317 builder
.Append(m_eSQL
);
321 builder
.Append(m_commandTree
.Print());