package com.tesora.dve.sql.transform; /* * #%L * Tesora Inc. * Database Virtualization Engine * %% * Copyright (C) 2011 - 2014 Tesora Inc. * %% * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * #L% */ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import com.tesora.dve.common.LinkedHashSetFactory; import com.tesora.dve.common.MultiMap; import com.tesora.dve.sql.SchemaException; import com.tesora.dve.sql.ParserException.Pass; import com.tesora.dve.sql.expression.ColumnKey; import com.tesora.dve.sql.expression.TableKey; import com.tesora.dve.sql.node.Edge; import com.tesora.dve.sql.node.EdgeName; import com.tesora.dve.sql.node.LanguageNode; import com.tesora.dve.sql.node.Traversal; import com.tesora.dve.sql.node.expression.ColumnInstance; import com.tesora.dve.sql.node.expression.ConstantExpression; import com.tesora.dve.sql.node.expression.ExpressionNode; import com.tesora.dve.sql.node.expression.FunctionCall; import com.tesora.dve.sql.node.test.EngineConstant; import com.tesora.dve.sql.node.test.EngineToken; import com.tesora.dve.sql.schema.FunctionName; import com.tesora.dve.sql.schema.PEColumn; import com.tesora.dve.sql.schema.SchemaContext; import com.tesora.dve.sql.transform.constraints.ConstraintCollection; import com.tesora.dve.sql.transform.constraints.PlanningConstraint; import com.tesora.dve.sql.util.Functional; import com.tesora.dve.sql.util.ListOfPairs; import com.tesora.dve.sql.util.ListSet; import com.tesora.dve.sql.util.UnaryFunction; public class ConstraintCollector { public static PlanningConstraint chooseBest(SchemaContext sc, Iterable<PlanningConstraint> constraints) { PlanningConstraint best = null; for(PlanningConstraint pc : constraints) { if (best == null) best = pc; else if (best.compareTo(pc) >= 0) best = pc; } return best; } private final LanguageNode rootNode; private final SchemaContext context; private ListSet<Part> parts; private final boolean findDistKeys; private final boolean findKeys; private final boolean allowPartialMatches; public static PlanningConstraint findBestConstraint(SchemaContext sc, LanguageNode node, boolean partials) { ConstraintCollector cc = new ConstraintCollector(sc,node,false,true,partials); return chooseBest(sc,cc.getConstraints()); } public ConstraintCollector(SchemaContext sc,LanguageNode rn, boolean distKeys, boolean keys, boolean partials) { rootNode = rn; context = sc; findDistKeys = distKeys; findKeys = keys; allowPartialMatches = partials; } private ListSet<Part> buildParts() { CollectorTraversal action = new CollectorTraversal(this); LanguageNode flattened = new AndOrCollector().traverse(rootNode); action.traverse(flattened); return action.getCompletedParts(); } public ListSet<Part> getParts() { if (parts == null) parts = buildParts(); return parts; } // convert the parts into useful information // for this - complete single parts are constraints, and complete series parts are constraint collections public ListSet<PlanningConstraint> getConstraints() { ListSet<PlanningConstraint> out = new ListSet<PlanningConstraint>(); ListSet<Part> any = getParts(); if (any == null) return out; for(Part p : any) { if (!p.isComplete()) continue; out.addAll(p.convertAll(context)); } return out; } protected Set<MatchableKey> isQualifyingColumn(ColumnInstance ci){ return isQualifyingColumn(ci.getPEColumn()); } protected Set<MatchableKey> isQualifyingColumn(PEColumn c) { HashSet<MatchableKey> allSuch = new HashSet<MatchableKey>(); if (findDistKeys) { ListSet<PEColumn> cols = c.getTable().getDistributionVector(context).getColumns(context); if (cols.contains(c)) allSuch.add(c.getTable().getDistributionVector(context)); } if (findKeys) allSuch.addAll(c.getReferencedBy(context)); return allSuch; } protected AndedParts completeKey(EqualityPart existing, PEColumn c, ConstantExpression litex, Set<MatchableKey> onKeys) { EqualityPart np = makeNewEqualityPart(existing,c,litex, onKeys); FunctionCall andEx = new FunctionCall(FunctionName.makeAnd(),(ExpressionNode)existing.getParent(),(ExpressionNode)np.getParent()); ArrayList<Part> subp = new ArrayList<Part>(); subp.add(existing); subp.add(np); return buildAndedParts(andEx,subp); } protected AndedParts completeKey(AndedParts existing, PEColumn c, ConstantExpression litex, Set<MatchableKey> onKeys) { EqualityPart ep = (EqualityPart) existing.getParts().get(0); EqualityPart np = makeNewEqualityPart(ep,c,litex, onKeys); ArrayList<Part> subp = new ArrayList<Part>(); subp.addAll(existing.getParts()); subp.add(np); List<ExpressionNode> subexprs = Functional.apply(subp, Part.castToExpression); FunctionCall andEx = new FunctionCall(FunctionName.makeAnd(),subexprs); return buildAndedParts(andEx,subp); } private EqualityPart makeNewEqualityPart(EqualityPart existing, PEColumn c, ConstantExpression litex, Set<MatchableKey> onKeys) { TableKey tk = existing.getColumn().getColumnKey().getTableKey(); ColumnInstance nc = new ColumnInstance(c,tk.toInstance()); FunctionCall eq = new FunctionCall(FunctionName.makeEquals(),nc,litex); EqualityPart eqp = buildEqualityPart(eq, nc, litex, onKeys); return eqp; } protected EqualityPart buildEqualityPart(LanguageNode parent, ColumnInstance ci, ConstantExpression litex, Set<MatchableKey> onKeys) { return new EqualityPart(context, parent, ci, litex, onKeys); } protected OredParts buildOredParts(LanguageNode parent,TableKey tk, List<Part> comps, Set<MatchableKey> onKeys) { return new OredParts(parent, tk, comps, onKeys); } protected AndedParts buildAndedParts(LanguageNode parent, TableKey tk, List<Part> parts, Set<MatchableKey> onKeys) { return new AndedParts(parent, tk, parts, onKeys); } protected final OredParts buildOredParts(LanguageNode parent, List<Part> comps) { return buildOredParts(parent, assertSingleTableKey(comps), comps, collapseKeys(comps)); } protected final Set<MatchableKey> collapseKeys(List<Part> comps) { HashSet<MatchableKey> out = null; for(Part p : comps) { if (out == null) out = new HashSet<MatchableKey>(p.getKeys()); else if (!out.equals(p.getKeys())) throw new SchemaException(Pass.PLANNER, "Collapse keys: multiple differing sets of keys"); } return out; } protected final AndedParts buildAndedParts(LanguageNode parent, List<Part> comps) { // in this case, the anded parts is the intersection of the keys HashSet<MatchableKey> any = null; for(Part p : comps) { if (any == null) any = new HashSet<MatchableKey>(p.getKeys()); else any.retainAll(p.getKeys()); } if (any.isEmpty()) return null; return buildAndedParts(parent, assertSingleTableKey(comps), comps, any); } protected TableKey assertSingleTableKey(List<Part> comps) { TableKey tk = null; for(Part p : comps) { if (tk == null) tk = p.getTableKey(); else if (!tk.equals(p.getTableKey())) throw new SchemaException(Pass.PLANNER, "Mixed table keys for key collector"); } return tk; } // return all keys for which it is complete protected Set<MatchableKey> isComplete(Part ep) { HashSet<PEColumn> cols = new HashSet<PEColumn>(); for(ColumnKey ck : ep.getColumns()) cols.add(ck.getPEColumn()); HashSet<MatchableKey> complete = new HashSet<MatchableKey>(); for(MatchableKey mk : ep.getKeys()) { if (mk.isComplete(context, cols, allowPartialMatches)) complete.add(mk); } return complete; } protected MultiMap<MatchableKey, PEColumn> getNeeded(Collection<ColumnKey> colKeys, Set<MatchableKey> onKeys) { MultiMap<MatchableKey, PEColumn> needed = new MultiMap<MatchableKey,PEColumn>(); List<PEColumn> have = Functional.apply(colKeys, ColumnKey.getPEColumn); for(MatchableKey mk : onKeys) { HashSet<PEColumn> buf = new HashSet<PEColumn>(mk.getColumns(context)); buf.removeAll(have); if (!buf.isEmpty()) needed.putAll(mk, buf); } return needed; } protected AndedParts maybeMakeComplete(Part sp, Set<MatchableKey> onKeys) { ConstantExpression litex = context.getPolicyContext().getTenantIDLiteral(false); if (litex == null) return null; MultiMap<MatchableKey, PEColumn> needed = getNeeded(sp.getColumns(),onKeys); if (needed.isEmpty()) return null; else { HashSet<MatchableKey> complete = new HashSet<MatchableKey>(); PEColumn tenCol = null; for(MatchableKey mk : needed.keySet()) { Collection<PEColumn> sub = needed.get(mk); if (sub == null || sub.isEmpty()) continue; if (sub.size() == 1) { PEColumn c = sub.iterator().next(); if (c.isTenantColumn()) { complete.add(mk); tenCol = c; } } } if (!complete.isEmpty()) { if (sp instanceof EqualityPart) return completeKey((EqualityPart)sp,tenCol,litex,complete); else return completeKey((AndedParts)sp,tenCol,litex,complete); } } return null; } private static class CollectorTraversal extends Traversal { private final ConstraintCollector parent; private boolean stopped; // the target is a Part of some variety protected Map<LanguageNode, Part> state; protected ListSet<Part> completedParts; protected LanguageNode avoiding = null; private CollectorTraversal(ConstraintCollector implementor) { super(Order.POSTORDER, ExecStyle.ONCE); parent = implementor; stopped = false; state = new HashMap<LanguageNode, Part>(); completedParts = new ListSet<Part>(); } public void push(LanguageNode n) { super.push(n); if (n instanceof FunctionCall) { FunctionCall fc = (FunctionCall) n; if (fc.getFunctionName().isEffectiveNot()) avoiding = n; } } public LanguageNode pop() { LanguageNode ln = super.pop(); if (ln == avoiding) avoiding = null; return ln; } private static final EnumSet<EdgeName> denyMask = EnumSet.of(EdgeName.UPDATE_EXPRS, EdgeName.PROJECTION, EdgeName.ORDERBY, EdgeName.GROUPBY, EdgeName.HAVING, EdgeName.SUBQUERY); public boolean allow(Edge<?,?> e) { // we don't want to consider anything not in the from or where clause // we also don't want to cross subquery boundaries if (e.getName().any(denyMask)) return false; return !stopped && avoiding == null; } public boolean allow(LanguageNode ln) { return !stopped && avoiding == null; } protected ListSet<Part> getCompletedParts() { if (stopped) return null; ListSet<Part> cols = new ListSet<Part>(); for(Part p : completedParts) { if (p instanceof OredParts) cols.add(p); } completedParts.removeAll(cols); for(Part p : cols) { for(Part sp : p.getParts()) { completedParts.remove(sp); } } completedParts.addAll(cols); return completedParts; } @Override public LanguageNode action(LanguageNode in) { if (stopped) return in; if (EngineConstant.FUNCTION.has(in)) { FunctionCall fc = (FunctionCall)in; LanguageNode after = in; if (fc.getFunctionName().isEquals()) after = handleEqualsFunction(fc); else if (fc.getFunctionName().isAnd()) after = handleAndFunction(fc); else if (fc.getFunctionName().isOr()) after = handleOrFunction(fc); else if (fc.getFunctionName().isEffectiveNot()) return fc; else if (fc.getFunctionName().isIn()) after = handleInFunction(fc); return after; } return in; } private void broadening() { stopped = true; } private void setComplete(Part p) { p.setComplete(); completedParts.add(p); } private LanguageNode handleEqualsFunction(FunctionCall fc) { ExpressionNode lhs = fc.getParameters().get(0); ExpressionNode rhs = fc.getParameters().get(1); if (EngineConstant.CONSTANT.has(rhs) && EngineConstant.COLUMN.has(lhs)) { Set<MatchableKey> matching = parent.isQualifyingColumn((ColumnInstance)lhs); if (!matching.isEmpty()) { ColumnInstance ci = (ColumnInstance)lhs; ConstantExpression litex = (ConstantExpression)rhs; Part p = buildPart(fc, ci, litex, matching); return p.getParent(); } } return fc; } private LanguageNode handleAndFunction(FunctionCall fc) { // and functions take incomplete simple parts and turn them into complete parts, if so desired // note that not all subexprs of the and expression may be subparts or parts - in that case // just ignore them MultiMap<TableKey,Part> incompletes = new MultiMap<TableKey,Part>(); ArrayList<ExpressionNode> ok = new ArrayList<ExpressionNode>(); MultiMap<TableKey,Part> subparts = new MultiMap<TableKey,Part>(); for(ExpressionNode en : fc.getParameters()) { Part p = state.get(en); if (p == null || p.isComplete()) { ok.add(en); if (p != null) subparts.put(p.getTableKey(), p); continue; } incompletes.put(p.getTableKey(),p); } if (incompletes.isEmpty()) return fc; // ok has all the subexprs that are irrelevant, whereas incompletes has subexprs that may be pertinent // now we have the problem of a mishmash of incompletes. some may be complex, some may be simple // some may be collections. we need to handle cases like the following: // (a = 1) and (b = 2) {a,b} (1 key) // (a = 1) and (b = 2 or b = 3) {a,b} (2 keys) // (a = 1 or a = 2) and (b = 3) {a,b} (2 keys) // (a = 1 or a = 2) and (b = 3 or b = 4) {a,b} (4 keys) // all of the above, where the result is still not complete due to missing tenant column MultiMap<Part,PEColumn> needed = new MultiMap<Part,PEColumn>(new LinkedHashSetFactory<PEColumn>()); MultiMap<ColumnKey, Part> classified = new MultiMap<ColumnKey, Part>(); for(Part p : incompletes.values()) { ListSet<ColumnKey> has = new ListSet<ColumnKey>(); has.addAll(p.getColumns()); MultiMap<MatchableKey,PEColumn> subn = parent.getNeeded(has, p.getKeys()); for(MatchableKey mk : subn.keySet()) { needed.putAll(p, subn.get(mk)); } for(ColumnKey ck : has) { classified.put(ck, p); } } // so let's say we have a part that is (a = 1 and b = 2), needs c and tenant, and we have a part // that is c in (1,2,3). The needed for (a = 1 and b = 2) is {c,tenant}. we'll pull (c in (1,2,3)) // so we'll get at least (a = 1 and b = 2 and c = 3) or (a = 1 and b =2 and c = 3) ... // these we can then individually try to complete. while(!needed.isEmpty()) { combineParts(needed, classified, ok, subparts); } for(Part p : subparts.values()) state.put(p.getParent(), p); if (ok.size() == 1) return ok.get(0); else { // what's left is a mix of unrelated and complete or incomplete subexprs. unrelated nodes // would come in from above, as would previously complete. return new FunctionCall(FunctionName.makeAnd(),ok); } } private void combineParts(MultiMap<Part, PEColumn> needed, MultiMap<ColumnKey,Part> classified, List<ExpressionNode> andexprs, MultiMap<TableKey,Part> andparts) { Part p = needed.keySet().iterator().next(); Collection<PEColumn> missing = needed.get(p); if (missing == null || missing.isEmpty()) { // nothing further to be done. andexprs.add((ExpressionNode)p.getParent()); needed.remove(p); andparts.put(p.getTableKey(),p); return; } ListSet<Part> containingMissing = new ListSet<Part>(); boolean first = true; for(PEColumn pec : missing) { ColumnKey ck = new ColumnKey(p.getTableKey(),pec); Collection<Part> matching = classified.get(ck); if (matching == null || matching.isEmpty()) continue; if (first) containingMissing.addAll(matching); else { containingMissing.retainAll(matching); } } if (containingMissing.isEmpty()) { andexprs.add((ExpressionNode)p.getParent()); andparts.put(p.getTableKey(),p); needed.remove(p); } else { ListSet<Part> toCombine = new ListSet<Part>(); toCombine.add(p); toCombine.addAll(containingMissing); ListSet<Part> toRemove = new ListSet<Part>(); toRemove.addAll(toCombine); Part clhs = choosePartForAndCombo(toCombine); toCombine.remove(clhs); while(!toCombine.isEmpty()) { Part crhs = choosePartForAndCombo(toCombine); toCombine.remove(crhs); clhs = combineParts(clhs,crhs); } for(Part rp : toRemove) { needed.remove(rp); ArrayList<ColumnKey> classKeys = new ArrayList<ColumnKey>(classified.keySet()); for(ColumnKey ck : classKeys) { Collection<Part> sub = classified.get(ck); if (sub == null || sub.isEmpty()) continue; if (sub.contains(rp)) classified.remove(ck, rp); } } andexprs.add((ExpressionNode)clhs.getParent()); andparts.put(clhs.getTableKey(),clhs); } } private Part choosePartForAndCombo(ListSet<Part> in) { Part out = null; for(Part p : in) { if (p instanceof OredParts) { out = p; break; } } if (out == null) out = in.get(0); return out; } private Part combineParts(Part lp, Part rp) { if (!(lp instanceof OredParts) && !(rp instanceof OredParts)) { return buildNewComplexPart(lp, rp); } else if (lp instanceof OredParts && rp instanceof OredParts) { return buildNewMultiMultiPart((OredParts)lp,(OredParts)rp); } else if (lp instanceof OredParts && !(rp instanceof OredParts)) { return buildNewPartCollection((OredParts)lp,rp); } else if (!(lp instanceof OredParts) && rp instanceof OredParts) { return buildNewPartCollection((OredParts)rp,lp); } else { throw new SchemaException(Pass.PLANNER, "Can't combine parts: " + lp.getClass().getName() + " and " + rp.getClass().getName()); } } private Part buildNewComplexPart(Part lp, Part rp) { ListSet<Part> allParts = new ListSet<Part>(); allParts.addAll(lp.getParts()); allParts.addAll(rp.getParts()); FunctionCall andCall = new FunctionCall(FunctionName.makeAnd(), Functional.apply(allParts, Part.castToExpression)); AndedParts cp = parent.buildAndedParts(andCall, allParts); if (cp == null) return null; Set<MatchableKey> completeFor = parent.isComplete(cp); if (!completeFor.isEmpty()) setComplete(cp); else { AndedParts ncp = parent.maybeMakeComplete(cp,cp.getKeys()); if (ncp != null) { setComplete(ncp); return ncp; } } return cp; } private Part buildNewPartCollection(OredParts lp, Part rp) { // rp could be simple or complex, lp has either simple or complex elements. // the strategy here is to build a new complex part for each item in the collection, and return // a new collection. Since PartCollections are only built for or/in, we use the or connector ArrayList<Part> newParts = new ArrayList<Part>(); for(Part sp : lp.getParts()) { newParts.add(buildNewComplexPart(sp, rp.copy())); } FunctionCall orcall = new FunctionCall(FunctionName.makeOr(), Functional.apply(newParts, Part.castToExpression)); OredParts op = parent.buildOredParts(orcall, newParts); if (op.isComplete()) setComplete(op); return op; } private Part buildNewMultiMultiPart(OredParts lp, OredParts rp) { ArrayList<Part> newParts = new ArrayList<Part>(); for(Part lpc : lp.getParts()) { for(Part rpc : rp.getParts()) { newParts.add(combineParts(lpc.copy(),rpc.copy())); } } FunctionCall orcall = new FunctionCall(FunctionName.makeOr(), Functional.apply(newParts, Part.castToExpression)); OredParts op = parent.buildOredParts(orcall, newParts); if (op.isComplete()) setComplete(op); return op; } private LanguageNode handleOrFunction(FunctionCall fc) { boolean allands = false; LanguageNode ln = fc.getParent(); if (ln == parent.rootNode) { if (EngineConstant.FUNCTION.has(ln,EngineConstant.AND)) allands = true; } else { while(ln != null && ln != parent.rootNode) { if (EngineConstant.FUNCTION.has(ln, EngineConstant.AND)) { allands = true; } else { allands = false; break; } ln = ln.getParent(); } } ArrayList<Part> subparts = new ArrayList<Part>(); for(ExpressionNode en : fc.getParameters()) { Part p = state.get(en); if (p == null) { if (!allands) broadening(); return fc; } subparts.add(p); } // now to figure out what we have. we may have a a bunch of incomplete subexprs, // in which case we took a = 1 or a = 2 or a = 3 => a part collection of incompletes // or we may have (a = 1 and b = 2) or (a =3 and b =4) ... - likewise // sort subparts by table key; if there's more than one let's just set broadening for now TableKey tk = null; // has to be the same key as well MatchableKey mk = null; for(Part p : subparts) { if (tk == null) { tk = p.getTableKey(); mk = p.getKeys().iterator().next(); } else if (!tk.equals(p.getTableKey()) || !mk.equals(p.getKeys().iterator().next())) { broadening(); return fc; } } OredParts op = parent.buildOredParts(fc, subparts); if (op.isComplete()) setComplete(op); state.put(fc, op); return fc; } private LanguageNode handleInFunction(FunctionCall fc) { ExpressionNode lhs = fc.getParameters().get(0); if (!EngineConstant.COLUMN.has(lhs)) return fc; Set<MatchableKey> matching = parent.isQualifyingColumn((ColumnInstance)lhs); if (matching.isEmpty()) return fc; // only matches if all the rhs are constant for(ExpressionNode en : fc.getParameters(1)) { if (!EngineConstant.CONSTANT.has(en)) return fc; } ColumnInstance ci = (ColumnInstance) lhs; ArrayList<ExpressionNode> subexprs = new ArrayList<ExpressionNode>(); ArrayList<Part> parts = new ArrayList<Part>(); for(ExpressionNode en : fc.getParameters(1)) { ColumnInstance tci = (ColumnInstance)ci.copy(null); ConstantExpression litex = (ConstantExpression) en; ExpressionNode subeq = new FunctionCall(FunctionName.makeEquals(),tci,litex); Part p = buildPart(subeq, tci, litex, matching); parts.add(p); subexprs.add((ExpressionNode)p.getParent()); } if (subexprs.size() > 1) { FunctionCall orcall = new FunctionCall(FunctionName.makeOr(),subexprs); OredParts pc = parent.buildOredParts(orcall,parts); if (pc.isComplete()) setComplete(pc); orcall.setGrouped(); state.put(orcall, pc); return orcall; } else { Part p = parts.get(0); return p.getParent(); } } private Part buildPart(LanguageNode parentNode, ColumnInstance ci, ConstantExpression litex, Set<MatchableKey> onKeys) { EqualityPart sp = parent.buildEqualityPart(parentNode, ci, litex, onKeys); Set<MatchableKey> complete = parent.isComplete(sp); if (!complete.isEmpty()) { setComplete(sp); state.put(parentNode, sp); } else { AndedParts xformed = parent.maybeMakeComplete(sp, onKeys); if (xformed != null) { setComplete(xformed); LanguageNode ret = xformed.getParent(); state.put(ret, xformed); return xformed; } else { state.put(sp.getParent(), sp); } } return sp; } } // make a common base class for parts so you can represent series of them // allows you to handle a in (1,2,3) and b = 2 as.. (a = 1 and b = 2) or (a = 2 and b = 2) or (a =3 and b = 3) public static abstract class Part { protected LanguageNode parent; // disallow parts to cross tables by recording table key as well protected TableKey tableKey; // parts are against a set of possibly matching keys protected Set<MatchableKey> keys; public Part(LanguageNode ln, TableKey tk, Set<MatchableKey> onKeys) { parent = ln; tableKey = tk; keys = new HashSet<MatchableKey>(onKeys); } public Part(Part p) { parent = p.parent; tableKey = p.tableKey; keys = p.keys; } public LanguageNode getParent() { return parent; } public TableKey getTableKey() { return tableKey; } public Set<MatchableKey> getKeys() { return keys; } public abstract List<ColumnKey> getColumns(); public abstract boolean isComplete(); public abstract void setComplete(); public abstract ListSet<Part> getParts(); public abstract Part copy(); public abstract ListSet<PlanningConstraint> convertAll(SchemaContext sc); public static final UnaryFunction<ExpressionNode, Part> castToExpression = new UnaryFunction<ExpressionNode,Part>() { @Override public ExpressionNode evaluate(Part object) { return (ExpressionNode) object.getParent(); } }; @Override public String toString() { return parent.toString(); } } // collection of parts public static class OredParts extends Part{ protected ListSet<Part> parts; public OredParts(LanguageNode parent, TableKey tk, Collection<Part> p, Set<MatchableKey> onKeys) { super(parent,tk, onKeys); parts = new ListSet<Part>(); parts.addAll(p); ((ExpressionNode)parent).setGrouped(); } public OredParts(OredParts o) { super(o); parts = o.parts; } @Override public ListSet<Part> getParts() { return parts; } @Override public List<ColumnKey> getColumns() { ArrayList<ColumnKey> cols = new ArrayList<ColumnKey>(); for(Part p : parts) cols.addAll(p.getColumns()); return cols; } @Override public ListSet<PlanningConstraint> convertAll(SchemaContext sc) { HashMap<MatchableKey,ConstraintCollection> byKey = new HashMap<MatchableKey,ConstraintCollection>(); for(Part p : parts) { ListSet<PlanningConstraint> sub = p.convertAll(sc); for(PlanningConstraint c : sub) { ConstraintCollection cc = byKey.get(c.getKey(sc)); if (cc == null) { cc = new ConstraintCollection(); byKey.put(c.getKey(sc), cc); } cc.add(c); } } ListSet<PlanningConstraint> out = new ListSet<PlanningConstraint>(); for(ConstraintCollection cc : byKey.values()) out.add(cc); return out; } @Override public boolean isComplete() { for(Part p : parts) if (!p.isComplete()) return false; return true; } @Override public void setComplete() { // does nothing - all state depends on children } @Override public Part copy() { List<Part> newparts = new ArrayList<Part>(); for(Part p : parts) newparts.add(p.copy()); FunctionCall newp = new FunctionCall(FunctionName.makeOr(), Functional.apply(newparts, new UnaryFunction<ExpressionNode, Part>() { @Override public ExpressionNode evaluate(Part object) { return (ExpressionNode) object.getParent(); } })); return new AndedParts(newp, getTableKey(), newparts, isComplete(), keys); } } public static class EqualityPart extends Part { protected ColumnInstance column; protected ConstantExpression constant; protected boolean complete; protected SchemaContext context; public EqualityPart(SchemaContext sc, LanguageNode owning, ColumnInstance ci, ConstantExpression litex, Set<MatchableKey> onKeys) { this(sc, owning, ci, litex, false,onKeys); } private EqualityPart(SchemaContext sc, LanguageNode owning, ColumnInstance ci, ConstantExpression litex, boolean comp, Set<MatchableKey> onKeys) { super(owning,ci.getColumnKey().getTableKey(),onKeys); column = ci; constant = litex; complete = comp; context = sc; } public ColumnInstance getColumn() { return column; } public ConstantExpression getLiteral() { return constant; } @Override public List<ColumnKey> getColumns() { return Collections.singletonList(column.getColumnKey()); } @Override public boolean isComplete() { return complete; } @Override public void setComplete() { complete = true; } @Override public ListSet<Part> getParts() { ListSet<Part> me = new ListSet<Part>(); me.add(this); return me; } @Override public ListSet<PlanningConstraint> convertAll(SchemaContext sc) { ListSet<PlanningConstraint> out = new ListSet<PlanningConstraint>(); ListOfPairs<PEColumn,ConstantExpression> values = new ListOfPairs<PEColumn,ConstantExpression>(); values.add(column.getPEColumn(),constant); for(MatchableKey mk : getKeys()) { List<PEColumn> keyCols = mk.getColumns(context); if (keyCols.size() > 1) continue; out.add(mk.buildEmptyConstraint(sc, getTableKey(), values)); } return out; } @Override public Part copy() { FunctionCall nfc = (FunctionCall)getParent().copy(null); ColumnInstance ci = (ColumnInstance) nfc.getParametersEdge().get(0); ConstantExpression litex = (ConstantExpression) nfc.getParametersEdge().get(1); return new EqualityPart(context, nfc, ci, litex,complete,keys); } } public static class AndedParts extends Part { protected ListSet<Part> parts; protected boolean complete; public AndedParts(LanguageNode owning, TableKey tk, List<Part> inparts, Set<MatchableKey> onKeys) { this(owning,tk,inparts,false, onKeys); ((ExpressionNode)owning).setGrouped(); } private AndedParts(LanguageNode owning, TableKey tk, List<Part> inparts, boolean comp, Set<MatchableKey> onKeys) { super(owning, tk, onKeys); this.parts = new ListSet<Part>(); this.parts.addAll(inparts); complete = comp; } @Override public ListSet<Part> getParts() { return this.parts; } @Override public List<ColumnKey> getColumns() { return Functional.apply(getParts(), new UnaryFunction<ColumnKey,Part>() { @Override public ColumnKey evaluate(Part object) { return object.getColumns().get(0); } }); } @Override public boolean isComplete() { return complete; } @Override public void setComplete() { complete = true; } @Override public ListSet<PlanningConstraint> convertAll(SchemaContext sc) { ListSet<PlanningConstraint> out = new ListSet<PlanningConstraint>(); ListOfPairs<PEColumn,ConstantExpression> values = new ListOfPairs<PEColumn,ConstantExpression>(); for(Part p : parts) { EqualityPart ep = (EqualityPart) p; values.add(ep.getColumn().getPEColumn(),ep.getLiteral()); } for(MatchableKey mk : getKeys()) { out.add(mk.buildEmptyConstraint(sc, getTableKey(), values)); } return out; } @Override public Part copy() { List<Part> newparts = new ArrayList<Part>(); for(Part p : parts) newparts.add(p.copy()); FunctionCall newp = new FunctionCall(FunctionName.makeAnd(), Functional.apply(newparts, new UnaryFunction<ExpressionNode, Part>() { @Override public ExpressionNode evaluate(Part object) { return (ExpressionNode) object.getParent(); } })); return new AndedParts(newp, getTableKey(), newparts, complete, keys); } } // given and(a,and(b,c)), and(and(a,b),c), convert to and(a,b,c) // given and(and(a,b),and(c,d)) convert to and(a,b,c,d) public static class AndOrCollector extends Traversal { public AndOrCollector() { super(Order.POSTORDER, ExecStyle.REPEAT); } @Override public LanguageNode action(LanguageNode in) { if (EngineConstant.FUNCTION.has(in, EngineConstant.AND)) { return fold(in, EngineConstant.AND); } else if (EngineConstant.FUNCTION.has(in, EngineConstant.OR)) { return fold(in, EngineConstant.OR); } else { return in; } } private LanguageNode fold(LanguageNode in, EngineToken variety) { FunctionCall fc = (FunctionCall) in; ArrayList<ExpressionNode> grouped = new ArrayList<ExpressionNode>(); ArrayList<ExpressionNode> same = new ArrayList<ExpressionNode>(); ArrayList<ExpressionNode> others = new ArrayList<ExpressionNode>(); for(ExpressionNode p : fc.getParameters()) { if (EngineConstant.FUNCTION.has(p, variety)) { if (p.isGrouped()) grouped.add(p); else same.add(p); } else { others.add(p); } } if (same.isEmpty() || !grouped.isEmpty()) return in; // if others is empty, this is op(op(a,b),op(c,d)), build op(a,b,c,d) // if others is not empty, this is op(a,op(b,c)) or op(op(a,b),c), build op(a,b,c) ArrayList<ExpressionNode> allsubs = new ArrayList<ExpressionNode>(); for(ExpressionNode p : same) { FunctionCall pfc = (FunctionCall) p; allsubs.addAll(pfc.getParameters()); } allsubs.addAll(others); // we're going to group this as well FunctionCall ofc = new FunctionCall(fc.getFunctionName(), allsubs); ofc.setGrouped(); return ofc; } } }