//--------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. All rights reserved. // // // @owner [....] // @backupOwner [....] //--------------------------------------------------------------------- using System.Data.Common.CommandTrees; using System.Data.Common.CommandTrees.ExpressionBuilder; using System.Data.Common.Utils; using System.Linq; using System.Text; using System.Collections.Generic; using System.Data.Mapping.ViewGeneration.CqlGeneration; using System.Data.Metadata.Edm; using System.Diagnostics; namespace System.Data.Mapping.ViewGeneration.Structures { /// /// A class to denote a case statement: /// CASE /// WHEN condition1 THEN value1 /// WHEN condition2 THEN value2 /// ... /// END /// internal sealed class CaseStatement : InternalBase { #region Constructors /// /// Creates a case statement for the with no clauses. /// internal CaseStatement(MemberPath memberPath) { m_memberPath = memberPath; m_clauses = new List(); } #endregion #region Fields /// /// The field. /// private readonly MemberPath m_memberPath; /// /// All the WHEN THENs. /// private List m_clauses; /// /// Value for the else clause. /// private ProjectedSlot m_elseValue; private bool m_simplified = false; #endregion #region Properties internal MemberPath MemberPath { get { return m_memberPath; } } internal List Clauses { get { return m_clauses; } } internal ProjectedSlot ElseValue { get { return m_elseValue; } } #endregion #region Methods /// /// Recursively qualifies all s and returns a new deeply qualified . /// internal CaseStatement DeepQualify(CqlBlock block) { // Go through the whenthens and else and make a new case statement with qualified slots as needed. CaseStatement result = new CaseStatement(m_memberPath); foreach (WhenThen whenThen in m_clauses) { WhenThen newClause = whenThen.ReplaceWithQualifiedSlot(block); result.m_clauses.Add(newClause); } if (m_elseValue != null) { result.m_elseValue = m_elseValue.DeepQualify(block); } result.m_simplified = m_simplified; return result; } /// /// Adds an expression of the form "WHEN THEN ". /// This operation is not allowed after the call. /// internal void AddWhenThen(BoolExpression condition, ProjectedSlot value) { Debug.Assert(!m_simplified, "Attempt to modify a simplified case statement"); Debug.Assert(value != null); condition.ExpensiveSimplify(); m_clauses.Add(new WhenThen(condition, value)); } /// /// Returns true if the depends on (projects) its slot in THEN value or ELSE value. /// internal bool DependsOnMemberValue { get { if (m_elseValue is MemberProjectedSlot) { Debug.Assert(m_memberPath.Equals(((MemberProjectedSlot)m_elseValue).MemberPath), "case statement slot (ELSE) must depend only on its own slot value"); return true; } foreach (WhenThen whenThen in m_clauses) { if (whenThen.Value is MemberProjectedSlot) { Debug.Assert(m_memberPath.Equals(((MemberProjectedSlot)whenThen.Value).MemberPath), "case statement slot (THEN) must depend only on its own slot value"); return true; } } return false; } } internal IEnumerable InstantiatedTypes { get { foreach (WhenThen whenThen in m_clauses) { EdmType type; if (TryGetInstantiatedType(whenThen.Value, out type)) { yield return type; } } EdmType elseType; if (TryGetInstantiatedType(m_elseValue, out elseType)) { yield return elseType; } } } private bool TryGetInstantiatedType(ProjectedSlot slot, out EdmType type) { type = null; ConstantProjectedSlot constantSlot = slot as ConstantProjectedSlot; if (constantSlot != null) { TypeConstant typeConstant = constantSlot.CellConstant as TypeConstant; if (typeConstant != null) { type = typeConstant.EdmType; return true; } } return false; } /// /// Simplifies the so that unnecessary WHEN/THENs for nulls/undefined values are eliminated. /// Also, adds an ELSE clause if possible. /// internal void Simplify() { if (m_simplified) { return; } List clauses = new List(); // remove all WHEN clauses where the value gets set to "undefined" // We eliminate the last clause for now - we could determine the // "most complicated" WHEN clause and eliminate it bool eliminatedNullClauses = false; foreach (WhenThen clause in m_clauses) { ConstantProjectedSlot constantSlot = clause.Value as ConstantProjectedSlot; // If null or undefined, remove it if (constantSlot != null && (constantSlot.CellConstant.IsNull() || constantSlot.CellConstant.IsUndefined())) { eliminatedNullClauses = true; } else { clauses.Add(clause); if (clause.Condition.IsTrue) { // none of subsequent case statements will be evaluated - ignore them break; } } } if (eliminatedNullClauses && clauses.Count == 0) { // There is nothing left -- we should add a null as the value m_elseValue = new ConstantProjectedSlot(Constant.Null, m_memberPath); } // If we eliminated some undefined or null clauses, we do not want an else clause if (clauses.Count > 0 && false == eliminatedNullClauses) { // turn the last WHEN clause into an ELSE int lastIndex = clauses.Count - 1; m_elseValue = clauses[lastIndex].Value; clauses.RemoveAt(lastIndex); } m_clauses = clauses; m_simplified = true; } /// /// Generates eSQL for the current . /// internal StringBuilder AsEsql(StringBuilder builder, IEnumerable withRelationships, string blockAlias, int indentLevel) { if (this.Clauses.Count == 0) { // This is just a single ELSE: no condition at all. Debug.Assert(this.ElseValue != null, "CASE statement with no WHEN/THENs must have ELSE."); CaseSlotValueAsEsql(builder, this.ElseValue, this.MemberPath, blockAlias, withRelationships, indentLevel); return builder; } // Generate the Case WHEN .. THEN ..., WHEN ... THEN ..., END builder.Append("CASE"); foreach (CaseStatement.WhenThen clause in this.Clauses) { StringUtil.IndentNewLine(builder, indentLevel + 2); builder.Append("WHEN "); clause.Condition.AsEsql(builder, blockAlias); builder.Append(" THEN "); CaseSlotValueAsEsql(builder, clause.Value, this.MemberPath, blockAlias, withRelationships, indentLevel + 2); } if (this.ElseValue != null) { StringUtil.IndentNewLine(builder, indentLevel + 2); builder.Append("ELSE "); CaseSlotValueAsEsql(builder, this.ElseValue, this.MemberPath, blockAlias, withRelationships, indentLevel + 2); } StringUtil.IndentNewLine(builder, indentLevel + 1); builder.Append("END"); return builder; } /// /// Generates CQT for the current . /// internal DbExpression AsCqt(DbExpression row, IEnumerable withRelationships) { // Generate the Case WHEN .. THEN ..., WHEN ... THEN ..., END List conditions = new List(); List values = new List(); foreach (CaseStatement.WhenThen clause in this.Clauses) { conditions.Add(clause.Condition.AsCqt(row)); values.Add(CaseSlotValueAsCqt(row, clause.Value, this.MemberPath, withRelationships)); } // Generate ELSE DbExpression elseValue = this.ElseValue != null ? CaseSlotValueAsCqt(row, this.ElseValue, this.MemberPath, withRelationships) : Constant.Null.AsCqt(row, this.MemberPath); if (this.Clauses.Count > 0) { return DbExpressionBuilder.Case(conditions, values, elseValue); } else { Debug.Assert(elseValue != null, "CASE statement with no WHEN/THENs must have ELSE."); return elseValue; } } private static StringBuilder CaseSlotValueAsEsql(StringBuilder builder, ProjectedSlot slot, MemberPath outputMember, string blockAlias, IEnumerable withRelationships, int indentLevel) { // We should never have THEN as a BooleanProjectedSlot. Debug.Assert(slot is MemberProjectedSlot || slot is QualifiedSlot || slot is ConstantProjectedSlot, "Case statement THEN can only have constants or members."); slot.AsEsql(builder, outputMember, blockAlias, 1); WithRelationshipsClauseAsEsql(builder, withRelationships, blockAlias, indentLevel, slot); return builder; } private static void WithRelationshipsClauseAsEsql(StringBuilder builder, IEnumerable withRelationships, string blockAlias, int indentLevel, ProjectedSlot slot) { bool first = true; WithRelationshipsClauseAsCql( // emitWithRelationship action (withRelationship) => { if (first) { builder.Append(" WITH "); first = false; } withRelationship.AsEsql(builder, blockAlias, indentLevel); }, withRelationships, slot); } private static DbExpression CaseSlotValueAsCqt(DbExpression row, ProjectedSlot slot, MemberPath outputMember, IEnumerable withRelationships) { // We should never have THEN as a BooleanProjectedSlot. Debug.Assert(slot is MemberProjectedSlot || slot is QualifiedSlot || slot is ConstantProjectedSlot, "Case statement THEN can only have constants or members."); DbExpression cqt = slot.AsCqt(row, outputMember); cqt = WithRelationshipsClauseAsCqt(row, cqt, withRelationships, slot); return cqt; } private static DbExpression WithRelationshipsClauseAsCqt(DbExpression row, DbExpression slotValueExpr, IEnumerable withRelationships, ProjectedSlot slot) { List relatedEntityRefs = new List(); WithRelationshipsClauseAsCql( // emitWithRelationship action (withRelationship) => { relatedEntityRefs.Add(withRelationship.AsCqt(row)); }, withRelationships, slot); if (relatedEntityRefs.Count > 0) { DbNewInstanceExpression typeConstructor = slotValueExpr as DbNewInstanceExpression; Debug.Assert(typeConstructor != null && typeConstructor.ResultType.EdmType.BuiltInTypeKind == BuiltInTypeKind.EntityType, "WITH RELATIONSHIP clauses should be specified for entity type constructors only."); return DbExpressionBuilder.CreateNewEntityWithRelationshipsExpression( (EntityType)typeConstructor.ResultType.EdmType, typeConstructor.Arguments, relatedEntityRefs); } else { return slotValueExpr; } } private static void WithRelationshipsClauseAsCql(Action emitWithRelationship, IEnumerable withRelationships, ProjectedSlot slot) { if (withRelationships != null && withRelationships.Count() > 0) { ConstantProjectedSlot constantSlot = slot as ConstantProjectedSlot; Debug.Assert(constantSlot != null, "WITH RELATIONSHIP clauses should be specified for type constant slots only."); TypeConstant typeConstant = constantSlot.CellConstant as TypeConstant; Debug.Assert(typeConstant != null, "WITH RELATIONSHIP clauses should be there for type constants only."); EdmType fromType = typeConstant.EdmType; foreach (WithRelationship withRelationship in withRelationships) { // Add With statement for the types that participate in the association. if (withRelationship.FromEndEntityType.IsAssignableFrom(fromType)) { emitWithRelationship(withRelationship); } } } } internal override void ToCompactString(StringBuilder builder) { builder.AppendLine("CASE"); foreach (WhenThen clause in m_clauses) { builder.Append(" WHEN "); clause.Condition.ToCompactString(builder); builder.Append(" THEN "); clause.Value.ToCompactString(builder); builder.AppendLine(); } if (m_elseValue != null) { builder.Append(" ELSE "); m_elseValue.ToCompactString(builder); builder.AppendLine(); } builder.Append(" END AS "); m_memberPath.ToCompactString(builder); } #endregion /// /// A class that stores WHEN condition THEN value. /// internal sealed class WhenThen : InternalBase { #region Constructor /// /// Creates WHEN condition THEN value. /// internal WhenThen(BoolExpression condition, ProjectedSlot value) { m_condition = condition; m_value = value; } #endregion #region Fields private readonly BoolExpression m_condition; private readonly ProjectedSlot m_value; #endregion #region Properties /// /// Returns WHEN condition. /// internal BoolExpression Condition { get { return m_condition; } } /// /// Returns THEN value. /// internal ProjectedSlot Value { get { return m_value; } } #endregion #region String Methods internal WhenThen ReplaceWithQualifiedSlot(CqlBlock block) { // Change the THEN part ProjectedSlot newValue = m_value.DeepQualify(block); return new WhenThen(m_condition, newValue); } internal override void ToCompactString(StringBuilder builder) { builder.Append("WHEN "); m_condition.ToCompactString(builder); builder.Append("THEN "); m_value.ToCompactString(builder); } #endregion } } }