From 610f4d3c6b60c840069da64594dfcbe00cabc0d0 Mon Sep 17 00:00:00 2001 From: lukaseder Date: Fri, 28 Aug 2015 14:18:44 +0200 Subject: [PATCH] [#2364] Multi-Result queries may mix ResultSets with update counts. --- jOOQ/src/main/java/org/jooq/ResultOrRows.java | 66 ++++++ jOOQ/src/main/java/org/jooq/Results.java | 30 ++- .../org/jooq/impl/AbstractResultQuery.java | 2 +- .../java/org/jooq/impl/AbstractRoutine.java | 2 +- .../main/java/org/jooq/impl/ResultsImpl.java | 203 +++++++++--------- jOOQ/src/main/java/org/jooq/impl/Utils.java | 11 +- 6 files changed, 206 insertions(+), 108 deletions(-) create mode 100644 jOOQ/src/main/java/org/jooq/ResultOrRows.java diff --git a/jOOQ/src/main/java/org/jooq/ResultOrRows.java b/jOOQ/src/main/java/org/jooq/ResultOrRows.java new file mode 100644 index 0000000000..e0d5bb86bc --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/ResultOrRows.java @@ -0,0 +1,66 @@ +/** + * Copyright (c) 2009-2015, Data Geekery GmbH (http://www.datageekery.com) + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Other licenses: + * ----------------------------------------------------------------------------- + * Commercial licenses for this work are available. These replace the above + * ASL 2.0 and offer limited warranties, support, maintenance, and commercial + * database integrations. + * + * For more information, please visit: http://www.jooq.org/licenses + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ +package org.jooq; + +import java.sql.Statement; + +/** + * A type that contains either a {@link Result} or an update count. + * + * @author Lukas Eder + */ +public interface ResultOrRows { + + /** + * The result or null if there was no result. + * + * @see Statement#getResultSet() + */ + Result result(); + + /** + * The update count if applicable, or the number of rows in + * {@link #result()}. + * + * @see Statement#getUpdateCount() + */ + int rows(); +} diff --git a/jOOQ/src/main/java/org/jooq/Results.java b/jOOQ/src/main/java/org/jooq/Results.java index b730f6b2f8..ad1b589133 100644 --- a/jOOQ/src/main/java/org/jooq/Results.java +++ b/jOOQ/src/main/java/org/jooq/Results.java @@ -46,13 +46,41 @@ import java.util.List; * A list of {@link Result} and update counts that can be returned by * {@link ResultQuery#fetchMany()} calls and other calls that produce multiple * cursors and update counts. + *

+ * For backwards-compatibility (e.g. with {@link ResultQuery#fetchMany()}), this + * type extends {@link List} containing only the {@link Result}, not the rows / + * update counts of interleaved updates. In order to get both, call + * {@link #resultsOrRows()}. * * @author Lukas Eder */ public interface Results extends List>, Attachable { // ------------------------------------------------------------------------ - // Specialisations of Attachable methods + // XXX: Additional, Results-specific methods + // ------------------------------------------------------------------------ + + /** + * All the results or update counts in their order as fetched via JDBC. + *

+ * While {@link #iterator()} and all the other methods inherited from the + * {@link List} API return the {@link Result} objects only, this method also + * includes update counts that may have occurred between two results. + *

+ * It can be safely assumed that: + *

+     * result.resultsOrRows()
+     *       .stream()
+     *       .filter(r -> r.result() != null)
+     *       .map(r -> r.result())
+     *       .collect(Collectors.toList())
+     *       .equals(result);
+     * 
+ */ + List resultsOrRows(); + + // ------------------------------------------------------------------------ + // XXX: Specialisations of Attachable methods // ------------------------------------------------------------------------ /** diff --git a/jOOQ/src/main/java/org/jooq/impl/AbstractResultQuery.java b/jOOQ/src/main/java/org/jooq/impl/AbstractResultQuery.java index 6b997c8628..a0470a76b3 100644 --- a/jOOQ/src/main/java/org/jooq/impl/AbstractResultQuery.java +++ b/jOOQ/src/main/java/org/jooq/impl/AbstractResultQuery.java @@ -104,7 +104,7 @@ abstract class AbstractResultQuery extends AbstractQuery imple private transient boolean many; private transient Cursor cursor; private Result result; - private Results results; + private ResultsImpl results; // Some temp variables for String interning private final Intern intern = new Intern(); diff --git a/jOOQ/src/main/java/org/jooq/impl/AbstractRoutine.java b/jOOQ/src/main/java/org/jooq/impl/AbstractRoutine.java index fa312fdbe8..c4414048b0 100644 --- a/jOOQ/src/main/java/org/jooq/impl/AbstractRoutine.java +++ b/jOOQ/src/main/java/org/jooq/impl/AbstractRoutine.java @@ -119,7 +119,7 @@ public abstract class AbstractRoutine extends AbstractQueryPart implements Ro private final List> outParameters; private final DataType type; private Parameter returnParameter; - private Results results; + private ResultsImpl results; private boolean overloaded; private boolean hasDefaultedParameters; diff --git a/jOOQ/src/main/java/org/jooq/impl/ResultsImpl.java b/jOOQ/src/main/java/org/jooq/impl/ResultsImpl.java index 70f834afd1..394ef83456 100644 --- a/jOOQ/src/main/java/org/jooq/impl/ResultsImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/ResultsImpl.java @@ -40,34 +40,42 @@ */ package org.jooq.impl; +import java.util.AbstractList; import java.util.ArrayList; -import java.util.Collection; -import java.util.Iterator; import java.util.List; -import java.util.ListIterator; import org.jooq.AttachableInternal; import org.jooq.Configuration; import org.jooq.Record; import org.jooq.Result; +import org.jooq.ResultOrRows; import org.jooq.Results; /** * @author Lukas Eder */ -class ResultsImpl implements Results, AttachableInternal { +class ResultsImpl extends AbstractList> implements Results, AttachableInternal { /** * Generated UID */ - private static final long serialVersionUID = 1744826140354980500L; + private static final long serialVersionUID = 1744826140354980500L; - private Configuration configuration; - private final List> results; + private Configuration configuration; + private final List results; ResultsImpl(Configuration configuration) { this.configuration = configuration; - this.results = new ArrayList>(); + this.results = new ArrayList(); + } + + // ------------------------------------------------------------------------ + // XXX: Additional, Results-specific methods + // ------------------------------------------------------------------------ + + @Override + public final List resultsOrRows() { + return results; } // ------------------------------------------------------------------------- @@ -78,7 +86,7 @@ class ResultsImpl implements Results, AttachableInternal { public final void attach(Configuration c) { this.configuration = c; - for (Result result : results) + for (Result result : this) if (result != null) result.attach(c); } @@ -100,11 +108,13 @@ class ResultsImpl implements Results, AttachableInternal { @Override public String toString() { StringBuilder sb = new StringBuilder(); - String separator = ""; - for (Result result : this) { - sb.append(separator) - .append(result); + + for (ResultOrRows result : results) { + if (result.result() == null) + sb.append(separator).append("Update count: ").append(result.rows()); + else + sb.append(separator).append("Result set:\n").append(result.result()); separator = "\n"; } @@ -137,116 +147,103 @@ class ResultsImpl implements Results, AttachableInternal { @Override public final int size() { - return results.size(); - } - - @Override - public final boolean isEmpty() { - return results.isEmpty(); - } - - @Override - public final boolean contains(Object o) { - return results.contains(o); - } - - @Override - public final Iterator> iterator() { - return results.iterator(); - } - - @Override - public final Object[] toArray() { - return results.toArray(); - } - - @Override - public final T[] toArray(T[] a) { - return results.toArray(a); - } - - @Override - public final boolean add(Result e) { - return results.add(e); - } - - @Override - public final boolean remove(Object o) { - return results.remove(o); - } - - @Override - public final boolean containsAll(Collection c) { - return results.containsAll(c); - } - - @Override - public final boolean addAll(Collection> c) { - return results.addAll(c); - } - - @Override - public final boolean addAll(int index, Collection> c) { - return results.addAll(index, c); - } - - @Override - public final boolean removeAll(Collection c) { - return results.removeAll(c); - } - - @Override - public final boolean retainAll(Collection c) { - return results.retainAll(c); - } - - @Override - public final void clear() { - results.clear(); + return list().size(); } @Override public final Result get(int index) { - return results.get(index); + return list().get(index); } @Override - public final Result set(int index, Result element) { - return results.set(index, element); + public Result set(int index, Result element) { + return results.set(translatedIndex(index), new ResultOrRowsImpl(element)).result(); } @Override - public final void add(int index, Result element) { - results.add(index, element); + public void add(int index, Result element) { + results.add(translatedIndex(index), new ResultOrRowsImpl(element)); } @Override - public final Result remove(int index) { - return results.remove(index); + public Result remove(int index) { + return results.remove(translatedIndex(index)).result(); } - @Override - public final int indexOf(Object o) { - return results.indexOf(o); + private final List> list() { + List> list = new ArrayList>(); + + for (ResultOrRows result : results) + if (result.result() != null) + list.add(result.result()); + + return list; } - @Override - public final int lastIndexOf(Object o) { - return results.lastIndexOf(o); + private final int translatedIndex(int index) { + int translated = 0; + + for (int i = 0; i < index; i++) + while (results.get(translated++).result() == null); + + return translated; } - @Override - public final ListIterator> listIterator() { - return results.listIterator(); - } + static final class ResultOrRowsImpl implements ResultOrRows { - @Override - public final ListIterator> listIterator(int index) { - return results.listIterator(index); - } + private final Result result; + private final int rows; - @Override - public final List> subList(int fromIndex, int toIndex) { - return results.subList(fromIndex, toIndex); + ResultOrRowsImpl(Result result) { + this(result, result != null ? result.size() : 0); + } + + ResultOrRowsImpl(int rows) { + this(null, rows); + } + + private ResultOrRowsImpl(Result result, int rows) { + this.result = result; + this.rows = rows; + } + + @Override + public final Result result() { + return result; + } + + @Override + public final int rows() { + return rows; + } + + @Override + public int hashCode() { + final int prime = 31; + int r = 1; + r = prime * r + ((this.result == null) ? 0 : this.result.hashCode()); + r = prime * r + rows; + return r; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + ResultOrRowsImpl other = (ResultOrRowsImpl) obj; + if (result == null) { + if (other.result != null) + return false; + } + else if (!result.equals(other.result)) + return false; + if (rows != other.rows) + return false; + return true; + } } } diff --git a/jOOQ/src/main/java/org/jooq/impl/Utils.java b/jOOQ/src/main/java/org/jooq/impl/Utils.java index 285c5d0dca..0b8203e468 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Utils.java +++ b/jOOQ/src/main/java/org/jooq/impl/Utils.java @@ -2523,6 +2523,7 @@ final class Utils { static void consumeResultSets(ExecuteContext ctx, ExecuteListener listener, Results results, Intern intern) throws SQLException { boolean anyResults = false; int i = 0; + int rows = (ctx.resultSet() == null) ? ctx.statement().getUpdateCount() : 0; for (i = 0; i < maxConsumedResults; i++) { if (ctx.resultSet() != null) { @@ -2530,12 +2531,18 @@ final class Utils { Field[] fields = new MetaDataFieldProvider(ctx.configuration(), ctx.resultSet().getMetaData()).getFields(); Cursor c = new CursorImpl(ctx, listener, fields, intern != null ? intern.internIndexes(fields) : null, true, false); - results.add(c.fetch()); + results.resultsOrRows().add(new ResultsImpl.ResultOrRowsImpl(c.fetch())); + } + else { + if (rows != -1) + results.resultsOrRows().add(new ResultsImpl.ResultOrRowsImpl(rows)); + else + break; } if (ctx.statement().getMoreResults()) ctx.resultSet(ctx.statement().getResultSet()); - else if (ctx.statement().getUpdateCount() != -1) + else if ((rows = ctx.statement().getUpdateCount()) != -1) ctx.resultSet(null); else break;