/* * JBoss, Home of Professional Open Source. * See the COPYRIGHT.txt file distributed with this work for information * regarding copyright ownership. Some portions may be licensed * to Red Hat, Inc. under one or more contributor license agreements. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ package org.teiid.translator.accumulo; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Stack; import org.apache.accumulo.core.client.IteratorSetting; import org.apache.accumulo.core.data.Key; import org.apache.accumulo.core.data.PartialKey; import org.apache.accumulo.core.data.Range; import org.teiid.language.*; import org.teiid.language.visitor.HierarchyVisitor; import org.teiid.language.visitor.SQLStringVisitor; import org.teiid.metadata.AbstractMetadataRecord; import org.teiid.metadata.Column; import org.teiid.metadata.KeyRecord; import org.teiid.metadata.Table; import org.teiid.query.metadata.DDLStringVisitor; import org.teiid.query.metadata.SystemMetadata; import org.teiid.translator.TranslatorException; public class AccumuloQueryVisitor extends HierarchyVisitor { protected Stack<Object> onGoingExpression = new Stack<Object>(); protected List<Range> ranges = new ArrayList<Range>(); protected Table scanTable; protected ArrayList<TranslatorException> exceptions = new ArrayList<TranslatorException>(); private HashMap<String, Column> keybasedColumnMap = new HashMap<String, Column>(); private ArrayList<Column> selectColumns = new ArrayList<Column>(); private ArrayList<IteratorSetting> scanIterators = new ArrayList<IteratorSetting>(); private String currentAlias; private int aliasIdx = 0; private int iteratorPriority = 2; private boolean doScanEvaluation = false; private AccumuloExecutionFactory ef; public AccumuloQueryVisitor(AccumuloExecutionFactory ef) { this.ef = ef; } public List<Range> getRanges(){ return this.ranges; } public Table getScanTable() { return this.scanTable; } public Column lookupColumn(String key) { return this.keybasedColumnMap.get(key); } public List<Column> projectedColumns(){ return this.selectColumns; } public List<IteratorSetting> scanIterators(){ return this.scanIterators; } @Override public void visit(Select obj) { visitNodes(obj.getFrom()); visitNodes(obj.getDerivedColumns()); visitNode(obj.getWhere()); visitNode(obj.getGroupBy()); visitNode(obj.getHaving()); visitNode(obj.getOrderBy()); visitNode(obj.getLimit()); if (this.doScanEvaluation) { HashMap<String, String> options = buildEvaluatorOptions(this.scanTable); SQLStringVisitor visitor = new SQLStringVisitor() { @Override public String getName(AbstractMetadataRecord object) { return object.getName(); } }; visitor.append(obj.getWhere()); options.put(EvaluatorIterator.QUERYSTRING, visitor.toString()); IteratorSetting it = new IteratorSetting(1, EvaluatorIterator.class, options); this.scanIterators.add(it); } } @Override public void visit(DerivedColumn obj) { this.currentAlias = buildAlias(obj.getAlias()); visitNode(obj.getExpression()); Column column = (Column)this.onGoingExpression.pop(); String CF = column.getProperty(AccumuloMetadataProcessor.CF, false); String CQ = column.getProperty(AccumuloMetadataProcessor.CQ, false); if (CQ != null) { this.keybasedColumnMap.put(CF+"/"+CQ, column); //$NON-NLS-1$ } else { this.keybasedColumnMap.put(CF, column); } // no expressions in select are allowed. this.selectColumns.add(column); } private String buildAlias(String alias) { if (alias != null) { return alias; } return "_m"+this.aliasIdx; //$NON-NLS-1$ } @Override public void visit(ColumnReference obj) { this.onGoingExpression.push(obj.getMetadataObject()); } @Override public void visit(AndOr obj) { visitNode(obj.getLeftCondition()); visitNode(obj.getRightCondition()); this.ranges = Range.mergeOverlapping(this.ranges); } @Override public void visit(Comparison obj) { visitNode(obj.getLeftExpression()); Column column = (Column)this.onGoingExpression.pop(); visitNode(obj.getRightExpression()); Object rightExpr = this.onGoingExpression.pop(); Key rightKey = buildKey(rightExpr); if (isPartOfPrimaryKey(column)) { switch(obj.getOperator()) { case EQ: this.ranges.add(singleRowRange(rightKey)); break; case NE: this.ranges.add(new Range(null, true, rightKey, false)); this.ranges.add(new Range(rightKey.followingKey(PartialKey.ROW), null, false, true, false, true)); break; /* case LT: this.ranges.add(new Range(null, true, rightKey, false)); break; case LE: this.ranges.add(new Range(null, true, rightKey, false)); this.ranges.add(singleRowRange(rightKey)); break; case GT: this.ranges.add(new Range(rightKey.followingKey(PartialKey.ROW), null, false, true, false, true)); break; case GE: this.ranges.add(new Range(rightKey, true, null, true)); break; */ } this.doScanEvaluation = true; } else { this.doScanEvaluation = true; } } static Key buildKey(Object value) { byte[] row = AccumuloDataTypeManager.serialize(value); Key rangeKey = new Key(row, AccumuloDataTypeManager.EMPTY_BYTES, AccumuloDataTypeManager.EMPTY_BYTES, AccumuloDataTypeManager.EMPTY_BYTES, Long.MAX_VALUE); return rangeKey; } @Override public void visit(In obj) { visitNode(obj.getLeftExpression()); Column column = (Column)this.onGoingExpression.pop(); visitNodes(obj.getRightExpressions()); if (isPartOfPrimaryKey(column)) { Object prevExpr = null; // NOTE: we are popping in reverse order to IN stmt for (int i = 0; i < obj.getRightExpressions().size(); i++) { Object rightExpr = this.onGoingExpression.pop(); Key rightKey = buildKey(rightExpr); Key prevKey = null; if (prevExpr != null) { prevKey = buildKey(prevExpr); } Range range = singleRowRange(rightKey); if (obj.isNegated()) { if (prevExpr == null) { this.ranges.add(new Range(rightKey, false, null, true)); this.ranges.add(new Range(null, true, rightKey, false)); } else { this.ranges.remove(this.ranges.size()-1); this.ranges.add(new Range(rightKey, false, prevKey, false)); this.ranges.add(new Range(null, true, rightKey, false)); } prevExpr = rightExpr; } else { this.ranges.add(range); } } } else { this.doScanEvaluation = true; } } static Range singleRowRange(Key key) { Range range = new Range(key, key.followingKey(PartialKey.ROW), true, false, false, false); return range; } public static boolean isPartOfPrimaryKey(Column column) { KeyRecord pk = ((Table)column.getParent()).getPrimaryKey(); if (pk != null) { for (Column col:pk.getColumns()) { if (col.getName().equals(column.getName())) { return true; } } } return false; } @Override public void visit(AggregateFunction obj) { if (!obj.getParameters().isEmpty()) { visitNodes(obj.getParameters()); } if (obj.getName().equals(AggregateFunction.COUNT)) { HashMap<String, String> options = new HashMap<String, String>(); options.put(CountStarIterator.ALIAS, this.currentAlias); IteratorSetting it = new IteratorSetting(this.iteratorPriority++, CountStarIterator.class, options); // expression expects a column Column c = new Column(); c.setName(this.currentAlias); c.setDatatype(SystemMetadata.getInstance().getSystemStore().getDatatypes().get("integer"));//$NON-NLS-1$ c.setProperty(AccumuloMetadataProcessor.CF, this.currentAlias); this.scanIterators.add(it); this.onGoingExpression.push(c) ; } else if (obj.getName().equals(AggregateFunction.AVG)) { } else if (obj.getName().equals(AggregateFunction.SUM)) { } else if (obj.getName().equals(AggregateFunction.MIN)) { } else if (obj.getName().equals(AggregateFunction.MAX)) { } else { } } @Override public void visit(IsNull obj) { visitNode(obj.getExpression()); Column column = (Column)onGoingExpression.pop(); // this will never be part of the rowid, as it can never be null, so scan this.doScanEvaluation = true; } @Override public void visit(Literal obj) { this.onGoingExpression.push(obj.getValue()); } @Override public void visit(NamedTable obj) { this.scanTable = obj.getMetadataObject(); } private static HashMap<String, String> buildEvaluatorOptions(Table table) { HashMap<String, String> options = new HashMap<String, String>(); options.put(EvaluatorIterator.TABLE, table.getName()); String ddl = DDLStringVisitor.getDDLString(table.getParent(), null, table.getName()); options.put(EvaluatorIterator.DDL, ddl); return options; } }