/* * 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.jdbc; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.teiid.language.*; import org.teiid.language.Comparison.Operator; import org.teiid.language.visitor.HierarchyVisitor; import org.teiid.logging.LogConstants; import org.teiid.logging.LogManager; import org.teiid.translator.DataNotAvailableException; import org.teiid.translator.ExecutionContext; import org.teiid.translator.ResultSetExecution; import org.teiid.translator.TranslatorException; /** * */ public class JDBCQueryExecution extends JDBCBaseExecution implements ResultSetExecution { private static final class RenamingVisitor extends HierarchyVisitor { private Map<String, String> nameMap; private RenamingVisitor(Map<String, String> nameMap) { super(true); this.nameMap = nameMap; } @Override public void visit(NamedTable obj) { if (obj.getMetadataObject() != null) { return; } String name = obj.getName(); String val = nameMap.get(name); if (val != null) { obj.setName(val); } if (obj.getCorrelationName() == null) { obj.setCorrelationName(name); } } } private static final String KEY_TABLE_PREFIX = "TEIID_DKJ"; //$//$NON-NLS-1$ private static final String FULL_TABLE_PREFIX = "TEIID_DJ"; //$//$NON-NLS-1$ private static final String COL_PREFIX = "COL"; //$//$NON-NLS-1$ protected ResultSet results; protected Class<?>[] columnDataTypes; protected List<NamedTable> tempTables; public JDBCQueryExecution(Command command, Connection connection, ExecutionContext context, JDBCExecutionFactory env) { super(command, connection, context, env); } @Override public void execute() throws TranslatorException { // get column types QueryExpression qe = (QueryExpression)command; columnDataTypes = qe.getColumnTypes(); TranslatedCommand translatedComm = null; boolean usingTxn = false; boolean success = false; try { if (command instanceof Select) { Select select = (Select)command; if (select.getDependentValues() != null) { usingTxn = createTempTables(select); } } if (qe.getWith() != null) { usingTxn = createFullTempTables(qe, usingTxn); } // translate command translatedComm = translateCommand(command); String sql = translatedComm.getSql(); if (!translatedComm.isPrepared()) { results = getStatement().executeQuery(sql); } else { PreparedStatement pstatement = getPreparedStatement(sql); bind(pstatement, translatedComm.getPreparedValues(), null); results = pstatement.executeQuery(); } addStatementWarnings(); success = true; } catch (SQLException e) { if (translatedComm == null) { throw new JDBCExecutionException(JDBCPlugin.Event.TEIID11008, e, command.toString()); } throw new JDBCExecutionException(JDBCPlugin.Event.TEIID11008, e, translatedComm); } finally { if (usingTxn) { try { try { if (success) { connection.commit(); } else { connection.rollback(); } } finally { connection.setAutoCommit(true); } } catch (SQLException e) { } } } } /** * * @param qe * @param usingTxn * @return * @throws SQLException * @throws TranslatorException */ protected boolean createFullTempTables(QueryExpression qe, boolean usingTxn) throws SQLException, TranslatorException { //TODO: should likely consolidate the two temp table mechansims With with = qe.getWith(); int t = 1; Map<String, String> nameMap = null; for (Iterator<WithItem> iter = with.getItems().iterator(); iter.hasNext();) { WithItem item = iter.next(); if (item.getDependentValues() == null) { continue; } List<ColumnReference> cols = item.getColumns(); if (!usingTxn && this.executionFactory.tempTableRequiresTransaction() && connection.getAutoCommit()) { usingTxn = true; connection.setAutoCommit(false); } String tableName = this.executionFactory.createTempTable(FULL_TABLE_PREFIX + (t++), cols, this.context, getConnection()); if (nameMap == null) { nameMap = new HashMap<String, String>(); } nameMap.put(item.getTable().getName(), tableName); NamedTable table = new NamedTable(tableName, null, null); if (tempTables == null) { tempTables = new ArrayList<NamedTable>(); } tempTables.add(table); iter.remove(); List<Expression> params = new ArrayList<Expression>(item.getColumns().size()); for (int i = 0; i < cols.size(); i++) { Parameter parameter = new Parameter(); parameter.setType(cols.get(i).getType()); parameter.setValueIndex(i); params.add(parameter); } loadTempTable(cols, params, tableName, table, item.getDependentValues()); } //substitute the from with a real table name - TODO: associate real metadata through out if (nameMap != null) { HierarchyVisitor hv = new RenamingVisitor(nameMap); qe.acceptVisitor(hv); if (qe.getWith().getItems().isEmpty()) { qe.setWith(null); } } return usingTxn; } /** * * @param select * @return * @throws SQLException * @throws TranslatorException */ protected boolean createTempTables(Select select) throws SQLException, TranslatorException { boolean result = false; if (this.executionFactory.tempTableRequiresTransaction() && connection.getAutoCommit()) { result = true; connection.setAutoCommit(false); } LogManager.logDetail(LogConstants.CTX_CONNECTOR, "creating temporary tables for key set dependent join processing"); //$NON-NLS-1$ tempTables = new ArrayList<NamedTable>(); Condition c = select.getWhere(); List<Condition> conditions = LanguageUtil.separateCriteriaByAnd(c); Map<String, List<Comparison>> tables = new HashMap<String, List<Comparison>>(); //build a list of comparisons for each dependent source for (Iterator<Condition> iter = conditions.iterator(); iter.hasNext();) { Condition condition = iter.next(); //TODO: this would be easier with a specific type of condition if (!(condition instanceof Comparison)) { continue; } Comparison comp = (Comparison)condition; if (comp.getOperator() != Operator.EQ) { continue; } Parameter p = null; if (comp.getRightExpression() instanceof Parameter) { iter.remove(); p = (Parameter)comp.getRightExpression(); } else if (comp.getRightExpression() instanceof Array) { Array array = (Array)comp.getRightExpression(); if (array.getExpressions().get(0) instanceof Parameter) { iter.remove(); p = (Parameter)array.getExpressions().get(0); } } if (p == null) { continue; } List<Comparison> compares = tables.get(p.getDependentValueId()); if (compares == null) { compares = new ArrayList<Comparison>(); tables.put(p.getDependentValueId(), compares); } compares.add(comp); } //turn each dependent source into a temp table int t = 1; for (Map.Entry<String, List<Comparison>> entry : tables.entrySet()) { List<ColumnReference> cols = new ArrayList<ColumnReference>(); List<Expression> params = new ArrayList<Expression>(); for (Comparison comp : entry.getValue()) { Expression ex = comp.getLeftExpression(); if (ex instanceof Array) { Array array = (Array)ex; params.addAll(((Array)comp.getRightExpression()).getExpressions()); for (Expression expr : array.getExpressions()) { cols.add(createTempColumn(cols.size()+1, expr)); } } else { params.add(comp.getRightExpression()); cols.add(createTempColumn(cols.size()+1, ex)); } } //TODO: this should return a proper Table metadata object String tableName = this.executionFactory.createTempTable(KEY_TABLE_PREFIX + (t++), cols, this.context, getConnection()); NamedTable table = new NamedTable(tableName, null, null); tempTables.add(table); select.getFrom().add(0, table); //TODO: assumes that ansi and non-ansi can be mixed //replace each condition with the appropriate comparison int i = 1; for (Comparison comp : entry.getValue()) { Expression ex = comp.getLeftExpression(); if (ex instanceof Array) { Array array = (Array)ex; for (Expression expr : array.getExpressions()) { conditions.add(new Comparison(expr, new ColumnReference(table, COL_PREFIX+i++, null, expr.getType()), Comparison.Operator.EQ)); } } else { conditions.add(new Comparison(ex, new ColumnReference(table, COL_PREFIX+i++, null, ex.getType()), Comparison.Operator.EQ)); } } //bulk load List<? extends List<?>> list = select.getDependentValues().get(entry.getKey()); loadTempTable(cols, params, tableName, table, list); } select.setDependentValues(null); select.setWhere(LanguageUtil.combineCriteria(conditions)); return result; } private void loadTempTable(List<ColumnReference> cols, List<Expression> params, String tableName, NamedTable table, List<? extends List<?>> vals) throws TranslatorException, SQLException { LogManager.logDetail(LogConstants.CTX_CONNECTOR, "loading temporary table", tableName, "with", vals.size(), "rows"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ ExpressionValueSource evs = new ExpressionValueSource(params); for (ColumnReference col : cols) { col.setMetadataObject(null); //we don't want to confuse the insert handling } Insert insert = new Insert(table, cols, evs); insert.setParameterValues(vals.iterator()); JDBCUpdateExecution ex = this.executionFactory.createUpdateExecution(insert, context, context.getRuntimeMetadata(), getConnection()); int size = this.executionFactory.getMaxDependentInPredicates() * this.executionFactory.getMaxInCriteriaSize() / cols.size(); ex.setMaxPreparedInsertBatchSize(Math.max(size, this.executionFactory.getMaxPreparedInsertBatchSize())); ex.setAtomic(false); ex.execute(); ex.statement.close(); this.executionFactory.loadedTemporaryTable(tableName, this.context, this.connection); } private ColumnReference createTempColumn(int i, Expression ex) { if (ex instanceof ColumnReference) { ColumnReference left = (ColumnReference)ex; return new ColumnReference(null, COL_PREFIX + i, left.getMetadataObject(), ex.getType()); } //just an expression - there's a lot of metadata lost here return new ColumnReference(null, COL_PREFIX + i, null, ex.getType()); } @Override public List<?> next() throws TranslatorException, DataNotAvailableException { try { if (results.next()) { // New row for result set List<Object> vals = new ArrayList<Object>(columnDataTypes.length); for (int i = 0; i < columnDataTypes.length; i++) { // Convert from 0-based to 1-based Object value = this.executionFactory.retrieveValue(results, i+1, columnDataTypes[i]); vals.add(value); } return vals; } } catch (SQLException e) { throw new TranslatorException(e, JDBCPlugin.Util.getString("JDBCTranslator.Unexpected_exception_translating_results___8", e.getMessage())); //$NON-NLS-1$ } return null; } /** * @see org.teiid.translator.jdbc.JDBCBaseExecution#close() */ public void close() { // first we would need to close the result set here then we can close // the statement, using the base class. try { if (results != null) { try { results.close(); results = null; } catch (SQLException e) { LogManager.logDetail(LogConstants.CTX_CONNECTOR, e, "Exception closing"); //$NON-NLS-1$ } } } finally { if (tempTables != null) { Statement s = null; try { s = getConnection().createStatement(); for (NamedTable temp : tempTables) { try { LogManager.logDetail(LogConstants.CTX_CONNECTOR, "dropping temporary table", temp.getName()); //$NON-NLS-1$ s.execute(this.executionFactory.getDialect().getDefaultMultiTableBulkIdStrategy().getIdTableSupport().getDropIdTableCommand() + " " + temp.getName()); //$NON-NLS-1$ } catch (SQLException e) { //TODO: could refine this logic as drop is being performed as part of the txn cleanup for some sources } } } catch (SQLException e1) { } finally { tempTables = null; if (s != null) { try { s.close(); } catch (SQLException e) { } } } } super.close(); } } }