/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 org.apache.ignite.internal.jdbc2; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; import java.sql.SQLWarning; import java.sql.Statement; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; import org.apache.ignite.Ignite; import org.apache.ignite.internal.processors.query.IgniteSQLException; import org.apache.ignite.internal.util.typedef.F; import static java.sql.ResultSet.CONCUR_READ_ONLY; import static java.sql.ResultSet.FETCH_FORWARD; import static java.sql.ResultSet.HOLD_CURSORS_OVER_COMMIT; import static java.sql.ResultSet.TYPE_FORWARD_ONLY; /** * JDBC statement implementation. */ public class JdbcStatement implements Statement { /** Default fetch size. */ private static final int DFLT_FETCH_SIZE = 1024; /** Connection. */ protected final JdbcConnection conn; /** Closed flag. */ private boolean closed; /** Rows limit. */ private int maxRows; /** Current result set. */ protected ResultSet rs; /** Query arguments. */ protected ArrayList<Object> args; /** Fetch size. */ private int fetchSize = DFLT_FETCH_SIZE; /** Result sets. */ final Set<JdbcResultSet> resSets = new HashSet<>(); /** Fields indexes. */ Map<String, Integer> fieldsIdxs = new HashMap<>(); /** Current updated items count. */ long updateCnt = -1; /** Batch statements. */ private List<String> batch; /** * Creates new statement. * * @param conn Connection. */ JdbcStatement(JdbcConnection conn) { assert conn != null; this.conn = conn; } /** {@inheritDoc} */ @SuppressWarnings("deprecation") @Override public ResultSet executeQuery(String sql) throws SQLException { ensureNotClosed(); rs = null; updateCnt = -1; if (F.isEmpty(sql)) throw new SQLException("SQL query is empty"); Ignite ignite = conn.ignite(); UUID nodeId = conn.nodeId(); UUID uuid = UUID.randomUUID(); boolean loc = nodeId == null; JdbcQueryTask qryTask = new JdbcQueryTask(loc ? ignite : null, conn.cacheName(), sql, true, loc, getArgs(), fetchSize, uuid, conn.isLocalQuery(), conn.isCollocatedQuery(), conn.isDistributedJoins()); try { JdbcQueryTask.QueryResult res = loc ? qryTask.call() : ignite.compute(ignite.cluster().forNodeId(nodeId)).call(qryTask); JdbcResultSet rs = new JdbcResultSet(uuid, this, res.getTbls(), res.getCols(), res.getTypes(), res.getRows(), res.isFinished()); rs.setFetchSize(fetchSize); resSets.add(rs); return rs; } catch (IgniteSQLException e) { throw e.toJdbcException(); } catch (Exception e) { throw new SQLException("Failed to query Ignite.", e); } } /** {@inheritDoc} */ @Override public int executeUpdate(String sql) throws SQLException { ensureNotClosed(); rs = null; updateCnt = -1; return Long.valueOf(doUpdate(sql, getArgs())).intValue(); } /** * Run update query. * @param sql SQL query. * @param args Update arguments. * @return Number of affected items. * @throws SQLException If failed. */ long doUpdate(String sql, Object[] args) throws SQLException { if (F.isEmpty(sql)) throw new SQLException("SQL query is empty"); Ignite ignite = conn.ignite(); UUID nodeId = conn.nodeId(); UUID uuid = UUID.randomUUID(); boolean loc = nodeId == null; if (!conn.isDmlSupported()) throw new SQLException("Failed to query Ignite: DML operations are supported in versions 1.8.0 and newer"); JdbcQueryTask qryTask = new JdbcQueryTask(loc ? ignite : null, conn.cacheName(), sql, false, loc, args, fetchSize, uuid, conn.isLocalQuery(), conn.isCollocatedQuery(), conn.isDistributedJoins()); try { JdbcQueryTask.QueryResult qryRes = loc ? qryTask.call() : ignite.compute(ignite.cluster().forNodeId(nodeId)).call(qryTask); return updateCnt = updateCounterFromQueryResult(qryRes.getRows()); } catch (IgniteSQLException e) { throw e.toJdbcException(); } catch (SQLException e) { throw e; } catch (Exception e) { throw new SQLException("Failed to query Ignite.", e); } } /** * @param rows query result. * @return update counter, if found * @throws SQLException if getting an update counter from result proved to be impossible. */ private static long updateCounterFromQueryResult(List<List<?>> rows) throws SQLException { if (F.isEmpty(rows)) return -1; if (rows.size() != 1) throw new SQLException("Expected fetch size of 1 for update operation"); List<?> row = rows.get(0); if (row.size() != 1) throw new SQLException("Expected row size of 1 for update operation"); Object objRes = row.get(0); if (!(objRes instanceof Long)) throw new SQLException("Unexpected update result type"); return (Long)objRes; } /** {@inheritDoc} */ @Override public void close() throws SQLException { conn.statements.remove(this); closeInternal(); } /** * Marks statement as closed and closes all result sets. */ void closeInternal() throws SQLException { for (Iterator<JdbcResultSet> it = resSets.iterator(); it.hasNext(); ) { JdbcResultSet rs = it.next(); rs.closeInternal(); it.remove(); } closed = true; } /** {@inheritDoc} */ @Override public int getMaxFieldSize() throws SQLException { ensureNotClosed(); return 0; } /** {@inheritDoc} */ @Override public void setMaxFieldSize(int max) throws SQLException { ensureNotClosed(); throw new SQLFeatureNotSupportedException("Field size limitation is not supported."); } /** {@inheritDoc} */ @Override public int getMaxRows() throws SQLException { ensureNotClosed(); return maxRows; } /** {@inheritDoc} */ @Override public void setMaxRows(int maxRows) throws SQLException { ensureNotClosed(); this.maxRows = maxRows; } /** {@inheritDoc} */ @Override public void setEscapeProcessing(boolean enable) throws SQLException { ensureNotClosed(); } /** {@inheritDoc} */ @Override public int getQueryTimeout() throws SQLException { ensureNotClosed(); throw new SQLFeatureNotSupportedException("Query timeout is not supported."); } /** {@inheritDoc} */ @Override public void setQueryTimeout(int timeout) throws SQLException { ensureNotClosed(); throw new SQLFeatureNotSupportedException("Query timeout is not supported."); } /** {@inheritDoc} */ @Override public void cancel() throws SQLException { ensureNotClosed(); throw new SQLFeatureNotSupportedException("Cancellation is not supported."); } /** {@inheritDoc} */ @Override public SQLWarning getWarnings() throws SQLException { ensureNotClosed(); return null; } /** {@inheritDoc} */ @Override public void clearWarnings() throws SQLException { ensureNotClosed(); } /** {@inheritDoc} */ @Override public void setCursorName(String name) throws SQLException { ensureNotClosed(); throw new SQLFeatureNotSupportedException("Updates are not supported."); } /** {@inheritDoc} */ @Override public boolean execute(String sql) throws SQLException { if (!conn.isDmlSupported()) { // We attempt to run a query without any checks as long as server does not support DML anyway, // so it simply will throw an exception when given a DML statement instead of a query. rs = executeQuery(sql); return true; } ensureNotClosed(); rs = null; updateCnt = -1; if (F.isEmpty(sql)) throw new SQLException("SQL query is empty"); Ignite ignite = conn.ignite(); UUID nodeId = conn.nodeId(); UUID uuid = UUID.randomUUID(); boolean loc = nodeId == null; JdbcQueryTask qryTask = new JdbcQueryTask(loc ? ignite : null, conn.cacheName(), sql, null, loc, getArgs(), fetchSize, uuid, conn.isLocalQuery(), conn.isCollocatedQuery(), conn.isDistributedJoins()); try { JdbcQueryTask.QueryResult res = loc ? qryTask.call() : ignite.compute(ignite.cluster().forNodeId(nodeId)).call(qryTask); if (res.isQuery()) { JdbcResultSet rs = new JdbcResultSet(uuid, this, res.getTbls(), res.getCols(), res.getTypes(), res.getRows(), res.isFinished()); rs.setFetchSize(fetchSize); resSets.add(rs); this.rs = rs; } else updateCnt = updateCounterFromQueryResult(res.getRows()); return res.isQuery(); } catch (IgniteSQLException e) { throw e.toJdbcException(); } catch (Exception e) { throw new SQLException("Failed to query Ignite.", e); } } /** {@inheritDoc} */ @Override public ResultSet getResultSet() throws SQLException { ensureNotClosed(); ResultSet rs0 = rs; rs = null; return rs0; } /** {@inheritDoc} */ @Override public int getUpdateCount() throws SQLException { ensureNotClosed(); long res = updateCnt; updateCnt = -1; return Long.valueOf(res).intValue(); } /** {@inheritDoc} */ @Override public boolean getMoreResults() throws SQLException { ensureNotClosed(); return false; } /** {@inheritDoc} */ @Override public void setFetchDirection(int direction) throws SQLException { ensureNotClosed(); if (direction != FETCH_FORWARD) throw new SQLFeatureNotSupportedException("Only forward direction is supported"); } /** {@inheritDoc} */ @Override public int getFetchDirection() throws SQLException { ensureNotClosed(); return FETCH_FORWARD; } /** {@inheritDoc} */ @Override public void setFetchSize(int fetchSize) throws SQLException { ensureNotClosed(); if (fetchSize < 0) throw new SQLException("Fetch size must be greater or equal zero."); this.fetchSize = fetchSize; } /** {@inheritDoc} */ @Override public int getFetchSize() throws SQLException { ensureNotClosed(); return fetchSize; } /** {@inheritDoc} */ @Override public int getResultSetConcurrency() throws SQLException { ensureNotClosed(); return CONCUR_READ_ONLY; } /** {@inheritDoc} */ @Override public int getResultSetType() throws SQLException { ensureNotClosed(); return TYPE_FORWARD_ONLY; } /** {@inheritDoc} */ @Override public void addBatch(String sql) throws SQLException { ensureNotClosed(); if (F.isEmpty(sql)) throw new SQLException("SQL query is empty"); if (batch == null) batch = new ArrayList<>(); batch.add(sql); } /** {@inheritDoc} */ @Override public void clearBatch() throws SQLException { ensureNotClosed(); batch = null; } /** {@inheritDoc} */ @Override public int[] executeBatch() throws SQLException { ensureNotClosed(); throw new SQLFeatureNotSupportedException("Batch statements are not supported yet."); } /** {@inheritDoc} */ @Override public Connection getConnection() throws SQLException { ensureNotClosed(); return conn; } /** {@inheritDoc} */ @Override public boolean getMoreResults(int curr) throws SQLException { ensureNotClosed(); if (curr == KEEP_CURRENT_RESULT || curr == CLOSE_ALL_RESULTS) throw new SQLFeatureNotSupportedException("Multiple open results are not supported."); return false; } /** {@inheritDoc} */ @Override public ResultSet getGeneratedKeys() throws SQLException { ensureNotClosed(); throw new SQLFeatureNotSupportedException("Auto generated keys are not supported."); } /** {@inheritDoc} */ @Override public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException { ensureNotClosed(); if (autoGeneratedKeys == RETURN_GENERATED_KEYS) throw new SQLFeatureNotSupportedException("Auto generated keys are not supported."); return executeUpdate(sql); } /** {@inheritDoc} */ @Override public int executeUpdate(String sql, int[] colIndexes) throws SQLException { ensureNotClosed(); if (!F.isEmpty(colIndexes)) throw new SQLFeatureNotSupportedException("Auto generated keys are not supported."); return executeUpdate(sql); } /** {@inheritDoc} */ @Override public int executeUpdate(String sql, String[] colNames) throws SQLException { ensureNotClosed(); if (!F.isEmpty(colNames)) throw new SQLFeatureNotSupportedException("Auto generated keys are not supported."); return executeUpdate(sql); } /** {@inheritDoc} */ @Override public boolean execute(String sql, int autoGeneratedKeys) throws SQLException { ensureNotClosed(); if (autoGeneratedKeys == RETURN_GENERATED_KEYS) throw new SQLFeatureNotSupportedException("Auto generated keys are not supported."); return execute(sql); } /** {@inheritDoc} */ @Override public boolean execute(String sql, int[] colIndexes) throws SQLException { ensureNotClosed(); if (!F.isEmpty(colIndexes)) throw new SQLFeatureNotSupportedException("Auto generated keys are not supported."); return execute(sql); } /** {@inheritDoc} */ @Override public boolean execute(String sql, String[] colNames) throws SQLException { ensureNotClosed(); if (!F.isEmpty(colNames)) throw new SQLFeatureNotSupportedException("Auto generated keys are not supported."); return execute(sql); } /** {@inheritDoc} */ @Override public int getResultSetHoldability() throws SQLException { ensureNotClosed(); return HOLD_CURSORS_OVER_COMMIT; } /** {@inheritDoc} */ @Override public boolean isClosed() throws SQLException { return closed; } /** {@inheritDoc} */ @Override public void setPoolable(boolean poolable) throws SQLException { ensureNotClosed(); if (poolable) throw new SQLFeatureNotSupportedException("Pooling is not supported."); } /** {@inheritDoc} */ @Override public boolean isPoolable() throws SQLException { ensureNotClosed(); return false; } /** {@inheritDoc} */ @SuppressWarnings("unchecked") @Override public <T> T unwrap(Class<T> iface) throws SQLException { if (!isWrapperFor(iface)) throw new SQLException("Statement is not a wrapper for " + iface.getName()); return (T)this; } /** {@inheritDoc} */ @Override public boolean isWrapperFor(Class<?> iface) throws SQLException { return iface != null && iface == Statement.class; } /** {@inheritDoc} */ @Override public void closeOnCompletion() throws SQLException { throw new SQLFeatureNotSupportedException("closeOnCompletion is not supported."); } /** {@inheritDoc} */ @Override public boolean isCloseOnCompletion() throws SQLException { ensureNotClosed(); return false; } /** * @return Args for current statement */ protected final Object[] getArgs() { return args != null ? args.toArray() : null; } /** * Ensures that statement is not closed. * * @throws SQLException If statement is closed. */ void ensureNotClosed() throws SQLException { if (closed) throw new SQLException("Statement is closed."); } }