/** * 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 ValueLobDb createTempClob(Reader in, long length, DataHandler handler) { BufferedReader reader; if (in instanceof BufferedReader) { reader = (BufferedReader) in; } else { reader = new BufferedReader(in, Constants.IO_BUFFER_SIZE); } try { boolean compress = handler.getLobCompressionAlgorithm(Value.CLOB) != null; long remaining = Long.MAX_VALUE; if (length >= 0 && length < remaining) { remaining = length; } int len = getBufferSize(handler, compress, 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()) { byte[] small = new String(buff, 0, len).getBytes(Constants.UTF8); return ValueLobDb.createSmallLob(Value.CLOB, small, len); } reader.reset(); ValueLobDb lob = new ValueLobDb(handler, reader, remaining); return lob; } catch (IOException e) { throw DbException.convertIOException(e, null); } }
/** * Create a temporary BLOB value from a stream. * * @param in the input stream * @param length the number of characters to read, or -1 for no limit * @param handler the data handler * @return the lob value */ public static ValueLobDb createTempBlob(InputStream in, long length, DataHandler handler) { try { long remaining = Long.MAX_VALUE; boolean compress = handler.getLobCompressionAlgorithm(Value.BLOB) != null; if (length >= 0 && length < remaining) { remaining = length; } int len = getBufferSize(handler, compress, remaining); byte[] buff; if (len >= Integer.MAX_VALUE) { buff = IOUtils.readBytesAndClose(in, -1); len = buff.length; } else { buff = DataUtils.newBytes(len); len = IOUtils.readFully(in, buff, len); } if (len <= handler.getMaxLengthInplaceLob()) { byte[] small = DataUtils.newBytes(len); System.arraycopy(buff, 0, small, 0, len); return ValueLobDb.createSmallLob(Value.BLOB, small, small.length); } ValueLobDb lob = new ValueLobDb(handler, buff, len, in, remaining); return lob; } catch (IOException e) { throw DbException.convertIOException(e, null); } }
@Override public void remove() { if (fileName != null) { if (tempFile != null) { tempFile.stopAutoDelete(); } // synchronize on the database, to avoid concurrent temp file // creation / deletion / backup synchronized (handler.getLobSyncObject()) { FileUtils.delete(fileName); } } if (handler != null) { handler.getLobStorage().removeLob(this); } }
private static String createTempLobFileName(DataHandler handler) throws IOException { String path = handler.getDatabasePath(); if (path.length() == 0) { path = SysProperties.PREFIX_TEMP_FILE; } return FileUtils.createTempFile(path, Constants.SUFFIX_TEMP_FILE, true, true); }
@Override public InputStream getInputStream() { if (small != null) { return new ByteArrayInputStream(small); } else if (fileName != null) { FileStore store = handler.openFile(fileName, "r", true); boolean alwaysClose = SysProperties.lobCloseBetweenReads; return new BufferedInputStream( new FileStoreInputStream(store, handler, false, alwaysClose), Constants.IO_BUFFER_SIZE); } long byteCount = (type == Value.BLOB) ? precision : -1; try { return handler.getLobStorage().getInputStream(this, hmac, byteCount); } catch (IOException e) { throw DbException.convertIOException(e, toString()); } }
@Override public Value copy(DataHandler database, int tableId) { if (small == null) { Value v2 = handler.getLobStorage().copyLob(this, tableId, getPrecision()); return v2; } else if (small.length > database.getMaxLengthInplaceLob()) { LobStorageInterface s = database.getLobStorage(); Value v; if (type == Value.BLOB) { v = s.createBlob(getInputStream(), getPrecision()); } else { v = s.createClob(getReader(), getPrecision()); } Value v2 = v.copy(database, tableId); v.remove(); return v2; } return this; }
/** * Create an independent copy of this value, that will be bound to a result. * * @return the value (this for small objects) */ @Override public ValueLobDb copyToResult() { if (handler == null) { return this; } LobStorageInterface s = handler.getLobStorage(); if (s.isReadOnly()) { return this; } return s.copyLob(this, LobStorageFrontend.TABLE_RESULT, getPrecision()); }
/** * Convert a lob to another data type. The data is fully read in memory except when converting to * BLOB or CLOB. * * @param t the new type * @return the converted value */ @Override public Value convertTo(int t) { if (t == type) { return this; } else if (t == Value.CLOB) { if (handler != null) { Value copy = handler.getLobStorage().createClob(getReader(), -1); return copy; } else if (small != null) { return ValueLobDb.createSmallLob(t, small); } } else if (t == Value.BLOB) { if (handler != null) { Value copy = handler.getLobStorage().createBlob(getInputStream(), -1); return copy; } else if (small != null) { return ValueLobDb.createSmallLob(t, small); } } return super.convertTo(t); }
private static int getBufferSize(DataHandler handler, boolean compress, long remaining) { if (remaining < 0 || remaining > Integer.MAX_VALUE) { remaining = Integer.MAX_VALUE; } int inplace = handler.getMaxLengthInplaceLob(); long m = compress ? Constants.IO_BUFFER_SIZE_COMPRESS : Constants.IO_BUFFER_SIZE; if (m < remaining && m <= inplace) { // using "1L" to force long arithmetic because // inplace could be Integer.MAX_VALUE m = Math.min(remaining, inplace + 1L); // the buffer size must be bigger than the inplace lob, otherwise we // can't know if it must be stored in-place or not m = MathUtils.roundUpLong(m, Constants.IO_BUFFER_SIZE); } m = Math.min(remaining, m); m = MathUtils.convertLongToInt(m); if (m < 0) { m = Integer.MAX_VALUE; } return (int) m; }
/** * Read a value. * * @return the value */ private Value readValue(ByteBuffer buff) { int type = buff.get() & 255; switch (type) { case Value.NULL: return ValueNull.INSTANCE; case BOOLEAN_TRUE: return ValueBoolean.get(true); case BOOLEAN_FALSE: return ValueBoolean.get(false); case INT_NEG: return ValueInt.get(-readVarInt(buff)); case Value.INT: return ValueInt.get(readVarInt(buff)); case LONG_NEG: return ValueLong.get(-readVarLong(buff)); case Value.LONG: return ValueLong.get(readVarLong(buff)); case Value.BYTE: return ValueByte.get(buff.get()); case Value.SHORT: return ValueShort.get(buff.getShort()); case DECIMAL_0_1: return (ValueDecimal) ValueDecimal.ZERO; case DECIMAL_0_1 + 1: return (ValueDecimal) ValueDecimal.ONE; case DECIMAL_SMALL_0: return ValueDecimal.get(BigDecimal.valueOf(readVarLong(buff))); case DECIMAL_SMALL: { int scale = readVarInt(buff); return ValueDecimal.get(BigDecimal.valueOf(readVarLong(buff), scale)); } case Value.DECIMAL: { int scale = readVarInt(buff); int len = readVarInt(buff); byte[] buff2 = DataUtils.newBytes(len); buff.get(buff2, 0, len); BigInteger b = new BigInteger(buff2); return ValueDecimal.get(new BigDecimal(b, scale)); } case LOCAL_DATE: { return ValueDate.fromDateValue(readVarLong(buff)); } case Value.DATE: { long x = readVarLong(buff) * MILLIS_PER_MINUTE; return ValueDate.get(new Date(DateTimeUtils.getTimeUTCWithoutDst(x))); } case LOCAL_TIME: { long nanos = readVarLong(buff) * 1000000 + readVarLong(buff); return ValueTime.fromNanos(nanos); } case Value.TIME: // need to normalize the year, month and day return ValueTime.get(new Time(DateTimeUtils.getTimeUTCWithoutDst(readVarLong(buff)))); case LOCAL_TIMESTAMP: { long dateValue = readVarLong(buff); long nanos = readVarLong(buff) * 1000000 + readVarLong(buff); return ValueTimestamp.fromDateValueAndNanos(dateValue, nanos); } case Value.TIMESTAMP: { Timestamp ts = new Timestamp(DateTimeUtils.getTimeUTCWithoutDst(readVarLong(buff))); ts.setNanos(readVarInt(buff)); return ValueTimestamp.get(ts); } case Value.BYTES: { int len = readVarInt(buff); byte[] b = DataUtils.newBytes(len); buff.get(b, 0, len); return ValueBytes.getNoCopy(b); } case Value.JAVA_OBJECT: { int len = readVarInt(buff); byte[] b = DataUtils.newBytes(len); buff.get(b, 0, len); return ValueJavaObject.getNoCopy(null, b); } case Value.UUID: return ValueUuid.get(buff.getLong(), buff.getLong()); case Value.STRING: return ValueString.get(readString(buff)); case Value.STRING_IGNORECASE: return ValueStringIgnoreCase.get(readString(buff)); case Value.STRING_FIXED: return ValueStringFixed.get(readString(buff)); case FLOAT_0_1: return ValueFloat.get(0); case FLOAT_0_1 + 1: return ValueFloat.get(1); case DOUBLE_0_1: return ValueDouble.get(0); case DOUBLE_0_1 + 1: return ValueDouble.get(1); case Value.DOUBLE: return ValueDouble.get(Double.longBitsToDouble(Long.reverse(readVarLong(buff)))); case Value.FLOAT: return ValueFloat.get(Float.intBitsToFloat(Integer.reverse(readVarInt(buff)))); case Value.BLOB: case Value.CLOB: { int smallLen = readVarInt(buff); if (smallLen >= 0) { byte[] small = DataUtils.newBytes(smallLen); buff.get(small, 0, smallLen); return LobStorageFrontend.createSmallLob(type, small); } else if (smallLen == -3) { int tableId = readVarInt(buff); long lobId = readVarLong(buff); long precision = readVarLong(buff); LobStorageInterface lobStorage = handler.getLobStorage(); ValueLobDb lob = ValueLobDb.create(type, lobStorage, tableId, lobId, null, precision); return lob; } else { int tableId = readVarInt(buff); int objectId = readVarInt(buff); long precision = 0; boolean compression = false; // -1: regular // -2: regular, but not linked (in this case: including file name) if (smallLen == -1 || smallLen == -2) { precision = readVarLong(buff); compression = buff.get() == 1; } if (smallLen == -2) { String filename = readString(buff); return ValueLob.openUnlinked( type, handler, tableId, objectId, precision, compression, filename); } ValueLob lob = ValueLob.openLinked(type, handler, tableId, objectId, precision, compression); return lob; } } case Value.ARRAY: { int len = readVarInt(buff); Value[] list = new Value[len]; for (int i = 0; i < len; i++) { list[i] = readValue(buff); } return ValueArray.get(list); } case Value.RESULT_SET: { SimpleResultSet rs = new SimpleResultSet(); int columns = readVarInt(buff); for (int i = 0; i < columns; i++) { rs.addColumn(readString(buff), readVarInt(buff), readVarInt(buff), readVarInt(buff)); } while (true) { if (buff.get() == 0) { break; } Object[] o = new Object[columns]; for (int i = 0; i < columns; i++) { o[i] = readValue(buff).getObject(); } rs.addRow(o); } return ValueResultSet.get(rs); } default: if (type >= INT_0_15 && type < INT_0_15 + 16) { return ValueInt.get(type - INT_0_15); } else if (type >= LONG_0_7 && type < LONG_0_7 + 8) { return ValueLong.get(type - LONG_0_7); } else if (type >= BYTES_0_31 && type < BYTES_0_31 + 32) { int len = type - BYTES_0_31; byte[] b = DataUtils.newBytes(len); buff.get(b, 0, len); return ValueBytes.getNoCopy(b); } else if (type >= STRING_0_31 && type < STRING_0_31 + 32) { return ValueString.get(readString(buff, type - STRING_0_31)); } throw DbException.get(ErrorCode.FILE_CORRUPTED_1, "type: " + type); } }