提交 5d3a10b2 authored 作者: Thomas Mueller's avatar Thomas Mueller

New LOB storage.

上级 19a80ed5
......@@ -12,8 +12,8 @@ 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;
import org.h2.value.ValueLob;
/**
* This class represents the statement
......@@ -83,7 +83,7 @@ public class DropTable extends SchemaCommand {
table.setModified();
Database db = session.getDatabase();
db.removeSchemaObject(session, table);
ValueLob.removeAllForTable(db, dropTableId);
LobStorage.removeAllForTable(db, dropTableId);
}
if (next != null) {
next.executeDrop();
......
......@@ -24,6 +24,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.LobStorage;
import org.h2.tools.CompressTool;
import org.h2.util.IOUtils;
import org.h2.util.SmallLRUCache;
......@@ -223,4 +224,8 @@ public abstract class ScriptBase extends Prepared implements DataHandler {
return null;
}
public LobStorage getLobStorage() {
return null;
}
}
......@@ -54,7 +54,6 @@ import org.h2.util.StatementBuilder;
import org.h2.util.StringUtils;
import org.h2.util.Utils;
import org.h2.value.Value;
import org.h2.value.ValueLob;
import org.h2.value.ValueString;
/**
......@@ -254,10 +253,10 @@ public class ScriptCommand extends ScriptBase {
if (v.getPrecision() > lobBlockSize) {
int id;
if (v.getType() == Value.CLOB) {
id = writeLobStream((ValueLob) v);
id = writeLobStream(v);
buff.append("SYSTEM_COMBINE_CLOB(" + id + ")");
} else if (v.getType() == Value.BLOB) {
id = writeLobStream((ValueLob) v);
id = writeLobStream(v);
buff.append("SYSTEM_COMBINE_BLOB(" + id + ")");
} else {
buff.append(v.getSQL());
......@@ -328,7 +327,7 @@ public class ScriptCommand extends ScriptBase {
return r;
}
private int writeLobStream(ValueLob v) throws IOException {
private int writeLobStream(Value v) throws IOException {
if (!tempLobTableCreated) {
add("CREATE TABLE IF NOT EXISTS SYSTEM_LOB_STREAM(ID INT NOT NULL, PART INT NOT NULL, CDATA VARCHAR, BDATA BINARY)", true);
add("CREATE PRIMARY KEY SYSTEM_LOB_STREAM_PRIMARY_KEY ON SYSTEM_LOB_STREAM(ID, PART)", true);
......
......@@ -255,6 +255,12 @@ public class SysProperties {
*/
public static final int LOB_FILES_PER_DIRECTORY = getIntSetting("h2.lobFilesPerDirectory", 256);
/**
* System property <code>h2.lobInDatabase</code> (default: false).<br />
* Store LOB files in the database.
*/
public static final boolean LOB_IN_DATABASE = getBooleanSetting("h2.lobInDatabase", false);
/**
* System property <code>h2.logAllErrors</code> (default: false).<br />
* Write stack traces of any kind of error to a file.
......
......@@ -7,6 +7,7 @@
package org.h2.engine;
import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.BitSet;
......@@ -25,6 +26,7 @@ import org.h2.constraint.Constraint;
import org.h2.index.Cursor;
import org.h2.index.Index;
import org.h2.index.IndexType;
import org.h2.jdbc.JdbcConnection;
import org.h2.message.DbException;
import org.h2.message.Trace;
import org.h2.message.TraceSystem;
......@@ -38,6 +40,7 @@ import org.h2.store.DataHandler;
import org.h2.store.FileLock;
import org.h2.store.FileStore;
import org.h2.store.InDoubtTransaction;
import org.h2.store.LobStorage;
import org.h2.store.PageStore;
import org.h2.store.WriterThread;
import org.h2.store.fs.FileSystemMemory;
......@@ -50,7 +53,6 @@ import org.h2.table.TableLinkConnection;
import org.h2.table.TableView;
import org.h2.tools.DeleteDbFiles;
import org.h2.tools.Server;
import org.h2.util.Utils;
import org.h2.util.IOUtils;
import org.h2.util.NetUtils;
import org.h2.util.New;
......@@ -58,10 +60,10 @@ import org.h2.util.SmallLRUCache;
import org.h2.util.SourceCompiler;
import org.h2.util.StringUtils;
import org.h2.util.TempFileDeleter;
import org.h2.util.Utils;
import org.h2.value.CompareMode;
import org.h2.value.Value;
import org.h2.value.ValueInt;
import org.h2.value.ValueLob;
/**
* There is one database object per open database.
......@@ -164,6 +166,7 @@ public class Database implements DataHandler {
private SourceCompiler compiler;
private boolean metaTablesInitialized;
private boolean flushOnEachCommit;
private LobStorage lobStorage;
public Database(String name, ConnectionInfo ci, String cipher) {
this.compareMode = CompareMode.getInstance(null, 0);
......@@ -1044,7 +1047,7 @@ public class Database implements DataHandler {
// remove all session variables
if (persistent) {
try {
ValueLob.removeAllForTable(this, ValueLob.TABLE_ID_SESSION_VARIABLE);
LobStorage.removeAllForTable(this, LobStorage.TABLE_ID_SESSION_VARIABLE);
} catch (DbException e) {
traceSystem.getTrace(Trace.DATABASE).error("close", e);
}
......@@ -2203,4 +2206,13 @@ public class Database implements DataHandler {
return compiler;
}
public LobStorage getLobStorage() {
if (lobStorage == null) {
String url = Constants.CONN_URL_INTERNAL;
Connection conn = new JdbcConnection(systemSession, systemUser.getName(), url);
lobStorage = new LobStorage(conn);
}
return lobStorage;
}
}
......@@ -28,10 +28,10 @@ import org.h2.result.Row;
import org.h2.schema.Schema;
import org.h2.store.DataHandler;
import org.h2.store.InDoubtTransaction;
import org.h2.store.LobStorage;
import org.h2.table.Table;
import org.h2.util.New;
import org.h2.value.Value;
import org.h2.value.ValueLob;
import org.h2.value.ValueLong;
import org.h2.value.ValueNull;
import org.h2.value.ValueString;
......@@ -81,7 +81,7 @@ public class Session extends SessionWithState implements SessionFactory {
private String currentSchemaName;
private String[] schemaSearchPath;
private String traceModuleName;
private HashMap<String, ValueLob> unlinkMap;
private HashMap<String, Value> unlinkLobMap;
private int systemIdentifier;
private HashMap<String, Procedure> procedures;
private boolean undoLogEnabled = true;
......@@ -144,10 +144,8 @@ public class Session extends SessionWithState implements SessionFactory {
if (value == ValueNull.INSTANCE) {
old = variables.remove(name);
} else {
if (value instanceof ValueLob) {
// link it, to make sure we have our own file
value = value.link(database, ValueLob.TABLE_ID_SESSION_VARIABLE);
}
// link LOB values, to make sure we have our own object
value = value.link(database, LobStorage.TABLE_ID_SESSION_VARIABLE);
old = variables.put(name, value);
}
if (old != null) {
......@@ -476,14 +474,14 @@ public class Session extends SessionWithState implements SessionFactory {
autoCommitAtTransactionEnd = false;
}
}
if (unlinkMap != null && unlinkMap.size() > 0) {
if (unlinkLobMap != null && unlinkLobMap.size() > 0) {
// need to flush the transaction log, because we can't unlink lobs if the
// commit record is not written
database.flush();
for (Value v : unlinkMap.values()) {
for (Value v : unlinkLobMap.values()) {
v.unlink();
}
unlinkMap = null;
unlinkLobMap = null;
}
unlockAll();
}
......@@ -913,14 +911,14 @@ public class Session extends SessionWithState implements SessionFactory {
*
* @param v the value
*/
public void unlinkAtCommit(ValueLob v) {
public void unlinkAtCommit(Value v) {
if (SysProperties.CHECK && !v.isLinked()) {
DbException.throwInternalError();
}
if (unlinkMap == null) {
unlinkMap = New.hashMap();
if (unlinkLobMap == null) {
unlinkLobMap = New.hashMap();
}
unlinkMap.put(v.toString(), v);
unlinkLobMap.put(v.toString(), v);
}
/**
......@@ -929,8 +927,8 @@ public class Session extends SessionWithState implements SessionFactory {
* @param v the value
*/
public void unlinkAtCommitStop(Value v) {
if (unlinkMap != null) {
unlinkMap.remove(v.toString());
if (unlinkLobMap != null) {
unlinkLobMap.remove(v.toString());
}
}
......
......@@ -23,7 +23,7 @@ import org.h2.message.TraceSystem;
import org.h2.result.ResultInterface;
import org.h2.store.DataHandler;
import org.h2.store.FileStore;
import org.h2.util.Utils;
import org.h2.store.LobStorage;
import org.h2.util.IOUtils;
import org.h2.util.MathUtils;
import org.h2.util.NetUtils;
......@@ -31,6 +31,7 @@ import org.h2.util.New;
import org.h2.util.SmallLRUCache;
import org.h2.util.StringUtils;
import org.h2.util.TempFileDeleter;
import org.h2.util.Utils;
import org.h2.value.Transfer;
import org.h2.value.Value;
import org.h2.value.ValueString;
......@@ -95,9 +96,7 @@ public class SessionRemote extends SessionWithState implements SessionFactory, D
trans.setSSL(ci.isSSL());
trans.init();
trans.writeInt(clientVersion);
if (clientVersion >= Constants.TCP_PROTOCOL_VERSION) {
trans.writeInt(clientVersion);
}
trans.writeString(db);
trans.writeString(ci.getOriginalURL());
trans.writeString(ci.getUserName());
......@@ -109,7 +108,7 @@ public class SessionRemote extends SessionWithState implements SessionFactory, D
trans.writeString(key).writeString(ci.getProperty(key));
}
try {
convert(trans);
done(trans);
if (clientVersion >= Constants.TCP_PROTOCOL_VERSION) {
clientVersion = trans.readInt();
}
......@@ -180,7 +179,7 @@ public class SessionRemote extends SessionWithState implements SessionFactory, D
try {
traceOperation("COMMAND_COMMIT", 0);
transfer.writeInt(SessionRemote.COMMAND_COMMIT);
convert(transfer);
done(transfer);
} catch (IOException e) {
removeServer(e, i--, ++count);
}
......@@ -364,7 +363,7 @@ public class SessionRemote extends SessionWithState implements SessionFactory, D
traceOperation("SESSION_SET_ID", 0);
transfer.writeInt(SessionRemote.SESSION_SET_ID);
transfer.writeString(sessionId);
convert(transfer);
done(transfer);
} catch (Exception e) {
trace.error("sessionSetId", e);
}
......@@ -456,7 +455,7 @@ public class SessionRemote extends SessionWithState implements SessionFactory, D
try {
traceOperation("SESSION_CLOSE", 0);
transfer.writeInt(SessionRemote.SESSION_CLOSE);
convert(transfer);
done(transfer);
transfer.close();
} catch (Exception e) {
trace.error("close", e);
......@@ -494,7 +493,7 @@ public class SessionRemote extends SessionWithState implements SessionFactory, D
* @throws IOException if there is a communication problem between client
* and server
*/
public void convert(Transfer transfer) throws IOException {
public void done(Transfer transfer) throws IOException {
transfer.flush();
int status = transfer.readInt();
if (status == STATUS_ERROR) {
......@@ -620,4 +619,8 @@ public class SessionRemote extends SessionWithState implements SessionFactory, D
// nothing to do
}
public LobStorage getLobStorage() {
return null;
}
}
......@@ -34,6 +34,7 @@ import org.h2.schema.Sequence;
import org.h2.security.BlockCipher;
import org.h2.security.CipherFactory;
import org.h2.security.SHA256;
import org.h2.store.LobStorage;
import org.h2.table.Column;
import org.h2.table.ColumnResolver;
import org.h2.table.LinkSchema;
......@@ -41,14 +42,14 @@ import org.h2.table.TableFilter;
import org.h2.tools.CompressTool;
import org.h2.tools.Csv;
import org.h2.util.AutoCloseInputStream;
import org.h2.util.JdbcUtils;
import org.h2.util.Utils;
import org.h2.util.DateTimeUtils;
import org.h2.util.IOUtils;
import org.h2.util.JdbcUtils;
import org.h2.util.MathUtils;
import org.h2.util.New;
import org.h2.util.StatementBuilder;
import org.h2.util.StringUtils;
import org.h2.util.Utils;
import org.h2.value.DataType;
import org.h2.value.Value;
import org.h2.value.ValueArray;
......@@ -57,7 +58,6 @@ import org.h2.value.ValueBytes;
import org.h2.value.ValueDate;
import org.h2.value.ValueDouble;
import org.h2.value.ValueInt;
import org.h2.value.ValueLob;
import org.h2.value.ValueLong;
import org.h2.value.ValueNull;
import org.h2.value.ValueResultSet;
......@@ -1114,7 +1114,7 @@ public class Function extends Expression implements FunctionCall {
try {
InputStream in = new AutoCloseInputStream(IOUtils.openFileInputStream(fileName));
if (blob) {
result = ValueLob.createBlob(in, -1, database);
result = LobStorage.createBlob(in, -1, database);
} else {
Reader reader;
if (v1 == ValueNull.INSTANCE) {
......@@ -1122,7 +1122,7 @@ public class Function extends Expression implements FunctionCall {
} else {
reader = new InputStreamReader(in, v1.getString());
}
result = ValueLob.createClob(reader, -1, database);
result = LobStorage.createClob(reader, -1, database);
}
} catch (IOException e) {
throw DbException.convertIOException(e, fileName);
......
......@@ -13,6 +13,7 @@ import org.h2.message.DbException;
import org.h2.result.Row;
import org.h2.result.SearchRow;
import org.h2.store.Data;
import org.h2.store.LobStorage;
import org.h2.store.Page;
import org.h2.store.PageStore;
import org.h2.table.Column;
......@@ -20,7 +21,6 @@ import org.h2.table.IndexColumn;
import org.h2.table.TableData;
import org.h2.util.MathUtils;
import org.h2.value.Value;
import org.h2.value.ValueLob;
import org.h2.value.ValueNull;
/**
......@@ -218,7 +218,7 @@ public class PageBtreeIndex extends PageIndex {
for (int i = 0; i < row.getColumnCount(); i++) {
Value v = row.getValue(i);
if (v.isLinked()) {
session.unlinkAtCommit((ValueLob) v);
session.unlinkAtCommit(v);
}
}
}
......@@ -253,7 +253,7 @@ public class PageBtreeIndex extends PageIndex {
}
removeAllRows();
if (tableData.getContainsLargeObject()) {
ValueLob.removeAllForTable(database, table.getId());
LobStorage.removeAllForTable(database, table.getId());
}
tableData.setRowCount(0);
}
......
......@@ -19,6 +19,7 @@ import org.h2.message.DbException;
import org.h2.result.Row;
import org.h2.result.SearchRow;
import org.h2.store.Data;
import org.h2.store.LobStorage;
import org.h2.store.Page;
import org.h2.store.PageStore;
import org.h2.table.Column;
......@@ -27,7 +28,6 @@ import org.h2.table.TableData;
import org.h2.util.MathUtils;
import org.h2.util.New;
import org.h2.value.Value;
import org.h2.value.ValueLob;
import org.h2.value.ValueNull;
/**
......@@ -296,7 +296,7 @@ public class PageDataIndex extends PageIndex {
for (int i = 0; i < row.getColumnCount(); i++) {
Value v = row.getValue(i);
if (v.isLinked()) {
session.unlinkAtCommit((ValueLob) v);
session.unlinkAtCommit(v);
}
}
}
......@@ -347,7 +347,7 @@ public class PageDataIndex extends PageIndex {
store.logTruncate(session, tableData.getId());
removeAllRows();
if (tableData.getContainsLargeObject() && tableData.isPersistData()) {
ValueLob.removeAllForTable(database, table.getId());
LobStorage.removeAllForTable(database, table.getId());
}
if (database.isMultiVersion()) {
sessionRowCount.clear();
......
......@@ -18,11 +18,11 @@ import org.h2.engine.UndoLogRecord;
import org.h2.message.DbException;
import org.h2.result.Row;
import org.h2.result.SearchRow;
import org.h2.store.LobStorage;
import org.h2.table.Column;
import org.h2.table.IndexColumn;
import org.h2.table.TableData;
import org.h2.util.New;
import org.h2.value.ValueLob;
/**
* The scan index is not really an 'index' in the strict sense, because it can
......@@ -55,7 +55,7 @@ public class ScanIndex extends BaseIndex {
rows = New.arrayList();
firstFree = -1;
if (tableData.getContainsLargeObject() && tableData.isPersistData()) {
ValueLob.removeAllForTable(database, table.getId());
LobStorage.removeAllForTable(database, table.getId());
}
tableData.setRowCount(0);
rowCount = 0;
......
......@@ -21,7 +21,6 @@ import java.sql.Savepoint;
import java.sql.Statement;
import java.util.Map;
import java.util.Properties;
import org.h2.command.CommandInterface;
import org.h2.constant.ErrorCode;
import org.h2.constant.SysProperties;
......@@ -33,11 +32,11 @@ 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;
import org.h2.value.ValueInt;
import org.h2.value.ValueLob;
import org.h2.value.ValueNull;
import org.h2.value.ValueString;
......@@ -1439,7 +1438,7 @@ public class JdbcConnection extends TraceObject implements Connection {
debugCodeAssign("Clob", TraceObject.CLOB, id, "createClob()");
checkClosedForWrite();
try {
ValueLob v = ValueLob.createSmallLob(Value.CLOB, Utils.EMPTY_BYTES);
Value v = LobStorage.createSmallLob(Value.CLOB, Utils.EMPTY_BYTES);
return new JdbcClob(this, v, id);
} finally {
afterWriting();
......@@ -1460,7 +1459,7 @@ public class JdbcConnection extends TraceObject implements Connection {
debugCodeAssign("Blob", TraceObject.BLOB, id, "createClob()");
checkClosedForWrite();
try {
ValueLob v = ValueLob.createSmallLob(Value.BLOB, Utils.EMPTY_BYTES);
Value v = LobStorage.createSmallLob(Value.BLOB, Utils.EMPTY_BYTES);
return new JdbcBlob(this, v, id);
} finally {
afterWriting();
......@@ -1615,7 +1614,7 @@ public class JdbcConnection extends TraceObject implements Connection {
if (length <= 0) {
length = -1;
}
Value v = ValueLob.createClob(x, length, session.getDataHandler());
Value v = LobStorage.createClob(x, length, session.getDataHandler());
return v;
}
......@@ -1634,7 +1633,7 @@ public class JdbcConnection extends TraceObject implements Connection {
if (length <= 0) {
length = -1;
}
Value v = ValueLob.createBlob(x, length, session.getDataHandler());
Value v = LobStorage.createBlob(x, length, session.getDataHandler());
return v;
}
......
......@@ -290,7 +290,8 @@ public class JdbcDatabaseMetaData extends TraceObject implements DatabaseMetaDat
* </ul>
*
* @param catalogPattern null or the catalog name
* @param schemaPattern schema name (must be specified)
* @param schemaPattern null (to get all objects) or a schema name
* (uppercase for unquoted names)
* @param tableName table name (must be specified)
* @param unique only unique indexes
* @param approximate is ignored
......@@ -356,7 +357,8 @@ public class JdbcDatabaseMetaData extends TraceObject implements DatabaseMetaDat
* </ul>
*
* @param catalogPattern null or the catalog name
* @param schemaPattern schema name (must be specified)
* @param schemaPattern null (to get all objects) or a schema name
* (uppercase for unquoted names)
* @param tableName table name (must be specified)
* @return the list of primary key columns
* @throws SQLException if the connection is closed
......@@ -529,7 +531,8 @@ public class JdbcDatabaseMetaData extends TraceObject implements DatabaseMetaDat
* </ul>
*
* @param catalogPattern null or the catalog name
* @param schemaPattern schema name (must be specified)
* @param schemaPattern null (to get all objects) or a schema name
* (uppercase for unquoted names)
* @param procedureNamePattern the procedure name pattern
* @return the procedures
* @throws SQLException if the connection is closed
......@@ -599,7 +602,8 @@ public class JdbcDatabaseMetaData extends TraceObject implements DatabaseMetaDat
* </ul>
*
* @param catalogPattern null or the catalog name
* @param schemaPattern schema name (must be specified)
* @param schemaPattern null (to get all objects) or a schema name
* (uppercase for unquoted names)
* @param procedureNamePattern the procedure name pattern
* @param columnNamePattern the procedure name pattern
* @return the procedure columns
......@@ -867,7 +871,8 @@ public class JdbcDatabaseMetaData extends TraceObject implements DatabaseMetaDat
* </li></ul>
*
* @param catalogPattern null (to get all objects) or the catalog name
* @param schemaPattern schema name (must be specified)
* @param schemaPattern null (to get all objects) or a schema name
* (uppercase for unquoted names)
* @param tableName table name (must be specified)
* @param scope ignored
* @param nullable ignored
......@@ -932,7 +937,8 @@ public class JdbcDatabaseMetaData extends TraceObject implements DatabaseMetaDat
* </li></ul>
*
* @param catalog null (to get all objects) or the catalog name
* @param schema schema name (must be specified)
* @param schemaPattern null (to get all objects) or a schema name
* (uppercase for unquoted names)
* @param tableName table name (must be specified)
* @return an empty result set
* @throws SQLException if the connection is closed
......@@ -1130,12 +1136,12 @@ public class JdbcDatabaseMetaData extends TraceObject implements DatabaseMetaDat
* </ul>
*
* @param primaryCatalogPattern null or the catalog name
* @param primarySchemaPattern the schema name of the primary table (must be
* specified)
* @param primarySchemaPattern the schema name of the primary table
* (optional)
* @param primaryTable the name of the primary table (must be specified)
* @param foreignCatalogPattern null or the catalog name
* @param foreignSchemaPattern the schema name of the foreign table (must be
* specified)
* @param foreignSchemaPattern the schema name of the foreign table
* (optional)
* @param foreignTable the name of the foreign table (must be specified)
* @return the result set
* @throws SQLException if the connection is closed
......
......@@ -80,8 +80,7 @@ public class DbException extends RuntimeException {
params[i] = StringUtils.quoteIdentifier(s);
}
}
Object[] o = params;
message = MessageFormat.format(message, o);
message = MessageFormat.format(message, (Object[]) params);
}
return message;
}
......
......@@ -15,7 +15,6 @@ import org.h2.store.Data;
import org.h2.store.FileStore;
import org.h2.util.New;
import org.h2.value.Value;
import org.h2.value.ValueLob;
/**
* A list of rows. If the list grows too large, it is buffered to disk
......@@ -29,7 +28,7 @@ public class RowList {
private int index, listIndex;
private FileStore file;
private Data rowBuff;
private ArrayList<ValueLob> lobs;
private ArrayList<Value> lobs;
private int memory, maxMemory;
private boolean written;
private boolean readUncached;
......@@ -65,17 +64,15 @@ public class RowList {
if (v.getType() == Value.CLOB || v.getType() == Value.BLOB) {
// need to keep a reference to temporary lobs,
// otherwise the temp file is deleted
ValueLob lob = (ValueLob) v;
if (lob.getSmall() == null && lob.getTableId() == 0) {
if (v.getSmall() == null && v.getTableId() == 0) {
if (lobs == null) {
lobs = New.arrayList();
}
// need to create a copy, otherwise,
// if stored multiple times, it may be renamed
// and then not found
lob = lob.copyToTemp();
lobs.add(lob);
v = lob;
v = v.copyToTemp();
lobs.add(v);
}
}
buff.checkCapacity(buff.getValueLen(v));
......@@ -182,11 +179,10 @@ public class RowList {
} else {
v = buff.readValue();
if (v.isLinked()) {
ValueLob lob = (ValueLob) v;
// the table id is 0 if it was linked when writing
// a temporary entry
if (lob.getTableId() == 0) {
session.unlinkAtCommit(lob);
if (v.getTableId() == 0) {
session.unlinkAtCommit(v);
}
}
}
......
......@@ -65,10 +65,14 @@ public class TcpServerThread implements Runnable {
// TODO server: should support a list of allowed databases
// and a list of allowed clients
try {
clientVersion = transfer.readInt();
if (!server.allow(transfer.getSocket())) {
throw DbException.get(ErrorCode.REMOTE_CONNECTION_NOT_ALLOWED);
}
clientVersion = transfer.readInt();
if (clientVersion < Constants.TCP_PROTOCOL_VERSION) {
throw DbException.get(ErrorCode.DRIVER_VERSION_ERROR_2, "current client version: " +
clientVersion + "; minimum version: " + Constants.TCP_PROTOCOL_VERSION);
}
// max version (currently not used)
transfer.readInt();
String db = transfer.readString();
......
......@@ -653,7 +653,7 @@ public class Data {
if (smallLen >= 0) {
byte[] small = Utils.newBytes(smallLen);
read(small, 0, smallLen);
return ValueLob.createSmallLob(type, small);
return LobStorage.createSmallLob(type, small);
}
int tableId = readVarInt();
int objectId = readVarInt();
......
......@@ -91,4 +91,6 @@ public interface DataHandler {
*/
SmallLRUCache<String, String[]> getLobFileListCache();
LobStorage getLobStorage();
}
/*
* Copyright 2004-2010 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.store;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Arrays;
import java.util.HashMap;
import org.h2.constant.ErrorCode;
import org.h2.constant.SysProperties;
import org.h2.message.DbException;
import org.h2.util.IOUtils;
import org.h2.util.New;
import org.h2.value.ValueLob;
public class LobStorage {
/**
* The 'table id' to use for session variables.
*/
public static final int TABLE_ID_SESSION_VARIABLE = -1;
// TODO test recovery
private static final String LOBS = "INFORMATION_SCHEMA.LOBS";
private static final String LOB_MAP = "INFORMATION_SCHEMA.LOB_MAP";
private static final String LOB_DATA = "INFORMATION_SCHEMA.LOB_DATA";
private static final int BLOCK_LENGTH = 20000;
private static final boolean HASH = true;
private static final long UNIQUE = 0xffff;
private Connection conn;
private HashMap<String, PreparedStatement> prepared = New.hashMap();
private long nextLob;
private long nextBlock;
public static void removeAllForTable(DataHandler handler, int tableId) {
if (SysProperties.LOB_IN_DATABASE) {
// remove both lobs in the database as well as in the file system
}
ValueLob.removeAllForTable(handler, tableId);
}
public static ValueLob createSmallLob(int type, byte[] small) {
if (SysProperties.LOB_IN_DATABASE) {
return null;
}
return ValueLob.createSmallLob(type, small);
}
public static ValueLob createBlob(InputStream in, long length, DataHandler handler) {
if (SysProperties.LOB_IN_DATABASE) {
return null;
}
return ValueLob.createBlob(in, length, handler);
}
public static ValueLob createClob(Reader in, long length, DataHandler handler) {
if (SysProperties.LOB_IN_DATABASE) {
return null;
}
return ValueLob.createClob(in, length, handler);
}
/**
* An input stream that reads from a LOB.
*/
public static class LobInputStream extends InputStream {
private Connection conn;
private PreparedStatement prepSelect;
private byte[] buffer;
private int pos;
private long remaining;
private long lob;
private int seq;
public LobInputStream(Connection conn, long lob) throws IOException {
try {
this.lob = lob;
PreparedStatement prep = conn.prepareStatement(
"SELECT LENGTH FROM " + LOBS + " WHERE ID = ?");
prep.setLong(1, lob);
ResultSet rs = prep.executeQuery();
if (!rs.next()) {
throw DbException.get(ErrorCode.IO_EXCEPTION_1, "lob: "+ lob + " seq: " + seq).getSQLException();
}
} catch (SQLException e) {
throw DbException.convertToIOException(e);
}
}
public int read() throws IOException {
fillBuffer();
if (remaining <= 0) {
return -1;
}
remaining--;
return buffer[pos++] & 255;
}
public int read(byte[] buff) throws IOException {
return readFully(buff, 0, buff.length);
}
public int read(byte[] buff, int off, int length) throws IOException {
return readFully(buff, 0, buff.length);
}
private int readFully(byte[] buff, int off, int length) throws IOException {
if (length == 0) {
return 0;
}
int read = 0;
while (length > 0) {
fillBuffer();
if (remaining <= 0) {
break;
}
int len = (int) Math.min(length, remaining);
len = Math.min(len, buffer.length - pos);
System.arraycopy(buffer, pos, buff, off, len);
read += len;
remaining -= len;
off += len;
length -= len;
}
return read == 0 ? -1 : read;
}
private void fillBuffer() throws IOException {
if (buffer != null && pos < buffer.length) {
return;
}
if (remaining <= 0) {
return;
}
try {
if (prepSelect == null) {
prepSelect = conn.prepareStatement(
"SELECT DATA FROM " + LOB_MAP + " M " +
"INNER JOIN " + LOB_DATA + " D ON M.BLOCK = D.BLOCK " +
"WHERE M.LOB = ? AND M.SEQ = ?");
}
prepSelect.setLong(1, lob);
prepSelect.setInt(2, seq);
ResultSet rs = prepSelect.executeQuery();
if (!rs.next()) {
throw DbException.get(ErrorCode.IO_EXCEPTION_1, "lob: "+ lob + " seq: " + seq).getSQLException();
}
seq++;
buffer = rs.getBytes(1);
pos = 0;
} catch (SQLException e) {
throw DbException.convertToIOException(e);
}
}
}
public LobStorage(Connection newConn) {
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 TABLE IF NOT EXISTS " + LOB_DATA + "(BLOCK BIGINT PRIMARY KEY, DATA BINARY)");
ResultSet rs;
rs = stat.executeQuery("SELECT MAX(BLOCK) FROM " + LOB_DATA);
rs.next();
nextBlock = rs.getLong(1) + 1;
if (HASH) {
nextBlock = Math.max(UNIQUE + 1, nextLob);
}
rs = stat.executeQuery("SELECT MAX(ID) FROM " + LOBS);
rs.next();
nextLob = rs.getLong(1) + 1;
} catch (SQLException e) {
throw DbException.convert(e);
}
}
protected synchronized PreparedStatement prepare(String sql) throws SQLException {
PreparedStatement prep = prepared.get(sql);
if (prep == null) {
prep = conn.prepareStatement(sql);
prepared.put(sql, prep);
}
return prep;
}
private void deleteLob(long lob) throws SQLException {
PreparedStatement prep;
prep = prepare(
"DELETE FROM " + LOB_MAP + " " +
"WHERE LOB = ?");
prep.setLong(1, lob);
prep.execute();
prep = prepare(
"DELETE FROM " + LOB_DATA + " D " +
"WHERE BLOCK IN(SELECT M.BLOCK FROM " + LOB_MAP + " M WHERE LOB = ?) " +
"AND NOT EXISTS(SELECT 1 FROM " + LOB_MAP + " M " +
"WHERE M.BLOCK = D.BLOCK AND M.LOB <> ?)");
prep.setLong(1, lob);
prep.setLong(2, lob);
prep.execute();
prep = prepare(
"DELETE FROM " + LOBS + " " +
"WHERE ID = ?");
prep.setLong(1, lob);
prep.execute();
}
public long addLob(InputStream in, long maxLength, int table) throws SQLException {
byte[] buff = new byte[BLOCK_LENGTH];
if (maxLength < 0) {
maxLength = Long.MAX_VALUE;
}
long length = 0;
long lob = nextLob++;
try {
for (int seq = 0; maxLength > 0; seq++) {
int len = IOUtils.readFully(in, buff, 0, BLOCK_LENGTH);
if (len <= 0) {
break;
}
length += len;
maxLength -= len;
byte[] b;
if (len != buff.length) {
b = new byte[len];
System.arraycopy(buff, 0, b, 0, len);
} else {
b = buff;
}
long block;
boolean blockExists = false;
if (HASH) {
block = Arrays.hashCode(b) & UNIQUE;
int todoSynchronize;
PreparedStatement prep = prepare(
"SELECT DATA FROM " + LOB_DATA +
" WHERE BLOCK = ?");
prep.setLong(1, block);
ResultSet rs = prep.executeQuery();
if (rs.next()) {
byte[] compare = rs.getBytes(1);
if (Arrays.equals(b, compare)) {
blockExists = true;
} else {
block = nextBlock++;
}
}
} else {
block = nextBlock++;
}
if (!blockExists) {
PreparedStatement prep = prepare(
"INSERT INTO " + LOB_DATA + "(BLOCK, DATA) VALUES(?, ?)");
prep.setLong(1, block);
prep.setBytes(2, b);
prep.execute();
}
PreparedStatement prep = prepare(
"INSERT INTO " + LOB_MAP + "(LOB, SEQ, BLOCK) VALUES(?, ?, ?)");
prep.setLong(1, lob);
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(2, length);
prep.setInt(3, table);
prep.execute();
return lob;
} catch (IOException e) {
deleteLob(lob);
throw DbException.convertIOException(e, "adding blob");
}
}
public InputStream getInputStream(long id) throws IOException {
return new LobInputStream(conn, id);
}
}
......@@ -1423,7 +1423,7 @@ public class PageStore implements CacheWriter {
int key = index.getId() + 1;
Row row = metaIndex.getRow(session, key);
if (row.getKey() != key) {
DbException.throwInternalError();
throw DbException.get(ErrorCode.FILE_CORRUPTED_1, "key: " + key + " index: " + index + " row: " + row);
}
metaIndex.remove(session, row);
}
......
......@@ -15,6 +15,7 @@ import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.Reader;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.BitSet;
......@@ -38,11 +39,11 @@ import org.h2.store.DataReader;
import org.h2.store.FileLister;
import org.h2.store.FileStore;
import org.h2.store.FileStoreInputStream;
import org.h2.store.LobStorage;
import org.h2.store.Page;
import org.h2.store.PageFreeList;
import org.h2.store.PageLog;
import org.h2.store.PageStore;
import org.h2.util.Utils;
import org.h2.util.IOUtils;
import org.h2.util.IntArray;
import org.h2.util.MathUtils;
......@@ -51,8 +52,10 @@ import org.h2.util.SmallLRUCache;
import org.h2.util.StatementBuilder;
import org.h2.util.TempFileDeleter;
import org.h2.util.Tool;
import org.h2.util.Utils;
import org.h2.value.Value;
import org.h2.value.ValueLob;
import org.h2.value.ValueLob2;
import org.h2.value.ValueLong;
/**
......@@ -149,6 +152,21 @@ public class Recover extends Tool implements DataHandler {
return new BufferedInputStream(IOUtils.openFileInputStream(fileName));
}
/**
* INTERNAL
*/
public static Reader readClobDb(Connection conn, long lobId) throws IOException {
return new BufferedReader(new InputStreamReader(readBlobDb(conn, lobId), "UTF-8"));
}
/**
* INTERNAL
*/
public static InputStream readBlobDb(Connection conn, long lobId) throws IOException {
return new BufferedInputStream(new LobStorage.LobInputStream(conn, lobId));
}
private void trace(String message) {
if (trace) {
out.println(message);
......@@ -272,6 +290,16 @@ public class Recover extends Tool implements DataHandler {
}
return "READ_CLOB('" + file + ".txt')";
}
} else if (v instanceof ValueLob2) {
ValueLob2 lob = (ValueLob2) v;
byte[] small = lob.getSmall();
if (small == null) {
long id = lob.getLobId();
if (lob.getType() == Value.BLOB) {
return "READ_BLOB_DB(" + id + ")";
}
return "READ_CLOB_DB(" + id + ")";
}
}
return v.getSQL();
}
......@@ -292,6 +320,8 @@ public class Recover extends Tool implements DataHandler {
writer = getWriter(fileName, ".sql");
writer.println("CREATE ALIAS IF NOT EXISTS READ_CLOB FOR \"" + this.getClass().getName() + ".readClob\";");
writer.println("CREATE ALIAS IF NOT EXISTS READ_BLOB FOR \"" + this.getClass().getName() + ".readBlob\";");
writer.println("CREATE ALIAS IF NOT EXISTS READ_CLOB_DB FOR \"" + this.getClass().getName() + ".readClobDb\";");
writer.println("CREATE ALIAS IF NOT EXISTS READ_BLOB_DB FOR \"" + this.getClass().getName() + ".readBlobDb\";");
resetSchema();
store = FileStore.open(null, fileName, remove ? "rw" : "r");
long length = store.length();
......@@ -1184,4 +1214,11 @@ public class Recover extends Tool implements DataHandler {
return TempFileDeleter.getInstance();
}
/**
* INTERNAL
*/
public LobStorage getLobStorage() {
return null;
}
}
......@@ -30,6 +30,7 @@ import org.h2.jdbc.JdbcBlob;
import org.h2.jdbc.JdbcClob;
import org.h2.jdbc.JdbcConnection;
import org.h2.message.DbException;
import org.h2.store.LobStorage;
import org.h2.util.Utils;
import org.h2.util.New;
import org.h2.util.StringUtils;
......@@ -539,23 +540,23 @@ public class DataType {
}
case Value.CLOB: {
if (session == null) {
v = ValueLob.createSmallLob(Value.CLOB, StringUtils.utf8Encode(rs.getString(columnIndex)));
v = LobStorage.createSmallLob(Value.CLOB, StringUtils.utf8Encode(rs.getString(columnIndex)));
} else {
Reader in = rs.getCharacterStream(columnIndex);
if (in == null) {
v = ValueNull.INSTANCE;
} else {
v = ValueLob.createClob(new BufferedReader(in), -1, session.getDataHandler());
v = LobStorage.createClob(new BufferedReader(in), -1, session.getDataHandler());
}
}
break;
}
case Value.BLOB: {
if (session == null) {
v = ValueLob.createSmallLob(Value.BLOB, rs.getBytes(columnIndex));
v = LobStorage.createSmallLob(Value.BLOB, rs.getBytes(columnIndex));
} else {
InputStream in = rs.getBinaryStream(columnIndex);
v = (in == null) ? (Value) ValueNull.INSTANCE : ValueLob.createBlob(in, -1, session.getDataHandler());
v = (in == null) ? (Value) ValueNull.INSTANCE : LobStorage.createBlob(in, -1, session.getDataHandler());
}
break;
}
......@@ -863,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 ValueLob.createClob(r, -1, session.getDataHandler());
return LobStorage.createClob(r, -1, session.getDataHandler());
} else if (x instanceof java.sql.Clob) {
try {
Reader r = new BufferedReader(((java.sql.Clob) x).getCharacterStream());
return ValueLob.createClob(r, -1, session.getDataHandler());
return LobStorage.createClob(r, -1, session.getDataHandler());
} catch (SQLException e) {
throw DbException.convert(e);
}
} else if (x instanceof java.io.InputStream) {
return ValueLob.createBlob((java.io.InputStream) x, -1, session.getDataHandler());
return LobStorage.createBlob((java.io.InputStream) x, -1, session.getDataHandler());
} else if (x instanceof java.sql.Blob) {
try {
return ValueLob.createBlob(((java.sql.Blob) x).getBinaryStream(), -1, session.getDataHandler());
return LobStorage.createBlob(((java.sql.Blob) x).getBinaryStream(), -1, session.getDataHandler());
} catch (SQLException e) {
throw DbException.convert(e);
}
......
......@@ -29,6 +29,7 @@ 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;
......@@ -483,7 +484,7 @@ public class Transfer {
return ValueStringFixed.get(readString());
case Value.BLOB: {
long length = readLong();
ValueLob v = ValueLob.createBlob(in, length, session.getDataHandler());
Value v = LobStorage.createBlob(in, length, session.getDataHandler());
int magic = readInt();
if (magic != LOB_MAGIC) {
throw DbException.get(ErrorCode.CONNECTION_BROKEN_1, "magic=" + magic);
......@@ -492,7 +493,7 @@ public class Transfer {
}
case Value.CLOB: {
long length = readLong();
ValueLob v = ValueLob.createClob(new ExactUTF8InputStreamReader(in), length, session.getDataHandler());
Value v = LobStorage.createClob(new ExactUTF8InputStreamReader(in), length, session.getDataHandler());
int magic = readInt();
if (magic != LOB_MAGIC) {
throw DbException.get(ErrorCode.CONNECTION_BROKEN_1, "magic=" + magic);
......
......@@ -21,10 +21,11 @@ import org.h2.constant.ErrorCode;
import org.h2.constant.SysProperties;
import org.h2.message.DbException;
import org.h2.store.DataHandler;
import org.h2.store.LobStorage;
import org.h2.tools.SimpleResultSet;
import org.h2.util.Utils;
import org.h2.util.IOUtils;
import org.h2.util.StringUtils;
import org.h2.util.Utils;
/**
* This is the base class for all value classes.
......@@ -705,7 +706,7 @@ public abstract class Value {
case BLOB: {
switch(getType()) {
case BYTES:
return ValueLob.createSmallLob(Value.BLOB, getBytesNoCopy());
return LobStorage.createSmallLob(Value.BLOB, getBytesNoCopy());
}
break;
}
......@@ -763,9 +764,9 @@ public abstract class Value {
case FLOAT:
return ValueFloat.get(Float.parseFloat(s.trim()));
case CLOB:
return ValueLob.createSmallLob(CLOB, StringUtils.utf8Encode(s));
return LobStorage.createSmallLob(CLOB, StringUtils.utf8Encode(s));
case BLOB:
return ValueLob.createSmallLob(BLOB, Utils.convertStringToBytes(s.trim()));
return LobStorage.createSmallLob(BLOB, Utils.convertStringToBytes(s.trim()));
case ARRAY:
return ValueArray.get(new Value[]{ValueString.get(s)});
case RESULT_SET: {
......@@ -978,4 +979,16 @@ public abstract class Value {
throw DbException.getUnsupportedException(DataType.getDataType(getType()).name);
}
public int getTableId() {
return 0;
}
public byte[] getSmall() {
return null;
}
public Value copyToTemp() {
return this;
}
}
......@@ -48,11 +48,6 @@ public class ValueLob extends Value {
// (to create a large blob from pieces)
// and a getpart function (to get it in pieces) and make sure a file is created!
/**
* The 'table id' to use for session variables.
*/
public static final int TABLE_ID_SESSION_VARIABLE = -1;
/**
* This counter is used to calculate the next directory to store lobs. It is
* better than using a random number because less directories are created.
......
/*
* Copyright 2004-2010 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.value;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import org.h2.constant.SysProperties;
import org.h2.engine.Constants;
import org.h2.message.DbException;
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.fs.FileSystemMemory;
import org.h2.util.IOUtils;
import org.h2.util.MathUtils;
import org.h2.util.StringUtils;
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 long lobId;
private String fileName;
private boolean linked;
private byte[] small;
private int hash;
private boolean compression;
private FileStore tempFile;
private ValueLob2(int type, DataHandler handler, String fileName, int tableId, long lobId, boolean linked,
long precision, boolean compression) {
this.type = type;
this.handler = handler;
this.fileName = fileName;
this.tableId = tableId;
this.lobId = lobId;
this.linked = linked;
this.precision = precision;
this.compression = compression;
}
private ValueLob2(int type, byte[] small) {
this.type = type;
this.small = small;
if (small != null) {
if (type == Value.BLOB) {
this.precision = small.length;
} else {
this.precision = getString().length();
}
}
}
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;
}
/**
* Create a small lob using the given byte array.
*
* @param type the type (Value.BLOB or CLOB)
* @param small the byte array
* @return the lob value
*/
public static ValueLob2 createSmallLob(int type, byte[] 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
* except when converting to BLOB or CLOB.
*
* @param t the new type
* @return the converted value
*/
public Value convertTo(int t) {
if (t == type) {
return this;
} else if (t == Value.CLOB) {
ValueLob2 copy = ValueLob2.createClob(getReader(), -1, handler);
return copy;
} else if (t == Value.BLOB) {
ValueLob2 copy = ValueLob2.createBlob(getInputStream(), -1, handler);
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;
}
public void close() {
if (fileName != null) {
if (tempFile != null) {
tempFile.stopAutoDelete();
}
deleteFile(handler, fileName);
}
}
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;
}
}
}
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;
}
return this;
}
private int getNewObjectId(DataHandler h) {
// TODO Auto-generated method stub
return 0;
}
/**
* Get the current table id of this lob.
*
* @return the table id
*/
public int getTableId() {
return tableId;
}
public int getType() {
return type;
}
public long getPrecision() {
return precision;
}
public String getString() {
int len = precision > Integer.MAX_VALUE || precision == 0 ? Integer.MAX_VALUE : (int) precision;
try {
if (type == Value.CLOB) {
if (small != null) {
return StringUtils.utf8Decode(small);
}
return IOUtils.readStringAndClose(getReader(), len);
}
byte[] buff;
if (small != null) {
buff = small;
} else {
buff = IOUtils.readBytesAndClose(getInputStream(), len);
}
return Utils.convertBytesToString(buff);
} catch (IOException e) {
throw DbException.convertIOException(e, fileName);
}
}
public byte[] getBytes() {
if (type == CLOB) {
// convert hex to string
return super.getBytes();
}
byte[] data = getBytesNoCopy();
return Utils.cloneByteArray(data);
}
public byte[] getBytesNoCopy() {
if (type == CLOB) {
// convert hex to string
return super.getBytesNoCopy();
}
if (small != null) {
return small;
}
try {
return IOUtils.readBytesAndClose(getInputStream(), Integer.MAX_VALUE);
} catch (IOException e) {
throw DbException.convertIOException(e, fileName);
}
}
public int hashCode() {
if (hash == 0) {
if (precision > 4096) {
// TODO: should calculate the hash code when saving, and store
// it in the database file
return (int) (precision ^ (precision >>> 32));
}
if (type == CLOB) {
hash = getString().hashCode();
} else {
hash = Utils.getByteArrayHash(getBytes());
}
}
return hash;
}
protected int compareSecure(Value v, CompareMode mode) {
if (type == Value.CLOB) {
return Integer.signum(getString().compareTo(v.getString()));
}
byte[] v2 = v.getBytesNoCopy();
return Utils.compareNotNull(getBytes(), v2);
}
public Object getObject() {
if (type == Value.CLOB) {
return getReader();
}
return getInputStream();
}
public Reader getReader() {
return IOUtils.getReader(getInputStream());
}
public InputStream getInputStream() {
if (fileName == null) {
return new ByteArrayInputStream(small);
}
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 {
long p = getPrecision();
if (p > Integer.MAX_VALUE || p <= 0) {
p = -1;
}
if (type == Value.BLOB) {
prep.setBinaryStream(parameterIndex, getInputStream(), (int) p);
} else {
prep.setCharacterStream(parameterIndex, getReader(), (int) p);
}
}
public String getSQL() {
String s;
if (type == Value.CLOB) {
s = getString();
return StringUtils.quoteStringSQL(s);
}
byte[] buff = getBytes();
s = Utils.convertBytesToString(buff);
return "X'" + s + "'";
}
public String getTraceSQL() {
if (small != null && getPrecision() <= SysProperties.MAX_TRACE_DATA_LENGTH) {
return getSQL();
}
StringBuilder buff = new StringBuilder();
if (type == Value.CLOB) {
buff.append("SPACE(").append(getPrecision());
} else {
buff.append("CAST(REPEAT('00', ").append(getPrecision()).append(") AS BINARY");
}
buff.append(" /* ").append(fileName).append(" */)");
return buff.toString();
}
/**
* Get the data if this a small lob value.
*
* @return the data
*/
public byte[] getSmall() {
return small;
}
public int getDisplaySize() {
return MathUtils.convertLongToInt(getPrecision());
}
public boolean equals(Object other) {
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);
}
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);
}
}
}
}
/**
* Check if this lob value is compressed.
*
* @return true if it is
*/
public boolean useCompression() {
return compression;
}
public boolean isFileBased() {
return fileName != null;
}
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);
}
}
private static synchronized void renameFile(DataHandler handler, String oldName, String newName)
{
synchronized (handler.getLobSyncObject()) {
IOUtils.rename(oldName, newName);
}
}
private void copyFileTo(DataHandler h, String sourceFileName, String targetFileName) {
synchronized (h.getLobSyncObject()) {
FileSystem.getInstance(sourceFileName).copy(sourceFileName, targetFileName);
}
}
/**
* 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() {
if (small != null) {
return small.length + 32;
}
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);
}
return lob;
}
public long getLobId() {
return lobId;
}
}
......@@ -118,6 +118,7 @@ import org.h2.test.unit.TestIntArray;
import org.h2.test.unit.TestIntIntHashMap;
import org.h2.test.unit.TestMathUtils;
import org.h2.test.unit.TestNetUtils;
import org.h2.test.unit.TestOldVersion;
import org.h2.test.unit.TestOverflow;
import org.h2.test.unit.TestPageStore;
import org.h2.test.unit.TestPattern;
......@@ -292,6 +293,15 @@ java org.h2.test.TestAll timer
power failure test: larger binaries and additional index.
drop table test;
create table test(id identity, name varchar(100) default space(100));
@LOOP 10 insert into test select null, null from system_range(1, 100000);
delete from test;
insert 50,000,000 tuples into table
2. execute 'delete table where (always true)'
compatibility test for tcp/ip
rename Page* classes
move classes to the right packages
......@@ -400,6 +410,8 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
memory = true;
test();
memory = false;
networked = false;
diskUndo = true;
diskResult = true;
traceLevelFile = 3;
......@@ -568,6 +580,7 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
new TestIntArray().runTest(this);
new TestIntIntHashMap().runTest(this);
new TestMathUtils().runTest(this);
new TestOldVersion().runTest(this);
new TestNetUtils().runTest(this);
new TestMultiThreadedKernel().runTest(this);
new TestOverflow().runTest(this);
......
......@@ -1150,30 +1150,15 @@ public abstract class TestBase {
*/
protected void eatMemory(int remainingKB) {
byte[] reserve = new byte[remainingKB * 1024];
int max = 128 * 1024 * 1024;
int div = 2;
// first, eat memory in 16 KB blocks, then eat in 16 byte blocks
for (int size = 16 * 1024; size > 0; size /= 1024) {
while (true) {
long free = Runtime.getRuntime().freeMemory();
long freeTry = free / div;
int eat = (int) Math.min(max, freeTry);
try {
byte[] block = new byte[eat];
byte[] block = new byte[16 * 1024];
memory.add(block);
} catch (OutOfMemoryError e) {
if (eat < 32) {
break;
}
if (eat == max) {
max /= 2;
if (max < 128) {
break;
}
}
if (eat == freeTry) {
div += 1;
} else {
div = 2;
}
}
}
// silly code - makes sure there are no warnings
......
......@@ -32,8 +32,8 @@ public class TestCompatibility extends TestBase {
public void test() throws SQLException {
deleteDb("compatibility");
conn = getConnection("compatibility");
conn = getConnection("compatibility");
testDomain();
testColumnAlias();
testUniqueIndexSingleNull();
......
......@@ -13,6 +13,7 @@ import java.sql.Timestamp;
import org.h2.store.Data;
import org.h2.store.DataHandler;
import org.h2.store.FileStore;
import org.h2.store.LobStorage;
import org.h2.test.TestBase;
import org.h2.util.SmallLRUCache;
import org.h2.util.TempFileDeleter;
......@@ -304,4 +305,8 @@ public class TestDataPage extends TestBase implements DataHandler {
return TempFileDeleter.getInstance();
}
public LobStorage getLobStorage() {
return null;
}
}
......@@ -9,6 +9,7 @@ package org.h2.test.unit;
import java.util.Random;
import org.h2.store.DataHandler;
import org.h2.store.FileStore;
import org.h2.store.LobStorage;
import org.h2.test.TestBase;
import org.h2.util.IOUtils;
import org.h2.util.SmallLRUCache;
......@@ -169,4 +170,8 @@ public class TestFile extends TestBase implements DataHandler {
return TempFileDeleter.getInstance();
}
public LobStorage getLobStorage() {
return null;
}
}
......@@ -13,6 +13,7 @@ import java.util.HashMap;
import java.util.Random;
import org.h2.store.DataHandler;
import org.h2.store.FileStore;
import org.h2.store.LobStorage;
import org.h2.test.TestBase;
import org.h2.util.New;
import org.h2.util.SmallLRUCache;
......@@ -147,4 +148,8 @@ public class TestValueHashMap extends TestBase implements DataHandler {
return TempFileDeleter.getInstance();
}
public LobStorage getLobStorage() {
return null;
}
}
......@@ -15,10 +15,11 @@ import java.util.IdentityHashMap;
import java.util.Random;
import org.h2.store.DataHandler;
import org.h2.store.FileStore;
import org.h2.store.LobStorage;
import org.h2.test.TestBase;
import org.h2.util.Utils;
import org.h2.util.SmallLRUCache;
import org.h2.util.TempFileDeleter;
import org.h2.util.Utils;
import org.h2.value.Value;
import org.h2.value.ValueArray;
import org.h2.value.ValueBoolean;
......@@ -30,7 +31,6 @@ import org.h2.value.ValueDouble;
import org.h2.value.ValueFloat;
import org.h2.value.ValueInt;
import org.h2.value.ValueJavaObject;
import org.h2.value.ValueLob;
import org.h2.value.ValueLong;
import org.h2.value.ValueNull;
import org.h2.value.ValueShort;
......@@ -137,12 +137,12 @@ public class TestValueMemory extends TestBase implements DataHandler {
case Value.BLOB: {
int len = (int) Math.abs(random.nextGaussian() * 10);
byte[] data = randomBytes(len);
return ValueLob.createBlob(new ByteArrayInputStream(data), len, this);
return LobStorage.createBlob(new ByteArrayInputStream(data), len, this);
}
case Value.CLOB: {
int len = (int) Math.abs(random.nextGaussian() * 10);
String s = randomString(len);
return ValueLob.createClob(new StringReader(s), len, this);
return LobStorage.createClob(new StringReader(s), len, this);
}
case Value.ARRAY: {
int len = random.nextInt(20);
......@@ -226,4 +226,8 @@ public class TestValueMemory extends TestBase implements DataHandler {
return TempFileDeleter.getInstance();
}
public LobStorage getLobStorage() {
return null;
}
}
......@@ -17,6 +17,7 @@ import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Random;
import org.h2.constant.ErrorCode;
import org.h2.message.DbException;
......@@ -31,13 +32,15 @@ import org.h2.util.Profiler;
*/
public class TestLob {
private static final String LOBS = "INFORMATION_SCHEMA.LOBS";
private static final String LOB_MAP = "INFORMATION_SCHEMA.LOB_MAP";
private static final String LOB_DATA = "INFORMATION_SCHEMA.LOB_DATA";
private static final int BLOCK_LENGTH = 20000;
private static final boolean HASH = true;
private static final long UNIQUE = 0xffff;
private Connection conn;
private PreparedStatement prepInsertLob, prepInsertMap, prepInsertBlock;
private PreparedStatement prepSelectMapBlock, prepSelectBlock;
private PreparedStatement prepDeleteBlockUnused, prepDeleteMap, prepDeleteLob;
private HashMap<String, PreparedStatement> prepared = New.hashMap();
private long nextLob;
private long nextBlock;
......@@ -67,19 +70,17 @@ public class TestLob {
/**
* An input stream that reads from a LOB.
*/
static class LobInputStream extends InputStream {
class LobInputStream extends InputStream {
private byte[] buffer;
private int pos;
private PreparedStatement prepSelectMapBlock;
private long remaining;
private long lob;
private int seq;
LobInputStream(PreparedStatement prepSelectMapBlock, long lob, long length) {
LobInputStream(long lob, long length) {
this.lob = lob;
this.remaining = length;
this.prepSelectMapBlock = prepSelectMapBlock;
}
public int read() throws IOException {
......@@ -128,11 +129,13 @@ public class TestLob {
return;
}
try {
// select data from map m inner join block b
// on m.block = b.id where m.lob = ? and m.seq = ?
prepSelectMapBlock.setLong(1, lob);
prepSelectMapBlock.setInt(2, seq);
ResultSet rs = prepSelectMapBlock.executeQuery();
PreparedStatement prep = prepare(
"SELECT DATA FROM " + LOB_MAP + " M " +
"INNER JOIN " + LOB_DATA + " D ON M.BLOCK = D.BLOCK " +
"WHERE M.LOB = ? AND M.SEQ = ?");
prep.setLong(1, lob);
prep.setInt(2, seq);
ResultSet rs = prep.executeQuery();
if (!rs.next()) {
throw DbException.get(ErrorCode.IO_EXCEPTION_1, "lob: "+ lob + " seq: " + seq).getSQLException();
}
......@@ -144,7 +147,6 @@ public class TestLob {
}
}
}
/**
......@@ -220,9 +222,9 @@ public class TestLob {
for (int i = 0; i < count; i++) {
// random.nextBytes(buff);
if (regular) {
prep.setInt(1, x++);
prep.setBinaryStream(2, new ByteArrayInputStream(buff), len);
prep.execute();
// prep.setInt(1, x++);
// prep.setBinaryStream(2, new ByteArrayInputStream(buff), len);
// prep.execute();
} else {
LobId id = addLob(new ByteArrayInputStream(buff), -1, -1);
list.add(id);
......@@ -269,44 +271,53 @@ public class TestLob {
private void init(Connection newConn) throws SQLException {
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 lob(id bigint primary key, length bigint, table int)");
stat.execute("create table if not exists map(lob bigint, seq int, block bigint, primary key(lob, seq))");
stat.execute("create index idx_map_block on map(block, lob)");
stat.execute("create table if not exists block(id bigint primary key, data binary)");
// 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 TABLE IF NOT EXISTS " + LOB_DATA + "(BLOCK BIGINT PRIMARY KEY, DATA BINARY)");
ResultSet rs;
rs = stat.executeQuery("select max(id) from block");
rs = stat.executeQuery("SELECT MAX(BLOCK) FROM " + LOB_DATA);
rs.next();
nextBlock = rs.getLong(1) + 1;
if (HASH) {
nextBlock = Math.max(UNIQUE + 1, nextLob);
}
rs = stat.executeQuery("select max(id) from lob");
rs = stat.executeQuery("SELECT MAX(ID) FROM " + LOBS);
rs.next();
nextLob = rs.getLong(1) + 1;
prepInsertLob = conn.prepareStatement("insert into lob(id, length, table) values(?, ?, ?)");
prepInsertMap = conn.prepareStatement("insert into map(lob, seq, block) values(?, ?, ?)");
prepInsertBlock = conn.prepareStatement("insert into block(id, data) values(?, ?)");
prepSelectMapBlock = conn.prepareStatement("select data from map m inner join block b on m.block = b.id where m.lob = ? and m.seq = ?");
prepSelectBlock = conn.prepareStatement("select data from block where id = ?");
prepDeleteLob = conn.prepareStatement("delete from lob where id = ?");
prepDeleteMap = conn.prepareStatement("delete from map where lob = ?");
prepDeleteBlockUnused = conn.prepareStatement("delete from block where id in(select block from map where lob = ?) and not exists(select 1 from map where block = id and lob <> ?)");
}
protected synchronized PreparedStatement prepare(String sql) throws SQLException {
PreparedStatement prep = prepared.get(sql);
if (prep == null) {
prep = conn.prepareStatement(sql);
prepared.put(sql, prep);
}
return prep;
}
private void deleteLob(long lob) throws SQLException {
// delete from map where lob = ?
prepDeleteMap.setLong(1, lob);
prepDeleteMap.execute();
// delete from block where id in(select block from map where lob = ?)
// and not exists(select 1 from map where block = id and lob <> ?)
prepDeleteBlockUnused.setLong(1, lob);
prepDeleteBlockUnused.setLong(2, lob);
prepDeleteBlockUnused.execute();
// delete from lob where id = ?
prepDeleteLob.setLong(1, lob);
prepDeleteLob.execute();
PreparedStatement prep;
prep = prepare(
"DELETE FROM " + LOB_MAP + " " +
"WHERE LOB = ?");
prep.setLong(1, lob);
prep.execute();
prep = prepare(
"DELETE FROM " + LOB_DATA + " D " +
"WHERE BLOCK IN(SELECT M.BLOCK FROM " + LOB_MAP + " M WHERE LOB = ?) " +
"AND NOT EXISTS(SELECT 1 FROM " + LOB_MAP + " M " +
"WHERE M.BLOCK = D.BLOCK AND M.LOB <> ?)");
prep.setLong(1, lob);
prep.setLong(2, lob);
prep.execute();
prep = prepare(
"DELETE FROM " + LOBS + " " +
"WHERE ID = ?");
prep.setLong(1, lob);
prep.execute();
}
private LobId addLob(InputStream in, long maxLength, int table) throws SQLException {
......@@ -332,13 +343,16 @@ public class TestLob {
} else {
b = buff;
}
// insert into block(id, data) values(?, ?)
long block;
boolean blockExists = false;
if (HASH) {
block = Arrays.hashCode(b) & UNIQUE;
prepSelectBlock.setLong(1, block);
ResultSet rs = prepSelectBlock.executeQuery();
int todoSynchronize;
PreparedStatement prep = prepare(
"SELECT DATA FROM " + LOB_DATA +
" WHERE BLOCK = ?");
prep.setLong(1, block);
ResultSet rs = prep.executeQuery();
if (rs.next()) {
byte[] compare = rs.getBytes(1);
if (Arrays.equals(b, compare)) {
......@@ -351,27 +365,25 @@ public class TestLob {
block = nextBlock++;
}
if (!blockExists) {
// insert into block(id, data) values(?, ?)
prepInsertBlock.setLong(1, block);
prepInsertBlock.setBytes(2, b);
try {
prepInsertBlock.execute();
} catch (SQLException e) {
int test;
e.printStackTrace();
}
PreparedStatement prep = prepare(
"INSERT INTO " + LOB_DATA + "(BLOCK, DATA) VALUES(?, ?)");
prep.setLong(1, block);
prep.setBytes(2, b);
prep.execute();
}
// insert into map(lob, seq, block) values(?, ?, ?)
prepInsertMap.setLong(1, lob);
prepInsertMap.setInt(2, seq);
prepInsertMap.setLong(3, block);
prepInsertMap.execute();
PreparedStatement prep = prepare(
"INSERT INTO " + LOB_MAP + "(LOB, SEQ, BLOCK) VALUES(?, ?, ?)");
prep.setLong(1, lob);
prep.setInt(2, seq);
prep.setLong(3, block);
prep.execute();
}
// insert into lob(id, length, table) values(?, ?, ?)
prepInsertLob.setLong(1, lob);
prepInsertLob.setLong(2, length);
prepInsertLob.setInt(3, table);
prepInsertLob.execute();
PreparedStatement prep = prepare(
"INSERT INTO " + LOBS + "(ID, LENGTH, TABLE) VALUES(?, ?, ?)");
prep.setLong(1, lob);
prep.setLong(2, length);
prep.setInt(3, table);
prep.execute();
return new LobId(lob, length);
} catch (IOException e) {
deleteLob(lob);
......@@ -382,7 +394,7 @@ public class TestLob {
private InputStream getInputStream(LobId lobId) {
long id = lobId.getId();
long length = lobId.getLength();
return new LobInputStream(prepSelectMapBlock, id, length);
return new LobInputStream(id, length);
}
}
......@@ -532,6 +532,10 @@ public class Build extends BuildBase {
* Compile and run all tests.
*/
public void test() {
// for TestOldVersion
download("ext/h2-1.2.127.jar",
"http://repo1.maven.org/maven2/com/h2database/h2/1.2.127/h2-1.2.127.jar",
"056e784c7cf009483366ab9cd8d21d02fe47031a");
compile();
java("org.h2.test.TestAll", null);
}
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论