org.h2.result.ResultTempTable Maven / Gradle / Ivy
/*
* Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.result;
import java.util.ArrayList;
import java.util.Arrays;
import org.h2.command.ddl.CreateTableData;
import org.h2.engine.Constants;
import org.h2.engine.Database;
import org.h2.engine.Session;
import org.h2.expression.Expression;
import org.h2.index.Cursor;
import org.h2.index.Index;
import org.h2.index.IndexType;
import org.h2.schema.Schema;
import org.h2.table.Column;
import org.h2.table.IndexColumn;
import org.h2.table.Table;
import org.h2.value.Value;
import org.h2.value.ValueNull;
/**
* This class implements the temp table buffer for the LocalResult class.
*/
public class ResultTempTable implements ResultExternal {
private static final String COLUMN_NAME = "DATA";
private final boolean distinct;
private final SortOrder sort;
private Index index;
private Session session;
private Table table;
private Cursor resultCursor;
private int rowCount;
private int columnCount;
private final ResultTempTable parent;
private boolean closed;
private int childCount;
private boolean containsLob;
ResultTempTable(Session session, Expression[] expressions, boolean distinct, SortOrder sort) {
this.session = session;
this.distinct = distinct;
this.sort = sort;
this.columnCount = expressions.length;
Schema schema = session.getDatabase().getSchema(Constants.SCHEMA_MAIN);
CreateTableData data = new CreateTableData();
for (int i = 0; i < expressions.length; i++) {
int type = expressions[i].getType();
Column col = new Column(COLUMN_NAME + i,
type);
if (type == Value.CLOB || type == Value.BLOB) {
containsLob = true;
}
data.columns.add(col);
}
data.id = session.getDatabase().allocateObjectId();
data.tableName = "TEMP_RESULT_SET_" + data.id;
data.temporary = true;
data.persistIndexes = false;
data.persistData = true;
data.create = true;
data.session = session;
table = schema.createTable(data);
if (sort != null || distinct) {
createIndex();
}
parent = null;
}
private ResultTempTable(ResultTempTable parent) {
this.parent = parent;
this.columnCount = parent.columnCount;
this.distinct = parent.distinct;
this.session = parent.session;
this.table = parent.table;
this.index = parent.index;
this.rowCount = parent.rowCount;
this.sort = parent.sort;
this.containsLob = parent.containsLob;
reset();
}
private void createIndex() {
IndexColumn[] indexCols = null;
// If we need to do distinct, the distinct columns may not match the
// sort columns. So we need to disregard the sort. Not ideal.
if (sort != null && !distinct) {
int[] colIndex = sort.getQueryColumnIndexes();
indexCols = new IndexColumn[colIndex.length];
for (int i = 0; i < colIndex.length; i++) {
IndexColumn indexColumn = new IndexColumn();
indexColumn.column = table.getColumn(colIndex[i]);
indexColumn.sortType = sort.getSortTypes()[i];
indexColumn.columnName = COLUMN_NAME + i;
indexCols[i] = indexColumn;
}
} else {
indexCols = new IndexColumn[columnCount];
for (int i = 0; i < columnCount; i++) {
IndexColumn indexColumn = new IndexColumn();
indexColumn.column = table.getColumn(i);
indexColumn.columnName = COLUMN_NAME + i;
indexCols[i] = indexColumn;
}
}
String indexName = table.getSchema().getUniqueIndexName(session,
table, Constants.PREFIX_INDEX);
int indexId = session.getDatabase().allocateObjectId();
IndexType indexType = IndexType.createNonUnique(true);
index = table.addIndex(session, indexName, indexId, indexCols,
indexType, true, null);
}
@Override
public synchronized ResultExternal createShallowCopy() {
if (parent != null) {
return parent.createShallowCopy();
}
if (closed) {
return null;
}
childCount++;
return new ResultTempTable(this);
}
@Override
public int removeRow(Value[] values) {
Row row = convertToRow(values);
Cursor cursor = find(row);
if (cursor != null) {
row = cursor.get();
table.removeRow(session, row);
rowCount--;
}
return rowCount;
}
@Override
public boolean contains(Value[] values) {
return find(convertToRow(values)) != null;
}
@Override
public int addRow(Value[] values) {
Row row = convertToRow(values);
if (distinct) {
Cursor cursor = find(row);
if (cursor == null) {
table.addRow(session, row);
rowCount++;
}
} else {
table.addRow(session, row);
rowCount++;
}
return rowCount;
}
@Override
public int addRows(ArrayList rows) {
// speeds up inserting, but not really needed:
if (sort != null) {
sort.sort(rows);
}
for (Value[] values : rows) {
addRow(values);
}
return rowCount;
}
private synchronized void closeChild() {
if (--childCount == 0 && closed) {
dropTable();
}
}
@Override
public synchronized void close() {
if (closed) {
return;
}
closed = true;
if (parent != null) {
parent.closeChild();
} else {
if (childCount == 0) {
dropTable();
}
}
}
private void dropTable() {
if (table == null) {
return;
}
if (containsLob) {
// contains BLOB or CLOB: can not truncate now,
// otherwise the BLOB and CLOB entries are removed
return;
}
try {
Database database = session.getDatabase();
// Need to lock because not all of the code-paths
// that reach here have already taken this lock,
// notably via the close() paths.
synchronized (session) {
synchronized (database) {
table.truncate(session);
}
}
// This session may not lock the sys table (except if it already has
// locked it) because it must be committed immediately, otherwise
// other threads can not access the sys table. If the table is not
// removed now, it will be when the database is opened the next
// time. (the table is truncated, so this is just one record)
if (!database.isSysTableLocked()) {
Session sysSession = database.getSystemSession();
table.removeChildrenAndResources(sysSession);
if (index != null) {
// need to explicitly do this,
// as it's not registered in the system session
session.removeLocalTempTableIndex(index);
}
// the transaction must be committed immediately
// TODO this synchronization cascade is very ugly
synchronized (session) {
synchronized (sysSession) {
synchronized (database) {
sysSession.commit(false);
}
}
}
}
} finally {
table = null;
}
}
@Override
public void done() {
// nothing to do
}
@Override
public Value[] next() {
if (resultCursor == null) {
Index idx;
if (distinct || sort != null) {
idx = index;
} else {
idx = table.getScanIndex(session);
}
if (session.getDatabase().getMvStore() != null) {
// sometimes the transaction is already committed,
// in which case we can't use the session
if (idx.getRowCount(session) == 0 && rowCount > 0) {
// this means querying is not transactional
resultCursor = idx.find((Session) null, null, null);
} else {
// the transaction is still open
resultCursor = idx.find(session, null, null);
}
} else {
resultCursor = idx.find(session, null, null);
}
}
if (!resultCursor.next()) {
return null;
}
Row row = resultCursor.get();
return row.getValueList();
}
@Override
public void reset() {
resultCursor = null;
}
private Row convertToRow(Value[] values) {
if (values.length < columnCount) {
Value[] v2 = Arrays.copyOf(values, columnCount);
for (int i = values.length; i < columnCount; i++) {
v2[i] = ValueNull.INSTANCE;
}
values = v2;
}
return session.createRow(values, Row.MEMORY_CALCULATE);
}
private Cursor find(Row row) {
if (index == null) {
// for the case "in(select ...)", the query might
// use an optimization and not create the index
// up front
createIndex();
}
Cursor cursor = index.find(session, row, row);
while (cursor.next()) {
SearchRow found = cursor.getSearchRow();
boolean ok = true;
Database db = session.getDatabase();
for (int i = 0; i < row.getColumnCount(); i++) {
if (!db.areEqual(row.getValue(i), found.getValue(i))) {
ok = false;
break;
}
}
if (ok) {
return cursor;
}
}
return null;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy