public override bool Appliable(CGroupMember expr) { LogicJoin a_bc = expr.logic_ as LogicJoin; if (a_bc is null || !a_bc.IsInnerJoin()) { return(false); } var bc = (a_bc.rchild_() as LogicMemoRef).Deref(); var bcfilter = bc.filter_; if (bc is LogicJoin bcj) { if (!bcj.IsInnerJoin()) { return(false); } // we only reject cases that logically impossible to apply // association rule, but leave anything may generate worse // plan (say catersisan joins) to apply stage. return(true); } return(false); }
// classic formula is: // A X B => |A|*|B|/max(dA, dB) where dA,dB are distinct values of joining columns // This however does not consider join key distribution. In SQL Server 2014, it introduced // histogram join to better the estimation. // public override ulong LogicJoinCE(LogicJoin node) { ulong getDistinct(Expr key) { if (key is ColExpr col) { var tr = col.tabRef_; if (tr is FromQueryRef fqr && fqr.MapOutputName(col.colName_) != null) { if (fqr.MapOutputName(col.colName_) is ColExpr ce) { tr = ce.tabRef_; } } if (tr is BaseTableRef btr) { var stats = Catalog.sysstat_.GetColumnStat(btr.relname_, col.colName_); return(stats?.EstDistinct() ?? 0); } } return(0); } ulong card; node.CreateKeyList(); var cardl = node.lchild_().Card(); var cardr = node.rchild_().Card(); ulong dl = 0, dr = 0, mindlr = 1; for (int i = 0; i < node.leftKeys_.Count; i++) { var lv = node.leftKeys_[i]; dl = getDistinct(lv); var rv = node.rightKeys_[i]; dr = getDistinct(rv); if (node.ops_[i] != "=") { mindlr = 0; break; } mindlr = mindlr * Math.Min(dl, dr); } if (mindlr != 0) { card = Math.Max(1, (cardl * cardr) / mindlr); } else { // fall back to the old estimator card = DefaultEstimate(node); } return(card); }
public override CGroupMember Apply(CGroupMember expr) { LogicJoin log = expr.logic_ as LogicJoin; var l = new PhysicMemoRef(log.l_()); var r = new PhysicMemoRef(log.r_()); PhysicNode phy = new PhysicNLJoin(log, l, r); return(new CGroupMember(phy, expr.group_)); }
public override CGroupMember Apply(CGroupMember expr) { LogicJoin log = expr.logic_ as LogicJoin; var l = new PhysicMemoRef(log.l_()); var r = new PhysicMemoRef(log.r_()); var hashjoin = new PhysicHashJoin(log, l, r); return(new CGroupMember(hashjoin, expr.group_)); }
public override bool Appliable(CGroupMember expr) { LogicJoin log = expr.logic_ as LogicJoin; if (log is null || log is LogicMarkJoin || log is LogicSingleJoin) { return(false); } return(true); }
// A Xs B => A LOJ B if max1row is assured LogicJoin singleJoin2OuterJoin(LogicSingleJoin singJoinNode) { LogicJoin newjoin = singJoinNode; if (!singJoinNode.max1rowCheck_) { newjoin = new LogicJoin(singJoinNode.l_(), singJoinNode.r_(), singJoinNode.filter_); newjoin.type_ = JoinType.Left; } return(newjoin); }
public override CGroupMember Apply(CGroupMember expr) { LogicJoin a_bc = expr.logic_ as LogicJoin; LogicNode a = (a_bc.l_() as LogicMemoRef).Deref <LogicNode>(); LogicJoin bc = (a_bc.r_() as LogicMemoRef).Deref <LogicJoin>(); Expr bcfilter = bc.filter_; var ab = new LogicJoin(a_bc.l_(), bc.l_()); var c = bc.r_(); var ab_c = new LogicJoin(ab, c); Debug.Assert(!a.LeftReferencesRight(bc)); if (ab.LeftReferencesRight(c)) { return(expr); } // pull up all join filters and re-push them back Expr allfilters = bcfilter; if (a_bc.filter_ != null) { allfilters = allfilters.AddAndFilter(a_bc.filter_); } if (allfilters != null) { var andlist = allfilters.FilterToAndList(); andlist.RemoveAll(e => ab_c.PushJoinFilter(e)); if (andlist.Count > 0) { ab_c.filter_ = andlist.AndListToExpr(); } } // Ideally if there is no cross join in the given plan but cross join // in the new plan, we shall return the original plan. However, stop // exploration now will prevent generating other promising plans. So // we have to return the new plan. // if (expr.QueryOption().optimize_.memo_disable_crossjoin_) { if (a_bc.filter_ != null && bcfilter != null) { if (ab_c.filter_ is null || ab.filter_ is null) { return(expr); } } } return(new CGroupMember(ab_c, expr.group_)); }
public override CGroupMember Apply(CGroupMember expr) { LogicJoin join = expr.logic_ as LogicJoin; var l = join.lchild_(); var r = join.rchild_(); var f = join.filter_; Debug.Assert(!l.LeftReferencesRight(r)); if (r.LeftReferencesRight(l)) { return(expr); } LogicJoin newjoin = new LogicJoin(r, l, f); return(new CGroupMember(newjoin, expr.group_)); }
// classic formula is: // A X B => |A|*|B|/max(dA, dB) where dA,dB are distinct values of joining columns // This however does not consider join key distribution. In SQL Server 2014, it introduced // histogram join to better the estimation. // public override long LogicJoinCE(LogicJoin node) { long card; node.CreateKeyList(); var cardl = node.l_().Card(); var cardr = node.r_().Card(); long dl = 0, dr = 0, mindlr = 1; for (int i = 0; i < node.leftKeys_.Count; i++) { var lv = node.leftKeys_[i]; if (lv is ColExpr vl && vl.tabRef_ is BaseTableRef bvl) { var stat = Catalog.sysstat_.GetColumnStat(bvl.relname_, vl.colName_); dl = stat.EstDistinct(); } var rv = node.rightKeys_[i]; if (rv is ColExpr vr && vr.tabRef_ is BaseTableRef bvr) { var stat = Catalog.sysstat_.GetColumnStat(bvr.relname_, vr.colName_); dr = stat.EstDistinct(); } if (node.ops_[i] != "=") { mindlr = 0; break; } mindlr = mindlr * Math.Min(dl, dr); } if (mindlr != 0) { card = Math.Max(1, (cardl * cardr) / mindlr); } else { // fall back to the old estimator card = DefaultEstimate(node); } return(card); }
// an outer join can be converted to inner join if join condition is null-rejected. // Null-rejected condition meaning it evalues to not true (i.e., false or null) for // any null completed row generated by outer join. // // a join condition is null if // https://dev.mysql.com/doc/refman/8.0/en/outer-join-simplification.html // - It is of the form A IS NOT NULL, where A is an attribute of any of the inner tables // - It is a predicate containing a reference to an inner table that evaluates to UNKNOWN // when one of its arguments is NULL // - It is a conjunction containing a null-rejected condition as a conjunct // - It is a disjunction of null-rejected conditions // LogicJoin trySimplifyOuterJoin(LogicJoin join, Expr extraFilter) { bool nullRejectingSingleCondition(Expr condition) { // FIXME: for now, assuming any predicate is null-rejecting unless it is IS NULL Debug.Assert(condition.IsBoolean()); var bcond = condition as BinExpr; if (bcond?.op_ == "is") { return(false); } return(false); } // if no extra filters, can't convert if (extraFilter is null) { return(join); } if (join.type_ != JoinType.Left) { return(join); } // It is a conjunction containing a null-rejected condition as a conjunct var andlist = extraFilter.FilterToAndList(); bool nullreject = andlist.Where(x => nullRejectingSingleCondition(x)).Count() > 0; if (nullreject) { goto convert_inner; } // It is a conjunction containing a null-rejected condition as a conjunct return(join); convert_inner: join.type_ = JoinType.Inner; return(join); }
public override bool Appliable(CGroupMember expr) { LogicJoin join = expr.logic_ as LogicJoin; if (join is null || join is LogicMarkJoin || join is LogicSingleJoin) { return(false); } if (join.filter_.FilterHashable()) { var stmt = expr.Stmt() as SelectStmt; bool lhasSubqCol = stmt.PlanContainsCorrelatedSubquery() && TableRef.HasColsUsedBySubquries(join.l_().InclusiveTableRefs()); if (!lhasSubqCol) { return(true); } } return(false); }
public abstract ulong LogicJoinCE(LogicJoin node);
public static void Run(SQLStatement stmt, ExecContext context) { PhysicCollect PhysicCollect108 = stmt.physicPlan_.LocateNode("108") as PhysicCollect; PhysicProfiling PhysicProfiling109 = stmt.physicPlan_.LocateNode("109") as PhysicProfiling; LogicLimit LogicLimit109 = PhysicProfiling109.logic_ as LogicLimit; var filter109 = LogicLimit109.filter_; var output109 = LogicLimit109.output_; PhysicLimit PhysicLimit110 = stmt.physicPlan_.LocateNode("110") as PhysicLimit; LogicLimit LogicLimit110 = PhysicLimit110.logic_ as LogicLimit; var filter110 = LogicLimit110.filter_; var output110 = LogicLimit110.output_; PhysicProfiling PhysicProfiling111 = stmt.physicPlan_.LocateNode("111") as PhysicProfiling; LogicAgg LogicAgg111 = PhysicProfiling111.logic_ as LogicAgg; var filter111 = LogicAgg111.filter_; var output111 = LogicAgg111.output_; PhysicHashAgg PhysicHashAgg112 = stmt.physicPlan_.LocateNode("112") as PhysicHashAgg; LogicAgg LogicAgg112 = PhysicHashAgg112.logic_ as LogicAgg; var filter112 = LogicAgg112.filter_; var output112 = LogicAgg112.output_; PhysicProfiling PhysicProfiling113 = stmt.physicPlan_.LocateNode("113") as PhysicProfiling; LogicJoin LogicJoin113 = PhysicProfiling113.logic_ as LogicJoin; var filter113 = LogicJoin113.filter_; var output113 = LogicJoin113.output_; PhysicHashJoin PhysicHashJoin114 = stmt.physicPlan_.LocateNode("114") as PhysicHashJoin; LogicJoin LogicJoin114 = PhysicHashJoin114.logic_ as LogicJoin; var filter114 = LogicJoin114.filter_; var output114 = LogicJoin114.output_; PhysicProfiling PhysicProfiling115 = stmt.physicPlan_.LocateNode("115") as PhysicProfiling; LogicJoin LogicJoin115 = PhysicProfiling115.logic_ as LogicJoin; var filter115 = LogicJoin115.filter_; var output115 = LogicJoin115.output_; PhysicHashJoin PhysicHashJoin116 = stmt.physicPlan_.LocateNode("116") as PhysicHashJoin; LogicJoin LogicJoin116 = PhysicHashJoin116.logic_ as LogicJoin; var filter116 = LogicJoin116.filter_; var output116 = LogicJoin116.output_; PhysicProfiling PhysicProfiling117 = stmt.physicPlan_.LocateNode("117") as PhysicProfiling; LogicScanTable LogicScanTable117 = PhysicProfiling117.logic_ as LogicScanTable; var filter117 = LogicScanTable117.filter_; var output117 = LogicScanTable117.output_; PhysicScanTable PhysicScanTablea = stmt.physicPlan_.LocateNode("a") as PhysicScanTable; LogicScanTable LogicScanTablea = PhysicScanTablea.logic_ as LogicScanTable; var filtera = LogicScanTablea.filter_; var outputa = LogicScanTablea.output_; PhysicProfiling PhysicProfiling118 = stmt.physicPlan_.LocateNode("118") as PhysicProfiling; LogicScanTable LogicScanTable118 = PhysicProfiling118.logic_ as LogicScanTable; var filter118 = LogicScanTable118.filter_; var output118 = LogicScanTable118.output_; PhysicScanTable PhysicScanTablec = stmt.physicPlan_.LocateNode("c") as PhysicScanTable; LogicScanTable LogicScanTablec = PhysicScanTablec.logic_ as LogicScanTable; var filterc = LogicScanTablec.filter_; var outputc = LogicScanTablec.output_; var hm116 = new Dictionary <KeyList, List <TaggedRow> >(); PhysicProfiling PhysicProfiling119 = stmt.physicPlan_.LocateNode("119") as PhysicProfiling; LogicScanTable LogicScanTable119 = PhysicProfiling119.logic_ as LogicScanTable; var filter119 = LogicScanTable119.filter_; var output119 = LogicScanTable119.output_; PhysicScanTable PhysicScanTableb = stmt.physicPlan_.LocateNode("b") as PhysicScanTable; LogicScanTable LogicScanTableb = PhysicScanTableb.logic_ as LogicScanTable; var filterb = LogicScanTableb.filter_; var outputb = LogicScanTableb.output_; var hm114 = new Dictionary <KeyList, List <TaggedRow> >(); var aggrcore112 = LogicAgg112.aggrFns_; var hm112 = new Dictionary <KeyList, Row>(); var nrows110 = 0; PhysicProfiling109.nloops_++; PhysicProfiling111.nloops_++; PhysicProfiling113.nloops_++; PhysicProfiling115.nloops_++; PhysicProfiling117.nloops_++; var heapa = (LogicScanTablea.tabref_).Table().heap_.GetEnumerator(); for (;;) { Row ra = null; if (context.stop_) { break; } if (heapa.MoveNext()) { ra = heapa.Current; } else { break; } { { // projection on PhysicScanTablea: Output: a.a1[0],a.a2[1] Row rproj = new Row(2); rproj[0] = ra[0]; rproj[1] = ra[1]; ra = rproj; } PhysicProfiling117.nrows_++; var r117 = ra; var keys116 = KeyList.ComputeKeys(context, LogicJoin116.leftKeys_, r117); if (hm116.TryGetValue(keys116, out List <TaggedRow> exist)) { exist.Add(new TaggedRow(r117)); } else { var rows = new List <TaggedRow>(); rows.Add(new TaggedRow(r117)); hm116.Add(keys116, rows); } } } if (hm116.Count == 0) { return; } PhysicProfiling118.nloops_++; var heapc = (LogicScanTablec.tabref_).Table().heap_.GetEnumerator(); for (;;) { Row rc = null; if (context.stop_) { break; } if (heapc.MoveNext()) { rc = heapc.Current; } else { break; } { { // projection on PhysicScanTablec: Output: c.c2[1] Row rproj = new Row(1); rproj[0] = rc[1]; rc = rproj; } PhysicProfiling118.nrows_++; var r118 = rc; if (context.stop_) { return; } Row fakel116 = new Row(2); Row r116 = new Row(fakel116, r118); var keys116 = KeyList.ComputeKeys(context, LogicJoin116.rightKeys_, r116); bool foundOneMatch116 = false; if (hm116.TryGetValue(keys116, out List <TaggedRow> exist116)) { foundOneMatch116 = true; foreach (var v116 in exist116) { r116 = new Row(v116.row_, r118); { // projection on PhysicHashJoin116: Output: a.a1[0],a.a2[1] Row rproj = new Row(2); rproj[0] = r116[0]; rproj[1] = r116[1]; r116 = rproj; } PhysicProfiling115.nrows_++; var r115 = r116; var keys114 = KeyList.ComputeKeys(context, LogicJoin114.leftKeys_, r115); if (hm114.TryGetValue(keys114, out List <TaggedRow> exist)) { exist.Add(new TaggedRow(r115)); } else { var rows = new List <TaggedRow>(); rows.Add(new TaggedRow(r115)); hm114.Add(keys114, rows); } } } else { // no match for antisemi } } } if (hm114.Count == 0) { return; } PhysicProfiling119.nloops_++; var heapb = (LogicScanTableb.tabref_).Table().heap_.GetEnumerator(); for (;;) { Row rb = null; if (context.stop_) { break; } if (heapb.MoveNext()) { rb = heapb.Current; } else { break; } { { // projection on PhysicScanTableb: Output: b.b1[0] Row rproj = new Row(1); rproj[0] = rb[0]; rb = rproj; } PhysicProfiling119.nrows_++; var r119 = rb; if (context.stop_) { return; } Row fakel114 = new Row(2); Row r114 = new Row(fakel114, r119); var keys114 = KeyList.ComputeKeys(context, LogicJoin114.rightKeys_, r114); bool foundOneMatch114 = false; if (hm114.TryGetValue(keys114, out List <TaggedRow> exist114)) { foundOneMatch114 = true; foreach (var v114 in exist114) { r114 = new Row(v114.row_, r119); { // projection on PhysicHashJoin114: Output: a.a1[0],a.a2[1] Row rproj = new Row(2); rproj[0] = r114[0]; rproj[1] = r114[1]; r114 = rproj; } PhysicProfiling113.nrows_++; var r113 = r114; var keys = KeyList.ComputeKeys(context, LogicAgg112.keys_, r113); if (hm112.TryGetValue(keys, out Row exist)) { for (int i = 0; i < 1; i++) { var old = exist[i]; exist[i] = aggrcore112[i].Accum(context, old, r113); } } else { hm112.Add(keys, PhysicHashAgg112.AggrCoreToRow(r113)); exist = hm112[keys]; for (int i = 0; i < 1; i++) { exist[i] = aggrcore112[i].Init(context, r113); } } } } else { // no match for antisemi } } } foreach (var v112 in hm112) { if (context.stop_) { break; } var keys112 = v112.Key; Row aggvals112 = v112.Value; for (int i = 0; i < 1; i++) { aggvals112[i] = aggrcore112[i].Finalize(context, aggvals112[i]); } var r112 = new Row(keys112, aggvals112); if (true || LogicAgg112.having_.Exec(context, r112) is true) { { // projection on PhysicHashAgg112: Output: {a.a2}[0]*2,{count(a.a1)}[1],repeat('a',{a.a2}[0]) Row rproj = new Row(3); rproj[0] = ((dynamic)r112[0] * (dynamic)2); rproj[1] = r112[1]; rproj[2] = ExprSearch.Locate("74").Exec(context, r112) /*repeat('a',{a.a2}[0])*/; r112 = rproj; } PhysicProfiling111.nrows_++; var r111 = r112; nrows110++; Debug.Assert(nrows110 <= 2); if (nrows110 == 2) { context.stop_ = true; } var r110 = r111; PhysicProfiling109.nrows_++; var r109 = r110; Row newr = new Row(3); newr[0] = r109[0]; newr[1] = r109[1]; newr[2] = r109[2]; PhysicCollect108.rows_.Add(newr); Console.WriteLine(newr); } } }
public LogicNode CreateSetOpPlan(bool top = true) { if (top) { // traversal on top node is the time to examine the setop tree Debug.Assert(!IsLeaf()); VerifySelection(); } if (IsLeaf()) { return(stmt_.CreatePlan()); } else { LogicNode plan = null; var lplan = left_.CreateSetOpPlan(false); var rplan = right_.CreateSetOpPlan(false); // try to reuse existing operators to implment because users may write // SQL code like this and this helps reduce optimizer search space // switch (op_) { case "unionall": // union all keeps all rows, including duplicates plan = new LogicAppend(lplan, rplan); break; case "union": // union collect rows from both sides, and remove duplicates plan = new LogicAppend(lplan, rplan); var groupby = new List <Expr>(first_.selection_.CloneList()); plan = new LogicAgg(plan, groupby, null, null); break; case "except": // except keeps left rows not found in right case "intersect": // intersect keeps rows found in both sides var filter = FilterHelper.MakeFullComparator( left_.first_.selection_, right_.first_.selection_); var join = new LogicJoin(lplan, rplan); if (op_.Contains("except")) { join.type_ = JoinType.AntiSemi; } if (op_.Contains("intersect")) { join.type_ = JoinType.Semi; } var logfilter = new LogicFilter(join, filter); groupby = new List <Expr>(first_.selection_.CloneList()); plan = new LogicAgg(logfilter, groupby, null, null); break; case "exceptall": case "intersectall": // the 'all' semantics is a bit confusing than intuition: // {1,1,1} exceptall {1,1} => {1} // {1,1,1} intersectall {1,1} => {1,1} // throw new NotImplementedException(); default: throw new InvalidProgramException(); } return(plan); } }
public LogicJoinBlock(LogicJoin join, JoinGraph graph) { graph_ = graph; join_ = join; children_.AddRange(graph.vertices_); }
// from clause - // pair each from item with cross join, their join conditions will be handled // with where clause processing. // LogicNode transformFromClause() { LogicNode transformOneFrom(TableRef tab) { LogicNode from; switch (tab) { case BaseTableRef bref: if (bref.Table().source_ == TableDef.TableSource.Table) { from = new LogicScanTable(bref); } else { from = new LogicScanStream(bref); } if (bref.tableSample_ != null) { from = new LogicSampleScan(from, bref.tableSample_); } break; case ExternalTableRef eref: from = new LogicScanFile(eref); break; case QueryRef qref: var plan = qref.query_.CreatePlan(); if (qref is FromQueryRef && queryOpt_.optimize_.remove_from_) { from = plan; } else { string alias = null; if (qref is CTEQueryRef cq) { alias = cq.alias_; } else if (qref is FromQueryRef fq) { alias = fq.alias_; } var key = new NamedQuery(qref.query_, alias); from = new LogicFromQuery(qref, plan); subQueries_.Add(key); // if from CTE, then it could be duplicates if (!fromQueries_.ContainsKey(key)) { fromQueries_.Add(key, from as LogicFromQuery); } } break; case JoinQueryRef jref: // We will form join group on all tables and put a filter on top // of the joins as a normalized form for later processing. // // from a join b on a1=b1 or a3=b3 join c on a2=c2; // => from a , b, c where (a1=b1 or a3=b3) and a2=c2; // LogicJoin subjoin = new LogicJoin(null, null); Expr filterexpr = null; for (int i = 0; i < jref.tables_.Count; i++) { LogicNode t = transformOneFrom(jref.tables_[i]); var children = subjoin.children_; if (children[0] is null) { children[0] = t; } else { if (children[1] is null) { children[1] = t; } else { subjoin = new LogicJoin(t, subjoin); } subjoin.type_ = jref.joinops_[i - 1]; filterexpr = filterexpr.AddAndFilter(jref.constraints_[i - 1]); } } Debug.Assert(filterexpr != null); from = new LogicFilter(subjoin, filterexpr); break; default: throw new InvalidProgramException(); } return(from); } LogicNode root; if (from_.Count >= 2) { var join = new LogicJoin(null, null); var children = join.children_; from_.ForEach(x => { LogicNode from = transformOneFrom(x); if (children[0] is null) { children[0] = from; } else { children[1] = (children[1] is null) ? from : new LogicJoin(from, children[1]); } }); root = join; } else if (from_.Count == 1) { root = transformOneFrom(from_[0]); } else { root = new LogicResult(selection_); } // distributed plan is required if any table is distributed, subquery // referenced tables are not counted here. So we may have the query // shape with main queyr not distributed but subquery is. // bool hasdtable = false; bool onlyreplicated = true; from_.ForEach(x => checkifHasdtableAndifOnlyReplicated(x, ref hasdtable, ref onlyreplicated)); if (hasdtable) { Debug.Assert(!distributed_); distributed_ = true; // distributed table query can also use memo // remote exchange is considered in memo optimization queryOpt_.optimize_.memo_use_remoteexchange_ = true; if (onlyreplicated) { root = new LogicGather(root, new List <int> { 0 }); } else { root = new LogicGather(root); } } return(root); }
// from clause - // pair each from item with cross join, their join conditions will be handled // with where clauss processing. // LogicNode transformFromClause() { LogicNode transformOneFrom(TableRef tab) { LogicNode from; switch (tab) { case BaseTableRef bref: from = new LogicScanTable(bref); break; case ExternalTableRef eref: from = new LogicScanFile(eref); break; case QueryRef qref: var plan = qref.query_.CreatePlan(); if (qref is FromQueryRef && queryOpt_.optimize_.remove_from_) { from = plan; } else { string alias = null; if (qref is CTEQueryRef cq) { alias = cq.alias_; } else if (qref is FromQueryRef fq) { alias = fq.alias_; } var key = new NamedQuery(qref.query_, alias); from = new LogicFromQuery(qref, plan); subQueries_.Add(key); // if from CTE, then it could be duplicates if (!fromQueries_.ContainsKey(key)) { fromQueries_.Add(key, from as LogicFromQuery); } } break; case JoinQueryRef jref: // We will form join group on all tables and put a filter on top // of the joins as a normalized form for later processing. // // from a join b on a1=b1 or a3=b3 join c on a2=c2; // => from a , b, c where (a1=b1 or a3=b3) and a2=c2; // LogicJoin subjoin = new LogicJoin(null, null); Expr filterexpr = null; for (int i = 0; i < jref.tables_.Count; i++) { LogicNode t = transformOneFrom(jref.tables_[i]); var children = subjoin.children_; if (children[0] is null) { children[0] = t; } else { if (children[1] is null) { children[1] = t; } else { subjoin = new LogicJoin(t, subjoin); } subjoin.type_ = jref.joinops_[i - 1]; filterexpr = filterexpr.AddAndFilter(jref.constraints_[i - 1]); } } Debug.Assert(filterexpr != null); from = new LogicFilter(subjoin, filterexpr); break; default: throw new Exception(); } return(from); } LogicNode root; if (from_.Count >= 2) { var join = new LogicJoin(null, null); var children = join.children_; from_.ForEach(x => { LogicNode from = transformOneFrom(x); if (children[0] is null) { children[0] = from; } else { children[1] = (children[1] is null) ? from : new LogicJoin(from, children[1]); } }); root = join; } else if (from_.Count == 1) { root = transformOneFrom(from_[0]); } else { root = new LogicResult(selection_); } return(root); }