|
//---------------------------------------------------------------------
// <copyright file="CaseStatement.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner Microsoft
// @backupOwner Microsoft
//---------------------------------------------------------------------
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
{
/// <summary>
/// A class to denote a case statement:
/// CASE
/// WHEN condition1 THEN value1
/// WHEN condition2 THEN value2
/// ...
/// END
/// </summary>
internal sealed class CaseStatement : InternalBase
{
#region Constructors
/// <summary>
/// Creates a case statement for the <paramref name="memberPath"/> with no clauses.
/// </summary>
internal CaseStatement(MemberPath memberPath)
{
m_memberPath = memberPath;
m_clauses = new List<WhenThen>();
}
#endregion
#region Fields
/// <summary>
/// The field.
/// </summary>
private readonly MemberPath m_memberPath;
/// <summary>
/// All the WHEN THENs.
/// </summary>
private List<WhenThen> m_clauses;
/// <summary>
/// Value for the else clause.
/// </summary>
private ProjectedSlot m_elseValue;
private bool m_simplified = false;
#endregion
#region Properties
internal MemberPath MemberPath
{
get { return m_memberPath; }
}
internal List<WhenThen> Clauses
{
get { return m_clauses; }
}
internal ProjectedSlot ElseValue
{
get { return m_elseValue; }
}
#endregion
#region Methods
/// <summary>
/// Recursively qualifies all <see cref="ProjectedSlot"/>s and returns a new deeply qualified <see cref="CaseStatement"/>.
/// </summary>
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;
}
/// <summary>
/// Adds an expression of the form "WHEN <paramref name="condition"/> THEN <paramref name="value"/>".
/// This operation is not allowed after the <see cref="Simplify"/> call.
/// </summary>
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));
}
/// <summary>
/// Returns true if the <see cref="CaseStatement"/> depends on (projects) its slot in THEN value or ELSE value.
/// </summary>
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<EdmType> 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;
}
/// <summary>
/// Simplifies the <see cref="CaseStatement"/> so that unnecessary WHEN/THENs for nulls/undefined values are eliminated.
/// Also, adds an ELSE clause if possible.
/// </summary>
internal void Simplify()
{
if (m_simplified)
{
return;
}
List<CaseStatement.WhenThen> clauses = new List<CaseStatement.WhenThen>();
// 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;
}
/// <summary>
/// Generates eSQL for the current <see cref="CaseStatement"/>.
/// </summary>
internal StringBuilder AsEsql(StringBuilder builder, IEnumerable<WithRelationship> 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;
}
/// <summary>
/// Generates CQT for the current <see cref="CaseStatement"/>.
/// </summary>
internal DbExpression AsCqt(DbExpression row, IEnumerable<WithRelationship> withRelationships)
{
// Generate the Case WHEN .. THEN ..., WHEN ... THEN ..., END
List<DbExpression> conditions = new List<DbExpression>();
List<DbExpression> values = new List<DbExpression>();
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<WithRelationship> 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<WithRelationship> 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<WithRelationship> 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<WithRelationship> withRelationships, ProjectedSlot slot)
{
List<DbRelatedEntityRef> relatedEntityRefs = new List<DbRelatedEntityRef>();
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<WithRelationship> emitWithRelationship, IEnumerable<WithRelationship> 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
/// <summary>
/// A class that stores WHEN condition THEN value.
/// </summary>
internal sealed class WhenThen : InternalBase
{
#region Constructor
/// <summary>
/// Creates WHEN condition THEN value.
/// </summary>
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
/// <summary>
/// Returns WHEN condition.
/// </summary>
internal BoolExpression Condition
{
get { return m_condition; }
}
/// <summary>
/// Returns THEN value.
/// </summary>
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
}
}
}
|