
org.h2.value.ValueClob Maven / Gradle / Ivy
/*
* Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (https://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.value;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import org.h2.engine.CastDataProvider;
import org.h2.engine.Constants;
import org.h2.engine.SysProperties;
import org.h2.message.DbException;
import org.h2.store.DataHandler;
import org.h2.store.FileStore;
import org.h2.store.FileStoreOutputStream;
import org.h2.store.LobStorageInterface;
import org.h2.store.RangeReader;
import org.h2.util.Bits;
import org.h2.util.IOUtils;
import org.h2.util.MathUtils;
import org.h2.util.StringUtils;
import org.h2.value.lob.LobData;
import org.h2.value.lob.LobDataDatabase;
import org.h2.value.lob.LobDataFetchOnDemand;
import org.h2.value.lob.LobDataFile;
import org.h2.value.lob.LobDataInMemory;
/**
* Implementation of the CHARACTER LARGE OBJECT data type.
*/
public final class ValueClob extends ValueLob {
/**
* Creates a small CLOB value that can be stored in the row directly.
*
* @param data
* the data in UTF-8 encoding
* @return the CLOB
*/
public static ValueClob createSmall(byte[] data) {
return new ValueClob(new LobDataInMemory(data), data.length,
new String(data, StandardCharsets.UTF_8).length());
}
/**
* Creates a small CLOB value that can be stored in the row directly.
*
* @param data
* the data in UTF-8 encoding
* @param charLength
* the count of characters, must be exactly the same as count of
* characters in the data
* @return the CLOB
*/
public static ValueClob createSmall(byte[] data, long charLength) {
return new ValueClob(new LobDataInMemory(data), data.length, charLength);
}
/**
* Creates a small CLOB value that can be stored in the row directly.
*
* @param string
* the string with value
* @return the CLOB
*/
public static ValueClob createSmall(String string) {
byte[] bytes = string.getBytes(StandardCharsets.UTF_8);
return new ValueClob(new LobDataInMemory(bytes), bytes.length, string.length());
}
/**
* Create a temporary CLOB value from a stream.
*
* @param in
* the reader
* @param length
* the number of characters to read, or -1 for no limit
* @param handler
* the data handler
* @return the lob value
*/
public static ValueClob createTempClob(Reader in, long length, DataHandler handler) {
if (length >= 0) {
// Otherwise BufferedReader may try to read more data than needed
// and that
// blocks the network level
try {
in = new RangeReader(in, 0, length);
} catch (IOException e) {
throw DbException.convert(e);
}
}
BufferedReader reader;
if (in instanceof BufferedReader) {
reader = (BufferedReader) in;
} else {
reader = new BufferedReader(in, Constants.IO_BUFFER_SIZE);
}
try {
long remaining = Long.MAX_VALUE;
if (length >= 0 && length < remaining) {
remaining = length;
}
int len = ValueLob.getBufferSize(handler, remaining);
char[] buff;
if (len >= Integer.MAX_VALUE) {
String data = IOUtils.readStringAndClose(reader, -1);
buff = data.toCharArray();
len = buff.length;
} else {
buff = new char[len];
reader.mark(len);
len = IOUtils.readFully(reader, buff, len);
}
if (len <= handler.getMaxLengthInplaceLob()) {
return ValueClob.createSmall(new String(buff, 0, len));
}
reader.reset();
return createTemporary(handler, reader, remaining);
} catch (IOException e) {
throw DbException.convertIOException(e, null);
}
}
/**
* Create a CLOB in a temporary file.
*/
private static ValueClob createTemporary(DataHandler handler, Reader in, long remaining) throws IOException {
String fileName = ValueLob.createTempLobFileName(handler);
FileStore tempFile = handler.openFile(fileName, "rw", false);
tempFile.autoDelete();
long octetLength = 0L, charLength = 0L;
try (FileStoreOutputStream out = new FileStoreOutputStream(tempFile, null)) {
char[] buff = new char[Constants.IO_BUFFER_SIZE];
while (true) {
int len = ValueLob.getBufferSize(handler, remaining);
len = IOUtils.readFully(in, buff, len);
if (len == 0) {
break;
}
// TODO reduce memory allocation
byte[] data = new String(buff, 0, len).getBytes(StandardCharsets.UTF_8);
out.write(data);
octetLength += data.length;
charLength += len;
}
}
return new ValueClob(new LobDataFile(handler, fileName, tempFile), octetLength, charLength);
}
public ValueClob(LobData lobData, long octetLength, long charLength) {
super(lobData, octetLength, charLength);
}
@Override
public int getValueType() {
return CLOB;
}
@Override
public String getString() {
if (charLength > Constants.MAX_STRING_LENGTH) {
throw getStringTooLong(charLength);
}
if (lobData instanceof LobDataInMemory) {
return new String(((LobDataInMemory) lobData).getSmall(), StandardCharsets.UTF_8);
}
return readString((int) charLength);
}
@Override
byte[] getBytesInternal() {
long p = octetLength;
if (p >= 0L) {
if (p > Constants.MAX_STRING_LENGTH) {
throw getBinaryTooLong(p);
}
return readBytes((int) p);
}
if (octetLength > Constants.MAX_STRING_LENGTH) {
throw getBinaryTooLong(octetLength());
}
byte[] b = readBytes(Integer.MAX_VALUE);
octetLength = p = b.length;
if (p > Constants.MAX_STRING_LENGTH) {
throw getBinaryTooLong(p);
}
return b;
}
@Override
public InputStream getInputStream() {
return lobData.getInputStream(-1L);
}
@Override
public InputStream getInputStream(long oneBasedOffset, long length) {
return rangeInputStream(lobData.getInputStream(-1L), oneBasedOffset, length, -1L);
}
@Override
public Reader getReader(long oneBasedOffset, long length) {
return rangeReader(getReader(), oneBasedOffset, length, charLength);
}
@Override
public int compareTypeSafe(Value v, CompareMode mode, CastDataProvider provider) {
if (v == this) {
return 0;
}
ValueClob v2 = (ValueClob) v;
LobData lobData = this.lobData, lobData2 = v2.lobData;
if (lobData.getClass() == lobData2.getClass()) {
if (lobData instanceof LobDataInMemory) {
return Integer.signum(getString().compareTo(v2.getString()));
} else if (lobData instanceof LobDataDatabase) {
if (((LobDataDatabase) lobData).getLobId() == ((LobDataDatabase) lobData2).getLobId()) {
return 0;
}
} else if (lobData instanceof LobDataFetchOnDemand) {
if (((LobDataFetchOnDemand) lobData).getLobId() == ((LobDataFetchOnDemand) lobData2).getLobId()) {
return 0;
}
}
}
return compare(this, v2);
}
/**
* Compares two CLOB values directly.
*
* @param v1
* first CLOB value
* @param v2
* second CLOB value
* @return result of comparison
*/
private static int compare(ValueClob v1, ValueClob v2) {
long minPrec = Math.min(v1.charLength, v2.charLength);
try (Reader reader1 = v1.getReader(); Reader reader2 = v2.getReader()) {
char[] buf1 = new char[BLOCK_COMPARISON_SIZE];
char[] buf2 = new char[BLOCK_COMPARISON_SIZE];
for (; minPrec >= BLOCK_COMPARISON_SIZE; minPrec -= BLOCK_COMPARISON_SIZE) {
if (IOUtils.readFully(reader1, buf1, BLOCK_COMPARISON_SIZE) != BLOCK_COMPARISON_SIZE
|| IOUtils.readFully(reader2, buf2, BLOCK_COMPARISON_SIZE) != BLOCK_COMPARISON_SIZE) {
throw DbException.getUnsupportedException("Invalid LOB");
}
int cmp = Bits.compareNotNull(buf1, buf2);
if (cmp != 0) {
return cmp;
}
}
for (;;) {
int c1 = reader1.read(), c2 = reader2.read();
if (c1 < 0) {
return c2 < 0 ? 0 : -1;
}
if (c2 < 0) {
return 1;
}
if (c1 != c2) {
return c1 < c2 ? -1 : 1;
}
}
} catch (IOException ex) {
throw DbException.convert(ex);
}
}
@Override
public StringBuilder getSQL(StringBuilder builder, int sqlFlags) {
if ((sqlFlags & REPLACE_LOBS_FOR_TRACE) != 0
&& (!(lobData instanceof LobDataInMemory) || charLength > SysProperties.MAX_TRACE_DATA_LENGTH)) {
builder.append("SPACE(").append(charLength);
LobDataDatabase lobDb = (LobDataDatabase) lobData;
builder.append(" /* table: ").append(lobDb.getTableId()).append(" id: ").append(lobDb.getLobId())
.append(" */)");
} else {
if ((sqlFlags & (REPLACE_LOBS_FOR_TRACE | NO_CASTS)) == 0) {
StringUtils.quoteStringSQL(builder.append("CAST("), getString()).append(" AS CHARACTER LARGE OBJECT(")
.append(charLength).append("))");
} else {
StringUtils.quoteStringSQL(builder, getString());
}
}
return builder;
}
/**
* Convert the precision to the requested value.
*
* @param precision
* the new precision
* @return the truncated or this value
*/
ValueClob convertPrecision(long precision) {
if (this.charLength <= precision) {
return this;
}
ValueClob lob;
DataHandler handler = lobData.getDataHandler();
if (handler != null) {
lob = createTempClob(getReader(), precision, handler);
} else {
try {
lob = createSmall(IOUtils.readStringAndClose(getReader(), MathUtils.convertLongToInt(precision)));
} catch (IOException e) {
throw DbException.convertIOException(e, null);
}
}
return lob;
}
@Override
public ValueLob copy(DataHandler database, int tableId) {
if (lobData instanceof LobDataInMemory) {
byte[] small = ((LobDataInMemory) lobData).getSmall();
if (small.length > database.getMaxLengthInplaceLob()) {
LobStorageInterface s = database.getLobStorage();
ValueClob v = s.createClob(getReader(), charLength);
ValueLob v2 = v.copy(database, tableId);
v.remove();
return v2;
}
return this;
} else if (lobData instanceof LobDataDatabase) {
return database.getLobStorage().copyLob(this, tableId);
} else {
throw new UnsupportedOperationException();
}
}
@Override
public long charLength() {
return charLength;
}
@Override
public long octetLength() {
long p = octetLength;
if (p < 0L) {
if (lobData instanceof LobDataInMemory) {
p = ((LobDataInMemory) lobData).getSmall().length;
} else {
try (InputStream is = getInputStream()) {
p = 0L;
for (;;) {
p += is.skip(Long.MAX_VALUE);
if (is.read() < 0) {
break;
}
p++;
}
} catch (IOException e) {
throw DbException.convertIOException(e, null);
}
}
octetLength = p;
}
return p;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy