blob: f7b5ce66130601f9d966e92a0202591e2fcbf0d7 [file] [log] [blame]
/*
Derby - Class org.apache.derby.iapi.types.SQLBinary
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to you under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package org.apache.derby.iapi.types;
import org.apache.derby.iapi.sql.conn.StatementContext;
import org.apache.derby.iapi.reference.ContextId;
import org.apache.derby.iapi.reference.SQLState;
import org.apache.derby.iapi.reference.MessageId;
import org.apache.derby.iapi.error.StandardException;
import org.apache.derby.iapi.services.context.ContextService;
import org.apache.derby.iapi.services.io.DerbyIOException;
import org.apache.derby.iapi.services.io.StoredFormatIds;
import org.apache.derby.iapi.services.io.FormatIdInputStream;
import org.apache.derby.iapi.services.io.InputStreamUtil;
import org.apache.derby.shared.common.sanity.SanityManager;
import org.apache.derby.iapi.services.i18n.MessageService;
import org.apache.derby.iapi.services.cache.ClassSize;
import java.io.ObjectOutput;
import java.io.ObjectInput;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Blob;
import java.sql.DataTruncation;
import java.sql.SQLException;
import java.sql.PreparedStatement;
/**
* SQLBinary is the abstract class for the binary datatypes.
* <UL>
* <LI> CHAR FOR BIT DATA
* <LI> VARCHAR FOR BIT DATA
* <LI> LONG VARCHAR
* <LI> BLOB
* </UL>
<P>
Format : <encoded length><raw data>
<BR>
Length is encoded to support Cloudscape 5.x databases where the length was stored as the number of bits.
The first bit of the first byte indicates if the format is an old (Cloudscape 5.x) style or a new Derby style.
Derby then uses the next two bits to indicate how the length is encoded.
<BR>
<encoded length> is one of N styles.
<UL>
<LI> (5.x format zero) 4 byte Java format integer value 0 - either <raw data> is 0 bytes/bits or an unknown number of bytes.
<LI> (5.x format bits) 4 byte Java format integer value &gt;0 (positive) - number of bits in raw data, number of bytes in <raw data>
is the minimum number of bytes required to store the number of bits.
<LI> (Derby format) 1 byte encoded length (0 &lt;= L &lt;= 31) - number of bytes of raw data - encoded = 0x80 &amp; L
<LI> (Derby format) 3 byte encoded length (32 &lt;= L &lt; 64k) - number of bytes of raw data - encoded = 0xA0 <L as Java format unsigned short>
<LI> (Derby format) 5 byte encoded length (64k &lt;= L &lt; 2G) - number of bytes of raw data - encoded = 0xC0 <L as Java format integer>
<LI> (future) to be determined L &gt;= 2G - encoded 0xE0 <encoding of L to be determined>
(0xE0 is an esacape to allow any number of arbitary encodings in the future).
</UL>
<BR>
When the value was written from a byte array the Derby encoded byte
length format was always used from Derby 10.0 onwards (ie. all open
source versions).
<BR>
When the value was written from a stream (e.g. PreparedStatement.setBinaryStream)
then the Cloudscape '5.x format zero' was used by 10.0 and 10.1.
The was due to the class RawToBinaryFormatStream always writing
four zero bytes for the length before the data.
<BR>
The Cloudscape '5.x format bits' format I think was never used by Derby.
*/
abstract class SQLBinary
extends DataType implements BitDataValue
{
static final byte PAD = (byte) 0x20;
private static final int BASE_MEMORY_USAGE = ClassSize.estimateBaseFromCatalog( SQLBinary.class);
private static final int LEN_OF_BUFFER_TO_WRITE_BLOB = 1024;
public int estimateMemoryUsage()
{
if (dataValue == null) {
if (streamValueLength>=0) {
return BASE_MEMORY_USAGE + streamValueLength;
} else {
return getMaxMemoryUsage();
}
} else {
return BASE_MEMORY_USAGE + dataValue.length;
}
} // end of estimateMemoryUsage
/**
* Return max memory usage for a SQL Binary
*/
abstract int getMaxMemoryUsage();
/*
* value as a blob
*/
Blob _blobValue;
/*
* object state
*/
byte[] dataValue;
/**
* Value as a stream, this stream represents the on-disk
* format of the value. That is it has length information
* encoded in the first fe bytes.
*/
InputStream stream;
/**
Length of the value in bytes when this value
is set as a stream. Represents the length of the
value itself and not the length of the stream
which contains this length encoded as the first
few bytes. If the value of the stream is unknown
then this will be set to -1. If this value is
not set as a stream then this value should be ignored.
*/
int streamValueLength;
/**
Create a binary value set to NULL
*/
SQLBinary()
{
}
SQLBinary(byte[] val)
{
dataValue = val;
}
SQLBinary(Blob val)
{
setValue( val );
}
public final void setValue(byte[] theValue)
{
dataValue = theValue;
_blobValue = null;
stream = null;
streamValueLength = -1;
}
public final void setValue(Blob theValue)
{
dataValue = null;
_blobValue = theValue;
stream = null;
streamValueLength = -1;
}
/**
* Used by JDBC -- string should not contain
* SQL92 formatting.
*
* @exception StandardException Thrown on error
*/
public final String getString() throws StandardException
{
if (getValue() == null)
return null;
else if (dataValue.length * 2 < 0) //if converted to hex, length exceeds max int
{
throw StandardException.newException(SQLState.LANG_STRING_TRUNCATION, getTypeName(),
"",
String.valueOf(Integer.MAX_VALUE));
}
else
{
return org.apache.derby.iapi.util.StringUtil.toHexString(dataValue, 0, dataValue.length);
}
}
/**
* @exception StandardException Thrown on error
*/
public final InputStream getStream() throws StandardException
{
if (!hasStream()) {
throw StandardException.newException(
SQLState.LANG_STREAM_INVALID_ACCESS, getTypeName());
}
return (stream);
}
/**
*
* @exception StandardException Thrown on error
*/
public final byte[] getBytes() throws StandardException
{
return getValue();
}
byte[] getValue() throws StandardException
{
try
{
if ((dataValue == null) && (_blobValue != null) )
{
dataValue = _blobValue.getBytes( 1L, getBlobLength() );
_blobValue = null;
stream = null;
streamValueLength = -1;
}
else if ((dataValue == null) && (stream != null) )
{
if (stream instanceof FormatIdInputStream) {
readExternal((FormatIdInputStream) stream);
}
else {
readExternal(new FormatIdInputStream(stream));
}
_blobValue = null;
stream = null;
streamValueLength = -1;
}
}
catch (IOException ioe)
{
throwStreamingIOException(ioe);
}
catch (SQLException se) { throw StandardException.plainWrapException( se ); }
return dataValue;
}
/**
* length in bytes
*
* @exception StandardException Thrown on error
*/
public final int getLength() throws StandardException
{
if ( _blobValue != null ) { return getBlobLength(); }
else if (stream != null) {
if (streamValueLength != -1)
return streamValueLength;
else if (stream instanceof Resetable){
try {
// If we have the stream length encoded.
// just read that.
streamValueLength = readBinaryLength((ObjectInput) stream);
if (streamValueLength == 0) {
// Otherwise we will have to read the whole stream.
streamValueLength =
(int) InputStreamUtil.skipUntilEOF(stream);
}
return streamValueLength;
}
catch (IOException ioe) {
throwStreamingIOException(ioe);
}
finally {
try {
((Resetable) stream).resetStream();
} catch (IOException ioe) {
throwStreamingIOException(ioe);
}
}
}
}
byte[] bytes = getBytes();
return (bytes == null) ? 0 : bytes.length;
}
private void throwStreamingIOException(IOException ioe) throws StandardException {
throw StandardException.
newException(SQLState.LANG_STREAMING_COLUMN_I_O_EXCEPTION,
ioe, getTypeName());
}
/*
* Storable interface, implies Externalizable, TypedFormat
*/
/**
* see if the Bit value is null.
* @see org.apache.derby.iapi.services.io.Storable#isNull
*/
public final boolean isNull()
{
return (dataValue == null) && (stream == null) && (_blobValue == null);
}
/**
Write the value out from the byte array (not called if null)
using the 8.1 encoding.
* @exception IOException io exception
*/
public final void writeExternal(ObjectOutput out) throws IOException
{
if ( _blobValue != null )
{
writeBlob( out );
return;
}
int len = dataValue.length;
writeLength( out, len );
out.write(dataValue, 0, dataValue.length);
}
/**
Serialize a blob using the 8.1 encoding. Not called if null.
* @exception IOException io exception
*/
private void writeBlob(ObjectOutput out) throws IOException
{
try {
int len = getBlobLength();
InputStream is = _blobValue.getBinaryStream();
writeLength( out, len );
int bytesRead = 0;
int numOfBytes = 0;
byte[] buffer = new byte[Math.min(len, LEN_OF_BUFFER_TO_WRITE_BLOB)];
while(bytesRead < len) {
numOfBytes = is.read(buffer);
if (numOfBytes == -1) {
throw new DerbyIOException(
MessageService.getTextMessage(
SQLState.SET_STREAM_INEXACT_LENGTH_DATA),
SQLState.SET_STREAM_INEXACT_LENGTH_DATA);
}
out.write(buffer, 0, numOfBytes);
bytesRead += numOfBytes;
}
}
catch (StandardException se) { throw new IOException( se.getMessage() ); }
catch (SQLException se) { throw new IOException( se.getMessage() ); }
}
/**
Write the length if
using the 8.1 encoding.
* @exception IOException io exception
*/
private void writeLength( ObjectOutput out, int len ) throws IOException
{
if (len <= 31)
{
out.write((byte) (0x80 | (len & 0xff)));
}
else if (len <= 0xFFFF)
{
out.write((byte) 0xA0);
out.writeShort((short) len);
}
else
{
out.write((byte) 0xC0);
out.writeInt(len);
}
}
/**
* delegated to bit
*
* @exception IOException io exception
* @exception ClassNotFoundException class not found
*/
public final void readExternal(ObjectInput in) throws IOException
{
// need to clear stream first, in case this object is reused, and
// stream is set by previous use. Track 3794.
stream = null;
streamValueLength = -1;
_blobValue = null;
int len = SQLBinary.readBinaryLength(in);
if (len != 0)
{
dataValue = new byte[len];
in.readFully(dataValue);
}
else
{
readFromStream((InputStream) in);
}
}
/**
* Read the encoded length of the value from the on-disk format.
*
* @see SQLBinary
*/
private static int readBinaryLength(ObjectInput in) throws IOException {
int bl = in.read();
if (bl == -1)
throw new java.io.EOFException();
byte li = (byte) bl;
int len;
if ((li & ((byte) 0x80)) != 0)
{
if (li == ((byte) 0xC0))
{
len = in.readInt();
}
else if (li == ((byte) 0xA0))
{
len = in.readUnsignedShort();
}
else
{
len = li & 0x1F;
}
}
else
{
// old length in bits
int v2 = in.read();
int v3 = in.read();
int v4 = in.read();
if (v2 == -1 || v3 == -1 || v4 == -1)
throw new java.io.EOFException();
int lenInBits = (((bl & 0xff) << 24) | ((v2 & 0xff) << 16) | ((v3 & 0xff) << 8) | (v4 & 0xff));
len = lenInBits / 8;
if ((lenInBits % 8) != 0)
len++;
}
return len;
}
/**
* Read the value from an input stream. The length
* encoded in the input stream has already been read
* and determined to be unknown.
*/
private void readFromStream(InputStream in) throws IOException {
dataValue = null; // allow gc of the old value before the new.
byte[] tmpData = new byte[32 * 1024];
int off = 0;
for (;;) {
int len = in.read(tmpData, off, tmpData.length - off);
if (len == -1)
break;
off += len;
int available = Math.max(1, in.available());
int extraSpace = available - (tmpData.length - off);
if (extraSpace > 0)
{
// need to grow the array
int size = tmpData.length * 2;
if (extraSpace > tmpData.length)
size += extraSpace;
byte[] grow = new byte[size];
System.arraycopy(tmpData, 0, grow, 0, off);
tmpData = grow;
}
}
dataValue = new byte[off];
System.arraycopy(tmpData, 0, dataValue, 0, off);
}
/**
* @see org.apache.derby.iapi.services.io.Storable#restoreToNull
*/
public final void restoreToNull()
{
dataValue = null;
_blobValue = null;
stream = null;
streamValueLength = -1;
}
/**
@exception StandardException thrown on error
*/
public final boolean compare(int op,
DataValueDescriptor other,
boolean orderedNulls,
boolean unknownRV)
throws StandardException
{
if (!orderedNulls) // nulls are unordered
{
if (SanityManager.DEBUG)
{
int otherTypeFormatId = other.getTypeFormatId();
if (!((StoredFormatIds.SQL_BIT_ID == otherTypeFormatId)
|| (StoredFormatIds.SQL_VARBIT_ID == otherTypeFormatId)
|| (StoredFormatIds.SQL_LONGVARBIT_ID == otherTypeFormatId)
|| (StoredFormatIds.SQL_CHAR_ID == otherTypeFormatId)
|| (StoredFormatIds.SQL_VARCHAR_ID == otherTypeFormatId)
|| (StoredFormatIds.SQL_LONGVARCHAR_ID == otherTypeFormatId)
|| ((StoredFormatIds.SQL_BLOB_ID == otherTypeFormatId)
&& (StoredFormatIds.SQL_BLOB_ID == getTypeFormatId()))
))
SanityManager.THROWASSERT(
"An object of type " + other.getClass().getName() +
", with format id " + otherTypeFormatId +
", was passed to SQLBinary.compare()");
}
if (this.isNull() || other.isNull())
return unknownRV;
}
/* Do the comparison */
return super.compare(op, other, orderedNulls, unknownRV);
}
/**
@exception StandardException thrown on error
*/
public final int compare(DataValueDescriptor other) throws StandardException
{
/* Use compare method from dominant type, negating result
* to reflect flipping of sides.
*/
if (typePrecedence() < other.typePrecedence())
{
return - (other.compare(this));
}
/*
** By convention, nulls sort High, and null == null
*/
if (this.isNull() || other.isNull())
{
if (!isNull())
return -1;
if (!other.isNull())
return 1;
return 0; // both null
}
return SQLBinary.compare(getBytes(), other.getBytes());
}
/**
* Shallow clone a StreamStorable without objectifying.
* This is used to avoid unnecessary objectifying of a stream object.
*
* Beetle 4896
*/
public final DataValueDescriptor cloneHolder() {
if (stream == null && _blobValue == null) {
return cloneValue(false);
} else {
// Cast to SQLBinary to avoid having to catch StandardException.
SQLBinary self = (SQLBinary)getNewNull();
if (stream != null) {
// Just reference the same stream in the cloned holder.
self.setValue(stream, streamValueLength);
} else if (_blobValue != null) {
// Just reference the same BLOB value in the cloned holder.
self.setValue(_blobValue);
} else {
throw new IllegalStateException("unknown BLOB value repr");
}
return self;
}
}
/*
* DataValueDescriptor interface
*/
/** @see DataValueDescriptor#cloneValue */
public DataValueDescriptor cloneValue(boolean forceMaterialization)
{
try
{
DataValueDescriptor cloneDVD = getNewNull();
cloneDVD.setValue(getValue());
return cloneDVD;
}
catch (StandardException se)
{
if (SanityManager.DEBUG)
SanityManager.THROWASSERT("Unexpected exception", se);
return null;
}
}
/*
* DataValueDescriptor interface
*/
/*
* StreamStorable interface :
*/
public final InputStream returnStream()
{
return stream;
}
/**
* Set me to the value represented by this stream.
* The format of the stream is the on-disk format
* described in this class's javadoc. That is the
* length is encoded in the first few bytes of the
* stream.
*/
public final void setStream(InputStream newStream)
{
this.dataValue = null;
_blobValue = null;
this.stream = newStream;
streamValueLength = -1;
}
public final void loadStream() throws StandardException
{
getValue();
}
/*
* class interface
*/
boolean objectNull(Object o)
{
if (o == null)
{
setToNull();
return true;
}
return false;
}
/**
* Set the value from the stream which is in the on-disk format.
* @param theStream On disk format of the stream
* @param valueLength length of the logical value in bytes, or
* <code>DataValueDescriptor.UNKNOWN_LOGICAL_LENGTH</code>
*/
public final void setValue(InputStream theStream, int valueLength)
{
dataValue = null;
_blobValue = null;
stream = theStream;
this.streamValueLength = valueLength;
}
protected final void setFrom(DataValueDescriptor theValue) throws StandardException {
if (theValue instanceof SQLBinary)
{
SQLBinary theValueBinary = (SQLBinary) theValue;
dataValue = theValueBinary.dataValue;
_blobValue = theValueBinary._blobValue;
stream = theValueBinary.stream;
streamValueLength = theValueBinary.streamValueLength;
}
else
{
setValue(theValue.getBytes());
}
}
/*
** SQL Operators
*/
/**
* The = operator as called from the language module, as opposed to
* the storage module.
*
* @param left The value on the left side of the =
* @param right The value on the right side of the =
* is not.
* @return A SQL boolean value telling whether the two parameters are equal
*
* @exception StandardException Thrown on error
*/
public final BooleanDataValue equals(DataValueDescriptor left,
DataValueDescriptor right)
throws StandardException
{
boolean isEqual;
if (left.isNull() || right.isNull())
{
isEqual = false;
}
else
{
isEqual = SQLBinary.compare(left.getBytes(), right.getBytes()) == 0;
}
return SQLBoolean.truthValue(left,
right,
isEqual);
}
/**
* The &lt;&gt; operator as called from the language module, as opposed to
* the storage module.
*
* @param left The value on the left side of the operator
* @param right The value on the right side of the operator
*
* @return A SQL boolean value telling whether the two parameters
* are not equal
*
* @exception StandardException Thrown on error
*/
public final BooleanDataValue notEquals(DataValueDescriptor left,
DataValueDescriptor right)
throws StandardException
{
boolean isNotEqual;
if (left.isNull() || right.isNull())
{
isNotEqual = false;
}
else
{
isNotEqual = SQLBinary.compare(left.getBytes(), right.getBytes()) != 0;
}
return SQLBoolean.truthValue(left,
right,
isNotEqual);
}
/**
* The &lt; operator as called from the language module, as opposed to
* the storage module.
*
* @param left The value on the left side of the operator
* @param right The value on the right side of the operator
*
* @return A SQL boolean value telling whether the first operand is
* less than the second operand
*
* @exception StandardException Thrown on error
*/
public final BooleanDataValue lessThan(DataValueDescriptor left,
DataValueDescriptor right)
throws StandardException
{
boolean isLessThan;
if (left.isNull() || right.isNull())
{
isLessThan = false;
}
else
{
isLessThan = SQLBinary.compare(left.getBytes(), right.getBytes()) < 0;
}
return SQLBoolean.truthValue(left,
right,
isLessThan);
}
/**
* The &gt; operator as called from the language module, as opposed to
* the storage module.
*
* @param left The value on the left side of the operator
* @param right The value on the right side of the operator
*
* @return A SQL boolean value telling whether the first operand is
* greater than the second operand
*
* @exception StandardException Thrown on error
*/
public final BooleanDataValue greaterThan(DataValueDescriptor left,
DataValueDescriptor right)
throws StandardException
{
boolean isGreaterThan = false;
if (left.isNull() || right.isNull())
{
isGreaterThan = false;
}
else
{
isGreaterThan = SQLBinary.compare(left.getBytes(), right.getBytes()) > 0;
}
return SQLBoolean.truthValue(left,
right,
isGreaterThan);
}
/**
* The &lt;= operator as called from the language module, as opposed to
* the storage module.
*
* @param left The value on the left side of the operator
* @param right The value on the right side of the operator
*
* @return A SQL boolean value telling whether the first operand is
* less than or equal to the second operand
*
* @exception StandardException Thrown on error
*/
public final BooleanDataValue lessOrEquals(DataValueDescriptor left,
DataValueDescriptor right)
throws StandardException
{
boolean isLessEquals = false;
if (left.isNull() || right.isNull())
{
isLessEquals = false;
}
else
{
isLessEquals = SQLBinary.compare(left.getBytes(), right.getBytes()) <= 0;
}
return SQLBoolean.truthValue(left,
right,
isLessEquals);
}
/**
* The &gt;= operator as called from the language module, as opposed to
* the storage module.
*
* @param left The value on the left side of the &gt;=
* @param right The value on the right side of the &gt;=
*
* @return A SQL boolean value telling whether the first operand is
* greater than or equal to the second operand
*
* @exception StandardException Thrown on error
*/
public final BooleanDataValue greaterOrEquals(DataValueDescriptor left,
DataValueDescriptor right)
throws StandardException
{
boolean isGreaterEquals = false;
if (left.isNull() || right.isNull())
{
isGreaterEquals = false;
}
else
{
isGreaterEquals = SQLBinary.compare(left.getBytes(), right.getBytes()) >= 0;
}
return SQLBoolean.truthValue(left,
right,
isGreaterEquals);
}
/**
*
* This method implements the char_length function for bit.
*
* @param result The result of a previous call to this method, null
* if not called yet
*
* @return A SQLInteger containing the length of the char value
*
* @exception StandardException Thrown on error
*
* @see ConcatableDataValue#charLength
*/
public final NumberDataValue charLength(NumberDataValue result)
throws StandardException
{
if (result == null)
{
result = new SQLInteger();
}
if (this.isNull())
{
result.setToNull();
return result;
}
result.setValue(getValue().length);
return result;
}
/**
* @see BitDataValue#concatenate
*
* @exception StandardException Thrown on error
*/
public final BitDataValue concatenate(
BitDataValue left,
BitDataValue right,
BitDataValue result)
throws StandardException
{
if (result == null)
{
result = (BitDataValue) getNewNull();
}
if (left.isNull() || right.isNull())
{
result.setToNull();
return result;
}
byte[] leftData = left.getBytes();
byte[] rightData = right.getBytes();
byte[] concatData = new byte[leftData.length + rightData.length];
System.arraycopy(leftData, 0, concatData, 0, leftData.length);
System.arraycopy(rightData, 0, concatData, leftData.length, rightData.length);
result.setValue(concatData);
return result;
}
/**
* The SQL substr() function.
*
* @param start Start of substr
* @param length Length of substr
* @param result The result of a previous call to this method,
* null if not called yet.
* @param maxLen Maximum length of the result
*
* @return A ConcatableDataValue containing the result of the substr()
*
* @exception StandardException Thrown on error
*/
public final ConcatableDataValue substring(
NumberDataValue start,
NumberDataValue length,
ConcatableDataValue result,
int maxLen)
throws StandardException
{
int startInt;
int lengthInt;
BitDataValue varbitResult;
if (result == null)
{
result = new SQLVarbit();
}
varbitResult = (BitDataValue) result;
/* The result is null if the receiver (this) is null or if the length is negative.
* Oracle docs don't say what happens if the start position or the length is a usernull.
* We will return null, which is the only sensible thing to do.
* (If the user did not specify a length then length is not a user null.)
*/
if (this.isNull() || start.isNull() || (length != null && length.isNull()))
{
varbitResult.setToNull();
return varbitResult;
}
startInt = start.getInt();
// If length is not specified, make it till end of the string
if (length != null)
{
lengthInt = length.getInt();
}
else lengthInt = getLength() - startInt + 1;
/* DB2 Compatibility: Added these checks to match DB2. We currently enforce these
* limits in both modes. We could do these checks in DB2 mode only, if needed, so
* leaving earlier code for out of range in for now, though will not be exercised
*/
if ((startInt <= 0 || lengthInt < 0 || startInt > getLength() ||
lengthInt > getLength() - startInt + 1))
throw StandardException.newException(SQLState.LANG_SUBSTR_START_OR_LEN_OUT_OF_RANGE);
// Return null if length is non-positive
if (lengthInt < 0)
{
varbitResult.setToNull();
return varbitResult;
}
/* If startInt < 0 then we count from the right of the string */
if (startInt < 0)
{
startInt += getLength();
if (startInt < 0)
{
lengthInt += startInt;
startInt = 0;
}
if (lengthInt + startInt > 0)
{
lengthInt += startInt;
}
else
{
lengthInt = 0;
}
}
else if (startInt > 0)
{
/* java substr() is 0 based */
startInt--;
}
/* Oracle docs don't say what happens if the window is to the
* left of the string. Return "" if the window
* is to the left or right or if the length is 0.
*/
if (lengthInt == 0 ||
lengthInt <= 0 - startInt ||
startInt > getLength())
{
varbitResult.setValue(new byte[0]);
return varbitResult;
}
if (lengthInt >= getLength() - startInt)
{
byte[] substring = new byte[dataValue.length - startInt];
System.arraycopy(dataValue, startInt, substring, 0, substring.length);
varbitResult.setValue(substring);
}
else
{
byte[] substring = new byte[lengthInt];
System.arraycopy(dataValue, startInt, substring, 0, substring.length);
varbitResult.setValue(substring);
}
return varbitResult;
}
/**
Host variables are rejected if their length is
bigger than the declared length, regardless of
if the trailing bytes are the pad character.
@exception StandardException Variable is too big.
*/
public final void checkHostVariable(int declaredLength) throws StandardException
{
// stream length checking occurs at the JDBC layer
int variableLength = -1;
if ( _blobValue != null ) { variableLength = -1; }
else if (stream == null)
{
if (dataValue != null)
variableLength = dataValue.length;
}
else
{
variableLength = streamValueLength;
}
if (variableLength != -1 && variableLength > declaredLength)
throw StandardException.newException(SQLState.LANG_STRING_TRUNCATION, getTypeName(),
MessageService.getTextMessage(
MessageId.BINARY_DATA_HIDDEN),
String.valueOf(declaredLength));
}
/*
* String display of value
*/
public final String toString()
{
if (dataValue == null)
{
if ((stream == null) && (_blobValue == null) )
{
return "NULL";
}
else
{
if (SanityManager.DEBUG)
SanityManager.THROWASSERT(
"value is null, stream or blob is not null");
return "";
}
}
else
{
return org.apache.derby.iapi.util.StringUtil.toHexString(dataValue, 0, dataValue.length);
}
}
/*
* Hash code
*/
public final int hashCode()
{
try {
if (getValue() == null)
{
return 0;
}
}
catch (StandardException se)
{
if (SanityManager.DEBUG)
SanityManager.THROWASSERT("Unexpected exception", se);
return 0;
}
// Hash code should ignore trailing PAD bytes.
byte[] bytes = dataValue;
int lastNonPadByte = bytes.length - 1;
while (lastNonPadByte >= 0 && bytes[lastNonPadByte] == PAD) {
lastNonPadByte--;
}
// Build the hash code in a way similar to String.hashCode() and
// SQLChar.hashCode()
int hashcode = 0;
for (int i = 0; i <= lastNonPadByte; i++) {
hashcode = hashcode * 31 + bytes[i];
}
return hashcode;
}
private static int compare(byte[] left, byte[] right) {
int minLen = left.length;
byte[] longer = right;
if (right.length < minLen) {
minLen = right.length;
longer = left;
}
for (int i = 0; i < minLen; i++) {
int lb = left[i] & 0xff;
int rb = right[i] & 0xff;
if (lb == rb)
continue;
return lb - rb;
}
// complete match on all the bytes for the smallest value.
// if the longer value is all pad characters
// then the values are equal.
for (int i = minLen; i < longer.length; i++) {
byte nb = longer[i];
if (nb == SQLBinary.PAD)
continue;
// longer value is bigger.
if (left == longer)
return 1;
return -1;
}
return 0;
}
/** Adding this method to ensure that super class' setInto method doesn't get called
* that leads to the violation of JDBC spec( untyped nulls ) when batching is turned on.
*/
public void setInto(PreparedStatement ps, int position) throws SQLException, StandardException {
ps.setBytes(position, getBytes());
}
/**
* Gets a trace representation for debugging.
*
* @return a trace representation of this SQL DataType.
*/
public final String getTraceString() throws StandardException {
// Check if the value is SQL NULL.
if (isNull()) {
return "NULL";
}
// Check if we have a stream.
if (hasStream()) {
return (getTypeName() + "(" + getStream().toString() + ")");
}
return (getTypeName() + ":Length=" + getLength());
}
private int getBlobLength() throws StandardException
{
try {
long maxLength = Integer.MAX_VALUE;
long length = _blobValue.length();
if ( length > Integer.MAX_VALUE )
{
throw StandardException.newException
( SQLState.BLOB_TOO_LARGE_FOR_CLIENT, Long.toString( length ), Long.toString( maxLength ) );
}
return (int) length;
}
catch (SQLException se) { throw StandardException.plainWrapException( se ); }
}
/**
* Truncate this value to the desired width by removing bytes at the
* end of the byte sequence.
*
* @param sourceWidth the original width in bytes (only used for
* diagnostics, ignored if {@code warn} is {@code false})
* @param desiredWidth the desired width in bytes
* @param warn whether or not to generate a truncation warning
*/
void truncate(int sourceWidth, int desiredWidth, boolean warn)
throws StandardException {
if (warn) {
// SQL:2003, part 2, 6.12 <cast specification>,
// general rule 12 says we should warn about truncation.
DataTruncation warning = new DataTruncation(
-1, // column index is unknown
false, // parameter
true, // read
getLength(), desiredWidth);
StatementContext statementContext = (StatementContext)
DataValueFactoryImpl.getContext(ContextId.LANG_STATEMENT);
statementContext.getActivation().
getResultSet().addWarning(warning);
}
// Truncate to the desired width.
byte[] shrunkData = new byte[desiredWidth];
System.arraycopy(getValue(), 0, shrunkData, 0, desiredWidth);
setValue(shrunkData);
}
}