All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.voltdb.jdbc.JDBC4Statement Maven / Gradle / Ivy

There is a newer version: 13.3.2-preview1
Show newest version
/* This file is part of VoltDB.
 * Copyright (C) 2008-2017 VoltDB Inc.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with VoltDB.  If not, see .
 */

package org.voltdb.jdbc;

import java.io.IOException;
import java.sql.BatchUpdateException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import org.voltdb.VoltTable;
import org.voltdb.VoltType;
import org.voltdb.client.ClientResponse;
import org.voltdb.client.ProcCallException;
import org.voltdb.parser.JDBCParser;
import org.voltdb.parser.SQLLexer;
import org.voltdb.parser.JDBCParser.ParsedCall;

public class JDBC4Statement implements java.sql.Statement
{

    //Timeout for statement. This is used for execute* methods. batch add dont have timeout.
    private int m_timeout = Integer.MAX_VALUE;
    static class VoltSQL
    {
        public static final byte TYPE_SELECT = 1;
        public static final byte TYPE_UPDATE = 2;
        public static final byte TYPE_EXEC = 3;

        private final String[] sql;
        private final int parameterCount;
        private final byte type;
        private final byte queryType;   // Type of query EXEC'd by @AdHoc
        private final Object[] parameters;

        private VoltSQL(String[] sql, int parameterCount, byte type)
        {
            this.sql = sql;
            this.parameterCount = parameterCount;
            this.type = this.queryType = type;
            this.parameters = null;
        }

        private VoltSQL(String[] sql, int parameterCount, byte type, Object[] parameters)
        {
            this(sql, parameterCount, type, type, parameters);
        }

        private VoltSQL(String[] sql, int parameterCount, byte type, byte queryType, Object[] parameters)
        {
            this.sql = sql;
            this.parameterCount = parameterCount;
            this.type = type;
            this.queryType = queryType;
            this.parameters = parameters;
        }

        public boolean hasParameters()
        {
            return this.parameterCount > 0;
        }

        public Object[] getParameterArray() throws SQLException
        {
            return new Object[this.parameterCount];
        }

        public int getParameterCount()
        {
            return this.parameterCount;
        }

        public boolean isOfType(int... types)
        {
            for(int i=0;i -1)
            {
                String[] queryParts = (query + ";").split("\\?");
                parameterCount = queryParts.length-1;
            }

            return new VoltSQL(new String[] {query}, parameterCount, type);
        }
    }

    private ArrayList batch = null;
    protected boolean isClosed = false;
    private int fetchDirection = ResultSet.FETCH_FORWARD;
    private int fetchSize = 0;
    private final int maxFieldSize = VoltType.MAX_VALUE_LENGTH;
    private int maxRows = VoltTable.MAX_SERIALIZED_TABLE_LENGTH/2; // Not exactly true, but best type of estimate we can give...
    protected JDBC4Connection sourceConnection;
    private boolean isPoolable = false;

    protected VoltTable[] tableResults = null;
    protected int tableResultIndex = -1;
    protected int lastUpdateCount = -1;
    protected Set openResults = new HashSet();
    protected JDBC4ResultSet result = null;

    public JDBC4Statement(JDBC4Connection connection)
    {
        sourceConnection = connection;
    }

    protected void checkClosed() throws SQLException
    {
        if (this.isClosed()) {
            throw SQLError.get(SQLError.CONNECTION_CLOSED);
        }
    }

    private void closeCurrentResult() throws SQLException
    {
        this.lastUpdateCount = -1;
        if (this.result != null) {
            this.result.close();
        }
        this.result = null;
    }

    private JDBC4ResultSet createTrimmedResultSet(VoltTable input) throws SQLException
    {
        VoltTable result = input;
        if (maxRows > 0 && input.getRowCount() > maxRows) {
            VoltTable trimmed = new VoltTable(input.getTableSchema());
            input.resetRowPosition();
            for (int i = 0; i < maxRows; i++) {
                input.advanceRow();
                trimmed.add(input.cloneRow());
            }
            result = trimmed;
        }
        return new JDBC4ResultSet(this, result);
    }

