Hi everyone,

Here is one PR #1424
<https://github.com/h2database/h2database/pull/1424>

Issue #1405 <https://github.com/h2database/h2database/issues/1405>
Add ability to external implementation of the LocalResult. It may be used 
to track allocated RAM, custom results storage, 
custom manage of externalize results.

I wrote the code, it's mine, and I'm contributing it to H2 for distribution 
multiple-licensed under the MPL 2.0, and the EPL 1.0 (
http://h2database.com/html/license.html).

-- 
You received this message because you are subscribed to the Google Groups "H2 
Database" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to h2-database+unsubscr...@googlegroups.com.
To post to this group, send email to h2-database@googlegroups.com.
Visit this group at https://groups.google.com/group/h2-database.
For more options, visit https://groups.google.com/d/optout.
Index: h2/src/main/org/h2/result/LocalResult.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- h2/src/main/org/h2/result/LocalResult.java	(revision 5f682ecea5dc182df2cd42f4c26c60a72a90e538)
+++ h2/src/main/org/h2/result/LocalResult.java	(date 1536655889000)
@@ -5,604 +5,75 @@
  */
 package org.h2.result;
 
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.util.ArrayList;
-import java.util.Arrays;
-
-import org.h2.engine.Database;
-import org.h2.engine.Session;
-import org.h2.engine.SessionInterface;
-import org.h2.expression.Expression;
-import org.h2.message.DbException;
-import org.h2.mvstore.db.MVTempResult;
-import org.h2.util.Utils;
-import org.h2.util.ValueHashMap;
-import org.h2.value.DataType;
+import org.h2.engine.SysProperties;
 import org.h2.value.Value;
-import org.h2.value.ValueArray;
 
 /**
  * A local result set contains all row data of a result set.
- * This is the object generated by engine,
+ * The object is generated by {@link LocalResultFactory},
  * and it is also used directly by the ResultSet class in the embedded mode.
- * If the result does not fit in memory, it is written to a temporary file.
+ * The memory usage and other policies are defined by implementation.
  */
-public class LocalResult implements ResultInterface, ResultTarget {
-
-    private int maxMemoryRows;
-    private Session session;
-    private int visibleColumnCount;
-    private Expression[] expressions;
-    private int rowId, rowCount;
-    private ArrayList<Value[]> rows;
-    private SortOrder sort;
-    private ValueHashMap<Value[]> distinctRows;
-    private Value[] currentRow;
-    private int offset;
-    private int limit = -1;
-    private boolean fetchPercent;
-    private boolean withTies;
-    private boolean limitsWereApplied;
-    private ResultExternal external;
-    private boolean distinct;
-    private int[] distinctIndexes;
-    private boolean closed;
-    private boolean containsLobs;
-
-    /**
-     * Construct a local result object.
-     */
-    public LocalResult() {
-        // nothing to do
-    }
-
+public interface LocalResult extends ResultInterface, ResultTarget {
     /**
-     * Construct a local result object.
+     * Redefine count of maximum rows holds in memory for the result.
      *
-     * @param session the session
-     * @param expressions the expression array
-     * @param visibleColumnCount the number of visible columns
-     */
-    public LocalResult(Session session, Expression[] expressions,
-            int visibleColumnCount) {
-        this.session = session;
-        if (session == null) {
-            this.maxMemoryRows = Integer.MAX_VALUE;
-        } else {
-            Database db = session.getDatabase();
-            if (db.isPersistent() && !db.isReadOnly()) {
-                this.maxMemoryRows = session.getDatabase().getMaxMemoryRows();
-            } else {
-                this.maxMemoryRows = Integer.MAX_VALUE;
-            }
-        }
-        rows = Utils.newSmallArrayList();
-        this.visibleColumnCount = visibleColumnCount;
-        rowId = -1;
-        this.expressions = expressions;
-    }
-
-    @Override
-    public boolean isLazy() {
-        return false;
-    }
-
-    public void setMaxMemoryRows(int maxValue) {
-        this.maxMemoryRows = maxValue;
-    }
-
-    /**
-     * Construct a local result set by reading all data from a regular result
-     * set.
+     * @param maxValue Maximum rows count in memory.
      *
-     * @param session the session
-     * @param rs the result set
-     * @param maxrows the maximum number of rows to read (0 for no limit)
-     * @return the local result set
+     * @see SysProperties#MAX_MEMORY_ROWS
      */
-    public static LocalResult read(Session session, ResultSet rs, int maxrows) {
-        Expression[] cols = Expression.getExpressionColumns(session, rs);
-        int columnCount = cols.length;
-        LocalResult result = new LocalResult(session, cols, columnCount);
-        try {
-            for (int i = 0; (maxrows == 0 || i < maxrows) && rs.next(); i++) {
-                Value[] list = new Value[columnCount];
-                for (int j = 0; j < columnCount; j++) {
-                    int type = result.getColumnType(j);
-                    list[j] = DataType.readValue(session, rs, j + 1, type);
-                }
-                result.addRow(list);
-            }
-        } catch (SQLException e) {
-            throw DbException.convert(e);
-        }
-        result.done();
-        return result;
-    }
+    public void setMaxMemoryRows(int maxValue);
 
     /**
-     * Create a shallow copy of the result set. The data and a temporary table
-     * (if there is any) is not copied.
-     *
-     * @param targetSession the session of the copy
-     * @return the copy if possible, or null if copying is not possible
+     * @param sort Sort order.
      */
-    @Override
-    public LocalResult createShallowCopy(SessionInterface targetSession) {
-        if (external == null && (rows == null || rows.size() < rowCount)) {
-            return null;
-        }
-        if (containsLobs) {
-            return null;
-        }
-        ResultExternal e2 = null;
-        if (external != null) {
-            e2 = external.createShallowCopy();
-            if (e2 == null) {
-                return null;
-            }
-        }
-        LocalResult copy = new LocalResult();
-        copy.maxMemoryRows = this.maxMemoryRows;
-        copy.session = (Session) targetSession;
-        copy.visibleColumnCount = this.visibleColumnCount;
-        copy.expressions = this.expressions;
-        copy.rowId = -1;
-        copy.rowCount = this.rowCount;
-        copy.rows = this.rows;
-        copy.sort = this.sort;
-        copy.distinctRows = this.distinctRows;
-        copy.distinct = distinct;
-        copy.distinctIndexes = distinctIndexes;
-        copy.currentRow = null;
-        copy.offset = 0;
-        copy.limit = -1;
-        copy.external = e2;
-        return copy;
-    }
-
-    /**
-     * Set the sort order.
-     *
-     * @param sort the sort order
-     */
-    public void setSortOrder(SortOrder sort) {
-        this.sort = sort;
-    }
+    public void setSortOrder(SortOrder sort);
 
     /**
      * Remove duplicate rows.
      */
-    public void setDistinct() {
-        assert distinctIndexes == null;
-        distinct = true;
-        distinctRows = ValueHashMap.newInstance();
-    }
+    public void setDistinct();
 
     /**
      * Remove rows with duplicates in columns with specified indexes.
      *
      * @param distinctIndexes distinct indexes
      */
-    public void setDistinct(int[] distinctIndexes) {
-        assert !distinct;
-        this.distinctIndexes = distinctIndexes;
-        distinctRows = ValueHashMap.newInstance();
-    }
-
-    /**
-     * @return whether this result is a distinct result
-     */
-    public boolean isAnyDistinct() {
-        return distinct || distinctIndexes != null;
-    }
+    public void setDistinct(int[] distinctIndexes);
 
     /**
      * Remove the row from the result set if it exists.
      *
      * @param values the row
      */
-    public void removeDistinct(Value[] values) {
-        if (!distinct) {
-            DbException.throwInternalError();
-        }
-        assert values.length == visibleColumnCount;
-        if (distinctRows != null) {
-            ValueArray array = ValueArray.get(values);
-            distinctRows.remove(array);
-            rowCount = distinctRows.size();
-        } else {
-            rowCount = external.removeRow(values);
-        }
-    }
-
-    /**
-     * Check if this result set contains the given row.
-     *
-     * @param values the row
-     * @return true if the row exists
-     */
-    @Override
-    public boolean containsDistinct(Value[] values) {
-        assert values.length == visibleColumnCount;
-        if (external != null) {
-            return external.contains(values);
-        }
-        if (distinctRows == null) {
-            distinctRows = ValueHashMap.newInstance();
-            for (Value[] row : rows) {
-                ValueArray array = getArrayOfDistinct(row);
-                distinctRows.put(array, array.getList());
-            }
-        }
-        ValueArray array = ValueArray.get(values);
-        return distinctRows.get(array) != null;
-    }
-
-    @Override
-    public void reset() {
-        rowId = -1;
-        currentRow = null;
-        if (external != null) {
-            external.reset();
-        }
-    }
-
-    @Override
-    public Value[] currentRow() {
-        return currentRow;
-    }
-
-    @Override
-    public boolean next() {
-        if (!closed && rowId < rowCount) {
-            rowId++;
-            if (rowId < rowCount) {
-                if (external != null) {
-                    currentRow = external.next();
-                } else {
-                    currentRow = rows.get(rowId);
-                }
-                return true;
-            }
-            currentRow = null;
-        }
-        return false;
-    }
-
-    @Override
-    public int getRowId() {
-        return rowId;
-    }
-
-    @Override
-    public boolean isAfterLast() {
-        return rowId >= rowCount;
-    }
-
-    private void cloneLobs(Value[] values) {
-        for (int i = 0; i < values.length; i++) {
-            Value v = values[i];
-            Value v2 = v.copyToResult();
-            if (v2 != v) {
-                containsLobs = true;
-                session.addTemporaryLob(v2);
-                values[i] = v2;
-            }
-        }
-    }
-
-    private ValueArray getArrayOfDistinct(Value[] values) {
-        if (distinctIndexes != null) {
-            int cnt = distinctIndexes.length;
-            Value[] newValues = new Value[cnt];
-            for (int i = 0; i < cnt; i++) {
-                newValues[i] = values[distinctIndexes[i]];
-            }
-            values = newValues;
-        } else if (values.length > visibleColumnCount) {
-            values = Arrays.copyOf(values, visibleColumnCount);
-        }
-        return ValueArray.get(values);
-    }
-
-    private void createExternalResult() {
-        Database database = session.getDatabase();
-        external = database.isMVStore()
-                || /* not supported by ResultTempTable */ distinct && expressions.length != visibleColumnCount
-                || distinctIndexes != null
-                ? MVTempResult.of(database, expressions, distinct, distinctIndexes, visibleColumnCount, sort)
-                        : new ResultTempTable(session, expressions, distinct, sort);
-    }
-
-    /**
-     * Add a row to this object.
-     *
-     * @param values the row to add
-     */
-    @Override
-    public void addRow(Value[] values) {
-        cloneLobs(values);
-        if (isAnyDistinct()) {
-            if (distinctRows != null) {
-                ValueArray array = getArrayOfDistinct(values);
-                distinctRows.putIfAbsent(array, values);
-                rowCount = distinctRows.size();
-                if (rowCount > maxMemoryRows) {
-                    createExternalResult();
-                    rowCount = external.addRows(distinctRows.values());
-                    distinctRows = null;
-                }
-            } else {
-                rowCount = external.addRow(values);
-            }
-        } else {
-            rows.add(values);
-            rowCount++;
-            if (rows.size() > maxMemoryRows) {
-                addRowsToDisk();
-            }
-        }
-    }
-
-    private void addRowsToDisk() {
-        if (external == null) {
-            createExternalResult();
-        }
-        rowCount = external.addRows(rows);
-        rows.clear();
-    }
-
-    @Override
-    public int getVisibleColumnCount() {
-        return visibleColumnCount;
-    }
+    public void removeDistinct(Value[] values);
 
     /**
      * This method is called after all rows have been added.
      */
-    public void done() {
-        if (external != null) {
-            addRowsToDisk();
-        } else {
-            if (isAnyDistinct()) {
-                rows = distinctRows.values();
-            }
-            if (sort != null && limit != 0) {
-                boolean withLimit = limit > 0 && !withTies;
-                if (offset > 0 || withLimit) {
-                    sort.sort(rows, offset, withLimit ? limit : rows.size());
-                } else {
-                    sort.sort(rows);
-                }
-            }
-        }
-        applyOffsetAndLimit();
-        reset();
-    }
-
-    private void applyOffsetAndLimit() {
-        if (limitsWereApplied) {
-            return;
-        }
-        int offset = Math.max(this.offset, 0);
-        int limit = this.limit;
-        if (offset == 0 && limit < 0 && !fetchPercent || rowCount == 0) {
-            return;
-        }
-        if (fetchPercent) {
-            if (limit < 0 || limit > 100) {
-                throw DbException.getInvalidValueException("FETCH PERCENT", limit);
-            }
-            // Oracle rounds percent up, do the same for now
-            limit = (int) (((long) limit * rowCount + 99) / 100);
-        }
-        boolean clearAll = offset >= rowCount || limit == 0;
-        if (!clearAll) {
-            int remaining = rowCount - offset;
-            limit = limit < 0 ? remaining : Math.min(remaining, limit);
-            if (offset == 0 && remaining <= limit) {
-                return;
-            }
-        } else {
-            limit = 0;
-        }
-        distinctRows = null;
-        rowCount = limit;
-        if (external == null) {
-            if (clearAll) {
-                rows.clear();
-                return;
-            }
-            int to = offset + limit;
-            if (withTies && sort != null) {
-                Value[] expected = rows.get(to - 1);
-                while (to < rows.size() && sort.compare(expected, rows.get(to)) == 0) {
-                    to++;
-                    rowCount++;
-                }
-            }
-            if (offset != 0 || to != rows.size()) {
-                // avoid copying the whole array for each row
-                rows = new ArrayList<>(rows.subList(offset, to));
-            }
-        } else {
-            if (clearAll) {
-                external.close();
-                external = null;
-                return;
-            }
-            trimExternal(offset, limit);
-        }
-    }
-
-    private void trimExternal(int offset, int limit) {
-        ResultExternal temp = external;
-        external = null;
-        temp.reset();
-        while (--offset >= 0) {
-            temp.next();
-        }
-        Value[] row = null;
-        while (--limit >= 0) {
-            row = temp.next();
-            rows.add(row);
-            if (rows.size() > maxMemoryRows) {
-                addRowsToDisk();
-            }
-        }
-        if (withTies && sort != null && row != null) {
-            Value[] expected = row;
-            while ((row = temp.next()) != null && sort.compare(expected, row) == 0) {
-                rows.add(row);
-                rowCount++;
-                if (rows.size() > maxMemoryRows) {
-                    addRowsToDisk();
-                }
-            }
-        }
-        if (external != null) {
-            addRowsToDisk();
-        }
-        temp.close();
-    }
-
-    @Override
-    public int getRowCount() {
-        return rowCount;
-    }
-
-    @Override
-    public void limitsWereApplied() {
-        this.limitsWereApplied = true;
-    }
-
-    @Override
-    public boolean hasNext() {
-        return !closed && rowId < rowCount - 1;
-    }
+    public void done();
 
     /**
      * Set the number of rows that this result will return at the maximum.
      *
      * @param limit the limit (-1 means no limit, 0 means no rows)
      */
-    public void setLimit(int limit) {
-        this.limit = limit;
-    }
+    public void setLimit(int limit);
 
     /**
      * @param fetchPercent whether limit expression specifies percentage of rows
      */
-    public void setFetchPercent(boolean fetchPercent) {
-        this.fetchPercent = fetchPercent;
-    }
+    public void setFetchPercent(boolean fetchPercent);
 
     /**
      * @param withTies whether tied rows should be included in result too
      */
-    public void setWithTies(boolean withTies) {
-        this.withTies = withTies;
-    }
-
-    @Override
-    public boolean needToClose() {
-        return external != null;
-    }
-
-    @Override
-    public void close() {
-        if (external != null) {
-            external.close();
-            external = null;
-            closed = true;
-        }
-    }
-
-    @Override
-    public String getAlias(int i) {
-        return expressions[i].getAlias();
-    }
-
-    @Override
-    public String getTableName(int i) {
-        return expressions[i].getTableName();
-    }
-
-    @Override
-    public String getSchemaName(int i) {
-        return expressions[i].getSchemaName();
-    }
-
-    @Override
-    public int getDisplaySize(int i) {
-        return expressions[i].getDisplaySize();
-    }
-
-    @Override
-    public String getColumnName(int i) {
-        return expressions[i].getColumnName();
-    }
-
-    @Override
-    public int getColumnType(int i) {
-        return expressions[i].getType();
-    }
-
-    @Override
-    public long getColumnPrecision(int i) {
-        return expressions[i].getPrecision();
-    }
-
-    @Override
-    public int getNullable(int i) {
-        return expressions[i].getNullable();
-    }
-
-    @Override
-    public boolean isAutoIncrement(int i) {
-        return expressions[i].isAutoIncrement();
-    }
-
-    @Override
-    public int getColumnScale(int i) {
-        return expressions[i].getScale();
-    }
+    public void setWithTies(boolean withTies);
 
     /**
      * Set the offset of the first row to return.
      *
      * @param offset the offset
      */
-    public void setOffset(int offset) {
-        this.offset = offset;
-    }
-
-    @Override
-    public String toString() {
-        return super.toString() + " columns: " + visibleColumnCount +
-                " rows: " + rowCount + " pos: " + rowId;
-    }
-
-    /**
-     * Check if this result set is closed.
-     *
-     * @return true if it is
-     */
-    @Override
-    public boolean isClosed() {
-        return closed;
-    }
-
-    @Override
-    public int getFetchSize() {
-        return 0;
-    }
-
-    @Override
-    public void setFetchSize(int fetchSize) {
-        // ignore
-    }
-
+    public void setOffset(int offset);
 }
Index: h2/src/main/org/h2/command/dml/Call.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- h2/src/main/org/h2/command/dml/Call.java	(revision 5f682ecea5dc182df2cd42f4c26c60a72a90e538)
+++ h2/src/main/org/h2/command/dml/Call.java	(date 1536655889000)
@@ -12,6 +12,7 @@
 import org.h2.expression.Expression;
 import org.h2.expression.ExpressionVisitor;
 import org.h2.result.LocalResult;
+import org.h2.result.LocalResultFactory;
 import org.h2.result.ResultInterface;
 import org.h2.value.Value;
 
@@ -34,9 +35,9 @@
         LocalResult result;
         if (isResultSet) {
             Expression[] expr = expression.getExpressionColumns(session);
-            result = new LocalResult(session, expr, expr.length);
+            result = session.getDatabase().getResultFactory().create(session, expr, expr.length);
         } else {
-            result = new LocalResult(session, expressions, 1);
+            result = session.getDatabase().getResultFactory().create(session, expressions, 1);
         }
         result.done();
         return result;
@@ -66,9 +67,9 @@
         if (isResultSet) {
             v = v.convertTo(Value.RESULT_SET);
             ResultSet rs = v.getResultSet();
-            return LocalResult.read(session, rs, maxrows);
+            return LocalResultFactory.read(session, rs, maxrows);
         }
-        LocalResult result = new LocalResult(session, expressions, 1);
+        LocalResult result = session.getDatabase().getResultFactory().create(session, expressions, 1);
         Value[] row = { v };
         result.addRow(row);
         result.done();
Index: h2/src/main/org/h2/command/dml/ScriptCommand.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- h2/src/main/org/h2/command/dml/ScriptCommand.java	(revision 5f682ecea5dc182df2cd42f4c26c60a72a90e538)
+++ h2/src/main/org/h2/command/dml/ScriptCommand.java	(date 1536655889000)
@@ -137,7 +137,7 @@
     private LocalResult createResult() {
         Expression[] expressions = { new ExpressionColumn(
                 session.getDatabase(), new Column("SCRIPT", Value.STRING)) };
-        return new LocalResult(session, expressions, 1);
+        return session.getDatabase().getResultFactory().create(session, expressions, 1);
     }
 
     @Override
Index: h2/src/main/org/h2/engine/Database.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- h2/src/main/org/h2/engine/Database.java	(revision 5f682ecea5dc182df2cd42f4c26c60a72a90e538)
+++ h2/src/main/org/h2/engine/Database.java	(date 1536655889000)
@@ -41,6 +41,7 @@
 import org.h2.message.TraceSystem;
 import org.h2.mvstore.MVStore;
 import org.h2.mvstore.db.MVTableEngine;
+import org.h2.result.LocalResultFactory;
 import org.h2.result.Row;
 import org.h2.result.RowFactory;
 import org.h2.result.SearchRow;
@@ -230,6 +231,7 @@
     private int queryStatisticsMaxEntries = Constants.QUERY_STATISTICS_MAX_ENTRIES;
     private QueryStatisticsData queryStatisticsData;
     private RowFactory rowFactory = RowFactory.DEFAULT;
+    private LocalResultFactory resultFactory = LocalResultFactory.DEFAULT;
 
     private Authenticator authenticator;
 
@@ -369,6 +371,14 @@
         this.rowFactory = rowFactory;
     }
 
+    public LocalResultFactory getResultFactory() {
+        return resultFactory;
+    }
+
+    public void setResultFactory(LocalResultFactory resultFactory) {
+        this.resultFactory = resultFactory;
+    }
+
     public static void setInitialPowerOffCount(int count) {
         initialPowerOffCount = count;
     }
Index: h2/src/main/org/h2/engine/GeneratedKeys.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- h2/src/main/org/h2/engine/GeneratedKeys.java	(revision 5f682ecea5dc182df2cd42f4c26c60a72a90e538)
+++ h2/src/main/org/h2/engine/GeneratedKeys.java	(date 1536655889000)
@@ -128,7 +128,7 @@
         Database db = session.getDatabase();
         if (Boolean.FALSE.equals(generatedKeysRequest)) {
             clear(null);
-            return new LocalResult();
+            return db.getResultFactory().create();
         }
         ArrayList<ExpressionColumn> expressionColumns;
         if (Boolean.TRUE.equals(generatedKeysRequest)) {
@@ -152,7 +152,7 @@
                 }
             } else {
                 clear(null);
-                return new LocalResult();
+                return db.getResultFactory().create();
             }
         } else if (generatedKeysRequest instanceof String[]) {
             if (table != null) {
@@ -182,18 +182,19 @@
                 }
             } else {
                 clear(null);
-                return new LocalResult();
+                return db.getResultFactory().create();
             }
         } else {
             clear(null);
-            return new LocalResult();
+            return db.getResultFactory().create();
         }
         int columnCount = expressionColumns.size();
         if (columnCount == 0) {
             clear(null);
-            return new LocalResult();
+            return db.getResultFactory().create();
         }
