//--------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. All rights reserved. // // // @owner [....] // @backupOwner [....] //--------------------------------------------------------------------- namespace System.Data.Mapping.ViewGeneration { using System.Collections.Generic; using System.Data.Common.CommandTrees; using System.Data.Common.CommandTrees.Internal; using System.Data.Common.EntitySql; using System.Data.Common.Utils; using System.Data.Entity.Util; using System.Data.Mapping.ViewGeneration.Utils; using System.Data.Metadata.Edm; using System.Data.Query.InternalTrees; using System.Data.Query.PlanCompiler; using System.Diagnostics; using System.Text; /// /// Holds the view generated for a given OFTYPE(Extent, Type) combination. /// internal sealed class GeneratedView : InternalBase { #region Factory /// /// Creates generated view object for the combination of the and the . /// This constructor is used for regular cell-based view generation. /// internal static GeneratedView CreateGeneratedView(EntitySetBase extent, EdmType type, DbQueryCommandTree commandTree, string eSQL, StorageMappingItemCollection mappingItemCollection, ConfigViewGenerator config) { // If config.GenerateEsql is specified, eSQL must be non-null. // If config.GenerateEsql is false, commandTree is non-null except the case when loading pre-compiled eSQL views. Debug.Assert(!config.GenerateEsql || !String.IsNullOrEmpty(eSQL), "eSQL must be specified"); DiscriminatorMap discriminatorMap = null; if (commandTree != null) { commandTree = ViewSimplifier.SimplifyView(extent, commandTree); // See if the view matches the "discriminated" pattern (allows simplification of generated store commands) if (extent.BuiltInTypeKind == BuiltInTypeKind.EntitySet) { if (DiscriminatorMap.TryCreateDiscriminatorMap((EntitySet)extent, commandTree.Query, out discriminatorMap)) { Debug.Assert(discriminatorMap != null, "discriminatorMap == null after it has been created"); } } } return new GeneratedView(extent, type, commandTree, eSQL, discriminatorMap, mappingItemCollection, config); } /// /// Creates generated view object for the combination of the and the . /// This constructor is used for FK association sets only. /// internal static GeneratedView CreateGeneratedViewForFKAssociationSet(EntitySetBase extent, EdmType type, DbQueryCommandTree commandTree, StorageMappingItemCollection mappingItemCollection, ConfigViewGenerator config) { return new GeneratedView(extent, type, commandTree, null, null, mappingItemCollection, config); } /// /// Creates generated view object for the combination of the .Set and the . /// This constructor is used for user-defined query views only. /// internal static bool TryParseUserSpecifiedView(StorageSetMapping setMapping, EntityTypeBase type, string eSQL, bool includeSubtypes, StorageMappingItemCollection mappingItemCollection, ConfigViewGenerator config, /*out*/ IList errors, out GeneratedView generatedView) { bool failed = false; DbQueryCommandTree commandTree; DiscriminatorMap discriminatorMap; Exception parserException; if (!GeneratedView.TryParseView(eSQL, true, setMapping.Set, mappingItemCollection, config, out commandTree, out discriminatorMap, out parserException)) { EdmSchemaError error = new EdmSchemaError(System.Data.Entity.Strings.Mapping_Invalid_QueryView2(setMapping.Set.Name, parserException.Message), (int)StorageMappingErrorCode.InvalidQueryView, EdmSchemaErrorSeverity.Error, setMapping.EntityContainerMapping.SourceLocation, setMapping.StartLineNumber, setMapping.StartLinePosition, parserException); errors.Add(error); failed = true; } else { Debug.Assert(commandTree != null, "commandTree not set after parsing the view"); // Verify that all expressions appearing in the view are supported. foreach (var error in ViewValidator.ValidateQueryView(commandTree, setMapping, type, includeSubtypes)) { errors.Add(error); failed = true; } // Verify that the result type of the query view is assignable to the element type of the entityset CollectionType queryResultType = (commandTree.Query.ResultType.EdmType) as CollectionType; if ((queryResultType == null) || (!setMapping.Set.ElementType.IsAssignableFrom(queryResultType.TypeUsage.EdmType))) { EdmSchemaError error = new EdmSchemaError(System.Data.Entity.Strings.Mapping_Invalid_QueryView_Type(setMapping.Set.Name), (int)StorageMappingErrorCode.InvalidQueryViewResultType, EdmSchemaErrorSeverity.Error, setMapping.EntityContainerMapping.SourceLocation, setMapping.StartLineNumber, setMapping.StartLinePosition); errors.Add(error); failed = true; } } if (!failed) { generatedView = new GeneratedView(setMapping.Set, type, commandTree, eSQL, discriminatorMap, mappingItemCollection, config); return true; } else { generatedView = null; return false; } } private GeneratedView(EntitySetBase extent, EdmType type, DbQueryCommandTree commandTree, string eSQL, DiscriminatorMap discriminatorMap, StorageMappingItemCollection mappingItemCollection, ConfigViewGenerator config) { // At least one of the commandTree or eSQL must be specified. // Both are specified in the case of user-defined views. Debug.Assert(commandTree != null || !String.IsNullOrEmpty(eSQL), "commandTree or eSQL must be specified"); m_extent = extent; m_type = type; m_commandTree = commandTree; m_eSQL = eSQL; m_discriminatorMap = discriminatorMap; m_mappingItemCollection = mappingItemCollection; m_config = config; if (m_config.IsViewTracing) { StringBuilder trace = new StringBuilder(1024); this.ToCompactString(trace); Helpers.FormatTraceLine("CQL view for {0}", trace.ToString()); } } #endregion #region Fields private readonly EntitySetBase m_extent; private readonly EdmType m_type; private DbQueryCommandTree m_commandTree; //We cache CQTs for Update Views sicne that is the one update stack works of. private readonly string m_eSQL; private Node m_internalTreeNode; //we cache IQTs for Query Views since that is the one query stack works of. private DiscriminatorMap m_discriminatorMap; private readonly StorageMappingItemCollection m_mappingItemCollection; private readonly ConfigViewGenerator m_config; #endregion #region Properties internal string eSQL { get { return m_eSQL; } } #endregion #region Methods internal DbQueryCommandTree GetCommandTree() { if (m_commandTree == null) { Debug.Assert(!String.IsNullOrEmpty(m_eSQL), "m_eSQL must be initialized"); Exception parserException; if (TryParseView(m_eSQL, false, m_extent, m_mappingItemCollection, m_config, out m_commandTree, out m_discriminatorMap, out parserException)) { Debug.Assert(m_commandTree != null, "m_commandTree not set after parsing the view"); return m_commandTree; } else { throw new MappingException(System.Data.Entity.Strings.Mapping_Invalid_QueryView(m_extent.Name, parserException.Message)); } } return m_commandTree; } internal Node GetInternalTree(Command targetIqtCommand) { Debug.Assert(m_extent.EntityContainer.DataSpace == DataSpace.CSpace, "Internal Tree should be asked only for query view"); if (m_internalTreeNode == null) { DbQueryCommandTree tree = GetCommandTree(); // Convert this into an ITree first Command itree = ITreeGenerator.Generate(tree, m_discriminatorMap); // Pull out the root physical project-op, and copy this itree into our own itree PlanCompiler.Assert(itree.Root.Op.OpType == OpType.PhysicalProject, "Expected a physical projectOp at the root of the tree - found " + itree.Root.Op.OpType); // #554756: VarVec enumerators are not cached on the shared Command instance. itree.DisableVarVecEnumCaching(); m_internalTreeNode = itree.Root.Child0; } Debug.Assert(m_internalTreeNode != null, "m_internalTreeNode != null"); return OpCopier.Copy(targetIqtCommand, m_internalTreeNode); } /// /// Given an extent and its corresponding view, invokes the parser to check if the view definition is syntactically correct. /// Iff parsing succeeds: and are set to the parse result and method returns true, /// otherwise if parser has thrown a catchable exception, it is returned via parameter, /// otherwise exception is re-thrown. /// private static bool TryParseView(string eSQL, bool isUserSpecified, EntitySetBase extent, StorageMappingItemCollection mappingItemCollection, ConfigViewGenerator config, out DbQueryCommandTree commandTree, out DiscriminatorMap discriminatorMap, out Exception parserException) { commandTree = null; discriminatorMap = null; parserException = null; // We do not catch any internal exceptions any more config.StartSingleWatch(PerfType.ViewParsing); try { // If it is a user specified view, allow all queries. Otherwise parse the view in the restricted mode. ParserOptions.CompilationMode compilationMode = ParserOptions.CompilationMode.RestrictedViewGenerationMode; if (isUserSpecified) { compilationMode = ParserOptions.CompilationMode.UserViewGenerationMode; } Debug.Assert(!String.IsNullOrEmpty(eSQL), "eSQL query is not specified"); commandTree = (DbQueryCommandTree)ExternalCalls.CompileView(eSQL, mappingItemCollection, compilationMode); if (!isUserSpecified || AppSettings.SimplifyUserSpecifiedViews) { commandTree = ViewSimplifier.SimplifyView(extent, commandTree); } // See if the view matches the "discriminated" pattern (allows simplification of generated store commands) if (extent.BuiltInTypeKind == BuiltInTypeKind.EntitySet) { if (DiscriminatorMap.TryCreateDiscriminatorMap((EntitySet)extent, commandTree.Query, out discriminatorMap)) { Debug.Assert(discriminatorMap != null, "discriminatorMap == null after it has been created"); } } } catch (Exception e) { // Catching all the exception types since Query parser seems to be throwing veriety of // exceptions - EntityException, ArgumentException, ArgumentNullException etc. if (EntityUtil.IsCatchableExceptionType(e)) { parserException = e; } else { throw; } } finally { config.StopSingleWatch(PerfType.ViewParsing); } Debug.Assert(commandTree != null || parserException != null, "Either commandTree or parserException is expected."); // Note: m_commandTree might have been initialized by a previous call to this method, so in consequent calls it might occur that // both m_commandTree and parserException are not null - this would mean that the last parse attempt failed, but m_commandTree value is // preserved from the previous call. return parserException == null; } #endregion #region String Methods internal override void ToCompactString(StringBuilder builder) { bool ofTypeView = m_type != m_extent.ElementType; if (ofTypeView) { builder.Append("OFTYPE("); } builder.AppendFormat("{0}.{1}", m_extent.EntityContainer.Name, m_extent.Name); if (ofTypeView) { builder.Append(", ").Append(m_type.Name).Append(')'); } builder.AppendLine(" = "); if (!String.IsNullOrEmpty(m_eSQL)) { builder.Append(m_eSQL); } else { builder.Append(m_commandTree.Print()); } } #endregion } }