    private void setCurrentResult(VoltTable[] tables, int updateCount) throws SQLException
    {
        this.tableResults = tables;
        this.tableResultIndex = -1;
        this.lastUpdateCount = updateCount;
        if (this.result != null) {
            this.result.close();
        }
        if (this.tableResults == null || this.tableResults.length == 0) {
            return;
        }
        this.tableResultIndex = 0;
        this.result = createTrimmedResultSet(this.tableResults[this.tableResultIndex]);
    }

    private void closeAllOpenResults() throws SQLException
    {
        if (this.openResults != null)
        {
            for (Iterator iter = this.openResults.iterator(); iter.hasNext();)
            {
                JDBC4ResultSet element = iter.next();

                try
                {
                    element.close();
                }
                catch (SQLException x) {} // Will simply never happen, by design
            }
            this.openResults.clear();
        }
    }

    protected void addBatch(VoltSQL query) throws SQLException
    {
        if (batch == null) {
            batch = new ArrayList();
        }
        batch.add(query);
    }

    // Adds the given SQL command to the current list of commands for this Statement object.
    @Override
    public void addBatch(String sql) throws SQLException
    {
        checkClosed();
        VoltSQL query = VoltSQL.parseSQL(sql);
        // Reject SELECT statements.
        if (query.isOfType(VoltSQL.TYPE_SELECT)) {
            throw SQLError.get(SQLError.ILLEGAL_STATEMENT, sql);
        }
        this.addBatch(query);
    }

    // Cancels this Statement object if both the DBMS and driver support aborting an SQL statement.
    @Override
    public void cancel() throws SQLException
    {
        checkClosed();
        throw SQLError.noSupport();
    }

    // Empties this Statement object's current list of SQL commands.
    @Override
    public void clearBatch() throws SQLException
    {
        checkClosed();
        batch = null;
    }

    // Clears all the warnings reported on this Statement object.
    @Override
    public void clearWarnings() throws SQLException
    {
        checkClosed();
    }

    // Releases this Statement object's database and JDBC resources immediately instead of waiting for this to happen when it is automatically closed.
    @Override
    public void close() throws SQLException
    {
        // close resultset too
        this.isClosed = true;
    }

    protected boolean execute(VoltSQL query) throws SQLException
    {
        checkClosed();
        if (query.isQueryOfType(VoltSQL.TYPE_SELECT,VoltSQL.TYPE_EXEC))
        {
            setCurrentResult(query.execute(this.sourceConnection.NativeConnection, this.m_timeout,this.sourceConnection.queryTimeOutUnit), -1);
            return true;
        }
        else
        {
            setCurrentResult(null, (int) query.execute(this.sourceConnection.NativeConnection, this.m_timeout,this.sourceConnection.queryTimeOutUnit)[0].fetchRow(0).getLong(0));
            return false;
        }
    }

    // Executes the given SQL statement, which may return multiple results.
    @Override
    public boolean execute(String sql) throws SQLException
    {
        checkClosed();
        VoltSQL query = VoltSQL.parseSQL(sql);
        return this.execute(query);
    }

    // Executes the given SQL statement, which may return multiple results, and signals the driver that any auto-generated keys should be made available for retrieval.
    @Override
    public boolean execute(String sql, int autoGeneratedKeys) throws SQLException
    {
        checkClosed();
        throw SQLError.noSupport();
    }

    // Executes the given SQL statement, which may return multiple results, and signals the driver that the auto-generated keys indicated in the given array should be made available for retrieval.
    @Override
    public boolean execute(String sql, int[] columnIndexes) throws SQLException
    {
        checkClosed();
        throw SQLError.noSupport();
    }

    // Executes the given SQL statement, which may return multiple results, and signals the driver that the auto-generated keys indicated in the given array should be made available for retrieval.
    @Override
    public boolean execute(String sql, String[] columnNames) throws SQLException
    {
        checkClosed();
        throw SQLError.noSupport();
    }