-        LocalResult result = new LocalResult(session, expressionColumns.toArray(new Expression[0]), columnCount);
+        LocalResult result = db.getResultFactory().create(session,
+            expressionColumns.toArray(new Expression[0]), columnCount);
         for (Map<Column, Value> map : data) {
             Value[] row = new Value[columnCount];
             for (Map.Entry<Column, Value> entry : map.entrySet()) {
Index: h2/src/main/org/h2/command/dml/Explain.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- h2/src/main/org/h2/command/dml/Explain.java	(revision 5f682ecea5dc182df2cd42f4c26c60a72a90e538)
+++ h2/src/main/org/h2/command/dml/Explain.java	(date 1536655889000)
@@ -72,7 +72,7 @@
         Database db = session.getDatabase();
         ExpressionColumn expr = new ExpressionColumn(db, column);
         Expression[] expressions = { expr };
-        result = new LocalResult(session, expressions, 1);
+        result = db.getResultFactory().create(session, expressions, 1);
         if (maxrows >= 0) {
             String plan;
             if (executeCommand) {
Index: h2/src/main/org/h2/table/FunctionTable.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- h2/src/main/org/h2/table/FunctionTable.java	(revision 5f682ecea5dc182df2cd42f4c26c60a72a90e538)
+++ h2/src/main/org/h2/table/FunctionTable.java	(date 1536655889000)
@@ -19,6 +19,7 @@
 import org.h2.index.IndexType;
 import org.h2.message.DbException;
 import org.h2.result.LocalResult;
+import org.h2.result.LocalResultFactory;
 import org.h2.result.ResultInterface;
 import org.h2.result.Row;
 import org.h2.schema.Schema;
@@ -194,7 +195,7 @@
             cachedResult.reset();
             return cachedResult;
         }
-        LocalResult result = LocalResult.read(session,  v.getResultSet(), 0);
+        LocalResult result = LocalResultFactory.read(session,  v.getResultSet(), 0);
         if (function.isDeterministic()) {
             cachedResult = result;
             cachedValue = v;
Index: h2/src/main/org/h2/result/LocalResultFactory.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- h2/src/main/org/h2/result/LocalResultFactory.java	(date 1536655889000)
+++ h2/src/main/org/h2/result/LocalResultFactory.java	(date 1536655889000)
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2004-2018 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.sql.ResultSet;
+import java.sql.SQLException;
+import org.h2.engine.Session;
+import org.h2.expression.Expression;
+import org.h2.message.DbException;
+import org.h2.value.DataType;
+import org.h2.value.Value;
+
+/**
+ * Creates local result.
+ */
+public abstract class LocalResultFactory {
+    /**
+     * Default implementation of local result factory.
+     */
+    public static final LocalResultFactory DEFAULT = new DefaultLocalResultFactory();
+
+    /**
+     * Create a local result object.
+     *
+     * @param session the session
+     * @param expressions the expression array
+     * @param visibleColumnCount the number of visible columns
+     * @return object to collect local result.
+     */
+    public abstract LocalResult create(Session session, Expression[] expressions, int visibleColumnCount);
+
+    /**
+     * Create a local result object.
+     * @return object to collect local result.
+     */
+    public abstract LocalResult create();
+
+    /**
+     * Construct a local result set by reading all data from a regular result
+     * set.
+     *
+     * @param session the session
+     * @param rs the result set
+     * @param maxrows the maximum number of rows to read (0 for no limit)
+     * @return the local result set
+     */
+    public static LocalResult read(Session session, ResultSet rs, int maxrows) {
+        Expression[] cols = Expression.getExpressionColumns(session, rs);
+        int columnCount = cols.length;
+        LocalResult result = session.getDatabase().getResultFactory().create(session, cols, columnCount);
+        try {
+            for (int i = 0; (maxrows == 0 || i < maxrows) && rs.next(); i++) {
+                Value[] list = new Value[columnCount];
+                for (int j = 0; j < columnCount; j++) {
+                    int type = result.getColumnType(j);
+                    list[j] = DataType.readValue(session, rs, j + 1, type);
+                }
+                result.addRow(list);
+            }
+        } catch (SQLException e) {
+            throw DbException.convert(e);
+        }
+        result.done();
+        return result;
+    }
+
+    /**
+     * Default implementation of local result factory.
+     */
+    private static final class DefaultLocalResultFactory extends LocalResultFactory {
+        /**
+         *
+         */
+        DefaultLocalResultFactory() {
+            //No-op.
+        }
+
+        @Override
+        public LocalResult create(Session session, Expression[] expressions, int visibleColumnCount) {
+            return new LocalResultImpl(session, expressions, visibleColumnCount);
+        }
+
+        @Override
+        public LocalResult create() {
+            return new LocalResultImpl();
+        }
+    }
+}
Index: h2/src/main/org/h2/result/LocalResultImpl.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- h2/src/main/org/h2/result/LocalResultImpl.java	(date 1536655889000)
+++ h2/src/main/org/h2/result/LocalResultImpl.java	(date 1536655889000)
@@ -0,0 +1,575 @@
+/*
+ * Copyright 2004-2018 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.engine.Database;
+import org.h2.engine.Session;
+import org.h2.engine.SessionInterface;
+import org.h2.expression.Expression;
+import org.h2.message.DbException;
+import org.h2.mvstore.db.MVTempResult;
+import org.h2.util.Utils;
+import org.h2.util.ValueHashMap;
+import org.h2.value.Value;
+import org.h2.value.ValueArray;
+
+/**
+ * A local result set contains all row data of a result set.
+ * This is the object generated by engine,
+ * and it is also used directly by the ResultSet class in the embedded mode.
+ * If the result does not fit in memory, it is written to a temporary file.
+ */
+public class LocalResultImpl implements LocalResult {
+
+    private int maxMemoryRows;
+    private Session session;
+    private int visibleColumnCount;
+    private Expression[] expressions;
+    private int rowId, rowCount;
+    private ArrayList<Value[]> rows;
+    private SortOrder sort;
+    private ValueHashMap<Value[]> distinctRows;
+    private Value[] currentRow;
+    private int offset;
+    private int limit = -1;
+    private boolean fetchPercent;
+    private boolean withTies;
+    private boolean limitsWereApplied;
+    private ResultExternal external;
+    private boolean distinct;
+    private int[] distinctIndexes;
+    private boolean closed;
+    private boolean containsLobs;
+
+    /**
+     * Construct a local result object.
+     */
+    public LocalResultImpl() {
+        // nothing to do
+    }
+
+    /**
+     * Construct a local result object.
+     *
+     * @param session the session
+     * @param expressions the expression array
+     * @param visibleColumnCount the number of visible columns
+     */
+    public LocalResultImpl(Session session, Expression[] expressions,
+            int visibleColumnCount) {
+        this.session = session;
+        if (session == null) {
+            this.maxMemoryRows = Integer.MAX_VALUE;
+        } else {
+            Database db = session.getDatabase();
+            if (db.isPersistent() && !db.isReadOnly()) {
+                this.maxMemoryRows = session.getDatabase().getMaxMemoryRows();
+            } else {
+                this.maxMemoryRows = Integer.MAX_VALUE;
+            }
+        }
+        rows = Utils.newSmallArrayList();
+        this.visibleColumnCount = visibleColumnCount;
+        rowId = -1;
+        this.expressions = expressions;
+    }
+
+    @Override
+    public boolean isLazy() {
+        return false;
+    }
+
+    public void setMaxMemoryRows(int maxValue) {
+        this.maxMemoryRows = maxValue;
+    }
+
+    /**
+     * Create a shallow copy of the result set. The data and a temporary table
+     * (if there is any) is not copied.
+     *
+     * @param targetSession the session of the copy
+     * @return the copy if possible, or null if copying is not possible
+     */
+    @Override
+    public LocalResultImpl createShallowCopy(SessionInterface targetSession) {
+        if (external == null && (rows == null || rows.size() < rowCount)) {
+            return null;
+        }
+        if (containsLobs) {
+            return null;
+        }
+        ResultExternal e2 = null;
+        if (external != null) {
+            e2 = external.createShallowCopy();
+            if (e2 == null) {
+                return null;
+            }
+        }
+        LocalResultImpl copy = new LocalResultImpl();
+        copy.maxMemoryRows = this.maxMemoryRows;
+        copy.session = (Session) targetSession;
+        copy.visibleColumnCount = this.visibleColumnCount;
+        copy.expressions = this.expressions;
+        copy.rowId = -1;
+        copy.rowCount = this.rowCount;
+        copy.rows = this.rows;
+        copy.sort = this.sort;
+        copy.distinctRows = this.distinctRows;
+        copy.distinct = distinct;
+        copy.distinctIndexes = distinctIndexes;
+        copy.currentRow = null;
+        copy.offset = 0;
+        copy.limit = -1;
+        copy.external = e2;
+        return copy;
+    }
+
+    /**
+     * Set the sort order.
+     *
+     * @param sort the sort order
+     */
+    public void setSortOrder(SortOrder sort) {
+        this.sort = sort;
+    }
+
+    /**
+     * Remove duplicate rows.
+     */
+    public void setDistinct() {
+        assert distinctIndexes == null;
+        distinct = true;
+        distinctRows = ValueHashMap.newInstance();
+    }
+
+    /**
+     * Remove rows with duplicates in columns with specified indexes.
+     *
+     * @param distinctIndexes distinct indexes
+     */
+    public void setDistinct(int[] distinctIndexes) {
+        assert !distinct;
+        this.distinctIndexes = distinctIndexes;
+        distinctRows = ValueHashMap.newInstance();
+    }
+
+    /**
+     * @return whether this result is a distinct result
+     */
+    private boolean isAnyDistinct() {
+        return distinct || distinctIndexes != null;
+    }
+
+    /**
+     * Remove the row from the result set if it exists.
+     *
+     * @param values the row
+     */
+    public void removeDistinct(Value[] values) {
+        if (!distinct) {
+            DbException.throwInternalError();
+        }
+        assert values.length == visibleColumnCount;
+        if (distinctRows != null) {
+            ValueArray array = ValueArray.get(values);
+            distinctRows.remove(array);
+            rowCount = distinctRows.size();
+        } else {
+            rowCount = external.removeRow(values);
+        }
+    }
+
+    /**
+     * Check if this result set contains the given row.
+     *
+     * @param values the row
+     * @return true if the row exists
+     */
+    @Override
+    public boolean containsDistinct(Value[] values) {
+        assert values.length == visibleColumnCount;
+        if (external != null) {
+            return external.contains(values);
+        }
+        if (distinctRows == null) {
+            distinctRows = ValueHashMap.newInstance();
+            for (Value[] row : rows) {
+                ValueArray array = getArrayOfDistinct(row);
+                distinctRows.put(array, array.getList());
+            }
+        }
+        ValueArray array = ValueArray.get(values);
+        return distinctRows.get(array) != null;
+    }
+
+    @Override
+    public void reset() {
+        rowId = -1;
+        currentRow = null;
+        if (external != null) {
+            external.reset();
+        }
+    }
+
+    @Override
+    public Value[] currentRow() {
+        return currentRow;
+    }
+
+    @Override
+    public boolean next() {
+        if (!closed && rowId < rowCount) {
+            rowId++;
+            if (rowId < rowCount) {
+                if (external != null) {
+                    currentRow = external.next();
+                } else {
+                    currentRow = rows.get(rowId);
+                }
+                return true;
+            }
+            currentRow = null;
+        }
+        return false;
+    }
+
+    @Override
+    public int getRowId() {
+        return rowId;
+    }
+
+    @Override
+    public boolean isAfterLast() {
+        return rowId >= rowCount;
+    }
+
+    private void cloneLobs(Value[] values) {
+        for (int i = 0; i < values.length; i++) {
+            Value v = values[i];
+            Value v2 = v.copyToResult();
+            if (v2 != v) {
+                containsLobs = true;
+                session.addTemporaryLob(v2);
+                values[i] = v2;
+            }
+        }
+    }
+
+    private ValueArray getArrayOfDistinct(Value[] values) {
+        if (distinctIndexes != null) {
+            int cnt = distinctIndexes.length;
+            Value[] newValues = new Value[cnt];
+            for (int i = 0; i < cnt; i++) {
+                newValues[i] = values[distinctIndexes[i]];
+            }
+            values = newValues;
+        } else if (values.length > visibleColumnCount) {
+            values = Arrays.copyOf(values, visibleColumnCount);
+        }
+        return ValueArray.get(values);
+    }
+
+    private void createExternalResult() {
+        Database database = session.getDatabase();
+        external = database.isMVStore()
+                || /* not supported by ResultTempTable */ distinct && expressions.length != visibleColumnCount
+                || distinctIndexes != null
+                ? MVTempResult.of(database, expressions, distinct, distinctIndexes, visibleColumnCount, sort)
+                        : new ResultTempTable(session, expressions, distinct, sort);
+    }
+
+    /**
+     * Add a row to this object.
+     *
+     * @param values the row to add
+     */
+    @Override
+    public void addRow(Value[] values) {
+        cloneLobs(values);
+        if (isAnyDistinct()) {
+            if (distinctRows != null) {
+                ValueArray array = getArrayOfDistinct(values);
+                distinctRows.putIfAbsent(array, values);
+                rowCount = distinctRows.size();
+                if (rowCount > maxMemoryRows) {
+                    createExternalResult();
+                    rowCount = external.addRows(distinctRows.values());
+                    distinctRows = null;
+                }
+            } else {
+                rowCount = external.addRow(values);
+            }
+        } else {
+            rows.add(values);
+            rowCount++;
+            if (rows.size() > maxMemoryRows) {
+                addRowsToDisk();
+            }
+        }
+    }
+
+    private void addRowsToDisk() {
+        if (external == null) {
+            createExternalResult();
+        }
+        rowCount = external.addRows(rows);
+        rows.clear();
+    }
+
+    @Override
+    public int getVisibleColumnCount() {
+        return visibleColumnCount;
+    }
+
+    /**
+     * This method is called after all rows have been added.
+     */
+    public void done() {
+        if (external != null) {
+            addRowsToDisk();
+        } else {
+            if (isAnyDistinct()) {
+                rows = distinctRows.values();
+            }
+            if (sort != null && limit != 0) {
+                boolean withLimit = limit > 0 && !withTies;
+                if (offset > 0 || withLimit) {
+                    sort.sort(rows, offset, withLimit ? limit : rows.size());
+                } else {
+                    sort.sort(rows);
+                }
+            }
+        }
+        applyOffsetAndLimit();
+        reset();
+    }
+
+    private void applyOffsetAndLimit() {
+        if (limitsWereApplied) {
+            return;
+        }
+        int offset = Math.max(this.offset, 0);
+        int limit = this.limit;
+        if (offset == 0 && limit < 0 && !fetchPercent || rowCount == 0) {
+            return;
+        }
+        if (fetchPercent) {
+            if (limit < 0 || limit > 100) {
+                throw DbException.getInvalidValueException("FETCH PERCENT", limit);
+            }
+            // Oracle rounds percent up, do the same for now
+            limit = (int) (((long) limit * rowCount + 99) / 100);
+        }
+        boolean clearAll = offset >= rowCount || limit == 0;
+        if (!clearAll) {
+            int remaining = rowCount - offset;
+            limit = limit < 0 ? remaining : Math.min(remaining, limit);
+            if (offset == 0 && remaining <= limit) {
+                return;
+            }
+        } else {
+            limit = 0;
+        }
+        distinctRows = null;
+        rowCount = limit;
+        if (external == null) {
+            if (clearAll) {
+                rows.clear();
+                return;
+            }
+            int to = offset + limit;
+            if (withTies && sort != null) {
+                Value[] expected = rows.get(to - 1);
+                while (to < rows.size() && sort.compare(expected, rows.get(to)) == 0) {
+                    to++;
+                    rowCount++;
+                }
+            }
+            if (offset != 0 || to != rows.size()) {
+                // avoid copying the whole array for each row
+                rows = new ArrayList<>(rows.subList(offset, to));
+            }
+        } else {
+            if (clearAll) {
+                external.close();
+                external = null;
+                return;
+            }
+            trimExternal(offset, limit);
+        }
+    }
+
+    private void trimExternal(int offset, int limit) {
+        ResultExternal temp = external;
+        external = null;
+        temp.reset();
+        while (--offset >= 0) {
+            temp.next();
+        }
+        Value[] row = null;
+        while (--limit >= 0) {
+            row = temp.next();
+            rows.add(row);
+            if (rows.size() > maxMemoryRows) {
+                addRowsToDisk();
+            }
+        }
+        if (withTies && sort != null && row != null) {
+            Value[] expected = row;
+            while ((row = temp.next()) != null && sort.compare(expected, row) == 0) {
+                rows.add(row);
+                rowCount++;
+                if (rows.size() > maxMemoryRows) {
+                    addRowsToDisk();
+                }
+            }
+        }
+        if (external != null) {
+            addRowsToDisk();
+        }
+        temp.close();
+    }
+
+    @Override
+    public int getRowCount() {
+        return rowCount;
+    }
+
+    @Override
+    public void limitsWereApplied() {
+        this.limitsWereApplied = true;
+    }
+
+    @Override
+    public boolean hasNext() {
+        return !closed && rowId < rowCount - 1;
+    }
+
+    /**
+     * Set the number of rows that this result will return at the maximum.
+     *
+     * @param limit the limit (-1 means no limit, 0 means no rows)
+     */
+    public void setLimit(int limit) {
+        this.limit = limit;
+    }
+
+    /**
+     * @param fetchPercent whether limit expression specifies percentage of rows
+     */
+    public void setFetchPercent(boolean fetchPercent) {
+        this.fetchPercent = fetchPercent;
+    }
+
+    /**
+     * @param withTies whether tied rows should be included in result too
+     */
+    public void setWithTies(boolean withTies) {
+        this.withTies = withTies;
+    }
+
+    @Override
+    public boolean needToClose() {
+        return external != null;
+    }
+
+    @Override
+    public void close() {
+        if (external != null) {
+            external.close();
+            external = null;
+            closed = true;
+        }
+    }
+
+    @Override
+    public String getAlias(int i) {
+        return expressions[i].getAlias();
+    }
+
+    @Override
+    public String getTableName(int i) {
+        return expressions[i].getTableName();
+    }
+
+    @Override
+    public String getSchemaName(int i) {
+        return expressions[i].getSchemaName();
+    }
+
+    @Override
+    public int getDisplaySize(int i) {
+        return expressions[i].getDisplaySize();
+    }
+
+    @Override
+    public String getColumnName(int i) {
+        return expressions[i].getColumnName();
+    }
+
+    @Override
+    public int getColumnType(int i) {
+        return expressions[i].getType();
+    }
+
+    @Override
+    public long getColumnPrecision(int i) {
+        return expressions[i].getPrecision();
+    }
+
+    @Override
+    public int getNullable(int i) {
+        return expressions[i].getNullable();
+    }
+
+    @Override
+    public boolean isAutoIncrement(int i) {
+        return expressions[i].isAutoIncrement();
+    }
+
+    @Override
+    public int getColumnScale(int i) {
+        return expressions[i].getScale();
+    }
+
+    /**
+     * Set the offset of the first row to return.
+     *
+     * @param offset the offset
+     */
+    public void setOffset(int offset) {
+        this.offset = offset;
+    }
+
+    @Override
+    public String toString() {
+        return super.toString() + " columns: " + visibleColumnCount +
+                " rows: " + rowCount + " pos: " + rowId;
+    }
+
+    /**
+     * Check if this result set is closed.
+     *
+     * @return true if it is
+     */
+    @Override
+    public boolean isClosed() {
+        return closed;
+    }
+
+    @Override
+    public int getFetchSize() {
+        return 0;
+    }
+
+    @Override
+    public void setFetchSize(int fetchSize) {
+        // ignore
+    }
+
+}
Index: h2/src/test/org/h2/test/TestAll.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- h2/src/test/org/h2/test/TestAll.java	(revision 5f682ecea5dc182df2cd42f4c26c60a72a90e538)
+++ h2/src/test/org/h2/test/TestAll.java	(date 1536655889000)
@@ -193,6 +193,7 @@
 import org.h2.test.unit.TestIntPerfectHash;
 import org.h2.test.unit.TestInterval;
 import org.h2.test.unit.TestJmx;
+import org.h2.test.unit.TestLocalResultFactory;
 import org.h2.test.unit.TestLocale;
 import org.h2.test.unit.TestMathUtils;
 import org.h2.test.unit.TestMemoryUnmapper;
@@ -980,6 +981,7 @@
         addTest(new TestTraceSystem());
         addTest(new TestUtils());
         addTest(new TestValueHashMap());
+        addTest(new TestLocalResultFactory());
 
         runAddedTests();
 
Index: h2/src/main/org/h2/command/dml/SelectUnion.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- h2/src/main/org/h2/command/dml/SelectUnion.java	(revision 5f682ecea5dc182df2cd42f4c26c60a72a90e538)
+++ h2/src/main/org/h2/command/dml/SelectUnion.java	(date 1536655889000)
@@ -130,14 +130,14 @@
     @Override
     public ResultInterface queryMeta() {
         int columnCount = left.getColumnCount();
-        LocalResult result = new LocalResult(session, expressionArray, columnCount);
+        LocalResult result = session.getDatabase().getResultFactory().create(session, expressionArray, columnCount);
         result.done();
         return result;
     }
 
     public LocalResult getEmptyResult() {
         int columnCount = left.getColumnCount();
-        return new LocalResult(session, expressionArray, columnCount);
+        return session.getDatabase().getResultFactory().create(session, expressionArray, columnCount);
     }
 
     @Override
@@ -189,7 +189,7 @@
                 return lazyResult;
             }
         }
-        LocalResult result = new LocalResult(session, expressionArray, columnCount);
+        LocalResult result = session.getDatabase().getResultFactory().create(session, expressionArray, columnCount);
         if (sort != null) {
             result.setSortOrder(sort);
         }
@@ -239,7 +239,7 @@
             break;
         }
         case INTERSECT: {
-            LocalResult temp = new LocalResult(session, expressionArray, columnCount);
+            LocalResult temp = session.getDatabase().getResultFactory().create(session, expressionArray, columnCount);
             temp.setDistinct();
             while (l.next()) {
                 temp.addRow(convert(l.currentRow(), columnCount));
Index: h2/src/main/org/h2/command/dml/Select.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- h2/src/main/org/h2/command/dml/Select.java	(revision 5f682ecea5dc182df2cd42f4c26c60a72a90e538)
+++ h2/src/main/org/h2/command/dml/Select.java	(date 1536655889000)
@@ -687,7 +687,7 @@
 
     @Override
     public ResultInterface queryMeta() {
-        LocalResult result = new LocalResult(session, expressionArray,
+        LocalResult result = session.getDatabase().getResultFactory().create(session, expressionArray,
                 visibleColumnCount);
         result.done();
         return result;
@@ -858,12 +858,13 @@
     }
 
     private LocalResult createLocalResult(LocalResult old) {
-        return old != null ? old : new LocalResult(session, expressionArray,
+        return old != null ? old : session.getDatabase().getResultFactory().create(session, expressionArray,
                 visibleColumnCount);
     }
 
     private LocalResult convertToDistinct(ResultInterface result) {
-        LocalResult distinctResult = new LocalResult(session, expressionArray, visibleColumnCount);
+        LocalResult distinctResult = session.getDatabase().getResultFactory().create(session,
+            expressionArray, visibleColumnCount);
         distinctResult.setDistinct();
         result.reset();
         while (result.next()) {
Index: h2/src/main/org/h2/expression/TableFunction.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- h2/src/main/org/h2/expression/TableFunction.java	(revision 5f682ecea5dc182df2cd42f4c26c60a72a90e538)
+++ h2/src/main/org/h2/expression/TableFunction.java	(date 1536655889000)
@@ -86,7 +86,7 @@
             ExpressionColumn col = new ExpressionColumn(db, c);
             header[i] = col;
         }
-        LocalResult result = new LocalResult(session, header, len);
+        LocalResult result = db.getResultFactory().create(session, header, len);
         if (distinctRows) {
             result.setDistinct();
         }
Index: h2/src/main/org/h2/command/dml/Set.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- h2/src/main/org/h2/command/dml/Set.java	(revision 5f682ecea5dc182df2cd42f4c26c60a72a90e538)
+++ h2/src/main/org/h2/command/dml/Set.java	(date 1536655889000)
@@ -19,6 +19,7 @@
 import org.h2.expression.ValueExpression;
 import org.h2.message.DbException;
 import org.h2.message.Trace;
+import org.h2.result.LocalResultFactory;
 import org.h2.result.ResultInterface;
 import org.h2.result.RowFactory;
 import org.h2.schema.Schema;
@@ -556,6 +557,19 @@
                     throw DbException.convert(e);
                 }
             }
+            break;
+        }
+        case SetTypes.LOCAL_RESULT_FACTORY: {
+            session.getUser().checkAdmin();
+            String localResultFactoryName = expression.getColumnName();
+            Class<LocalResultFactory> localResultFactoryClass = JdbcUtils.loadUserClass(localResultFactoryName);
+            LocalResultFactory localResultFactory;
+            try {
+                localResultFactory = localResultFactoryClass.getDeclaredConstructor().newInstance();
+                database.setResultFactory(localResultFactory);
+            } catch (Exception e) {
+                throw DbException.convert(e);
+            }
             break;
         }
         default:
Index: h2/src/main/org/h2/command/dml/SetTypes.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- h2/src/main/org/h2/command/dml/SetTypes.java	(revision 5f682ecea5dc182df2cd42f4c26c60a72a90e538)
+++ h2/src/main/org/h2/command/dml/SetTypes.java	(date 1536655889000)
@@ -252,7 +252,12 @@
      */
     public static final int AUTHENTICATOR = 48;
 
-    private static final int COUNT = AUTHENTICATOR + 1;
+    /**
+     * The type of a SET LOCAL_RESULT_FACTORY statement.
+     */
+    public static final int LOCAL_RESULT_FACTORY = 49;
+
+    private static final int COUNT = LOCAL_RESULT_FACTORY + 1;
 
     private static final ArrayList<String> TYPES;
 
@@ -311,6 +316,7 @@
         list.add(BUILTIN_ALIAS_OVERRIDE, "BUILTIN_ALIAS_OVERRIDE");
         list.add(COLUMN_NAME_RULES, "COLUMN_NAME_RULES");
         list.add(AUTHENTICATOR, "AUTHENTICATOR");
+        list.add(LOCAL_RESULT_FACTORY, "LOCAL_RESULT_FACTORY");
         TYPES = list;
     }
 
Index: h2/src/test/org/h2/test/unit/TestLocalResultFactory.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- h2/src/test/org/h2/test/unit/TestLocalResultFactory.java	(date 1536655889000)
+++ h2/src/test/org/h2/test/unit/TestLocalResultFactory.java	(date 1536655889000)
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2004-2018 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.test.unit;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.Statement;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.h2.engine.Session;
+import org.h2.expression.Expression;
+import org.h2.result.LocalResult;
+import org.h2.result.LocalResultFactory;
+import org.h2.test.TestBase;
+
+/**
+ * Test {@link LocalResultFactory} setting.
+ */
+public class TestLocalResultFactory extends TestBase {
+
+    /**
+     * Run just this test.
+     *
+     * @param a ignored
+     */
+    public static void main(String[] a) throws Exception {
+        TestBase.createCaller().init().test();
+    }
+
+    @Override
+    public void test() throws Exception {
+        try (Connection conn = DriverManager.getConnection("jdbc:h2:mem:localResultFactory;LOCAL_RESULT_FACTORY=\""
+            + MyTestLocalResultFactory.class.getName() + '"')) {
+            Statement stat = conn.createStatement();
+
+            stat.execute("create table t1(id int, name varchar)");
+            for (int i = 0; i < 1000; i++) {
+                stat.execute("insert into t1 values(" + i + ", 'name')");
+            }
+            assertEquals(MyTestLocalResultFactory.COUNTER.get(), 0);
+
+            stat.execute("select * from t1");
+            assertEquals(MyTestLocalResultFactory.COUNTER.get(), 1);
+        }
+    }
+
+    /**
+     * Test local result factory.
+     */
+    public static class MyTestLocalResultFactory extends LocalResultFactory {
+        static final AtomicInteger COUNTER = new AtomicInteger();
+
+        @Override public LocalResult create(Session session, Expression[] expressions, int visibleColumnCount) {
+            COUNTER.incrementAndGet();
+            return LocalResultFactory.DEFAULT.create(session, expressions, visibleColumnCount);
+        }
+
+        @Override public LocalResult create() {
+            COUNTER.incrementAndGet();
+            return LocalResultFactory.DEFAULT.create();
+        }
+    }
+}

Reply via email to