/*
 * Copyright 2004-2019 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.io.IOException;
import java.util.ArrayList;

import org.h2.engine.SessionInterface;
import org.h2.engine.SessionRemote;
import org.h2.engine.SysProperties;
import org.h2.message.DbException;
import org.h2.message.Trace;
import org.h2.value.Transfer;
import org.h2.value.TypeInfo;
import org.h2.value.Value;

The client side part of a result set that is kept on the server. In many cases, the complete data is kept on the client side, but for large results only a subset is in-memory.
/** * The client side part of a result set that is kept on the server. * In many cases, the complete data is kept on the client side, * but for large results only a subset is in-memory. */
public class ResultRemote implements ResultInterface { private int fetchSize; private SessionRemote session; private Transfer transfer; private int id; private final ResultColumn[] columns; private Value[] currentRow; private final int rowCount; private int rowId, rowOffset; private ArrayList<Value[]> result; private final Trace trace; public ResultRemote(SessionRemote session, Transfer transfer, int id, int columnCount, int fetchSize) throws IOException { this.session = session; trace = session.getTrace(); this.transfer = transfer; this.id = id; this.columns = new ResultColumn[columnCount]; rowCount = transfer.readInt(); for (int i = 0; i < columnCount; i++) { columns[i] = new ResultColumn(transfer); } rowId = -1; result = new ArrayList<>(Math.min(fetchSize, rowCount)); this.fetchSize = fetchSize; fetchRows(false); } @Override public boolean isLazy() { return false; } @Override public String getAlias(int i) { return columns[i].alias; } @Override public String getSchemaName(int i) { return columns[i].schemaName; } @Override public String getTableName(int i) { return columns[i].tableName; } @Override public String getColumnName(int i) { return columns[i].columnName; } @Override public TypeInfo getColumnType(int i) { return columns[i].columnType; } @Override public boolean isAutoIncrement(int i) { return columns[i].autoIncrement; } @Override public int getNullable(int i) { return columns[i].nullable; } @Override public void reset() { rowId = -1; currentRow = null; if (session == null) { return; } synchronized (session) { session.checkClosed(); try { session.traceOperation("RESULT_RESET", id); transfer.writeInt(SessionRemote.RESULT_RESET).writeInt(id).flush(); } catch (IOException e) { throw DbException.convertIOException(e, null); } } } @Override public Value[] currentRow() { return currentRow; } @Override public boolean next() { if (rowId < rowCount) { rowId++; remapIfOld(); if (rowId < rowCount) { if (rowId - rowOffset >= result.size()) { fetchRows(true); } currentRow = result.get(rowId - rowOffset); return true; } currentRow = null; } return false; } @Override public int getRowId() { return rowId; } @Override public boolean isAfterLast() { return rowId >= rowCount; } @Override public int getVisibleColumnCount() { return columns.length; } @Override public int getRowCount() { return rowCount; } @Override public boolean hasNext() { return rowId < rowCount - 1; } private void sendClose() { if (session == null) { return; } // TODO result sets: no reset possible for larger remote result sets try { synchronized (session) { session.traceOperation("RESULT_CLOSE", id); transfer.writeInt(SessionRemote.RESULT_CLOSE).writeInt(id); } } catch (IOException e) { trace.error(e, "close"); } finally { transfer = null; session = null; } } @Override public void close() { result = null; sendClose(); } private void remapIfOld() { if (session == null) { return; } try { if (id <= session.getCurrentId() - SysProperties.SERVER_CACHED_OBJECTS / 2) { // object is too old - we need to map it to a new id int newId = session.getNextId(); session.traceOperation("CHANGE_ID", id); transfer.writeInt(SessionRemote.CHANGE_ID).writeInt(id).writeInt(newId); id = newId; // TODO remote result set: very old result sets may be // already removed on the server (theoretically) - how to // solve this? } } catch (IOException e) { throw DbException.convertIOException(e, null); } } private void fetchRows(boolean sendFetch) { synchronized (session) { session.checkClosed(); try { rowOffset += result.size(); result.clear(); int fetch = Math.min(fetchSize, rowCount - rowOffset); if (sendFetch) { session.traceOperation("RESULT_FETCH_ROWS", id); transfer.writeInt(SessionRemote.RESULT_FETCH_ROWS). writeInt(id).writeInt(fetch); session.done(transfer); } for (int r = 0; r < fetch; r++) { boolean row = transfer.readBoolean(); if (!row) { break; } int len = columns.length; Value[] values = new Value[len]; for (int i = 0; i < len; i++) { Value v = transfer.readValue(); values[i] = v; } result.add(values); } if (rowOffset + result.size() >= rowCount) { sendClose(); } } catch (IOException e) { throw DbException.convertIOException(e, null); } } } @Override public String toString() { return "columns: " + columns.length + " rows: " + rowCount + " pos: " + rowId; } @Override public int getFetchSize() { return fetchSize; } @Override public void setFetchSize(int fetchSize) { this.fetchSize = fetchSize; } @Override public boolean needToClose() { return true; } @Override public ResultInterface createShallowCopy(SessionInterface targetSession) { // The operation is not supported on remote result. return null; } @Override public boolean isClosed() { return result == null; } }