30ad18fab4b9c82ab60dd91596396fa9c1842d09
[mono.git] / mcs / class / referencesource / System.Data.Entity / System / Data / Mapping / Update / Internal / Propagator.JoinPropagator.cs
1 //---------------------------------------------------------------------
2 // <copyright file="JoinPropagator.cs" company="Microsoft">
3 //      Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //
6 // @owner Microsoft
7 // @backupOwner Microsoft
8 //---------------------------------------------------------------------
9
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;
16 using System.Linq;
17
18 namespace System.Data.Mapping.Update.Internal
19 {
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
25     {
26         /// <summary>
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).
32         /// </summary>
33         /// <remarks>
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').
37         /// </remarks>
38         /// <remarks>
39         /// Propagation rules for joins are stored in static fields of the class (initialized in the static
40         /// constructor for the class).
41         /// </remarks>
42         private partial class JoinPropagator
43         {
44             #region Constructors
45             /// <summary>
46             /// Constructs a join propagator.
47             /// </summary>
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)
53             {
54                 EntityUtil.CheckArgumentNull(left, "left");
55                 EntityUtil.CheckArgumentNull(right, "right");
56                 EntityUtil.CheckArgumentNull(node, "node");
57                 EntityUtil.CheckArgumentNull(parent, "parent");
58
59                 m_left = left;
60                 m_right = right;
61                 m_joinExpression = node;
62                 m_parent = parent;
63
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)
68                 {
69                     m_insertRules = s_innerJoinInsertRules;
70                     m_deleteRules = s_innerJoinDeleteRules;
71                 }
72                 else
73                 {
74                     m_insertRules = s_leftOuterJoinInsertRules;
75                     m_deleteRules = s_leftOuterJoinDeleteRules;
76                 }
77
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);
80
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);
84             }
85             #endregion
86
87             #region Fields
88             #region Propagation rules
89             /**
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.
93              * */
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;
98             #endregion
99
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;
110             #endregion
111
112             #region Methods
113             /// <summary>
114             /// Initialize rules.
115             /// </summary>
116             static JoinPropagator()
117             {
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);
122
123                 #region Initialize propagation rules
124                 // These rules are taken from the mapping.update.design.doc, Section 3.5.1.3
125                 // <Input, 
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);
135
136                 InitializeRule(Ops.LeftDelete | Ops.RightDelete, 
137                     Ops.Nothing,
138                     Ops.LeftDeleteJoinRightDelete,
139                     Ops.Nothing,
140                     Ops.LeftDeleteJoinRightDelete);
141
142                 InitializeRule(Ops.LeftInsert | Ops.RightInsert, 
143                     Ops.LeftInsertJoinRightInsert, 
144                     Ops.Nothing,
145                     Ops.LeftInsertJoinRightInsert, 
146                     Ops.Nothing);
147
148                 InitializeRule(Ops.LeftUpdate, 
149                     Ops.LeftInsertUnknownExtended, 
150                     Ops.LeftDeleteUnknownExtended,
151                     Ops.LeftInsertUnknownExtended, 
152                     Ops.LeftDeleteUnknownExtended);
153
154                 InitializeRule(Ops.RightUpdate, 
155                     Ops.RightInsertUnknownExtended, 
156                     Ops.RightDeleteUnknownExtended,
157                     Ops.RightInsertUnknownExtended, 
158                     Ops.RightDeleteUnknownExtended);
159
160                 InitializeRule(Ops.LeftUpdate | Ops.RightDelete, 
161                     Ops.Unsupported,
162                     Ops.Unsupported, 
163                     Ops.LeftInsertNullModifiedExtended, 
164                     Ops.LeftDeleteJoinRightDelete);
165
166                 InitializeRule(Ops.LeftUpdate | Ops.RightInsert, 
167                     Ops.Unsupported, 
168                     Ops.Unsupported,
169                     Ops.LeftInsertJoinRightInsert, 
170                     Ops.LeftDeleteNullModifiedExtended);
171
172                 InitializeRule(Ops.LeftDelete, 
173                     Ops.Unsupported, 
174                     Ops.Unsupported,
175                     Ops.Nothing, 
176                     Ops.LeftDeleteNullPreserveExtended);
177
178                 InitializeRule(Ops.LeftInsert, 
179                     Ops.Unsupported, 
180                     Ops.Unsupported,
181                     Ops.LeftInsertNullModifiedExtended, 
182                     Ops.Nothing);
183
184                 InitializeRule(Ops.RightDelete, 
185                     Ops.Unsupported, 
186                     Ops.Unsupported,
187                     Ops.LeftUnknownNullModifiedExtended, 
188                     Ops.RightDeleteUnknownExtended);
189
190                 InitializeRule(Ops.RightInsert, 
191                     Ops.Unsupported, 
192                     Ops.Unsupported,
193                     Ops.RightInsertUnknownExtended, 
194                     Ops.LeftUnknownNullModifiedExtended);
195
196                 InitializeRule(Ops.LeftDelete | Ops.RightUpdate, 
197                     Ops.Unsupported, 
198                     Ops.Unsupported,
199                     Ops.Unsupported, 
200                     Ops.Unsupported);
201
202                 InitializeRule(Ops.LeftDelete | Ops.RightInsert,
203                     Ops.Unsupported,
204                     Ops.Unsupported,
205                     Ops.Unsupported,
206                     Ops.Unsupported);
207
208                 InitializeRule(Ops.LeftInsert | Ops.RightUpdate,
209                     Ops.Unsupported,
210                     Ops.Unsupported,
211                     Ops.Unsupported,
212                     Ops.Unsupported);
213
214                 InitializeRule(Ops.LeftInsert | Ops.RightDelete,
215                     Ops.Unsupported,
216                     Ops.Unsupported,
217                     Ops.Unsupported,
218                     Ops.Unsupported);
219                 #endregion
220             }
221
222             /// <summary>
223             /// Initializes propagation rules for a specific input combination.
224             /// </summary>
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)
231             {
232                 s_innerJoinInsertRules.Add(input, joinInsert);
233                 s_innerJoinDeleteRules.Add(input, joinDelete);
234                 s_leftOuterJoinInsertRules.Add(input, lojInsert);
235                 s_leftOuterJoinDeleteRules.Add(input, lojDelete);
236
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");
242
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)");
248             }
249
250             /// <summary>
251             /// Performs join propagation.
252             /// </summary>
253             /// <returns>Changes propagated to the current join node in the update mapping view.</returns>
254             internal ChangeNode Propagate()
255             {
256                 // Construct an empty change node for the result
257                 ChangeNode result = Propagator.BuildChangeNode(m_joinExpression);
258
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);
269
270                 // Perform propagation one key at a time
271                 foreach (CompositeKey key in allKeys)
272                 {
273                     Propagate(key, result, leftDeletes, leftInserts, rightDeletes, rightInserts);
274                 }
275
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);
278
279                 return result;
280             }
281
282             /// <summary>
283             /// Propagate all changes associated with a particular join key.
284             /// </summary>
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)
289             {
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;
295
296                 Ops input = Ops.Nothing;
297
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; }
302
303                 // Get propagation rules for the changes
304                 Ops insertRule = m_insertRules[input];
305                 Ops deleteRule = m_deleteRules[input];
306
307                 if (Ops.Unsupported == insertRule || Ops.Unsupported == deleteRule)
308                 {
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.
314
315                     // First gather state entries contributing to the problem
316                     List<IEntityStateEntry> stateEntries = new List<IEntityStateEntry>();
317                     Action<Tuple<CompositeKey, PropagatorResult>> addStateEntries = (r) =>
318                         {
319                             if (r != null)
320                             {
321                                 stateEntries.AddRange(SourceInterpreter.GetAllStateEntries(r.Item2, this.m_parent.m_updateTranslator,
322                                     this.m_parent.m_table));
323                             }
324                         };
325                     addStateEntries(leftInsert);
326                     addStateEntries(leftDelete);
327                     addStateEntries(rightInsert);
328                     addStateEntries(rightDelete);
329
330                     throw EntityUtil.Update(Strings.Update_InvalidChanges, null, stateEntries);
331                 }
332
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))
339                 {
340                     leftInsert = Tuple.Create(key, LeftPlaceholder(key, PopulateMode.Unknown));
341                 }
342                 if (0 != (Ops.LeftUnknown & deleteRule))
343                 {
344                     leftDelete = Tuple.Create(key, LeftPlaceholder(key, PopulateMode.Unknown));
345                 }
346                 if (0 != (Ops.RightNullModified & insertRule)) 
347                 {
348                     rightInsert = Tuple.Create(key, RightPlaceholder(key, PopulateMode.NullModified));
349                 }
350                 else if (0 != (Ops.RightNullPreserve & insertRule)) 
351                 {
352                     rightInsert = Tuple.Create(key, RightPlaceholder(key, PopulateMode.NullPreserve));
353                 }
354                 else if (0 != (Ops.RightUnknown & insertRule)) 
355                 { 
356                     rightInsert = Tuple.Create(key, RightPlaceholder(key, PopulateMode.Unknown)); 
357                 }
358
359                 if (0 != (Ops.RightNullModified & deleteRule)) 
360                 {
361                     rightDelete = Tuple.Create(key, RightPlaceholder(key, PopulateMode.NullModified));
362                 }
363                 else if (0 != (Ops.RightNullPreserve & deleteRule)) 
364                 {
365                     rightDelete = Tuple.Create(key, RightPlaceholder(key, PopulateMode.NullPreserve));
366                 }
367                 else if (0 != (Ops.RightUnknown & deleteRule)) 
368                 {
369                     rightDelete = Tuple.Create(key, RightPlaceholder(key, PopulateMode.Unknown));
370                 }
371
372                 // Populate elements in join output
373                 if (null != leftInsert && null != rightInsert)
374                 {
375                     result.Inserted.Add(CreateResultTuple(leftInsert, rightInsert, result));
376                 }
377                 if (null != leftDelete && null != rightDelete)
378                 {
379                     result.Deleted.Add(CreateResultTuple(leftDelete, rightDelete, result));
380                 }
381             }
382
383             /// <summary>
384             /// Produce a tuple containing joined rows.
385             /// </summary>
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)
393             {
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))
401                 {
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
406                     // from both sides)
407                     map = new Dictionary<PropagatorResult, PropagatorResult>();
408                     for (int i = 0; i < leftKey.KeyComponents.Length; i++)
409                     {
410                         map[leftKey.KeyComponents[i]] = mergedKey.KeyComponents[i];
411                         map[rightKey.KeyComponents[i]] = mergedKey.KeyComponents[i];
412                     }
413                 }
414
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);
419
420                 // replace with merged key values as appropriate
421                 if (null != map)
422                 {
423                     PropagatorResult replacement;
424                     join = join.Replace(original => map.TryGetValue(original, out replacement) ? replacement : original);
425                 }
426
427                 return join;
428             }
429
430             /// <summary>
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.
433             /// </summary>
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)
440             {
441                 return PlaceholderPopulator.Populate(m_left.Placeholder, key, m_leftPlaceholderKey, mode, m_parent.UpdateTranslator);
442             }
443
444             /// <summary>
445             /// See <see cref="LeftPlaceholder"></see>
446             /// </summary>
447             /// <param name="key"></param>
448             /// <param name="mode"></param>
449             /// <returns></returns>
450             private PropagatorResult RightPlaceholder(CompositeKey key, PopulateMode mode)
451             {
452
453                 return PlaceholderPopulator.Populate(m_right.Placeholder, key, m_rightPlaceholderKey, mode, m_parent.UpdateTranslator);
454             }
455
456             /// <summary>
457             /// Produces a hash table of all instances and processes join keys, adding them to the list
458             /// of keys handled by this node.
459             /// </summary>
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)
464             {
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
467                 // state manager).
468                 var hash = new JoinDictionary(m_parent.UpdateTranslator.KeyComparer);
469
470                 foreach (PropagatorResult instance in instances)
471                 {
472                     CompositeKey key = ExtractKey(instance, keySelectors, m_parent);
473                     hash[key] = Tuple.Create(key, instance);
474                 }
475
476                 return hash;
477             }
478
479             // extracts key values from row expression
480             private static CompositeKey ExtractKey(PropagatorResult change, ReadOnlyCollection<DbExpression> keySelectors, Propagator parent)
481             {
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++)
485                 {
486                     PropagatorResult constant = Evaluator.Evaluate(keySelectors[i], change, parent);
487                     keyValues[i] = constant;
488                 }
489                 return new CompositeKey(keyValues);
490             }
491             #endregion
492
493             #region Nested types
494             /// <summary>
495             /// Flags indicating which change elements are available (0-4) and propagation
496             /// rules (0, 5-512)
497             /// </summary>
498             [Flags]
499             enum Ops : uint
500             {
501                 Nothing = 0,
502                 LeftInsert = 1,
503                 LeftDelete = 2,
504                 RightInsert = 4,
505                 RightDelete = 8,
506                 LeftUnknown = 32,
507                 RightNullModified = 128,
508                 RightNullPreserve = 256,
509                 RightUnknown = 512,
510                 LeftUpdate = LeftInsert | LeftDelete,
511                 RightUpdate = RightInsert | RightDelete,
512                 Unsupported = 4096,
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,
526                 #endregion
527             }
528             #endregion
529         }
530     }
531 }