blob: 0947ba9a49144a1b0cef77f9d5356a4a360ebc23 [file] [log] [blame]
// SPDX-License-Identifier: LGPL-2.1-or-later
// Copyright (c) 2012-2014 Monty Program Ab
// Copyright (c) 2015-2021 MariaDB Corporation Ab
package org.mariadb.jdbc.client.result;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.math.BigDecimal;
import java.sql.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.mariadb.jdbc.BasePreparedStatement;
import org.mariadb.jdbc.Connection;
import org.mariadb.jdbc.Statement;
import org.mariadb.jdbc.client.Column;
import org.mariadb.jdbc.client.ColumnDecoder;
import org.mariadb.jdbc.client.Context;
import org.mariadb.jdbc.client.result.rowdecoder.BinaryRowDecoder;
import org.mariadb.jdbc.codec.*;
import org.mariadb.jdbc.plugin.Codec;
import org.mariadb.jdbc.plugin.codec.*;
import org.mariadb.jdbc.util.ParameterList;
/** Updatable result implementation */
public class UpdatableResult extends CompleteResult {
private static final int STATE_STANDARD = 0;
private static final int STATE_UPDATE = 1;
private static final int STATE_UPDATED = 2;
private static final int STATE_INSERT = 3;
private static final int STATE_INSERTED = 4;
private String database;
private String table;
private boolean canInsert;
private boolean canUpdate;
private String sqlStateError = "HY000";
private boolean isAutoincrementPk;
private int savedRowPointer;
private String changeError;
private int state = STATE_STANDARD;
private ParameterList parameters;
private String[] primaryCols;
/**
* Constructor
*
* @param stmt statement that initiate this result
* @param binaryProtocol are rows binary encoded
* @param maxRows maximum rows
* @param metadataList columns metadata
* @param reader packet reader
* @param context connection context
* @param resultSetType result-set type
* @param closeOnCompletion close on completion
* @param traceEnable must network exchanges be logged
* @throws IOException if any socket error occurs
* @throws SQLException for other kind of error
*/
public UpdatableResult(
Statement stmt,
boolean binaryProtocol,
long maxRows,
ColumnDecoder[] metadataList,
org.mariadb.jdbc.client.socket.Reader reader,
Context context,
int resultSetType,
boolean closeOnCompletion,
boolean traceEnable)
throws IOException, SQLException {
super(
stmt,
binaryProtocol,
maxRows,
metadataList,
reader,
context,
resultSetType,
closeOnCompletion,
traceEnable);
checkIfUpdatable();
parameters = new ParameterList(metadataList.length);
}
private void checkIfUpdatable() throws SQLException {
isAutoincrementPk = false;
canInsert = true;
canUpdate = true;
// check that resultSet concern one table and database exactly
database = null;
table = null;
for (Column columnDefinition : metadataList) {
if (columnDefinition.getTable().isEmpty()) {
cannotUpdateInsertRow(
"The result-set contains fields without without any database/table information");
sqlStateError = "0A000";
return;
}
if (database != null && !database.equals(columnDefinition.getSchema())) {
cannotUpdateInsertRow("The result-set contains more than one database");
sqlStateError = "0A000";
return;
}
database = columnDefinition.getSchema();
if (table != null && !table.equals(columnDefinition.getTable())) {
cannotUpdateInsertRow("The result-set contains fields on different tables");
sqlStateError = "0A000";
return;
}
table = columnDefinition.getTable();
}
// check that listed column contain primary field
for (Column col : metadataList) {
if (col.isPrimaryKey()) {
isAutoincrementPk = col.isAutoIncrement();
if (isAutoincrementPk) {
primaryCols = new String[] {col.getColumnName()};
return;
}
}
}
// check that table contains a generated primary field
// to check if insert are still possible
ResultSet rs =
statement
.getConnection()
.createStatement()
.executeQuery("SHOW COLUMNS FROM `" + database + "`.`" + table + "`");
List<String> primaryColumns = new ArrayList<>();
while (rs.next()) {
if ("PRI".equals(rs.getString("Key"))) {
primaryColumns.add(rs.getString("Field"));
boolean keyPresent = false;
for (Column col : metadataList) {
if (rs.getString("Field").equals(col.getColumnName())) {
keyPresent = true;
}
}
boolean canBeNull = "YES".equals(rs.getString("Null"));
boolean hasDefault = rs.getString("Default") != null;
boolean generated = rs.getString("Extra") != null && !rs.getString("Extra").isEmpty();
isAutoincrementPk =
rs.getString("Extra") != null && rs.getString("Extra").contains("auto_increment");
if (!keyPresent && !canBeNull && !hasDefault && !generated) {
canInsert = false;
changeError =
String.format("primary field `%s` is not present in query", rs.getString("Field"));
}
if (!keyPresent) {
canUpdate = false;
changeError =
String.format(
"Cannot update rows, since primary field %s is not present in query",
rs.getString("Field"));
}
}
}
if (primaryColumns.isEmpty()) {
canUpdate = false;
changeError = "Cannot update rows, since no primary field is present in query";
} else {
primaryCols = primaryColumns.toArray(new String[0]);
}
}
private void cannotUpdateInsertRow(String reason) {
changeError = reason;
canUpdate = false;
canInsert = false;
}
private void checkUpdatable(int position) throws SQLException {
if (position <= 0 || position > metadataList.length) {
throw exceptionFactory.create("No such column: " + position, "22023");
}
if (state == STATE_STANDARD || state == STATE_UPDATED) {
state = STATE_UPDATE;
}
if (state == STATE_UPDATE) {
if (rowPointer <= BEFORE_FIRST_POS) {
throw new SQLDataException("Current position is before the first row", "22023");
}
if (rowPointer >= dataSize) {
throw new SQLDataException("Current position is after the last row", "22023");
}
if (!canUpdate) {
throw exceptionFactory.create("ResultSet cannot be updated. " + changeError, sqlStateError);
}
}
}
@Override
public boolean rowUpdated() {
return state == STATE_UPDATED;
}
@Override
public boolean rowInserted() {
return state == STATE_INSERTED;
}
@Override
public boolean rowDeleted() {
return false;
}
@Override
public void updateNull(int columnIndex) throws SQLException {
checkUpdatable(columnIndex);
parameters.set(columnIndex - 1, Parameter.NULL_PARAMETER);
}
@Override
public void updateBoolean(int columnIndex, boolean x) throws SQLException {
checkUpdatable(columnIndex);
parameters.set(columnIndex - 1, new Parameter<>(BooleanCodec.INSTANCE, x));
}
@Override
public void updateByte(int columnIndex, byte x) throws SQLException {
checkUpdatable(columnIndex);
parameters.set(columnIndex - 1, new Parameter<>(ByteCodec.INSTANCE, x));
}
@Override
public void updateShort(int columnIndex, short x) throws SQLException {
checkUpdatable(columnIndex);
parameters.set(columnIndex - 1, new Parameter<>(ShortCodec.INSTANCE, x));
}
@Override
public void updateInt(int columnIndex, int x) throws SQLException {
checkUpdatable(columnIndex);
parameters.set(columnIndex - 1, new Parameter<>(IntCodec.INSTANCE, x));
}
@Override
public void updateLong(int columnIndex, long x) throws SQLException {
checkUpdatable(columnIndex);
parameters.set(columnIndex - 1, new Parameter<>(LongCodec.INSTANCE, x));
}
@Override
public void updateFloat(int columnIndex, float x) throws SQLException {
checkUpdatable(columnIndex);
parameters.set(columnIndex - 1, new Parameter<>(FloatCodec.INSTANCE, x));
}
@Override
public void updateDouble(int columnIndex, double x) throws SQLException {
checkUpdatable(columnIndex);
parameters.set(columnIndex - 1, new Parameter<>(DoubleCodec.INSTANCE, x));
}
@Override
public void updateBigDecimal(int columnIndex, BigDecimal x) throws SQLException {
checkUpdatable(columnIndex);
parameters.set(columnIndex - 1, new Parameter<>(BigDecimalCodec.INSTANCE, x));
}
@Override
public void updateString(int columnIndex, String x) throws SQLException {
checkUpdatable(columnIndex);
parameters.set(columnIndex - 1, new Parameter<>(StringCodec.INSTANCE, x));
}
@Override
public void updateBytes(int columnIndex, byte[] x) throws SQLException {
checkUpdatable(columnIndex);
parameters.set(columnIndex - 1, new Parameter<>(ByteArrayCodec.INSTANCE, x));
}
@Override
public void updateDate(int columnIndex, Date x) throws SQLException {
checkUpdatable(columnIndex);
parameters.set(columnIndex - 1, new Parameter<>(DateCodec.INSTANCE, x));
}
@Override
public void updateTime(int columnIndex, Time x) throws SQLException {
checkUpdatable(columnIndex);
parameters.set(columnIndex - 1, new Parameter<>(TimeCodec.INSTANCE, x));
}
@Override
public void updateTimestamp(int columnIndex, Timestamp x) throws SQLException {
checkUpdatable(columnIndex);
parameters.set(columnIndex - 1, new Parameter<>(TimestampCodec.INSTANCE, x));
}
@Override
public void updateAsciiStream(int columnIndex, InputStream x, int length) throws SQLException {
checkUpdatable(columnIndex);
parameters.set(columnIndex - 1, new Parameter<>(StreamCodec.INSTANCE, x, (long) length));
}
@Override
public void updateBinaryStream(int columnIndex, InputStream x, int length) throws SQLException {
checkUpdatable(columnIndex);
parameters.set(columnIndex - 1, new Parameter<>(StreamCodec.INSTANCE, x, (long) length));
}
@Override
public void updateCharacterStream(int columnIndex, Reader x, int length) throws SQLException {
checkUpdatable(columnIndex);
parameters.set(columnIndex - 1, new Parameter<>(ReaderCodec.INSTANCE, x, (long) length));
}
@Override
public void updateObject(int columnIndex, Object x, int scaleOrLength) throws SQLException {
updateInternalObject(columnIndex, x, (long) scaleOrLength);
}
@Override
public void updateObject(int columnIndex, Object x) throws SQLException {
updateInternalObject(columnIndex, x, null);
}
@Override
public void updateNull(String columnLabel) throws SQLException {
updateNull(findColumn(columnLabel));
}
@Override
public void updateBoolean(String columnLabel, boolean x) throws SQLException {
updateBoolean(findColumn(columnLabel), x);
}
@Override
public void updateByte(String columnLabel, byte x) throws SQLException {
updateByte(findColumn(columnLabel), x);
}
@Override
public void updateShort(String columnLabel, short x) throws SQLException {
updateShort(findColumn(columnLabel), x);
}
@Override
public void updateInt(String columnLabel, int x) throws SQLException {
updateInt(findColumn(columnLabel), x);
}
@Override
public void updateLong(String columnLabel, long x) throws SQLException {
updateLong(findColumn(columnLabel), x);
}
@Override
public void updateFloat(String columnLabel, float x) throws SQLException {
updateFloat(findColumn(columnLabel), x);
}
@Override
public void updateDouble(String columnLabel, double x) throws SQLException {
updateDouble(findColumn(columnLabel), x);
}
@Override
public void updateBigDecimal(String columnLabel, BigDecimal x) throws SQLException {
updateBigDecimal(findColumn(columnLabel), x);
}
@Override
public void updateString(String columnLabel, String x) throws SQLException {
updateString(findColumn(columnLabel), x);
}
@Override
public void updateBytes(String columnLabel, byte[] x) throws SQLException {
updateBytes(findColumn(columnLabel), x);
}
@Override
public void updateDate(String columnLabel, Date x) throws SQLException {
updateDate(findColumn(columnLabel), x);
}
@Override
public void updateTime(String columnLabel, Time x) throws SQLException {
updateTime(findColumn(columnLabel), x);
}
@Override
public void updateTimestamp(String columnLabel, Timestamp x) throws SQLException {
updateTimestamp(findColumn(columnLabel), x);
}
@Override
public void updateAsciiStream(String columnLabel, InputStream x, int length) throws SQLException {
updateAsciiStream(findColumn(columnLabel), x, length);
}
@Override
public void updateBinaryStream(String columnLabel, InputStream x, int length)
throws SQLException {
updateBinaryStream(findColumn(columnLabel), x, length);
}
@Override
public void updateCharacterStream(String columnLabel, Reader reader, int length)
throws SQLException {
updateCharacterStream(findColumn(columnLabel), reader, length);
}
@Override
public void updateObject(String columnLabel, Object x, int scaleOrLength) throws SQLException {
updateObject(findColumn(columnLabel), x, scaleOrLength);
}
@Override
public void updateObject(String columnLabel, Object x) throws SQLException {
updateObject(findColumn(columnLabel), x);
}
@Override
public void insertRow() throws SQLException {
if (state == STATE_INSERT || state == STATE_INSERTED) {
// Create query will all field with WHERE clause contain primary field.
// if field are not updated, value DEFAULT will be set
// (if field has no default, then insert will throw an exception that will be return to
// user)
String insertSql = buildInsertQuery();
try (PreparedStatement insertPreparedStatement =
((Connection) statement.getConnection())
.prepareInternal(
insertSql,
Statement.RETURN_GENERATED_KEYS,
ResultSet.TYPE_FORWARD_ONLY,
ResultSet.CONCUR_READ_ONLY,
rowDecoder instanceof BinaryRowDecoder)) {
int paramPos = 0;
for (int pos = 0; pos < metadataList.length; pos++) {
Column colInfo = metadataList[pos];
org.mariadb.jdbc.client.util.Parameter param =
parameters.size() > pos ? parameters.get(pos) : null;
if (param != null) {
((BasePreparedStatement) insertPreparedStatement).setParameter(paramPos++, param);
} else if (!Arrays.asList(primaryCols).contains(colInfo.getColumnName())
&& !colInfo.hasDefault()) {
((BasePreparedStatement) insertPreparedStatement)
.setParameter(paramPos++, Parameter.NULL_PARAMETER);
}
}
ResultSet insertRs = insertPreparedStatement.executeQuery();
if (context.getVersion().isMariaDBServer()
&& context.getVersion().versionGreaterOrEqual(10, 5, 1)) {
if (insertRs.next()) {
byte[] rowByte = ((Result) insertRs).getCurrentRowData();
addRowData(rowByte);
}
} else if (isAutoincrementPk) {
// primary is auto_increment (only one field)
ResultSet rsKey = insertPreparedStatement.getGeneratedKeys();
if (rsKey.next()) {
try (PreparedStatement refreshPreparedStatement = prepareRefreshStmt()) {
refreshPreparedStatement.setObject(1, rsKey.getObject(1));
Result rs = (Result) refreshPreparedStatement.executeQuery();
// update row data only if not deleted externally
if (rs.next()) {
addRowData(rs.getCurrentRowData());
}
}
}
} else {
addRowData(refreshRawData());
}
}
parameters = new ParameterList(parameters.size());
state = STATE_INSERTED;
}
}
/**
* Build insert query
*
* @return insert sql
* @throws SQLException exception
*/
private String buildInsertQuery() throws SQLException {
StringBuilder insertSql = new StringBuilder("INSERT `" + database + "`.`" + table + "` ( ");
StringBuilder valueClause = new StringBuilder();
StringBuilder returningClause = new StringBuilder();
boolean firstParam = true;
for (int pos = 0; pos < metadataList.length; pos++) {
Column colInfo = metadataList[pos];
if (pos != 0) {
returningClause.append(", ");
}
returningClause.append("`").append(colInfo.getColumnName()).append("`");
org.mariadb.jdbc.client.util.Parameter param =
parameters.size() > pos ? parameters.get(pos) : null;
if (param != null) {
if (!firstParam) {
insertSql.append(",");
valueClause.append(", ");
}
insertSql.append("`").append(colInfo.getColumnName()).append("`");
valueClause.append("?");
firstParam = false;
} else {
if (Arrays.asList(primaryCols).contains(colInfo.getColumnName())) {
boolean isAutoIncrement =
colInfo.isAutoIncrement() || (primaryCols.length == 1 && isAutoincrementPk);
if (isAutoIncrement || colInfo.hasDefault()) {
if (!isAutoIncrement
&& (!context.getVersion().isMariaDBServer()
|| !context.getVersion().versionGreaterOrEqual(10, 5, 1))) {
// driver cannot know generated default value like uuid().
// but for server 10.5+, will use RETURNING to know primary key
throw exceptionFactory.create(
String.format(
"Cannot call insertRow() not setting value for primary key %s "
+ "with default value before server 10.5",
colInfo.getColumnName()));
}
} else {
throw exceptionFactory.create(
String.format(
"Cannot call insertRow() not setting value for primary key %s",
colInfo.getColumnName()));
}
} else if (!colInfo.hasDefault()) {
if (!firstParam) {
insertSql.append(",");
valueClause.append(", ");
}
firstParam = false;
insertSql.append("`").append(colInfo.getColumnName()).append("`");
valueClause.append("?");
}
}
}
insertSql.append(") VALUES (").append(valueClause).append(")");
if (context.getVersion().isMariaDBServer()
&& context.getVersion().versionGreaterOrEqual(10, 5, 1)) {
insertSql.append(" RETURNING ").append(returningClause);
}
return insertSql.toString();
}
private String refreshStmt() {
// Construct SELECT query according to column metadata, with WHERE part containing primary
// fields
StringBuilder selectSql = new StringBuilder("SELECT ");
StringBuilder whereClause = new StringBuilder(" WHERE ");
boolean firstPrimary = true;
for (int pos = 0; pos < metadataList.length; pos++) {
Column colInfo = metadataList[pos];
if (pos != 0) {
selectSql.append(",");
}
selectSql.append("`").append(colInfo.getColumnName()).append("`");
if (Arrays.asList(primaryCols).contains(colInfo.getColumnName())) {
if (!firstPrimary) {
whereClause.append("AND ");
}
firstPrimary = false;
whereClause.append("`").append(colInfo.getColumnName()).append("` = ? ");
}
}
selectSql
.append(" FROM `")
.append(database)
.append("`.`")
.append(table)
.append("`")
.append(whereClause);
return selectSql.toString();
}
private PreparedStatement prepareRefreshStmt() throws SQLException {
// row's raw bytes must be encoded according to current resultSet type
// so use Server or Client PrepareStatement accordingly
return ((Connection) statement.getConnection())
.prepareInternal(
refreshStmt(),
Statement.RETURN_GENERATED_KEYS,
ResultSet.TYPE_FORWARD_ONLY,
ResultSet.CONCUR_READ_ONLY,
rowDecoder instanceof BinaryRowDecoder);
}
private byte[] refreshRawData() throws SQLException {
int fieldsPrimaryIndex = 0;
try (PreparedStatement refreshPreparedStatement = prepareRefreshStmt()) {
for (int pos = 0; pos < metadataList.length; pos++) {
Column colInfo = metadataList[pos];
if (Arrays.asList(primaryCols).contains(colInfo.getColumnName())) {
if ((state != STATE_STANDARD) && parameters.size() > pos && parameters.get(pos) != null) {
// Row has just been updated using updateRow() methods.
// updateRow might have changed primary key, so must use the new value.
org.mariadb.jdbc.client.util.Parameter value = parameters.get(pos);
((BasePreparedStatement) refreshPreparedStatement)
.setParameter(fieldsPrimaryIndex++, value);
} else {
refreshPreparedStatement.setObject(++fieldsPrimaryIndex, getObject(pos + 1));
}
}
}
Result rs = (Result) refreshPreparedStatement.executeQuery();
rs.next();
return rs.getCurrentRowData();
}
}
private String updateQuery() {
StringBuilder updateSql = new StringBuilder("UPDATE `" + database + "`.`" + table + "` SET ");
StringBuilder whereClause = new StringBuilder(" WHERE ");
boolean firstUpdate = true;
for (int pos = 0; pos < primaryCols.length; pos++) {
String key = primaryCols[pos];
if (pos != 0) {
whereClause.append("AND ");
}
whereClause.append("`").append(key).append("` = ? ");
}
for (int pos = 0; pos < metadataList.length; pos++) {
Column colInfo = metadataList[pos];
if (parameters.size() > pos && parameters.get(pos) != null) {
if (!firstUpdate) {
updateSql.append(",");
}
firstUpdate = false;
updateSql.append("`").append(colInfo.getColumnName()).append("` = ? ");
}
}
if (firstUpdate) return null;
return updateSql.append(whereClause).toString();
}
@Override
public void updateRow() throws SQLException {
if (state == STATE_INSERT) {
throw exceptionFactory.create("Cannot call updateRow() when inserting a new row");
}
if (rowPointer < 0) {
throw exceptionFactory.create("Current position is before the first row", "22023");
}
if (rowPointer >= dataSize) {
throw exceptionFactory.create("Current position is after the last row", "22023");
}
if (state == STATE_UPDATE || state == STATE_UPDATED) {
// state is STATE_UPDATE, meaning that at least one field is modified, update query can be
// run.
// Construct UPDATE query according to modified field only
String updateQuery = updateQuery();
if (updateQuery != null) {
try (PreparedStatement preparedStatement =
((Connection) statement.getConnection())
.prepareInternal(
updateQuery,
Statement.RETURN_GENERATED_KEYS,
ResultSet.TYPE_FORWARD_ONLY,
ResultSet.CONCUR_READ_ONLY,
rowDecoder instanceof BinaryRowDecoder)) {
int fieldsIndex = 0;
for (int pos = 0; pos < metadataList.length; pos++) {
if (parameters.size() > pos) {
org.mariadb.jdbc.client.util.Parameter param = parameters.get(pos);
if (param != null) {
((BasePreparedStatement) preparedStatement).setParameter(fieldsIndex++, param);
}
}
}
for (int pos = 0; pos < metadataList.length; pos++) {
Column colInfo = metadataList[pos];
if (Arrays.asList(primaryCols).contains(colInfo.getColumnName())) {
preparedStatement.setObject(++fieldsIndex, getObject(pos + 1));
}
}
preparedStatement.execute();
}
refreshRow();
}
parameters = new ParameterList(parameters.size());
state = STATE_UPDATED;
}
}
@Override
public void deleteRow() throws SQLException {
if (state == STATE_INSERT) {
throw exceptionFactory.create("Cannot call deleteRow() when inserting a new row");
}
if (!canUpdate) {
throw exceptionFactory.create("ResultSet cannot be updated. " + changeError, sqlStateError);
}
if (rowPointer < 0) {
throw new SQLDataException("Current position is before the first row", "22023");
}
if (rowPointer >= dataSize) {
throw new SQLDataException("Current position is after the last row", "22023");
}
// Create query with WHERE clause contain primary field.
StringBuilder deleteSql =
new StringBuilder("DELETE FROM `" + database + "`.`" + table + "` WHERE ");
boolean firstPrimary = true;
for (Column colInfo : metadataList) {
if (Arrays.asList(primaryCols).contains(colInfo.getColumnName())) {
if (!firstPrimary) {
deleteSql.append("AND ");
}
firstPrimary = false;
deleteSql.append("`").append(colInfo.getColumnName()).append("` = ? ");
}
}
try (PreparedStatement deletePreparedStatement =
((Connection) statement.getConnection())
.prepareInternal(
deleteSql.toString(),
Statement.RETURN_GENERATED_KEYS,
ResultSet.TYPE_FORWARD_ONLY,
ResultSet.CONCUR_READ_ONLY,
false)) {
int fieldsPrimaryIndex = 1;
for (int pos = 0; pos < metadataList.length; pos++) {
Column colInfo = metadataList[pos];
if (Arrays.asList(primaryCols).contains(colInfo.getColumnName())) {
deletePreparedStatement.setObject(fieldsPrimaryIndex++, getObject(pos + 1));
}
}
deletePreparedStatement.executeUpdate();
// remove data
System.arraycopy(data, rowPointer + 1, data, rowPointer, dataSize - 1 - rowPointer);
data[dataSize - 1] = null;
dataSize--;
previous();
}
}
@Override
public void refreshRow() throws SQLException {
if (state == STATE_INSERT) {
throw exceptionFactory.create("Cannot call refreshRow() when inserting a new row");
}
if (rowPointer < 0) {
throw exceptionFactory.create("Current position is before the first row", "22023");
}
if (rowPointer >= data.length) {
throw exceptionFactory.create("Current position is after the last row", "22023");
}
if (canUpdate) {
updateRowData(refreshRawData());
}
}
@Override
public void cancelRowUpdates() {
parameters = new ParameterList(parameters.size());
state = STATE_STANDARD;
}
@Override
public void moveToInsertRow() throws SQLException {
if (!canInsert) {
throw exceptionFactory.create("No row can be inserted. " + changeError, sqlStateError);
}
parameters = new ParameterList(parameters.size());
state = STATE_INSERT;
savedRowPointer = rowPointer;
}
@Override
public void moveToCurrentRow() {
state = STATE_STANDARD;
resetToRowPointer();
}
@Override
public void updateBlob(int columnIndex, Blob x) throws SQLException {
checkUpdatable(columnIndex);
parameters.set(columnIndex - 1, new Parameter<>(BlobCodec.INSTANCE, x));
}
@Override
public void updateBlob(String columnLabel, Blob x) throws SQLException {
updateBlob(findColumn(columnLabel), x);
}
@Override
public void updateClob(int columnIndex, Clob x) throws SQLException {
checkUpdatable(columnIndex);
parameters.set(columnIndex - 1, new Parameter<>(ClobCodec.INSTANCE, x));
}
@Override
public void updateClob(String columnLabel, Clob x) throws SQLException {
updateClob(findColumn(columnLabel), x);
}
@Override
public void updateNString(int columnIndex, String nString) throws SQLException {
updateString(columnIndex, nString);
}
@Override
public void updateNString(String columnLabel, String nString) throws SQLException {
updateString(columnLabel, nString);
}
@Override
public void updateNClob(int columnIndex, NClob nClob) throws SQLException {
updateClob(columnIndex, nClob);
}
@Override
public void updateNClob(String columnLabel, NClob nClob) throws SQLException {
updateClob(columnLabel, nClob);
}
@Override
public void updateNCharacterStream(int columnIndex, Reader x, long length) throws SQLException {
updateCharacterStream(columnIndex, x, length);
}
@Override
public void updateNCharacterStream(String columnLabel, Reader reader, long length)
throws SQLException {
updateCharacterStream(columnLabel, reader, length);
}
@Override
public void updateAsciiStream(int columnIndex, InputStream x, long length) throws SQLException {
checkUpdatable(columnIndex);
parameters.set(columnIndex - 1, new Parameter<>(StreamCodec.INSTANCE, x, length));
}
@Override
public void updateBinaryStream(int columnIndex, InputStream x, long length) throws SQLException {
checkUpdatable(columnIndex);
parameters.set(columnIndex - 1, new Parameter<>(StreamCodec.INSTANCE, x, length));
}
@Override
public void updateCharacterStream(int columnIndex, Reader x, long length) throws SQLException {
checkUpdatable(columnIndex);
parameters.set(columnIndex - 1, new Parameter<>(ReaderCodec.INSTANCE, x, length));
}
@Override
public void updateAsciiStream(String columnLabel, InputStream x, long length)
throws SQLException {
updateAsciiStream(findColumn(columnLabel), x, length);
}
@Override
public void updateBinaryStream(String columnLabel, InputStream x, long length)
throws SQLException {
updateBinaryStream(findColumn(columnLabel), x, length);
}
@Override
public void updateCharacterStream(String columnLabel, Reader reader, long length)
throws SQLException {
updateCharacterStream(findColumn(columnLabel), reader, length);
}
@Override
public void updateBlob(int columnIndex, InputStream x, long length) throws SQLException {
checkUpdatable(columnIndex);
parameters.set(columnIndex - 1, new Parameter<>(StreamCodec.INSTANCE, x, length));
}
@Override
public void updateBlob(String columnLabel, InputStream inputStream, long length)
throws SQLException {
updateBlob(findColumn(columnLabel), inputStream, length);
}
@Override
public void updateClob(int columnIndex, Reader x, long length) throws SQLException {
checkUpdatable(columnIndex);
parameters.set(columnIndex - 1, new Parameter<>(ReaderCodec.INSTANCE, x, length));
}
@Override
public void updateClob(String columnLabel, Reader reader, long length) throws SQLException {
updateClob(findColumn(columnLabel), reader, length);
}
@Override
public void updateNClob(int columnIndex, Reader reader, long length) throws SQLException {
updateClob(columnIndex, reader, length);
}
@Override
public void updateNClob(String columnLabel, Reader reader, long length) throws SQLException {
updateClob(columnLabel, reader, length);
}
@Override
public void updateNCharacterStream(int columnIndex, Reader x) throws SQLException {
updateCharacterStream(columnIndex, x);
}
@Override
public void updateNCharacterStream(String columnLabel, Reader reader) throws SQLException {
updateCharacterStream(columnLabel, reader);
}
@Override
public void updateAsciiStream(int columnIndex, InputStream x) throws SQLException {
checkUpdatable(columnIndex);
parameters.set(columnIndex - 1, new Parameter<>(StreamCodec.INSTANCE, x));
}
@Override
public void updateBinaryStream(int columnIndex, InputStream x) throws SQLException {
checkUpdatable(columnIndex);
parameters.set(columnIndex - 1, new Parameter<>(StreamCodec.INSTANCE, x));
}
@Override
public void updateCharacterStream(int columnIndex, Reader x) throws SQLException {
checkUpdatable(columnIndex);
parameters.set(columnIndex - 1, new Parameter<>(ReaderCodec.INSTANCE, x));
}
@Override
public void updateAsciiStream(String columnLabel, InputStream x) throws SQLException {
updateAsciiStream(findColumn(columnLabel), x);
}
@Override
public void updateBinaryStream(String columnLabel, InputStream x) throws SQLException {
updateBinaryStream(findColumn(columnLabel), x);
}
@Override
public void updateCharacterStream(String columnLabel, Reader reader) throws SQLException {
updateCharacterStream(findColumn(columnLabel), reader);
}
@Override
public void updateBlob(int columnIndex, InputStream x) throws SQLException {
checkUpdatable(columnIndex);
parameters.set(columnIndex - 1, new Parameter<>(StreamCodec.INSTANCE, x));
}
@Override
public void updateBlob(String columnLabel, InputStream inputStream) throws SQLException {
updateBlob(findColumn(columnLabel), inputStream);
}
@Override
public void updateClob(int columnIndex, Reader x) throws SQLException {
checkUpdatable(columnIndex);
parameters.set(columnIndex - 1, new Parameter<>(ReaderCodec.INSTANCE, x));
}
@Override
public void updateClob(String columnLabel, Reader reader) throws SQLException {
updateClob(findColumn(columnLabel), reader);
}
@Override
public void updateNClob(int columnIndex, Reader reader) throws SQLException {
updateClob(columnIndex, reader);
}
@Override
public void updateNClob(String columnLabel, Reader reader) throws SQLException {
updateClob(columnLabel, reader);
}
@Override
public void updateObject(int columnIndex, Object x, SQLType targetSqlType, int scaleOrLength)
throws SQLException {
updateInternalObject(columnIndex, x, (long) scaleOrLength);
}
@SuppressWarnings({"unchecked", "rawtypes"})
private void updateInternalObject(int columnIndex, Object x, Long scaleOrLength)
throws SQLException {
checkUpdatable(columnIndex);
if (x == null) {
parameters.set(columnIndex - 1, Parameter.NULL_PARAMETER);
return;
}
for (Codec<?> codec : context.getConf().codecs()) {
if (codec.canEncode(x)) {
Parameter p = new Parameter(codec, x, scaleOrLength);
parameters.set(columnIndex - 1, p);
return;
}
}
throw new SQLException(String.format("Type %s not supported type", x.getClass().getName()));
}
@Override
public void updateObject(String columnLabel, Object x, SQLType targetSqlType, int scaleOrLength)
throws SQLException {
updateObject(findColumn(columnLabel), x, targetSqlType, scaleOrLength);
}
@Override
public void updateObject(int columnIndex, Object x, SQLType targetSqlType) throws SQLException {
updateInternalObject(columnIndex, x, null);
}
@Override
public void updateObject(String columnLabel, Object x, SQLType targetSqlType)
throws SQLException {
updateObject(findColumn(columnLabel), x, targetSqlType);
}
@Override
public int getConcurrency() {
return CONCUR_UPDATABLE;
}
private void resetToRowPointer() {
rowPointer = savedRowPointer;
if (rowPointer != BEFORE_FIRST_POS && rowPointer < dataSize - 1) {
setRow(data[rowPointer]);
} else {
// all data are reads and pointer is after last
setNullRowBuf();
}
savedRowPointer = -1;
}
@Override
public void beforeFirst() throws SQLException {
if (state == STATE_INSERT) {
resetToRowPointer();
}
state = STATE_STANDARD;
super.beforeFirst();
}
@Override
public boolean first() throws SQLException {
if (state == STATE_INSERT) {
resetToRowPointer();
}
state = STATE_STANDARD;
return super.first();
}
@Override
public boolean last() throws SQLException {
if (state == STATE_INSERT) {
resetToRowPointer();
}
state = STATE_STANDARD;
return super.last();
}
@Override
public void afterLast() throws SQLException {
if (state == STATE_INSERT) {
resetToRowPointer();
}
state = STATE_STANDARD;
super.afterLast();
}
@Override
public boolean absolute(int row) throws SQLException {
if (state == STATE_INSERT) {
resetToRowPointer();
}
state = STATE_STANDARD;
return super.absolute(row);
}
@Override
public boolean relative(int rows) throws SQLException {
if (state == STATE_INSERT) {
resetToRowPointer();
}
state = STATE_STANDARD;
return super.relative(rows);
}
@Override
public boolean next() throws SQLException {
if (state == STATE_INSERT) {
resetToRowPointer();
}
state = STATE_STANDARD;
return super.next();
}
@Override
public boolean previous() throws SQLException {
if (state == STATE_INSERT) {
resetToRowPointer();
}
state = STATE_STANDARD;
return super.previous();
}
}