org.postgresql.jdbc.ArrayEncoding Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of postgresql Show documentation
Show all versions of postgresql Show documentation
PostgreSQL JDBC Driver JDBC4
/*
* Copyright (c) 2020, PostgreSQL Global Development Group
* See the LICENSE file in the project root for more information.
*/
package org.postgresql.jdbc;
import org.postgresql.core.BaseConnection;
import org.postgresql.core.Encoding;
import org.postgresql.core.Oid;
import org.postgresql.util.ByteConverter;
import org.postgresql.util.GT;
import org.postgresql.util.PSQLException;
import org.postgresql.util.PSQLState;
import org.checkerframework.checker.index.qual.Positive;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.Array;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.HashMap;
import java.util.Map;
/**
* Utility for using arrays in requests.
*
*
* Binary format:
*
* - 4 bytes with number of dimensions
* - 4 bytes, boolean indicating nulls present or not
* - 4 bytes type oid
* - 8 bytes describing the length of each dimension (repeated for each dimension)
*
* - 4 bytes for length
* - 4 bytes for lower bound on length to check for overflow (it appears this value can always be 0)
*
* - data in depth first element order corresponding number and length of dimensions
*
* - 4 bytes describing length of element, {@code 0xFFFFFFFF} ({@code -1}) means {@code null}
* - binary representation of element (iff not {@code null}).
*
*
*
*
* @author Brett Okken
*/
final class ArrayEncoding {
public interface ArrayEncoder {
/**
* The default array type oid supported by this instance.
*
* @return The default array type oid supported by this instance.
*/
int getDefaultArrayTypeOid();
/**
* Creates {@code String} representation of the array.
*
* @param delim
* The character to use to delimit between elements.
* @param array
* The array to represent as a {@code String}.
* @return {@code String} representation of the array.
*/
String toArrayString(char delim, A array);
/**
* Indicates if an array can be encoded in binary form to array oid.
*
* @param oid
* The array oid to see check for binary support.
* @return Indication of whether
* {@link #toBinaryRepresentation(BaseConnection, Object, int)} is
* supported for oid.
*/
boolean supportBinaryRepresentation(int oid);
/**
* Creates binary representation of the array.
*
* @param connection
* The connection the binary representation will be used on. Attributes
* from the connection might impact how values are translated to
* binary.
* @param array
* The array to binary encode. Must not be {@code null}, but may
* contain {@code null} elements.
* @param oid
* The array type oid to use. Calls to
* {@link #supportBinaryRepresentation(int)} must have returned
* {@code true}.
* @return The binary representation of array.
* @throws SQLFeatureNotSupportedException
* If {@link #supportBinaryRepresentation(int)} is false for
* oid.
*/
byte[] toBinaryRepresentation(BaseConnection connection, A array, int oid)
throws SQLException, SQLFeatureNotSupportedException;
}
/**
* Base class to implement {@link ArrayEncoding.ArrayEncoder} and provide
* multi-dimensional support.
*
* @param
* Base array type supported.
*/
private abstract static class AbstractArrayEncoder
implements ArrayEncoder {
private final int oid;
final int arrayOid;
/**
*
* @param oid
* The default/primary base oid type.
* @param arrayOid
* The default/primary array oid type.
*/
AbstractArrayEncoder(int oid, int arrayOid) {
this.oid = oid;
this.arrayOid = arrayOid;
}
/**
*
* @param arrayOid
* The array oid to get base oid type for.
* @return The base oid type for the given array oid type given to
* {@link #toBinaryRepresentation(BaseConnection, Object, int)}.
*/
int getTypeOID(@SuppressWarnings("unused") int arrayOid) {
return oid;
}
/**
* By default returns the arrayOid this instance was instantiated with.
*/
@Override
public int getDefaultArrayTypeOid() {
return arrayOid;
}
/**
* Counts the number of {@code null} elements in array.
*
* @param array
* The array to count {@code null} elements in.
* @return The number of {@code null} elements in array.
*/
int countNulls(A array) {
int nulls = 0;
final int arrayLength = Array.getLength(array);
for (int i = 0; i < arrayLength; ++i) {
if (Array.get(array, i) == null) {
++nulls;
}
}
return nulls;
}
/**
* Creates {@code byte[]} of just the raw data (no metadata).
*
* @param connection
* The connection the binary representation will be used on.
* @param array
* The array to create binary representation of. Will not be
* {@code null}, but may contain {@code null} elements.
* @return {@code byte[]} of just the raw data (no metadata).
* @throws SQLFeatureNotSupportedException
* If {@link #supportBinaryRepresentation(int)} is false for
* oid.
*/
abstract byte[] toSingleDimensionBinaryRepresentation(BaseConnection connection, A array)
throws SQLException, SQLFeatureNotSupportedException;
/**
* {@inheritDoc}
*/
@Override
public String toArrayString(char delim, A array) {
final StringBuilder sb = new StringBuilder(1024);
appendArray(sb, delim, array);
return sb.toString();
}
/**
* Append {@code String} representation of array to sb.
*
* @param sb
* The {@link StringBuilder} to append to.
* @param delim
* The delimiter between elements.
* @param array
* The array to represent. Will not be {@code null}, but may contain
* {@code null} elements.
*/
abstract void appendArray(StringBuilder sb, char delim, A array);
/**
* By default returns {@code true} if oid matches the arrayOid
* this instance was instantiated with.
*/
@Override
public boolean supportBinaryRepresentation(int oid) {
return oid == arrayOid;
}
}
/**
* Base class to provide support for {@code Number} based arrays.
*
* @param
* The base type of array.
*/
private abstract static class NumberArrayEncoder extends AbstractArrayEncoder {
private final int fieldSize;
/**
*
* @param fieldSize
* The fixed size to represent each value in binary.
* @param oid
* The base type oid.
* @param arrayOid
* The array type oid.
*/
NumberArrayEncoder(int fieldSize, int oid, int arrayOid) {
super(oid, arrayOid);
this.fieldSize = fieldSize;
}
/**
* {@inheritDoc}
*/
@Override
final int countNulls(N[] array) {
int count = 0;
for (int i = 0; i < array.length; ++i) {
if (array[i] == null) {
++count;
}
}
return count;
}
/**
* {@inheritDoc}
*/
@Override
public final byte[] toBinaryRepresentation(BaseConnection connection, N[] array, int oid)
throws SQLException, SQLFeatureNotSupportedException {
assert oid == this.arrayOid;
final int nullCount = countNulls(array);
final byte[] bytes = writeBytes(array, nullCount, 20);
// 1 dimension
ByteConverter.int4(bytes, 0, 1);
// no null
ByteConverter.int4(bytes, 4, nullCount == 0 ? 0 : 1);
// oid
ByteConverter.int4(bytes, 8, getTypeOID(oid));
// length
ByteConverter.int4(bytes, 12, array.length);
// postgresql uses 1 base by default
ByteConverter.int4(bytes, 16, 1);
return bytes;
}
/**
* {@inheritDoc}
*/
@Override
final byte[] toSingleDimensionBinaryRepresentation(BaseConnection connection, N[] array)
throws SQLException, SQLFeatureNotSupportedException {
final int nullCount = countNulls(array);
return writeBytes(array, nullCount, 0);
}
private byte[] writeBytes(final N[] array, final int nullCount, final int offset) {
final int length = offset + (4 * array.length) + (fieldSize * (array.length - nullCount));
final byte[] bytes = new byte[length];
int idx = offset;
for (int i = 0; i < array.length; ++i) {
if (array[i] == null) {
ByteConverter.int4(bytes, idx, -1);
idx += 4;
} else {
ByteConverter.int4(bytes, idx, fieldSize);
idx += 4;
write(array[i], bytes, idx);
idx += fieldSize;
}
}
return bytes;
}
/**
* Write single value (number) to bytes beginning at
* offset.
*
* @param number
* The value to write to bytes. This will never be {@code null}.
* @param bytes
* The {@code byte[]} to write to.
* @param offset
* The offset into bytes to write the number value.
*/
protected abstract void write(N number, byte[] bytes, int offset);
/**
* {@inheritDoc}
*/
@Override
final void appendArray(StringBuilder sb, char delim, N[] array) {
sb.append('{');
for (int i = 0; i < array.length; ++i) {
if (i != 0) {
sb.append(delim);
}
if (array[i] == null) {
sb.append('N').append('U').append('L').append('L');
} else {
sb.append('"');
sb.append(array[i].toString());
sb.append('"');
}
}
sb.append('}');
}
}
/**
* Base support for primitive arrays.
*
* @param
* The primitive array to support.
*/
private abstract static class FixedSizePrimitiveArrayEncoder
extends AbstractArrayEncoder {
private final int fieldSize;
FixedSizePrimitiveArrayEncoder(int fieldSize, int oid, int arrayOid) {
super(oid, arrayOid);
this.fieldSize = fieldSize;
}
/**
* {@inheritDoc}
*
*
* Always returns {@code 0}.
*
*/
@Override
final int countNulls(A array) {
return 0;
}
/**
* {@inheritDoc}
*/
@Override
public final byte[] toBinaryRepresentation(BaseConnection connection, A array, int oid)
throws SQLException, SQLFeatureNotSupportedException {
assert oid == arrayOid;
final int arrayLength = Array.getLength(array);
final int length = 20 + ((fieldSize + 4) * arrayLength);
final byte[] bytes = new byte[length];
// 1 dimension
ByteConverter.int4(bytes, 0, 1);
// no null
ByteConverter.int4(bytes, 4, 0);
// oid
ByteConverter.int4(bytes, 8, getTypeOID(oid));
// length
ByteConverter.int4(bytes, 12, arrayLength);
// postgresql uses 1 base by default
ByteConverter.int4(bytes, 16, 1);
write(array, bytes, 20);
return bytes;
}
/**
* {@inheritDoc}
*/
@Override
final byte[] toSingleDimensionBinaryRepresentation(BaseConnection connection, A array)
throws SQLException, SQLFeatureNotSupportedException {
final int length = ((fieldSize + 4) * Array.getLength(array));
final byte[] bytes = new byte[length];
write(array, bytes, 0);
return bytes;
}
/**
* Write the entire contents of array to bytes starting at
* offset without metadata describing type or length.
*
* @param array
* The array to write.
* @param bytes
* The {@code byte[]} to write to.
* @param offset
* The offset into bytes to start writing.
*/
protected abstract void write(A array, byte[] bytes, int offset);
}
private static final AbstractArrayEncoder LONG_ARRAY = new FixedSizePrimitiveArrayEncoder(8, Oid.INT8,
Oid.INT8_ARRAY) {
/**
* {@inheritDoc}
*/
@Override
public void appendArray(StringBuilder sb, char delim, long[] array) {
sb.append('{');
for (int i = 0; i < array.length; ++i) {
if (i > 0) {
sb.append(delim);
}
sb.append(array[i]);
}
sb.append('}');
}
/**
* {@inheritDoc}
*/
@Override
protected void write(long[] array, byte[] bytes, int offset) {
int idx = offset;
for (int i = 0; i < array.length; ++i) {
bytes[idx + 3] = 8;
ByteConverter.int8(bytes, idx + 4, array[i]);
idx += 12;
}
}
};
private static final AbstractArrayEncoder LONG_OBJ_ARRAY = new NumberArrayEncoder(8, Oid.INT8,
Oid.INT8_ARRAY) {
@Override
protected void write(Long number, byte[] bytes, int offset) {
ByteConverter.int8(bytes, offset, number.longValue());
}
};
private static final AbstractArrayEncoder INT_ARRAY = new FixedSizePrimitiveArrayEncoder(4, Oid.INT4,
Oid.INT4_ARRAY) {
/**
* {@inheritDoc}
*/
@Override
public void appendArray(StringBuilder sb, char delim, int[] array) {
sb.append('{');
for (int i = 0; i < array.length; ++i) {
if (i > 0) {
sb.append(delim);
}
sb.append(array[i]);
}
sb.append('}');
}
/**
* {@inheritDoc}
*/
@Override
protected void write(int[] array, byte[] bytes, int offset) {
int idx = offset;
for (int i = 0; i < array.length; ++i) {
bytes[idx + 3] = 4;
ByteConverter.int4(bytes, idx + 4, array[i]);
idx += 8;
}
}
};
private static final AbstractArrayEncoder INT_OBJ_ARRAY = new NumberArrayEncoder(4, Oid.INT4,
Oid.INT4_ARRAY) {
@Override
protected void write(Integer number, byte[] bytes, int offset) {
ByteConverter.int4(bytes, offset, number.intValue());
}
};
private static final AbstractArrayEncoder SHORT_ARRAY = new FixedSizePrimitiveArrayEncoder(2,
Oid.INT2, Oid.INT2_ARRAY) {
/**
* {@inheritDoc}
*/
@Override
public void appendArray(StringBuilder sb, char delim, short[] array) {
sb.append('{');
for (int i = 0; i < array.length; ++i) {
if (i > 0) {
sb.append(delim);
}
sb.append(array[i]);
}
sb.append('}');
}
/**
* {@inheritDoc}
*/
@Override
protected void write(short[] array, byte[] bytes, int offset) {
int idx = offset;
for (int i = 0; i < array.length; ++i) {
bytes[idx + 3] = 2;
ByteConverter.int2(bytes, idx + 4, array[i]);
idx += 6;
}
}
};
private static final AbstractArrayEncoder SHORT_OBJ_ARRAY = new NumberArrayEncoder(2, Oid.INT2,
Oid.INT2_ARRAY) {
/**
* {@inheritDoc}
*/
@Override
protected void write(Short number, byte[] bytes, int offset) {
ByteConverter.int2(bytes, offset, number.shortValue());
}
};
private static final AbstractArrayEncoder DOUBLE_ARRAY = new FixedSizePrimitiveArrayEncoder(8,
Oid.FLOAT8, Oid.FLOAT8_ARRAY) {
/**
* {@inheritDoc}
*/
@Override
public void appendArray(StringBuilder sb, char delim, double[] array) {
sb.append('{');
for (int i = 0; i < array.length; ++i) {
if (i > 0) {
sb.append(delim);
}
// use quotes to account for any issues with scientific notation
sb.append('"');
sb.append(array[i]);
sb.append('"');
}
sb.append('}');
}
/**
* {@inheritDoc}
*/
@Override
protected void write(double[] array, byte[] bytes, int offset) {
int idx = offset;
for (int i = 0; i < array.length; ++i) {
bytes[idx + 3] = 8;
ByteConverter.float8(bytes, idx + 4, array[i]);
idx += 12;
}
}
};
private static final AbstractArrayEncoder DOUBLE_OBJ_ARRAY = new NumberArrayEncoder(8, Oid.FLOAT8,
Oid.FLOAT8_ARRAY) {
/**
* {@inheritDoc}
*/
@Override
protected void write(Double number, byte[] bytes, int offset) {
ByteConverter.float8(bytes, offset, number.doubleValue());
}
};
private static final AbstractArrayEncoder FLOAT_ARRAY = new FixedSizePrimitiveArrayEncoder(4,
Oid.FLOAT4, Oid.FLOAT4_ARRAY) {
/**
* {@inheritDoc}
*/
@Override
public void appendArray(StringBuilder sb, char delim, float[] array) {
sb.append('{');
for (int i = 0; i < array.length; ++i) {
if (i > 0) {
sb.append(delim);
}
// use quotes to account for any issues with scientific notation
sb.append('"');
sb.append(array[i]);
sb.append('"');
}
sb.append('}');
}
/**
* {@inheritDoc}
*/
@Override
protected void write(float[] array, byte[] bytes, int offset) {
int idx = offset;
for (int i = 0; i < array.length; ++i) {
bytes[idx + 3] = 4;
ByteConverter.float4(bytes, idx + 4, array[i]);
idx += 8;
}
}
};
private static final AbstractArrayEncoder FLOAT_OBJ_ARRAY = new NumberArrayEncoder(4, Oid.FLOAT4,
Oid.FLOAT4_ARRAY) {
/**
* {@inheritDoc}
*/
@Override
protected void write(Float number, byte[] bytes, int offset) {
ByteConverter.float4(bytes, offset, number.floatValue());
}
};
private static final AbstractArrayEncoder BOOLEAN_ARRAY = new FixedSizePrimitiveArrayEncoder(1,
Oid.BOOL, Oid.BOOL_ARRAY) {
/**
* {@inheritDoc}
*/
@Override
public void appendArray(StringBuilder sb, char delim, boolean[] array) {
sb.append('{');
for (int i = 0; i < array.length; ++i) {
if (i > 0) {
sb.append(delim);
}
sb.append(array[i] ? '1' : '0');
}
sb.append('}');
}
/**
* {@inheritDoc}
*/
@Override
protected void write(boolean[] array, byte[] bytes, int offset) {
int idx = offset;
for (int i = 0; i < array.length; ++i) {
bytes[idx + 3] = 1;
ByteConverter.bool(bytes, idx + 4, array[i]);
idx += 5;
}
}
};
private static final AbstractArrayEncoder BOOLEAN_OBJ_ARRAY = new AbstractArrayEncoder(Oid.BOOL,
Oid.BOOL_ARRAY) {
/**
* {@inheritDoc}
*/
@Override
public byte[] toBinaryRepresentation(BaseConnection connection, Boolean[] array, int oid)
throws SQLException, SQLFeatureNotSupportedException {
assert oid == arrayOid;
final int nullCount = countNulls(array);
final byte[] bytes = writeBytes(array, nullCount, 20);
// 1 dimension
ByteConverter.int4(bytes, 0, 1);
// no null
ByteConverter.int4(bytes, 4, nullCount == 0 ? 0 : 1);
// oid
ByteConverter.int4(bytes, 8, getTypeOID(oid));
// length
ByteConverter.int4(bytes, 12, array.length);
// postgresql uses 1 base by default
ByteConverter.int4(bytes, 16, 1);
return bytes;
}
private byte[] writeBytes(final Boolean[] array, final int nullCount, final int offset) {
final int length = offset + (4 * array.length) + (array.length - nullCount);
final byte[] bytes = new byte[length];
int idx = offset;
for (int i = 0; i < array.length; ++i) {
if (array[i] == null) {
ByteConverter.int4(bytes, idx, -1);
idx += 4;
} else {
ByteConverter.int4(bytes, idx, 1);
idx += 4;
write(array[i], bytes, idx);
++idx;
}
}
return bytes;
}
private void write(Boolean bool, byte[] bytes, int idx) {
ByteConverter.bool(bytes, idx, bool.booleanValue());
}
/**
* {@inheritDoc}
*/
@Override
byte[] toSingleDimensionBinaryRepresentation(BaseConnection connection, Boolean[] array)
throws SQLException, SQLFeatureNotSupportedException {
final int nullCount = countNulls(array);
return writeBytes(array, nullCount, 0);
}
/**
* {@inheritDoc}
*/
@Override
void appendArray(StringBuilder sb, char delim, Boolean[] array) {
sb.append('{');
for (int i = 0; i < array.length; ++i) {
if (i != 0) {
sb.append(delim);
}
if (array[i] == null) {
sb.append('N').append('U').append('L').append('L');
} else {
sb.append(array[i].booleanValue() ? '1' : '0');
}
}
sb.append('}');
}
};
private static final AbstractArrayEncoder STRING_ARRAY = new AbstractArrayEncoder(Oid.VARCHAR,
Oid.VARCHAR_ARRAY) {
/**
* {@inheritDoc}
*/
@Override
int countNulls(String[] array) {
int count = 0;
for (int i = 0; i < array.length; ++i) {
if (array[i] == null) {
++count;
}
}
return count;
}
/**
* {@inheritDoc}
*/
@Override
public boolean supportBinaryRepresentation(int oid) {
return oid == Oid.VARCHAR_ARRAY || oid == Oid.TEXT_ARRAY;
}
/**
* {@inheritDoc}
*/
@Override
int getTypeOID(int arrayOid) {
if (arrayOid == Oid.VARCHAR_ARRAY) {
return Oid.VARCHAR;
}
if (arrayOid == Oid.TEXT_ARRAY) {
return Oid.TEXT;
}
// this should not be possible based on supportBinaryRepresentation returning
// false for all other types
throw new IllegalStateException("Invalid array oid: " + arrayOid);
}
/**
* {@inheritDoc}
*/
@Override
public void appendArray(StringBuilder sb, char delim, String[] array) {
sb.append('{');
for (int i = 0; i < array.length; ++i) {
if (i > 0) {
sb.append(delim);
}
if (array[i] == null) {
sb.append('N').append('U').append('L').append('L');
} else {
PgArray.escapeArrayElement(sb, array[i]);
}
}
sb.append('}');
}
/**
* {@inheritDoc}
*/
@Override
public byte[] toBinaryRepresentation(BaseConnection connection, String[] array, int oid) throws SQLException {
final ByteArrayOutputStream baos = new ByteArrayOutputStream(Math.min(1024, (array.length * 32) + 20));
assert supportBinaryRepresentation(oid);
final byte[] buffer = new byte[4];
try {
// 1 dimension
ByteConverter.int4(buffer, 0, 1);
baos.write(buffer);
// null
ByteConverter.int4(buffer, 0, countNulls(array) > 0 ? 1 : 0);
baos.write(buffer);
// oid
ByteConverter.int4(buffer, 0, getTypeOID(oid));
baos.write(buffer);
// length
ByteConverter.int4(buffer, 0, array.length);
baos.write(buffer);
// postgresql uses 1 base by default
ByteConverter.int4(buffer, 0, 1);
baos.write(buffer);
final Encoding encoding = connection.getEncoding();
for (int i = 0; i < array.length; ++i) {
final String string = array[i];
if (string != null) {
final byte[] encoded;
try {
encoded = encoding.encode(string);
} catch (IOException e) {
throw new PSQLException(GT.tr("Unable to translate data into the desired encoding."),
PSQLState.DATA_ERROR, e);
}
ByteConverter.int4(buffer, 0, encoded.length);
baos.write(buffer);
baos.write(encoded);
} else {
ByteConverter.int4(buffer, 0, -1);
baos.write(buffer);
}
}
return baos.toByteArray();
} catch (IOException e) {
// this IO exception is from writing to baos, which will never throw an
// IOException
throw new java.lang.AssertionError(e);
}
}
/**
* {@inheritDoc}
*/
@Override
byte[] toSingleDimensionBinaryRepresentation(BaseConnection connection, String[] array)
throws SQLException, SQLFeatureNotSupportedException {
try {
final ByteArrayOutputStream baos = new ByteArrayOutputStream(Math.min(1024, (array.length * 32) + 20));
final byte[] buffer = new byte[4];
final Encoding encoding = connection.getEncoding();
for (int i = 0; i < array.length; ++i) {
final String string = array[i];
if (string != null) {
final byte[] encoded;
try {
encoded = encoding.encode(string);
} catch (IOException e) {
throw new PSQLException(GT.tr("Unable to translate data into the desired encoding."),
PSQLState.DATA_ERROR, e);
}
ByteConverter.int4(buffer, 0, encoded.length);
baos.write(buffer);
baos.write(encoded);
} else {
ByteConverter.int4(buffer, 0, -1);
baos.write(buffer);
}
}
return baos.toByteArray();
} catch (IOException e) {
// this IO exception is from writing to baos, which will never throw an
// IOException
throw new java.lang.AssertionError(e);
}
}
};
private static final AbstractArrayEncoder BYTEA_ARRAY = new AbstractArrayEncoder(Oid.BYTEA,
Oid.BYTEA_ARRAY) {
/**
* The possible characters to use for representing hex binary data.
*/
private final char[] hexDigits = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd',
'e', 'f' };
/**
* {@inheritDoc}
*/
@Override
public byte[] toBinaryRepresentation(BaseConnection connection, byte[][] array, int oid)
throws SQLException, SQLFeatureNotSupportedException {
assert oid == arrayOid;
int length = 20;
for (int i = 0; i < array.length; ++i) {
length += 4;
if (array[i] != null) {
length += array[i].length;
}
}
final byte[] bytes = new byte[length];
// 1 dimension
ByteConverter.int4(bytes, 0, 1);
// no null
ByteConverter.int4(bytes, 4, 0);
// oid
ByteConverter.int4(bytes, 8, getTypeOID(oid));
// length
ByteConverter.int4(bytes, 12, array.length);
// postgresql uses 1 base by default
ByteConverter.int4(bytes, 16, 1);
write(array, bytes, 20);
return bytes;
}
/**
* {@inheritDoc}
*/
@Override
byte[] toSingleDimensionBinaryRepresentation(BaseConnection connection, byte[][] array)
throws SQLException, SQLFeatureNotSupportedException {
int length = 0;
for (int i = 0; i < array.length; ++i) {
length += 4;
if (array[i] != null) {
length += array[i].length;
}
}
final byte[] bytes = new byte[length];
write(array, bytes, 0);
return bytes;
}
/**
* {@inheritDoc}
*/
@Override
int countNulls(byte[][] array) {
int nulls = 0;
for (int i = 0; i < array.length; ++i) {
if (array[i] == null) {
++nulls;
}
}
return nulls;
}
private void write(byte[][] array, byte[] bytes, int offset) {
int idx = offset;
for (int i = 0; i < array.length; ++i) {
if (array[i] != null) {
ByteConverter.int4(bytes, idx, array[i].length);
idx += 4;
System.arraycopy(array[i], 0, bytes, idx, array[i].length);
idx += array[i].length;
} else {
ByteConverter.int4(bytes, idx, -1);
idx += 4;
}
}
}
/**
* {@inheritDoc}
*/
@Override
void appendArray(StringBuilder sb, char delim, byte[][] array) {
sb.append('{');
for (int i = 0; i < array.length; ++i) {
if (i > 0) {
sb.append(delim);
}
if (array[i] != null) {
sb.append("\"\\\\x");
for (int j = 0; j < array[i].length; ++j) {
byte b = array[i][j];
// get the value for the left 4 bits (drop sign)
sb.append(hexDigits[(b & 0xF0) >>> 4]);
// get the value for the right 4 bits
sb.append(hexDigits[b & 0x0F]);
}
sb.append('"');
} else {
sb.append("NULL");
}
}
sb.append('}');
}
};
private static final AbstractArrayEncoder
© 2015 - 2025 Weber Informatics LLC | Privacy Policy