DeveelDB  20151217
complete SQL database system, primarly developed for .NET/Mono frameworks
QueryTablePlanner.cs
Go to the documentation of this file.
1 //
2 // Copyright 2010-2015 Deveel
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 // http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 //
16 
17 using System;
18 using System.Collections.Generic;
19 using System.Linq;
20 
22 
23 namespace Deveel.Data.Sql.Query {
25  private List<TablePlan> tablePlans;
26 
27  public QueryTablePlanner() {
28  tablePlans = new List<TablePlan>();
29  HasJoin = false;
30  }
31 
32  public bool HasJoin { get; private set; }
33 
34  public TablePlan SinglePlan {
35  get {
36  if (tablePlans.Count != 1)
37  throw new InvalidOperationException("The planner has more than one table.");
38 
39  return tablePlans[0];
40  }
41  }
42 
43  private int IndexOfPlan(TablePlan plan) {
44  int sz = tablePlans.Count;
45  for (int i = 0; i < sz; ++i) {
46  if (tablePlans[i] == plan)
47  return i;
48  }
49 
50  return -1;
51  }
52 
53  private static TablePlan ConcatPlans(TablePlan left, TablePlan right, IQueryPlanNode plan) {
54  // Merge the variable list
55  var newVarList = new ObjectName[left.ColumnNames.Length + right.ColumnNames.Length];
56  Array.Copy(left.ColumnNames, 0, newVarList, 0, left.ColumnNames.Length);
57  Array.Copy(right.ColumnNames, 0, newVarList, left.ColumnNames.Length - 1, right.ColumnNames.Length);
58 
59  // Merge the unique table names list
60  var newUniqueList = new string[left.UniqueNames.Length + right.UniqueNames.Length];
61  Array.Copy(left.UniqueNames, 0, newUniqueList, 0, left.UniqueNames.Length);
62  Array.Copy(right.UniqueNames, 0, newUniqueList, left.UniqueNames.Length - 1, right.UniqueNames.Length);
63 
64  // Return the new table source plan.
65  return new TablePlan(plan, newVarList, newUniqueList);
66  }
67 
68  private TablePlan MergePlans(TablePlan left, TablePlan right, IQueryPlanNode mergePlan) {
69  // Remove the sources from the table list.
70  tablePlans.Remove(left);
71  tablePlans.Remove(right);
72 
73  // Add the concatenation of the left and right tables.
74  var newPlan = ConcatPlans(left, right, mergePlan);
75  newPlan.MergeJoin(left, right);
76  newPlan.SetUpdated();
77 
78  AddPlan(newPlan);
79 
80  return newPlan;
81  }
82 
83  private TablePlan FindPlan(ObjectName reference) {
84  // If there is only 1 plan then assume the variable is in there.
85  if (tablePlans.Count == 1)
86  return tablePlans[0];
87 
88  foreach (var source in tablePlans) {
89  if (source.ContainsColumn(reference))
90  return source;
91  }
92 
93  throw new ArgumentException("Unable to find table with variable reference: " + reference);
94  }
95 
96  private TablePlan FindCommonPlan(IList<ObjectName> columnNames) {
97  if (columnNames.Count == 0)
98  return null;
99 
100  TablePlan pivotPlan = null;
101  foreach (var columnName in columnNames) {
102  var plan = FindPlan(columnName);
103  if (pivotPlan == null) {
104  pivotPlan = plan;
105  } else if (plan != pivotPlan) {
106  return null;
107  }
108  }
109 
110  return pivotPlan;
111  }
112 
113  private void SetCachePoints() {
114  foreach (var plan in tablePlans) {
115  plan.SetCachePoint();
116  }
117  }
118 
119  private TablePlan JoinPlansForColumns(IEnumerable<ObjectName> columnNames) {
120  // Collect all the plans that encapsulate these variables.
121  var touchedPlans = new List<TablePlan>();
122 
123  foreach (var name in columnNames) {
124  var plan = FindPlan(name);
125 
126  if (!touchedPlans.Contains(plan))
127  touchedPlans.Add(plan);
128  }
129 
130  return JoinToSingle(touchedPlans);
131  }
132 
134  int sz = tablePlans.Count;
135  if (sz == 1)
136  return tablePlans[0];
137 
138  // Produce a plan that naturally joins all tables.
139  return JoinToSingle(tablePlans);
140  }
141 
142  private static int AssertBeNaturalJoin(TablePlan plan1, TablePlan plan2) {
143  if (plan1.LeftPlan == plan2 || plan1.RightPlan == plan2)
144  return 0;
145  if (plan1.LeftPlan != null && plan2.LeftPlan != null)
146  // This is a left clash
147  return 2;
148  if (plan1.RightPlan != null && plan2.RightPlan != null)
149  // This is a right clash
150  return 1;
151  if ((plan1.LeftPlan == null && plan2.RightPlan == null) ||
152  (plan1.RightPlan == null && plan2.LeftPlan == null))
153  // This means a merge between the plans is fine
154  return 0;
155 
156  // Must be a left and right clash
157  return 2;
158  }
159 
161  JoinType joinType;
162  SqlExpression onExpr;
163  TablePlan leftPlan, rightPlan;
164 
165  // Are the plans linked by common join information?
166  if (plan1.RightPlan == plan2) {
167  joinType = plan1.RightJoinType;
168  onExpr = plan1.RightOnExpression;
169  leftPlan = plan1;
170  rightPlan = plan2;
171  } else if (plan1.LeftPlan == plan2) {
172  joinType = plan1.LeftJoinType;
173  onExpr = plan1.LeftOnExpression;
174  leftPlan = plan2;
175  rightPlan = plan1;
176  } else {
177  // Assertion - make sure no join clashes!
178  if ((plan1.LeftPlan != null && plan2.LeftPlan != null) ||
179  (plan1.RightPlan != null && plan2.RightPlan != null)) {
180  throw new InvalidOperationException("Plans can not be naturally join because " +
181  "the left/right join plans clash.");
182  }
183 
184  // Else we must assume a non-dependent join (not an outer join).
185  // Perform a natural join
186  IQueryPlanNode node1 = new NaturalJoinNode(plan1.Plan, plan2.Plan);
187  return MergePlans(plan1, plan2, node1);
188  }
189 
190  // This means plan1 and plan2 are linked by a common join and ON
191  // expression which we evaluate now.
192  bool outerJoin;
193  if (joinType == JoinType.Left) {
194  // Mark the left plan
195  leftPlan.UpdatePlan(new MarkerNode(leftPlan.Plan, "OUTER_JOIN"));
196  outerJoin = true;
197  } else if (joinType == JoinType.Right) {
198  // Mark the right plan
199  rightPlan.UpdatePlan(new MarkerNode(rightPlan.Plan, "OUTER_JOIN"));
200  outerJoin = true;
201  } else if (joinType == JoinType.Inner) {
202  // Inner join with ON expression
203  outerJoin = false;
204  } else {
205  throw new InvalidOperationException(String.Format("Join type ({0}) is not supported.", joinType));
206  }
207 
208  // Make a Planner object for joining these plans.
209  var planner = new QueryTablePlanner();
210  planner.AddPlan(leftPlan.Clone());
211  planner.AddPlan(rightPlan.Clone());
212 
213  // Evaluate the on expression
214  var node = planner.LogicalEvaluate(onExpr);
215 
216  // If outer join add the left outer join node
217  if (outerJoin)
218  node = new LeftOuterJoinNode(node, "OUTER_JOIN");
219 
220  // And merge the plans in this set with the new node.
221  return MergePlans(plan1, plan2, node);
222  }
223 
224  private TablePlan JoinToSingle(IList<TablePlan> allPlans) {
225  // If there are no plans then return null
226  if (allPlans.Count == 0)
227  return null;
228 
229  if (allPlans.Count == 1)
230  // Return early if there is only 1 table.
231  return allPlans[0];
232 
233  // Make a working copy of the plan list.
234  var workingPlanList = new List<TablePlan>(allPlans);
235 
236  // We go through each plan in turn.
237  while (workingPlanList.Count > 1) {
238  var leftPlan = workingPlanList[0];
239  var rightPlan = workingPlanList[1];
240 
241  // First we need to determine if the left and right plan can be
242  // naturally joined.
243  int status = AssertBeNaturalJoin(leftPlan, rightPlan);
244  if (status == 0) {
245  // Yes they can so join them
246  var newPlan = NaturallyJoinPlans(leftPlan, rightPlan);
247 
248  // Remove the left and right plan from the list and add the new plan
249  workingPlanList.Remove(leftPlan);
250  workingPlanList.Remove(rightPlan);
251  workingPlanList.Insert(0, newPlan);
252  } else if (status == 1) {
253  // No we can't because of a right join clash, so we join the left
254  // plan right in hopes of resolving the clash.
255  var newPlan = NaturallyJoinPlans(leftPlan, leftPlan.RightPlan);
256  workingPlanList.Remove(leftPlan);
257  workingPlanList.Remove(leftPlan.RightPlan);
258  workingPlanList.Insert(0, newPlan);
259  } else if (status == 2) {
260  // No we can't because of a left join clash, so we join the left
261  // plan left in hopes of resolving the clash.
262  var newPlan = NaturallyJoinPlans(leftPlan, leftPlan.LeftPlan);
263  workingPlanList.Remove(leftPlan);
264  workingPlanList.Remove(leftPlan.LeftPlan);
265  workingPlanList.Insert(0, newPlan);
266  } else {
267  throw new InvalidOperationException(String.Format("Natural join assessed status {0} is unknown.", status));
268  }
269  }
270 
271  // Return the working plan of the merged tables.
272  return workingPlanList[0];
273  }
274 
276  if (expression == null) {
277  // Naturally join everything and return the plan.
278  NaturalJoinAll();
279  return SinglePlan.Plan;
280  }
281 
282  // Plan the expression
283  PlanExpression(expression);
284 
285  // Naturally join any straggling tables
286  NaturalJoinAll();
287 
288  // Return the plan
289  return SinglePlan.Plan;
290  }
291 
292  private static void AddSingleColumnPlan(IList<SingleColumnPlan> list, TablePlan table, ObjectName columnName, ObjectName uniqueName, SqlExpression[] expParts, SqlExpressionType op) {
293  var exp = SqlExpression.Binary(expParts[0], op, expParts[1]);
294 
295  // Is this source in the list already?
296  foreach (var existingPlan in list) {
297  if (existingPlan.TablePlan == table &&
298  (columnName == null || existingPlan.ColumnName.Equals(columnName))) {
299  // Append to end of current expression
300  existingPlan.SetSource(columnName, SqlExpression.And(existingPlan.Expression, exp));
301  return;
302  }
303  }
304 
305  // Didn't find so make a new entry in the list.
306  list.Add(new SingleColumnPlan(table, columnName, uniqueName, exp));
307  }
308 
310  var copy = new QueryTablePlanner();
311 
312  int sz = tablePlans.Count;
313  for (int i = 0; i < sz; ++i) {
314  copy.tablePlans.Add(tablePlans[i].Clone());
315  }
316 
317  // Copy the left and right links in the PlanTableSource
318  for (int i = 0; i < sz; ++i) {
319  var src = tablePlans[i];
320  var mod = copy.tablePlans[i];
321 
322  // See how the left plan links to which index,
323  if (src.LeftPlan != null) {
324  int n = IndexOfPlan(src.LeftPlan);
325  mod.LeftJoin(copy.tablePlans[n], src.LeftJoinType, src.LeftOnExpression);
326  }
327 
328  // See how the right plan links to which index,
329  if (src.RightPlan != null) {
330  int n = IndexOfPlan(src.RightPlan);
331  mod.RightJoin(copy.tablePlans[n], src.RightJoinType, src.RightOnExpression);
332  }
333  }
334 
335  return copy;
336  }
337 
338  private void PlanAllOuterJoins() {
339  int sz = tablePlans.Count;
340  if (sz <= 1)
341  return;
342 
343  // Make a working copy of the plan list.
344  var workingPlanList = new List<TablePlan>(tablePlans);
345 
346  var plan1 = workingPlanList[0];
347  for (int i = 1; i < sz; ++i) {
348  var plan2 = workingPlanList[i];
349 
350  if (plan1.RightPlan == plan2) {
351  plan1 = NaturallyJoinPlans(plan1, plan2);
352  } else {
353  plan1 = plan2;
354  }
355  }
356  }
357 
358  private void PlanExpressionList(IEnumerable<SqlExpression> expressions) {
359  var subLogicExpressions = new List<SqlBinaryExpression>();
360  // The list of expressions that have a sub-select in them.
361  var subQueryExpressions = new List<SqlBinaryExpression>();
362  // The list of all constant expressions ( true = true )
363  var constants = new List<SqlExpression>();
364  // The list of pattern matching expressions (eg. 't LIKE 'a%')
365  var patternExpressions = new List<SqlBinaryExpression>();
366  // The list of all expressions that are a single variable on one
367  // side, a conditional operator, and a constant on the other side.
368  var singleVars = new List<SqlBinaryExpression>();
369  // The list of multi variable expressions (possible joins)
370  var multiVars = new List<SqlBinaryExpression>();
371 
372  foreach (var expression in expressions) {
374 
375  if (!(expression is SqlBinaryExpression)) {
376  // If this is not a binary expression we imply
377  // [expression] = 'true'
378  exp = SqlExpression.Equal(expression, SqlExpression.Constant(true));
379  } else {
380  exp = (SqlBinaryExpression) expression;
381  }
382 
383  if (exp.ExpressionType.IsLogical()) {
384  subLogicExpressions.Add(exp);
385  } else if (exp.HasSubQuery()) {
386  subQueryExpressions.Add(exp);
387  } else if (exp.ExpressionType.IsPattern()) {
388  patternExpressions.Add(exp);
389  } else {
390  // The list of variables in the expression.
391  var columnNames = exp.DiscoverReferences().ToList();
392  if (columnNames.Count == 0) {
393  // These are ( 54 + 9 = 9 ), ( "z" > "a" ), ( 9.01 - 2 ), etc
394  constants.Add(exp);
395  } else if (columnNames.Count == 1) {
396  // These are ( id = 90 ), ( 'a' < number ), etc
397  singleVars.Add(exp);
398  } else if (columnNames.Count > 1) {
399  // These are ( id = part_id ),
400  // ( cost_of + value_of < sold_at ), ( id = part_id - 10 )
401  multiVars.Add(exp);
402  } else {
403  throw new InvalidOperationException("Invalid number of column names");
404  }
405  }
406  }
407 
408  // The order in which expression are evaluated,
409  // (ExpressionPlan)
410  var evaluateOrder = new List<ExpressionPlan>();
411 
412  // Evaluate the constants. These should always be evaluated first
413  // because they always evaluate to either true or false or null.
414  EvaluateConstants(constants, evaluateOrder);
415 
416  // Evaluate the singles. If formed well these can be evaluated
417  // using fast indices. eg. (a > 9 - 3) is more optimal than
418  // (a + 3 > 9).
419  EvaluateSingles(singleVars, evaluateOrder);
420 
421  // Evaluate the pattern operators. Note that some patterns can be
422  // optimized better than others, but currently we keep this near the
423  // middle of our evaluation sequence.
424  EvaluatePatterns(patternExpressions, evaluateOrder);
425 
426  // Evaluate the sub-queries. These are queries of the form,
427  // (a IN ( SELECT ... )), (a = ( SELECT ... ) = ( SELECT ... )), etc.
428  EvaluateSubQueries(subQueryExpressions, evaluateOrder);
429 
430  // Evaluate multiple variable expressions. It's possible these are
431  // joins.
432  EvaluateMultiples(multiVars, evaluateOrder);
433 
434  // Lastly evaluate the sub-logic expressions. These expressions are
435  // OR type expressions.
436  EvaluateSubLogic(subLogicExpressions, evaluateOrder);
437 
438  evaluateOrder.Sort();
439 
440  // And add each expression to the plan
441  foreach (ExpressionPlan plan in evaluateOrder) {
442  plan.AddToPlanTree();
443  }
444  }
445 
446  private void EvaluateSubLogic(List<SqlBinaryExpression> list, List<ExpressionPlan> plans) {
447  foreach (var expression in list) {
448  var orExprs = new[] {expression.Left, expression.Right};
449 
450  // An optimizations here;
451 
452  // If all the expressions we are ORing together are in the same table
453  // then we should execute them before the joins, otherwise they
454  // should go after the joins.
455 
456  // The reason for this is because if we can lesson the amount of work a
457  // join has to do then we should. The actual time it takes to perform
458  // an OR search shouldn't change if it is before or after the joins.
459 
460  TablePlan common = null;
461 
462  foreach (var orExpr in orExprs) {
463  var vars = orExpr.DiscoverReferences().ToArray();
464 
465  bool breakRule = false;
466 
467  // If there are no variables then don't bother with this expression
468  if (vars.Any()) {
469  // Find the common table source (if any)
470  var ts = FindCommonPlan(vars);
471  bool orAfterJoins = false;
472  if (ts == null) {
473  // No common table, so OR after the joins
474  orAfterJoins = true;
475  } else if (common == null) {
476  common = ts;
477  } else if (common != ts) {
478  // No common table with the vars in this OR list so do this OR
479  // after the joins.
480  orAfterJoins = true;
481  }
482 
483  if (orAfterJoins) {
484  plans.Add(new SubLogicPlan(this, expression, 0.70f));
485 
486  // Continue to the next logic expression
487  breakRule = true;
488  }
489  }
490 
491  if (!breakRule) {
492  // Either we found a common table or there are no variables in the OR.
493  // Either way we should evaluate this after the join.
494  plans.Add(new SubLogicPlan(this, expression, 0.58f));
495  }
496  }
497  }
498  }
499 
500  private void EvaluateMultiples(List<SqlBinaryExpression> list, List<ExpressionPlan> plans) {
501  // FUTURE OPTIMIZATION:
502  // This join order planner is a little primitive in design. It orders
503  // optimizable joins first and least optimizable last, but does not
504  // take into account other factors that we could use to optimize
505  // joins in the future.
506 
507  foreach (var expression in list) {
508  // Get the list of variables in the left hand and right hand side
509  var lhsVar = expression.Left.AsReferenceName();
510  var rhsVar = expression.Right.AsReferenceName();
511 
512  // Work out how optimizable the join is.
513  // The calculation is as follows;
514  // a) If both the lhs and rhs are a single variable then the
515  // optimizable value is set to 0.6f.
516  // b) If only one of lhs or rhs is a single variable then the
517  // optimizable value is set to 0.64f.
518  // c) Otherwise it is set to 0.68f (exhaustive select guarenteed).
519 
520  if (lhsVar == null && rhsVar == null) {
521  // Neither lhs or rhs are single vars
522  plans.Add(new ExhaustiveJoinPlan(this, expression));
523  } else if (lhsVar != null && rhsVar != null) {
524  // Both lhs and rhs are a single var (most optimizable type of
525  // join).
526  plans.Add(new StandardJoinPlan(this, expression, 0.60f));
527  } else {
528  // Either lhs or rhs is a single var
529  plans.Add(new StandardJoinPlan(this, expression, 064f));
530  }
531  }
532  }
533 
534  private void EvaluateSubQueries(List<SqlBinaryExpression> list, List<ExpressionPlan> plans) {
535  foreach (var expression in list) {
536  bool exhaustive;
537 
538  var op = expression.ExpressionType;
539  if (op.IsSubQuery()) {
540  // Must be an exhaustive sub-command
541  exhaustive = true;
542  } else {
543  // Check that the left is a simple enough variable reference
544  var leftColumn = expression.Left.AsReferenceName();
545  if (leftColumn == null) {
546  exhaustive = true;
547  } else {
548  // Check that the right is a sub-command plan.
549  IQueryPlanNode rightPlan = expression.Right.AsQueryPlan();
550  if (rightPlan == null)
551  exhaustive = true;
552  else {
553  // Finally, check if the plan is correlated or not
554  var cv = rightPlan.DiscoverQueryReferences(1);
555  exhaustive = cv.Count != 0;
556  }
557  }
558  }
559 
560  if (exhaustive) {
561  // This expression could involve multiple variables, so we may need
562  // to join.
563  var columnNames = expression.DiscoverReferences().ToList();
564 
565  // Also find all correlated variables.
566  int level = 0;
567  var allCorrelated = expression.DiscoverQueryReferences(ref level);
568  int sz = allCorrelated.Count;
569 
570  // If there are no variables (and no correlated variables) then this
571  // must be a constant select, For example, 3 in ( select ... )
572  if (!columnNames.Any() && sz == 0) {
573  plans.Add(new ConstantPlan(this, expression));
574  } else {
575  columnNames.AddRange(allCorrelated.Select(cv => cv.Name));
576 
577  // An exhaustive expression plan which might require a join or a
578  // slow correlated search. This should be evaluated after the
579  // multiple variables are processed.
580  plans.Add(new ExhaustiveSubQueryPlan(this, columnNames.ToArray(), expression));
581  }
582  } else {
583  plans.Add(new SimpleSubQueryPlan(this, expression));
584  }
585  }
586  }
587 
588  private void EvaluatePatterns(List<SqlBinaryExpression> list, List<ExpressionPlan> plans) {
589  foreach (var expression in list) {
590  // If the LHS is a single column and the RHS is a constant then
591  // the conditions are right for a simple pattern search.
592  var leftColumnName = expression.Left.AsReferenceName();
593 
594  if (expression.IsConstant()) {
595  plans.Add(new ConstantPlan(this, expression));
596  } else if (leftColumnName != null &&
597  expression.Right.IsConstant()) {
598  plans.Add(new SimplePatternPlan(this, leftColumnName, expression));
599  } else {
600  // Otherwise we must assume a complex pattern search which may
601  // require a join. For example, 'a + b LIKE 'a%'' or
602  // 'a LIKE b'. At the very least, this will be an exhaustive
603  // search and at the worst it will be a join + exhaustive search.
604  // So we should evaluate these at the end of the evaluation order.
605  plans.Add(new ExhaustiveSelectPlan(this, expression));
606  }
607  }
608  }
609 
610  private void EvaluateSingles(List<SqlBinaryExpression> list, List<ExpressionPlan> plans) {
611  // The list of simple expression plans (lhs = single)
612  var simplePlanList = new List<SingleColumnPlan>();
613  // The list of complex function expression plans (lhs = expression)
614  var complexPlanList = new List<SingleColumnPlan>();
615 
616  foreach (var expression in list) {
617  // The single var
618  ObjectName singleVar;
619  SqlExpressionType op = expression.ExpressionType;
620  SqlExpression left = expression.Left, right = expression.Right;
621 
622  if (op.IsSubQuery()) {
623  singleVar = expression.Left.AsReferenceName();
624 
625  if (singleVar != null) {
626  plans.Add(new SimpleSelectPlan(this, singleVar, op, expression.Right));
627  } else {
628  singleVar = expression.Left.DiscoverReferences().First();
629  plans.Add(new ComplexSinglePlan(this, singleVar, expression));
630  }
631  } else {
632  singleVar = expression.Left.DiscoverReferences().FirstOrDefault();
633  if (singleVar == null) {
634  // Reverse the expressions and the operator
635  var tempExp = left;
636  left = right;
637  right = tempExp;
638  op = op.Reverse();
639  singleVar = left.DiscoverReferences().First();
640  }
641 
642  var tableSource = FindPlan(singleVar);
643 
644  // Simple LHS?
645  var v = left.AsReferenceName();
646  if (v != null) {
647  AddSingleColumnPlan(simplePlanList, tableSource, v, singleVar, new []{left, right}, op);
648  } else {
649  // No, complex lhs
650  AddSingleColumnPlan(complexPlanList, tableSource, null, singleVar, new []{left, right}, op);
651  }
652  }
653  }
654 
655  plans.AddRange(simplePlanList.Select(plan => new SimpleSinglePlan(this, plan.UniqueName, plan.Expression)).Cast<ExpressionPlan>());
656  plans.AddRange(complexPlanList.Select(plan => new ComplexSinglePlan(this, plan.UniqueName, plan.Expression)).Cast<ExpressionPlan>());
657  }
658 
659  private void EvaluateConstants(List<SqlExpression> list, List<ExpressionPlan> plans) {
660  // For each constant variable
661  plans.AddRange(list.Select(expr => new ConstantPlan(this, expr)).Cast<ExpressionPlan>());
662  }
663 
664  private void PlanExpression(SqlExpression expression) {
665  if (expression is SqlBinaryExpression &&
666  expression.ExpressionType.IsLogical()) {
667  var binary = (SqlBinaryExpression) expression;
668 
669  if (expression.ExpressionType == SqlExpressionType.Or) {
670  // parsing an OR block
671  // Split left and right of logical operator.
672  var exps = new[]{binary.Left, binary.Right};
673 
674  // If we are an 'or' then evaluate left and right and union the
675  // result.
676 
677  // Before we branch set cache points.
678  SetCachePoints();
679 
680  // Make copies of the left and right planner
681  var leftPlanner = Clone();
682  var rightPlanner = Clone();
683 
684  // Plan the left and right side of the OR
685  leftPlanner.PlanExpression(exps[0]);
686  rightPlanner.PlanExpression(exps[1]);
687 
688  // Fix the left and right planner so that they represent the same
689  // 'group'.
690  // The current implementation naturally joins all sources if the
691  // number of sources is different than the original size.
692  int leftSz = leftPlanner.tablePlans.Count;
693  int rightSz = rightPlanner.tablePlans.Count;
694  if (leftSz != rightSz || leftPlanner.HasJoin || rightPlanner.HasJoin) {
695  // Naturally join all in the left and right plan
696  leftPlanner.NaturalJoinAll();
697  rightPlanner.NaturalJoinAll();
698  }
699 
700  // Union all table sources, but only if they have changed.
701  var leftTableList = leftPlanner.tablePlans;
702  var rightTableList = rightPlanner.tablePlans;
703  int sz = leftTableList.Count;
704 
705  // First we must determine the plans that need to be joined in the
706  // left and right plan.
707  var leftJoinList = new List<TablePlan>();
708  var rightJoinList = new List<TablePlan>();
709  for (int i = 0; i < sz; ++i) {
710  var leftPlan = leftTableList[i];
711  var rightPlan = rightTableList[i];
712  if (leftPlan.IsUpdated || rightPlan.IsUpdated) {
713  leftJoinList.Add(leftPlan);
714  rightJoinList.Add(rightPlan);
715  }
716  }
717 
718  // Make sure the plans are joined in the left and right planners
719  leftPlanner.JoinToSingle(leftJoinList);
720  rightPlanner.JoinToSingle(rightJoinList);
721 
722  // Since the planner lists may have changed we update them here.
723  leftTableList = leftPlanner.tablePlans;
724  rightTableList = rightPlanner.tablePlans;
725  sz = leftTableList.Count;
726 
727  var newTableList = new List<TablePlan>(sz);
728 
729  for (int i = 0; i < sz; ++i) {
730  var leftPlan = leftTableList[i];
731  var rightPlan = rightTableList[i];
732 
733  TablePlan newPlan;
734 
735  // If left and right plan updated so we need to union them
736  if (leftPlan.IsUpdated || rightPlan.IsUpdated) {
737  // In many causes, the left and right branches will contain
738  // identical branches that would best be optimized out.
739 
740  // Take the left plan, add the logical union to it, and make it
741  // the plan for this.
742  var node = new LogicalUnionNode(leftPlan.Plan, rightPlan.Plan);
743 
744  // Update the plan in this table list
745  leftPlan.UpdatePlan(node);
746 
747  newPlan = leftPlan;
748  } else {
749  // If the left and right plan didn't update, then use the
750  // left plan (it doesn't matter if we use left or right because
751  // they are the same).
752  newPlan = leftPlan;
753  }
754 
755  // Add the left plan to the new table list we are creating
756  newTableList.Add(newPlan);
757 
758  }
759 
760  // Set the new table list
761  tablePlans = newTableList;
762  } else if (expression.ExpressionType == SqlExpressionType.And) {
763  PlanExpressionList(new[]{binary.Left, binary.Right});
764  } else {
765  throw new InvalidOperationException();
766  }
767  } else {
768  PlanExpressionList(new []{expression});
769  }
770  }
771 
772  public void AddPlan(TablePlan tablePlan) {
773  tablePlans.Add(tablePlan);
774  HasJoin = true;
775  }
776 
777  public void AddPlan(IQueryPlanNode plan, IFromTableSource tableSource) {
778  var columns = tableSource.ColumnNames;
779  var uniqueNames = new[] {tableSource.UniqueName};
780  AddPlan(new TablePlan(plan, columns, uniqueNames));
781  }
782 
783  public void JoinAt(int betweenIndex, JoinType joinType, SqlExpression onExpression) {
784  var planLeft = tablePlans[betweenIndex];
785  var planRight = tablePlans[betweenIndex + 1];
786  planLeft.RightJoin(planRight, joinType, onExpression);
787  planRight.LeftJoin(planLeft, joinType, onExpression);
788  }
789 
791  // First perform all outer tables.
792  PlanAllOuterJoins();
793 
794  return LogicalEvaluate(searchExpression);
795  }
796 
797  #region SingleColumnPlan
798 
800  public SingleColumnPlan(TablePlan tablePlan, ObjectName columnName, ObjectName uniqueName, SqlExpression expression) {
801  TablePlan = tablePlan;
802  ColumnName = columnName;
803  UniqueName = uniqueName;
804  Expression = expression;
805  }
806 
807  public TablePlan TablePlan { get; private set; }
808 
809  public ObjectName ColumnName { get; private set; }
810 
811  public ObjectName UniqueName { get; private set; }
812 
813  public SqlExpression Expression { get; private set; }
814 
815  public void SetSource(ObjectName columnName, SqlExpression expression) {
816  ColumnName = columnName;
817  Expression = expression;
818  }
819  }
820 
821  #endregion
822 
823  #region ExpressionPlan
824 
825  abstract class ExpressionPlan : IExpressionPlan {
826  protected ExpressionPlan(float optimizeFactor) {
827  OptimizeFactor = optimizeFactor;
828  }
829 
830  public float OptimizeFactor { get; private set; }
831 
832  public abstract void AddToPlanTree();
833 
834  public int CompareTo(IExpressionPlan other) {
835  return OptimizeFactor.CompareTo(other.OptimizeFactor);
836  }
837 
838  int IComparable.CompareTo(object obj) {
839  var other = (ExpressionPlan) obj;
840  return CompareTo(other);
841  }
842  }
843 
844  #endregion
845 
846  #region ConstantPlan
847 
849  private readonly QueryTablePlanner planner;
850  private readonly SqlExpression expression;
851 
852  public ConstantPlan(QueryTablePlanner planner, SqlExpression expression)
853  : base(0f) {
854  this.planner = planner;
855  this.expression = expression;
856  }
857 
858  public override void AddToPlanTree() {
859  foreach (var tablePlan in planner.tablePlans) {
860  tablePlan.UpdatePlan(new ConstantSelectNode(tablePlan.Plan, expression));
861  }
862  }
863  }
864 
865  #endregion
866 
867  #region SimpleSelectPlan
868 
870  private readonly ObjectName columnName;
871  private readonly SqlExpressionType op;
872  private readonly SqlExpression expression;
873  private readonly QueryTablePlanner planner;
874 
876  : base(0.2f){
877  this.planner = planner;
878  this.columnName = columnName;
879  this.op = op;
880  this.expression = expression;
881  }
882 
883  public override void AddToPlanTree() {
884  var tablePlan = planner.FindPlan(columnName);
885  tablePlan.UpdatePlan(new SimpleSelectNode(tablePlan.Plan, columnName, op, expression));
886  }
887  }
888 
889  #endregion
890 
891  #region SimpleSinglePlan
892 
894  private readonly QueryTablePlanner planner;
895  private readonly ObjectName columnName;
896  private readonly SqlExpression expression;
897 
898  public SimpleSinglePlan(QueryTablePlanner planner, ObjectName columnName, SqlExpression expression)
899  : base(0.2f){
900  this.planner = planner;
901  this.columnName = columnName;
902  this.expression = expression;
903  }
904 
905  public override void AddToPlanTree() {
906  var tablePlan = planner.FindPlan(columnName);
907  tablePlan.UpdatePlan(new RangeSelectNode(tablePlan.Plan, expression));
908  }
909  }
910 
911  #endregion
912 
913  #region ComplexSinglePlan
914 
916  private readonly QueryTablePlanner planner;
917  private readonly ObjectName columnName;
918  private readonly SqlExpression expression;
919 
920  public ComplexSinglePlan(QueryTablePlanner planner, ObjectName columnName, SqlExpression expression)
921  : base(0.8f) {
922  this.planner = planner;
923  this.columnName = columnName;
924  this.expression = expression;
925  }
926 
927  public override void AddToPlanTree() {
928  var tablePlan = planner.FindPlan(columnName);
929  tablePlan.UpdatePlan(new ExhaustiveSelectNode(tablePlan.Plan, expression));
930  }
931  }
932 
933  #endregion
934 
935  #region SimplePatternPlan
936 
938  private readonly QueryTablePlanner planner;
939  private readonly ObjectName columnName;
940  private readonly SqlExpression expression;
941 
942  public SimplePatternPlan(QueryTablePlanner planner, ObjectName columnName, SqlExpression expression)
943  : base(0.25f) {
944  this.planner = planner;
945  this.columnName = columnName;
946  this.expression = expression;
947  }
948 
949  public override void AddToPlanTree() {
950  var tablePlan = planner.FindPlan(columnName);
951  tablePlan.UpdatePlan(new SimplePatternSelectNode(tablePlan.Plan, expression));
952  }
953  }
954 
955  #endregion
956 
957  #region ExhaustiveSelectPlan
958 
960  private readonly QueryTablePlanner planner;
961  private readonly SqlExpression expression;
962 
964  : base(0.82f) {
965  this.planner = planner;
966  this.expression = expression;
967  }
968 
969  public override void AddToPlanTree() {
970  var columnNames = expression.DiscoverReferences();
971  var tablePlan = planner.JoinPlansForColumns(columnNames);
972  tablePlan.UpdatePlan(new ExhaustiveSelectNode(tablePlan.Plan, expression));
973  }
974  }
975 
976  #endregion
977 
978  #region ExhaustiveSubQueryPlan
979 
981  private readonly QueryTablePlanner planner;
982  private readonly ObjectName[] columnNames;
983  private readonly SqlExpression expression;
984 
985  public ExhaustiveSubQueryPlan(QueryTablePlanner planner, ObjectName[] columnNames, SqlExpression expression)
986  : base(0.85f) {
987  this.planner = planner;
988  this.columnNames = columnNames;
989  this.expression = expression;
990  }
991 
992  public override void AddToPlanTree() {
993  var tablePlan = planner.JoinPlansForColumns(columnNames);
994  tablePlan.UpdatePlan(new ExhaustiveSelectNode(tablePlan.Plan, expression));
995  }
996  }
997 
998  #endregion
999 
1000  #region SimpleSubQueryPlan
1001 
1003  private readonly QueryTablePlanner planner;
1005 
1007  : base(0.3f) {
1008  this.planner = planner;
1009  this.expression = expression;
1010  }
1011 
1012  public override void AddToPlanTree() {
1013  var op = expression.ExpressionType;
1014  var columnName = expression.Left.AsReferenceName();
1015  var queryPlan = expression.Right.AsQueryPlan();
1016 
1017  var tablePlan = planner.FindPlan(columnName);
1018  var leftPlan = tablePlan.Plan;
1019 
1020  tablePlan.UpdatePlan(new NonCorrelatedAnyAllNode(leftPlan, queryPlan, new []{columnName}, op));
1021  }
1022  }
1023 
1024  #endregion
1025 
1026  #region ExhaustiveJoinPlan
1027 
1029  private readonly QueryTablePlanner planner;
1030  private readonly SqlExpression expression;
1031 
1033  : base(0.68f) {
1034  this.planner = planner;
1035  this.expression = expression;
1036  }
1037 
1038  public override void AddToPlanTree() {
1039  var columnNames = expression.DiscoverReferences();
1040  var tablePlan = planner.JoinPlansForColumns(columnNames);
1041  tablePlan.UpdatePlan(new ExhaustiveSelectNode(tablePlan.Plan, expression));
1042  }
1043  }
1044 
1045  #endregion
1046 
1047  #region StandardJoinPlan
1048 
1050  private readonly QueryTablePlanner planner;
1052 
1053  public StandardJoinPlan(QueryTablePlanner planner, SqlBinaryExpression expression, float optimizeFactor)
1054  : base(optimizeFactor) {
1055  this.planner = planner;
1056  this.expression = expression;
1057  }
1058 
1059  public override void AddToPlanTree() {
1060  var op = expression.ExpressionType;
1061  var lhsVar = expression.Left.AsReferenceName();
1062  var rhsVar = expression.Right.AsReferenceName();
1063  var lhsVars = expression.Left.DiscoverReferences();
1064  var rhsVars = expression.Right.DiscoverReferences();
1065 
1066  var lhsPlan = planner.JoinPlansForColumns(lhsVars);
1067  var rhsPlan = planner.JoinPlansForColumns(rhsVars);
1068 
1069  if (lhsPlan != rhsPlan) {
1070  // If either the LHS or the RHS is a single column then we can
1071  // optimize the join.
1072 
1073  if (lhsVar != null || rhsVar != null) {
1074  // If right column is a single and left column is not then we must
1075  // reverse the expression.
1076  JoinNode joinNode;
1077  if (lhsVar == null) {
1078  // Reverse the expressions and the operator
1079  joinNode = new JoinNode(rhsPlan.Plan, lhsPlan.Plan, rhsVar, op.Reverse(), expression.Left);
1080  planner.MergePlans(rhsPlan, lhsPlan, joinNode);
1081  } else {
1082  // Otherwise, use it as it is.
1083  joinNode = new JoinNode(lhsPlan.Plan, rhsPlan.Plan, lhsVar, op, expression.Right);
1084  planner.MergePlans(lhsPlan, rhsPlan, joinNode);
1085  }
1086 
1087  // Return because we are done
1088  return;
1089  }
1090  }
1091 
1092  // If we get here either both the lhs and rhs are complex expressions
1093  // or the lhs and rhs of the variable are not different plans, or
1094  // the operator is not a conditional. Either way, we must evaluate
1095  // this via a natural join of the variables involved coupled with an
1096  // exhaustive select. These types of queries are poor performing.
1097 
1098  var columnNames = expression.DiscoverReferences();
1099  var tablePlan = planner.JoinPlansForColumns(columnNames);
1100  tablePlan.UpdatePlan(new ExhaustiveSelectNode(tablePlan.Plan, expression));
1101  }
1102  }
1103 
1104  #endregion
1105 
1106  #region SubLogicPlan
1107 
1109  private readonly QueryTablePlanner planner;
1110  private readonly SqlExpression expression;
1111 
1112  public SubLogicPlan(QueryTablePlanner planner, SqlExpression expression, float optimizeFactor)
1113  : base(optimizeFactor) {
1114  this.planner = planner;
1115  this.expression = expression;
1116  }
1117 
1118  public override void AddToPlanTree() {
1119  planner.PlanExpression(expression);
1120  }
1121  }
1122 
1123  #endregion
1124  }
1125 }
SqlExpression LeftOnExpression
Definition: TablePlan.cs:67
ExhaustiveJoinPlan(QueryTablePlanner planner, SqlExpression expression)
TablePlan FindPlan(ObjectName reference)
TablePlan NaturallyJoinPlans(TablePlan plan1, TablePlan plan2)
ComplexSinglePlan(QueryTablePlanner planner, ObjectName columnName, SqlExpression expression)
ObjectName[] ColumnNames
Returns an array of ObjectName objects that references each column available in the table set item in...
SingleColumnPlan(TablePlan tablePlan, ObjectName columnName, ObjectName uniqueName, SqlExpression expression)
static SqlBinaryExpression And(SqlExpression left, SqlExpression right)
IQueryPlanNode LogicalEvaluate(SqlExpression expression)
string[] UniqueNames
The list of unique key names of the tables in this plan.
Definition: TablePlan.cs:57
JoinType
Enumerates the kind of group join in a selection query.
Definition: JoinType.cs:23
The node for evaluating an expression that contains entirely constant values (no variables).
A query to the database to select data from a set of tables and columns.
ExhaustiveSubQueryPlan(QueryTablePlanner planner, ObjectName[] columnNames, SqlExpression expression)
A single table resource item in a query which handles the behaviour of resolving references to column...
void EvaluateConstants(List< SqlExpression > list, List< ExpressionPlan > plans)
void EvaluatePatterns(List< SqlBinaryExpression > list, List< ExpressionPlan > plans)
static int AssertBeNaturalJoin(TablePlan plan1, TablePlan plan2)
SimplePatternPlan(QueryTablePlanner planner, ObjectName columnName, SqlExpression expression)
static SqlBinaryExpression Equal(SqlExpression left, SqlExpression right)
Describes the name of an object within a database.
Definition: ObjectName.cs:44
ExhaustiveSelectPlan(QueryTablePlanner planner, SqlExpression expression)
TablePlan FindCommonPlan(IList< ObjectName > columnNames)
static void AddSingleColumnPlan(IList< SingleColumnPlan > list, TablePlan table, ObjectName columnName, ObjectName uniqueName, SqlExpression[] expParts, SqlExpressionType op)
ObjectName[] ColumnNames
The list of fully qualified column objects that are accessible within this plan.
Definition: TablePlan.cs:52
string UniqueName
Gets a unique name given to this table source.
SqlExpressionType
All the possible type of SqlExpression supported
The node for performing a simple indexed query on a single column of the child node.
ConstantPlan(QueryTablePlanner planner, SqlExpression expression)
A node element of a query plan tree. /summary>
The node for performing a simple select operation on a table.
A marker node that takes the result of a child and marks it as a name that can later be retrieved...
Definition: MarkerNode.cs:32
void EvaluateSubQueries(List< SqlBinaryExpression > list, List< ExpressionPlan > plans)
void UpdatePlan(IQueryPlanNode queryPlan)
Definition: TablePlan.cs:131
static SqlBinaryExpression Add(SqlExpression left, SqlExpression right)
void EvaluateSubLogic(List< SqlBinaryExpression > list, List< ExpressionPlan > plans)
SimpleSelectPlan(QueryTablePlanner planner, ObjectName columnName, SqlExpressionType op, SqlExpression expression)
static TablePlan ConcatPlans(TablePlan left, TablePlan right, IQueryPlanNode plan)
SimpleSubQueryPlan(QueryTablePlanner planner, SqlBinaryExpression expression)
IQueryPlanNode Plan
Returns the plan for this table source.
Definition: TablePlan.cs:41
The node for performing a exhaustive select operation on the child node.
A branch node for a left outer join.
void SetSource(ObjectName columnName, SqlExpression expression)
SqlExpression RightOnExpression
Definition: TablePlan.cs:69
TablePlan MergePlans(TablePlan left, TablePlan right, IQueryPlanNode mergePlan)
TablePlan JoinToSingle(IList< TablePlan > allPlans)
SimpleSinglePlan(QueryTablePlanner planner, ObjectName columnName, SqlExpression expression)
IQueryPlanNode PlanSearchExpression(SqlExpression searchExpression)
A branch node for naturally joining two tables together.
StandardJoinPlan(QueryTablePlanner planner, SqlBinaryExpression expression, float optimizeFactor)
void PlanExpression(SqlExpression expression)
void PlanExpressionList(IEnumerable< SqlExpression > expressions)
TablePlan JoinPlansForColumns(IEnumerable< ObjectName > columnNames)
static SqlBinaryExpression Binary(SqlExpression left, SqlExpressionType expressionType, SqlExpression right)
Defines the base class for instances that represent SQL expression tree nodes.
static SqlConstantExpression Constant(object value)
abstract SqlExpressionType ExpressionType
Gets the type code of this SQL expression.
SubLogicPlan(QueryTablePlanner planner, SqlExpression expression, float optimizeFactor)
void EvaluateSingles(List< SqlBinaryExpression > list, List< ExpressionPlan > plans)
void EvaluateMultiples(List< SqlBinaryExpression > list, List< ExpressionPlan > plans)
void AddPlan(IQueryPlanNode plan, IFromTableSource tableSource)
void JoinAt(int betweenIndex, JoinType joinType, SqlExpression onExpression)