/* * Copyright 2014-2015 the original author or authors * * Licensed under the Apache License, Version 2.0 (the “License”); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an “AS IS” BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.wplatform.ddal.command; import java.sql.SQLException; import java.util.ArrayList; import com.wplatform.ddal.command.expression.ParameterInterface; import com.wplatform.ddal.engine.Constants; import com.wplatform.ddal.engine.Database; import com.wplatform.ddal.engine.Session; import com.wplatform.ddal.message.DbException; import com.wplatform.ddal.message.ErrorCode; import com.wplatform.ddal.message.Trace; import com.wplatform.ddal.result.ResultInterface; /** * Represents a SQL statement. This object is only used on the server side. */ public abstract class Command implements CommandInterface { /** * The session. */ protected final Session session; /** * The trace module. */ private final Trace trace; private final String sql; /** * The last start time. */ protected long startTime; /** * If this query was canceled. */ private volatile boolean cancel; private boolean canReuse; Command(Parser parser, String sql) { this.session = parser.getSession(); this.sql = sql; trace = session.getDatabase().getTrace(Trace.COMMAND); } /** * Check if this command is transactional. * If it is not, then it forces the current transaction to commit. * * @return true if it is */ public abstract boolean isTransactional(); /** * Check if this command is a query. * * @return true if it is */ @Override public abstract boolean isQuery(); /** * Get the list of parameters. * * @return the list of parameters */ @Override public abstract ArrayList<? extends ParameterInterface> getParameters(); /** * Check if this command is read only. * * @return true if it is */ public abstract boolean isReadOnly(); /** * Get an empty result set containing the meta data. * * @return an empty result set */ public abstract ResultInterface queryMeta(); /** * Execute an updating statement (for example insert, delete, or update), if * this is possible. * * @return the update count * @throws DbException if the command is not an updating statement */ public int update() { throw DbException.get(ErrorCode.METHOD_NOT_ALLOWED_FOR_QUERY); } /** * Execute a query statement, if this is possible. * * @param maxrows the maximum number of rows returned * @return the local result set * @throws DbException if the command is not a query */ public ResultInterface query(int maxrows) { throw DbException.get(ErrorCode.METHOD_ONLY_ALLOWED_FOR_QUERY); } @Override public final ResultInterface getMetaData() { return queryMeta(); } /** * Start the stopwatch. */ void start() { if (trace.isInfoEnabled()) { startTime = System.currentTimeMillis(); } } /** * Check if this command has been canceled, and throw an exception if yes. * * @throws DbException if the statement has been canceled */ protected void checkCanceled() { if (cancel) { cancel = false; throw DbException.get(ErrorCode.STATEMENT_WAS_CANCELED); } } private void stop() { session.endStatement(); session.setCurrentCommand(null); if (!isTransactional()) { session.commit(true); } else if (session.getAutoCommit()) { session.commit(false); } if (trace.isInfoEnabled() && startTime > 0) { long time = System.currentTimeMillis() - startTime; if (time > Constants.SLOW_QUERY_LIMIT_MS) { trace.info("slow query: {0} ms", time); } } } /** * Execute a query and return the result. * This method prepares everything and calls {@link #query(int)} finally. * * @param maxrows the maximum number of rows to return * @param scrollable if the result set must be scrollable (ignored) * @return the result set */ @Override public ResultInterface executeQuery(int maxrows, boolean scrollable) { startTime = 0; Database database = session.getDatabase(); Object sync = session; boolean callStop = true; synchronized (sync) { session.setCurrentCommand(this); try { while (true) { try { return query(maxrows); } catch (DbException e) { throw e; } catch (OutOfMemoryError e) { callStop = false; // there is a serious problem: // the transaction may be applied partially // in this case we need to panic: // close the database database.shutdownImmediately(); throw DbException.convert(e); } catch (Throwable e) { throw DbException.convert(e); } } } catch (DbException e) { e = e.addSQL(sql); SQLException s = e.getSQLException(); if (s.getErrorCode() == ErrorCode.OUT_OF_MEMORY) { callStop = false; database.shutdownImmediately(); throw e; } throw e; } finally { if (callStop) { stop(); } } } } @Override public int executeUpdate() { Database database = session.getDatabase(); Object sync = session; boolean callStop = true; synchronized (sync) { Session.Savepoint rollback = session.setSavepoint(); session.setCurrentCommand(this); try { while (true) { try { return update(); } catch (DbException e) { throw e; } catch (OutOfMemoryError e) { callStop = false; database.shutdownImmediately(); throw DbException.convert(e); } catch (Throwable e) { throw DbException.convert(e); } } } catch (DbException e) { e = e.addSQL(sql); SQLException s = e.getSQLException(); if (s.getErrorCode() == ErrorCode.OUT_OF_MEMORY) { callStop = false; database.shutdownImmediately(); throw e; } if (s.getErrorCode() == ErrorCode.DEADLOCK_1) { session.rollback(); } else { session.rollbackTo(rollback, false); } throw e; } finally { try { if (callStop) { stop(); } } finally { } } } } @Override public void close() { canReuse = true; } @Override public void cancel() { this.cancel = true; } @Override public String toString() { return sql + Trace.formatParams(getParameters()); } public boolean isCacheable() { return false; } /** * Whether the command is already closed (in which case it can be re-used). * * @return true if it can be re-used */ public boolean canReuse() { return canReuse; } /** * The command is now re-used, therefore reset the canReuse flag, and the * parameter values. */ public void reuse() { canReuse = false; ArrayList<? extends ParameterInterface> parameters = getParameters(); for (int i = 0, size = parameters.size(); i < size; i++) { ParameterInterface param = parameters.get(i); param.setValue(null, true); } } }