    // Submits a batch of commands to the database for execution and if all commands execute successfully, returns an array of update counts.
    @Override
    public int[] executeBatch() throws SQLException
    {
        checkClosed();
        closeCurrentResult();
        if (batch == null || batch.size() == 0) {
            return new int[0];
        }

        int[] updateCounts = new int[batch.size()];
        // keep a running total of update counts
        int runningUpdateCount = 0;

        int i = 0;
        try {
            for (; i < batch.size(); i++) {

                setCurrentResult(
                        null,
                        (int) batch.get(i).execute(
                                sourceConnection.NativeConnection,
                                this.m_timeout,
                                sourceConnection.queryTimeOutUnit)[0].fetchRow(
                                0).getLong(0));
                updateCounts[i] = this.lastUpdateCount;
                runningUpdateCount += this.lastUpdateCount;
            }
        } catch (SQLException x) {
            updateCounts[i] = EXECUTE_FAILED;
            throw new BatchUpdateException(Arrays.copyOf(updateCounts, i + 1),
                    x);
        } finally {
            clearBatch();
        }
        // replace the update count from the last statement with the update count
        // from the last batch.
        this.lastUpdateCount = runningUpdateCount;

        return updateCounts;
    }

    protected ResultSet executeQuery(VoltSQL query) throws SQLException
    {
        setCurrentResult(query.execute(this.sourceConnection.NativeConnection, this.m_timeout, this.sourceConnection.queryTimeOutUnit), -1);
        return this.result;
    }

    // Executes the given SQL statement, which returns a single ResultSet object.
    @Override
    public ResultSet executeQuery(String sql) throws SQLException
    {
        checkClosed();
        VoltSQL query = VoltSQL.parseSQL(sql);
        if (!query.isOfType(VoltSQL.TYPE_SELECT)) {
            throw SQLError.get(SQLError.ILLEGAL_STATEMENT, sql);
        }
        return this.executeQuery(query);
    }

    protected int executeUpdate(VoltSQL query) throws SQLException
    {
        setCurrentResult(null, (int) query.execute(this.sourceConnection.NativeConnection, this.m_timeout,this.sourceConnection.queryTimeOutUnit)[0].fetchRow(0).getLong(0));
        return this.lastUpdateCount;
    }

    // Executes the given SQL statement, which may be an INSERT, UPDATE, or DELETE statement or an SQL statement that returns nothing, such as an SQL DDL statement.
    @Override
    public int executeUpdate(String sql) throws SQLException
    {
        checkClosed();
        VoltSQL query = VoltSQL.parseSQL(sql);
        // Reject SELECT statements.
        if (query.isOfType(VoltSQL.TYPE_SELECT)) {
            throw SQLError.get(SQLError.ILLEGAL_STATEMENT, sql);
        }
        return this.executeUpdate(query);
    }

    // Executes the given SQL statement and signals the driver with the given flag about whether the auto-generated keys produced by this Statement object should be made available for retrieval.
    @Override
    public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException
    {
        checkClosed();
        throw SQLError.noSupport(); // AutoGeneratedKeys not supported by provider
    }

    // Executes the given SQL statement and signals the driver that the auto-generated keys indicated in the given array should be made available for retrieval.
    @Override
    public int executeUpdate(String sql, int[] columnIndexes) throws SQLException
    {
        checkClosed();
        throw SQLError.noSupport(); // AutoGeneratedKeys not supported by provider
    }

    // Executes the given SQL statement and signals the driver that the auto-generated keys indicated in the given array should be made available for retrieval.
    @Override
    public int executeUpdate(String sql, String[] columnNames) throws SQLException
    {
        checkClosed();
        throw SQLError.noSupport(); // AutoGeneratedKeys not supported by provider
    }

    // Retrieves the Connection object that produced this Statement object.
    @Override
    public Connection getConnection() throws SQLException
    {
        checkClosed();
        return sourceConnection;
    }

    // Retrieves the direction for fetching rows from database tables that is the default for result sets generated from this Statement object.
    @Override
    public int getFetchDirection() throws SQLException
    {
        checkClosed();
        return this.fetchDirection;
    }

