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 [email protected].
To post to this group, send email to [email protected].
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