/*
* Copyright © 2014 Cask Data, Inc.
*
* 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 co.cask.cdap.explore.jdbc;
import co.cask.cdap.explore.client.ExploreClient;
import com.google.common.collect.Maps;
import java.io.InputStream;
import java.io.Reader;
import java.math.BigDecimal;
import java.net.URL;
import java.sql.Array;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.Date;
import java.sql.NClob;
import java.sql.ParameterMetaData;
import java.sql.PreparedStatement;
import java.sql.Ref;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.RowId;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.sql.SQLXML;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.Calendar;
import java.util.Map;
/**
* Explore JDBC prepared statement.
*/
public class ExplorePreparedStatement extends ExploreStatement implements PreparedStatement {
private final String sql;
private String lastUpdatedSql = null;
private ResultSet lastResultSet = null;
private boolean lastResultSuccess = false;
// Save the SQL parameters {paramLoc:paramValue}
private final Map<Integer, String> parameters = Maps.newHashMap();
ExplorePreparedStatement(Connection connection, ExploreClient exploreClient, String sql, String namespace) {
super(connection, exploreClient, namespace);
// Although a PreparedStatement is meant to precompile sql statement, our Explore client
// interface does not allow it.
this.sql = sql;
}
@Override
public boolean execute() throws SQLException {
if (isClosed()) {
throw new SQLException("Can't execute after statement has been closed");
}
String updatedSql = updateSql();
if (lastUpdatedSql == null || !lastUpdatedSql.equals(updatedSql)) {
lastUpdatedSql = updatedSql;
lastResultSuccess = super.execute(lastUpdatedSql);
lastResultSet = getResultSet();
}
return lastResultSuccess;
}
@Override
public ResultSet executeQuery() throws SQLException {
if (!execute()) {
throw new SQLException("The query did not generate a result set!");
}
return lastResultSet;
}
@Override
public void setBoolean(int parameterIndex, boolean x) throws SQLException {
this.parameters.put(parameterIndex, String.valueOf(x));
}
@Override
public void setByte(int parameterIndex, byte x) throws SQLException {
this.parameters.put(parameterIndex, String.valueOf(x));
}
@Override
public void setShort(int parameterIndex, short x) throws SQLException {
this.parameters.put(parameterIndex, String.valueOf(x));
}
@Override
public void setInt(int parameterIndex, int x) throws SQLException {
this.parameters.put(parameterIndex, String.valueOf(x));
}
@Override
public void setLong(int parameterIndex, long x) throws SQLException {
this.parameters.put(parameterIndex, String.valueOf(x));
}
@Override
public void setFloat(int parameterIndex, float x) throws SQLException {
this.parameters.put(parameterIndex, String.valueOf(x));
}
@Override
public void setDouble(int parameterIndex, double x) throws SQLException {
this.parameters.put(parameterIndex, String.valueOf(x));
}
@Override
public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException {
this.parameters.put(parameterIndex, x.toPlainString());
}
@Override
public void setString(int parameterIndex, String x) throws SQLException {
this.parameters.put(parameterIndex, String.format("'%s'", x.replace("'", "\\'")));
}
@Override
public void setNull(int i, int i2) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public void setBytes(int i, byte[] bytes) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public void setDate(int parameterIndex, Date date) throws SQLException {
this.parameters.put(parameterIndex, date.toString());
}
@Override
public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException {
this.parameters.put(parameterIndex, x.toString());
}
@Override
public void clearParameters() throws SQLException {
parameters.clear();
}
@Override
public void setTime(int i, Time time) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public int executeUpdate() throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public void setAsciiStream(int i, InputStream inputStream, int i2) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
@Deprecated
public void setUnicodeStream(int i, InputStream inputStream, int i2) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public void setBinaryStream(int i, InputStream inputStream, int i2) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public void setObject(int i, Object o, int i2) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public void setObject(int i, Object o) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public void addBatch() throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public void setCharacterStream(int i, Reader reader, int i2) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public void setRef(int i, Ref ref) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public void setBlob(int i, Blob blob) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public void setClob(int i, Clob clob) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public void setArray(int i, Array array) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public ResultSetMetaData getMetaData() throws SQLException {
if (lastResultSet != null) {
// If the query has already been run, we return the metadata of the existing result set
return lastResultSet.getMetaData();
}
// Otherwise we first run the query and return the metadata, making it expensive to call this method
return executeQuery().getMetaData();
}
@Override
public void setDate(int i, Date date, Calendar calendar) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public void setTime(int i, Time time, Calendar calendar) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public void setTimestamp(int i, Timestamp timestamp, Calendar calendar) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public void setNull(int i, int i2, String s) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public void setURL(int i, URL url) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public ParameterMetaData getParameterMetaData() throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public void setRowId(int i, RowId rowId) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public void setNString(int i, String s) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public void setNCharacterStream(int i, Reader reader, long l) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public void setNClob(int i, NClob nClob) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public void setClob(int i, Reader reader, long l) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public void setBlob(int i, InputStream inputStream, long l) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public void setNClob(int i, Reader reader, long l) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public void setSQLXML(int i, SQLXML sqlxml) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public void setObject(int i, Object o, int i2, int i3) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public void setAsciiStream(int i, InputStream inputStream, long l) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public void setBinaryStream(int i, InputStream inputStream, long l) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public void setCharacterStream(int i, Reader reader, long l) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public void setAsciiStream(int i, InputStream inputStream) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public void setBinaryStream(int i, InputStream inputStream) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public void setCharacterStream(int i, Reader reader) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public void setNCharacterStream(int i, Reader reader) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public void setClob(int i, Reader reader) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public void setBlob(int i, InputStream inputStream) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public void setNClob(int i, Reader reader) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
/**
* Update the SQL string with parameters set by setXXX methods of {@link PreparedStatement}.
* Package visibility is for testing.
*/
String updateSql() throws SQLException {
StringBuilder newSql = new StringBuilder(sql);
int paramLoc = 1;
while (true) {
int nextParamIndex = getCharIndexFromSqlByParamLocation(newSql, '?', 1);
if (nextParamIndex < 0) {
break;
}
String tmp = parameters.get(paramLoc);
if (tmp == null) {
throw new SQLException("Parameter in position " + paramLoc + " has not been set.");
}
newSql.replace(nextParamIndex, nextParamIndex + 1, tmp);
paramLoc++;
}
return newSql.toString();
}
/**
* Get the index of the paramLoc-th given cchar from the SQL string.
* -1 will be return, if nothing found
*/
private int getCharIndexFromSqlByParamLocation(StringBuilder sql, char cchar, int paramLoc) {
boolean singleQuoteStr = false;
boolean doubleQuoteStr = false;
boolean escapeActive = false;
int charIndex = -1;
int num = 0;
for (int i = 0; i < sql.length(); i++) {
char c = sql.charAt(i);
if (escapeActive) {
escapeActive = false;
} else if (!singleQuoteStr && !doubleQuoteStr && c == '"') {
doubleQuoteStr = true;
} else if (!singleQuoteStr && !doubleQuoteStr && c == '\'') {
singleQuoteStr = true;
} else if ((singleQuoteStr || doubleQuoteStr) && !escapeActive && c == '\\') {
escapeActive = true;
} else if (singleQuoteStr && c == '\'') {
singleQuoteStr = false;
} else if (doubleQuoteStr && c == '"') {
doubleQuoteStr = false;
} else if (c == cchar && !singleQuoteStr && !doubleQuoteStr) {
num++;
if (num == paramLoc) {
charIndex = i;
break;
}
}
}
return charIndex;
}
}