/* * 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.BatchUpdateException; 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.Arrays; import java.util.Iterator; import java.util.List; import org.teiid.GeneratedKeys; import org.teiid.language.BatchedCommand; import org.teiid.language.BatchedUpdates; import org.teiid.language.Command; import org.teiid.language.Insert; import org.teiid.language.NamedTable; import org.teiid.logging.LogConstants; import org.teiid.logging.LogManager; import org.teiid.metadata.Column; import org.teiid.metadata.KeyRecord; import org.teiid.translator.DataNotAvailableException; import org.teiid.translator.ExecutionContext; import org.teiid.translator.TranslatorBatchException; import org.teiid.translator.TranslatorException; import org.teiid.translator.TypeFacility; import org.teiid.translator.UpdateExecution; /** */ public class JDBCUpdateExecution extends JDBCBaseExecution implements UpdateExecution { private int[] result; private int maxPreparedInsertBatchSize; private boolean atomic = true; /** * @param connection * @param sqlTranslator * @param logger * @param props * @param id */ public JDBCUpdateExecution(Command command, Connection connection, ExecutionContext context, JDBCExecutionFactory env) { super(command, connection, context, env); this.maxPreparedInsertBatchSize = this.executionFactory.getMaxPreparedInsertBatchSize(); } // =========================================================================================================================== // Methods // =========================================================================================================================== @Override public void execute() throws TranslatorException { if (command instanceof BatchedUpdates) { execute(((BatchedUpdates)command)); } else { // translate command TranslatedCommand translatedComm = translateCommand(command); executeTranslatedCommand(translatedComm); } } public int[] execute(BatchedUpdates batchedCommand) throws TranslatorException { boolean succeeded = false; boolean commitType = false; if (batchedCommand.isSingleResult()) { commitType = getAutoCommit(null); } Command[] commands = batchedCommand.getUpdateCommands().toArray(new Command[batchedCommand.getUpdateCommands().size()]); result = new int[commands.length]; TranslatedCommand tCommand = null; int batchStart = 0; int i = 0; try { // temporarily turn the auto commit off, and set it back to what it was // before at the end of the command execution. if (commitType) { connection.setAutoCommit(false); } List<TranslatedCommand> executedCmds = new ArrayList<TranslatedCommand>(); TranslatedCommand previousCommand = null; for (; i < commands.length; i++) { tCommand = translateCommand(commands[i]); if (tCommand.isPrepared()) { PreparedStatement pstmt = null; if (previousCommand != null && previousCommand.isPrepared() && previousCommand.getSql().equals(tCommand.getSql())) { pstmt = (PreparedStatement)statement; } else { if (!executedCmds.isEmpty()) { executeBatch(i, result, executedCmds); batchStart = i; } pstmt = getPreparedStatement(tCommand.getSql()); } bind(pstmt, tCommand.getPreparedValues(), null); pstmt.addBatch(); } else { if (previousCommand != null && previousCommand.isPrepared()) { executeBatch(i, result, executedCmds); batchStart = i; getStatement(); } if (statement == null) { getStatement(); } statement.addBatch(tCommand.getSql()); } executedCmds.add(tCommand); previousCommand = tCommand; } if (!executedCmds.isEmpty()) { executeBatch(commands.length, result, executedCmds); } succeeded = true; } catch (TranslatorException e) { if (batchedCommand.isSingleResult()) { throw e; } int size = i + 1; //if there is a BatchUpdateException, there are more update counts to accumulate if (e.getCause() instanceof BatchUpdateException) { BatchUpdateException bue = (BatchUpdateException)e.getCause(); int[] batchResults = bue.getUpdateCounts(); for (int j = 0; j < batchResults.length; j++) { result[batchStart + j] = batchResults[j]; } size = batchStart + batchResults.length; } else { size = batchStart; } //resize the result and throw exception throw new TranslatorBatchException(e, Arrays.copyOf(result, size)); } catch (SQLException e) { if (batchedCommand.isSingleResult()) { throw new JDBCExecutionException(JDBCPlugin.Event.TEIID11011, e, tCommand); } //resize the result and throw exception throw new TranslatorBatchException(e, Arrays.copyOf(result, batchStart)); } finally { if (commitType) { restoreAutoCommit(!succeeded, null); } } return result; } private void executeBatch(int commandCount, int[] results, List<TranslatedCommand> commands) throws TranslatorException { try { int[] batchResults = statement.executeBatch(); addStatementWarnings(); for (int j = 0; j < batchResults.length; j++) { results[commandCount - 1 - j] = batchResults[batchResults.length - 1 - j]; } commands.clear(); } catch (SQLException err) { throw new JDBCExecutionException(JDBCPlugin.Event.TEIID11012, err, commands.toArray(new TranslatedCommand[commands.size()])); } } /** * @param translatedComm * @throws TranslatorException * @since 4.3 */ private void executeTranslatedCommand(TranslatedCommand translatedComm) throws TranslatorException { // create statement or PreparedStatement and execute String sql = translatedComm.getSql(); boolean commitType = false; boolean succeeded = false; try { int updateCount = 0; Class<?>[] keyColumnDataTypes = null; String[] keyColumnNames = null; if (command instanceof Insert && context.getCommandContext().isReturnAutoGeneratedKeys() && executionFactory.supportsGeneratedKeys(context, command)) { Insert insert = (Insert)command; NamedTable nt = insert.getTable(); if (nt.getMetadataObject() != null) { KeyRecord key = nt.getMetadataObject().getPrimaryKey(); if (key != null) { List<Column> cols = key.getColumns(); keyColumnDataTypes = new Class<?>[cols.size()]; keyColumnNames = new String[cols.size()]; for (int i = 0; i < cols.size(); i++) { Column c = cols.get(i); keyColumnDataTypes[i] = c.getJavaType(); //won't work in scenarios where the teiid name is changed or contains a . keyColumnNames[i] = c.getName(); } } } } if (!translatedComm.isPrepared()) { statement = getStatement(); //handle autoGeneratedKeys if (keyColumnDataTypes != null) { if (executionFactory.useColumnNamesForGeneratedKeys()) { updateCount = statement.executeUpdate(sql, keyColumnNames); } else { updateCount = statement.executeUpdate(sql, Statement.RETURN_GENERATED_KEYS); } } else { updateCount = statement.executeUpdate(sql); } result = new int[] {updateCount}; addStatementWarnings(); } else { PreparedStatement pstatement = null; if (statement != null) { statement.close(); } if (keyColumnDataTypes != null) { if (executionFactory.useColumnNamesForGeneratedKeys()) { pstatement = connection.prepareStatement(sql, keyColumnNames); } else { pstatement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); } } else { pstatement = getPreparedStatement(sql); } statement = pstatement; Iterator<? extends List<?>> vi = null; if (command instanceof BatchedCommand) { BatchedCommand batchCommand = (BatchedCommand)command; vi = batchCommand.getParameterValues(); } int k = 0; int batchStart = 0; if (vi != null) { try { commitType = getAutoCommit(translatedComm); if (commitType) { connection.setAutoCommit(false); } int maxBatchSize = (command instanceof Insert)?maxPreparedInsertBatchSize:Integer.MAX_VALUE; boolean done = false; outer: while (!done) { for (int i = 0; i < maxBatchSize; i++) { if (vi.hasNext()) { List<?> values = vi.next(); bind(pstatement, translatedComm.getPreparedValues(), values); k++; } else { if (i == 0) { break outer; } done = true; break; } } int[] results = pstatement.executeBatch(); batchStart = k; if (result == null) { result = results; } else { int len = result.length; result = Arrays.copyOf(result, len + results.length); System.arraycopy(results, 0, result, len, results.length); } } } catch (SQLException e) { int size = k + 1; if (result == null) { result = new int[size]; } else { result = Arrays.copyOf(result, size); } //if there is a BatchUpdateException, there are more update counts to accumulate if (e instanceof BatchUpdateException) { BatchUpdateException bue = (BatchUpdateException)e; int[] batchResults = bue.getUpdateCounts(); for (int j = 0; j < batchResults.length; j++) { result[batchStart + j] = batchResults[j]; } size = batchStart + batchResults.length; } else { size = batchStart; } //resize the result and throw exception throw new TranslatorBatchException(e, Arrays.copyOf(result, size)); } } else { bind(pstatement, translatedComm.getPreparedValues(), null); updateCount = pstatement.executeUpdate(); result = new int[] {updateCount}; } addStatementWarnings(); succeeded = true; } if (keyColumnDataTypes != null) { try { ResultSet keys = statement.getGeneratedKeys(); GeneratedKeys generatedKeys = context.getCommandContext().returnGeneratedKeys(keyColumnNames, keyColumnDataTypes); //many databases only support returning a single generated value, but we'll still attempt to gather all outer: while (keys.next()) { List<Object> vals = new ArrayList<Object>(keyColumnDataTypes.length); for (int i = 0; i < keyColumnDataTypes.length; i++) { Object value = this.executionFactory.retrieveValue(keys, i+1, keyColumnDataTypes[i]); if (value != null && TypeFacility.getRuntimeType(value.getClass()) != keyColumnDataTypes[i]) { //TODO we may need to let the engine to the final conversion LogManager.logDetail(LogConstants.CTX_CONNECTOR, JDBCPlugin.Util.gs(JDBCPlugin.Event.TEIID11023, keyColumnDataTypes[i], keyColumnNames[i], value.getClass())); continue outer; } vals.add(value); } generatedKeys.addKey(vals); } } catch (SQLException e) { context.addWarning(e); LogManager.logDetail(LogConstants.CTX_CONNECTOR, e, "Exception determining generated keys, no keys will be returned"); //$NON-NLS-1$ } } } catch (SQLException err) { throw new JDBCExecutionException(JDBCPlugin.Event.TEIID11013, err, translatedComm); } finally { if (commitType) { restoreAutoCommit(!succeeded, translatedComm); } } } /** * @param command * @return * @throws TranslatorException */ private boolean getAutoCommit(TranslatedCommand tCommand) throws TranslatorException { if (!atomic) { return false; } try { return connection.getAutoCommit(); } catch (SQLException err) { throw new JDBCExecutionException(JDBCPlugin.Event.TEIID11014, err, tCommand); } } /** * Set autoCommit back to true * * @param exceptionOccurred * @param command * @throws TranslatorException */ private void restoreAutoCommit(boolean exceptionOccurred, TranslatedCommand tCommand) throws TranslatorException { try { if (exceptionOccurred) { connection.rollback(); } } catch (SQLException err) { throw new JDBCExecutionException(JDBCPlugin.Event.TEIID11015, err, tCommand); } finally { try { if (!exceptionOccurred) { connection.commit(); // in JbossAs setAutocommit = true does not trigger the commit. } connection.setAutoCommit(true); } catch (SQLException err) { throw new JDBCExecutionException(JDBCPlugin.Event.TEIID11016, err, tCommand); } } } @Override public int[] getUpdateCounts() throws DataNotAvailableException, TranslatorException { return result; } public void setMaxPreparedInsertBatchSize(int maxPreparedInsertBatchSize) { this.maxPreparedInsertBatchSize = maxPreparedInsertBatchSize; } public void setAtomic(boolean atomic) { this.atomic = atomic; } }