From 07453be66c449c2d450078d29ac03d16732b8a4a Mon Sep 17 00:00:00 2001 From: Mark Rotteveel Date: Sun, 17 Jul 2016 14:35:34 +0200 Subject: [PATCH] JDBC-243 / JDBC-187 implements stream, reader and blob methods currently throwing FBDriverNotCapableException --- .../jdbc/AbstractCallableStatement.java | 68 +++- .../jdbc/AbstractPreparedStatement.java | 115 +++---- .../firebirdsql/jdbc/AbstractResultSet.java | 309 ++++++++---------- src/main/org/firebirdsql/jdbc/FBBlob.java | 107 +++--- src/main/org/firebirdsql/jdbc/FBClob.java | 40 ++- .../firebirdsql/jdbc/field/FBBinaryField.java | 26 +- .../firebirdsql/jdbc/field/FBBlobField.java | 58 ++-- .../org/firebirdsql/jdbc/field/FBField.java | 35 +- .../jdbc/field/FBFlushableField.java | 4 +- .../jdbc/field/FBLongVarCharField.java | 44 ++- .../firebirdsql/jdbc/field/FBNullField.java | 13 +- .../firebirdsql/jdbc/field/FBStringField.java | 29 +- src/main/org/firebirdsql/util/IOUtils.java | 26 ++ .../org/firebirdsql/jdbc/TestFBResultSet.java | 111 ++++++- .../jdbc/field/BaseJUnit4TestFBField.java | 12 - .../jdbc/field/TestFBBinaryField.java | 21 -- .../jdbc/field/TestFBNullField.java | 16 - .../jdbc/field/TestFBStringField.java | 33 +- 18 files changed, 570 insertions(+), 497 deletions(-) diff --git a/src/main/org/firebirdsql/jdbc/AbstractCallableStatement.java b/src/main/org/firebirdsql/jdbc/AbstractCallableStatement.java index a89a8b577a..2053971824 100644 --- a/src/main/org/firebirdsql/jdbc/AbstractCallableStatement.java +++ b/src/main/org/firebirdsql/jdbc/AbstractCallableStatement.java @@ -335,8 +335,8 @@ protected boolean internalExecute(boolean sendOutParams) throws SQLException { field.setNull(); } else if (value instanceof WrapperWithCalendar) { setField(field, (WrapperWithCalendar)value); - } else if (value instanceof WrapperWithInt) { - setField(field, (WrapperWithInt)value); + } else if (value instanceof WrapperWithLong) { + setField(field, (WrapperWithLong)value); } else { field.setObject(value); } @@ -354,20 +354,21 @@ protected boolean internalExecute(boolean sendOutParams) throws SQLException { return hasResultSet; } - private void setField(FBField field, WrapperWithInt value) throws SQLException { + private void setField(FBField field, WrapperWithLong value) throws SQLException { Object obj = value.getValue(); if (obj == null) { field.setNull(); } else { - int intValue = value.getIntValue(); + long longValue = value.getLongValue(); - if (obj instanceof InputStream) - field.setBinaryStream((InputStream) obj, intValue); - else if (obj instanceof Reader) - field.setCharacterStream((Reader) obj, intValue); - else + if (obj instanceof InputStream) { + field.setBinaryStream((InputStream) obj, longValue); + } else if (obj instanceof Reader) { + field.setCharacterStream((Reader) obj, longValue); + } else { throw new TypeConversionException("Cannot convert type " + obj.getClass().getName()); + } } } @@ -1290,14 +1291,29 @@ public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException } public void setBinaryStream(int parameterIndex, InputStream inputStream, int length) throws SQLException { - procedureCall.getInputParam(parameterIndex).setValue( - new WrapperWithInt(inputStream, length)); + procedureCall.getInputParam(parameterIndex).setValue(new WrapperWithLong(inputStream, length)); + } + + public void setBinaryStream(int parameterIndex, InputStream inputStream, long length) throws SQLException { + procedureCall.getInputParam(parameterIndex).setValue(new WrapperWithLong(inputStream, length)); + } + + public void setBinaryStream(int parameterIndex, InputStream inputStream) throws SQLException { + procedureCall.getInputParam(parameterIndex).setValue(inputStream); } public void setBlob(int parameterIndex, Blob blob) throws SQLException { procedureCall.getInputParam(parameterIndex).setValue(blob); } + public void setBlob(int parameterIndex, InputStream inputStream, long length) throws SQLException { + procedureCall.getInputParam(parameterIndex).setValue(new WrapperWithLong(inputStream, length)); + } + + public void setBlob(int parameterIndex, InputStream inputStream) throws SQLException { + procedureCall.getInputParam(parameterIndex).setValue(inputStream); + } + public void setBoolean(int parameterIndex, boolean x) throws SQLException { procedureCall.getInputParam(parameterIndex).setValue(x); } @@ -1311,13 +1327,29 @@ public void setBytes(int parameterIndex, byte[] x) throws SQLException { } public void setCharacterStream(int parameterIndex, Reader reader, int length) throws SQLException { - procedureCall.getInputParam(parameterIndex).setValue(new WrapperWithInt(reader, length)); + procedureCall.getInputParam(parameterIndex).setValue(new WrapperWithLong(reader, length)); + } + + public void setCharacterStream(int parameterIndex, Reader reader, long length) throws SQLException { + procedureCall.getInputParam(parameterIndex).setValue(new WrapperWithLong(reader, length)); + } + + public void setCharacterStream(int parameterIndex, Reader reader) throws SQLException { + procedureCall.getInputParam(parameterIndex).setValue(reader); } public void setClob(int parameterIndex, Clob x) throws SQLException { procedureCall.getInputParam(parameterIndex).setValue(x); } + public void setClob(int parameterIndex, Reader reader, long length) throws SQLException { + procedureCall.getInputParam(parameterIndex).setValue(new WrapperWithLong(reader, length)); + } + + public void setClob(int parameterIndex, Reader reader) throws SQLException { + procedureCall.getInputParam(parameterIndex).setValue(reader); + } + public void setDate(int parameterIndex, java.sql.Date x, Calendar cal) throws SQLException { procedureCall.getInputParam(parameterIndex).setValue(new WrapperWithCalendar(x, cal)); } @@ -1473,21 +1505,21 @@ private Calendar getCalendar() { } } - private static class WrapperWithInt { + private static class WrapperWithLong { private final Object value; - private final int intValue; + private final long longValue; - private WrapperWithInt(Object value, int intValue) { + private WrapperWithLong(Object value, long longValue) { this.value = value; - this.intValue = intValue; + this.longValue = longValue; } private Object getValue() { return value; } - private int getIntValue() { - return intValue; + private long getLongValue() { + return longValue; } } diff --git a/src/main/org/firebirdsql/jdbc/AbstractPreparedStatement.java b/src/main/org/firebirdsql/jdbc/AbstractPreparedStatement.java index 06fec18b2a..34cef52a49 100644 --- a/src/main/org/firebirdsql/jdbc/AbstractPreparedStatement.java +++ b/src/main/org/firebirdsql/jdbc/AbstractPreparedStatement.java @@ -233,36 +233,22 @@ public void setNull(int parameterIndex, int sqlType) throws SQLException { isParamSet[parameterIndex - 1] = true; } - /** - * Sets the designated parameter to the given input stream, which will have - * the specified number of bytes. - * - *

- * Note: This stream object can either be a standard Java stream - * object or your own subclass that implements the standard interface. - * - * @param parameterIndex - * the first parameter is 1, the second is 2, ... - * @param inputStream - * the Java input stream - * @param length - * the number of bytes in the stream - * @exception SQLException - * if a database access error occurs - */ + @Override public void setBinaryStream(int parameterIndex, InputStream inputStream, int length) throws SQLException { getField(parameterIndex).setBinaryStream(inputStream, length); isParamSet[parameterIndex - 1] = true; } - + + @Override public void setBinaryStream(int parameterIndex, InputStream inputStream, long length) throws SQLException { - if (length > Integer.MAX_VALUE) - throw new FBDriverNotCapableException("Only length <= Integer.MAX_VALUE supported"); - setBinaryStream(parameterIndex, inputStream, (int)length); + getField(parameterIndex).setBinaryStream(inputStream, length); + isParamSet[parameterIndex - 1] = true; } - public void setBinaryStream(int parameterIndex, InputStream x) throws SQLException { - throw new FBDriverNotCapableException(); + @Override + public void setBinaryStream(int parameterIndex, InputStream inputStream) throws SQLException { + getField(parameterIndex).setBinaryStream(inputStream); + isParamSet[parameterIndex - 1] = true; } /** @@ -512,8 +498,9 @@ protected FieldDescriptor getParameterDescriptor(int columnIndex) { */ protected FBField getField(int columnIndex) throws SQLException { checkValidity(); - if (columnIndex > fields.length) - throw new FBSQLException("Invalid column index.", SQLStateConstants.SQL_STATE_INVALID_COLUMN); + if (columnIndex > fields.length) { + throw new SQLException("Invalid column index: " + columnIndex, SQLStateConstants.SQL_STATE_INVALID_COLUMN); + } return fields[columnIndex - 1]; } @@ -539,16 +526,18 @@ protected FBField getField(int columnIndex) throws SQLException { * @exception SQLException * if a database access error occurs */ - public void setAsciiStream(int parameterIndex, InputStream x, int length) throws SQLException { + @Override + public final void setAsciiStream(int parameterIndex, InputStream x, int length) throws SQLException { setBinaryStream(parameterIndex, x, length); } - - public void setAsciiStream(int parameterIndex, InputStream x, long length) throws SQLException { + + @Override + public final void setAsciiStream(int parameterIndex, InputStream x, long length) throws SQLException { setBinaryStream(parameterIndex, x, length); } - public void setAsciiStream(int parameterIndex, InputStream x) - throws SQLException { + @Override + public final void setAsciiStream(int parameterIndex, InputStream x) throws SQLException { setBinaryStream(parameterIndex, x); } @@ -929,44 +918,22 @@ private void executeSingleForBatch(RowValue data, List results) throws SQL results.add(getLargeUpdateCount()); } - /** - * Sets the designated parameter to the given Reader object, - * which is the given number of characters long. When a very large UNICODE - * value is input to a LONGVARCHAR parameter, it may be more - * practical to send it via a java.io.Reader object. The data - * will be read from the stream as needed until end-of-file is reached. The - * JDBC driver will do any necessary conversion from UNICODE to the database - * char format. - * - *

- * Note: This stream object can either be a standard Java stream - * object or your own subclass that implements the standard interface. - * - * @param parameterIndex - * the first parameter is 1, the second is 2, ... - * @param reader - * the java reader which contains the UNICODE data - * @param length - * the number of characters in the stream - * @exception SQLException - * if a database access error occurs - * @since 1.2 - * @see What Is in the JDBC 2.0 API - * - */ + @Override public void setCharacterStream(int parameterIndex, Reader reader, int length) throws SQLException { getField(parameterIndex).setCharacterStream(reader, length); isParamSet[parameterIndex - 1] = true; } - + + @Override public void setCharacterStream(int parameterIndex, Reader reader, long length) throws SQLException { - if (length > Integer.MAX_VALUE) - throw new FBDriverNotCapableException("Only length <= Integer.MAX_VALUE supported"); - setCharacterStream(parameterIndex, reader, (int)length); + getField(parameterIndex).setCharacterStream(reader, length); + isParamSet[parameterIndex - 1] = true; } + @Override public void setCharacterStream(int parameterIndex, Reader reader) throws SQLException { - throw new FBDriverNotCapableException(); + getField(parameterIndex).setCharacterStream(reader); + isParamSet[parameterIndex - 1] = true; } /** @@ -1001,10 +968,10 @@ public void setRef(int i, Ref x) throws SQLException { * @see What Is in the JDBC 2.0 API * */ + @Override public void setBlob(int parameterIndex, Blob blob) throws SQLException { - // if the passed BLOB is not instance of our class, copy its content - // into the our BLOB - if (!(blob instanceof FBBlob)) { + // if the passed BLOB is not instance of our class, copy its content into the our BLOB + if (blob != null && !(blob instanceof FBBlob)) { FBBlob fbb = new FBBlob(gdsHelper, blobListener); fbb.copyStream(blob.getBinaryStream()); blob = fbb; @@ -1013,15 +980,15 @@ public void setBlob(int parameterIndex, Blob blob) throws SQLException { getField(parameterIndex).setBlob((FBBlob) blob); isParamSet[parameterIndex - 1] = true; } - + + @Override public void setBlob(int parameterIndex, InputStream inputStream, long length) throws SQLException { - if (length > Integer.MAX_VALUE) - throw new FBDriverNotCapableException("Only length <= Integer.MAX_VALUE supported"); FBBlob blob = new FBBlob(gdsHelper, blobListener); - blob.copyStream(inputStream, (int) length); + blob.copyStream(inputStream, length); setBlob(parameterIndex, blob); } + @Override public void setBlob(int parameterIndex, InputStream inputStream) throws SQLException { FBBlob blob = new FBBlob(gdsHelper, blobListener); blob.copyStream(inputStream); @@ -1040,9 +1007,9 @@ public void setBlob(int parameterIndex, InputStream inputStream) throws SQLExcep * if a database access error occurs * @since 1.2 */ + @Override public void setClob(int parameterIndex, Clob clob) throws SQLException { - // if the passed BLOB is not instance of our class, copy its content - // into the our BLOB + // if the passed BLOB is not instance of our class, copy its content into the our BLOB if (!(clob instanceof FBClob)) { FBClob fbc = new FBClob(new FBBlob(gdsHelper, blobListener)); fbc.copyCharacterStream(clob.getCharacterStream()); @@ -1052,11 +1019,15 @@ public void setClob(int parameterIndex, Clob clob) throws SQLException { getField(parameterIndex).setClob((FBClob) clob); isParamSet[parameterIndex - 1] = true; } - + + @Override public void setClob(int parameterIndex, Reader reader, long length) throws SQLException { - throw new FBDriverNotCapableException(); + FBClob clob = new FBClob(new FBBlob(gdsHelper, blobListener)); + clob.copyCharacterStream(reader, length); + setClob(parameterIndex, clob); } + @Override public void setClob(int parameterIndex, Reader reader) throws SQLException { FBClob clob = new FBClob(new FBBlob(gdsHelper, blobListener)); clob.copyCharacterStream(reader); @@ -1351,8 +1322,6 @@ public boolean execute(String sql, String[] columnNames) throws SQLException { throw new FBSQLException(METHOD_NOT_SUPPORTED); } - // TODO Implement large update count method below using SqlCountHolder - public long executeLargeUpdate() throws SQLException { executeUpdate(); return getLargeUpdateCount(); diff --git a/src/main/org/firebirdsql/jdbc/AbstractResultSet.java b/src/main/org/firebirdsql/jdbc/AbstractResultSet.java index 59aa8ba40e..7d28c3e193 100644 --- a/src/main/org/firebirdsql/jdbc/AbstractResultSet.java +++ b/src/main/org/firebirdsql/jdbc/AbstractResultSet.java @@ -447,8 +447,9 @@ public boolean wasNull() throws SQLException { * @throws SQLException if this parameter cannot be retrieved as an ASCII * stream */ - public InputStream getAsciiStream(int columnIndex) throws SQLException { - return getField(columnIndex).getAsciiStream(); + @Override + public final InputStream getAsciiStream(int columnIndex) throws SQLException { + return getBinaryStream(columnIndex); } /** @@ -738,11 +739,11 @@ public FBField getField(int columnIndex, boolean checkRowPosition) throws SQLExc checkOpen(); if (checkRowPosition && row == null && rowUpdater == null) { - throw new SQLException("The resultSet is not in a row, use next", SQLStateConstants.SQL_STATE_NO_ROW_AVAIL); + throw new SQLException("The result set is not in a row, use next", SQLStateConstants.SQL_STATE_NO_ROW_AVAIL); } if (columnIndex > rowDescriptor.getCount()) { - throw new SQLException("Invalid column index.", SQLStateConstants.SQL_STATE_INVALID_COLUMN); + throw new SQLException("Invalid column index: " + columnIndex, SQLStateConstants.SQL_STATE_INVALID_COLUMN); } if (rowUpdater != null) { @@ -983,8 +984,9 @@ public Timestamp getTimestamp(String columnName) throws SQLException { * @return The value as an InputStream * @throws SQLException if the given column cannot be retrieved */ - public InputStream getAsciiStream(String columnName) throws SQLException { - return getField(columnName).getAsciiStream(); + @Override + public final InputStream getAsciiStream(String columnName) throws SQLException { + return getBinaryStream(columnName); } /** @@ -1978,80 +1980,40 @@ public void updateTimestamp(int columnIndex, Timestamp x) throws SQLException { getField(columnIndex).setTimestamp(x); } - /** - * Updates the designated column with an ascii stream value. - * The updateXXX methods are used to update column values in the - * current row or the insert row. The updateXXX methods do not - * update the underlying database; instead the updateRow or - * insertRow methods are called to update the database. - * - * @param columnIndex the first column is 1, the second is 2, ... - * @param x the new column value - * @param length the length of the stream - * @exception SQLException if a database access error occurs - * @since 1.2 - * @see What Is in the JDBC - * 2.0 API - */ - public void updateAsciiStream(int columnIndex, InputStream x, int length) throws SQLException { - checkUpdatable(); - getField(columnIndex).setAsciiStream(x, length); - } - - /** - * Updates the designated column with a binary stream value. - * The updateXXX methods are used to update column values in the - * current row or the insert row. The updateXXX methods do not - * update the underlying database; instead the updateRow or - * insertRow methods are called to update the database. - * - * @param columnIndex the first column is 1, the second is 2, ... - * @param x the new column value - * @param length the length of the stream - * @exception SQLException if a database access error occurs - * @since 1.2 - * @see What Is in the JDBC - * 2.0 API - */ + @Override public void updateBinaryStream(int columnIndex, InputStream x, int length) throws SQLException { checkUpdatable(); getField(columnIndex).setBinaryStream(x, length); } + @Override public void updateBinaryStream(int columnIndex, InputStream x, long length) throws SQLException { - throw new FBDriverNotCapableException(); + checkUpdatable(); + getField(columnIndex).setBinaryStream(x, length); } + @Override public void updateBinaryStream(int columnIndex, InputStream x) throws SQLException { - throw new FBDriverNotCapableException(); + checkUpdatable(); + getField(columnIndex).setBinaryStream(x); } - public void updateBinaryStream(String columnLabel, InputStream x, long length) throws SQLException { - throw new FBDriverNotCapableException(); + @Override + public void updateBinaryStream(String columnName, InputStream x, int length) throws SQLException { + checkUpdatable(); + getField(columnName).setBinaryStream(x, length); } - public void updateBinaryStream(String columnLabel, InputStream x) throws SQLException { - throw new FBDriverNotCapableException(); + @Override + public void updateBinaryStream(String columnLabel, InputStream x, long length) throws SQLException { + checkUpdatable(); + getField(columnLabel).setBinaryStream(x, length); } - /** - * Updates the designated column with a character stream value. - * The updateXXX methods are used to update column values in the - * current row or the insert row. The updateXXX methods do not - * update the underlying database; instead the updateRow or - * insertRow methods are called to update the database. - * - * @param columnIndex the first column is 1, the second is 2, ... - * @param x the new column value - * @param length the length of the stream - * @exception SQLException if a database access error occurs - * @since 1.2 - * @see What Is in the JDBC - * 2.0 API - */ - public void updateCharacterStream(int columnIndex, Reader x, int length) throws SQLException { + @Override + public void updateBinaryStream(String columnLabel, InputStream x) throws SQLException { checkUpdatable(); - getField(columnIndex).setCharacterStream(x, length); + getField(columnLabel).setBinaryStream(x); } /** @@ -2394,89 +2356,70 @@ public void updateTimestamp(String columnName, Timestamp x) throws SQLException getField(columnName).setTimestamp(x); } - /** - * Updates the designated column with an ascii stream value. - * The updateXXX methods are used to update column values in the - * current row or the insert row. The updateXXX methods do not - * update the underlying database; instead the updateRow or - * insertRow methods are called to update the database. - * - * @param columnName the name of the column - * @param x the new column value - * @param length the length of the stream - * @exception SQLException if a database access error occurs - * @since 1.2 - * @see What Is in the JDBC - * 2.0 API - */ - public void updateAsciiStream(String columnName, InputStream x, int length) throws SQLException { - checkUpdatable(); - getField(columnName).setAsciiStream(x, length); + @Override + public final void updateAsciiStream(int columnIndex, InputStream x, int length) throws SQLException { + updateBinaryStream(columnIndex, x, length); } - public void updateAsciiStream(int columnIndex, InputStream x, long length) throws SQLException { - // TODO Write implementation - throw new FBDriverNotCapableException(); + @Override + public final void updateAsciiStream(String columnName, InputStream x, int length) throws SQLException { + updateBinaryStream(columnName, x, length); } - public void updateAsciiStream(int columnIndex, InputStream x) throws SQLException { - // TODO Write implementation - throw new FBDriverNotCapableException(); + @Override + public final void updateAsciiStream(int columnIndex, InputStream x, long length) throws SQLException { + updateBinaryStream(columnIndex, x, length); } - public void updateAsciiStream(String columnLabel, InputStream x, long length) throws SQLException { - // TODO Write implementation - throw new FBDriverNotCapableException(); + @Override + public final void updateAsciiStream(int columnIndex, InputStream x) throws SQLException { + updateBinaryStream(columnIndex, x); } - public void updateAsciiStream(String columnLabel, InputStream x) throws SQLException { - // TODO Write implementation - throw new FBDriverNotCapableException(); + @Override + public final void updateAsciiStream(String columnLabel, InputStream x, long length) throws SQLException { + updateBinaryStream(columnLabel, x, length); } - public void updateBinaryStream(String columnName, InputStream x, int length) throws SQLException { - checkUpdatable(); - getField(columnName).setBinaryStream(x, length); + @Override + public final void updateAsciiStream(String columnLabel, InputStream x) throws SQLException { + updateBinaryStream(columnLabel, x); } - /** - * Updates the designated column with a character stream value. - * The updateXXX methods are used to update column values in the - * current row or the insert row. The updateXXX methods do not - * update the underlying database; instead the updateRow or - * insertRow methods are called to update the database. - * - * @param columnName the name of the column - * @param reader the new column value - * @param length the length of the stream - * @exception SQLException if a database access error occurs - * @since 1.2 - * @see What Is in the JDBC - * 2.0 API - */ - public void updateCharacterStream(String columnName, Reader reader, int length) throws SQLException { + @Override + public void updateCharacterStream(int columnIndex, Reader x, int length) throws SQLException { checkUpdatable(); - getField(columnName).setCharacterStream(reader, length); + getField(columnIndex).setCharacterStream(x, length); } + @Override public void updateCharacterStream(int columnIndex, Reader x, long length) throws SQLException { - // TODO Write implementation - throw new FBDriverNotCapableException(); + checkUpdatable(); + getField(columnIndex).setCharacterStream(x, length); } + @Override public void updateCharacterStream(int columnIndex, Reader x) throws SQLException { - // TODO Write implementation - throw new FBDriverNotCapableException(); + checkUpdatable(); + getField(columnIndex).setCharacterStream(x); + } + + @Override + public void updateCharacterStream(String columnName, Reader reader, int length) throws SQLException { + checkUpdatable(); + getField(columnName).setCharacterStream(reader, length); } + @Override public void updateCharacterStream(String columnLabel, Reader reader, long length) throws SQLException { - // TODO Write implementation - throw new FBDriverNotCapableException(); + checkUpdatable(); + getField(columnLabel).setCharacterStream(reader, length); } + @Override public void updateCharacterStream(String columnLabel, Reader reader) throws SQLException { - // TODO Write implementation - throw new FBDriverNotCapableException(); + checkUpdatable(); + getField(columnLabel).setCharacterStream(reader); } /** @@ -3024,92 +2967,102 @@ public void updateRef(String param1, Ref param2) throws SQLException { throw new FBDriverNotCapableException("Type REF not supported"); } - /** - * This operation is not supported - * - * @param param1 - * @param param2 - * @exception java.sql.SQLException - */ - public void updateBlob(int param1, Blob param2) throws SQLException { - // TODO Write implementation - throw new FBDriverNotCapableException(); + @Override + public void updateBlob(int columnIndex, Blob blob) throws SQLException { + checkUpdatable(); + getField(columnIndex).setBlob(asFBBlob(blob)); } - /** - * This operation is not supported - * - * @param param1 - * @param param2 - * @exception java.sql.SQLException - */ - public void updateBlob(String param1, Blob param2) throws SQLException { - // TODO Write implementation - throw new FBDriverNotCapableException(); + private FBBlob asFBBlob(Blob blob) throws SQLException { + // if the passed BLOB is not instance of our class, copy its content into the our BLOB + if (blob == null) { + return null; + } + if (blob instanceof FBBlob) { + return (FBBlob) blob; + } + FBBlob fbb = new FBBlob(gdsHelper); + fbb.copyStream(blob.getBinaryStream()); + return fbb; + } + + @Override + public void updateBlob(String columnLabel, Blob blob) throws SQLException { + checkUpdatable(); + getField(columnLabel).setBlob(asFBBlob(blob)); } + @Override public void updateBlob(int columnIndex, InputStream inputStream, long length) throws SQLException { - // TODO Write implementation - throw new FBDriverNotCapableException(); + checkUpdatable(); + getField(columnIndex).setBinaryStream(inputStream, length); } + @Override public void updateBlob(int columnIndex, InputStream inputStream) throws SQLException { - // TODO Write implementation - throw new FBDriverNotCapableException(); + checkUpdatable(); + getField(columnIndex).setBinaryStream(inputStream); } + @Override public void updateBlob(String columnLabel, InputStream inputStream, long length) throws SQLException { - // TODO Write implementation - throw new FBDriverNotCapableException(); + checkUpdatable(); + getField(columnLabel).setBinaryStream(inputStream, length); } + @Override public void updateBlob(String columnLabel, InputStream inputStream) throws SQLException { - // TODO Write implementation - throw new FBDriverNotCapableException(); + checkUpdatable(); + getField(columnLabel).setBinaryStream(inputStream); } - /** - * This operation is not supported - * - * @param param1 - * @param param2 - * @exception java.sql.SQLException - */ - public void updateClob(int param1, Clob param2) throws SQLException { - // TODO Write implementation - throw new FBDriverNotCapableException(); + @Override + public void updateClob(int columnIndex, java.sql.Clob clob) throws SQLException { + checkUpdatable(); + getField(columnIndex).setClob(asFBClob(clob)); } - /** - * This operation is not supported - * - * @param param1 - * @param param2 - * @exception java.sql.SQLException - */ - public void updateClob(String param1, Clob param2) throws SQLException { - // TODO Write implementation - throw new FBDriverNotCapableException(); + private FBClob asFBClob(Clob clob) throws SQLException { + // if the passed BLOB is not instance of our class, copy its content into the our BLOB + if (clob == null) { + return null; + } + if (clob instanceof FBClob) { + return (FBClob) clob; + } + FBClob fbc = new FBClob(new FBBlob(gdsHelper)); + fbc.copyCharacterStream(clob.getCharacterStream()); + return fbc; + } + + @Override + public void updateClob(String columnLabel, Clob clob) throws SQLException { + checkUpdatable(); + getField(columnLabel).setClob(asFBClob(clob)); } + @Override public void updateClob(int columnIndex, Reader reader, long length) throws SQLException { - // TODO Write implementation - throw new FBDriverNotCapableException(); + checkUpdatable(); + getField(columnIndex).setCharacterStream(reader, length); } + @Override public void updateClob(int columnIndex, Reader reader) throws SQLException { - // TODO Write implementation - throw new FBDriverNotCapableException(); + checkUpdatable(); + getField(columnIndex).setCharacterStream(reader); } + @Override public void updateClob(String columnLabel, Reader reader, long length) throws SQLException { - // TODO Write implementation - throw new FBDriverNotCapableException(); + checkUpdatable(); + getField(columnLabel).setCharacterStream(reader, length); } + @Override public void updateClob(String columnLabel, Reader reader) throws SQLException { - // TODO Write implementation - throw new FBDriverNotCapableException(); + checkUpdatable(); + getField(columnLabel).setCharacterStream(reader); } public void updateArray(int param1, Array param2) throws SQLException { diff --git a/src/main/org/firebirdsql/jdbc/FBBlob.java b/src/main/org/firebirdsql/jdbc/FBBlob.java index b41c1cb7b4..7ffb34104d 100644 --- a/src/main/org/firebirdsql/jdbc/FBBlob.java +++ b/src/main/org/firebirdsql/jdbc/FBBlob.java @@ -431,83 +431,114 @@ public GDSHelper getGdsHelper() { } /** - * Copy the contents of an InputStream into this Blob. + * Copy the contents of an {@code InputStream} into this Blob. + *

+ * Calling with length {@code -1} is equivalent to calling {@link #copyStream(InputStream)}, and will copy + * the whole stream. + *

* * @param inputStream the stream from which data will be copied - * @param length The maximum number of bytes to read from the InputStream + * @param length The maximum number of bytes to read from the InputStream, {@code -1} to read whole stream * @throws SQLException if a database access error occurs */ - public void copyStream(InputStream inputStream, int length) throws SQLException { - OutputStream os = setBinaryStream(1); - try { - final byte[] buffer = new byte[Math.min(bufferLength, length)]; + public void copyStream(InputStream inputStream, long length) throws SQLException { + if (length == -1L) { + copyStream(inputStream); + return; + } + try (OutputStream os = setBinaryStream(1)) { + final byte[] buffer = new byte[(int) Math.min(bufferLength, length)]; int chunk; - while (length > 0 && (chunk = inputStream.read(buffer, 0, Math.min(buffer.length, length))) != -1) { + while (length > 0 && (chunk = inputStream.read(buffer, 0, (int) Math.min(buffer.length, length))) != -1) { os.write(buffer, 0, chunk); length -= chunk; } - os.flush(); - os.close(); } catch (IOException ioe) { - throw new FBSQLException(ioe); + throw new SQLException(ioe); } } /** * Copy the contents of an InputStream into this Blob. Unlike - * the {@link #copyStream(InputStream, int)} method, this one copies bytes + * the {@link #copyStream(InputStream, long)} method, this one copies bytes * until the EOF is reached. * * @param inputStream the stream from which data will be copied * @throws SQLException if a database access error occurs */ public void copyStream(InputStream inputStream) throws SQLException { - OutputStream os = setBinaryStream(1); - try { + try (OutputStream os = setBinaryStream(1)) { final byte[] buffer = new byte[bufferLength]; int chunk; - while ((chunk = inputStream.read(buffer)) != -1) + while ((chunk = inputStream.read(buffer)) != -1) { os.write(buffer, 0, chunk); - - os.flush(); - os.close(); + } } catch (IOException ioe) { - throw new FBSQLException(ioe); + throw new SQLException(ioe); } } /** * Copy data from a character stream into this Blob. + *

+ * Calling with length {@code -1} is equivalent to calling {@link #copyCharacterStream(Reader, String)}. + *

* - * @param inputStream the source of data to copy - * @param length The maximum number of bytes to copy + * @param reader the source of data to copy + * @param length The maximum number of bytes to copy, or {@code -1} to read the whole stream * @param encoding The encoding used in the character stream */ - public void copyCharacterStream(Reader inputStream, int length, String encoding) throws SQLException { - try { - OutputStream os = setBinaryStream(1); - OutputStreamWriter osw = encoding != null - ? new OutputStreamWriter(os, encoding) - : new OutputStreamWriter(os); - - final char[] buffer = new char[Math.min(bufferLength, length)]; + public void copyCharacterStream(Reader reader, long length, String encoding) throws SQLException { + if (length == -1L) { + copyCharacterStream(reader, encoding); + return; + } + try (OutputStream os = setBinaryStream(1); + OutputStreamWriter osw = encoding != null + ? new OutputStreamWriter(os, encoding) + : new OutputStreamWriter(os)) { + + final char[] buffer = new char[(int) Math.min(bufferLength, length)]; int chunk; - try { - while (length > 0 && (chunk = inputStream.read(buffer, 0, Math.min(buffer.length, length))) != -1) { - osw.write(buffer, 0, chunk); - length -= chunk; - } - } finally { - osw.flush(); - osw.close(); + while (length > 0 && (chunk = reader.read(buffer, 0, (int) Math.min(buffer.length, length))) != -1) { + osw.write(buffer, 0, chunk); + length -= chunk; + } + } catch (UnsupportedEncodingException ex) { + throw new SQLException("Cannot set character stream because " + + "the encoding '" + encoding + "' is unsupported in the JVM. " + + "Please report this to the driver developers." + ); + } catch (IOException ioe) { + throw new SQLException(ioe); + } + } + + /** + * Copy data from a character stream into this Blob. Unlike + * the {@link #copyCharacterStream(Reader, long, String)} )} method, this one copies bytes + * until the EOF is reached. + * + * @param reader the source of data to copy + * @param encoding The encoding used in the character stream + */ + public void copyCharacterStream(Reader reader, String encoding) throws SQLException { + try (OutputStream os = setBinaryStream(1); + OutputStreamWriter osw = encoding != null + ? new OutputStreamWriter(os, encoding) + : new OutputStreamWriter(os)) { + final char[] buffer = new char[bufferLength]; + int chunk; + while ((chunk = reader.read(buffer, 0, buffer.length)) != -1) { + osw.write(buffer, 0, chunk); } } catch (UnsupportedEncodingException ex) { - throw new FBSQLException("Cannot set character stream because " + + throw new SQLException("Cannot set character stream because " + "the encoding '" + encoding + "' is unsupported in the JVM. " + "Please report this to the driver developers." ); } catch (IOException ioe) { - throw new FBSQLException(ioe); + throw new SQLException(ioe); } } } diff --git a/src/main/org/firebirdsql/jdbc/FBClob.java b/src/main/org/firebirdsql/jdbc/FBClob.java index 013b23e474..f31b7bd53d 100644 --- a/src/main/org/firebirdsql/jdbc/FBClob.java +++ b/src/main/org/firebirdsql/jdbc/FBClob.java @@ -291,19 +291,41 @@ public Reader getCharacterStream(long pos, long length) throws SQLException { } } + /** + * Copy data from a character stream into this Blob. + *

+ * Calling with length {@code -1} is equivalent to calling {@link #copyCharacterStream(Reader)}. + *

+ * + * @param characterStream the source of data to copy + * @param length The maximum number of bytes to copy, or {@code -1} to read the whole stream + */ + public void copyCharacterStream(Reader characterStream, long length) throws SQLException { + if (length == -1L) { + copyCharacterStream(characterStream); + return; + } + try (Writer writer = setCharacterStream(1)) { + int chunk; + final char[] buffer = new char[1024]; + while (length > 0 && (chunk = characterStream.read(buffer)) != -1) { + writer.write(buffer, 0, chunk); + length -= chunk; + } + } catch (IOException ioe) { + throw new SQLException(ioe); + } + } + public void copyCharacterStream(Reader characterStream) throws SQLException { - Writer writer = setCharacterStream(1); - try { + try (Writer writer = setCharacterStream(1)) { int chunk; final char[] buffer = new char[1024]; - - while ((chunk = characterStream.read(buffer)) != -1) - writer.write(buffer, 0, chunk); - - writer.flush(); - writer.close(); + while ((chunk = characterStream.read(buffer)) != -1) { + writer.write(buffer, 0, chunk); + } } catch (IOException ioe) { - throw new FBSQLException(ioe); + throw new SQLException(ioe); } } diff --git a/src/main/org/firebirdsql/jdbc/field/FBBinaryField.java b/src/main/org/firebirdsql/jdbc/field/FBBinaryField.java index b7d2d1b986..e2477533d6 100644 --- a/src/main/org/firebirdsql/jdbc/field/FBBinaryField.java +++ b/src/main/org/firebirdsql/jdbc/field/FBBinaryField.java @@ -87,38 +87,36 @@ public InputStream getBinaryStream() throws SQLException { } @Override - public void setBinaryStream(InputStream in, int length) throws SQLException { + public void setBinaryStream(InputStream in, long length) throws SQLException { if (in == null) { setNull(); return; } + if (length > fieldDescriptor.getLength()) { + throw new DataTruncation(-1, true, false, (int) length, fieldDescriptor.getLength()); + } + try { - setBytes(IOUtils.toBytes(in, length)); + setBytes(IOUtils.toBytes(in, (int) length)); } catch (IOException ioex) { throw new TypeConversionException(BINARY_STREAM_CONVERSION_ERROR); } } @Override - public InputStream getAsciiStream() throws SQLException { - return getBinaryStream(); - } - - @Override - public void setAsciiStream(InputStream in, int length) throws SQLException { - setBinaryStream(in, length); - } - - @Override - public void setCharacterStream(Reader in, int length) throws SQLException { + public void setCharacterStream(Reader in, long length) throws SQLException { if (in == null) { setNull(); return; } + if (length > fieldDescriptor.getLength()) { + throw new DataTruncation(-1, true, false, (int) length, fieldDescriptor.getLength()); + } + try { - setString(IOUtils.toString(in, length)); + setString(IOUtils.toString(in, (int) length)); } catch (IOException ioex) { throw new TypeConversionException(CHARACTER_STREAM_CONVERSION_ERROR); } diff --git a/src/main/org/firebirdsql/jdbc/field/FBBlobField.java b/src/main/org/firebirdsql/jdbc/field/FBBlobField.java index 6ef31e4266..e1b5358552 100644 --- a/src/main/org/firebirdsql/jdbc/field/FBBlobField.java +++ b/src/main/org/firebirdsql/jdbc/field/FBBlobField.java @@ -39,7 +39,7 @@ class FBBlobField extends FBField implements FBFlushableField { private FBBlob blob; - private int length; + private long length; private InputStream binaryStream; private Reader characterStream; private byte[] bytes; @@ -48,6 +48,7 @@ class FBBlobField extends FBField implements FBFlushableField { super(fieldDescriptor, dataProvider, requiredType); } + @Override public void close() throws SQLException { try { if (blob != null) blob.free(); @@ -62,6 +63,7 @@ public void close() throws SQLException { } } + @Override public Blob getBlob() throws SQLException { if (blob != null) return blob; final byte[] bytes = getFieldData(); @@ -73,33 +75,23 @@ public Blob getBlob() throws SQLException { return blob; } + @Override public Clob getClob() throws SQLException { FBBlob blob = (FBBlob) getBlob(); if (blob == null) return null; return new FBClob(blob); } - public InputStream getAsciiStream() throws SQLException { - return getBinaryStream(); - } - + @Override public InputStream getBinaryStream() throws SQLException { - // getBinaryStream() is not defined for BLOB types, only for BINARY - if (fieldDescriptor.getSubType() < 0) - throw new TypeConversionException(BINARY_STREAM_CONVERSION_ERROR); - Blob blob = getBlob(); if (blob == null) return null; return blob.getBinaryStream(); } + @Override public byte[] getBytes() throws SQLException { - // getBytes() is not defined for BLOB types, only for BINARY -// if (field.sqlsubtype < 0) -// throw (SQLException)createException( -// BYTES_CONVERSION_ERROR); - return getBytesInternal(); } @@ -133,6 +125,7 @@ public byte[] getBytesInternal() throws SQLException { } } + @Override public byte[] getCachedData() throws SQLException { if (isNull()) { return bytes; @@ -140,12 +133,15 @@ public byte[] getCachedData() throws SQLException { return getBytesInternal(); } + @Override public FBFlushableField.CachedObject getCachedObject() throws SQLException { if (isNull()) return new FBFlushableField.CachedObject(bytes, binaryStream, characterStream, length); - return new CachedObject(getBytesInternal(), null, null, 0); + final byte[] bytes = getBytesInternal(); + return new CachedObject(bytes, null, null, bytes.length); } + @Override public void setCachedObject(FBFlushableField.CachedObject cachedObject) throws SQLException { // setNull() to reset field to empty state setNull(); @@ -155,6 +151,7 @@ public void setCachedObject(FBFlushableField.CachedObject cachedObject) throws S length = cachedObject.length; } + @Override public String getString() throws SQLException { // getString() is not defined for BLOB fields, only for BINARY if (fieldDescriptor.getSubType() < 0) @@ -170,11 +167,8 @@ public String getString() throws SQLException { //--- setXXX methods - public void setAsciiStream(InputStream in, int length) throws SQLException { - setBinaryStream(in, length); - } - - public void setCharacterStream(Reader in, int length) throws SQLException { + @Override + public void setCharacterStream(Reader in, long length) throws SQLException { // setNull() to reset field to empty state setNull(); if (in != null) { @@ -183,7 +177,8 @@ public void setCharacterStream(Reader in, int length) throws SQLException { } } - public void setBinaryStream(InputStream in, int length) throws SQLException { + @Override + public void setBinaryStream(InputStream in, long length) throws SQLException { // setNull() to reset field to empty state setNull(); if (in != null) { @@ -192,16 +187,18 @@ public void setBinaryStream(InputStream in, int length) throws SQLException { } } + @Override public void flushCachedData() throws SQLException { - if (binaryStream != null) + if (binaryStream != null) { copyBinaryStream(binaryStream, length); - else if (characterStream != null) + } else if (characterStream != null) { copyCharacterStream(characterStream, length, getDatatypeCoder().getEncodingFactory().getDefaultEncoding().getCharsetName()); - else if (bytes != null) - copyBytes(bytes, length); - else if (blob == null) + } else if (bytes != null) { + copyBytes(bytes, (int) length); + } else if (blob == null) { setNull(); + } this.characterStream = null; this.binaryStream = null; @@ -209,13 +206,13 @@ else if (blob == null) this.length = 0; } - private void copyBinaryStream(InputStream in, int length) throws SQLException { + private void copyBinaryStream(InputStream in, long length) throws SQLException { FBBlob blob = new FBBlob(gdsHelper); blob.copyStream(in, length); setFieldData(getDatatypeCoder().encodeLong(blob.getBlobId())); } - private void copyCharacterStream(Reader in, int length, String encoding) throws SQLException { + private void copyCharacterStream(Reader in, long length, String encoding) throws SQLException { FBBlob blob = new FBBlob(gdsHelper); blob.copyCharacterStream(in, length, encoding); setFieldData(getDatatypeCoder().encodeLong(blob.getBlobId())); @@ -227,6 +224,7 @@ private void copyBytes(byte[] bytes, int length) throws SQLException { setFieldData(getDatatypeCoder().encodeLong(blob.getBlobId())); } + @Override public void setBytes(byte[] value) throws SQLException { // setNull() to reset field to empty state setNull(); @@ -236,6 +234,7 @@ public void setBytes(byte[] value) throws SQLException { } } + @Override public void setString(String value) throws SQLException { if (value == null) { setNull(); @@ -246,6 +245,7 @@ public void setString(String value) throws SQLException { mappingPath)); } + @Override public void setBlob(FBBlob blob) throws SQLException { // setNull() to reset field to empty state setNull(); @@ -253,11 +253,13 @@ public void setBlob(FBBlob blob) throws SQLException { this.blob = blob; } + @Override public void setClob(FBClob clob) throws SQLException { FBBlob blob = clob.getWrappedBlob(); setBlob(blob); } + @Override public void setNull() { super.setNull(); try { diff --git a/src/main/org/firebirdsql/jdbc/field/FBField.java b/src/main/org/firebirdsql/jdbc/field/FBField.java index f8ee161d5c..629834b5b6 100644 --- a/src/main/org/firebirdsql/jdbc/field/FBField.java +++ b/src/main/org/firebirdsql/jdbc/field/FBField.java @@ -54,7 +54,6 @@ public abstract class FBField { static final String DATE_CONVERSION_ERROR = "Error converting to date."; static final String TIME_CONVERSION_ERROR = "Error converting to time."; static final String TIMESTAMP_CONVERSION_ERROR = "Error converting to timestamp."; - static final String ASCII_STREAM_CONVERSION_ERROR = "Error converting to ascii stream."; static final String BINARY_STREAM_CONVERSION_ERROR = "Error converting to binary stream."; static final String CHARACTER_STREAM_CONVERSION_ERROR = "Error converting to character stream."; static final String BYTES_CONVERSION_ERROR = "Error converting to array of bytes."; @@ -514,10 +513,6 @@ public T getObject(Class type) throws SQLException { return getObjectConverter().getObject(this, type); } - public InputStream getAsciiStream() throws SQLException { - throw new TypeConversionException(FBField.ASCII_STREAM_CONVERSION_ERROR); - } - public InputStream getBinaryStream() throws SQLException { throw new TypeConversionException(FBField.BINARY_STREAM_CONVERSION_ERROR); } @@ -626,8 +621,12 @@ public void setObject(Object value) throws SQLException { if (value instanceof FBBlob) { setBlob((FBBlob) value); } else { - setBinaryStream(((Blob) value).getBinaryStream(), (int) ((Blob) value).length()); + setBinaryStream(((Blob) value).getBinaryStream()); } + } else if (value instanceof InputStream) { + setBinaryStream((InputStream) value); + } else if (value instanceof Reader) { + setCharacterStream((Reader) value); } else if (value instanceof Boolean) { setBoolean((Boolean) value); } else if (value instanceof Byte) { @@ -659,16 +658,28 @@ public void setObject(Object value) throws SQLException { } } - public void setAsciiStream(InputStream in, int length) throws SQLException { - throw new TypeConversionException(FBField.ASCII_STREAM_CONVERSION_ERROR); + public void setBinaryStream(InputStream in, long length) throws SQLException { + throw new TypeConversionException(FBField.BINARY_STREAM_CONVERSION_ERROR); } - public void setBinaryStream(InputStream in, int length) throws SQLException { - throw new TypeConversionException(FBField.BINARY_STREAM_CONVERSION_ERROR); + public final void setBinaryStream(InputStream in) throws SQLException { + setBinaryStream(in, -1L); + } + + public final void setBinaryStream(InputStream in, int length) throws SQLException { + setBinaryStream(in, (long) length); + } + + public void setCharacterStream(Reader in, long length) throws SQLException { + throw new TypeConversionException(FBField.CHARACTER_STREAM_CONVERSION_ERROR); + } + + public final void setCharacterStream(Reader in) throws SQLException { + setCharacterStream(in, -1L); } - public void setCharacterStream(Reader in, int length) throws SQLException { - throw new TypeConversionException(FBField.ASCII_STREAM_CONVERSION_ERROR); + public final void setCharacterStream(Reader in, int length) throws SQLException { + setCharacterStream(in, (long) length); } public void setBytes(byte[] value) throws SQLException { diff --git a/src/main/org/firebirdsql/jdbc/field/FBFlushableField.java b/src/main/org/firebirdsql/jdbc/field/FBFlushableField.java index f5bea60ead..d4b0f8e52f 100644 --- a/src/main/org/firebirdsql/jdbc/field/FBFlushableField.java +++ b/src/main/org/firebirdsql/jdbc/field/FBFlushableField.java @@ -35,9 +35,9 @@ class CachedObject { public byte[] bytes; public InputStream binaryStream; public Reader characterStream; - public int length; + public long length; - public CachedObject(byte[] bytes, InputStream binaryStream, Reader characterStream, int length) { + public CachedObject(byte[] bytes, InputStream binaryStream, Reader characterStream, long length) { this.bytes = bytes; this.binaryStream = binaryStream; this.characterStream = characterStream; diff --git a/src/main/org/firebirdsql/jdbc/field/FBLongVarCharField.java b/src/main/org/firebirdsql/jdbc/field/FBLongVarCharField.java index fd9128d577..9a17b0f822 100644 --- a/src/main/org/firebirdsql/jdbc/field/FBLongVarCharField.java +++ b/src/main/org/firebirdsql/jdbc/field/FBLongVarCharField.java @@ -47,7 +47,7 @@ public class FBLongVarCharField extends FBStringField implements FBFlushableFiel private FBBlob blob; // Rather then hold cached data in the XSQLDAVar we will hold it in here. - private int length; + private long length; private byte[] bytes; private InputStream binaryStream; private Reader characterStream; @@ -56,7 +56,8 @@ public class FBLongVarCharField extends FBStringField implements FBFlushableFiel throws SQLException { super(fieldDescriptor, dataProvider, requiredType); } - + + @Override public void close() throws SQLException { try { if (blob != null) blob.free(); @@ -72,7 +73,8 @@ public void close() throws SQLException { length = 0; } } - + + @Override public Blob getBlob() throws SQLException { if (blob != null) return blob; if (isNull()) return null; @@ -80,19 +82,22 @@ public Blob getBlob() throws SQLException { blob = new FBBlob(gdsHelper, getDatatypeCoder().decodeLong(getFieldData())); return blob; } - + + @Override public Clob getClob() throws SQLException { FBBlob blob = (FBBlob) getBlob(); if (blob == null) return null; return new FBClob(blob); } - + + @Override public InputStream getBinaryStream() throws SQLException { Blob blob = getBlob(); if (blob == null) return null; return blob.getBinaryStream(); } - + + @Override public byte[] getBytes() throws SQLException { final Blob blob = getBlob(); if (blob == null) return null; @@ -112,11 +117,13 @@ public byte[] getBytes() throws SQLException { } } + @Override public byte[] getCachedData() throws SQLException { if (isNull()) return bytes; return getBytes(); } - + + @Override public FBFlushableField.CachedObject getCachedObject() throws SQLException { if (isNull()) { return new CachedObject(bytes, binaryStream, characterStream, length); @@ -125,7 +132,8 @@ public FBFlushableField.CachedObject getCachedObject() throws SQLException { final byte[] bytes = getBytes(); return new CachedObject(bytes, null, null, bytes.length); } - + + @Override public void setCachedObject(FBFlushableField.CachedObject cachedObject) throws SQLException { bytes = cachedObject.bytes; binaryStream = cachedObject.binaryStream; @@ -133,6 +141,7 @@ public void setCachedObject(FBFlushableField.CachedObject cachedObject) throws S length = cachedObject.length; } + @Override public String getString() throws SQLException { byte[] data = getBytes(); if (data == null) return null; @@ -140,19 +149,22 @@ public String getString() throws SQLException { return getDatatypeCoder().decodeString(data, encodingDefinition.getEncoding(), mappingPath); } + @Override public void setBlob(FBBlob blob) throws SQLException { // setNull() to reset field to empty state setNull(); setFieldData(getDatatypeCoder().encodeLong(blob.getBlobId())); this.blob = blob; } - + + @Override public void setClob(FBClob clob) throws SQLException { FBBlob blob = clob.getWrappedBlob(); setBlob(blob); } - public void setCharacterStream(Reader in, int length) throws SQLException { + @Override + public void setCharacterStream(Reader in, long length) throws SQLException { // setNull() to reset field to empty state setNull(); if (in != null) { @@ -161,6 +173,7 @@ public void setCharacterStream(Reader in, int length) throws SQLException { } } + @Override public void setString(String value) throws SQLException { // setNull() to reset field to empty state setNull(); @@ -169,6 +182,7 @@ public void setString(String value) throws SQLException { } } + @Override public void setBytes(byte[] value) throws SQLException { // setNull() to reset field to empty state setNull(); @@ -178,7 +192,8 @@ public void setBytes(byte[] value) throws SQLException { } } - public void setBinaryStream(InputStream in, int length) throws SQLException { + @Override + public void setBinaryStream(InputStream in, long length) throws SQLException { // setNull() to reset field to empty state setNull(); if (in != null) { @@ -187,13 +202,14 @@ public void setBinaryStream(InputStream in, int length) throws SQLException { } } + @Override public void flushCachedData() throws SQLException { if (binaryStream != null) { copyBinaryStream(this.binaryStream, this.length); } else if (characterStream != null) { copyCharacterStream(characterStream, length, encodingDefinition.getJavaEncodingName()); } else if (bytes != null) { - copyBytes(bytes, length); + copyBytes(bytes, (int) length); } else if (blob == null) { setNull(); } @@ -220,13 +236,13 @@ public void setNull() { } } - private void copyBinaryStream(InputStream in, int length) throws SQLException { + private void copyBinaryStream(InputStream in, long length) throws SQLException { FBBlob blob = new FBBlob(gdsHelper); blob.copyStream(in, length); setFieldData(getDatatypeCoder().encodeLong(blob.getBlobId())); } - private void copyCharacterStream(Reader in, int length, String encoding) throws SQLException { + private void copyCharacterStream(Reader in, long length, String encoding) throws SQLException { FBBlob blob = new FBBlob(gdsHelper); blob.copyCharacterStream(in, length, encoding); setFieldData(getDatatypeCoder().encodeLong(blob.getBlobId())); diff --git a/src/main/org/firebirdsql/jdbc/field/FBNullField.java b/src/main/org/firebirdsql/jdbc/field/FBNullField.java index ff0646f25c..b65cbc0a92 100644 --- a/src/main/org/firebirdsql/jdbc/field/FBNullField.java +++ b/src/main/org/firebirdsql/jdbc/field/FBNullField.java @@ -126,11 +126,6 @@ public InputStream getBinaryStream() throws SQLException { return null; } - public InputStream getAsciiStream() throws SQLException { - checkNull(); - return null; - } - public byte[] getBytes() throws SQLException { checkNull(); return null; @@ -210,11 +205,7 @@ public void setBoolean(boolean value) throws SQLException { // ----- setXXXStream code - public void setAsciiStream(InputStream in, int length) throws SQLException { - setBinaryStream(in, length); - } - - public void setBinaryStream(InputStream in, int length) throws SQLException { + public void setBinaryStream(InputStream in, long length) throws SQLException { if (in == null) { setNull(); return; @@ -223,7 +214,7 @@ public void setBinaryStream(InputStream in, int length) throws SQLException { setDummyObject(); } - public void setCharacterStream(Reader in, int length) throws SQLException { + public void setCharacterStream(Reader in, long length) throws SQLException { if (in == null) { setNull(); return; diff --git a/src/main/org/firebirdsql/jdbc/field/FBStringField.java b/src/main/org/firebirdsql/jdbc/field/FBStringField.java index 3f7586dab6..732966fad5 100644 --- a/src/main/org/firebirdsql/jdbc/field/FBStringField.java +++ b/src/main/org/firebirdsql/jdbc/field/FBStringField.java @@ -22,6 +22,7 @@ import org.firebirdsql.gds.ISCConstants; import org.firebirdsql.gds.impl.GDSHelper; import org.firebirdsql.gds.ng.fields.FieldDescriptor; +import org.firebirdsql.jdbc.FBDriverNotCapableException; import org.firebirdsql.util.IOUtils; import java.io.ByteArrayInputStream; @@ -148,7 +149,7 @@ public long getLong() throws SQLException { public BigDecimal getBigDecimal() throws SQLException { if (isNull()) return null; - /**@todo check what exceptions can be thrown here */ + /*@todo check what exceptions can be thrown here */ return new BigDecimal(getString().trim()); } @@ -198,10 +199,6 @@ public InputStream getBinaryStream() throws SQLException { return new ByteArrayInputStream(getFieldData()); } - public InputStream getAsciiStream() throws SQLException { - return getBinaryStream(); - } - public byte[] getBytes() throws SQLException { if (isNull()) return null; // protect against unintentional modification of cached or shared byte-arrays (eg in DatabaseMetaData) @@ -297,31 +294,37 @@ public void setString(String value) throws SQLException { //----- setXXXStream code - public void setAsciiStream(InputStream in, int length) throws SQLException { - setBinaryStream(in, length); - } - - public void setBinaryStream(InputStream in, int length) throws SQLException { + public void setBinaryStream(InputStream in, long length) throws SQLException { if (in == null) { setNull(); return; } + // TODO More specific value + if (length > Integer.MAX_VALUE) { + throw new FBDriverNotCapableException("Only length <= Integer.MAX_VALUE supported"); + } + try { - setBytes(IOUtils.toBytes(in, length)); + setBytes(IOUtils.toBytes(in, (int) length)); } catch (IOException ioex) { throw new TypeConversionException(BINARY_STREAM_CONVERSION_ERROR); } } - public void setCharacterStream(Reader in, int length) throws SQLException { + public void setCharacterStream(Reader in, long length) throws SQLException { if (in == null) { setNull(); return; } + // TODO More specific value + if (length > Integer.MAX_VALUE) { + throw new FBDriverNotCapableException("Only length <= Integer.MAX_VALUE supported"); + } + try { - setString(IOUtils.toString(in, length)); + setString(IOUtils.toString(in, (int) length)); } catch (IOException ioex) { throw new TypeConversionException(CHARACTER_STREAM_CONVERSION_ERROR); } diff --git a/src/main/org/firebirdsql/util/IOUtils.java b/src/main/org/firebirdsql/util/IOUtils.java index 7185439f19..fa2b70b733 100644 --- a/src/main/org/firebirdsql/util/IOUtils.java +++ b/src/main/org/firebirdsql/util/IOUtils.java @@ -33,6 +33,9 @@ private IOUtils() { } public static byte[] toBytes(final InputStream in, final int length) throws IOException { + if (length == -1) { + return toBytes(in); + } final ByteArrayOutputStream out = new ByteArrayOutputStream(); final byte[] buff = new byte[Math.min(4096, length)]; int counter; @@ -44,7 +47,20 @@ public static byte[] toBytes(final InputStream in, final int length) throws IOEx return out.toByteArray(); } + public static byte[] toBytes(final InputStream in) throws IOException { + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + final byte[] buff = new byte[4096]; + int counter; + while ((counter = in.read(buff, 0, buff.length)) != -1) { + out.write(buff, 0, counter); + } + return out.toByteArray(); + } + public static String toString(final Reader in, final int length) throws IOException { + if (length == -1) { + return toString(in); + } final StringWriter out = new StringWriter(); final char[] buff = new char[Math.min(4096, length)]; int counter; @@ -55,4 +71,14 @@ public static String toString(final Reader in, final int length) throws IOExcept } return out.toString(); } + + public static String toString(final Reader in) throws IOException { + final StringWriter out = new StringWriter(); + final char[] buff = new char[4096]; + int counter; + while ((counter = in.read(buff, 0, buff.length)) != -1) { + out.write(buff, 0, counter); + } + return out.toString(); + } } diff --git a/src/test/org/firebirdsql/jdbc/TestFBResultSet.java b/src/test/org/firebirdsql/jdbc/TestFBResultSet.java index de4ede36b8..ee4e272e97 100644 --- a/src/test/org/firebirdsql/jdbc/TestFBResultSet.java +++ b/src/test/org/firebirdsql/jdbc/TestFBResultSet.java @@ -25,6 +25,8 @@ import org.junit.Ignore; import org.junit.Test; +import java.io.ByteArrayInputStream; +import java.io.StringReader; import java.sql.*; import java.util.Properties; import java.util.Random; @@ -47,12 +49,13 @@ public class TestFBResultSet extends FBJUnit4TestBase { public static final String CREATE_TABLE_STATEMENT = "CREATE TABLE test_table(" + - " id INTEGER NOT NULL PRIMARY KEY, " + - " str VARCHAR(10), " + - " long_str VARCHAR(255), " + - " very_long_str VARCHAR(20000), " + - " blob_str BLOB SUB_TYPE 1, " + - " \"CamelStr\" VARCHAR(255)" + + " id INTEGER NOT NULL PRIMARY KEY," + + " str VARCHAR(10)," + + " long_str VARCHAR(255)," + + " very_long_str VARCHAR(20000)," + + " blob_str BLOB SUB_TYPE TEXT," + + " \"CamelStr\" VARCHAR(255)," + + " blob_bin BLOB SUB_TYPE BINARY" + ")"; public static final String SELECT_TEST_TABLE = @@ -1025,4 +1028,100 @@ public void testClosedOnRollback() throws Exception { assertTrue("Expected resultset to be closed", rs.isClosed()); } } + + @Test + public void testUpdatableBinaryStream() throws Exception { + executeCreateTable(connection, CREATE_TABLE_STATEMENT); + + try (Statement stmt = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE)) { + stmt.executeUpdate("insert into test_table(id, blob_bin) values (1, null)"); + + byte[] value = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; + connection.setAutoCommit(false); + try (ResultSet rs = stmt.executeQuery("select id, blob_bin from test_table")) { + assertTrue(rs.next()); + ByteArrayInputStream bais = new ByteArrayInputStream(value); + rs.updateBinaryStream(2, bais); + rs.updateRow(); + } + + try (ResultSet rs = stmt.executeQuery("select id, blob_bin from test_table")) { + assertTrue(rs.next()); + + assertArrayEquals(value, rs.getBytes(2)); + } + } + } + + @Test + public void testUpdatableBinaryStream_intLength() throws Exception { + executeCreateTable(connection, CREATE_TABLE_STATEMENT); + + try (Statement stmt = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE)) { + stmt.executeUpdate("insert into test_table(id, blob_bin) values (1, null)"); + + byte[] value = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; + connection.setAutoCommit(false); + try (ResultSet rs = stmt.executeQuery("select id, blob_bin from test_table")) { + assertTrue(rs.next()); + ByteArrayInputStream bais = new ByteArrayInputStream(value); + rs.updateBinaryStream(2, bais, 5); + rs.updateRow(); + } + + try (ResultSet rs = stmt.executeQuery("select id, blob_bin from test_table")) { + assertTrue(rs.next()); + + assertArrayEquals(new byte[] { 1, 2, 3, 4, 5 }, rs.getBytes(2)); + } + } + } + + @Test + public void testUpdatableCharacterStream() throws Exception { + executeCreateTable(connection, CREATE_TABLE_STATEMENT); + + try (Statement stmt = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE)) { + stmt.executeUpdate("insert into test_table(id, blob_str) values (1, null)"); + + String value = "String for testing"; + connection.setAutoCommit(false); + try (ResultSet rs = stmt.executeQuery("select id, blob_str from test_table")) { + assertTrue(rs.next()); + StringReader stringReader = new StringReader(value); + rs.updateCharacterStream(2, stringReader); + rs.updateRow(); + } + + try (ResultSet rs = stmt.executeQuery("select id, blob_str from test_table")) { + assertTrue(rs.next()); + + assertEquals(value, rs.getString(2)); + } + } + } + + @Test + public void testUpdatableCharacterStream_intLength() throws Exception { + executeCreateTable(connection, CREATE_TABLE_STATEMENT); + + try (Statement stmt = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE)) { + stmt.executeUpdate("insert into test_table(id, blob_str) values (1, null)"); + + String value = "String for testing"; + connection.setAutoCommit(false); + try (ResultSet rs = stmt.executeQuery("select id, blob_str from test_table")) { + assertTrue(rs.next()); + StringReader stringReader = new StringReader(value); + rs.updateCharacterStream(2, stringReader, 6); + rs.updateRow(); + } + + try (ResultSet rs = stmt.executeQuery("select id, blob_str from test_table")) { + assertTrue(rs.next()); + + assertEquals("String", rs.getString(2)); + } + } + } } diff --git a/src/test/org/firebirdsql/jdbc/field/BaseJUnit4TestFBField.java b/src/test/org/firebirdsql/jdbc/field/BaseJUnit4TestFBField.java index 3d1a65213a..ad8d65ba96 100644 --- a/src/test/org/firebirdsql/jdbc/field/BaseJUnit4TestFBField.java +++ b/src/test/org/firebirdsql/jdbc/field/BaseJUnit4TestFBField.java @@ -112,18 +112,6 @@ public void getArrayNonNull() throws SQLException { field.getArray(); } - @Test - public void getAsciiStreamNonNull() throws Exception { - expectedException.expect(TypeConversionException.class); - field.getAsciiStream(); - } - - @Test - public void setAsciiStreamNonNull() throws Exception { - expectedException.expect(TypeConversionException.class); - field.setAsciiStream(context.mock(InputStream.class), 100); - } - @Test public void getBigDecimalNonNull() throws SQLException { expectedException.expect(TypeConversionException.class); diff --git a/src/test/org/firebirdsql/jdbc/field/TestFBBinaryField.java b/src/test/org/firebirdsql/jdbc/field/TestFBBinaryField.java index 03eba6959a..f96ccd3ee5 100644 --- a/src/test/org/firebirdsql/jdbc/field/TestFBBinaryField.java +++ b/src/test/org/firebirdsql/jdbc/field/TestFBBinaryField.java @@ -121,27 +121,6 @@ public void setStringNonNull() throws SQLException { field.setString(string); } - @Test - @Override - public void getAsciiStreamNonNull() throws Exception { - final byte[] bytes = getRandomBytes(); - toReturnValueExpectations(bytes); - - InputStream stream = field.getAsciiStream(); - - assertArrayEquals(bytes, streamToBytes(stream)); - } - - @Test - @Override - public void setAsciiStreamNonNull() throws SQLException { - final byte[] bytes = getRandomBytes(); - setValueExpectations(bytes); - InputStream stream = new ByteArrayInputStream(bytes); - - field.setAsciiStream(stream, FIELD_LENGTH); - } - @Test @Override public void getBinaryStreamNonNull() throws Exception { diff --git a/src/test/org/firebirdsql/jdbc/field/TestFBNullField.java b/src/test/org/firebirdsql/jdbc/field/TestFBNullField.java index 396f8902a8..5974407122 100644 --- a/src/test/org/firebirdsql/jdbc/field/TestFBNullField.java +++ b/src/test/org/firebirdsql/jdbc/field/TestFBNullField.java @@ -69,22 +69,6 @@ public void setUp() throws Exception { // TODO Investigate necessity to test getters, it looks like FBNullField is only used for parameters and never for ResultSet columns - @Test - public void setAsciiStreamNull() throws SQLException { - setNullExpectations(); - - field.setAsciiStream(null, 7); - } - - @Test - public void setAsciiStreanNonNull() throws SQLException { - setNonNullExpectations(); - InputStream in = context.mock(InputStream.class); - // TODO Read and/or close expectation? - - field.setAsciiStream(in, 15); - } - @Test public void setBigDecimalNull() throws SQLException { setNullExpectations(); diff --git a/src/test/org/firebirdsql/jdbc/field/TestFBStringField.java b/src/test/org/firebirdsql/jdbc/field/TestFBStringField.java index b2bf9dc505..78672f393d 100644 --- a/src/test/org/firebirdsql/jdbc/field/TestFBStringField.java +++ b/src/test/org/firebirdsql/jdbc/field/TestFBStringField.java @@ -83,38 +83,7 @@ public void getString() throws SQLException { */ } - @Test - @Override - public void getAsciiStreamNonNull() throws Exception { - toReturnStringExpectations(TEST_STRING_SHORT, encoding); - String fromStream = new String(IOUtils.toBytes(field.getAsciiStream(), Integer.MAX_VALUE)); - assertEquals("ASCII stream values test failure", TEST_STRING_SHORT, fromStream.trim()); - } - - @Test - public void getAsciiStreamNull() throws Exception { - toReturnNullExpectations(); - - assertNull("Expected null ascii stream", field.getAsciiStream()); - } - - @Test - @Override - public void setAsciiStreamNonNull() throws Exception { - setStringExpectations(TEST_STRING_SHORT, encoding); - byte[] bytes = TEST_STRING_SHORT.getBytes(); - - field.setAsciiStream(new ByteArrayInputStream(bytes), bytes.length); - } - - @Test - public void setAsciiStream_tooLong() throws Exception { - expectedException.expect(java.sql.DataTruncation.class); - byte[] bytes = TEST_STRING_LONG.getBytes(); - field.setAsciiStream(new ByteArrayInputStream(bytes), bytes.length); - } - - @Test + @Test public void getBigDecimalNull() throws SQLException { toReturnNullExpectations();