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

New lob storage.

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