/* * Copyright 2004-2011 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.result; import java.io.ByteArrayOutputStream; import java.util.ArrayList; import org.h2.engine.Constants; import org.h2.engine.Database; import org.h2.engine.Session; import org.h2.message.DbException; import org.h2.store.Data; import org.h2.store.FileStore; import org.h2.util.New; import org.h2.value.Value; /** * This class implements the disk buffer for the LocalResult class. */ class ResultDiskBuffer implements ResultExternal { private static final int READ_AHEAD = 128; private final Data rowBuff; private final ArrayList<ResultDiskTape> tapes; private final ResultDiskTape mainTape; private final SortOrder sort; private final int columnCount; private final int maxBufferSize; private FileStore file; private int rowCount; private final ResultDiskBuffer parent; private boolean closed; private int childCount; /** * Represents a virtual disk tape for the merge sort algorithm. * Each virtual disk tape is a region of the temp file. */ static class ResultDiskTape { /** * The start position of this tape in the file. */ long start; /** * The end position of this tape in the file. */ long end; /** * The current read position. */ long pos; /** * A list of rows in the buffer. */ ArrayList<Value[]> buffer = New.arrayList(); } ResultDiskBuffer(Session session, SortOrder sort, int columnCount) { this.parent = null; this.sort = sort; this.columnCount = columnCount; Database db = session.getDatabase(); rowBuff = Data.create(db, Constants.DEFAULT_PAGE_SIZE); String fileName = db.createTempFile(); file = db.openFile(fileName, "rw", false); file.setCheckedWriting(false); file.seek(FileStore.HEADER_LENGTH); if (sort != null) { tapes = New.arrayList(); mainTape = null; } else { tapes = null; mainTape = new ResultDiskTape(); mainTape.pos = FileStore.HEADER_LENGTH; } this.maxBufferSize = db.getSettings().largeResultBufferSize; } private ResultDiskBuffer(ResultDiskBuffer parent) { this.parent = parent; rowBuff = Data.create(parent.rowBuff.getHandler(), Constants.DEFAULT_PAGE_SIZE); file = parent.file; if (parent.tapes != null) { tapes = New.arrayList(); for (ResultDiskTape t : parent.tapes) { ResultDiskTape t2 = new ResultDiskTape(); t2.pos = t2.start = t.start; t2.end = t.end; tapes.add(t2); } } else { tapes = null; } if (parent.mainTape != null) { mainTape = new ResultDiskTape(); mainTape.pos = FileStore.HEADER_LENGTH; mainTape.start = parent.mainTape.start; mainTape.end = parent.mainTape.end; } else { mainTape = null; } sort = parent.sort; columnCount = parent.columnCount; maxBufferSize = parent.maxBufferSize; } public synchronized ResultDiskBuffer createShallowCopy() { if (closed || parent != null) { return null; } childCount++; return new ResultDiskBuffer(this); } public int addRows(ArrayList<Value[]> rows) { if (sort != null) { sort.sort(rows); } Data buff = rowBuff; long start = file.getFilePointer(); ByteArrayOutputStream buffer = new ByteArrayOutputStream(); int bufferLen = 0; for (Value[] row : rows) { buff.reset(); buff.writeInt(0); for (int j = 0; j < columnCount; j++) { Value v = row[j]; buff.checkCapacity(buff.getValueLen(v)); buff.writeValue(v); } buff.fillAligned(); int len = buff.length(); buff.setInt(0, len); if (maxBufferSize > 0) { buffer.write(buff.getBytes(), 0, len); bufferLen += len; if (bufferLen > maxBufferSize) { byte[] data = buffer.toByteArray(); buffer.reset(); file.write(data, 0, data.length); bufferLen = 0; } } else { file.write(buff.getBytes(), 0, len); } } if (bufferLen > 0) { byte[] data = buffer.toByteArray(); file.write(data, 0, data.length); } if (sort != null) { ResultDiskTape tape = new ResultDiskTape(); tape.start = start; tape.end = file.getFilePointer(); tapes.add(tape); } else { mainTape.end = file.getFilePointer(); } rowCount += rows.size(); return rowCount; } public void done() { file.seek(FileStore.HEADER_LENGTH); file.autoDelete(); } public void reset() { if (sort != null) { for (ResultDiskTape tape : tapes) { tape.pos = tape.start; tape.buffer = New.arrayList(); } } else { mainTape.pos = FileStore.HEADER_LENGTH; mainTape.buffer = New.arrayList(); } } private void readRow(ResultDiskTape tape) { int min = Constants.FILE_BLOCK_SIZE; Data buff = rowBuff; buff.reset(); file.readFully(buff.getBytes(), 0, min); int len = buff.readInt(); buff.checkCapacity(len); if (len - min > 0) { file.readFully(buff.getBytes(), min, len - min); } tape.pos += len; Value[] row = new Value[columnCount]; for (int k = 0; k < columnCount; k++) { row[k] = buff.readValue(); } tape.buffer.add(row); } public Value[] next() { return sort != null ? nextSorted() : nextUnsorted(); } private Value[] nextUnsorted() { file.seek(mainTape.pos); if (mainTape.buffer.size() == 0) { for (int j = 0; mainTape.pos < mainTape.end && j < READ_AHEAD; j++) { readRow(mainTape); } } Value[] row = mainTape.buffer.get(0); mainTape.buffer.remove(0); return row; } private Value[] nextSorted() { int next = -1; for (int i = 0, size = tapes.size(); i < size; i++) { ResultDiskTape tape = tapes.get(i); if (tape.buffer.size() == 0 && tape.pos < tape.end) { file.seek(tape.pos); for (int j = 0; tape.pos < tape.end && j < READ_AHEAD; j++) { readRow(tape); } } if (tape.buffer.size() > 0) { if (next == -1) { next = i; } else if (compareTapes(tape, tapes.get(next)) < 0) { next = i; } } } ResultDiskTape t = tapes.get(next); Value[] row = t.buffer.get(0); t.buffer.remove(0); return row; } private int compareTapes(ResultDiskTape a, ResultDiskTape b) { Value[] va = a.buffer.get(0); Value[] vb = b.buffer.get(0); return sort.compare(va, vb); } private synchronized void closeChild() { if (--childCount == 0 && closed) { file.closeAndDeleteSilently(); file = null; } } protected void finalize() { close(); } public synchronized void close() { if (closed) { return; } closed = true; if (parent != null) { parent.closeChild(); } else if (file != null) { if (childCount == 0) { file.closeAndDeleteSilently(); file = null; } } } public int removeRow(Value[] values) { throw DbException.throwInternalError(); } public boolean contains(Value[] values) { throw DbException.throwInternalError(); } public int addRow(Value[] values) { throw DbException.throwInternalError(); } }