    // Retrieves the number of result set rows that is the default fetch size for ResultSet objects generated from this Statement object.
    @Override
    public int getFetchSize() throws SQLException
    {
        checkClosed();
        return this.fetchSize;
    }

    // Retrieves any auto-generated keys created as a result of executing this Statement object.
    @Override
    public ResultSet getGeneratedKeys() throws SQLException
    {
        checkClosed();
        throw SQLError.noSupport();
    }

    // Retrieves the maximum number of bytes that can be returned for character and binary column values in a ResultSet object produced by this Statement object.
    @Override
    public int getMaxFieldSize() throws SQLException
    {
        checkClosed();
        return this.maxFieldSize;
    }

    // Retrieves the maximum number of rows that a ResultSet object produced by this Statement object can contain.
    @Override
    public int getMaxRows() throws SQLException
    {
        checkClosed();
        return this.maxRows;
    }

    // Moves to this Statement object's next result, returns true if it is a ResultSet object, and implicitly closes any current ResultSet object(s) throws SQLException { throw SQLError.noSupport(); } obtained with the method getResultSet.
    @Override
    public boolean getMoreResults() throws SQLException
    {
        return this.getMoreResults(Statement.CLOSE_CURRENT_RESULT);
    }

    // Moves to this Statement object's next result, deals with any current ResultSet object(s) throws SQLException { throw SQLError.noSupport(); } according to the instructions specified by the given flag, and returns true if the next result is a ResultSet object.
    @Override
    public boolean getMoreResults(int current) throws SQLException
    {
        checkClosed();
        switch(current)
        {
            case Statement.KEEP_CURRENT_RESULT:
                this.openResults.add(this.result);
                this.result = null;
                this.lastUpdateCount = -1;
                break;
            case Statement.CLOSE_CURRENT_RESULT:
                closeCurrentResult();
                break;
            case Statement.CLOSE_ALL_RESULTS:
                closeCurrentResult();
                closeAllOpenResults();
                break;
            default:
                throw SQLError.get(SQLError.ILLEGAL_ARGUMENT, current);
        }
        if (current != Statement.CLOSE_ALL_RESULTS)
        {
            this.tableResultIndex++;
            if (this.tableResultIndex < this.tableResults.length)
            {
                VoltTable table = this.tableResults[this.tableResultIndex];
                if (VoltSQL.isUpdateResult(table)) {
                    this.lastUpdateCount = (int)table.fetchRow(0).getLong(0);
                } else
                {
                    this.result = createTrimmedResultSet(table);
                    return true;
                }
            }
        }
        return false;
    }

    // Retrieves the number of seconds the driver will wait for a Statement object to execute.
    @Override
    public int getQueryTimeout() throws SQLException
    {
        checkClosed();
        return this.m_timeout;
    }

    // Retrieves the current result as a ResultSet object.
    @Override
    public ResultSet getResultSet() throws SQLException
    {
        checkClosed();
        return this.result;
    }

    // Retrieves the result set concurrency for ResultSet objects generated by this Statement object.
    @Override
    public int getResultSetConcurrency() throws SQLException
    {
        checkClosed();
        return ResultSet.CONCUR_READ_ONLY;
    }

    // Retrieves the result set holdability for ResultSet objects generated by this Statement object.
    @Override
    public int getResultSetHoldability() throws SQLException
    {
        checkClosed();
        throw SQLError.noSupport();
    }

    // Retrieves the result set type for ResultSet objects generated by this Statement object.
    @Override
    public int getResultSetType() throws SQLException
    {
        checkClosed();
        return ResultSet.TYPE_SCROLL_INSENSITIVE;
    }

    // Retrieves the current result as an update count; if the result is a ResultSet object or there are no more results, -1 is returned.
    @Override
    public int getUpdateCount() throws SQLException
    {
        checkClosed();
        return this.lastUpdateCount;
    }

    // Retrieves the first warning reported by calls on this Statement object.
    @Override
    public SQLWarning getWarnings() throws SQLException
    {
        checkClosed();
        throw SQLError.noSupport();
    }

    // Retrieves whether this Statement object has been closed.
    @Override
    public boolean isClosed() throws SQLException
    {
        return this.isClosed;
    }

