/// <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. var result = new CaseStatement(m_memberPath); foreach (var whenThen in m_clauses) { var 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> /// Given the slot (<paramref name="foundSlot"/>) and its corresponding case statement (<paramref name="thisCaseStatement"/>), /// generates the slotinfos for the cql block producing the case statement. /// </summary> private SlotInfo[] CreateSlotInfosForCaseStatement(bool[] parentRequiredSlots, int foundSlot, CqlBlock childBlock, CaseStatement thisCaseStatement, IEnumerable<WithRelationship> withRelationships) { int numSlotsAddedByChildBlock = childBlock.Slots.Count - TotalSlots; SlotInfo[] slotInfos = new SlotInfo[TotalSlots + numSlotsAddedByChildBlock]; for (int slotNum = 0; slotNum < TotalSlots; slotNum++) { bool isProjected = childBlock.IsProjected(slotNum); bool isRequiredByParent = parentRequiredSlots[slotNum]; ProjectedSlot slot = childBlock.SlotValue(slotNum); MemberPath outputMember = GetOutputMemberPath(slotNum); if (slotNum == foundSlot) { // We need a case statement instead for this slot that we // are handling right now Debug.Assert(isRequiredByParent, "Case result not needed by parent"); // Get a case statement with all slots replaced by aliases slots CaseStatement newCaseStatement = thisCaseStatement.DeepQualify(childBlock); slot = new CaseStatementProjectedSlot(newCaseStatement, withRelationships); isProjected = true; // We are projecting this slot now } else if (isProjected && isRequiredByParent) { // We only alias something that is needed and is being projected by the child. // It is a qualified slot into the child block. slot = childBlock.QualifySlotWithBlockAlias(slotNum); } // For slots, if it is not required by the parent, we want to // set the isRequiredByParent for this slot to be // false. Furthermore, we do not want to introduce any "NULL // AS something" at this stage for slots not being // projected. So if the child does not project that slot, we // declare it as not being required by the parent (if such a // NULL was needed, it would have been pushed all the way // down to a non-case block. // Essentially, from a Case statement's parent perspective, // it is saying "If you can produce a slot either by yourself // or your children, please do. Otherwise, do not concoct anything" SlotInfo slotInfo = new SlotInfo(isRequiredByParent && isProjected, isProjected, slot, outputMember); slotInfos[slotNum] = slotInfo; } for (int i = TotalSlots; i < TotalSlots + numSlotsAddedByChildBlock; i++) { QualifiedSlot childAddedSlot = childBlock.QualifySlotWithBlockAlias(i); slotInfos[i] = new SlotInfo(true, true, childAddedSlot, childBlock.MemberPath(i)); } return slotInfos; }
// for backward compatibility: add (WHEN True THEN Type) for non-scalar types internal void AddTrivialCaseStatementsForConditionMembers() { for (var memberNum = 0; memberNum < _context.MemberMaps.ProjectedSlotMap.Count; memberNum++) { var memberPath = _context.MemberMaps.ProjectedSlotMap[memberNum]; if (!memberPath.IsScalarType() && !_caseStatements.ContainsKey(memberPath)) { Constant typeConstant = new TypeConstant(memberPath.EdmType); { var caseStmt = new CaseStatement(memberPath); caseStmt.AddWhenThen(BoolExpression.True, new ConstantProjectedSlot(typeConstant)); _caseStatements[memberPath] = caseStmt; } } } }
// Modifies _caseStatements and _topLevelWhereClause private List<LeftCellWrapper> RemapFromVariables() { var usedCells = new List<LeftCellWrapper>(); // remap CellIdBooleans appearing in WHEN clauses and in topLevelWhereClause so the first used cell = 0, second = 1, etc. // This ordering is exploited in CQL generation var newNumber = 0; var literalRemap = new Dictionary<BoolLiteral, BoolLiteral>(BoolLiteral.EqualityIdentifierComparer); foreach (var leftCellWrapper in _context.AllWrappersForExtent) { if (_usedViews.Contains(leftCellWrapper.FragmentQuery)) { usedCells.Add(leftCellWrapper); var oldNumber = leftCellWrapper.OnlyInputCell.CellNumber; if (newNumber != oldNumber) { literalRemap[new CellIdBoolean(_identifiers, oldNumber)] = new CellIdBoolean(_identifiers, newNumber); } newNumber++; } } if (literalRemap.Count > 0) { // Remap _from literals in WHERE clause _topLevelWhereClause = _topLevelWhereClause.RemapLiterals(literalRemap); // Remap _from literals in case statements var newCaseStatements = new Dictionary<MemberPath, CaseStatement>(); foreach (var entry in _caseStatements) { var newCaseStatement = new CaseStatement(entry.Key); Debug.Assert(entry.Value.ElseValue == null); foreach (var clause in entry.Value.Clauses) { newCaseStatement.AddWhenThen(clause.Condition.RemapLiterals(literalRemap), clause.Value); } newCaseStatements[entry.Key] = newCaseStatement; } _caseStatements = newCaseStatements; } return usedCells; }
private void AddElseDefaultToCaseStatement( MemberPath currentPath, CaseStatement caseStatement, List<Constant> domain, CellTreeNode rightDomainQuery, Tile<FragmentQuery> unionCaseRewriting) { Debug.Assert(_context.ViewTarget == ViewTarget.UpdateView, "Used for update views only"); Constant defaultValue; var hasDefaultValue = Domain.TryGetDefaultValueForMemberPath(currentPath, out defaultValue); if (false == hasDefaultValue || false == domain.Contains(defaultValue)) { Debug.Assert(unionCaseRewriting != null, "No union of rewritings for case statements"); var unionTree = TileToCellTree(unionCaseRewriting, _context); var configurationNeedsDefault = _context.RightFragmentQP.Difference( rightDomainQuery.RightFragmentQuery, unionTree.RightFragmentQuery); if (_context.RightFragmentQP.IsSatisfiable(configurationNeedsDefault)) { if (hasDefaultValue) { caseStatement.AddWhenThen(BoolExpression.True, new ConstantProjectedSlot(defaultValue)); } else { configurationNeedsDefault.Condition.ExpensiveSimplify(); var builder = new StringBuilder(); builder.AppendLine( Strings.ViewGen_No_Default_Value_For_Configuration(currentPath.PathToString(false /* for alias */))); RewritingValidator.EntityConfigurationToUserString(configurationNeedsDefault.Condition, builder); _errorLog.AddEntry( new ErrorLog.Record( ViewGenErrorCode.NoDefaultValue, builder.ToString(), _context.AllWrappersForExtent, String.Empty)); } } } }
private void GenerateCaseStatements( IEnumerable<MemberPath> members, HashSet<FragmentQuery> outputUsedViews) { // Compute right domain query - non-simplified version of "basic view" // It is used below to check whether we need a default value in a case statement var usedCells = _context.AllWrappersForExtent.Where(w => _usedViews.Contains(w.FragmentQuery)); CellTreeNode rightDomainQuery = new OpCellTreeNode( _context, CellTreeOpType.Union, usedCells.Select(wrapper => new LeafCellTreeNode(_context, wrapper)).ToArray()); foreach (var currentPath in members) { // Add the types can member have, i.e., its type and its subtypes var domain = GetDomain(currentPath).ToList(); var caseStatement = new CaseStatement(currentPath); Tile<FragmentQuery> unionCaseRewriting = null; // optimization for domain = {NULL, NOT_NULL} // Create a single case: WHEN True THEN currentPath // Reason: if the WHEN condition is not satisfied (say because of LOJ), then currentPath = NULL var needCaseStatement = !(domain.Count == 2 && domain.Contains(Constant.Null, Constant.EqualityComparer) && domain.Contains(Constant.NotNull, Constant.EqualityComparer)); { // go over the domain foreach (var domainValue in domain) { if (domainValue == Constant.Undefined && _context.ViewTarget == ViewTarget.QueryView) { // we cannot assume closed domain for query views; // if obtaining undefined is possible, we need to account for that caseStatement.AddWhenThen( BoolExpression.False /* arbitrary condition */, new ConstantProjectedSlot(Constant.Undefined)); continue; } TraceVerbose("CASE STATEMENT FOR {0}={1}", currentPath, domainValue); // construct WHERE clause for this value var memberConditionQuery = CreateMemberConditionQuery(currentPath, domainValue); Tile<FragmentQuery> caseRewriting; if (FindRewritingAndUsedViews( memberConditionQuery.Attributes, memberConditionQuery.Condition, outputUsedViews, out caseRewriting)) { if (_context.ViewTarget == ViewTarget.UpdateView) { unionCaseRewriting = (unionCaseRewriting != null) ? _qp.Union(unionCaseRewriting, caseRewriting) : caseRewriting; } if (needCaseStatement) { var isAlwaysTrue = AddRewritingToCaseStatement(caseRewriting, caseStatement, currentPath, domainValue); if (isAlwaysTrue) { break; } } } else { if (!IsDefaultValue(domainValue, currentPath)) { Debug.Assert(_context.ViewTarget == ViewTarget.UpdateView || !_config.IsValidationEnabled); if (!ErrorPatternMatcher.FindMappingErrors(_context, _domainMap, _errorLog)) { var builder = new StringBuilder(); var extentName = StringUtil.FormatInvariant("{0}", _extentPath); var objectString = _context.ViewTarget == ViewTarget.QueryView ? Strings.ViewGen_Entities : Strings.ViewGen_Tuples; if (_context.ViewTarget == ViewTarget.QueryView) { builder.AppendLine(Strings.Viewgen_CannotGenerateQueryViewUnderNoValidation(extentName)); } else { builder.AppendLine(Strings.ViewGen_Cannot_Disambiguate_MultiConstant(objectString, extentName)); } RewritingValidator.EntityConfigurationToUserString( memberConditionQuery.Condition, builder, _context.ViewTarget == ViewTarget.UpdateView); var record = new ErrorLog.Record( ViewGenErrorCode.AmbiguousMultiConstants, builder.ToString(), _context.AllWrappersForExtent, String.Empty); _errorLog.AddEntry(record); } } } } } if (_errorLog.Count == 0) { // for update views, add WHEN True THEN defaultValue // which will ultimately be translated into a (possibly implicit) ELSE clause if (_context.ViewTarget == ViewTarget.UpdateView && needCaseStatement) { AddElseDefaultToCaseStatement(currentPath, caseStatement, domain, rightDomainQuery, unionCaseRewriting); } if (caseStatement.Clauses.Count > 0) { TraceVerbose("{0}", caseStatement.ToString()); _caseStatements[currentPath] = caseStatement; } } } }
// returns true when the case statement is completed private bool AddRewritingToCaseStatement( Tile<FragmentQuery> rewriting, CaseStatement caseStatement, MemberPath currentPath, Constant domainValue) { var whenCondition = BoolExpression.True; // check whether the rewriting is always true or always false // if it's always true, we don't need any other WHEN clauses in the case statement // if it's always false, we don't need to add this WHEN clause to the case statement // given: domainQuery is satisfied. Check (domainQuery -> rewriting) var isAlwaysTrue = _qp.IsContainedIn(CreateTile(_domainQuery), rewriting); var isAlwaysFalse = _qp.IsDisjointFrom(CreateTile(_domainQuery), rewriting); Debug.Assert(!(isAlwaysTrue && isAlwaysFalse)); if (isAlwaysFalse) { return false; // don't need an unsatisfiable WHEN clause } if (isAlwaysTrue) { Debug.Assert(caseStatement.Clauses.Count == 0); } ProjectedSlot projectedSlot; if (domainValue.HasNotNull()) { projectedSlot = new MemberProjectedSlot(currentPath); } else { projectedSlot = new ConstantProjectedSlot(domainValue); } if (!isAlwaysTrue) { whenCondition = TileToBoolExpr(rewriting); } else { whenCondition = BoolExpression.True; } caseStatement.AddWhenThen(whenCondition, projectedSlot); return isAlwaysTrue; }
/// <summary> /// Creates a slot for <paramref name="statement"/>. /// </summary> internal CaseStatementProjectedSlot(CaseStatement statement, IEnumerable<WithRelationship> withRelationships) { m_caseStatement = statement; m_withRelationships = withRelationships; }
/// <summary> /// Creates new <see cref="ProjectedSlot"/> that is qualified with <paramref name="block"/>.CqlAlias. /// If current slot is composite (such as <see cref="CaseStatementProjectedSlot"/>, then this method recursively qualifies all parts /// and returns a new deeply qualified slot (as opposed to <see cref="CqlBlock.QualifySlotWithBlockAlias"/>). /// </summary> internal override ProjectedSlot DeepQualify(CqlBlock block) { CaseStatement newStatement = m_caseStatement.DeepQualify(block); return(new CaseStatementProjectedSlot(newStatement, null)); }
/// <summary> /// Creates a slot for <paramref name="statement"/>. /// </summary> internal CaseStatementProjectedSlot(CaseStatement statement, IEnumerable <WithRelationship> withRelationships) { m_caseStatement = statement; m_withRelationships = withRelationships; }
// effects: Generates a SlotInfo object for a slot of a join node. It // uses the type of the join operation (opType), whether the slot is // required by the parent or not (isRequiredSlot), the children of // this node (children) and the number of the slotNum private SlotInfo GetJoinSlotInfo(CellTreeOpType opType, bool isRequiredSlot, List <CqlBlock> children, int slotNum, CqlIdentifiers identifiers) { if (false == isRequiredSlot) { // The slot will not be used. So we can set the projected slot to be null SlotInfo unrequiredSlotInfo = new SlotInfo(false, false, null, GetMemberPath(slotNum)); return(unrequiredSlotInfo); } // For a required slot, determine the child who is contributing to this value int childDefiningSlot = -1; CaseStatement caseForOuterJoins = null; for (int childNum = 0; childNum < children.Count; childNum++) { CqlBlock child = children[childNum]; if (false == child.IsProjected(slotNum)) { continue; } // For keys, we can pick any child block. So the first // one that we find is fine as well if (IsKeySlot(slotNum)) { childDefiningSlot = childNum; break; } else if (opType == CellTreeOpType.IJ) { // For Inner Joins, most of the time, the entries will be // the same in all the children. However, in some cases, // we will end up with NULL in one child and an actual // value in another -- we should pick up the actual value in that case childDefiningSlot = GetInnerJoinChildForSlot(children, slotNum); break; } else { // For LOJs, we generate a case statement if more than // one child generates the value - until then we do not // create the caseForOuterJoins object if (childDefiningSlot != -1) { // We really need a case statement now // We have the value being generated by another child // We need to fetch the variable from the appropriate child Debug.Assert(false == IsBoolSlot(slotNum), "Boolean slots cannot come from two children"); if (caseForOuterJoins == null) { MemberPath outputMember = GetMemberPath(slotNum); caseForOuterJoins = new CaseStatement(outputMember); // Add the child that we had not added in the first shot AddCaseForOuterJoins(caseForOuterJoins, children[childDefiningSlot], slotNum, identifiers); } AddCaseForOuterJoins(caseForOuterJoins, child, slotNum, identifiers); } childDefiningSlot = childNum; } } MemberPath memberPath = GetMemberPath(slotNum); ProjectedSlot slot = null; // Generate the slot value -- case statement slot, or a qualified slot or null or false. // If case statement slot has nothing, treat it as null/empty. if (caseForOuterJoins != null && (caseForOuterJoins.Clauses.Count > 0 || caseForOuterJoins.ElseValue != null)) { caseForOuterJoins.Simplify(); slot = new CaseStatementProjectedSlot(caseForOuterJoins, null); } else if (childDefiningSlot >= 0) { slot = children[childDefiningSlot].QualifySlotWithBlockAlias(slotNum); } else { // need to produce output slot, but don't have a value // output NULL for fields or False for bools if (IsBoolSlot(slotNum)) { slot = new BooleanProjectedSlot(BoolExpression.False, identifiers, SlotToBoolIndex(slotNum)); } else { slot = new ConstantProjectedSlot(Domain.GetDefaultValueForMemberPath(memberPath, GetLeaves(), ViewgenContext.Config), memberPath); } } // We need to ensure that _from variables are never null since // view generation uses 2-valued boolean logic. // They can become null in outer joins. We compensate for it by // adding AND NOT NULL condition on boolean slots coming from outer joins. bool enforceNotNull = IsBoolSlot(slotNum) && ((opType == CellTreeOpType.LOJ && childDefiningSlot > 0) || opType == CellTreeOpType.FOJ); // We set isProjected to be true since we have come up with some value for it SlotInfo slotInfo = new SlotInfo(true, true, slot, memberPath, enforceNotNull); return(slotInfo); }