package com.tesora.dve.sql.statement.dml; /* * #%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.Arrays; import java.util.List; import com.tesora.dve.exceptions.PEException; import com.tesora.dve.resultset.ProjectionInfo; import com.tesora.dve.sql.SchemaException; import com.tesora.dve.sql.ParserException.Pass; import com.tesora.dve.sql.expression.ExpressionUtils; 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.SingleEdge; import com.tesora.dve.sql.node.expression.AliasInstance; import com.tesora.dve.sql.node.expression.ExpressionAlias; import com.tesora.dve.sql.node.expression.ExpressionNode; import com.tesora.dve.sql.node.expression.LiteralExpression; import com.tesora.dve.sql.node.expression.TableInstance; import com.tesora.dve.sql.node.structural.SortingSpecification; import com.tesora.dve.sql.parser.SourceLocation; import com.tesora.dve.sql.schema.DistributionKey; import com.tesora.dve.sql.schema.SchemaContext; import com.tesora.dve.sql.schema.SchemaContext.DistKeyOpType; import com.tesora.dve.sql.statement.StatementType; import com.tesora.dve.sql.transform.execution.ExecutionStep; import com.tesora.dve.sql.transform.execution.ExecutionType; import com.tesora.dve.sql.util.ListSet; public class UnionStatement extends ProjectingStatement { private SingleEdge<UnionStatement, ProjectingStatement> lhs = new SingleEdge<UnionStatement, ProjectingStatement>(UnionStatement.class, this, EdgeName.UNION_FROM); private SingleEdge<UnionStatement, ProjectingStatement> rhs = new SingleEdge<UnionStatement, ProjectingStatement>(UnionStatement.class, this, EdgeName.UNION_TO); private boolean all; // lazily computed ProjectionInfo projectionInfo = null; @SuppressWarnings("rawtypes") private List edges = Arrays.asList(new Edge[] { lhs, rhs, orderBys, limitExpression }); public UnionStatement(ProjectingStatement left, ProjectingStatement right, boolean unionAll, SourceLocation loc) { super(loc); lhs.set(left); rhs.set(right); all = unionAll; } public Edge<UnionStatement, ProjectingStatement> getFromEdge() { return lhs; } public Edge<UnionStatement, ProjectingStatement> getToEdge() { return rhs; } public boolean isUnionAll() { return all; } @Override public List<TableInstance> getBaseTables() { ListSet<TableInstance> out = new ListSet<TableInstance>(); out.addAll(lhs.get().getBaseTables()); out.addAll(rhs.get().getBaseTables()); return out; } @Override public ExecutionType getExecutionType() { return ExecutionType.UNION; } @Override public ExecutionStep buildSingleKeyStep(SchemaContext sc, TableKey tk, DistributionKey kv, DMLStatement sql) throws PEException { // so, this is used when we determine that the entire thing is on a single site - so I guess this is just // a regular select? throw new PEException("union buildSingleKeyStep?"); } @Override public DistKeyOpType getKeyOpType() { return DistKeyOpType.QUERY; } @Override public void normalize(SchemaContext sc) { pullUpOrderbyLimit(sc); // normalize the two sides, I guess. // really should verify that they have the same number of columns lhs.get().normalize(sc); rhs.get().normalize(sc); // now verify that we have the same number of columns List<List<ExpressionNode>> projs = getProjections(); int width = -1; for(List<ExpressionNode> l : projs) { if (width == -1) width = l.size(); else if (width != l.size()) throw new SchemaException(Pass.NORMALIZE, "Invalid union, found differing numbers of columns"); } } private ProjectingStatement findOrderByLimitHost() { if (isGrouped()) return null; ProjectingStatement rhp = rhs.get(); if (rhp.isGrouped()) return null; else if (rhp instanceof UnionStatement) return ((UnionStatement)rhp).findOrderByLimitHost(); else if (rhp.getOrderBysEdge().has() || rhp.getLimitEdge().has()) return rhp; else return null; } private List<SortingSpecification> convertOrderBys(SchemaContext sc, List<SortingSpecification> sorts, ProjectingStatement h) { // since this is before normalization - we're going to make a copy of the statement, then normalize List<ExpressionNode> unnormalized = h.getProjections().get(0); List<ExpressionNode> normalized = h.normalize(sc,unnormalized); ArrayList<SortingSpecification> out = new ArrayList<SortingSpecification>(); for(SortingSpecification ss : sorts) { SortingSpecification oss = null; ExpressionNode targ = ss.getTarget(); ExpressionNode actual = null; if (targ instanceof AliasInstance) { AliasInstance ai = (AliasInstance) targ; actual = ai.getTarget(); } else if (targ instanceof LiteralExpression) { // copy it over LiteralExpression litex = (LiteralExpression) targ; oss = new SortingSpecification(litex,ss.isAscending()); } else { actual = targ; } if (oss == null) { int offset = -1; for(int i = 0; i < normalized.size(); i++) { if (actual instanceof ExpressionAlias) { ExpressionAlias ea = (ExpressionAlias) actual; if (ea.isSchemaEqual(normalized.get(i))) { offset = i; break; } } else { ExpressionNode en = ExpressionUtils.getTarget(normalized.get(i)); if (actual.isSchemaEqual(en)) { offset = i; break; } } } if (offset == -1) throw new SchemaException(Pass.NORMALIZE, "Unable to determine column offset for order by clause " + ss.getTarget().toString(sc)); oss = new SortingSpecification(LiteralExpression.makeLongLiteral(offset+1),ss.isAscending()); } oss.setOrdering(ss.isOrdering()); out.add(oss); } return out; } @Override protected SelectStatement findLeftmostSelect() { return lhs.get().findLeftmostSelect(); } private void pullUpOrderbyLimit(SchemaContext sc) { if (getOrderBysEdge().has()) { setOrderBy(convertOrderBys(sc, getOrderBys(),findLeftmostSelect())); } else { ProjectingStatement hosted = findOrderByLimitHost(); if (hosted != null) { if (hosted.getOrderBysEdge().has()) setOrderBy(convertOrderBys(sc, hosted.getOrderBys(),hosted)); setLimit(hosted.getLimit()); hosted.getOrderBysEdge().clear(); hosted.getLimitEdge().clear(); hosted.setGrouped(true); } } } @Override public List<ExpressionNode> normalize(SchemaContext sc, List<ExpressionNode> in) { throw new SchemaException(Pass.NORMALIZE, "Independent projection normalize unsupported on union statements"); } @Override public List<List<ExpressionNode>> getProjections() { ArrayList<List<ExpressionNode>> out = new ArrayList<List<ExpressionNode>>(); ProjectingStatement lps = (ProjectingStatement) lhs.get(); ProjectingStatement rps = (ProjectingStatement) rhs.get(); out.addAll(lps.getProjections()); out.addAll(rps.getProjections()); return out; } @Override public ProjectionInfo getProjectionMetadata(SchemaContext sc) { if (projectionInfo == null) { DMLStatement lps = (DMLStatement) lhs.get(); projectionInfo = lps.getProjectionMetadata(sc); projectionInfo.unionize(); pullUpOrderbyLimit(sc); } return projectionInfo; } @SuppressWarnings("unchecked") @Override public <T extends Edge<?,?>> List<T> getEdges() { return edges; } @Override public StatementType getStatementType() { return StatementType.UNION; } @Override public boolean supportsPartitions() { return false; } @Override protected boolean schemaSelfEqual(LanguageNode other) { UnionStatement ous = (UnionStatement) other; return all == ous.all; } @Override protected int selfHashCode() { return addSchemaHash(1,all); } }