    // Returns a value indicating whether the Statement is poolable or not.
    @Override
    public boolean isPoolable() throws SQLException
    {
        checkClosed();
        return this.isPoolable;
    }

    // Sets the SQL cursor name to the given String, which will be used by subsequent Statement object execute methods.
    @Override
    public void setCursorName(String name) throws SQLException
    {
        checkClosed();
        throw SQLError.noSupport();
    }

    // Sets escape processing on or off.
    @Override
    public void setEscapeProcessing(boolean enable) throws SQLException
    {
        checkClosed();
        // Do nothing / not applicable?
    }

    // Gives the driver a hint as to the direction in which rows will be processed in ResultSet objects created using this Statement object.
    @Override
    public void setFetchDirection(int direction) throws SQLException
    {
        checkClosed();
        if ((direction != ResultSet.FETCH_FORWARD) && (direction != ResultSet.FETCH_REVERSE) && (direction != ResultSet.FETCH_UNKNOWN)) {
            throw SQLError.get(SQLError.ILLEGAL_ARGUMENT, direction);
        }
        this.fetchDirection = direction;
    }

    // Gives the JDBC driver a hint as to the number of rows that should be fetched from the database when more rows are needed for ResultSet objects genrated by this Statement.
    @Override
    public void setFetchSize(int rows) throws SQLException
    {
        checkClosed();
        if (rows < 0) {
            throw SQLError.get(SQLError.ILLEGAL_ARGUMENT, rows);
        }
        this.fetchSize = rows;
    }

    // Sets the limit for the maximum number of bytes that can be returned for character and binary column values in a ResultSet object produced by this Statement object.
    @Override
    public void setMaxFieldSize(int max) throws SQLException
    {
        checkClosed();
        if (max < 0) {
            throw SQLError.get(SQLError.ILLEGAL_ARGUMENT, max);
        }
        throw SQLError.noSupport(); // Not supported by provider - no point trashing data we received from the server just to simulate the feature while not getting any gains!
    }

    // Sets the limit for the maximum number of rows that any ResultSet object generated by this Statement object can contain to the given number.
    @Override
    public void setMaxRows(int max) throws SQLException
    {
        checkClosed();
        if (max < 0) {
            throw SQLError.get(SQLError.ILLEGAL_ARGUMENT, max);
        }
        this.maxRows = max;
    }

    // Requests that a Statement be pooled or not pooled.
    @Override
    public void setPoolable(boolean poolable) throws SQLException
    {
        checkClosed();
        this.isPoolable = poolable;
    }

    // Sets the number of seconds the driver will wait for a Statement object to execute to the given number of seconds.
    // 0 is infinite in our case its Integer.MAX_VALUE
    @Override
    public void setQueryTimeout(int seconds) throws SQLException
    {
        checkClosed();
        if (seconds < 0) {
            throw SQLError.get(SQLError.ILLEGAL_ARGUMENT, seconds);
        }
        if (seconds == 0) {
            this.m_timeout = Integer.MAX_VALUE;
        } else {
            this.m_timeout = seconds;
        }
    }

    // Returns true if this either implements the interface argument or is directly or indirectly a wrapper for an object that does.
    @Override
    public boolean isWrapperFor(Class iface) throws SQLException
    {
        return iface.isInstance(this);
    }

    // Returns an object that implements the given interface to allow access to non-standard methods, or standard methods not exposed by the proxy.
    @Override
    public  T unwrap(Class iface)    throws SQLException
    {
        try
        {
            return iface.cast(this);
        }
         catch (ClassCastException cce)
         {
            throw SQLError.get(SQLError.ILLEGAL_ARGUMENT, iface.toString());
        }
    }

    // No @Override because this has to compile with source 1.6.
    // Method not there in the interface in 1.6.
    public void closeOnCompletion() throws SQLException {
        throw SQLError.noSupport();
    }

    // No @Override because this has to compile with source 1.6.
    // Method not there in the interface in 1.6.
    public boolean isCloseOnCompletion() throws SQLException {
        throw SQLError.noSupport();
    }
}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy