org.hsqldb.jdbc.JDBCClobClient Maven / Gradle / Ivy
Show all versions of sqltool Show documentation
/* Copyright (c) 2001-2021, The HSQL Development Group
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of the HSQL Development Group nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.hsqldb.jdbc;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CoderResult;
import java.nio.charset.CodingErrorAction;
import java.nio.charset.IllegalCharsetNameException;
import java.sql.Clob;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import org.hsqldb.HsqlException;
import org.hsqldb.SessionInterface;
import org.hsqldb.error.ErrorCode;
import org.hsqldb.types.ClobDataID;
import org.hsqldb.types.ClobInputStream;
/**
* A wrapper for HSQLDB ClobData objects.
*
* Instances of this class are returned by calls to ResultSet methods.
*
* @author Fred Toussi (fredt@users dot sourceforge.net)
* @version 2.4.0
* @since JDK 1.2, HSQLDB 1.9.0
*/
public class JDBCClobClient implements Clob {
/**
* Retrieves the CLOB
value designated by this
* Clob
object as an ascii stream.
*
* @return a java.io.InputStream
object containing the
* CLOB
data
* @throws SQLException if there is an error accessing the
* CLOB
value
*/
public synchronized InputStream getAsciiStream() throws SQLException {
checkClosed();
return new InputStream() {
private final byte[] oneChar = new byte[1];
private boolean m_closed;
// better size than 8192 for network connections.
private CharBuffer m_charBuffer =
(CharBuffer) CharBuffer.allocate(64 * 1024).flip();
private ByteBuffer m_byteBuffer = ByteBuffer.allocate(1024);
private Charset m_charset = charsetForName("US-ASCII");
private CharsetEncoder m_encoder =
m_charset.newEncoder().onMalformedInput(
CodingErrorAction.REPLACE).onUnmappableCharacter(
CodingErrorAction.REPLACE);
private Reader m_reader = clob.getCharacterStream(session);
public int read() throws IOException {
if (isEOF()) {
return -1;
}
synchronized (oneChar) {
int charsRead = read(oneChar, 0, 1);
return charsRead == 1 ? oneChar[0]
: -1;
}
}
public int read(byte[] b, int off, int len) throws IOException {
checkClosed();
if (isEOF()) {
return -1;
}
final CharBuffer cb = m_charBuffer;
//
int charsRead;
int bytesRead;
if (cb.remaining() == 0) {
cb.clear();
charsRead = m_reader.read(cb);
cb.flip();
if (charsRead < 0) {
setEOF();
return -1;
} else if (charsRead == 0) {
return 0;
}
}
final ByteBuffer bb = (m_byteBuffer.capacity() < len)
? ByteBuffer.allocate(len)
: m_byteBuffer;
// Since ASCII is single-byte, restrict encoder character consumption
// to at most 'len' characters' to produce at most len ASCII
// characters
int cbLimit = cb.limit();
int cbPosition = cb.position();
cb.limit(cbPosition + len);
bb.clear();
int bbPosition = bb.position();
CoderResult result = m_encoder.encode(cb, bb, false);
if (bbPosition == bb.position() && result.isUnderflow()) {
// surrogate character time
cb.limit(cb.limit() + 1);
m_encoder.encode(cb, bb, false);
}
// Restore the old limit so the buffer gets topped up
// when required.
cb.limit(cbLimit);
bb.flip();
bytesRead = bb.limit();
if (bytesRead == 0) {
setEOF();
return -1;
}
m_byteBuffer = bb;
bb.get(b, off, bytesRead);
return bytesRead;
}
public void close() throws IOException {
boolean isClosed = m_closed;
if (!isClosed) {
m_closed = true;
m_charBuffer = null;
m_charset = null;
m_encoder = null;
try {
m_reader.close();
} catch (Exception ex) {}
}
}
private boolean isEOF() {
final Reader reader = m_reader;
return (reader == null);
}
private void setEOF() {
final Reader reader = m_reader;
if (reader != null) {
try {
reader.close();
} catch (IOException iOException) {}
}
m_reader = null;
}
private void checkClosed() throws IOException {
if (JDBCClobClient.this.isClosed()) {
try {
this.close();
} catch (Exception ex) {}
}
if (m_closed) {
throw new IOException("The stream is closed.");
}
}
};
}
/**
* Retrieves the CLOB
value designated by this
* Clob
object as a java.io.Reader
object (or
* as a stream of characters).
*
* @return a java.io.Reader
object containing the
* CLOB
data
* @throws SQLException if there is an error accessing the
* CLOB
value
*/
public synchronized Reader getCharacterStream() throws SQLException {
checkClosed();
return new ClobInputStream(session, clob, 0, length());
}
/**
* Retrieves a copy of the specified substring in the CLOB
* value designated by this Clob
object.
*
* @param pos the first character of the substring to be extracted. The
* first character is at position 1.
* @param length the number of consecutive characters to be copied
* @return a String
that is the specified substring in the
* CLOB
value designated by this Clob
object
* @throws SQLException if there is an error accessing the
* CLOB
value
*/
public synchronized String getSubString(long pos,
int length) throws SQLException {
checkClosed();
if (!isInLimits(Long.MAX_VALUE, pos - 1, length)) {
throw JDBCUtil.outOfRangeArgument();
}
try {
return clob.getSubString(session, pos - 1, length);
} catch (HsqlException e) {
throw JDBCUtil.sqlException(e);
}
}
/**
* Retrieves the number of characters in the CLOB
value
* designated by this Clob
object.
*
* @return length of the CLOB
in characters
* @throws SQLException if there is an error accessing the length of the
* CLOB
value
*/
public synchronized long length() throws SQLException {
checkClosed();
try {
return clob.length(session);
} catch (HsqlException e) {
throw JDBCUtil.sqlException(e);
}
}
/**
* Retrieves the character position at which the specified substring
* searchstr
appears in the SQL CLOB
value
* represented by this Clob
object.
*
* @param searchstr the substring for which to search
* @param start the position at which to begin searching; the first
* position is 1
* @return the position at which the substring appears or -1 if it is
* not present; the first position is 1
* @throws SQLException if there is an error accessing the
* CLOB
value
*/
public synchronized long position(String searchstr,
long start) throws SQLException {
checkClosed();
if (!isInLimits(Long.MAX_VALUE, start - 1, 0)) {
throw JDBCUtil.outOfRangeArgument();
}
try {
return clob.position(session, searchstr, start - 1);
} catch (HsqlException e) {
throw JDBCUtil.sqlException(e);
}
}
/**
* Retrieves the character position at which the specified
* Clob
object searchstr
appears in this
* Clob
object.
*
* @param searchstr the Clob
object for which to search
* @param start the position at which to begin searching; the first
* position is 1
* @return the position at which the Clob
object appears or
* -1 if it is not present; the first position is 1
* @throws SQLException if there is an error accessing the
* CLOB
value
*/
public synchronized long position(Clob searchstr,
long start) throws SQLException {
checkClosed();
if (!isInLimits(Long.MAX_VALUE, start - 1, 0)) {
throw JDBCUtil.outOfRangeArgument();
}
if (searchstr instanceof JDBCClobClient) {
ClobDataID searchClob = ((JDBCClobClient) searchstr).clob;
try {
return clob.position(session, searchClob, start - 1);
} catch (HsqlException e) {
throw JDBCUtil.sqlException(e);
}
}
return position(searchstr.getSubString(1, (int) searchstr.length()),
start);
}
/**
* Retrieves a stream to be used to write Ascii characters to the
* CLOB
value that this Clob
object represents,
* starting at position pos
.
*
* @param pos the position at which to start writing to this
* CLOB
object
* @return the stream to which ASCII encoded characters can be written
* @throws SQLException if there is an error accessing the
* CLOB
value
*/
public synchronized OutputStream setAsciiStream(final long pos)
throws SQLException {
checkClosed();
if (pos < 1) {
throw JDBCUtil.outOfRangeArgument("pos: " + pos);
}
if (!isWritable) {
throw JDBCUtil.notUpdatableColumn();
}
startUpdate();
return new OutputStream() {
private long m_position = pos - 1;
private Charset m_charset = charsetForName("US-ASCII");
private CharsetDecoder m_decoder =
m_charset.newDecoder().onMalformedInput(
CodingErrorAction.REPLACE).onUnmappableCharacter(
CodingErrorAction.REPLACE);
private CharBuffer m_charBuffer = CharBuffer.allocate(64 * 1024);
private ByteBuffer m_byteBuffer = ByteBuffer.allocate(1024);
private final byte[] oneByte = new byte[1];
private boolean m_closed;
public void write(int b) throws IOException {
synchronized (oneByte) {
oneByte[0] = (byte) b;
this.write(oneByte, 0, 1);
}
}
public void write(byte[] b, int off, int len) throws IOException {
checkClosed();
final ByteBuffer bb = (m_byteBuffer.capacity() < len)
? ByteBuffer.allocate(len)
: m_byteBuffer;
if (m_charBuffer.remaining() < len) {
flush0();
}
final CharBuffer cb = m_charBuffer.capacity() < len
? CharBuffer.allocate(len)
: m_charBuffer;
bb.clear();
bb.put(b, off, len);
bb.flip();
m_decoder.decode(bb, cb, false);
if (cb.remaining() == 0) {
flush();
}
}
public void flush() throws IOException {
checkClosed();
flush0();
}
public void close() throws IOException {
if (!m_closed) {
try {
flush0();
} finally {
m_closed = true;
m_byteBuffer = null;
m_charBuffer = null;
m_charset = null;
m_decoder = null;
}
}
}
private void checkClosed() throws IOException {
if (JDBCClobClient.this.isClosed()) {
try {
close();
} catch (Exception ex) {}
}
if (m_closed) {
throw new IOException("The stream is closed.");
}
}
private void flush0() throws IOException {
final CharBuffer cb = m_charBuffer;
cb.flip();
final char[] chars = new char[cb.length()];
cb.get(chars);
cb.clear();
try {
clob.setChars(session, m_position, chars, 0, chars.length);
} catch (Exception e) {
throw new IOException(e.toString());
}
m_position += chars.length;
}
};
}
/**
* Retrieves a stream to be used to write a stream of Unicode characters
* to the CLOB
value that this Clob
object
* represents, at position pos
.
*
* @param pos the position at which to start writing to the
* CLOB
value
* @return a stream to which Unicode encoded characters can be written
* @throws SQLException if there is an error accessing the
* CLOB
value
*/
public synchronized Writer setCharacterStream(final long pos)
throws SQLException {
checkClosed();
if (pos < 1) {
throw JDBCUtil.outOfRangeArgument("pos: " + pos);
}
if (!isWritable) {
throw JDBCUtil.notUpdatableColumn();
}
startUpdate();
return new Writer() {
private long m_clobPosition = pos - 1;
private boolean m_closed;
public void write(char[] cbuf, int off,
int len) throws IOException {
checkClosed();
clob.setChars(session, m_clobPosition, cbuf, off, len);
m_clobPosition += len;
}
public void flush() throws IOException {
// no-op
}
@Override
public void close() throws IOException {
m_closed = true;
}
private void checkClosed() throws IOException {
if (m_closed || JDBCClobClient.this.isClosed()) {
throw new IOException("The stream is closed");
}
}
};
}
/**
* Writes the given Java String
to the CLOB
* value that this Clob
object designates at the position
* pos
.
*
* @param pos the position at which to start writing to the
* CLOB
value that this Clob
object
* represents
* @param str the string to be written to the CLOB
value
* that this Clob
designates
* @return the number of characters written
* @throws SQLException if there is an error accessing the
* CLOB
value
*/
public synchronized int setString(long pos,
String str) throws SQLException {
return setString(pos, str, 0, str.length());
}
/**
* Writes len
characters of str
, starting at
* character offset
, to the CLOB
value that
* this Clob
represents.
*
* @param pos the position at which to start writing to this
* CLOB
object
* @param str the string to be written to the CLOB
value
* that this Clob
object represents
* @param offset the offset into str
to start reading the
* characters to be written
* @param len the number of characters to be written
* @return the number of characters written
* @throws SQLException if there is an error accessing the
* CLOB
value
*/
public synchronized int setString(long pos, String str, int offset,
int len) throws SQLException {
checkClosed();
if (!isInLimits(str.length(), offset, len)) {
throw JDBCUtil.outOfRangeArgument();
}
if (pos < 1) {
throw JDBCUtil.outOfRangeArgument("pos: " + pos);
}
if (!isWritable) {
throw JDBCUtil.notUpdatableColumn();
}
try {
startUpdate();
str = str.substring(offset, offset + len);
clob.setString(session, pos - 1, str);
return len;
} catch (HsqlException e) {
throw JDBCUtil.sqlException(e);
}
}
/**
* Truncates the CLOB
value that this Clob
* designates to have a length of len
characters.
*
* @param len the length, in bytes, to which the CLOB
value
* should be truncated
* @throws SQLException if there is an error accessing the
* CLOB
value
*/
public synchronized void truncate(long len) throws SQLException {
checkClosed();
if (len < 0) {
throw JDBCUtil.outOfRangeArgument("len: " + len);
}
try {
clob.truncate(session, len);
} catch (HsqlException e) {
throw JDBCUtil.sqlException(e);
}
}
//------------------------- JDBC 4.0 -----------------------------------
/**
* This method frees the Clob
object and releases the resources the resources
* that it holds. The object is invalid once the free
method
* is called.
*
* After free
has been called, any attempt to invoke a
* method other than free
will result in a SQLException
* being thrown. If free
is called multiple times, the subsequent
* calls to free
are treated as a no-op.
*
* @throws SQLException if an error occurs releasing
* the Clob's resources
*
* @exception SQLFeatureNotSupportedException if the JDBC driver does not support
* this method
* @since JDK 1.6, HSQLDB 2.0
*/
public synchronized void free() throws SQLException {
isClosed = true;
clob = null;
session = null;
}
/**
* Returns a Reader
object that contains a partial Clob
value, starting
* with the character specified by pos, which is length characters in length.
*
* @param pos the offset to the first character of the partial value to
* be retrieved. The first character in the Clob is at position 1.
* @param length the length in characters of the partial value to be retrieved.
* @return Reader
through which the partial Clob
value can be read.
* @throws SQLException if pos is less than 1 or if pos is greater than the number of
* characters in the Clob
or if pos + length is greater than the number of
* characters in the Clob
*
* @exception SQLFeatureNotSupportedException if the JDBC driver does not support
* this method
* @since JDK 1.6, HSQLDB 2.0
*/
public synchronized Reader getCharacterStream(long pos,
long length) throws SQLException {
checkClosed();
if (!isInLimits(this.length(), pos - 1, length)) {
throw JDBCUtil.outOfRangeArgument();
}
return new ClobInputStream(session, clob, pos - 1, length);
}
char[] getChars(long position, int length) throws SQLException {
try {
return clob.getChars(session, position - 1, length);
} catch (HsqlException e) {
throw JDBCUtil.sqlException(e);
}
}
//
ClobDataID originalClob;
ClobDataID clob;
SessionInterface session;
int colIndex;
private boolean isClosed;
private boolean isWritable;
JDBCResultSet resultSet;
public JDBCClobClient(SessionInterface session, ClobDataID clob) {
this.session = session;
this.clob = clob;
}
public ClobDataID getClob() {
return clob;
}
public synchronized boolean isClosed() {
return isClosed;
}
public synchronized void setWritable(JDBCResultSet result, int index) {
isWritable = true;
resultSet = result;
colIndex = index;
}
public synchronized void clearUpdates() {
if (originalClob != null) {
clob = originalClob;
originalClob = null;
}
}
private void startUpdate() throws SQLException {
if (originalClob != null) {
return;
}
originalClob = clob;
clob = (ClobDataID) clob.duplicate(session);
resultSet.startUpdate(colIndex + 1);
resultSet.preparedStatement.parameterValues[colIndex] = clob;
resultSet.preparedStatement.parameterSet[colIndex] = true;
}
private void checkClosed() throws SQLException {
if (isClosed) {
throw JDBCUtil.sqlException(ErrorCode.X_0F502);
}
}
static boolean isInLimits(long fullLength, long pos, long len) {
return fullLength >= 0 && pos >= 0 && len >= 0
&& pos <= fullLength - len;
}
protected static Charset charsetForName(final String charsetName)
throws SQLException {
String csn = charsetName;
if (csn == null) {
csn = Charset.defaultCharset().name();
}
try {
if (Charset.isSupported(csn)) {
return Charset.forName(csn);
}
} catch (IllegalCharsetNameException x) {}
throw JDBCUtil.sqlException(new UnsupportedEncodingException(csn));
}
}