1 //---------------------------------------------------------------------
2 // <copyright file="JoinPropagator.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
7 // @backupOwner Microsoft
8 //---------------------------------------------------------------------
10 using System.Collections.Generic;
11 using System.Collections.ObjectModel;
12 using System.Data.Common.CommandTrees;
13 using System.Data.Entity;
14 using System.Data.Metadata.Edm;
15 using System.Diagnostics;
18 namespace System.Data.Mapping.Update.Internal
20 // We use CompositeKey on both sides of the dictionary because it is used both to identify rows that should be
21 // joined (the Key part) and to carry context about the rows being joined (e.g. which components of the row
22 // correspond to the join key).
23 using JoinDictionary = Dictionary<CompositeKey, Tuple<CompositeKey, PropagatorResult>>;
24 internal partial class Propagator
27 /// Performs join propagation. The basic strategy is to identify changes (inserts, deletes)
28 /// on either side of the join that are related according to the join criteria. Support is restricted
29 /// to conjunctions of equality predicates of the form <c>left property == right property</c>.
30 /// When a group of related changes is identified, rules are applied based on the existence of
31 /// different components (e.g., a left insert + right insert).
34 /// The joins handled by this class are degenerate in the sense that a row in the 'left' input always
35 /// joins with at most one row in the 'right' input. The restrictions that allow for this assumption
36 /// are described in the update design spec (see 'Level 5 Optimization').
39 /// Propagation rules for joins are stored in static fields of the class (initialized in the static
40 /// constructor for the class).
42 private partial class JoinPropagator
46 /// Constructs a join propagator.
48 /// <param name="left">Result of propagating changes in the left input to the join</param>
49 /// <param name="right">Result of propagating changes in the right input to the join</param>
50 /// <param name="node">Join operator in update mapping view over which to propagate changes</param>
51 /// <param name="parent">Handler of propagation for the entire update mapping view</param>
52 internal JoinPropagator(ChangeNode left, ChangeNode right, DbJoinExpression node, Propagator parent)
54 EntityUtil.CheckArgumentNull(left, "left");
55 EntityUtil.CheckArgumentNull(right, "right");
56 EntityUtil.CheckArgumentNull(node, "node");
57 EntityUtil.CheckArgumentNull(parent, "parent");
61 m_joinExpression = node;
64 Debug.Assert(DbExpressionKind.LeftOuterJoin == node.ExpressionKind || DbExpressionKind.InnerJoin == node.ExpressionKind, "(Update/JoinPropagagtor/JoinEvaluator) " +
65 "caller must ensure only left outer and inner joins are requested");
66 // Retrieve propagation rules for the join type of the expression.
67 if (DbExpressionKind.InnerJoin == m_joinExpression.ExpressionKind)
69 m_insertRules = s_innerJoinInsertRules;
70 m_deleteRules = s_innerJoinDeleteRules;
74 m_insertRules = s_leftOuterJoinInsertRules;
75 m_deleteRules = s_leftOuterJoinDeleteRules;
78 // Figure out key selectors involved in the equi-join (if it isn't an equi-join, we don't support it)
79 JoinConditionVisitor.GetKeySelectors(node.JoinCondition, out m_leftKeySelectors, out m_rightKeySelectors);
81 // Find the key selector expressions in the left and right placeholders
82 m_leftPlaceholderKey = ExtractKey(m_left.Placeholder, m_leftKeySelectors, m_parent);
83 m_rightPlaceholderKey = ExtractKey(m_right.Placeholder, m_rightKeySelectors, m_parent);
88 #region Propagation rules
90 * These static dictionaries are initialized by the static constructor for this class.
91 * They describe for each combination of input elements (the key) propagation rules, which
92 * are expressions over the input expressions.
94 private static readonly Dictionary<Ops, Ops> s_innerJoinInsertRules;
95 private static readonly Dictionary<Ops, Ops> s_innerJoinDeleteRules;
96 private static readonly Dictionary<Ops, Ops> s_leftOuterJoinInsertRules;
97 private static readonly Dictionary<Ops, Ops> s_leftOuterJoinDeleteRules;
100 private readonly DbJoinExpression m_joinExpression;
101 private readonly Propagator m_parent;
102 private readonly Dictionary<Ops, Ops> m_insertRules;
103 private readonly Dictionary<Ops, Ops> m_deleteRules;
104 private readonly ReadOnlyCollection<DbExpression> m_leftKeySelectors;
105 private readonly ReadOnlyCollection<DbExpression> m_rightKeySelectors;
106 private readonly ChangeNode m_left;
107 private readonly ChangeNode m_right;
108 private readonly CompositeKey m_leftPlaceholderKey;
109 private readonly CompositeKey m_rightPlaceholderKey;
114 /// Initialize rules.
116 static JoinPropagator()
118 s_innerJoinInsertRules = new Dictionary<Ops,Ops>(EqualityComparer<Ops>.Default);
119 s_innerJoinDeleteRules = new Dictionary<Ops, Ops>(EqualityComparer<Ops>.Default);
120 s_leftOuterJoinInsertRules = new Dictionary<Ops, Ops>(EqualityComparer<Ops>.Default);
121 s_leftOuterJoinDeleteRules = new Dictionary<Ops, Ops>(EqualityComparer<Ops>.Default);
123 #region Initialize propagation rules
124 // These rules are taken from the mapping.update.design.doc, Section 3.5.1.3
126 // Inner join insert rule,
127 // Inner join delete rule,
128 // Left outer join insert rule,
129 // Left outer join delete rule>
130 InitializeRule(Ops.LeftUpdate | Ops.RightUpdate,
131 Ops.LeftInsertJoinRightInsert,
132 Ops.LeftDeleteJoinRightDelete,
133 Ops.LeftInsertJoinRightInsert,
134 Ops.LeftDeleteJoinRightDelete);
136 InitializeRule(Ops.LeftDelete | Ops.RightDelete,
138 Ops.LeftDeleteJoinRightDelete,
140 Ops.LeftDeleteJoinRightDelete);
142 InitializeRule(Ops.LeftInsert | Ops.RightInsert,
143 Ops.LeftInsertJoinRightInsert,
145 Ops.LeftInsertJoinRightInsert,
148 InitializeRule(Ops.LeftUpdate,
149 Ops.LeftInsertUnknownExtended,
150 Ops.LeftDeleteUnknownExtended,
151 Ops.LeftInsertUnknownExtended,
152 Ops.LeftDeleteUnknownExtended);
154 InitializeRule(Ops.RightUpdate,
155 Ops.RightInsertUnknownExtended,
156 Ops.RightDeleteUnknownExtended,
157 Ops.RightInsertUnknownExtended,
158 Ops.RightDeleteUnknownExtended);
160 InitializeRule(Ops.LeftUpdate | Ops.RightDelete,
163 Ops.LeftInsertNullModifiedExtended,
164 Ops.LeftDeleteJoinRightDelete);
166 InitializeRule(Ops.LeftUpdate | Ops.RightInsert,
169 Ops.LeftInsertJoinRightInsert,
170 Ops.LeftDeleteNullModifiedExtended);
172 InitializeRule(Ops.LeftDelete,
176 Ops.LeftDeleteNullPreserveExtended);
178 InitializeRule(Ops.LeftInsert,
181 Ops.LeftInsertNullModifiedExtended,
184 InitializeRule(Ops.RightDelete,
187 Ops.LeftUnknownNullModifiedExtended,
188 Ops.RightDeleteUnknownExtended);
190 InitializeRule(Ops.RightInsert,
193 Ops.RightInsertUnknownExtended,
194 Ops.LeftUnknownNullModifiedExtended);
196 InitializeRule(Ops.LeftDelete | Ops.RightUpdate,
202 InitializeRule(Ops.LeftDelete | Ops.RightInsert,
208 InitializeRule(Ops.LeftInsert | Ops.RightUpdate,
214 InitializeRule(Ops.LeftInsert | Ops.RightDelete,
223 /// Initializes propagation rules for a specific input combination.
225 /// <param name="input">Describes the elements available in the input</param>
226 /// <param name="joinInsert">Describes the rule for inserts when the operator is an inner join</param>
227 /// <param name="joinDelete">Describes the rule for deletes when the operator is an inner join</param>
228 /// <param name="lojInsert">Describes the rule for inserts when the operator is a left outer join</param>
229 /// <param name="lojDelete">Describes the rule for deletes when the operator is a left outer join</param>
230 private static void InitializeRule(Ops input, Ops joinInsert, Ops joinDelete, Ops lojInsert, Ops lojDelete)
232 s_innerJoinInsertRules.Add(input, joinInsert);
233 s_innerJoinDeleteRules.Add(input, joinDelete);
234 s_leftOuterJoinInsertRules.Add(input, lojInsert);
235 s_leftOuterJoinDeleteRules.Add(input, lojDelete);
237 // Ensure that the right hand side of each rule contains no requests for specific row values
238 // that are not also in the input.
239 Debug.Assert((((joinInsert | joinDelete | lojInsert | lojDelete) &
240 (Ops.LeftInsert | Ops.LeftDelete | Ops.RightInsert | Ops.RightDelete)) & (~input)) == Ops.Nothing,
241 "(Update/JoinPropagator/Initialization) Rules can't use unavailable data");
243 // An unknown value can appear in both the delete and insert rule result or neither.
244 Debug.Assert(((joinInsert ^ joinDelete) & (Ops.LeftUnknown | Ops.RightUnknown)) == Ops.Nothing &&
245 ((lojInsert ^ lojDelete) & (Ops.LeftUnknown | Ops.RightUnknown)) == Ops.Nothing,
246 "(Update/JoinPropagator/Initialization) Unknowns must appear in both delete and insert rules " +
247 "or in neither (in other words, for updates only)");
251 /// Performs join propagation.
253 /// <returns>Changes propagated to the current join node in the update mapping view.</returns>
254 internal ChangeNode Propagate()
256 // Construct an empty change node for the result
257 ChangeNode result = Propagator.BuildChangeNode(m_joinExpression);
259 // Gather all keys involved in the join
260 JoinDictionary leftDeletes = ProcessKeys(m_left.Deleted, m_leftKeySelectors);
261 JoinDictionary leftInserts = ProcessKeys(m_left.Inserted, m_leftKeySelectors);
262 JoinDictionary rightDeletes = ProcessKeys(m_right.Deleted, m_rightKeySelectors);
263 JoinDictionary rightInserts = ProcessKeys(m_right.Inserted, m_rightKeySelectors);
264 var allKeys = leftDeletes.Keys
265 .Concat(leftInserts.Keys)
266 .Concat(rightDeletes.Keys)
267 .Concat(rightInserts.Keys)
268 .Distinct(m_parent.UpdateTranslator.KeyComparer);
270 // Perform propagation one key at a time
271 foreach (CompositeKey key in allKeys)
273 Propagate(key, result, leftDeletes, leftInserts, rightDeletes, rightInserts);
276 // Construct a new placeholder (see ChangeNode.Placeholder) for the join result node.
277 result.Placeholder = CreateResultTuple(Tuple.Create((CompositeKey)null, m_left.Placeholder), Tuple.Create((CompositeKey)null, m_right.Placeholder), result);
283 /// Propagate all changes associated with a particular join key.
285 /// <param name="key">Key.</param>
286 /// <param name="result">Resulting changes are added to this result.</param>
287 private void Propagate(CompositeKey key, ChangeNode result, JoinDictionary leftDeletes, JoinDictionary leftInserts,
288 JoinDictionary rightDeletes, JoinDictionary rightInserts)
290 // Retrieve changes associates with this join key
291 Tuple<CompositeKey, PropagatorResult> leftInsert = null;
292 Tuple<CompositeKey, PropagatorResult> leftDelete = null;
293 Tuple<CompositeKey, PropagatorResult> rightInsert = null;
294 Tuple<CompositeKey, PropagatorResult> rightDelete = null;
296 Ops input = Ops.Nothing;
298 if (leftInserts.TryGetValue(key, out leftInsert)) { input |= Ops.LeftInsert; }
299 if (leftDeletes.TryGetValue(key, out leftDelete)) { input |= Ops.LeftDelete; }
300 if (rightInserts.TryGetValue(key, out rightInsert)) { input |= Ops.RightInsert; }
301 if (rightDeletes.TryGetValue(key, out rightDelete)) { input |= Ops.RightDelete; }
303 // Get propagation rules for the changes
304 Ops insertRule = m_insertRules[input];
305 Ops deleteRule = m_deleteRules[input];
307 if (Ops.Unsupported == insertRule || Ops.Unsupported == deleteRule)
309 // If no propagation rules are defined, it suggests an invalid workload (e.g.
310 // a required entity or relationship is missing). In general, such exceptions
311 // should be caught by the RelationshipConstraintValidator, but we defensively
312 // check for problems here regardless. For instance, a 0..1:1..1 self-assocation
313 // implied a stronger constraint that cannot be checked by RelationshipConstraintValidator.
315 // First gather state entries contributing to the problem
316 List<IEntityStateEntry> stateEntries = new List<IEntityStateEntry>();
317 Action<Tuple<CompositeKey, PropagatorResult>> addStateEntries = (r) =>
321 stateEntries.AddRange(SourceInterpreter.GetAllStateEntries(r.Item2, this.m_parent.m_updateTranslator,
322 this.m_parent.m_table));
325 addStateEntries(leftInsert);
326 addStateEntries(leftDelete);
327 addStateEntries(rightInsert);
328 addStateEntries(rightDelete);
330 throw EntityUtil.Update(Strings.Update_InvalidChanges, null, stateEntries);
333 // Where needed, substitute null/unknown placeholders. In some of the join propagation
334 // rules, we handle the case where a side of the join is 'unknown', or where one side
335 // of a join is comprised of an record containing only nulls. For instance, we may update
336 // only one extent appearing in a row of a table (unknown), or; we may insert only
337 // the left hand side of a left outer join, in which case the right hand side is 'null'.
338 if (0 != (Ops.LeftUnknown & insertRule))
340 leftInsert = Tuple.Create(key, LeftPlaceholder(key, PopulateMode.Unknown));
342 if (0 != (Ops.LeftUnknown & deleteRule))
344 leftDelete = Tuple.Create(key, LeftPlaceholder(key, PopulateMode.Unknown));
346 if (0 != (Ops.RightNullModified & insertRule))
348 rightInsert = Tuple.Create(key, RightPlaceholder(key, PopulateMode.NullModified));
350 else if (0 != (Ops.RightNullPreserve & insertRule))
352 rightInsert = Tuple.Create(key, RightPlaceholder(key, PopulateMode.NullPreserve));
354 else if (0 != (Ops.RightUnknown & insertRule))
356 rightInsert = Tuple.Create(key, RightPlaceholder(key, PopulateMode.Unknown));
359 if (0 != (Ops.RightNullModified & deleteRule))
361 rightDelete = Tuple.Create(key, RightPlaceholder(key, PopulateMode.NullModified));
363 else if (0 != (Ops.RightNullPreserve & deleteRule))
365 rightDelete = Tuple.Create(key, RightPlaceholder(key, PopulateMode.NullPreserve));
367 else if (0 != (Ops.RightUnknown & deleteRule))
369 rightDelete = Tuple.Create(key, RightPlaceholder(key, PopulateMode.Unknown));
372 // Populate elements in join output
373 if (null != leftInsert && null != rightInsert)
375 result.Inserted.Add(CreateResultTuple(leftInsert, rightInsert, result));
377 if (null != leftDelete && null != rightDelete)
379 result.Deleted.Add(CreateResultTuple(leftDelete, rightDelete, result));
384 /// Produce a tuple containing joined rows.
386 /// <param name="left">Left row.</param>
387 /// <param name="right">Right row.</param>
388 /// <param name="leftKey">Key used to join left element.</param>
389 /// <param name="rightKey">Key used to join right element.</param>
390 /// <param name="result">Result change node; used for type information.</param>
391 /// <returns>Result of joining the input rows.</returns>
392 private PropagatorResult CreateResultTuple(Tuple<CompositeKey, PropagatorResult> left, Tuple<CompositeKey, PropagatorResult> right, ChangeNode result)
394 // using ref compare to avoid triggering value based
395 CompositeKey leftKey = left.Item1;
396 CompositeKey rightKey = right.Item1;
397 Dictionary<PropagatorResult, PropagatorResult> map = null;
398 if (!object.ReferenceEquals(null, leftKey) &&
399 !object.ReferenceEquals(null, rightKey) &&
400 !object.ReferenceEquals(leftKey, rightKey))
402 // Merge key values from the left and the right (since they're equal, there's a possibility we'll
403 // project values only from the left or the right hand side and lose important context.)
404 CompositeKey mergedKey = leftKey.Merge(m_parent.m_updateTranslator.KeyManager, rightKey);
405 // create a dictionary so that we can replace key values with merged key values (carrying context
407 map = new Dictionary<PropagatorResult, PropagatorResult>();
408 for (int i = 0; i < leftKey.KeyComponents.Length; i++)
410 map[leftKey.KeyComponents[i]] = mergedKey.KeyComponents[i];
411 map[rightKey.KeyComponents[i]] = mergedKey.KeyComponents[i];
415 PropagatorResult[] joinRecordValues = new PropagatorResult[2];
416 joinRecordValues[0] = left.Item2;
417 joinRecordValues[1] = right.Item2;
418 PropagatorResult join = PropagatorResult.CreateStructuralValue(joinRecordValues, (StructuralType)result.ElementType.EdmType, false);
420 // replace with merged key values as appropriate
423 PropagatorResult replacement;
424 join = join.Replace(original => map.TryGetValue(original, out replacement) ? replacement : original);
431 /// Constructs a new placeholder record for the left hand side of the join. Values taken
432 /// from the join key are injected into the record.
434 /// <param name="key">Key producing the left hand side.</param>
435 /// <param name="mode">Mode used to populate the placeholder</param>
436 /// <returns>Record corresponding to the type of the left input to the join. Each value in
437 /// the record is flagged as <see cref="PropagatorFlags.Unknown" /> except when it is
438 /// a component of the key.</returns>
439 private PropagatorResult LeftPlaceholder(CompositeKey key, PopulateMode mode)
441 return PlaceholderPopulator.Populate(m_left.Placeholder, key, m_leftPlaceholderKey, mode, m_parent.UpdateTranslator);
445 /// See <see cref="LeftPlaceholder"></see>
447 /// <param name="key"></param>
448 /// <param name="mode"></param>
449 /// <returns></returns>
450 private PropagatorResult RightPlaceholder(CompositeKey key, PopulateMode mode)
453 return PlaceholderPopulator.Populate(m_right.Placeholder, key, m_rightPlaceholderKey, mode, m_parent.UpdateTranslator);
457 /// Produces a hash table of all instances and processes join keys, adding them to the list
458 /// of keys handled by this node.
460 /// <param name="instances">List of instances (whether delete or insert) for this node.</param>
461 /// <param name="keySelectors">Selectors for key components.</param>
462 /// <returns>A map from join keys to instances.</returns>
463 private JoinDictionary ProcessKeys(IEnumerable<PropagatorResult> instances, ReadOnlyCollection<DbExpression> keySelectors)
465 // Dictionary uses the composite key on both sides. This is because the composite key, in addition
466 // to supporting comparison, maintains some context information (e.g., source of a value in the
468 var hash = new JoinDictionary(m_parent.UpdateTranslator.KeyComparer);
470 foreach (PropagatorResult instance in instances)
472 CompositeKey key = ExtractKey(instance, keySelectors, m_parent);
473 hash[key] = Tuple.Create(key, instance);
479 // extracts key values from row expression
480 private static CompositeKey ExtractKey(PropagatorResult change, ReadOnlyCollection<DbExpression> keySelectors, Propagator parent)
482 Debug.Assert(null != change && null != keySelectors && null != parent);
483 PropagatorResult[] keyValues = new PropagatorResult[keySelectors.Count];
484 for (int i = 0; i < keySelectors.Count; i++)
486 PropagatorResult constant = Evaluator.Evaluate(keySelectors[i], change, parent);
487 keyValues[i] = constant;
489 return new CompositeKey(keyValues);
495 /// Flags indicating which change elements are available (0-4) and propagation
507 RightNullModified = 128,
508 RightNullPreserve = 256,
510 LeftUpdate = LeftInsert | LeftDelete,
511 RightUpdate = RightInsert | RightDelete,
513 #region Propagation rule descriptions
514 LeftInsertJoinRightInsert = LeftInsert | RightInsert,
515 LeftDeleteJoinRightDelete = LeftDelete | RightDelete,
516 LeftInsertNullModifiedExtended = LeftInsert | RightNullModified,
517 LeftInsertNullPreserveExtended = LeftInsert | RightNullPreserve,
518 LeftInsertUnknownExtended = LeftInsert | RightUnknown,
519 LeftDeleteNullModifiedExtended = LeftDelete | RightNullModified,
520 LeftDeleteNullPreserveExtended = LeftDelete | RightNullPreserve,
521 LeftDeleteUnknownExtended = LeftDelete | RightUnknown,
522 LeftUnknownNullModifiedExtended = LeftUnknown | RightNullModified,
523 LeftUnknownNullPreserveExtended = LeftUnknown | RightNullPreserve,
524 RightInsertUnknownExtended = LeftUnknown | RightInsert,
525 RightDeleteUnknownExtended = LeftUnknown | RightDelete,