提交 40de3268 authored 作者: Thomas Mueller's avatar Thomas Mueller

New lob storage.

上级 8ffac30c
......@@ -12,7 +12,6 @@ import org.h2.engine.Right;
import org.h2.engine.Session;
import org.h2.message.DbException;
import org.h2.schema.Schema;
import org.h2.store.LobStorage;
import org.h2.table.Table;
/**
......@@ -83,7 +82,7 @@ public class DropTable extends SchemaCommand {
table.setModified();
Database db = session.getDatabase();
db.removeSchemaObject(session, table);
LobStorage.removeAllForTable(db, dropTableId);
db.getLobStorage().removeAllForTable(dropTableId);
}
if (next != null) {
next.executeDrop();
......
......@@ -11,6 +11,7 @@ import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.sql.Connection;
import org.h2.command.Prepared;
import org.h2.constant.ErrorCode;
import org.h2.constant.SysProperties;
......@@ -228,4 +229,8 @@ public abstract class ScriptBase extends Prepared implements DataHandler {
return null;
}
public Connection getLobConnection() {
return null;
}
}
......@@ -66,7 +66,7 @@ public class SysProperties {
public static final String LINE_SEPARATOR = getStringSetting("line.separator", "\n");
/**
* System property <code>user.home</code> (default: empty string).<br />
* System property <code>user.home</code> (empty string if not set).<br />
* It is usually set by the system, and used as a replacement for ~ in file
* names.
*/
......@@ -437,6 +437,12 @@ public class SysProperties {
*/
public static final String PG_DEFAULT_CLIENT_ENCODING = getStringSetting("h2.pgClientEncoding", "UTF-8");
/**
* System property <code>h2.prefixTempFile</code> (default: h2.temp).<br />
* The prefix for temporary files in the temp directory.
*/
public static final String PREFIX_TEMP_FILE = getStringSetting("h2.prefixTempFile", "h2.temp");
/**
* System property <code>h2.recompileAlways</code> (default: false).<br />
* Always recompile prepared statements.
......
......@@ -8,6 +8,7 @@ package org.h2.engine;
import java.io.IOException;
import java.net.Socket;
import java.sql.Connection;
import java.util.ArrayList;
import org.h2.api.DatabaseEventListener;
import org.h2.command.CommandInterface;
......@@ -80,6 +81,7 @@ public class SessionRemote extends SessionWithState implements SessionFactory, D
private int lastReconnect;
private SessionInterface embedded;
private DatabaseEventListener eventListener;
private LobStorage lobStorage;
public SessionRemote() {
// nothing to do
......@@ -620,6 +622,13 @@ public class SessionRemote extends SessionWithState implements SessionFactory, D
}
public LobStorage getLobStorage() {
if (lobStorage == null) {
lobStorage = new LobStorage(this);
}
return lobStorage;
}
public Connection getLobConnection() {
return null;
}
......
......@@ -6,7 +6,9 @@
*/
package org.h2.jdbc;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.sql.Blob;
import java.sql.CallableStatement;
......@@ -32,7 +34,6 @@ import org.h2.message.DbException;
import org.h2.message.Trace;
import org.h2.message.TraceObject;
import org.h2.result.ResultInterface;
import org.h2.store.LobStorage;
import org.h2.util.Utils;
import org.h2.value.CompareMode;
import org.h2.value.Value;
......@@ -1438,7 +1439,9 @@ public class JdbcConnection extends TraceObject implements Connection {
debugCodeAssign("Clob", TraceObject.CLOB, id, "createClob()");
checkClosedForWrite();
try {
Value v = LobStorage.createSmallLob(Value.CLOB, Utils.EMPTY_BYTES);
Value v = session.getDataHandler().getLobStorage().createClob(
new InputStreamReader(
new ByteArrayInputStream(Utils.EMPTY_BYTES)), 0);
return new JdbcClob(this, v, id);
} finally {
afterWriting();
......@@ -1459,7 +1462,7 @@ public class JdbcConnection extends TraceObject implements Connection {
debugCodeAssign("Blob", TraceObject.BLOB, id, "createClob()");
checkClosedForWrite();
try {
Value v = LobStorage.createSmallLob(Value.BLOB, Utils.EMPTY_BYTES);
Value v = session.getDataHandler().getLobStorage().createBlob(new ByteArrayInputStream(Utils.EMPTY_BYTES), 0);
return new JdbcBlob(this, v, id);
} finally {
afterWriting();
......@@ -1480,8 +1483,14 @@ public class JdbcConnection extends TraceObject implements Connection {
int id = getNextId(TraceObject.CLOB);
debugCodeAssign("NClob", TraceObject.CLOB, id, "createNClob()");
checkClosedForWrite();
Value v = LobStorage.createSmallLob(Value.CLOB, Utils.EMPTY_BYTES);
return new JdbcClob(this, v, id);
try {
Value v = session.getDataHandler().getLobStorage().createClob(
new InputStreamReader(
new ByteArrayInputStream(Utils.EMPTY_BYTES)), 0);
return new JdbcClob(this, v, id);
} finally {
afterWriting();
}
} catch (Exception e) {
throw logAndConvert(e);
}
......@@ -1614,7 +1623,7 @@ public class JdbcConnection extends TraceObject implements Connection {
if (length <= 0) {
length = -1;
}
Value v = LobStorage.createClob(x, length, session.getDataHandler());
Value v = session.getDataHandler().getLobStorage().createClob(x, length);
return v;
}
......@@ -1633,7 +1642,7 @@ public class JdbcConnection extends TraceObject implements Connection {
if (length <= 0) {
length = -1;
}
Value v = LobStorage.createBlob(x, length, session.getDataHandler());
Value v = session.getDataHandler().getLobStorage().createBlob(x, length);
return v;
}
......
......@@ -32,6 +32,7 @@ import org.h2.value.ValueFloat;
import org.h2.value.ValueInt;
import org.h2.value.ValueJavaObject;
import org.h2.value.ValueLob;
import org.h2.value.ValueLob2;
import org.h2.value.ValueLong;
import org.h2.value.ValueNull;
import org.h2.value.ValueShort;
......@@ -514,25 +515,38 @@ public class Data {
case Value.BLOB:
case Value.CLOB: {
writeByte((byte) type);
ValueLob lob = (ValueLob) v;
lob.convertToFileIfRequired(handler);
byte[] small = lob.getSmall();
if (small == null) {
int t = -1;
if (!lob.isLinked()) {
t = -2;
}
writeVarInt(t);
writeVarInt(lob.getTableId());
writeVarInt(lob.getObjectId());
writeVarLong(lob.getPrecision());
writeByte((byte) (lob.useCompression() ? 1 : 0));
if (t == -2) {
writeString(lob.getFileName());
if (v instanceof ValueLob) {
ValueLob lob = (ValueLob) v;
lob.convertToFileIfRequired(handler);
byte[] small = lob.getSmall();
if (small == null) {
int t = -1;
if (!lob.isLinked()) {
t = -2;
}
writeVarInt(t);
writeVarInt(lob.getTableId());
writeVarInt(lob.getObjectId());
writeVarLong(lob.getPrecision());
writeByte((byte) (lob.useCompression() ? 1 : 0));
if (t == -2) {
writeString(lob.getFileName());
}
} else {
writeVarInt(small.length);
write(small, 0, small.length);
}
} else {
writeVarInt(small.length);
write(small, 0, small.length);
ValueLob2 lob = (ValueLob2) v;
byte[] small = lob.getSmall();
if (small == null) {
writeVarInt(-3);
writeVarLong(lob.getLobId());
writeVarLong(lob.getPrecision());
} else {
writeVarInt(small.length);
write(small, 0, small.length);
}
}
break;
}
......@@ -654,23 +668,29 @@ public class Data {
byte[] small = Utils.newBytes(smallLen);
read(small, 0, smallLen);
return LobStorage.createSmallLob(type, small);
} else if (smallLen == -3) {
long lobId = readVarLong();
long precision = readVarLong();
LobStorage lobStorage = handler.getLobStorage();
ValueLob2 lob = ValueLob2.create(type, lobStorage, null, lobId, precision);
return lob;
} else {
int tableId = readVarInt();
int objectId = readVarInt();
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();
compression = readByte() == 1;
}
ValueLob lob = ValueLob.open(type, handler, tableId, objectId, precision, compression);
if (smallLen == -2) {
lob.setFileName(readString(), false);
}
return lob;
}
int tableId = readVarInt();
int objectId = readVarInt();
long precision = 0;
boolean compression = false;
// TODO simplify
// -1: regular
// -2: regular, but not linked (in this case: including file name)
if (smallLen == -1 || smallLen == -2) {
precision = readVarLong();
compression = readByte() == 1;
}
ValueLob lob = ValueLob.open(type, handler, tableId, objectId, precision, compression);
if (smallLen == -2) {
lob.setFileName(readString(), false);
}
return lob;
}
case Value.ARRAY: {
int len = readVarInt();
......@@ -809,25 +829,38 @@ public class Data {
case Value.BLOB:
case Value.CLOB: {
int len = 1;
ValueLob lob = (ValueLob) v;
lob.convertToFileIfRequired(handler);
byte[] small = lob.getSmall();
if (small == null) {
int t = -1;
if (!lob.isLinked()) {
t = -2;
}
len += getVarIntLen(t);
len += getVarIntLen(lob.getTableId());
len += getVarIntLen(lob.getObjectId());
len += getVarLongLen(lob.getPrecision());
len += 1;
if (t == -2) {
len += getStringLen(lob.getFileName());
if (v instanceof ValueLob) {
ValueLob lob = (ValueLob) v;
lob.convertToFileIfRequired(handler);
byte[] small = lob.getSmall();
if (small == null) {
int t = -1;
if (!lob.isLinked()) {
t = -2;
}
len += getVarIntLen(t);
len += getVarIntLen(lob.getTableId());
len += getVarIntLen(lob.getObjectId());
len += getVarLongLen(lob.getPrecision());
len += 1;
if (t == -2) {
len += getStringLen(lob.getFileName());
}
} else {
len += getVarIntLen(small.length);
len += small.length;
}
} else {
len += getVarIntLen(small.length);
len += small.length;
ValueLob2 lob = (ValueLob2) v;
byte[] small = lob.getSmall();
if (small == null) {
len += getVarIntLen(-3);
len += getVarLongLen(lob.getLobId());
len += getVarLongLen(lob.getPrecision());
} else {
len += getVarIntLen(small.length);
len += small.length;
}
}
return len;
}
......
......@@ -6,6 +6,7 @@
*/
package org.h2.store;
import java.sql.Connection;
import org.h2.util.SmallLRUCache;
import org.h2.util.TempFileDeleter;
......@@ -91,6 +92,18 @@ public interface DataHandler {
*/
SmallLRUCache<String, String[]> getLobFileListCache();
/**
* Get the lob storage mechanism to use.
*
* @return the lob storage mechanism
*/
LobStorage getLobStorage();
/**
* Get a database connection to be used for LOB access.
*
* @return the connection or null
*/
Connection getLobConnection();
}
......@@ -22,16 +22,17 @@ import org.h2.engine.Constants;
import org.h2.message.DbException;
import org.h2.util.IOUtils;
import org.h2.util.New;
import org.h2.util.StringUtils;
import org.h2.util.Utils;
import org.h2.value.Value;
import org.h2.value.ValueLob;
import org.h2.value.ValueLob2;
/**
* This class stores LOB objects in the database.
*/
public class LobStorage {
/**
* The 'table id' to use for session variables.
*/
public static final int TABLE_ID_SESSION_VARIABLE = -1;
private static final String LOBS = "INFORMATION_SCHEMA.LOBS";
......@@ -46,15 +47,33 @@ public class LobStorage {
private long nextLob;
private long nextBlock;
public LobStorage(Connection newConn) {
private final DataHandler handler;
private boolean init;
public LobStorage(DataHandler handler) {
this.handler = handler;
}
/**
* Initialize the lob storage.
*/
public void init() {
if (init) {
return;
}
conn = handler.getLobConnection();
init = true;
if (conn == null) {
return;
}
int todoDatabaseGetFirstUserTable;
try {
this.conn = newConn;
Statement stat = conn.createStatement();
// stat.execute("SET UNDO_LOG 0");
// stat.execute("SET REDO_LOG_BINARY 0");
stat.execute("CREATE TABLE IF NOT EXISTS " + LOBS + "(ID BIGINT PRIMARY KEY, LENGTH BIGINT, TABLE INT)");
stat.execute("CREATE TABLE IF NOT EXISTS " + LOB_MAP + "(LOB BIGINT, SEQ INT, BLOCK BIGINT, PRIMARY KEY(LOB, SEQ))");
stat.execute("CREATE INDEX INFORMATION_SCHEMA.INDEX_LOB_MAP_DATA_LOB ON " + LOB_MAP + "(BLOCK, LOB)");
stat.execute("CREATE INDEX IF NOT EXISTS INFORMATION_SCHEMA.INDEX_LOB_MAP_DATA_LOB ON " + LOB_MAP + "(BLOCK, LOB)");
stat.execute("CREATE TABLE IF NOT EXISTS " + LOB_DATA + "(BLOCK BIGINT PRIMARY KEY, DATA BINARY)");
ResultSet rs;
rs = stat.executeQuery("SELECT MAX(BLOCK) FROM " + LOB_DATA);
......@@ -77,8 +96,10 @@ public class LobStorage {
* @param handler the data handler
* @param tableId the table id
*/
public static void removeAllForTable(DataHandler handler, int tableId) {
public void removeAllForTable(int tableId) {
if (SysProperties.LOB_IN_DATABASE) {
init();
int todo;
// remove both lobs in the database as well as in the file system
}
ValueLob.removeAllForTable(handler, tableId);
......@@ -91,50 +112,19 @@ public class LobStorage {
* @param small the byte array
* @return the LOB
*/
public static ValueLob createSmallLob(int type, byte[] small) {
public static Value createSmallLob(int type, byte[] small) {
if (SysProperties.LOB_IN_DATABASE) {
return null;
return ValueLob2.createSmallLob(type, small);
}
return ValueLob.createSmallLob(type, small);
}
/**
* Create a BLOB object.
*
* @param in the input stream
* @param maxLength the maximum length (-1 if not known)
* @param handler the data handler
* @return the LOB
*/
public static ValueLob createBlob(InputStream in, long maxLength, DataHandler handler) {
if (SysProperties.LOB_IN_DATABASE) {
handler.getLobStorage().addLob(in, maxLength, LobStorage.TABLE_ID_SESSION_VARIABLE);
}
return ValueLob.createBlob(in, maxLength, handler);
}
/**
* Create a CLOB object.
*
* @param reader the reader
* @param maxLength the maximum length (-1 if not known)
* @param handler the data handler
* @return the LOB
*/
public static ValueLob createClob(Reader reader, long maxLength, DataHandler handler) {
if (SysProperties.LOB_IN_DATABASE) {
CountingReaderInputStream in = new CountingReaderInputStream(reader);
handler.getLobStorage().addLob(in, maxLength, LobStorage.TABLE_ID_SESSION_VARIABLE);
}
return ValueLob.createClob(reader, maxLength, handler);
}
/**
* An input stream that reads from a LOB.
*/
public static class LobInputStream extends InputStream {
private Connection conn;
private final Connection conn;
private PreparedStatement prepSelect;
private byte[] buffer;
private int pos;
......@@ -143,6 +133,7 @@ public class LobStorage {
private int seq;
public LobInputStream(Connection conn, long lob) throws IOException {
this.conn = conn;
try {
this.lob = lob;
PreparedStatement prep = conn.prepareStatement(
......@@ -151,8 +142,9 @@ public class LobStorage {
ResultSet rs = prep.executeQuery();
if (!rs.next()) {
throw DbException.get(ErrorCode.IO_EXCEPTION_1, "lob: "+ lob + " seq: " + seq).getSQLException();
}
remaining = rs.getLong(1);
rs.close();
} catch (SQLException e) {
throw DbException.convertToIOException(e);
}
......@@ -257,21 +249,18 @@ public class LobStorage {
prep.execute();
}
/**
* Store the LOB in the database.
*
* @param in the input stream
* @param maxLength the maximum length (-1 for unknown)
* @param table the table
* @return the LOB id
*/
public long addLob(InputStream in, long maxLength, int table) {
public InputStream getInputStream(long lobId) throws IOException {
init();
return new LobInputStream(conn, lobId);
}
private ValueLob2 addLob(InputStream in, long maxLength, int type) {
byte[] buff = new byte[BLOCK_LENGTH];
if (maxLength < 0) {
maxLength = Long.MAX_VALUE;
}
long length = 0;
long lob = nextLob++;
long lobId = nextLob++;
try {
try {
for (int seq = 0; maxLength > 0; seq++) {
......@@ -281,7 +270,6 @@ public class LobStorage {
}
length += len;
maxLength -= len;
byte[] b;
if (len != buff.length) {
b = new byte[len];
......@@ -319,20 +307,21 @@ public class LobStorage {
}
PreparedStatement prep = prepare(
"INSERT INTO " + LOB_MAP + "(LOB, SEQ, BLOCK) VALUES(?, ?, ?)");
prep.setLong(1, lob);
prep.setLong(1, lobId);
prep.setInt(2, seq);
prep.setLong(3, block);
prep.execute();
}
PreparedStatement prep = prepare(
"INSERT INTO " + LOBS + "(ID, LENGTH, TABLE) VALUES(?, ?, ?)");
prep.setLong(1, lob);
prep.setLong(1, lobId);
prep.setLong(2, length);
prep.setInt(3, table);
prep.setInt(3, TABLE_ID_SESSION_VARIABLE);
prep.execute();
return lob;
ValueLob2 v = ValueLob2.create(type, this, null, lobId, length);
return v;
} catch (IOException e) {
deleteLob(lob);
deleteLob(lobId);
throw DbException.convertIOException(e, "adding blob");
}
} catch (SQLException e) {
......@@ -347,16 +336,87 @@ public class LobStorage {
private final Reader reader;
private long length;
private char[] buffer = new char[Constants.IO_BUFFER_SIZE];
private int pos;
private char[] charBuffer = new char[Constants.IO_BUFFER_SIZE];
private byte[] buffer;
CountingReaderInputStream(Reader reader) {
this.reader = reader;
buffer = Utils.EMPTY_BYTES;
}
public int read(byte[] buff, int offset, int len) throws IOException {
if (pos >= buffer.length) {
fillBuffer();
if (buffer == null) {
return -1;
}
}
len = Math.min(len, buffer.length - pos);
System.arraycopy(buffer, pos, buff, offset, len);
return len;
}
public int read() throws IOException {
return 0;
if (pos >= buffer.length) {
fillBuffer();
if (buffer == null) {
return -1;
}
}
return buffer[pos++];
}
private void fillBuffer() throws IOException {
int len = reader.read(charBuffer);
if (len < 0) {
buffer = null;
} else {
buffer = StringUtils.utf8Encode(new String(charBuffer, 0, len));
length += len;
}
}
public long getLength() {
return length;
}
public void close() throws IOException {
reader.close();
}
}
/**
* Create a BLOB object.
*
* @param in the input stream
* @param maxLength the maximum length (-1 if not known)
* @return the LOB
*/
public Value createBlob(InputStream in, long maxLength) {
if (SysProperties.LOB_IN_DATABASE) {
init();
if (conn == null) {
return ValueLob2.createTempBlob(in, maxLength, handler);
}
return addLob(in, maxLength, Value.BLOB);
}
return ValueLob.createBlob(in, maxLength, handler);
}
public Value createClob(Reader reader, long maxLength) {
if (SysProperties.LOB_IN_DATABASE) {
init();
if (conn == null) {
return ValueLob2.createTempClob(reader, maxLength, handler);
}
CountingReaderInputStream in = new CountingReaderInputStream(reader);
ValueLob2 lob = addLob(in, maxLength, Value.BLOB);
lob.setPrecision(in.getLength());
return lob;
}
return ValueLob.createClob(reader, maxLength, handler);
}
}
......@@ -1223,4 +1223,11 @@ public class Recover extends Tool implements DataHandler {
return null;
}
/**
* INTERNAL
*/
public Connection getLobConnection() {
return null;
}
}
......@@ -546,7 +546,7 @@ public class DataType {
if (in == null) {
v = ValueNull.INSTANCE;
} else {
v = LobStorage.createClob(new BufferedReader(in), -1, session.getDataHandler());
v = session.getDataHandler().getLobStorage().createClob(new BufferedReader(in), -1);
}
}
break;
......@@ -556,7 +556,7 @@ public class DataType {
v = LobStorage.createSmallLob(Value.BLOB, rs.getBytes(columnIndex));
} else {
InputStream in = rs.getBinaryStream(columnIndex);
v = (in == null) ? (Value) ValueNull.INSTANCE : LobStorage.createBlob(in, -1, session.getDataHandler());
v = (in == null) ? (Value) ValueNull.INSTANCE : session.getDataHandler().getLobStorage().createBlob(in, -1);
}
break;
}
......@@ -864,19 +864,19 @@ public class DataType {
return ValueTimestamp.get(new Timestamp(((java.util.Date) x).getTime()));
} else if (x instanceof java.io.Reader) {
Reader r = new BufferedReader((java.io.Reader) x);
return LobStorage.createClob(r, -1, session.getDataHandler());
return session.getDataHandler().getLobStorage().createClob(r, -1);
} else if (x instanceof java.sql.Clob) {
try {
Reader r = new BufferedReader(((java.sql.Clob) x).getCharacterStream());
return LobStorage.createClob(r, -1, session.getDataHandler());
return session.getDataHandler().getLobStorage().createClob(r, -1);
} catch (SQLException e) {
throw DbException.convert(e);
}
} else if (x instanceof java.io.InputStream) {
return LobStorage.createBlob((java.io.InputStream) x, -1, session.getDataHandler());
return session.getDataHandler().getLobStorage().createBlob((java.io.InputStream) x, -1);
} else if (x instanceof java.sql.Blob) {
try {
return LobStorage.createBlob(((java.sql.Blob) x).getBinaryStream(), -1, session.getDataHandler());
return session.getDataHandler().getLobStorage().createBlob(((java.sql.Blob) x).getBinaryStream(), -1);
} catch (SQLException e) {
throw DbException.convert(e);
}
......
......@@ -29,13 +29,12 @@ import org.h2.engine.Constants;
import org.h2.engine.SessionInterface;
import org.h2.message.DbException;
import org.h2.message.TraceSystem;
import org.h2.store.LobStorage;
import org.h2.tools.SimpleResultSet;
import org.h2.util.Utils;
import org.h2.util.ExactUTF8InputStreamReader;
import org.h2.util.IOUtils;
import org.h2.util.NetUtils;
import org.h2.util.StringUtils;
import org.h2.util.Utils;
/**
* The transfer class is used to send and receive Value objects.
......@@ -484,7 +483,7 @@ public class Transfer {
return ValueStringFixed.get(readString());
case Value.BLOB: {
long length = readLong();
Value v = LobStorage.createBlob(in, length, session.getDataHandler());
Value v = session.getDataHandler().getLobStorage().createBlob(in, length);
int magic = readInt();
if (magic != LOB_MAGIC) {
throw DbException.get(ErrorCode.CONNECTION_BROKEN_1, "magic=" + magic);
......@@ -493,7 +492,7 @@ public class Transfer {
}
case Value.CLOB: {
long length = readLong();
Value v = LobStorage.createClob(new ExactUTF8InputStreamReader(in), length, session.getDataHandler());
Value v = session.getDataHandler().getLobStorage().createClob(new ExactUTF8InputStreamReader(in), length);
int magic = readInt();
if (magic != LOB_MAGIC) {
throw DbException.get(ErrorCode.CONNECTION_BROKEN_1, "magic=" + magic);
......
......@@ -706,6 +706,7 @@ public abstract class Value {
case BLOB: {
switch(getType()) {
case BYTES:
return LobStorage.createSmallLob(Value.BLOB, getBytesNoCopy());
}
break;
......
......@@ -44,9 +44,6 @@ import org.h2.util.Utils;
* Data compression is supported.
*/
public class ValueLob extends Value {
// TODO lob: concatenate function for blob and clob
// (to create a large blob from pieces)
// and a getpart function (to get it in pieces) and make sure a file is created!
/**
* This counter is used to calculate the next directory to store lobs. It is
......
......@@ -20,7 +20,7 @@ import org.h2.store.DataHandler;
import org.h2.store.FileStore;
import org.h2.store.FileStoreInputStream;
import org.h2.store.FileStoreOutputStream;
import org.h2.store.fs.FileSystem;
import org.h2.store.LobStorage;
import org.h2.util.IOUtils;
import org.h2.util.MathUtils;
import org.h2.util.StringUtils;
......@@ -31,33 +31,27 @@ import org.h2.util.Utils;
*/
public class ValueLob2 extends Value {
/**
* The 'table id' to use for session variables.
*/
public static final int TABLE_ID_SESSION_VARIABLE = -1;
private final int type;
private long precision;
private DataHandler handler;
private int tableId;
private int hash;
private LobStorage lobStorage;
private long lobId;
private String fileName;
private boolean linked;
private byte[] small;
private int hash;
private boolean compression;
private DataHandler handler;
private FileStore tempFile;
private String fileName;
private ValueLob2(int type, DataHandler handler, String fileName, int tableId, long lobId, boolean linked,
long precision, boolean compression) {
private ValueLob2(int type, LobStorage lobStorage, String fileName, int tableId, long lobId, long precision) {
this.type = type;
this.handler = handler;
this.lobStorage = lobStorage;
this.fileName = fileName;
this.tableId = tableId;
this.lobId = lobId;
this.linked = linked;
this.precision = precision;
this.compression = compression;
}
private ValueLob2(int type, byte[] small) {
......@@ -72,11 +66,8 @@ public class ValueLob2 extends Value {
}
}
private static ValueLob2 copy(ValueLob2 lob) {
ValueLob2 copy = new ValueLob2(lob.type, lob.handler, lob.fileName, lob.tableId, lob.lobId, lob.linked, lob.precision, lob.compression);
copy.small = lob.small;
copy.hash = lob.hash;
return copy;
public static ValueLob2 create(int type, LobStorage lobStorage, String fileName, long id, long precision) {
return new ValueLob2(type, lobStorage, fileName, LobStorage.TABLE_ID_SESSION_VARIABLE, id, precision);
}
/**
......@@ -90,204 +81,6 @@ public class ValueLob2 extends Value {
return new ValueLob2(type, small);
}
private static String getFileName(DataHandler handler, int tableId, long objectId) {
if (SysProperties.CHECK && tableId == 0 && objectId == 0) {
DbException.throwInternalError("0 LOB");
}
String table = tableId < 0 ? ".temp" : ".t" + tableId;
return getFileNamePrefix(handler.getDatabasePath(), objectId) + table + Constants.SUFFIX_LOB_FILE;
}
/**
* Create a LOB value with the given parameters.
*
* @param type the data type
* @param handler the file handler
* @param tableId the table object id
* @param objectId the object id
* @param precision the precision (length in elements)
* @param compression if compression is used
* @return the value object
*/
public static ValueLob2 open(int type, DataHandler handler, int tableId, int objectId, long precision, boolean compression) {
String fileName = getFileName(handler, tableId, objectId);
return new ValueLob2(type, handler, fileName, tableId, objectId, true, precision, compression);
}
/**
* Create a 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 ValueLob2 createClob(Reader in, long length, DataHandler handler) {
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(in, -1);
buff = data.toCharArray();
len = buff.length;
} else {
buff = new char[len];
len = IOUtils.readFully(in, buff, len);
len = len < 0 ? 0 : len;
}
if (len <= handler.getMaxLengthInplaceLob()) {
byte[] small = StringUtils.utf8Encode(new String(buff, 0, len));
return ValueLob2.createSmallLob(Value.CLOB, small);
}
ValueLob2 lob = new ValueLob2(Value.CLOB, null);
lob.createFromReader(buff, len, in, remaining, handler);
return lob;
} catch (IOException e) {
throw DbException.convertIOException(e, null);
}
}
private static int getBufferSize(DataHandler handler, boolean compress, long remaining) {
if (remaining < 0 || remaining > Integer.MAX_VALUE) {
remaining = Integer.MAX_VALUE;
}
long inplace = handler.getMaxLengthInplaceLob();
if (inplace >= Integer.MAX_VALUE) {
inplace = remaining;
}
long m = compress ? Constants.IO_BUFFER_SIZE_COMPRESS : Constants.IO_BUFFER_SIZE;
if (m < remaining && m <= inplace) {
m = Math.min(remaining, inplace + 1);
// 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;
}
private void createFromReader(char[] buff, int len, Reader in, long remaining, DataHandler h) {
try {
FileStoreOutputStream out = initLarge(h);
boolean compress = h.getLobCompressionAlgorithm(Value.CLOB) != null;
try {
while (true) {
precision += len;
byte[] b = StringUtils.utf8Encode(new String(buff, 0, len));
out.write(b, 0, b.length);
remaining -= len;
if (remaining <= 0) {
break;
}
len = getBufferSize(h, compress, remaining);
len = IOUtils.readFully(in, buff, len);
if (len <= 0) {
break;
}
}
} finally {
out.close();
}
} catch (IOException e) {
throw DbException.convertIOException(e, null);
}
}
private static String getFileNamePrefix(String path, long lobId) {
return IOUtils.normalize(path + lobId);
}
/**
* Create a 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 ValueLob2 createBlob(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 = Utils.newBytes(len);
len = IOUtils.readFully(in, buff, 0, len);
}
if (len <= handler.getMaxLengthInplaceLob()) {
byte[] small = Utils.newBytes(len);
System.arraycopy(buff, 0, small, 0, len);
return ValueLob2.createSmallLob(Value.BLOB, small);
}
ValueLob2 lob = new ValueLob2(Value.BLOB, null);
lob.createFromStream(buff, len, in, remaining, handler);
return lob;
} catch (IOException e) {
throw DbException.convertIOException(e, null);
}
}
private FileStoreOutputStream initLarge(DataHandler h) {
this.handler = h;
this.tableId = 0;
this.linked = false;
this.precision = 0;
this.small = null;
this.hash = 0;
String compressionAlgorithm = h.getLobCompressionAlgorithm(type);
this.compression = compressionAlgorithm != null;
synchronized (h) {
lobId = getNewObjectId(h);
fileName = getFileNamePrefix(h.getDatabasePath(), lobId) + Constants.SUFFIX_TEMP_FILE;
tempFile = h.openFile(fileName, "rw", false);
tempFile.autoDelete();
}
FileStoreOutputStream out = new FileStoreOutputStream(tempFile, h, compressionAlgorithm);
return out;
}
private void createFromStream(byte[] buff, int len, InputStream in, long remaining, DataHandler h) {
try {
FileStoreOutputStream out = initLarge(h);
boolean compress = h.getLobCompressionAlgorithm(Value.BLOB) != null;
try {
while (true) {
precision += len;
out.write(buff, 0, len);
remaining -= len;
if (remaining <= 0) {
break;
}
len = getBufferSize(h, compress, remaining);
len = IOUtils.readFully(in, buff, 0, len);
if (len <= 0) {
break;
}
}
} finally {
out.close();
}
} catch (IOException e) {
throw DbException.convertIOException(e, null);
}
}
/**
* Convert a lob to another data type. The data is fully read in memory
* except when converting to BLOB or CLOB.
......@@ -299,26 +92,17 @@ public class ValueLob2 extends Value {
if (t == type) {
return this;
} else if (t == Value.CLOB) {
ValueLob2 copy = ValueLob2.createClob(getReader(), -1, handler);
Value copy = handler.getLobStorage().createClob(getReader(), -1);
return copy;
} else if (t == Value.BLOB) {
ValueLob2 copy = ValueLob2.createBlob(getInputStream(), -1, handler);
Value copy = handler.getLobStorage().createBlob(getInputStream(), -1);
return copy;
}
return super.convertTo(t);
}
public boolean isLinked() {
return linked;
}
/**
* Get the current file name where the lob is saved.
*
* @return the file name or null
*/
public String getFileName() {
return fileName;
return tableId != LobStorage.TABLE_ID_SESSION_VARIABLE;
}
public void close() {
......@@ -330,50 +114,19 @@ public class ValueLob2 extends Value {
}
}
public void unlink() {
if (linked && fileName != null) {
String temp;
// synchronize on the database, to avoid concurrent temp file
// creation / deletion / backup
synchronized (handler) {
temp = getFileName(handler, -1, lobId);
deleteFile(handler, temp);
renameFile(handler, fileName, temp);
tempFile = FileStore.open(handler, temp, "rw");
tempFile.autoDelete();
tempFile.closeSilently();
fileName = temp;
linked = false;
}
private static synchronized void deleteFile(DataHandler handler, String fileName) {
// synchronize on the database, to avoid concurrent temp file creation /
// deletion / backup
synchronized (handler.getLobSyncObject()) {
IOUtils.delete(fileName);
}
}
public void unlink() {
}
public Value link(DataHandler h, int tabId) {
if (fileName == null) {
this.tableId = tabId;
return this;
}
if (linked) {
ValueLob2 copy = ValueLob2.copy(this);
copy.lobId = getNewObjectId(h);
copy.tableId = tabId;
String live = getFileName(h, copy.tableId, copy.lobId);
copyFileTo(h, fileName, live);
copy.fileName = live;
copy.linked = true;
return copy;
}
if (!linked) {
this.tableId = tabId;
String live = getFileName(h, tableId, lobId);
if (tempFile != null) {
tempFile.stopAutoDelete();
tempFile = null;
}
renameFile(h, fileName, live);
fileName = live;
linked = true;
}
int todo;
return this;
}
......@@ -415,7 +168,7 @@ public class ValueLob2 extends Value {
}
return Utils.convertBytesToString(buff);
} catch (IOException e) {
throw DbException.convertIOException(e, fileName);
throw DbException.convertIOException(e, toString());
}
}
......@@ -439,7 +192,7 @@ public class ValueLob2 extends Value {
try {
return IOUtils.readBytesAndClose(getInputStream(), Integer.MAX_VALUE);
} catch (IOException e) {
throw DbException.convertIOException(e, fileName);
throw DbException.convertIOException(e, toString());
}
}
......@@ -479,13 +232,19 @@ public class ValueLob2 extends Value {
}
public InputStream getInputStream() {
if (fileName == null) {
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);
}
try {
return lobStorage.getInputStream(lobId);
} catch (IOException e) {
throw DbException.convertIOException(e, toString());
}
FileStore store = handler.openFile(fileName, "r", true);
boolean alwaysClose = SysProperties.lobCloseBetweenReads;
return new BufferedInputStream(new FileStoreInputStream(store, handler, compression, alwaysClose),
Constants.IO_BUFFER_SIZE);
}
public void set(PreparedStatement prep, int parameterIndex) throws SQLException {
......@@ -521,7 +280,7 @@ public class ValueLob2 extends Value {
} else {
buff.append("CAST(REPEAT('00', ").append(getPrecision()).append(") AS BINARY");
}
buff.append(" /* ").append(fileName).append(" */)");
buff.append(" /* table: ").append(tableId).append(" id: ").append(lobId).append(" */)");
return buff.toString();
}
......@@ -542,122 +301,206 @@ public class ValueLob2 extends Value {
return other instanceof ValueLob && compareSecure((Value) other, null) == 0;
}
/**
* Store the lob data to a file if the size of the buffer it larger than the
* maximum size for an in-place lob.
*
* @param h the data handler
*/
public void convertToFileIfRequired(DataHandler h) {
if (small != null && small.length > h.getMaxLengthInplaceLob()) {
boolean compress = h.getLobCompressionAlgorithm(type) != null;
int len = getBufferSize(h, compress, Long.MAX_VALUE);
int tabId = tableId;
if (type == Value.BLOB) {
createFromStream(Utils.newBytes(len), 0, getInputStream(), Long.MAX_VALUE, h);
} else {
createFromReader(new char[len], 0, getReader(), Long.MAX_VALUE, h);
}
Value v2 = link(h, tabId);
if (SysProperties.CHECK && v2 != this) {
DbException.throwInternalError();
}
}
}
/**
* Remove all lobs for a given table id.
*
* @param handler the data handler
* @param tableId the table id
*/
public static void removeAllForTable(DataHandler handler, int tableId) {
String dir = getFileNamePrefix(handler.getDatabasePath(), 0);
removeAllForTable(handler, dir, tableId);
public boolean isFileBased() {
return small == null;
}
private static void removeAllForTable(DataHandler handler, String dir, int tableId) {
for (String name : IOUtils.listFiles(dir)) {
if (IOUtils.isDirectory(name)) {
removeAllForTable(handler, name, tableId);
} else {
if (name.endsWith(".t" + tableId + Constants.SUFFIX_LOB_FILE)) {
deleteFile(handler, name);
}
}
public int getMemory() {
if (small != null) {
return small.length + 32;
}
return 128;
}
/**
* Check if this lob value is compressed.
* Create an independent copy of this temporary value.
* The file will not be deleted automatically.
*
* @return true if it is
* @return the value
*/
public boolean useCompression() {
return compression;
public ValueLob2 copyToTemp() {
return this;
}
public boolean isFileBased() {
return fileName != null;
public long getLobId() {
return lobId;
}
private static synchronized void deleteFile(DataHandler handler, String fileName) {
// synchronize on the database, to avoid concurrent temp file creation /
// deletion / backup
synchronized (handler.getLobSyncObject()) {
IOUtils.delete(fileName);
}
public void setPrecision(long precision) {
this.precision = precision;
}
private static synchronized void renameFile(DataHandler handler, String oldName, String newName)
{
synchronized (handler.getLobSyncObject()) {
IOUtils.rename(oldName, newName);
}
public String toString() {
return "lob: " + fileName + " table: " + tableId + " id: " + lobId;
}
private void copyFileTo(DataHandler h, String sourceFileName, String targetFileName) {
synchronized (h.getLobSyncObject()) {
FileSystem.getInstance(sourceFileName).copy(sourceFileName, targetFileName);
/**
* 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 ValueLob2 createTempClob(Reader in, long length, DataHandler handler) {
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(in, -1);
buff = data.toCharArray();
len = buff.length;
} else {
buff = new char[len];
len = IOUtils.readFully(in, buff, len);
len = len < 0 ? 0 : len;
}
if (len <= handler.getMaxLengthInplaceLob()) {
byte[] small = StringUtils.utf8Encode(new String(buff, 0, len));
return ValueLob2.createSmallLob(Value.CLOB, small);
}
ValueLob2 lob = new ValueLob2(Value.CLOB, null);
lob.createTempFromReader(buff, len, in, remaining, handler);
return lob;
} catch (IOException e) {
throw DbException.convertIOException(e, null);
}
}
/**
* Set the file name of this lob value.
* Create a temporary BLOB value from a stream.
*
* @param fileName the file name
* @param linked if the lob is linked
* @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 void setFileName(String fileName, boolean linked) {
this.fileName = fileName;
this.linked = linked;
public static ValueLob2 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 = Utils.newBytes(len);
len = IOUtils.readFully(in, buff, 0, len);
}
if (len <= handler.getMaxLengthInplaceLob()) {
byte[] small = Utils.newBytes(len);
System.arraycopy(buff, 0, small, 0, len);
return ValueLob2.createSmallLob(Value.BLOB, small);
}
ValueLob2 lob = new ValueLob2(Value.BLOB, null);
lob.createTempFromStream(buff, len, in, remaining, handler);
return lob;
} catch (IOException e) {
throw DbException.convertIOException(e, null);
}
}
public int getMemory() {
if (small != null) {
return small.length + 32;
private void createTempFromReader(char[] buff, int len, Reader in, long remaining, DataHandler h) {
try {
FileStoreOutputStream out = initTemp(h);
try {
while (true) {
precision += len;
byte[] b = StringUtils.utf8Encode(new String(buff, 0, len));
out.write(b, 0, b.length);
remaining -= len;
if (remaining <= 0) {
break;
}
len = getBufferSize(h, false, remaining);
len = IOUtils.readFully(in, buff, len);
if (len <= 0) {
break;
}
}
} finally {
out.close();
}
} catch (IOException e) {
throw DbException.convertIOException(e, null);
}
return 128;
}
/**
* Create an independent copy of this temporary value.
* The file will not be deleted automatically.
*
* @return the value
*/
public ValueLob2 copyToTemp() {
ValueLob2 lob;
if (type == CLOB) {
lob = ValueLob2.createClob(getReader(), precision, handler);
} else {
lob = ValueLob2.createBlob(getInputStream(), precision, handler);
private void createTempFromStream(byte[] buff, int len, InputStream in, long remaining, DataHandler h) {
try {
FileStoreOutputStream out = initTemp(h);
boolean compress = h.getLobCompressionAlgorithm(Value.BLOB) != null;
try {
while (true) {
precision += len;
out.write(buff, 0, len);
remaining -= len;
if (remaining <= 0) {
break;
}
len = getBufferSize(h, compress, remaining);
len = IOUtils.readFully(in, buff, 0, len);
if (len <= 0) {
break;
}
}
} finally {
out.close();
}
} catch (IOException e) {
throw DbException.convertIOException(e, null);
}
return lob;
}
public long getLobId() {
return lobId;
private FileStoreOutputStream initTemp(DataHandler h) {
this.precision = 0;
this.handler = h;
this.small = null;
try {
String path = h.getDatabasePath();
if (path.length() == 0) {
path = SysProperties.PREFIX_TEMP_FILE;
}
fileName = IOUtils.createTempFile(path, Constants.SUFFIX_TEMP_FILE, true, true);
} catch (IOException e) {
throw DbException.convertIOException(e, null);
}
tempFile = h.openFile(fileName, "rw", false);
tempFile.autoDelete();
FileStoreOutputStream out = new FileStoreOutputStream(tempFile, null, null);
return out;
}
private static int getBufferSize(DataHandler handler, boolean compress, long remaining) {
if (remaining < 0 || remaining > Integer.MAX_VALUE) {
remaining = Integer.MAX_VALUE;
}
long inplace = handler.getMaxLengthInplaceLob();
if (inplace >= Integer.MAX_VALUE) {
inplace = remaining;
}
long m = compress ? Constants.IO_BUFFER_SIZE_COMPRESS : Constants.IO_BUFFER_SIZE;
if (m < remaining && m <= inplace) {
m = Math.min(remaining, inplace + 1);
// 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;
}
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论