/// <summary> /// Copy Constructor /// </summary> internal Cell(Cell source) { m_cQuery = new CellQuery(source.m_cQuery); m_sQuery = new CellQuery(source.m_sQuery); m_label = new CellLabel(source.m_label); m_cellNumber = source.m_cellNumber; }
// effects: Creates a basic cell relation for query internal BasicCellRelation(CellQuery cellQuery, ViewCellRelation viewCellRelation, IEnumerable<MemberProjectedSlot> slots) : base(viewCellRelation.CellNumber) { m_cellQuery = cellQuery; m_slots = new List<MemberProjectedSlot>(slots); Debug.Assert(m_slots.Count > 0, "Cell relation with not even an exent?"); m_viewCellRelation = viewCellRelation; }
// effects: Creates a cell with the C and S queries private Cell(CellQuery cQuery, CellQuery sQuery, CellLabel label, int cellNumber) { Debug.Assert(label != null, "Cell lacks label"); m_cQuery = cQuery; m_sQuery = sQuery; m_label = label; m_cellNumber = cellNumber; Debug.Assert(m_sQuery.NumProjectedSlots == m_cQuery.NumProjectedSlots, "Cell queries disagree on the number of projected fields"); }
/// <summary> /// Creates an cql block representing the <paramref name="extent"/> (the FROM part). /// SELECT is given by <paramref name="slots"/>, WHERE by <paramref name="whereClause"/> and AS by <paramref name="blockAliasNum"/>. /// </summary> internal ExtentCqlBlock(EntitySetBase extent, CellQuery.SelectDistinct selectDistinct, SlotInfo[] slots, BoolExpression whereClause, CqlIdentifiers identifiers, int blockAliasNum) : base(slots, EmptyChildren, whereClause, identifiers, blockAliasNum) { m_extent = extent; m_nodeTableAlias = identifiers.GetBlockAlias(); m_selectDistinct = selectDistinct; }
// requires: domain not have any Negated constants other than NotNull // Also, cellQuery contains all final oneOfConsts or all partial oneOfConsts // cellquery must contain a whereclause of the form "True", "OneOfConst" or " // "OneOfConst AND ... AND OneOfConst" // slot must present in cellQuery and incomingDomain is the domain for it // effects: Returns the set of values that slot can take as restricted by cellQuery's whereClause private static bool TryGetDomainRestrictedByWhereClause(IEnumerable<Constant> domain, MemberProjectedSlot slot, CellQuery cellQuery, out CellConstantSet result) { var conditionsForSlot = cellQuery.GetConjunctsFromWhereClause() .Where(restriction => MemberPath.EqualityComparer.Equals(restriction.RestrictedMemberSlot.MemberPath, slot.MemberPath)) .Select(restriction => new CellConstantSet(restriction.Domain.Values, Constant.EqualityComparer)); //Debug.Assert(!conditionsForSlot.Skip(1).Any(), "More than one Clause with the same path"); if (!conditionsForSlot.Any()) { // If the slot was not mentioned in the query return the domain without restricting it result = new CellConstantSet(domain); return false; } // Now get all the possible values from domain and conditionValues CellConstantSet possibleValues = DeterminePossibleValues(conditionsForSlot.SelectMany(m => m.Select(c => c)), domain); Domain restrictedDomain = new Domain(domain, possibleValues); foreach (var conditionValues in conditionsForSlot) { // Domain derived from Edm-Type INTERSECTED with Conditions restrictedDomain = restrictedDomain.Intersect(new Domain(conditionValues, possibleValues)); } result = new CellConstantSet(restrictedDomain.Values, Constant.EqualityComparer); return !domain.SequenceEqual(result); }
//True = domain is restricted, False = domain is not restricted (because there is no condition) private static bool GetRestrictedOrUnrestrictedDomain(MemberProjectedSlot slot, CellQuery cellQuery, EdmItemCollection edmItemCollection, out CellConstantSet domain) { CellConstantSet domainValues = DeriveDomainFromMemberPath(slot.MemberPath, edmItemCollection, true /* leaveDomainUnbounded */); //Note, out domain is set even in the case where method call returns false return TryGetDomainRestrictedByWhereClause(domainValues, slot, cellQuery, out domain); }
internal static Cell CreateCS(CellQuery cQuery, CellQuery sQuery, CellLabel label, int cellNumber) { return new Cell(cQuery, sQuery, label, cellNumber); }
// effects: Given an existing cellquery, makes a new one based on it // but uses the slots as specified with newSlots private CellQuery(CellQuery existing, ProjectedSlot[] newSlots) : this(newSlots, existing.m_whereClause, existing.m_boolExprs, existing.m_selectDistinct, existing.m_extentMemberPath) { }
/// <summary> /// Copy Constructor /// </summary> internal CellQuery(CellQuery source) { m_basicCellRelation = source.m_basicCellRelation; m_boolExprs = source.m_boolExprs; m_selectDistinct = source.m_selectDistinct; m_extentMemberPath = source.m_extentMemberPath; m_originalWhereClause = source.m_originalWhereClause; m_projectedSlots = source.m_projectedSlots; m_whereClause = source.m_whereClause; }
// requires: projectedSlotMap which contains a mapping of the fields // for "this" to integers // effects: Align the fields of this cell query using the // projectedSlotMap and generates a new query into newMainQuery // Based on the re-aligned fields in this, re-aligns the // corresponding fields in otherQuery as well and modifies // newOtherQuery to contain it // Example: // input: Proj[A,B,"5"] = Proj[F,"7",G] // Proj[C,B] = Proj[H,I] // projectedSlotMap: A -> 0, B -> 1, C -> 2 // output: Proj[A,B,null] = Proj[F,"7",null] // Proj[null,B,C] = Proj[null,I,H] internal void CreateFieldAlignedCellQueries( CellQuery otherQuery, MemberProjectionIndex projectedSlotMap, out CellQuery newMainQuery, out CellQuery newOtherQuery) { // mainSlots and otherSlots hold the new slots for two queries var numAlignedSlots = projectedSlotMap.Count; var mainSlots = new ProjectedSlot[numAlignedSlots]; var otherSlots = new ProjectedSlot[numAlignedSlots]; // Go through the slots for this query and find the new slot for them for (var i = 0; i < m_projectedSlots.Length; i++) { var slot = m_projectedSlots[i] as MemberProjectedSlot; Debug.Assert(slot != null, "All slots during cell normalization must field slots"); // Get the the ith slot's variable and then get the // new slot number from the field map var newSlotNum = projectedSlotMap.IndexOf(slot.MemberPath); Debug.Assert(newSlotNum >= 0, "Field projected but not in projectedSlotMap"); mainSlots[newSlotNum] = m_projectedSlots[i]; otherSlots[newSlotNum] = otherQuery.m_projectedSlots[i]; // We ignore constants -- note that this is not the // isHighpriority or discriminator case. An example of this // is when (say) Address does not have zip but USAddress // does. Then the constraint looks like Pi_NULL, A, B(E) = // Pi_x, y, z(S) // We don't care about this null in the view generation of // the left side. Note that this could happen in inheritance // or in cases when say the S side has 20 fields but the C // side has only 3 - the other 17 are null or default. // NOTE: We allow such constants only on the C side and not // ont the S side. Otherwise, we can have a situation Pi_A, // B, C(E) = Pi_5, y, z(S) Then someone can set A to 7 and we // will not roundtrip. We check for this in validation } // Make the new cell queries with the new slots newMainQuery = new CellQuery(this, mainSlots); newOtherQuery = new CellQuery(otherQuery, otherSlots); }
// requires: All slots in this are join tree slots // This method is called for an S-side query // cQuery is the corresponding C-side query in the cell // sourceCell is the original cell for "this" and cQuery // effects: Checks if any of the columns in "this" are mapped to multiple properties in cQuery. If so, // returns an error record about the duplicated slots internal ErrorLog.Record CheckForDuplicateFields(CellQuery cQuery, Cell sourceCell) { // slotMap stores the slots on the S-side and the // C-side properties that it maps to var slotMap = new KeyToListMap<MemberProjectedSlot, int>(ProjectedSlot.EqualityComparer); // Note that this does work for self-association. In the manager // employee example, ManagerId and EmployeeId from the SEmployee // table map to the two ends -- Manager.ManagerId and // Employee.EmployeeId in the C Space for (var i = 0; i < m_projectedSlots.Length; i++) { var projectedSlot = m_projectedSlots[i]; var slot = projectedSlot as MemberProjectedSlot; Debug.Assert(slot != null, "All slots for this method must be JoinTreeSlots"); slotMap.Add(slot, i); } StringBuilder builder = null; // Now determine the entries that have more than one integer per slot var isErrorSituation = false; foreach (var slot in slotMap.Keys) { var indexes = slotMap.ListForKey(slot); Debug.Assert(indexes.Count >= 1, "Each slot must have one index at least"); if (indexes.Count > 1 && cQuery.AreSlotsEquivalentViaRefConstraints(indexes) == false) { // The column is mapped to more than one property and it // failed the "association corresponds to referential // constraints" check isErrorSituation = true; if (builder == null) { builder = new StringBuilder(Strings.ViewGen_Duplicate_CProperties(Extent.Name)); builder.AppendLine(); } var tmpBuilder = new StringBuilder(); for (var i = 0; i < indexes.Count; i++) { var index = indexes[i]; if (i != 0) { tmpBuilder.Append(", "); } // The slot must be a JoinTreeSlot. If it isn't it is an internal error var cSlot = (MemberProjectedSlot)cQuery.m_projectedSlots[index]; tmpBuilder.Append(cSlot.ToUserString()); } builder.AppendLine(Strings.ViewGen_Duplicate_CProperties_IsMapped(slot.ToUserString(), tmpBuilder.ToString())); } } if (false == isErrorSituation) { return null; } var record = new ErrorLog.Record(ViewGenErrorCode.DuplicateCPropertiesMapped, builder.ToString(), sourceCell, String.Empty); return record; }
// effects: Given an extent's ("extent") table fragment that is // contained inside typeMap, determine the cells that need to be // created and add them to cells // allTypes corresponds to all the different types that the type map // represents -- this parameter has something useful only if extent // is an entity set private void ExtractCellsFromTableFragment( EntitySetBase extent, StorageMappingFragment fragmentMap, Set<EdmType> allTypes, List<Cell> cells) { // create C-query components var cRootExtent = new MemberPath(extent); var cQueryWhereClause = BoolExpression.True; var cSlots = new List<ProjectedSlot>(); if (allTypes.Count > 0) { // Create a type condition for the extent, i.e., "extent in allTypes" cQueryWhereClause = BoolExpression.CreateLiteral(new TypeRestriction(cRootExtent, allTypes), null); } // create S-query components var sRootExtent = new MemberPath(fragmentMap.TableSet); var sQueryWhereClause = BoolExpression.True; var sSlots = new List<ProjectedSlot>(); // Association or entity set // Add the properties and the key properties to a list and // then process them in ExtractProperties ExtractProperties( fragmentMap.AllProperties, cRootExtent, cSlots, ref cQueryWhereClause, sRootExtent, sSlots, ref sQueryWhereClause); // limitation of MSL API: cannot assign constant values to table columns var cQuery = new CellQuery(cSlots, cQueryWhereClause, cRootExtent, CellQuery.SelectDistinct.No /*no distinct flag*/); var sQuery = new CellQuery( sSlots, sQueryWhereClause, sRootExtent, fragmentMap.IsSQueryDistinct ? CellQuery.SelectDistinct.Yes : CellQuery.SelectDistinct.No); var fragmentInfo = fragmentMap; Debug.Assert((fragmentInfo != null), "CSMappingFragment should support Line Info"); var label = new CellLabel(fragmentInfo); var cell = Cell.CreateCS(cQuery, sQuery, label, m_currentCellNumber); m_currentCellNumber++; cells.Add(cell); }
/// <summary> /// Given a cell, a member and a boolean condition on that member, creates additional cell /// which with the specified restriction on the member in addition to original condition. /// e.i conjunction of original condition AND member in newCondition /// /// Creation fails when the original condition contradicts new boolean condition /// /// ViewTarget tells whether MemberPath is in Cquery or SQuery /// </summary> private bool TryCreateAdditionalCellWithCondition( Cell originalCell, MemberPath memberToExpand, bool conditionValue, ViewTarget viewTarget, out Cell result) { Debug.Assert(originalCell != null); Debug.Assert(memberToExpand != null); result = null; //Create required structures var leftExtent = originalCell.GetLeftQuery(viewTarget).SourceExtentMemberPath; var rightExtent = originalCell.GetRightQuery(viewTarget).SourceExtentMemberPath; //Now for the given left-side projected member, find corresponding right-side member that it is mapped to var indexOfBooLMemberInProjection = originalCell.GetLeftQuery(viewTarget).GetProjectedMembers().TakeWhile(path => !path.Equals(memberToExpand)).Count(); var rightConditionMemberSlot = ((MemberProjectedSlot)originalCell.GetRightQuery(viewTarget).ProjectedSlotAt(indexOfBooLMemberInProjection)); var rightSidePath = rightConditionMemberSlot.MemberPath; var leftSlots = new List<ProjectedSlot>(); var rightSlots = new List<ProjectedSlot>(); //Check for impossible conditions (otehrwise we get inaccurate pre-validation errors) var negatedCondition = new ScalarConstant(!conditionValue); if (originalCell.GetLeftQuery(viewTarget).Conditions .Where(restriction => restriction.RestrictedMemberSlot.MemberPath.Equals(memberToExpand)) .Where(restriction => restriction.Domain.Values.Contains(negatedCondition)).Any() || originalCell.GetRightQuery(viewTarget).Conditions .Where(restriction => restriction.RestrictedMemberSlot.MemberPath.Equals(rightSidePath)) .Where(restriction => restriction.Domain.Values.Contains(negatedCondition)).Any()) { return false; } //End check //Create Projected Slots // Map all slots in original cell (not just keys) because some may be required (non nullable and no default) // and others may have not_null condition so MUST be projected. Rely on the user doing the right thing, otherwise // they will get the error message anyway for (var i = 0; i < originalCell.GetLeftQuery(viewTarget).NumProjectedSlots; i++) { leftSlots.Add(originalCell.GetLeftQuery(viewTarget).ProjectedSlotAt(i)); } for (var i = 0; i < originalCell.GetRightQuery(viewTarget).NumProjectedSlots; i++) { rightSlots.Add(originalCell.GetRightQuery(viewTarget).ProjectedSlotAt(i)); } //Create condition boolena expressions var leftQueryWhereClause = BoolExpression.CreateLiteral(new ScalarRestriction(memberToExpand, new ScalarConstant(conditionValue)), null); leftQueryWhereClause = BoolExpression.CreateAnd(originalCell.GetLeftQuery(viewTarget).WhereClause, leftQueryWhereClause); var rightQueryWhereClause = BoolExpression.CreateLiteral(new ScalarRestriction(rightSidePath, new ScalarConstant(conditionValue)), null); rightQueryWhereClause = BoolExpression.CreateAnd(originalCell.GetRightQuery(viewTarget).WhereClause, rightQueryWhereClause); //Create additional Cells var rightQuery = new CellQuery( rightSlots, rightQueryWhereClause, rightExtent, originalCell.GetRightQuery(viewTarget).SelectDistinctFlag); var leftQuery = new CellQuery( leftSlots, leftQueryWhereClause, leftExtent, originalCell.GetLeftQuery(viewTarget).SelectDistinctFlag); Cell newCell; if (viewTarget == ViewTarget.UpdateView) { newCell = Cell.CreateCS(rightQuery, leftQuery, originalCell.CellLabel, m_currentCellNumber); } else { newCell = Cell.CreateCS(leftQuery, rightQuery, originalCell.CellLabel, m_currentCellNumber); } m_currentCellNumber++; result = newCell; return true; }
MergeBoolExpressions(CellQuery query1, CellQuery query2, BoolExpression conjunct1, BoolExpression conjunct2, CellTreeOpType opType) { List<BoolExpression> bools1 = query1.BoolVars; List<BoolExpression> bools2 = query2.BoolVars; // Add conjuncts to both sets if needed if (false == conjunct1.IsTrue) { bools1 = BoolExpression.AddConjunctionToBools(bools1, conjunct1); } if (false == conjunct2.IsTrue) { bools2 = BoolExpression.AddConjunctionToBools(bools2, conjunct2); } // Perform merge Debug.Assert(bools1.Count == bools2.Count); List<BoolExpression> bools = new List<BoolExpression>(); // Both bools1[i] and bools2[i] be null for some of the i's. When // we merge two (leaf) cells (say), only one boolean each is set // in it; the rest are all nulls. If the SP/TM rules have been // applied, more than one boolean may be non-null in a cell query for (int i = 0; i < bools1.Count; i++) { BoolExpression merged = null; if (bools1[i] == null) { merged = bools2[i]; } else if (bools2[i] == null) { merged = bools1[i]; } else { if (opType == CellTreeOpType.IJ) { merged = BoolExpression.CreateAnd(bools1[i], bools2[i]); } else if (opType == CellTreeOpType.Union) { merged = BoolExpression.CreateOr(bools1[i], bools2[i]); } else if (opType == CellTreeOpType.LASJ) { merged = BoolExpression.CreateAnd(bools1[i], BoolExpression.CreateNot(bools2[i])); } else { Debug.Fail("No other operation expected for boolean merge"); } } if (merged != null) { merged.ExpensiveSimplify(); } bools.Add(merged); } return bools; }
// effects: Given two duplicate eliination choices, returns an OR of them static private CellQuery.SelectDistinct MergeDupl(CellQuery.SelectDistinct d1, CellQuery.SelectDistinct d2) { if (d1 == CellQuery.SelectDistinct.Yes || d2 == CellQuery.SelectDistinct.Yes) { return CellQuery.SelectDistinct.Yes; } else { return CellQuery.SelectDistinct.No; } }
// effects: Merges query2 with this according to the TM/SP rules for opType and // returns the merged result. canBooleansOverlap indicates whether the bools in this and query2 can overlap, i.e. // the same cells may have contributed to query2 and this earlier in the merge process internal bool TryMergeTwoCellQueries(CellQuery query1, CellQuery query2, CellTreeOpType opType, MemberDomainMap memberDomainMap, out CellQuery mergedQuery) { mergedQuery = null; // Initialize g1 and g2 according to the TM/SP rules for IJ, LOJ, Union, FOJ cases BoolExpression g1 = null; BoolExpression g2 = null; switch (opType) { case CellTreeOpType.IJ: break; case CellTreeOpType.LOJ: case CellTreeOpType.LASJ: g2 = BoolExpression.True; break; case CellTreeOpType.FOJ: case CellTreeOpType.Union: g1 = BoolExpression.True; g2 = BoolExpression.True; break; default: Debug.Fail("Unsupported operator"); break; } Dictionary<MemberPath, MemberPath> remap = new Dictionary<MemberPath, MemberPath>(MemberPath.EqualityComparer); //Continue merging only if both queries are over the same source MemberPath newRoot; if (!query1.Extent.Equals(query2.Extent)) { // could not merge return false; } else { newRoot = query1.SourceExtentMemberPath; } // Conjuncts for ANDing with the previous whereClauses BoolExpression conjunct1 = BoolExpression.True; BoolExpression conjunct2 = BoolExpression.True; BoolExpression whereClause = null; switch (opType) { case CellTreeOpType.IJ: // Project[D1, D2, A, B, C] Select[cond1 and cond2] (T) // We simply merge the two lists of booleans -- no conjuct is added // conjunct1 and conjunct2 don't change // query1.WhereCaluse AND query2.WhereCaluse Debug.Assert(g1 == null && g2 == null, "IJ does not affect g1 and g2"); whereClause = BoolExpression.CreateAnd(query1.WhereClause, query2.WhereClause); break; case CellTreeOpType.LOJ: // conjunct1 does not change since D1 remains as is // Project[D1, (expr2 and cond2 and G2) as D2, A, B, C] Select[cond1] (T) // D1 does not change. New d2 is the list of booleans expressions // for query2 ANDed with g2 AND query2.WhereClause Debug.Assert(g1 == null, "LOJ does not affect g1"); conjunct2 = BoolExpression.CreateAnd(query2.WhereClause, g2); // Just query1's whereclause whereClause = query1.WhereClause; break; case CellTreeOpType.FOJ: case CellTreeOpType.Union: // Project[(expr1 and cond1 and G1) as D1, (expr2 and cond2 and G2) as D2, A, B, C] Select[cond1] (T) // New D1 is a list -- newD1 = D1 AND query1.WhereClause AND g1 // New D1 is a list -- newD2 = D2 AND query2.WhereClause AND g2 conjunct1 = BoolExpression.CreateAnd(query1.WhereClause, g1); conjunct2 = BoolExpression.CreateAnd(query2.WhereClause, g2); // The new whereClause -- g1 AND query1.WhereCaluse OR g2 AND query2.WhereClause whereClause = BoolExpression.CreateOr(BoolExpression.CreateAnd(query1.WhereClause, g1), BoolExpression.CreateAnd(query2.WhereClause, g2)); break; case CellTreeOpType.LASJ: // conjunct1 does not change since D1 remains as is // Project[D1, (expr2 and cond2 and G2) as D2, A, B, C] Select[cond1] (T) // D1 does not change. New d2 is the list of booleans expressions // for query2 ANDed with g2 AND NOT query2.WhereClause Debug.Assert(g1 == null, "LASJ does not affect g1"); conjunct2 = BoolExpression.CreateAnd(query2.WhereClause, g2); whereClause = BoolExpression.CreateAnd(query1.WhereClause, BoolExpression.CreateNot(conjunct2)); break; default: Debug.Fail("Unsupported operator"); break; } // Create the various remapped parts for the cell query -- // boolean expressions, merged slots, whereclause, duplicate // elimination, join tree List<BoolExpression> boolExprs = MergeBoolExpressions(query1, query2, conjunct1, conjunct2, opType); //BoolExpression.RemapBools(boolExprs, remap); ProjectedSlot[] mergedSlots; if (false == ProjectedSlot.TryMergeRemapSlots(query1.ProjectedSlots, query2.ProjectedSlots, out mergedSlots)) { // merging failed because two different right slots go to same left slot return false; } whereClause = whereClause.RemapBool(remap); CellQuery.SelectDistinct elimDupl = MergeDupl(query1.SelectDistinctFlag, query2.SelectDistinctFlag); whereClause.ExpensiveSimplify(); mergedQuery = new CellQuery(mergedSlots, whereClause, boolExprs, elimDupl, newRoot); return true; }