You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jena.apache.org by an...@apache.org on 2025/04/11 07:36:57 UTC

(jena) 02/06: Update result set testing for triple terms

This is an automated email from the ASF dual-hosted git repository.

andy pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/jena.git

commit a3c030c4d8def19017e9da1732c9562285f660ca
Author: Andy Seaborne <an...@apache.org>
AuthorDate: Sun Feb 2 18:19:59 2025 +0000

    Update result set testing for triple terms
---
 .../org/apache/jena/sparql/exec/RowSetMem.java     |   3 +-
 .../org/apache/jena/sparql/expr/NodeValue.java     |  28 +-
 .../jena/sparql/expr/nodevalue/NodeFunctions.java  |  38 ++-
 .../jena/sparql/resultset/ResultSetCompare.java    | 304 ++++--------------
 .../jena/sparql/resultset/ResultSetCompare1.java   | 346 ++++++++++++++++++++
 .../{ResultSetCompare.java => ResultsCompare.java} | 302 +++++++-----------
 .../org/apache/jena/sparql/syntax/ElementData.java |   6 +-
 .../main/java/org/apache/jena/sparql/util/Iso.java | 214 ++++++-------
 .../org/apache/jena/sparql/util/IsoMatcher.java    |  24 +-
 .../jena/sparql/util/NodeIsomorphismMap.java       |  33 +-
 .../apache/jena/sparql/util/NodeToLabelMap.java    |  92 ++----
 .../org/apache/jena/sparql/util/iso/BNodeIso.java  |  87 +++++
 .../apache/jena/sparql/util/iso/IsoAlgRows.java    | 272 ++++++++++++++++
 .../util/{IsoAlg.java => iso/IsoAlgTuple.java}     | 212 ++++++-------
 .../org/apache/jena/sparql/util/iso/IsoLib.java    | 125 ++++++++
 .../apache/jena/sparql/util/iso/IsoMapping.java    |  67 ++++
 .../jena/arq/junit/sparql/tests/QueryEvalTest.java |  12 +-
 .../apache/jena/atlas/data/DataBagExaminer.java    |  17 +-
 .../jena/atlas/data/TestDistinctDataBag.java       |  88 ++----
 .../jena/atlas/data/TestDistinctDataNet.java       | 133 +++-----
 .../apache/jena/atlas/data/TestSortedDataBag.java  | 101 +++---
 .../jena/atlas/data/TestThresholdPolicyCount.java  |  19 +-
 .../jena/riot/protobuf/TestProtobufResultSet.java  |   8 +-
 .../jena/riot/resultset/TestResultSetIO.java       |   6 +-
 .../jena/riot/thrift/TestThriftResultSet.java      |   8 +-
 .../apache/jena/sparql/AbstractTestAdditional.java |   4 +-
 .../test/java/org/apache/jena/sparql/TC_ARQ.java   |   4 +-
 .../algebra/optimize/TestSemanticEquivalence.java  |   6 +-
 .../sparql/engine/binding/TestBindingStreams.java  |  96 +++---
 .../jena/sparql/engine/join/AbstractTestJoin.java  |   4 +-
 .../jena/sparql/engine/join/TestTableJoin.java     |   4 +-
 .../org/apache/jena/sparql/path/TestPathQuery.java |   9 +-
 .../jena/sparql/resultset/TestResultSet.java       |  92 +++---
 .../sparql/resultset/TestResultSetFormat1.java     |  85 +++--
 .../java/org/apache/jena/sparql/util/TS_Util.java  |   1 -
 .../sparql/util/{TS_Util.java => iso/TS_Iso.java}  |  14 +-
 .../org/apache/jena/sparql/util/iso/TestIso.java   |  71 +++++
 .../jena/sparql/util/iso/TestIsoAlgRows.java       | 351 +++++++++++++++++++++
 .../jena/sparql/util/{ => iso}/TestIsoMatcher.java |  24 +-
 .../java/org/apache/jena/atlas/lib/ListUtils.java  |   6 +-
 .../src/main/java/org/apache/jena/graph/Node.java  |   7 +-
 .../impl/TestServiceEnhancerCachedVsUncached.java  |  22 +-
 .../org/apache/jena/fuseki/main/TestQuery.java     |   7 +-
 .../test/service/TestCustomServiceExecutor.java    |  17 +-
 .../org/apache/jena/tdb1/solver/TestSolverTDB.java |  12 +-
 .../org/apache/jena/tdb2/solver/TestSolverTDB.java |  14 +-
 46 files changed, 2168 insertions(+), 1227 deletions(-)

diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/exec/RowSetMem.java b/jena-arq/src/main/java/org/apache/jena/sparql/exec/RowSetMem.java
index 81fdb73b2d..fbf2e0bbcd 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/exec/RowSetMem.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/exec/RowSetMem.java
@@ -49,8 +49,8 @@ public class RowSetMem implements RowSetRewindable
      * @param other     The other RowSetMem object
      */
     private RowSetMem(RowSetMem other) {
+        // Share vars and rows but not the iterator.
         vars = other.vars;
-        // Share results (not the iterator).
         rows = other.rows;
         reset();
     }
@@ -61,7 +61,6 @@ public class RowSetMem implements RowSetRewindable
      * necessary internal datastructures are shared. This operation destroys
      * (uses up) a RowSet object that is not an in-memory one.
      */
-
     private RowSetMem(RowSet other) {
         if ( other instanceof RowSetMem rsm ) {
             // See also the RowSetMem(RowSetMem) constructor.
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/expr/NodeValue.java b/jena-arq/src/main/java/org/apache/jena/sparql/expr/NodeValue.java
index 63be42bf31..69c4315fa9 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/expr/NodeValue.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/expr/NodeValue.java
@@ -353,30 +353,28 @@ public abstract class NodeValue extends ExprNode
         return makeNode(lexicalForm, XSDdate);
     }
 
-   // ----------------------------------------------------------------
-   // ---- Expr interface
+    // ----------------------------------------------------------------
+    // ---- Expr interface
 
-   @Override
-   public NodeValue eval(Binding binding, FunctionEnv env) {
-       return this;
-   }
+    @Override
+    public NodeValue eval(Binding binding, FunctionEnv env) {
+        return this;
+    }
 
-   // NodeValues are immutable so no need to duplicate.
-   @Override
-   public Expr copySubstitute(Binding binding) {
-       return this;
-   }
+    // NodeValues are immutable so no need to duplicate.
+    @Override
+    public Expr copySubstitute(Binding binding) {
+        return this;
+    }
 
     @Override
-    public Expr applyNodeTransform(NodeTransform transform)
-    {
+    public Expr applyNodeTransform(NodeTransform transform) {
         Node n = asNode();
         n = transform.apply(n);
         return makeNode(n);
     }
 
-    public Node evalNode(Binding binding, ExecutionContext execCxt)
-    {
+    public Node evalNode(Binding binding, ExecutionContext execCxt) {
         return asNode();
     }
 
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/expr/nodevalue/NodeFunctions.java b/jena-arq/src/main/java/org/apache/jena/sparql/expr/nodevalue/NodeFunctions.java
index 52144eccf1..0d04b2021f 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/expr/nodevalue/NodeFunctions.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/expr/nodevalue/NodeFunctions.java
@@ -38,7 +38,6 @@ import org.apache.jena.irix.IRIException;
 import org.apache.jena.irix.IRIs;
 import org.apache.jena.irix.IRIx;
 import org.apache.jena.query.ARQ;
-import org.apache.jena.rdf.model.impl.Util;
 import org.apache.jena.riot.out.NodeFmtLib;
 import org.apache.jena.riot.system.RiotLib;
 import org.apache.jena.sparql.expr.ExprEvalException;
@@ -127,22 +126,31 @@ public class NodeFunctions {
         return NodeValue.booleanReturn(sameTerm(nv1.asNode(), nv2.asNode()));
     }
 
+    // Jena - up to Jena 4.
+    // Language tags were kept in the case form they were given as.
+    // Jena5 - language tags are canonicalised.
+
     /** sameTerm(x,y) */
     public static boolean sameTerm(Node node1, Node node2) {
-        if ( node1.equals(node2) )
-            return true;
-        if ( Util.isLangString(node1) && Util.isLangString(node2) ) {
-            String lex1 = node1.getLiteralLexicalForm();
-            String lex2 = node2.getLiteralLexicalForm();
-            if ( !lex1.equals(lex2) )
-                return false;
-            return node1.getLiteralLanguage().equalsIgnoreCase(node2.getLiteralLanguage());
-        }
-        if ( node1.isTripleTerm() && node2.isTripleTerm() ) {
-            return sameTriples(node1.getTriple(), node2.getTriple());
-        }
-        return false;
-    }
+        return node1.sameTermAs(node2);
+    }
+
+//    /** sameTerm(x,y) */
+//    public static boolean sameTerm(Node node1, Node node2) {
+//        if ( node1.equals(node2) )
+//            return true;
+//        if ( Util.isLangString(node1) && Util.isLangString(node2) ) {
+//            String lex1 = node1.getLiteralLexicalForm();
+//            String lex2 = node2.getLiteralLexicalForm();
+//            if ( !lex1.equals(lex2) )
+//                return false;
+//            return node1.getLiteralLanguage().equalsIgnoreCase(node2.getLiteralLanguage());
+//        }
+//        if ( node1.isTripleTerm() && node2.isTripleTerm() ) {
+//            return sameTriples(node1.getTriple(), node2.getTriple());
+//        }
+//        return false;
+//    }
 
     private static boolean sameTriples(Triple t1, Triple t2) {
         return sameTerm(t1.getSubject(), t2.getSubject())
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/resultset/ResultSetCompare.java b/jena-arq/src/main/java/org/apache/jena/sparql/resultset/ResultSetCompare.java
index b18a48fd9f..ebaa375bcc 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/resultset/ResultSetCompare.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/resultset/ResultSetCompare.java
@@ -18,68 +18,23 @@
 
 package org.apache.jena.sparql.resultset;
 
-import java.util.*;
-
-import org.apache.jena.atlas.iterator.Iter;
-import org.apache.jena.graph.Node;
-import org.apache.jena.graph.Triple;
+import org.apache.jena.atlas.lib.ListUtils;
 import org.apache.jena.query.ResultSet;
 import org.apache.jena.query.ResultSetFactory;
 import org.apache.jena.query.ResultSetRewindable;
 import org.apache.jena.rdf.model.Model;
-import org.apache.jena.sparql.core.Var;
-import org.apache.jena.sparql.engine.binding.Binding;
 import org.apache.jena.sparql.exec.RowSet;
-import org.apache.jena.sparql.exec.RowSetRewindable;
-import org.apache.jena.sparql.util.EqualityTest;
-import org.apache.jena.sparql.util.Iso;
-import org.apache.jena.sparql.util.NodeIsomorphismMap;
-import org.apache.jena.sparql.util.NodeUtils;
 
-/** Comparison of ResultSets.
- *  Note that reading ResultSets is destructive so consider using {@link ResultSetRewindable}
- *  from {@link ResultSetFactory#makeRewindable}
+/**
+ * Comparison of ResultSets.
+ * Note that reading ResultSets is destructive so consider using {@link ResultSetRewindable}
+ * from {@link ResultSetFactory#makeRewindable}.
+ *
+ *  @deprecated Use {@link ResultsCompare}.
  */
-
+@Deprecated(forRemoval = true)
 public class ResultSetCompare
 {
-    /* This is from the DAWG test suite.
-     * Result set 1:
-     *   ---------------
-     *   | x    | y    |
-     *   ===============
-     *   | _:b0 | _:b1 |
-     *   | _:b2 | _:b3 |
-     *   | _:b1 | _:b0 |
-     *   ---------------
-     * Result set 2:
-     *   ---------------
-     *   | x    | y    |
-     *   ===============
-     *   | _:b1 | _:b0 |
-     *   | _:b3 | _:b2 |
-     *   | _:b2 | _:b3 |
-     *   ---------------
-     */
-
-//    private static String[] rs1$ = {
-//        "(resultset (?x ?y)",
-//        "   (row (?x _:b0) (?y _:b1))",
-//        "   (row (?x _:b2) (?y _:b3))",
-//        "   (row (?x _:b1) (?y _:b0))",
-//        ")"};
-//    private static String[] rs2$ = {
-//        "(resultset (?x ?y)",
-//        "   (row (?x _:c1) (?y _:c0))",
-//        "   (row (?x _:c3) (?y _:c2))",
-//        "   (row (?x _:c2) (?y _:c3))",
-//        ")"};
-//
-
-    // Limitations:
-    // This code does not do compare/isomorphism combined with value testing.
-    // It drops to graph isomorphism, which is term based.
-
     /** Compare two result sets for equivalence.  Equivalence means:
      * A row rs1 has one matching row in rs2, and vice versa.
      * A row is only matched once.
@@ -98,27 +53,11 @@ public class ResultSetCompare
 
     /** See {@link #equalsByValue(ResultSet, ResultSet)} */
     public static boolean equalsByValue(RowSet rs1, RowSet rs2) {
-        if ( ! compareHeader(rs1, rs2) ) return false;
-
-        RowSetRewindable rs1a = rs1.rewindable();
-        RowSetRewindable rs2a = rs2.rewindable();
-
-        if ( equivalent(convert(rs1a), convert(rs2a), new BNodeIso(NodeUtils.sameValue)) )
-            return true;
-        rs1a.reset();
-        rs2a.reset();
-        return isomorphic(rs1a, rs2a);
-    }
-
-    public static boolean equalsByTest(Collection<Binding> b1, Collection<Binding> b2, EqualityTest match)
-    {
-        List<Binding> rows1 = new ArrayList<>(b1);
-        List<Binding> rows2 = new ArrayList<>(b2);
-        return equivalent(rows1, rows2, match);
+        return ResultsCompare.equalsByValue(rs1, rs2);
     }
 
-
-    /** compare two result sets for equivalence.  Equivalence means:
+    /**
+     * Compare two result sets for equivalence.  Equivalence means:
      * A row rs1 has one matching row in rs2, and vice versa.
      * A row is only matched once.
      * Rows match if they have the same variables with the same values,
@@ -132,22 +71,24 @@ public class ResultSetCompare
      */
 
     public static boolean equalsByTerm(ResultSet rs1, ResultSet rs2) {
-        return equalsByTerm(RowSet.adapt(rs1), RowSet.adapt(rs2));
+        return ResultsCompare.equalsByTerm(rs1, rs2);
     }
 
-    /** See {@link #equalsByTerm(ResultSet, ResultSet)} */
+    /**
+     * Compare two result sets for equivalence.  Equivalence means:
+     * A row rs1 has one matching row in rs2, and vice versa.
+     * A row is only matched once.
+     * Rows match if they have the same variables with the same values,
+     * bNodes must map to a consistent other bNodes.
+     * Term comparisons of nodes.
+     *
+     * Destructive - rs1 and rs2 are both read, possibly to exhaustion.
+     * @param rs1
+     * @param rs2
+     * @return true if they are equivalent
+     */
     public static boolean equalsByTerm(RowSet rs1, RowSet rs2) {
-        if ( ! compareHeader(rs1, rs2) )
-            return false;
-
-        RowSetRewindable rs1a = rs1.rewindable();
-        RowSetRewindable rs2a = rs2.rewindable();
-        // Aligned rows
-        if ( equivalent(convert(rs1a), convert(rs2a), new BNodeIso(NodeUtils.sameNode)) )
-            return true;
-        rs1a.reset();
-        rs2a.reset();
-        return isomorphic(rs1a, rs2a);
+        return ResultsCompare.equalsByTerm(rs1, rs2);
     }
 
     /** Compare two result sets for equivalence.  Equivalence means:
@@ -156,206 +97,97 @@ public class ResultSetCompare
      * bNodes must map to a consistent other bNodes.
      * Value comparisons of nodes.
      *
-     * Destructive - rs1 and rs2 are both read, possibly to exhaustion.
      * @param rs1
      * @param rs2
      * @return true if they are equivalent
      */
     public static boolean equalsByValueAndOrder(ResultSet rs1, ResultSet rs2) {
-        return equalsByValueAndOrder(RowSet.adapt(rs1), RowSet.adapt(rs2));
+        return ResultsCompare.equalsByValueAndOrder(rs1, rs2);
     }
 
     /** See {@link #equalsByValueAndOrder(ResultSet, ResultSet)} */
     public static boolean equalsByValueAndOrder(RowSet rs1, RowSet rs2) {
-        if ( ! compareHeader(rs1, rs2) )
-            return false;
-        return equivalentByOrder(convert(rs1) , convert(rs2), new BNodeIso(NodeUtils.sameValue));
+        return ResultsCompare.equalsByValueAndOrder(rs1, rs2);
     }
 
-
     /** compare two result sets for equivalence.  Equivalence means:
      * Each row in rs1 matches the same index row in rs2.
      * Rows match if they have the same variables with the same values,
      * bNodes must map to a consistent other bNodes.
      * RDF term comparisons of nodes.
      *
-     * Destructive - rs1 and rs2 are both read, possibly to exhaustion.
      * @param rs1
      * @param rs2
      * @return true if they are equivalent
      */
     public static boolean equalsByTermAndOrder(ResultSet rs1, ResultSet rs2) {
-        return equalsByTermAndOrder(RowSet.adapt(rs1), RowSet.adapt(rs2));
+        return ResultsCompare.equalsByTermAndOrder(rs1, rs2);
     }
 
     /** See {@link #equalsByTermAndOrder(ResultSet, ResultSet)} */
     public static boolean equalsByTermAndOrder(RowSet rs1, RowSet rs2) {
-        if ( ! compareHeader(rs1, rs2) )
-            return false;
-        return equivalentByOrder(convert(rs1) , convert(rs2), new BNodeIso(NodeUtils.sameNode));
+        return ResultsCompare.equalsByTermAndOrder(rs1, rs2);
     }
 
-    /** compare two result sets for exact equality equivalence.
-     * Exact equalitymeans:
-     * Each row in rs1 matches the same index row in rs2.
-     * Rows match if they have the same variables with the same values,
-     * bNodes must have same labels
+    /**
+     * Compare two result sets for exact equality equivalence and order.
+     * Blank nodes must have same labels.
      *
-     * Destructive - rs1 and rs2 are both read, possibly to exhaustion.
      * @param rs1
      * @param rs2
      * @return true if they are equivalent
      */
     public static boolean equalsExact(ResultSet rs1, ResultSet rs2) {
-        return equalsExact(RowSet.adapt(rs1), RowSet.adapt(rs2));
+        return ResultsCompare.equalsExact(rs1, rs2);
     }
 
-    /** See {@link #equalsExact(ResultSet, ResultSet)} */
-    public static boolean equalsExact(RowSet rs1, RowSet rs2) {
-        if ( !compareHeader(rs1, rs2) )
-            return false;
-        return equivalentByOrder(convert(rs1) , convert(rs2), NodeUtils.sameNode);
-    }
-
-    /** Compare two result sets for bNode isomorphism equivalence.
-     * Only does RDF term comparison.
+    /**
+     * Compare two result sets for exact equality equivalence and order.
+     * Blank nodes must have same labels.
+     *
+     * @param rs1
+     * @param rs2
+     * @return true if they are equivalent
      */
-    public static boolean isomorphic(ResultSet rs1, ResultSet rs2) {
-        Model m1 = RDFOutput.encodeAsModel(rs1);
-        Model m2 = RDFOutput.encodeAsModel(rs2);
-        return m1.isIsomorphicWith(m2);
-    }
-
-    /** See {@link #isomorphic(ResultSet, ResultSet)} */
-    public static boolean isomorphic(RowSet rs1, RowSet rs2) {
-        return isomorphic(ResultSet.adapt(rs1), ResultSet.adapt(rs2));
-    }
-
-    /** Compare two bindings, use the node equality test provided */
-    static public boolean equal(Binding bind1, Binding bind2, EqualityTest test) {
-        if ( bind1 == bind2 )
-            return true;
-        if ( bind1.size() != bind2.size() )
-            return false;
-        // They are the same size so containment is enough.
-        if ( ! containedIn(bind1, bind2, test) ) return false;
-        return true;
+    public static boolean equalsExact(RowSet rs1, RowSet rs2) {
+        return ResultsCompare.equalsByTermAndOrder(rs1, rs2);
     }
 
-    static private boolean compareHeader(RowSet rs1, RowSet rs2) {
+    private static boolean compareHeader(RowSet rs1, RowSet rs2) {
         if ( rs1 == null && rs2 == null )
             return true;
         if ( rs1 == null )
             return false;
         if ( rs2 == null )
             return false;
-        Set<Var> names1 = Set.copyOf(rs1.getResultVars());
-        Set<Var> names2 = Set.copyOf(rs2.getResultVars());
-        return names1.equals(names2);
-    }
-
-    static private List<Binding> convert(RowSet rs) {
-        return Iter.iter(rs).toList();
+        return ListUtils.equalsUnordered(rs1.getResultVars(), rs2.getResultVars());
     }
 
-    static private boolean equivalent(Collection<Binding> rows1, Collection<Binding> rows2, EqualityTest match) {
-        if ( rows1.size() != rows2.size() )
-            return false;
-        for ( Binding row1 : rows1 ) {
-            // find in rows2.
-            Binding matched = null;
-            for ( Binding row2 : rows2 ) {
-                // NEED BACKTRACKING
-                if ( equal(row1, row2, match) ) {
-                    matched = row2;
-                    break;
-                }
-            }
-
-            if ( matched == null )
-                return false;
-            // Remove matching.
-            rows2.remove(matched);
-        }
-        return true;
-    }
-
-    static private boolean equivalentByOrder(List<Binding> rows1, List<Binding> rows2, EqualityTest match) {
-        if ( rows1.size() != rows2.size() )
-             return false;
-
-        Iterator<Binding> iter1 = rows1.iterator();
-        Iterator<Binding> iter2 = rows2.iterator();
-
-        while ( iter1.hasNext() )
-        {
-            // Does not need backtracking because rows must
-            // align and so must variables in a row.
-            Binding row1 = iter1.next();
-            Binding row2 = iter2.next();
-            if ( !equal(row1, row2, match) )
-                return false;
-        }
-        return true;
+    /*
+     * Compare two result sets for blank node isomorphism equivalence. Only does RDF
+     * term comparison.
+     *
+     * This method does not handle triple terms in results.
+     *
+     * @deprecated Use {@link #equalsByTerm(ResultSet, ResultSet)} */
+    @Deprecated(forRemoval = true)
+    public static boolean isomorphic(ResultSet rs1, ResultSet rs2) {
+        Model m1 = RDFOutput.encodeAsModel(rs1);
+        Model m2 = RDFOutput.encodeAsModel(rs2);
+        return m1.isIsomorphicWith(m2);
     }
 
-    // Is bind1 contained in bind2?  For every (var,value) in bind1, is it in bind2?
-    // Maybe more in bind2.
-    private static boolean containedIn(Binding bind1, Binding bind2, EqualityTest test) {
-        // There are about 100 ways to do this!
-        Iterator<Var> iter1 =  bind1.vars();
-
-        while(iter1.hasNext()) {
-            Var v = iter1.next();
-            Node n1 = bind1.get(v);
-            Node n2 = bind2.get(v);
-            if ( n2 == null )
-                // v bound in bind1 and not in bind2.
-                return false;
-            if ( ! test.equal(n1, n2) )
-                return false;
-        }
-        return true;
+    /**
+     * Compare two row sets for blank node isomorphism equivalence. Only does RDF
+     * term comparison.
+     *
+     * This method does not handle triple terms in results.
+     *
+     * @deprecated Use {@link #equalsByTerm(RowSet, RowSet)}
+     */
+    @Deprecated(forRemoval = true)
+    public static boolean isomorphic(RowSet rs1, RowSet rs2) {
+        return isomorphic(ResultSet.adapt(rs1), ResultSet.adapt(rs2));
     }
 
-    public static class BNodeIso implements EqualityTest
-    {
-        private NodeIsomorphismMap mapping;
-        private EqualityTest literalTest;
-
-        public BNodeIso(EqualityTest literalTest) {
-            this.mapping = new NodeIsomorphismMap();
-            this.literalTest = literalTest;
-        }
-
-        @Override
-        public boolean equal(Node n1, Node n2) {
-            if ( n1 == null && n2 == null )
-                return true;
-            if ( n1 == null )
-                return false;
-            if ( n2 == null )
-                return false;
-
-            if ( n1.isURI() && n2.isURI() )
-                return n1.equals(n2);
-
-            if ( n1.isLiteral() && n2.isLiteral() )
-                return literalTest.equal(n1, n2);
-
-            if ( n1.isBlank() && n2.isBlank() )
-                return Iso.nodeIso(n1, n2, mapping);
-
-            if ( n1.isVariable() && n2.isVariable() )
-                return mapping.makeIsomorphic(n1, n2);
-
-            if ( n1.isTripleTerm() && n2.isTripleTerm() ) {
-                Triple t1 = n1.getTriple();
-                Triple t2 = n2.getTriple();
-                return  Iso.tripleIso(t1, t2, mapping);
-            }
-
-            return false;
-        }
-    }
 }
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/resultset/ResultSetCompare1.java b/jena-arq/src/main/java/org/apache/jena/sparql/resultset/ResultSetCompare1.java
new file mode 100644
index 0000000000..597a4d6898
--- /dev/null
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/resultset/ResultSetCompare1.java
@@ -0,0 +1,346 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.jena.sparql.resultset;
+
+public class ResultSetCompare1{}
+//import java.util.Iterator;
+//import java.util.List;
+//import java.util.Set;
+//
+//import org.apache.jena.atlas.iterator.Iter;
+//import org.apache.jena.graph.Node;
+//import org.apache.jena.query.ResultSet;
+//import org.apache.jena.query.ResultSetFactory;
+//import org.apache.jena.query.ResultSetRewindable;
+//import org.apache.jena.sparql.core.Var;
+//import org.apache.jena.sparql.engine.binding.Binding;
+//import org.apache.jena.sparql.exec.RowSet;
+//import org.apache.jena.sparql.util.EqualityTest;
+//import org.apache.jena.sparql.util.NodeUtils;
+//import org.apache.jena.sparql.util.iso.BNodeIso;
+//
+///** Comparison of ResultSets.
+// *  Note that reading ResultSets is destructive so consider using {@link ResultSetRewindable}
+// *  from {@link ResultSetFactory#makeRewindable}
+// */
+//
+//public class ResultSetCompare1 -- Original.
+//{
+//    /** Compare two result sets for equivalence.  Equivalence means:
+//     * A row rs1 has one matching row in rs2, and vice versa.
+//     * A row is only matched once.
+//     * Rows match if they have the same variables with the same values.
+//     * bNodes must map to a consistent other bNodes.  Value comparisons of nodes.
+//     *
+//     * Destructive - rs1 and rs2 are both read, possibly to exhaustion.
+//     * @param rs1
+//     * @param rs2
+//     * @return true if they are equivalent
+//     */
+//
+//    public static boolean equalsByValue(ResultSet rs1, ResultSet rs2) {
+//        return ResultsCompare.equalsByValue(rs1, rs2);
+//    }
+//
+//    /** See {@link #equalsByValue(ResultSet, ResultSet)} */
+//    public static boolean equalsByValue(RowSet rs1, RowSet rs2) {
+//        return ResultsCompare.equalsByValue(rs1, rs2);
+//    }
+//
+////
+////    public static boolean equalsByTest(Collection<Binding> b1, Collection<Binding> b2, EqualityTest match)
+////    {
+////        List<Binding> rows1 = new ArrayList<>(b1);
+////        List<Binding> rows2 = new ArrayList<>(b2);
+////        return equivalent(rows1, rows2, match);
+////    }
+////
+//    /** compare two result sets for equivalence.  Equivalence means:
+//     * A row rs1 has one matching row in rs2, and vice versa.
+//     * A row is only matched once.
+//     * Rows match if they have the same variables with the same values,
+//     * bNodes must map to a consistent other bNodes.
+//     * Term comparisons of nodes.
+//     *
+//     * Destructive - rs1 and rs2 are both read, possibly to exhaustion.
+//     * @param rs1
+//     * @param rs2
+//     * @return true if they are equivalent
+//     */
+//
+//    public static boolean equalsByTerm(ResultSet rs1, ResultSet rs2) {
+//        return ResultsCompare.equalsByTerm(rs1, rs2);
+//    }
+//
+//    /** See {@link #equalsByTerm(ResultSet, ResultSet)} */
+//    public static boolean equalsByTerm(RowSet rs1, RowSet rs2) {
+//        return ResultsCompare.equalsByTerm(rs1, rs2);
+//    }
+//
+//    /** Compare two result sets for equivalence.  Equivalence means:
+//     * Each row in rs1 matches the same index row in rs2.
+//     * Rows match if they have the same variables with the same values,
+//     * bNodes must map to a consistent other bNodes.
+//     * Value comparisons of nodes.
+//     *
+//     * Destructive - rs1 and rs2 are both read, possibly to exhaustion.
+//     * @param rs1
+//     * @param rs2
+//     * @return true if they are equivalent
+//     */
+//    public static boolean equalsByValueAndOrder(ResultSet rs1, ResultSet rs2) {
+//        return equalsByValueAndOrder(RowSet.adapt(rs1), RowSet.adapt(rs2));
+//    }
+//
+//    /** See {@link #equalsByValueAndOrder(ResultSet, ResultSet)} */
+//    public static boolean equalsByValueAndOrder(RowSet rs1, RowSet rs2) {
+//        if ( ! compareHeader(rs1, rs2) )
+//            return false;
+//        return equivalentByOrder(convert(rs1) , convert(rs2), BNodeIso.bnodeIsoByValue());
+//    }
+//
+//
+//    /** compare two result sets for equivalence.  Equivalence means:
+//     * Each row in rs1 matches the same index row in rs2.
+//     * Rows match if they have the same variables with the same values,
+//     * bNodes must map to a consistent other bNodes.
+//     * RDF term comparisons of nodes.
+//     *
+//     * Destructive - rs1 and rs2 are both read, possibly to exhaustion.
+//     * @param rs1
+//     * @param rs2
+//     * @return true if they are equivalent
+//     */
+//    public static boolean equalsByTermAndOrder(ResultSet rs1, ResultSet rs2) {
+//        return equalsByTermAndOrder(RowSet.adapt(rs1), RowSet.adapt(rs2));
+//    }
+//
+//    /** See {@link #equalsByTermAndOrder(ResultSet, ResultSet)} */
+//    public static boolean equalsByTermAndOrder(RowSet rs1, RowSet rs2) {
+//        if ( ! compareHeader(rs1, rs2) )
+//            return false;
+//        return equivalentByOrder(convert(rs1) , convert(rs2), BNodeIso.bnodeIsoByTerm());
+//    }
+//
+//    /** compare two result sets for exact equality equivalence.
+//     * Exact equality means:
+//     * Each row in rs1 matches the same index row in rs2.
+//     * Rows match if they have the same variables with the same values,
+//     * bNodes must have same labels
+//     *
+//     * Destructive - rs1 and rs2 are both read, possibly to exhaustion.
+//     * @param rs1
+//     * @param rs2
+//     * @return true if they are equivalent
+//     */
+//    public static boolean equalsExact(ResultSet rs1, ResultSet rs2) {
+//        return equalsExact(RowSet.adapt(rs1), RowSet.adapt(rs2));
+//    }
+//
+//    /** Compare two list - unordered */
+//    public static boolean equalsByTerm(List<Binding> list1, List<Binding> list2) {
+//        return ResultsCompare.equalsByTerm(list1, list2);
+//    }
+//
+//    /** Compare two list - unordered */
+//    public static boolean equalsExact(List<Binding> list1, List<Binding> list2) {
+//        return ResultsCompare.equalsExact(list1, list2);
+//    }
+//
+//    /**
+//     * Compare two row sets for exact equality equivalence.
+//     * Exact equality means:
+//     * Each row in rs1 matches the same index row in rs2.
+//     * Rows match if they have the same variables with the same values,
+//     * bNodes must have same labels
+//     *
+//     * Destructive - rs1 and rs2 are both read, possibly to exhaustion.
+//     * @param rs1
+//     * @param rs2
+//     * @return true if they are equivalent
+//     */
+//    public static boolean equalsExact(RowSet rs1, RowSet rs2) {
+//        if ( !compareHeader(rs1, rs2) )
+//            return false;
+//        return equivalentByOrder(convert(rs1) , convert(rs2), NodeUtils.sameNode);
+//    }
+//
+////
+////    /**
+////     * Compare two result sets for blank node isomorphism equivalence.
+////     * Only does RDF term comparison.
+////     * @deprecated Use {@link #equalsByTerm(ResultSet, ResultSet)}
+////     */
+////    @Deprecated(forRemoval = true)
+////    public static boolean isomorphic(ResultSet rs1, ResultSet rs2) {
+////        Model m1 = RDFOutput.encodeAsModel(rs1);
+////        Model m2 = RDFOutput.encodeAsModel(rs2);
+////        return m1.isIsomorphicWith(m2);
+////    }
+////
+////    /**
+////     * Compare two row sets for blank node isomorphism equivalence.
+////     * Only does RDF term comparison.
+////     * @deprecated Use {@link #equalsByTerm(RowSet, RowSet)}
+////     */
+////    @Deprecated(forRemoval = true)
+////    public static boolean isomorphic(RowSet rs1, RowSet rs2) {
+////        return isomorphic(ResultSet.adapt(rs1), ResultSet.adapt(rs2));
+////    }
+////
+//
+//    static private List<Binding> convert(RowSet rs) {
+//        return Iter.iter(rs).toList();
+//    }
+//
+//
+//    /** Compare two bindings, use the node equality test provided */
+//    static public boolean equal(Binding bind1, Binding bind2, EqualityTest test) {
+//        if ( bind1 == bind2 )
+//            return true;
+//        if ( bind1.size() != bind2.size() )
+//            return false;
+//        // They are the same size so containment is enough.
+//        if ( ! containedIn(bind1, bind2, test) )
+//            return false;
+//        return true;
+//    }
+//
+//    static private boolean compareHeader(RowSet rs1, RowSet rs2) {
+//        if ( rs1 == null && rs2 == null )
+//            return true;
+//        if ( rs1 == null )
+//            return false;
+//        if ( rs2 == null )
+//            return false;
+//        Set<Var> names1 = Set.copyOf(rs1.getResultVars());
+//        Set<Var> names2 = Set.copyOf(rs2.getResultVars());
+//        return names1.equals(names2);
+//    }
+//
+////    private static final boolean DEBUG = false;
+////
+////    static private boolean equivalent(Collection<Binding> rows1, Collection<Binding> rows2, EqualityTest match) {
+////        if ( rows1.size() != rows2.size() )
+////            return false;
+////        for ( Binding row1 : rows1 ) {
+////            if ( DEBUG ) System.out.println("   Row1 = "+row1);
+////
+////            // find in rows2.
+////            Binding matched = null;
+////            int i = 0;
+////            for ( Binding row2 : rows2 ) {
+////                if ( DEBUG ) System.out.printf("%-2d Row2 = %s\n", i++, row2);
+////
+////                // NEED BACKTRACKING
+////                if ( equal(row1, row2, match) ) {
+////                    if ( DEBUG ) System.out.println("MATCHES");
+////                    matched = row2;
+////                    break;
+////                }
+////            }
+////
+////            if ( DEBUG ) System.out.println();
+////            if ( matched == null )
+////                return false;
+////            // Remove matching.
+////            rows2.remove(matched);
+////        }
+////        return true;
+////    }
+////
+//    static private boolean equivalentByOrder(List<Binding> rows1, List<Binding> rows2, EqualityTest match) {
+//        if ( rows1.size() != rows2.size() )
+//             return false;
+//
+//        Iterator<Binding> iter1 = rows1.iterator();
+//        Iterator<Binding> iter2 = rows2.iterator();
+//
+//        while ( iter1.hasNext() )
+//        {
+//            // Does not need backtracking because rows must
+//            // align and so must variables in a row.
+//            Binding row1 = iter1.next();
+//            Binding row2 = iter2.next();
+//            if ( !equal(row1, row2, match) )
+//                return false;
+//        }
+//        return true;
+//    }
+//
+//    // Is bind1 contained in bind2?  For every (var,value) in bind1, is it in bind2?
+//    // Maybe more in bind2.
+//    private static boolean containedIn(Binding bind1, Binding bind2, EqualityTest test) {
+//        // There are about 100 ways to do this!
+//        Iterator<Var> iter1 =  bind1.vars();
+//
+//        while(iter1.hasNext()) {
+//            Var v = iter1.next();
+//            Node n1 = bind1.get(v);
+//            Node n2 = bind2.get(v);
+//            if ( n2 == null )
+//                // v bound in bind1 and not in bind2.
+//                return false;
+//            if ( ! test.equal(n1, n2) )
+//                return false;
+//        }
+//        return true;
+//    }
+////
+////    public static class BNodeIso implements EqualityTest
+////    {
+////        private NodeIsomorphismMap mapping;
+////        private EqualityTest literalTest;
+////
+////        public BNodeIso(EqualityTest literalTest) {
+////            this.mapping = new NodeIsomorphismMap();
+////            this.literalTest = literalTest;
+////        }
+////
+////        @Override
+////        public boolean equal(Node n1, Node n2) {
+////            if ( n1 == null && n2 == null )
+////                return true;
+////            if ( n1 == null )
+////                return false;
+////            if ( n2 == null )
+////                return false;
+////
+////            if ( n1.isURI() && n2.isURI() )
+////                return n1.equals(n2);
+////
+////            if ( n1.isLiteral() && n2.isLiteral() )
+////                return literalTest.equal(n1, n2);
+////
+////            if ( n1.isBlank() && n2.isBlank() )
+////                return Iso.nodeIso(n1, n2, mapping);
+////
+////            if ( n1.isVariable() && n2.isVariable() )
+////                return mapping.makeIsomorphic(n1, n2);
+////
+////            if ( n1.isTripleTerm() && n2.isTripleTerm() ) {
+////                Triple t1 = n1.getTriple();
+////                Triple t2 = n2.getTriple();
+////                return  Iso.tripleIso(t1, t2, mapping);
+////            }
+////
+////            return false;
+////        }
+////    }
+//}
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/resultset/ResultSetCompare.java b/jena-arq/src/main/java/org/apache/jena/sparql/resultset/ResultsCompare.java
similarity index 51%
copy from jena-arq/src/main/java/org/apache/jena/sparql/resultset/ResultSetCompare.java
copy to jena-arq/src/main/java/org/apache/jena/sparql/resultset/ResultsCompare.java
index b18a48fd9f..f5e7726548 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/resultset/ResultSetCompare.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/resultset/ResultsCompare.java
@@ -18,111 +18,70 @@
 
 package org.apache.jena.sparql.resultset;
 
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
 
 import org.apache.jena.atlas.iterator.Iter;
 import org.apache.jena.graph.Node;
-import org.apache.jena.graph.Triple;
 import org.apache.jena.query.ResultSet;
 import org.apache.jena.query.ResultSetFactory;
 import org.apache.jena.query.ResultSetRewindable;
-import org.apache.jena.rdf.model.Model;
 import org.apache.jena.sparql.core.Var;
 import org.apache.jena.sparql.engine.binding.Binding;
 import org.apache.jena.sparql.exec.RowSet;
-import org.apache.jena.sparql.exec.RowSetRewindable;
 import org.apache.jena.sparql.util.EqualityTest;
-import org.apache.jena.sparql.util.Iso;
-import org.apache.jena.sparql.util.NodeIsomorphismMap;
 import org.apache.jena.sparql.util.NodeUtils;
+import org.apache.jena.sparql.util.iso.BNodeIso;
+import org.apache.jena.sparql.util.iso.IsoAlgRows;
 
 /** Comparison of ResultSets.
  *  Note that reading ResultSets is destructive so consider using {@link ResultSetRewindable}
  *  from {@link ResultSetFactory#makeRewindable}
  */
 
-public class ResultSetCompare
-{
-    /* This is from the DAWG test suite.
-     * Result set 1:
-     *   ---------------
-     *   | x    | y    |
-     *   ===============
-     *   | _:b0 | _:b1 |
-     *   | _:b2 | _:b3 |
-     *   | _:b1 | _:b0 |
-     *   ---------------
-     * Result set 2:
-     *   ---------------
-     *   | x    | y    |
-     *   ===============
-     *   | _:b1 | _:b0 |
-     *   | _:b3 | _:b2 |
-     *   | _:b2 | _:b3 |
-     *   ---------------
-     */
-
-//    private static String[] rs1$ = {
-//        "(resultset (?x ?y)",
-//        "   (row (?x _:b0) (?y _:b1))",
-//        "   (row (?x _:b2) (?y _:b3))",
-//        "   (row (?x _:b1) (?y _:b0))",
-//        ")"};
-//    private static String[] rs2$ = {
-//        "(resultset (?x ?y)",
-//        "   (row (?x _:c1) (?y _:c0))",
-//        "   (row (?x _:c3) (?y _:c2))",
-//        "   (row (?x _:c2) (?y _:c3))",
-//        ")"};
-//
-
-    // Limitations:
-    // This code does not do compare/isomorphism combined with value testing.
-    // It drops to graph isomorphism, which is term based.
+public class ResultsCompare {
 
-    /** Compare two result sets for equivalence.  Equivalence means:
-     * A row rs1 has one matching row in rs2, and vice versa.
-     * A row is only matched once.
-     * Rows match if they have the same variables with the same values.
-     * bNodes must map to a consistent other bNodes.  Value comparisons of nodes.
+    /**
+     * Compare two result sets for equivalence where nodes are compared by value. A
+     * row rs1 has one matching row in rs2, and vice versa. A row is only matched
+     * once. Rows match if they have the same variables with the same values. bNodes
+     * must map to a consistent other bNodes. Value comparisons of nodes.
      *
-     * Destructive - rs1 and rs2 are both read, possibly to exhaustion.
      * @param rs1
      * @param rs2
      * @return true if they are equivalent
      */
-
     public static boolean equalsByValue(ResultSet rs1, ResultSet rs2) {
         return equalsByValue(RowSet.adapt(rs1), RowSet.adapt(rs2));
     }
 
     /** See {@link #equalsByValue(ResultSet, ResultSet)} */
     public static boolean equalsByValue(RowSet rs1, RowSet rs2) {
-        if ( ! compareHeader(rs1, rs2) ) return false;
-
-        RowSetRewindable rs1a = rs1.rewindable();
-        RowSetRewindable rs2a = rs2.rewindable();
-
-        if ( equivalent(convert(rs1a), convert(rs2a), new BNodeIso(NodeUtils.sameValue)) )
-            return true;
-        rs1a.reset();
-        rs2a.reset();
-        return isomorphic(rs1a, rs2a);
+        return IsoAlgRows.isIsomorphic(rs1, rs2, BNodeIso.Match.BNODES_VALUE);
     }
 
-    public static boolean equalsByTest(Collection<Binding> b1, Collection<Binding> b2, EqualityTest match)
-    {
-        List<Binding> rows1 = new ArrayList<>(b1);
-        List<Binding> rows2 = new ArrayList<>(b2);
-        return equivalent(rows1, rows2, match);
+    /**
+     * Compare two result sets for equivalence where nodes are compared by beign the
+     * same term.. A row rs1 has one matching row in rs2, and vice versa. A row is
+     * only matched once. Rows match if they have the same variables with the same
+     * values, bNodes must map to a consistent other bNodes.
+     *
+     * @param rs1
+     * @param rs2
+     * @return true if they are equivalent
+     */
+    public static boolean equalsByTerm(ResultSet rs1, ResultSet rs2) {
+        return equalsByTerm(RowSet.adapt(rs1), RowSet.adapt(rs2));
     }
 
-
-    /** compare two result sets for equivalence.  Equivalence means:
+    /**
+     * Compare two row sets for equivalence.  Equivalence means:
      * A row rs1 has one matching row in rs2, and vice versa.
      * A row is only matched once.
      * Rows match if they have the same variables with the same values,
-     * bNodes must map to a consistent other bNodes.
+     * blank node must map to a consistent blank node in the other row set.
      * Term comparisons of nodes.
      *
      * Destructive - rs1 and rs2 are both read, possibly to exhaustion.
@@ -130,33 +89,21 @@ public class ResultSetCompare
      * @param rs2
      * @return true if they are equivalent
      */
-
-    public static boolean equalsByTerm(ResultSet rs1, ResultSet rs2) {
-        return equalsByTerm(RowSet.adapt(rs1), RowSet.adapt(rs2));
-    }
-
-    /** See {@link #equalsByTerm(ResultSet, ResultSet)} */
     public static boolean equalsByTerm(RowSet rs1, RowSet rs2) {
-        if ( ! compareHeader(rs1, rs2) )
-            return false;
+        return IsoAlgRows.isIsomorphic(rs1, rs2, BNodeIso.Match.BNODES_TERM);
+    }
 
-        RowSetRewindable rs1a = rs1.rewindable();
-        RowSetRewindable rs2a = rs2.rewindable();
-        // Aligned rows
-        if ( equivalent(convert(rs1a), convert(rs2a), new BNodeIso(NodeUtils.sameNode)) )
-            return true;
-        rs1a.reset();
-        rs2a.reset();
-        return isomorphic(rs1a, rs2a);
+    /** Compare two list - unordered */
+    public static boolean equalsByTerm(List<Binding> list1, List<Binding> list2) {
+        return IsoAlgRows.isIsomorphic(list1, list2, BNodeIso.Match.BNODES_TERM);
     }
 
-    /** Compare two result sets for equivalence.  Equivalence means:
-     * Each row in rs1 matches the same index row in rs2.
-     * Rows match if they have the same variables with the same values,
-     * bNodes must map to a consistent other bNodes.
-     * Value comparisons of nodes.
-     *
+    /**
+     * Compare two result sets for equivalence. Equivalence means: Each row in rs1
+     * matches the same index row in rs2. Rows match if they have the same variables
+     * with the same values, bNodes must map to a consistent other bNodes.
      * Destructive - rs1 and rs2 are both read, possibly to exhaustion.
+     *
      * @param rs1
      * @param rs2
      * @return true if they are equivalent
@@ -165,21 +112,30 @@ public class ResultSetCompare
         return equalsByValueAndOrder(RowSet.adapt(rs1), RowSet.adapt(rs2));
     }
 
-    /** See {@link #equalsByValueAndOrder(ResultSet, ResultSet)} */
+    /**
+     * Compare two result sets for equivalence. Equivalence means: Each row in rs1
+     * matches the same index row in rs2. Rows match if they have the same variables
+     * with the same values, bNodes must map to a consistent other bNodes.
+     * Destructive - rs1 and rs2 are both read, possibly to exhaustion.
+     *
+     * @param rs1
+     * @param rs2
+     * @return true if they are equivalent
+     */
     public static boolean equalsByValueAndOrder(RowSet rs1, RowSet rs2) {
         if ( ! compareHeader(rs1, rs2) )
             return false;
-        return equivalentByOrder(convert(rs1) , convert(rs2), new BNodeIso(NodeUtils.sameValue));
+        return equivalentByOrder(convert(rs1) , convert(rs2), BNodeIso.bnodeIsoByValue());
     }
 
-
-    /** compare two result sets for equivalence.  Equivalence means:
-     * Each row in rs1 matches the same index row in rs2.
-     * Rows match if they have the same variables with the same values,
-     * bNodes must map to a consistent other bNodes.
+    /**
+     * Compare two result sets for equivalence.
+     * Equivalence means: Each row in rs1
+     * matches the same index row in rs2. Rows match if they have the same variables
+     * with the same values, blank nodes must map to a consistent other blank node.
      * RDF term comparisons of nodes.
-     *
      * Destructive - rs1 and rs2 are both read, possibly to exhaustion.
+     *
      * @param rs1
      * @param rs2
      * @return true if they are equivalent
@@ -188,18 +144,30 @@ public class ResultSetCompare
         return equalsByTermAndOrder(RowSet.adapt(rs1), RowSet.adapt(rs2));
     }
 
-    /** See {@link #equalsByTermAndOrder(ResultSet, ResultSet)} */
+    /**
+     * Compare two row sets for equivalence.
+     * Equivalence means: Each row in rs1
+     * matches the same index row in rs2. Rows match if they have the same variables
+     * with the same values, blank nodes must map to a consistent other blank node.
+     * RDF term comparisons of nodes.
+     * Destructive - rs1 and rs2 are both read, possibly to exhaustion.
+     *
+     * @param rs1
+     * @param rs2
+     * @return true if they are equivalent
+     */
     public static boolean equalsByTermAndOrder(RowSet rs1, RowSet rs2) {
         if ( ! compareHeader(rs1, rs2) )
             return false;
-        return equivalentByOrder(convert(rs1) , convert(rs2), new BNodeIso(NodeUtils.sameNode));
+        return equivalentByOrder(convert(rs1) , convert(rs2), BNodeIso.bnodeIsoByTerm());
     }
 
-    /** compare two result sets for exact equality equivalence.
-     * Exact equalitymeans:
+    /**
+     * Compare two result sets for exact equality equivalence.
+     * Exact equality means:
      * Each row in rs1 matches the same index row in rs2.
      * Rows match if they have the same variables with the same values,
-     * bNodes must have same labels
+     * Blank nodes must have same labels
      *
      * Destructive - rs1 and rs2 are both read, possibly to exhaustion.
      * @param rs1
@@ -210,25 +178,60 @@ public class ResultSetCompare
         return equalsExact(RowSet.adapt(rs1), RowSet.adapt(rs2));
     }
 
-    /** See {@link #equalsExact(ResultSet, ResultSet)} */
+    /**
+     * Compare two row sets for equivalence where blank nodes must have same labels
+     * Destructive - rs1 and rs2 are both read, possibly to exhaustion.
+     *
+     * @param rs1
+     * @param rs2
+     * @return true if they are equivalent
+     */
     public static boolean equalsExact(RowSet rs1, RowSet rs2) {
         if ( !compareHeader(rs1, rs2) )
             return false;
-        return equivalentByOrder(convert(rs1) , convert(rs2), NodeUtils.sameNode);
+        return equalsExact(convert(rs1), convert(rs2));
     }
 
-    /** Compare two result sets for bNode isomorphism equivalence.
-     * Only does RDF term comparison.
+    /**
+     * Compare two row sets for exact node equality equivalence.
+     * Each row in rs1 matches some other row in rs2.
+     * Rows match if they have the same variables with the same terms.
+     * Blank nodes must have same labels
+     *
+     * @param list1
+     * @param list2
+     * @return true if they are equivalent
      */
-    public static boolean isomorphic(ResultSet rs1, ResultSet rs2) {
-        Model m1 = RDFOutput.encodeAsModel(rs1);
-        Model m2 = RDFOutput.encodeAsModel(rs2);
-        return m1.isIsomorphicWith(m2);
+    public static boolean equalsExact(List<Binding> list1, List<Binding> list2) {
+        // Mutable copy used to track matches from list1.
+        // This would also work.
+        //   return IsoAlgRows.isIsomorphic(list1, list2, BNodeIso.Match.EXACT);
+        // The code here is simpel and direct.
+
+        list2 = new ArrayList<>(list2);
+        if ( list1.size() != list2.size() )
+            return false;
+        for ( Binding binding : list1 ) {
+            boolean wasPresent = list2.remove(binding);
+            if ( ! wasPresent )
+                return false;
+        }
+        // list2 must be empty.
+        return true;
     }
 
-    /** See {@link #isomorphic(ResultSet, ResultSet)} */
-    public static boolean isomorphic(RowSet rs1, RowSet rs2) {
-        return isomorphic(ResultSet.adapt(rs1), ResultSet.adapt(rs2));
+    /**
+     * Compare two row sets for exact node equality equivalence.
+     * Each row in rs1 matches the same row in rs2.
+     * Rows match if they have the same variables with the same values,
+     * Blank nodes must have same labels
+     *
+     * @param list1
+     * @param list2
+     * @return true if they are equivalent
+     */
+    public static boolean equalsExactAndOrder(List<Binding> list1, List<Binding> list2) {
+        return equivalentByOrder(list1, list2, NodeUtils.sameRdfTerm);
     }
 
     /** Compare two bindings, use the node equality test provided */
@@ -238,7 +241,8 @@ public class ResultSetCompare
         if ( bind1.size() != bind2.size() )
             return false;
         // They are the same size so containment is enough.
-        if ( ! containedIn(bind1, bind2, test) ) return false;
+        if ( ! containedIn(bind1, bind2, test) )
+            return false;
         return true;
     }
 
@@ -258,28 +262,6 @@ public class ResultSetCompare
         return Iter.iter(rs).toList();
     }
 
-    static private boolean equivalent(Collection<Binding> rows1, Collection<Binding> rows2, EqualityTest match) {
-        if ( rows1.size() != rows2.size() )
-            return false;
-        for ( Binding row1 : rows1 ) {
-            // find in rows2.
-            Binding matched = null;
-            for ( Binding row2 : rows2 ) {
-                // NEED BACKTRACKING
-                if ( equal(row1, row2, match) ) {
-                    matched = row2;
-                    break;
-                }
-            }
-
-            if ( matched == null )
-                return false;
-            // Remove matching.
-            rows2.remove(matched);
-        }
-        return true;
-    }
-
     static private boolean equivalentByOrder(List<Binding> rows1, List<Binding> rows2, EqualityTest match) {
         if ( rows1.size() != rows2.size() )
              return false;
@@ -287,8 +269,7 @@ public class ResultSetCompare
         Iterator<Binding> iter1 = rows1.iterator();
         Iterator<Binding> iter2 = rows2.iterator();
 
-        while ( iter1.hasNext() )
-        {
+        while (iter1.hasNext()) {
             // Does not need backtracking because rows must
             // align and so must variables in a row.
             Binding row1 = iter1.next();
@@ -317,45 +298,4 @@ public class ResultSetCompare
         }
         return true;
     }
-
-    public static class BNodeIso implements EqualityTest
-    {
-        private NodeIsomorphismMap mapping;
-        private EqualityTest literalTest;
-
-        public BNodeIso(EqualityTest literalTest) {
-            this.mapping = new NodeIsomorphismMap();
-            this.literalTest = literalTest;
-        }
-
-        @Override
-        public boolean equal(Node n1, Node n2) {
-            if ( n1 == null && n2 == null )
-                return true;
-            if ( n1 == null )
-                return false;
-            if ( n2 == null )
-                return false;
-
-            if ( n1.isURI() && n2.isURI() )
-                return n1.equals(n2);
-
-            if ( n1.isLiteral() && n2.isLiteral() )
-                return literalTest.equal(n1, n2);
-
-            if ( n1.isBlank() && n2.isBlank() )
-                return Iso.nodeIso(n1, n2, mapping);
-
-            if ( n1.isVariable() && n2.isVariable() )
-                return mapping.makeIsomorphic(n1, n2);
-
-            if ( n1.isTripleTerm() && n2.isTripleTerm() ) {
-                Triple t1 = n1.getTriple();
-                Triple t2 = n2.getTriple();
-                return  Iso.tripleIso(t1, t2, mapping);
-            }
-
-            return false;
-        }
-    }
 }
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/syntax/ElementData.java b/jena-arq/src/main/java/org/apache/jena/sparql/syntax/ElementData.java
index c82b967eaf..e58e1a5a98 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/syntax/ElementData.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/syntax/ElementData.java
@@ -27,9 +27,8 @@ import org.apache.jena.sparql.algebra.Table ;
 import org.apache.jena.sparql.algebra.table.TableData ;
 import org.apache.jena.sparql.core.Var ;
 import org.apache.jena.sparql.engine.binding.Binding ;
-import org.apache.jena.sparql.resultset.ResultSetCompare ;
+import org.apache.jena.sparql.resultset.ResultsCompare;
 import org.apache.jena.sparql.util.NodeIsomorphismMap ;
-import org.apache.jena.sparql.util.NodeUtils ;
 
 public class ElementData extends Element
 {
@@ -81,7 +80,8 @@ public class ElementData extends Element
         ElementData f2 = (ElementData)el2 ;
         if ( ! vars.equals(f2.vars) )
             return false ;
-        if ( ! ResultSetCompare.equalsByTest(rows, f2.rows, new ResultSetCompare.BNodeIso(NodeUtils.sameNode)) )
+
+        if ( ! ResultsCompare.equalsByTerm(rows, f2.rows) )
             return false ;
         return true ;
     }
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/util/Iso.java b/jena-arq/src/main/java/org/apache/jena/sparql/util/Iso.java
index c528fd6220..f8e17f5a99 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/util/Iso.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/util/Iso.java
@@ -34,165 +34,137 @@ import org.apache.jena.sparql.core.Var;
  */
 public class Iso {
 
-    /*
-     * Are lists of triples isomorphic?
-     * The mapping policy {@link NodeIsomorphismMap} is mutated.
+    /**
+     * Are lists of triples isomorphic? The mapping policy {@link NodeIsomorphismMap}
+     * is mutated.
      */
     public static boolean isomorphicTriples(List<Triple> triples1, List<Triple> triples2, NodeIsomorphismMap isoMap) {
         if ( triples1.size() != triples2.size() )
-            return false ;
+            return false;
         for ( int i = 0 ; i < triples1.size() ; i++ ) {
-            Triple t1 = triples1.get(i) ;
-            Triple t2 = triples2.get(i) ;
-            if ( ! tripleIso(t1, t2, isoMap))
-                return false ;
+            Triple t1 = triples1.get(i);
+            Triple t2 = triples2.get(i);
+            if ( !tripleIso(t1, t2, isoMap) )
+                return false;
         }
-        return true ;
+        return true;
     }
 
-    /*
-     * Are lists of quads isomorphic?
-     * The mapping policy {@link NodeIsomorphismMap} is mutated.
+    /**
+     * Are lists of quads isomorphic? The mapping policy {@link NodeIsomorphismMap}
+     * is mutated.
      */
     public static boolean isomorphicQuads(List<Quad> quads1, List<Quad> quads2, NodeIsomorphismMap isoMap) {
         if ( quads1.size() != quads2.size() )
-            return false ;
+            return false;
         for ( int i = 0 ; i < quads1.size() ; i++ ) {
-            Quad q1 = quads1.get(i) ;
-            Quad q2 = quads2.get(i) ;
-            if ( ! quadIso(q1, q2, isoMap))
-                return false ;
+            Quad q1 = quads1.get(i);
+            Quad q2 = quads2.get(i);
+            if ( !quadIso(q1, q2, isoMap) )
+                return false;
         }
-        return true ;
+        return true;
     }
 
-    /*
-     * Are lists of nodes isomorphic?
-     * The mapping policy {@link NodeIsomorphismMap} is mutated.
+    /**
+     * Are lists of nodes isomorphic? The mapping policy {@link NodeIsomorphismMap}
+     * is mutated.
      */
     public static boolean isomorphicNodes(List<Node> nodes1, List<Node> nodes2, NodeIsomorphismMap isoMap) {
         if ( nodes1.size() != nodes2.size() )
-            return false ;
+            return false;
         for ( int i = 0 ; i < nodes1.size() ; i++ ) {
-            Node n1 = nodes1.get(i) ;
-            Node n2 = nodes2.get(i) ;
-            if ( ! nodeIso(n1, n2, isoMap))
-                return false ;
+            Node n1 = nodes1.get(i);
+            Node n2 = nodes2.get(i);
+            if ( !nodeIso(n1, n2, isoMap) )
+                return false;
         }
-        return true ;
+        return true;
     }
 
-    /*
-     * Are two triple paths isomorphic?
-     * The mapping policy {@link NodeIsomorphismMap} is mutated.
+    /**
+     * Are two triple paths isomorphic? The mapping policy {@link NodeIsomorphismMap}
+     * is mutated.
      */
-    public static boolean triplePathIso(TriplePath tp1, TriplePath tp2, NodeIsomorphismMap isoMap)
-    {
+    public static boolean triplePathIso(TriplePath tp1, TriplePath tp2, NodeIsomorphismMap isoMap) {
         if ( tp1.isTriple() ^ tp2.isTriple() )
-            return false ;
+            return false;
 
         if ( tp1.isTriple() )
-            return Iso.tripleIso(tp1.asTriple(), tp2.asTriple(), isoMap) ;
+            return Iso.tripleIso(tp1.asTriple(), tp2.asTriple(), isoMap);
         else
-            return Iso.nodeIso(tp1.getSubject(), tp2.getSubject(), isoMap) &&
-                   Iso.nodeIso(tp1.getObject(), tp2.getObject(), isoMap) &&
-                   tp1.getPath().equalTo(tp2.getPath(), isoMap) ;
+            return Iso.nodeIso(tp1.getSubject(), tp2.getSubject(), isoMap) && Iso.nodeIso(tp1.getObject(), tp2.getObject(), isoMap)
+                   && tp1.getPath().equalTo(tp2.getPath(), isoMap);
     }
 
-    /*
-     * Are two triples isomorphic?
-     * The mapping policy {@link NodeIsomorphismMap} is mutated.
+    /**
+     * Are two triples isomorphic? The mapping policy {@link NodeIsomorphismMap} is
+     * mutated.
      */
-    public static boolean tripleIso(Triple t1, Triple t2, NodeIsomorphismMap labelMap)
-    {
-        Node s1 = t1.getSubject() ;
-        Node p1 = t1.getPredicate() ;
-        Node o1 = t1.getObject() ;
-
-        Node s2 = t2.getSubject() ;
-        Node p2 = t2.getPredicate() ;
-        Node o2 = t2.getObject() ;
-
-        if ( ! nodeIso(s1, s2, labelMap) )
-            return false ;
-        if ( ! nodeIso(p1, p2, labelMap) )
-            return false ;
-        if ( ! nodeIso(o1, o2, labelMap) )
-            return false ;
-
-        return true ;
+    public static boolean tripleIso(Triple t1, Triple t2, NodeIsomorphismMap labelMap) {
+        Node s1 = t1.getSubject();
+        Node p1 = t1.getPredicate();
+        Node o1 = t1.getObject();
+
+        Node s2 = t2.getSubject();
+        Node p2 = t2.getPredicate();
+        Node o2 = t2.getObject();
+
+        if ( !nodeIso(s1, s2, labelMap) )
+            return false;
+        if ( !nodeIso(p1, p2, labelMap) )
+            return false;
+        if ( !nodeIso(o1, o2, labelMap) )
+            return false;
+
+        return true;
     }
 
-    /*
-     * Are two quads isomorphic?
-     * The mapping policy {@link NodeIsomorphismMap} is mutated.
+    /**
+     * Are two quads isomorphic? The mapping policy {@link NodeIsomorphismMap} is
+     * mutated.
      */
-    public static boolean quadIso(Quad t1, Quad t2, NodeIsomorphismMap labelMap)
-    {
-        Node g1 = t1.getGraph() ;
-        Node s1 = t1.getSubject() ;
-        Node p1 = t1.getPredicate() ;
-        Node o1 = t1.getObject() ;
-
-        Node g2 = t2.getGraph() ;
-        Node s2 = t2.getSubject() ;
-        Node p2 = t2.getPredicate() ;
-        Node o2 = t2.getObject() ;
-
-        if ( ! nodeIso(g1, g2, labelMap) )
-            return false ;
-        if ( ! nodeIso(s1, s2, labelMap) )
-            return false ;
-        if ( ! nodeIso(p1, p2, labelMap) )
-            return false ;
-        if ( ! nodeIso(o1, o2, labelMap) )
-            return false ;
-
-        return true ;
+    public static boolean quadIso(Quad t1, Quad t2, NodeIsomorphismMap labelMap) {
+        Node g1 = t1.getGraph();
+        Node s1 = t1.getSubject();
+        Node p1 = t1.getPredicate();
+        Node o1 = t1.getObject();
+
+        Node g2 = t2.getGraph();
+        Node s2 = t2.getSubject();
+        Node p2 = t2.getPredicate();
+        Node o2 = t2.getObject();
+
+        if ( !nodeIso(g1, g2, labelMap) )
+            return false;
+        if ( !nodeIso(s1, s2, labelMap) )
+            return false;
+        if ( !nodeIso(p1, p2, labelMap) )
+            return false;
+        if ( !nodeIso(o1, o2, labelMap) )
+            return false;
+
+        return true;
     }
 
-    /*
-     * Are two nodes isomorphic?
-     * The mapping policy {@link NodeIsomorphismMap} is mutated.
+    /**
+     * Are two nodes isomorphic? The mapping policy {@link NodeIsomorphismMap} is
+     * mutated.
      */
-    public static boolean nodeIso(Node n1, Node n2, NodeIsomorphismMap isoMap)
-    {
+    public static boolean nodeIso(Node n1, Node n2, NodeIsomorphismMap isoMap) {
         if ( isoMap != null ) {
             if ( n1.isBlank() && n2.isBlank() )
-                return isoMap.makeIsomorphic(n1, n2) ;
+                return isoMap.makeIsomorphic(n1, n2);
             if ( Var.isBlankNodeVar(n1) && Var.isBlankNodeVar(n2) )
                 return isoMap.makeIsomorphic(n1, n2) ;
-    }
-        return n1.equals(n2) ;
-    }
-
-    /** Interface for choosing the pairs of node that can be map[ped for isomorphism. */
-    public interface Mappable {
-        boolean isMappable(Node n);
-        boolean mappable(Node n1, Node n2);
-    }
-
-    /** Blank nodes are mappable in {@link IsoAlg} */
-    public static Iso.Mappable mappableBlankNodes = new Mappable() {
-        @Override public boolean isMappable(Node n) { return n.isBlank(); }
-        @Override public boolean mappable(Node n1, Node n2) { return n1.isBlank() && n2.isBlank(); }
-    };
-
-    /** Blank nodes and variables are mappable in {@link IsoAlg} */
-    public static Iso.Mappable mappableVariables = new Mappable() {
-        @Override public boolean isMappable(Node n) { return n.isVariable(); }
-        @Override public boolean mappable(Node n1, Node n2) { return n1.isVariable() && n2.isVariable(); }
-    };
-
-
-    /** Blank nodes and variables are mappable in {@link IsoAlg} */
-    public static Iso.Mappable mappableBlankNodesVariables = new Mappable() {
-        @Override public boolean isMappable(Node n) { return n.isBlank()|| n.isVariable() ; }
-        @Override public boolean mappable(Node n1, Node n2) {
-            if ( n1.isBlank() && n2.isBlank() ) return true;
-            if ( n1.isVariable() && n2.isVariable() ) return true;
-            return false;
+            if ( n1.isTripleTerm() && n2.isTripleTerm() )
+                return tripleIso(n1.getTriple(), n2.getTriple(), isoMap);
+            if ( n1.isNodeGraph() && n2.isNodeGraph() ) {
+                List<Triple> list1 = n1.getGraph().find().toList();
+                List<Triple> list2 = n2.getGraph().find().toList();
+                return isomorphicTriples(list1, list2, isoMap);
+            }
         }
-    };
+        return n1.equals(n2);
+    }
 }
-
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/util/IsoMatcher.java b/jena-arq/src/main/java/org/apache/jena/sparql/util/IsoMatcher.java
index 6230fd1312..6e3ba9581d 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/util/IsoMatcher.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/util/IsoMatcher.java
@@ -31,16 +31,19 @@ import org.apache.jena.graph.Node ;
 import org.apache.jena.graph.Triple ;
 import org.apache.jena.sparql.core.DatasetGraph ;
 import org.apache.jena.sparql.core.Quad ;
+import org.apache.jena.sparql.util.iso.IsoAlgRows;
+import org.apache.jena.sparql.util.iso.IsoAlgTuple;
+import org.apache.jena.util.iterator.ExtendedIterator;
 
 /**
  * Simple isomorphism testing for on unordered collections.
  * This code is simple and slow.
- * For graphs, the Graph isomorphism code in Jena is much better (better tested, better performance)
  * This code can work on any tuples of nodes.
  *
  * See {@link Iso} for isomorphism for ordered lists.
  *
- * See {@link IsoAlg} for the isomorphism algorithm.
+ * See {@link IsoAlgTuple} for the isomorphism algorithm.
+ * See {@link IsoAlgRows} for the isomorphism algorithm for rowsets.
  */
 public class IsoMatcher
 {
@@ -64,20 +67,23 @@ public class IsoMatcher
     }
 
     /** Helper - convert to {@code List<Tuple<Node>>} */
-    /*package*/ static List<Tuple<Node>> tuplesTriples(Iterator<Triple> iter) {
-        return Iter.iter(iter).map(t->tuple(t.getSubject(), t.getPredicate(), t.getObject())).toList();
+    private static List<Tuple<Node>> tuplesQuads(Iterator<Quad> iter) {
+        return Iter.iter(iter).map(q->tuple(q.getGraph(), q.getSubject(), q.getPredicate(), q.getObject())).toList();
     }
 
     /** Helper - convert to {@code List<Tuple<Node>>} */
-    public static List<Tuple<Node>> tuplesQuads(Iterator<Quad> iter) {
-        return Iter.iter(iter).map(q->tuple(q.getGraph(), q.getSubject(), q.getPredicate(), q.getObject())).toList();
+    private static List<Tuple<Node>> tuplesTriples(ExtendedIterator<Triple> iter) {
+        try {
+            return Iter.iter(iter).map(t->tuple(t.getSubject(), t.getPredicate(), t.getObject())).toList();
+        }
+        finally { iter.close(); }
     }
 
     /** Collection of tuples isomorphism, with choice of when two nodes are "equal".
-     * See also {@link IsoAlg#isIsomorphic(Collection, Collection, org.apache.jena.sparql.util.Iso.Mappable, EqualityTest)}
+     * See also {@link IsoAlgTuple#isIsomorphic(Collection, Collection, org.apache.jena.sparql.util.Iso.Mappable, EqualityTest)}
      * for isomorphisms testing for more than just blank nodes.
      */
-    public static boolean isomorphic(Collection<Tuple<Node>> x1, Collection<Tuple<Node>> x2, EqualityTest nodeTest) {
-        return IsoAlg.isIsomorphic(x1, x2, nodeTest);
+    private static boolean isomorphic(Collection<Tuple<Node>> x1, Collection<Tuple<Node>> x2, EqualityTest nodeTest) {
+        return IsoAlgTuple.isIsomorphic(x1, x2, nodeTest);
     }
 }
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/util/NodeIsomorphismMap.java b/jena-arq/src/main/java/org/apache/jena/sparql/util/NodeIsomorphismMap.java
index 7bc86d7645..f80d3b3b2f 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/util/NodeIsomorphismMap.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/util/NodeIsomorphismMap.java
@@ -18,30 +18,35 @@
 
 package org.apache.jena.sparql.util;
 
-import java.util.HashMap ;
-import java.util.Map ;
+import java.util.HashMap;
+import java.util.Map;
 
-import org.apache.jena.graph.Node ;
+import org.apache.jena.graph.Node;
 
 /** Map nodes to nodes. */
 public class NodeIsomorphismMap
 {
-    private Map<Node, Node> map = new HashMap<>() ;
-    
+    private Map<Node, Node> map = new HashMap<>();
+
     public NodeIsomorphismMap() {}
-    
-    private Node get(Node key)                { return map.get(key) ; }
-    private void put(Node key, Node value)    { map.put(key, value) ; }
-    
+
+    private Node get(Node key)                { return map.get(key); }
+    private void put(Node key, Node value)    { map.put(key, value); }
+
     public boolean makeIsomorphic(Node n1, Node n2) {
         if ( n1.isBlank() && n2.isBlank() ) {
-            Node other = get(n1) ;
+            Node other = get(n1);
             if ( other == null ) {
-                put(n1, n2) ;
-                return true ;
+                put(n1, n2);
+                return true;
             }
-            return other.equals(n2) ;
+            return other.equals(n2);
         }
-        return n1.equals(n2) ;
+        return n1.equals(n2);
+    }
+
+    @Override
+    public String toString() {
+        return "Iso["+map.toString()+"]";
     }
 }
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/util/NodeToLabelMap.java b/jena-arq/src/main/java/org/apache/jena/sparql/util/NodeToLabelMap.java
index 52877633e7..ab75c73e07 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/util/NodeToLabelMap.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/util/NodeToLabelMap.java
@@ -18,85 +18,55 @@
 
 package org.apache.jena.sparql.util;
 
-import java.util.HashMap ;
-import java.util.Map ;
+import java.util.HashMap;
+import java.util.Map;
 
-import org.apache.jena.graph.Node ;
+import org.apache.jena.graph.Node;
 
 /** Map nodes to blank node representations. */
-public class NodeToLabelMap
-{
+public class NodeToLabelMap {
     // Could abstract again as a node -> label cache + cache miss handler.
-    int bNodeCounter = 0 ;
-    Map<Node, String> bNodeStrings = new HashMap<>() ;
-    boolean bNodesAsFakeURIs = false ;
-    String prefixString = "b" ;
+    int bNodeCounter = 0;
+    Map<Node, String> bNodeStrings = new HashMap<>();
+    boolean bNodesAsFakeURIs = false;
+    String prefixString = "b";
 
-    public NodeToLabelMap() { this("b") ; }
+    public NodeToLabelMap() {
+        this("b");
+    }
 
-    public NodeToLabelMap(String prefix) { this(prefix, false) ; }
+    public NodeToLabelMap(String prefix) {
+        this(prefix, false);
+    }
 
-    public NodeToLabelMap(String prefix, boolean bNodesAsFakeURIs)
-    {
+    public NodeToLabelMap(String prefix, boolean bNodesAsFakeURIs) {
         if ( prefix == null || prefix.equals("") )
-            throw new IllegalArgumentException("Must provide a prefix") ;
-        this.prefixString = "_:"+prefix ;
-        this.bNodesAsFakeURIs = bNodesAsFakeURIs ;
+            throw new IllegalArgumentException("Must provide a prefix");
+        this.prefixString = "_:" + prefix;
+        this.bNodesAsFakeURIs = bNodesAsFakeURIs;
     }
 
     // Null means not mapped
-    public String asString(Node n)
-    {
-        if ( ! n.isBlank() )
-            return null ;
-
-        return mapNode(n) ;
+    public String asString(Node n) {
+        if ( !n.isBlank() )
+            return null;
+        return mapNode(n);
     }
 
     // synchronized because this may be used from a static via FmtUtils
-    protected synchronized String mapNode(Node n)
-    {
-        String s = bNodeStrings.get(n) ;
+    protected synchronized String mapNode(Node n) {
+        String s = bNodeStrings.get(n);
         if ( s != null )
-            return s ;
+            return s;
 
-        s = genStringForNode(n) ;
-        bNodeStrings.put(n, s) ;
-        return s ;
+        s = genStringForNode(n);
+        bNodeStrings.put(n, s);
+        return s;
     }
 
-    protected String genStringForNode(Node n)
-    {
+    protected String genStringForNode(Node n) {
         if ( bNodesAsFakeURIs && n.isBlank() )
-            return "<_:"+n.getBlankNodeLabel()+">" ;
-
-        return  prefixString+(bNodeCounter++) ;
+            return "<_:" + n.getBlankNodeLabel() + ">";
+        return prefixString + (bNodeCounter++);
     }
-
-//    /**
-//     * @return Returns the prefix.
-//     */
-//    public String getPrefixString()
-//    {
-//        return prefixString ;
-//    }
-//
-//    /**
-//     * @param prefix The prefix to set.
-//     */
-//    public void setPrefixString(String prefix)
-//    {
-//        if ( prefix == null )
-//        {
-//            Log.fatal(this,"Prefix string is null") ;
-//            throw new ARQInternalErrorException("Prefix string is null") ;
-//        }
-//        if ( prefix.equals("") )
-//        {
-//            Log.fatal(this,"Prefix string is the empty string") ;
-//            throw new ARQInternalErrorException("Prefix string is the empty string") ;
-//        }
-//
-//        this.prefixString = prefix ;
-//    }
 }
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/util/iso/BNodeIso.java b/jena-arq/src/main/java/org/apache/jena/sparql/util/iso/BNodeIso.java
new file mode 100644
index 0000000000..692c31142a
--- /dev/null
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/util/iso/BNodeIso.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.jena.sparql.util.iso;
+
+import org.apache.jena.graph.Node;
+import org.apache.jena.graph.Triple;
+import org.apache.jena.sparql.util.EqualityTest;
+import org.apache.jena.sparql.util.Iso;
+import org.apache.jena.sparql.util.NodeIsomorphismMap;
+import org.apache.jena.sparql.util.NodeUtils;
+
+public class BNodeIso implements EqualityTest
+{
+    /** Kind of matching to perform. */
+    // BNODES => blank node isomorphism.
+    public enum Match { BNODES_TERM, BNODES_VALUE, EXACT_TERM, EXACT_VALUE }
+
+    /**
+     * Create a fresh {@link EqualityTest} that provides blank node isomorphism and
+     * comparision by term.
+     */
+    public static EqualityTest bnodeIsoByTerm() {
+        return new BNodeIso(NodeUtils.sameRdfTerm);
+    }
+
+    /**
+     * Create a fresh {@link EqualityTest} that provides blank node isomorphism and
+     * comparision by term.
+     */
+    public static EqualityTest bnodeIsoByValue() {
+        return new BNodeIso(NodeUtils.sameValue);
+    }
+
+    private NodeIsomorphismMap mapping;
+    private EqualityTest literalTest;
+
+    private BNodeIso(EqualityTest literalTest) {
+        this.mapping = new NodeIsomorphismMap();
+        this.literalTest = literalTest;
+    }
+
+    @Override
+    public boolean equal(Node n1, Node n2) {
+        if ( n1 == null && n2 == null )
+            return true;
+        if ( n1 == null )
+            return false;
+        if ( n2 == null )
+            return false;
+
+        if ( n1.isURI() && n2.isURI() )
+            return n1.equals(n2);
+
+        if ( n1.isLiteral() && n2.isLiteral() )
+            return literalTest.equal(n1, n2);
+
+        if ( n1.isBlank() && n2.isBlank() )
+            return Iso.nodeIso(n1, n2, mapping);
+
+        if ( n1.isVariable() && n2.isVariable() )
+            return mapping.makeIsomorphic(n1, n2);
+
+        if ( n1.isTripleTerm() && n2.isTripleTerm() ) {
+            Triple t1 = n1.getTriple();
+            Triple t2 = n2.getTriple();
+            return  Iso.tripleIso(t1, t2, mapping);
+        }
+
+        return false;
+    }
+}
\ No newline at end of file
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/util/iso/IsoAlgRows.java b/jena-arq/src/main/java/org/apache/jena/sparql/util/iso/IsoAlgRows.java
new file mode 100644
index 0000000000..220de7a008
--- /dev/null
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/util/iso/IsoAlgRows.java
@@ -0,0 +1,272 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.jena.sparql.util.iso;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+
+import org.apache.jena.atlas.io.IndentedWriter;
+import org.apache.jena.atlas.iterator.Iter;
+import org.apache.jena.atlas.lib.InternalErrorException;
+import org.apache.jena.graph.Node;
+import org.apache.jena.graph.Triple;
+import org.apache.jena.sparql.core.Var;
+import org.apache.jena.sparql.engine.binding.Binding;
+import org.apache.jena.sparql.exec.RowSet;
+import org.apache.jena.sparql.util.EqualityTest;
+import org.apache.jena.sparql.util.NodeUtils;
+import org.apache.jena.sparql.util.iso.IsoLib.Mappable;
+
+public class IsoAlgRows {
+
+    private static final boolean        DEBUG = false;
+    private static final IndentedWriter out   = debugIndentedWriter();
+    private static final IndentedWriter debugIndentedWriter() {
+        IndentedWriter iout = null;
+        if ( DEBUG ) {
+            iout = new IndentedWriter(System.out);
+            iout.setFlushOnNewline(true);
+        }
+        return iout;
+    }
+
+    public static boolean isIsomorphic(RowSet rowset1, RowSet rowset2, BNodeIso.Match matching) {
+        Objects.requireNonNull(rowset1);
+        Objects.requireNonNull(rowset2);
+        if ( rowset1 == rowset2 )
+            return true;
+        if ( ! IsoLib.compareHeader(rowset1, rowset2) )
+            return false;
+        List<Binding> rows1 = toList(rowset1);
+        List<Binding> rows2 = toList(rowset2);
+        if ( rows1.size() != rows2.size() )
+            return false;
+
+        // Could divide into rows with exact matches (no blank nodes) and those with
+        // blank nodes.
+
+        return matcher(rows1, rows2, matching);
+    }
+
+    public static boolean isIsomorphic(List<Binding> list1, List<Binding> list2, BNodeIso.Match matching) {
+        if ( list1.size() != list2.size() )
+            return false;
+        return matcher(list1, list2, matching);
+    }
+
+    private static boolean matcher(List<Binding> list1, List<Binding> list2, BNodeIso.Match matching) {
+        Mappable mapping;
+        EqualityTest equalityTest;
+        switch(matching) {
+            case BNODES_TERM, EXACT_TERM    -> equalityTest = NodeUtils.sameRdfTerm;
+            case BNODES_VALUE, EXACT_VALUE  -> equalityTest = NodeUtils.sameValue;
+            default -> { throw new InternalErrorException("Unknown equality policy"); }
+        }
+        switch(matching) {
+            case EXACT_VALUE, EXACT_TERM    -> mapping = IsoLib.mappableNoMap;
+            case BNODES_TERM, BNODES_VALUE  -> mapping = IsoLib.mappableBlankNodes;
+            default -> { throw new InternalErrorException("Unknown matching policy"); }
+        }
+
+        return matcherWorker(list1, 0, list2, IsoMapping.rootMapping, mapping, equalityTest);
+    }
+
+    // Match rows in rows1, starting at idx1, against rows in rows2, starting at idx2
+    private static boolean matcherWorker(List<Binding> rows1, int idx1, List<Binding> rows2,
+                                         IsoMapping mapping, IsoLib.Mappable mappable, EqualityTest equalityTest) {
+        if ( rows1.size() == 0 )
+            return true;
+
+        for ( int i = idx1 ; i < rows1.size() ; i++ ) {
+            Binding row1 = rows1.get(i);
+            if ( row1 == null )
+                continue;
+            if ( DEBUG )
+                out.println("-- Row1 = "+row1);
+            List<Possibility> possibilities = matcherOneRow(row1, rows2, mapping, mappable, equalityTest);
+            if ( DEBUG )
+                out.println("Possibilities: "+possibilities);
+
+            if ( possibilities.isEmpty() )
+                return false;
+
+            for ( Possibility poss : possibilities ) {
+                if ( DEBUG )
+                    out.println("Try: " + poss);
+                // Without this row1, and the possibility row can we map the rest?
+                int iRest = i+1;
+                if ( iRest >= rows1.size() ) {
+                    // Last item
+                    if ( DEBUG )
+                        out.println("Yes (zero left)");
+                    return true;
+                }
+                // One element of row2 removed. Not by position.
+                List<Binding> rows2x = new ArrayList<>(rows2);
+                rows2x.remove(poss.row);
+
+                if ( DEBUG )
+                    out.incIndent();
+
+                if ( matcherWorker(rows1, iRest, rows2x, poss.mapping, mappable, equalityTest) ) {
+                    if ( DEBUG )
+                        out.decIndent();
+
+                    if ( DEBUG )
+                        out.println("Yes");
+                    return true;
+                }
+                if ( DEBUG )
+                    out.decIndent();
+                if ( DEBUG )
+                    out.println("No");
+            }
+            // No possibility
+            return false;
+        }
+        // Maybe a bug in this code
+        return false;
+    }
+
+    private static List<Possibility> matcherOneRow(Binding row1, List<Binding> rows2, IsoMapping mapping, IsoLib.Mappable mappable, EqualityTest equalityTest) {
+        // find in rows2.
+        List<Possibility> possibilities = new ArrayList<>();
+        for ( int j = 0 ; j < rows2.size() ; j++ ) {
+            Binding row2 = rows2.get(j);
+            if ( row2 == null )
+                continue;
+            if ( DEBUG )
+                out.printf("%-2d Row2 = %s\n", j, row2);
+            IsoMapping step = matchOne(row1, row2, mapping, mappable, equalityTest);
+            if ( step == null )
+                continue;
+            Possibility poss = new Possibility(row2, step);
+            if ( DEBUG )
+                out.println(poss.toString());
+            // if row1 all concrete, drop row1 and row2
+            possibilities.add(poss);
+        }
+        return possibilities;
+    }
+
+    /** Can row1 be matched  by row2? */
+    private static IsoMapping matchOne(Binding row1, Binding row2, IsoMapping mapping, IsoLib.Mappable mappable, EqualityTest equalityTest) {
+//        Set<Var> vars1 = row1.varsMentioned();
+//        Set<Var> vars2 = row2.varsMentioned();
+//        if ( ! vars1.equals(vars2) )
+//            return null;
+
+        Iterator<Var> iter = row1.vars();
+        IsoMapping mapping2 = mapping;
+        while(iter.hasNext()) {
+            Var v = iter.next();
+            Node n1 = row1.get(v);
+            Node n2 = row2.get(v);
+            if ( n2 == null )
+                return null;
+            mapping2 = matchTerms(n1, n2, mapping2, mappable, equalityTest);
+            if ( mapping2 == null )
+                return null;
+            if ( DEBUG )
+                out.println("Mapping: "+mapping2);
+
+        }
+        return mapping2;
+    }
+
+    /*package*/ static IsoMapping matchTermsTest(Node n1, Node n2, EqualityTest nodeTest) {
+        EqualityTest eqtest = BNodeIso.bnodeIsoByTerm();
+        return matchTerms(n1, n2, IsoMapping.rootMapping, IsoLib.mappableBlankNodes, eqtest);
+    }
+
+    static IsoMapping matchTerms(Node n1, Node n2, IsoMapping _mapping, IsoLib.Mappable mappable, EqualityTest nodeTest) {
+        if ( _mapping == null )
+            return null;
+        if ( n1 == null || n2 == null )
+            return null;
+        IsoMapping mapping = _mapping;
+        Node n1m = mapping.map(n1);
+
+        if ( n1m != null ) {
+            // Already mapped
+            if ( DEBUG )
+                out.printf("Existing: %s -> %s for %s\n", n1, n1m, n2);
+            if ( n1m.equals(n2) )
+                // Exact equals after mapping t1 slot.
+                return mapping;
+            // No match.
+            return null;
+        }
+
+        if ( n1.isTripleTerm() ) {
+            if ( n2.isTripleTerm() ) {
+                Triple t1 = n1.getTriple();
+                Triple t2 = n2.getTriple();
+                mapping = matchTerms(t1.getSubject(), t2.getSubject(), mapping, mappable, nodeTest);
+                if ( mapping == null )
+                    return null;
+                mapping = matchTerms(t1.getPredicate(), t2.getPredicate(), mapping, mappable, nodeTest);
+                if ( mapping == null )
+                    return null;
+                mapping = matchTerms(t1.getObject(), t2.getObject(), mapping, mappable, nodeTest);
+                if ( mapping == null )
+                    return null;
+                return mapping;
+            }
+            // n2 not a triple term.
+            return null;
+        } else if ( n2.isTripleTerm() ) {
+            // n1 not a triple term.
+            return null;
+        }
+
+        // Map an atomic term (not triple term)
+        if ( mappable.mappable(n1, n2) ) {
+            if ( mapping.reverseMapped(n2) ) {
+                // Already a target.
+                // but not the same (else n1m != null)
+                return null;
+            }
+            // Add mapping.
+            mapping = new IsoMapping(mapping, n1, n2);
+            return mapping;
+        }
+
+        if ( !nodeTest.equal(n1, n2) )
+            // No isomorphism.
+            return null;
+
+        return mapping;
+    }
+
+
+    static private List<Binding> toList(RowSet rs) {
+        return Iter.iter(rs).toList();
+    }
+
+    private record Possibility(Binding row, IsoMapping mapping) {
+        @Override
+        public String toString() {
+            return String.format("Poss|%s %s|", IsoLib.str(row), mapping);
+        }
+    }
+}
+
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/util/IsoAlg.java b/jena-arq/src/main/java/org/apache/jena/sparql/util/iso/IsoAlgTuple.java
similarity index 63%
rename from jena-arq/src/main/java/org/apache/jena/sparql/util/IsoAlg.java
rename to jena-arq/src/main/java/org/apache/jena/sparql/util/iso/IsoAlgTuple.java
index 3ce8de4514..9040c3218c 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/util/IsoAlg.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/util/iso/IsoAlgTuple.java
@@ -16,7 +16,7 @@
  * limitations under the License.
  */
 
-package org.apache.jena.sparql.util;
+package org.apache.jena.sparql.util.iso;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -29,7 +29,9 @@ import org.apache.jena.atlas.lib.tuple.Tuple;
 import org.apache.jena.atlas.lib.tuple.TupleFactory;
 import org.apache.jena.graph.Node;
 import org.apache.jena.graph.Triple;
-import org.apache.jena.sparql.util.Iso.Mappable;
+import org.apache.jena.sparql.util.EqualityTest;
+import org.apache.jena.sparql.util.Iso;
+import org.apache.jena.sparql.util.NodeUtils;
 
 /**
  * Simple isomorphism testing for collections of tuples of nodes. This can be used
@@ -37,75 +39,65 @@ import org.apache.jena.sparql.util.Iso.Mappable;
  * better (better tested, better performance) for graph isomorphism. This code is
  * simple, easier to understand, and works on collections of tuples, not just graphs.
  */
-public class IsoAlg {
-
-    /** Record the mapping of a node. This is a one-way linked list. */
-    public static class Mapping {
-        final Node     node1;
-        final Node     node2;
-        final Mapping  parent;
-
-        public static final Mapping rootMapping = new Mapping(null, null, null);
-
-        public Mapping(Mapping parent, Node node1, Node node2) {
-            super();
-            this.parent = parent;
-            this.node1 = node1;
-            this.node2 = node2;
-        }
-
-        public boolean mapped(Node node) {
-            return map(node) != null;
-        }
-
-        public Node map(Node node) {
-            Mapping mapping = this;
-            while (mapping != rootMapping) {
-                if ( mapping.node1.equals(node) )
-                    return mapping.node2;
-                mapping = mapping.parent;
-            }
-            return null;
-        }
-
-        public boolean reverseMapped(Node node) {
-            return reverseMap(node) != null;
-        }
-
-        public Node reverseMap(Node node) {
-            Mapping mapping = this;
-            while (mapping != rootMapping) {
-                if ( mapping.node2.equals(node) )
-                    return mapping.node1;
-                mapping = mapping.parent;
-            }
-            return null;
-        }
-
-
-        @Override
-        public String toString() {
-            StringBuilder sbuff = new StringBuilder();
-            Mapping mapping = this;
-            while (mapping != rootMapping) {
-                sbuff.append("{" + mapping.node1 + " => " + mapping.node2 + "}");
-                mapping = mapping.parent;
-            }
-            sbuff.append("{}");
-            return sbuff.toString();
-        }
-    }
-
-    static class Possibility {
-        final Tuple<Node> tuple;
-        final Mapping     mapping;
-
-        public Possibility(Tuple<Node> tuple, Mapping mapping) {
-            super();
-            this.tuple = tuple;
-            this.mapping = mapping;
-        }
-
+public class IsoAlgTuple {
+
+//    /** Record the IsoMapping of a node. This is a one-way linked list. */
+//    private static class IsoMapping {
+//        final Node     node1;
+//        final Node     node2;
+//        final IsoMapping  parent;
+//
+//        static final IsoMapping rootIsoMapping = new Mapping(null, null, null);
+//
+//        Mapping(IsoMapping parent, Node node1, Node node2) {
+//            super();
+//            this.parent = parent;
+//            this.node1 = node1;
+//            this.node2 = node2;
+//        }
+//
+//        boolean mapped(Node node) {
+//            return map(node) != null;
+//        }
+//
+//        Node map(Node node) {
+//            IsoMapping IsoMapping = this;
+//            while (IsoMapping != rootMapping) {
+//                if ( mapping.node1.equals(node) )
+//                    return mapping.node2;
+//                IsoMapping = mapping.parent;
+//            }
+//            return null;
+//        }
+//
+//        boolean reverseMapped(Node node) {
+//            return reverseMap(node) != null;
+//        }
+//
+//        Node reverseMap(Node node) {
+//            IsoMapping IsoMapping = this;
+//            while (IsoMapping != rootMapping) {
+//                if ( mapping.node2.equals(node) )
+//                    return mapping.node1;
+//                IsoMapping = mapping.parent;
+//            }
+//            return null;
+//        }
+//
+//        @Override
+//        public String toString() {
+//            StringBuilder sbuff = new StringBuilder();
+//            IsoMapping IsoMapping = this;
+//            while (IsoMapping != rootMapping) {
+//                sbuff.append("{" + mapping.node1 + " => " + mapping.node2 + "}");
+//                IsoMapping = mapping.parent;
+//            }
+//            sbuff.append("{}");
+//            return sbuff.toString();
+//        }
+//    }
+
+    private record Possibility(Tuple<Node> tuple, IsoMapping mapping) {
         @Override
         public String toString() {
             return String.format("Poss|%s %s|", tuple, mapping);
@@ -121,51 +113,55 @@ public class IsoAlg {
      * or {@link NodeUtils#sameRdfTerm} (Node.equals, with lang tag insensitive testing).
      */
     public static boolean isIsomorphic(Collection<Tuple<Node>> x1, Collection<Tuple<Node>> x2, EqualityTest nodeTest) {
-        return isIsomorphic(x1, x2, Iso.mappableBlankNodes, nodeTest);
+        return isIsomorphic(x1, x2, IsoLib.mappableBlankNodes, nodeTest);
     }
 
     /**
-     * Isomorphism test based on a class of mappable elements (e.g. blank nodes {@linkplain Iso#mappableBlankNodes},
+     * Isomorphism test based on a class of IsoLib.Mappable elements (e.g. blank nodes {@linkplain Iso#mappableBlankNodes},
      * or blank nodes and variables {@linkplain Iso#mappableBlankNodesVariables}).
      * Two nodes considered "equal" by an equality test such as
      * {@link NodeUtils#sameValue} (SPARQL value testing),
      * {@link NodeUtils#sameNode} (Node.equals),
      * or {@link NodeUtils#sameRdfTerm} (Node.equals, with lang tag insensitive testing).
      */
-    public static boolean isIsomorphic(Collection<Tuple<Node>> x1, Collection<Tuple<Node>> x2, Iso.Mappable mappable, EqualityTest nodeTest) {
-        return matcher(x1, x2, Mapping.rootMapping, mappable, nodeTest);
+    /*package*/ static boolean isIsomorphic(Collection<Tuple<Node>> x1, Collection<Tuple<Node>> x2, IsoLib.Mappable mappable, EqualityTest nodeTest) {
+        if ( x1.size() != x2.size() )
+            return false;
+        return matcher(x1, x2, IsoMapping.rootMapping, mappable, nodeTest);
     }
 
     /**
-     * Isomorphism test based on a class of mappable elements (e.g. blank nodes {@linkplain Iso#mappableBlankNodes},
+     * Isomorphism test based on a class of IsoLib.Mappable elements (e.g. blank nodes {@linkplain Iso#mappableBlankNodes},
      * or blank nodes and variables {@linkplain Iso#mappableBlankNodesVariables}).
      * Two nodes considered "equal" by an equality test such as
      * {@link NodeUtils#sameValue} (SPARQL value testing),
      * {@link NodeUtils#sameNode} (Node.equals),
      * or {@link NodeUtils#sameRdfTerm} (Node.equals, with lang tag insensitive testing).
      */
-    public static Mapping isIsomorphic(Tuple<Node> tuple1, Tuple<Node> tuple2, Mapping mapping, Mappable mappable, EqualityTest nodeTest) {
-        return gen(tuple1, tuple2, mapping, mappable, nodeTest);
+    private static IsoMapping isIsomorphic(Tuple<Node> tuple1, Tuple<Node> tuple2, IsoMapping mapping, IsoLib.Mappable mappable, EqualityTest nodeTest) {
+        return matchTuples(tuple1, tuple2, mapping, mappable, nodeTest);
     }
 
     // Debug.
     private static final boolean        DEBUG = false;
-    private static final IndentedWriter out   = new IndentedWriter(System.out);
-    static {
-        out.setFlushOnNewline(true);
+    private static final IndentedWriter out   = debugIndentedWriter();
+    private static final IndentedWriter debugIndentedWriter() {
+        IndentedWriter iout = null;
+        if ( DEBUG ) {
+            iout = new IndentedWriter(System.out);
+            iout.setFlushOnNewline(true);
+        }
+        return iout;
     }
 
-    private static boolean matcher(Collection<Tuple<Node>> tuples1, Collection<Tuple<Node>> tuples2, Mapping mapping,
-                                   Iso.Mappable mappable, EqualityTest nodeTest) {
+    private static boolean matcher(Collection<Tuple<Node>> tuples1, Collection<Tuple<Node>> tuples2, IsoMapping mapping,
+                                   IsoLib.Mappable mappable, EqualityTest nodeTest) {
         if ( DEBUG ) {
             out.println("match: ");
             out.println("  1: " + tuples1);
             out.println("  2: " + tuples2);
             out.println("  M: " + mapping);
         }
-        if ( tuples1.size() != tuples2.size() )
-            return false;
-
         // Process the inputs : tuples of all non-mappable terms can be processed here (Node.equals)
         // Loop on tuples1:
         //   if not mappable, remove from tuples2.
@@ -176,6 +172,7 @@ public class IsoAlg {
         // Fast path.
         if ( true )
         {
+            // XXX We can move this out to the caller of matcher.
             List<Tuple<Node>> tuples1$ = new ArrayList<>();
             // Step one - copy non-mappable tuples or exit now.
             for ( Tuple<Node> t1 : tuples1 ) {
@@ -187,7 +184,7 @@ public class IsoAlg {
                 }
                 tuples1$.add(t1);
             }
-            // Tuples for mappable processing.
+            // Tuples for IsoLib.Mappable processing.
             tuples1 = tuples1$;
         } else
             // No pre-process concrete.
@@ -205,10 +202,10 @@ public class IsoAlg {
 
             List<Possibility> causes = matcher(t1, tuples2, mapping, mappable, nodeTest);
 
-            if ( DEBUG )
+            if ( DEBUG ) {
                 out.println("    Possibilities: Tuple" + t1 + " :: " + causes);
-
-            out.incIndent();
+                out.incIndent();
+            }
             try {
                 // Try each possible tuple-tuple matching until one succeeds all the
                 // way.
@@ -218,9 +215,9 @@ public class IsoAlg {
                     // Try t1 -> t2
                     Tuple<Node> t2 = c.tuple;
                     tuples2.remove(t2);
-                    // Try without t1 and t2, using the mapping of this cause.
-                    if ( tuples1.isEmpty() && tuples2.isEmpty() ) // They are the
-                        // same size.
+                    // Try without t1 and t2, using the IsoMapping of this cause.
+                    if ( tuples1.isEmpty() && tuples2.isEmpty() )
+                        // They are the same size.
                         return true;
                     // Recurse
                     if ( matcher(tuples1, tuples2, c.mapping, mappable, nodeTest) ) {
@@ -235,7 +232,7 @@ public class IsoAlg {
                 return false;
             }
             finally {
-                out.decIndent();
+                if ( DEBUG ) out.decIndent();
             }
         }
         return true;
@@ -258,11 +255,10 @@ public class IsoAlg {
     }
 
     /** Return all possible tuple-tuple matches from tuple t1 to tuples in x2 */
-    private static List<Possibility> matcher(Tuple<Node> t1, Collection<Tuple<Node>> g2, Mapping mapping, Iso.Mappable mappable,
-                                       EqualityTest nodeTest) {
+    private static List<Possibility> matcher(Tuple<Node> t1, Collection<Tuple<Node>> g2, IsoMapping mapping, IsoLib.Mappable mappable, EqualityTest nodeTest) {
         List<Possibility> matches = new ArrayList<>();
         for ( Tuple<Node> t2 : g2 ) {
-            Mapping step = gen(t1, t2, mapping, mappable, nodeTest);
+            IsoMapping step = matchTuples(t1, t2, mapping, mappable, nodeTest);
             if ( step != null ) {
                 Possibility c = new Possibility(t2, step);
                 matches.add(c);
@@ -272,19 +268,19 @@ public class IsoAlg {
     }
 
     /**
-     * Find a mapping between the tuples, given a start mapping.
-     * Return a mapping or null for "no match".
+     * Find a IsoMapping between the tuples, given a start mapping.
+     * Return a IsoMapping or null for "no match".
      */
-    private static Mapping gen(Tuple<Node> t1, Tuple<Node> t2, Mapping _mapping, Iso.Mappable mappable, EqualityTest nodeTest) {
+    private static IsoMapping matchTuples(Tuple<Node> t1, Tuple<Node> t2, IsoMapping _mapping, IsoLib.Mappable mappable, EqualityTest nodeTest) {
         if ( t1.len() != t2.len() )
             return null;
 
-        Mapping mapping = _mapping;
+        IsoMapping mapping = _mapping;
         for ( int i = 0 ; i < t1.len() ; i++ ) {
             Node n1 = t1.get(i);
             Node n2 = t2.get(i);
 
-            Mapping mapping2 = gen(n1, n2, mapping, mappable, nodeTest);
+            IsoMapping mapping2 = matchTerms(n1, n2, mapping, mappable, nodeTest);
             if ( mapping2 == null )
                 return null;
             mapping = mapping2;
@@ -297,15 +293,14 @@ public class IsoAlg {
                                        triple.getPredicate(),
                                        triple.getObject());
 
-
-    static Mapping gen(Node n1, Node n2, Mapping _mapping, Iso.Mappable mappable, EqualityTest nodeTest) {
-        Mapping mapping = _mapping;
+    static IsoMapping matchTerms(Node n1, Node n2, IsoMapping _mapping, IsoLib.Mappable mappable, EqualityTest nodeTest) {
+        IsoMapping mapping = _mapping;
         Node n1m = mapping.map(n1);
 
         if ( n1m != null ) {
             // Already mapped
             if ( n1m.equals(n2) )
-                // Exact equals after mapping t1 slot.
+                // Exact equals after IsoMapping n1.
                 return mapping;
             // No match.
             return null;
@@ -317,8 +312,7 @@ public class IsoAlg {
                 Triple t2 = n2.getTriple();
                 Tuple<Node> tuple1 = tripleToTuple.apply(t1);
                 Tuple<Node> tuple2 = tripleToTuple.apply(t2);
-                // Whether to records the triple term mapping.
-                return gen(tuple1, tuple2, mapping, mappable, nodeTest);
+                return matchTuples(tuple1, tuple2, mapping, mappable, nodeTest);
             }
         } else if ( n2.isTripleTerm() ) {
             return null;
@@ -333,7 +327,7 @@ public class IsoAlg {
             }
             // **** If n2 not already mapped.
 
-            mapping = new Mapping(mapping, n1, n2);
+            mapping = new IsoMapping(mapping, n1, n2);
             return mapping;
         }
 
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/util/iso/IsoLib.java b/jena-arq/src/main/java/org/apache/jena/sparql/util/iso/IsoLib.java
new file mode 100644
index 0000000000..f0506dd11f
--- /dev/null
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/util/iso/IsoLib.java
@@ -0,0 +1,125 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.jena.sparql.util.iso;
+
+import java.util.Iterator;
+import java.util.Set;
+
+import org.apache.jena.graph.Node;
+import org.apache.jena.riot.out.NodeFmtLib;
+import org.apache.jena.sparql.core.Var;
+import org.apache.jena.sparql.engine.binding.Binding;
+import org.apache.jena.sparql.exec.RowSet;
+import org.apache.jena.sparql.exec.RowSetRewindable;
+import org.apache.jena.sparql.util.EqualityTest;
+
+/**
+ * Shared stuff. Where does this go?
+ */
+class IsoLib {
+    static final boolean DEBUG = false;
+
+    /**
+     * Interface for choosing the pairs of node that can be mapped for isomorphism.
+     */
+    @FunctionalInterface
+    interface Mappable {
+        boolean mappable(Node n1, Node n2);
+    }
+
+    /** Blank nodes are mappable in {@link IsoAlgTuple} */
+    static Mappable mappableBlankNodes = (Node n1, Node n2) -> n1.isBlank() && n2.isBlank();
+
+    static Mappable mappableVariables = (Node n1, Node n2) -> n1.isVariable() && n2.isVariable();
+
+    static Mappable mappableBlankNodesVariables = (Node n1, Node n2) ->  {
+            if ( n1.isBlank() && n2.isBlank() ) return true;
+            if ( n1.isVariable() && n2.isVariable() ) return true;
+            return false;
+        };
+
+    /** Exact match only (a separate test), same term, blank nodes have same label. */
+    static Mappable mappableNoMap = (Node n1, Node n2) -> false;
+
+    static boolean compareHeader(RowSet rs1, RowSet rs2) {
+        if ( rs1 == null && rs2 == null )
+            return true;
+        if ( rs1 == null )
+            return false;
+        if ( rs2 == null )
+            return false;
+        Set<Var> names1 = Set.copyOf(rs1.getResultVars());
+        Set<Var> names2 = Set.copyOf(rs2.getResultVars());
+        return names1.equals(names2);
+    }
+
+    static RowSetRewindable print(RowSet rs) {
+        RowSetRewindable rsw = rs.rewindable();
+        rsw.forEach(binding->{
+            String x = str(binding);
+            System.out.print("  ");
+            System.out.print(x);
+            System.out.println();
+        });
+        rsw.reset();
+        return rsw;
+    }
+
+    // String, with blank node ids, not "_:bN"
+    static String str(Binding binding) {
+        StringBuffer sbuff = new StringBuffer();
+        binding.forEach((var, node)-> {
+            String tmp = NodeFmtLib.strNT(node);
+            sbuff.append("( ?" + var.getVarName() + " = " + tmp + " )");
+
+        });
+        return sbuff.toString();
+    }
+
+
+    /** Compare two bindings, use the node equality test provided */
+    static boolean equal(Binding bind1, Binding bind2, EqualityTest test) {
+        if ( bind1 == bind2 )
+            return true;
+        if ( bind1.size() != bind2.size() )
+            return false;
+        // They are the same size so containment is enough.
+        if ( ! containedIn(bind1, bind2, test) )
+            return false;
+        return true;
+    }
+
+    // Is bind1 contained in bind2?  For every (var,value) in bind1, is it in bind2?
+    // Maybe more in bind2.
+    private static boolean containedIn(Binding bind1, Binding bind2, EqualityTest test) {
+        Iterator<Var> iter1 =  bind1.vars();
+        while(iter1.hasNext()) {
+            Var v = iter1.next();
+            Node n1 = bind1.get(v);
+            Node n2 = bind2.get(v);
+            if ( n2 == null )
+                // v bound in bind1 and not in bind2.
+                return false;
+            if ( ! test.equal(n1, n2) )
+                return false;
+        }
+        return true;
+    }
+
+}
\ No newline at end of file
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/util/iso/IsoMapping.java b/jena-arq/src/main/java/org/apache/jena/sparql/util/iso/IsoMapping.java
new file mode 100644
index 0000000000..263db14b33
--- /dev/null
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/util/iso/IsoMapping.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.jena.sparql.util.iso;
+
+import org.apache.jena.graph.Node;
+
+/** Record the mapping of a node. This is a one-way linked list. */
+/*package*/ record IsoMapping(IsoMapping parent, Node node1, Node node2) {
+
+    static final IsoMapping rootMapping = new IsoMapping(null, null, null);
+
+    boolean mapped(Node node) {
+        return map(node) != null;
+    }
+
+    Node map(Node node) {
+        IsoMapping mapping = this;
+        while (mapping != rootMapping) {
+            if ( mapping.node1.equals(node) )
+                return mapping.node2;
+            mapping = mapping.parent;
+        }
+        return null;
+    }
+
+    boolean reverseMapped(Node node) {
+        return reverseMap(node) != null;
+    }
+
+    Node reverseMap(Node node) {
+        IsoMapping mapping = this;
+        while (mapping != rootMapping) {
+            if ( mapping.node2.equals(node) )
+                return mapping.node1;
+            mapping = mapping.parent;
+        }
+        return null;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sbuff = new StringBuilder();
+        IsoMapping mapping = this;
+        while (mapping != rootMapping) {
+            sbuff.append("{" + mapping.node1 + " => " + mapping.node2 + "}");
+            mapping = mapping.parent;
+        }
+        sbuff.append("{}");
+        return sbuff.toString();
+    }
+}
\ No newline at end of file
diff --git a/jena-arq/src/test/java/org/apache/jena/arq/junit/sparql/tests/QueryEvalTest.java b/jena-arq/src/test/java/org/apache/jena/arq/junit/sparql/tests/QueryEvalTest.java
index ce1b51a95f..00d3377c52 100644
--- a/jena-arq/src/test/java/org/apache/jena/arq/junit/sparql/tests/QueryEvalTest.java
+++ b/jena-arq/src/test/java/org/apache/jena/arq/junit/sparql/tests/QueryEvalTest.java
@@ -50,7 +50,7 @@ import org.apache.jena.sparql.engine.binding.BindingBuilder;
 import org.apache.jena.sparql.engine.iterator.QueryIterPlainWrapper;
 import org.apache.jena.sparql.expr.nodevalue.NodeFunctions;
 import org.apache.jena.sparql.junit.QueryTestException;
-import org.apache.jena.sparql.resultset.ResultSetCompare;
+import org.apache.jena.sparql.resultset.ResultsCompare;
 import org.apache.jena.sparql.resultset.SPARQLResult;
 import org.apache.jena.sparql.util.IsoMatcher;
 import org.apache.jena.sparql.vocabulary.ResultSetGraphVocab;
@@ -211,7 +211,7 @@ public class QueryEvalTest implements Runnable {
         if ( results == null )
             return;
 
-        // Assumes resultSetCompare can cope with full isomorphism possibilities.
+        // Assumes ResultsCompare can cope with full isomorphism possibilities.
         ResultSetRewindable resultsExpected;
         if ( results.isResultSet() )
             resultsExpected = ResultSetFactory.makeRewindable(results.getResultSet());
@@ -301,14 +301,14 @@ public class QueryEvalTest implements Runnable {
     private static boolean resultSetEquivalent(Query query, ResultSetRewindable resultsExpected, ResultSetRewindable resultsActual) {
         if ( compareResultSetsByValue ) {
             if ( query.isOrdered() )
-                return ResultSetCompare.equalsByValueAndOrder(resultsExpected, resultsActual);
+                return ResultsCompare.equalsByValueAndOrder(resultsExpected, resultsActual);
             else
-                return ResultSetCompare.equalsByValue(resultsExpected, resultsActual);
+                return ResultsCompare.equalsByValue(resultsExpected, resultsActual);
         } else {
             if ( query.isOrdered() )
-                return ResultSetCompare.equalsByTermAndOrder(resultsExpected, resultsActual);
+                return ResultsCompare.equalsByTermAndOrder(resultsExpected, resultsActual);
             else
-                return ResultSetCompare.equalsByTerm(resultsExpected, resultsActual);
+                return ResultsCompare.equalsByTerm(resultsExpected, resultsActual);
         }
     }
 
diff --git a/jena-arq/src/test/java/org/apache/jena/atlas/data/DataBagExaminer.java b/jena-arq/src/test/java/org/apache/jena/atlas/data/DataBagExaminer.java
index 2c2c4bac16..2a3673b967 100644
--- a/jena-arq/src/test/java/org/apache/jena/atlas/data/DataBagExaminer.java
+++ b/jena-arq/src/test/java/org/apache/jena/atlas/data/DataBagExaminer.java
@@ -18,20 +18,17 @@
 
 package org.apache.jena.atlas.data;
 
-import java.io.File ;
+import java.io.File;
 
 /**
- * This class exists so that we can examine the internals of a DataBag during testing (which we can do here since it is in the same package).
+ * This class exists so that we can examine the internals of a DataBag during testing
+ * (which we can do here since it is in the same package).
  */
-public class DataBagExaminer
-{
-    public static int countTemporaryFiles(AbstractDataBag<?> bag)
-    {
+public class DataBagExaminer {
+    public static int countTemporaryFiles(AbstractDataBag<? > bag) {
         int count = 0;
-        for (File tempFile : bag.getSpillFiles())
-        {
-            if (tempFile.exists())
-            {
+        for ( File tempFile : bag.getSpillFiles() ) {
+            if ( tempFile.exists() ) {
                 count++;
             }
         }
diff --git a/jena-arq/src/test/java/org/apache/jena/atlas/data/TestDistinctDataBag.java b/jena-arq/src/test/java/org/apache/jena/atlas/data/TestDistinctDataBag.java
index 734b96d171..a75494a3e3 100644
--- a/jena-arq/src/test/java/org/apache/jena/atlas/data/TestDistinctDataBag.java
+++ b/jena-arq/src/test/java/org/apache/jena/atlas/data/TestDistinctDataBag.java
@@ -24,19 +24,22 @@ import static org.junit.Assert.assertTrue;
 import java.io.File ;
 import java.util.*;
 
+import org.junit.Test ;
+
 import org.apache.jena.atlas.iterator.Iter ;
 import org.apache.jena.datatypes.xsd.XSDDatatype ;
 import org.apache.jena.graph.NodeFactory ;
 import org.apache.jena.query.SortCondition ;
 import org.apache.jena.sparql.core.Var ;
-import org.apache.jena.sparql.engine.binding.*;
-import org.apache.jena.sparql.resultset.ResultSetCompare ;
+import org.apache.jena.sparql.engine.binding.Binding;
+import org.apache.jena.sparql.engine.binding.BindingBuilder;
+import org.apache.jena.sparql.engine.binding.BindingComparator;
+import org.apache.jena.sparql.engine.binding.BindingFactory;
+import org.apache.jena.sparql.resultset.ResultsCompare;
 import org.apache.jena.sparql.sse.Item ;
 import org.apache.jena.sparql.sse.SSE ;
 import org.apache.jena.sparql.sse.builders.BuilderBinding ;
 import org.apache.jena.sparql.system.SerializationFactoryFinder;
-import org.apache.jena.sparql.util.NodeUtils ;
-import org.junit.Test ;
 
 public class TestDistinctDataBag
 {
@@ -51,8 +54,7 @@ public class TestDistinctDataBag
     static Binding bb1 = build("(?a _:XYZ) (?b 1)");
     static Binding x10 = build("(?x <http://example/abc>)") ;
 
-    @Test public void testDistinct()
-    {
+    @Test public void testDistinct() {
         List<Binding> undistinct = new ArrayList<>();
         undistinct.add(b12);
         undistinct.add(b19);
@@ -71,55 +73,44 @@ public class TestDistinctDataBag
                 new ThresholdPolicyCount<Binding>(2),
                 SerializationFactoryFinder.bindingSerializationFactory(),
                 new BindingComparator(new ArrayList<SortCondition>()));
-        try
-        {
+        try {
             db.addAll(undistinct);
 
             Iterator<Binding> iter = db.iterator();
-            while (iter.hasNext())
-            {
+            while (iter.hasNext()) {
                 distinct.add(iter.next());
             }
             Iter.close(iter);
-        }
-        finally
-        {
+        } finally {
             db.close();
         }
 
         assertEquals(control.size(), distinct.size());
-        assertTrue(ResultSetCompare.equalsByTest(control, distinct, NodeUtils.sameNode));
+        assertTrue(ResultsCompare.equalsExact(control, distinct));
     }
 
-    @Test public void testTemporaryFilesAreCleanedUpAfterCompletion()
-    {
+    @Test
+    public void testTemporaryFilesAreCleanedUpAfterCompletion() {
         List<Binding> undistinct = new ArrayList<>();
         random = new Random();
-        Var[] vars = new Var[]{
-            Var.alloc("1"), Var.alloc("2"), Var.alloc("3"),
-            Var.alloc("4"), Var.alloc("5"), Var.alloc("6"),
-            Var.alloc("7"), Var.alloc("8"), Var.alloc("9"), Var.alloc("0")
-        };
-        for(int i = 0; i < 500; i++){
+        Var[] vars = new Var[]{Var.alloc("1"), Var.alloc("2"), Var.alloc("3"), Var.alloc("4"), Var.alloc("5"), Var.alloc("6"),
+            Var.alloc("7"), Var.alloc("8"), Var.alloc("9"), Var.alloc("0")};
+        for ( int i = 0 ; i < 500 ; i++ ) {
             undistinct.add(randomBinding(vars));
         }
 
-        DistinctDataBag<Binding> db = new DistinctDataBag<>(
-                new ThresholdPolicyCount<Binding>(10),
-                SerializationFactoryFinder.bindingSerializationFactory(),
-                new BindingComparator(new ArrayList<SortCondition>()));
+        DistinctDataBag<Binding> db = new DistinctDataBag<>(new ThresholdPolicyCount<Binding>(10),
+                                                            SerializationFactoryFinder.bindingSerializationFactory(),
+                                                            new BindingComparator(new ArrayList<SortCondition>()));
 
         List<File> spillFiles = new ArrayList<>();
-        try
-        {
+        try {
             db.addAll(undistinct);
             spillFiles.addAll(db.getSpillFiles());
 
             int count = 0;
-            for (File file : spillFiles)
-            {
-                if (file.exists())
-                {
+            for ( File file : spillFiles ) {
+                if ( file.exists() ) {
                     count++;
                 }
             }
@@ -127,36 +118,29 @@ public class TestDistinctDataBag
             assertEquals(49, count);
 
             Iterator<Binding> iter = db.iterator();
-            while (iter.hasNext())
-            {
+            while (iter.hasNext()) {
                 iter.next();
             }
             Iter.close(iter);
-        }
-        finally
-        {
+        } finally {
             db.close();
         }
 
         int count = 0;
-        for (File file : spillFiles)
-        {
-            if (file.exists())
-            {
+        for ( File file : spillFiles ) {
+            if ( file.exists() ) {
                 count++;
             }
         }
         assertEquals(0, count);
     }
 
-    private static Binding build(String string)
-    {
-        Item item = SSE.parse("(binding "+string+")") ;
-        return BuilderBinding.build(item) ;
+    private static Binding build(String string) {
+        Item item = SSE.parse("(binding " + string + ")");
+        return BuilderBinding.build(item);
     }
 
-    private Binding randomBinding(Var[] vars)
-    {
+    private Binding randomBinding(Var[] vars) {
         BindingBuilder builder = Binding.builder();
         builder.add(vars[0], NodeFactory.createBlankNode());
         builder.add(vars[1], NodeFactory.createURI(randomURI()));
@@ -171,15 +155,13 @@ public class TestDistinctDataBag
         return builder.build();
     }
 
-    public String randomURI()
-    {
+    public String randomURI() {
         return String.format("http://%s.example.com/%s", randomString(10), randomString(10));
     }
 
-    public String randomString(int length)
-    {
+    public String randomString(int length) {
         StringBuilder builder = new StringBuilder();
-        for(int i = 0; i < length; i++){
+        for ( int i = 0 ; i < length ; i++ ) {
             builder.append(LETTERS.charAt(random.nextInt(LETTERS.length())));
         }
         return builder.toString();
@@ -215,6 +197,6 @@ public class TestDistinctDataBag
         }
 
         assertEquals(control.size(), distinct.size());
-        assertTrue(ResultSetCompare.equalsByTest(control, distinct, NodeUtils.sameNode));
+        assertTrue(ResultsCompare.equalsExact(control, distinct));
     }
 }
diff --git a/jena-arq/src/test/java/org/apache/jena/atlas/data/TestDistinctDataNet.java b/jena-arq/src/test/java/org/apache/jena/atlas/data/TestDistinctDataNet.java
index a84610195d..09c267935f 100644
--- a/jena-arq/src/test/java/org/apache/jena/atlas/data/TestDistinctDataNet.java
+++ b/jena-arq/src/test/java/org/apache/jena/atlas/data/TestDistinctDataNet.java
@@ -24,6 +24,8 @@ import static org.junit.Assert.assertTrue;
 import java.io.File ;
 import java.util.*;
 
+import org.junit.Test ;
+
 import org.apache.jena.atlas.iterator.Iter ;
 import org.apache.jena.datatypes.xsd.XSDDatatype ;
 import org.apache.jena.graph.NodeFactory ;
@@ -32,13 +34,11 @@ import org.apache.jena.sparql.core.Var ;
 import org.apache.jena.sparql.engine.binding.Binding ;
 import org.apache.jena.sparql.engine.binding.BindingBuilder;
 import org.apache.jena.sparql.engine.binding.BindingComparator ;
-import org.apache.jena.sparql.resultset.ResultSetCompare ;
+import org.apache.jena.sparql.resultset.ResultsCompare;
 import org.apache.jena.sparql.sse.Item ;
 import org.apache.jena.sparql.sse.SSE ;
 import org.apache.jena.sparql.sse.builders.BuilderBinding ;
 import org.apache.jena.sparql.system.SerializationFactoryFinder;
-import org.apache.jena.sparql.util.NodeUtils ;
-import org.junit.Test ;
 
 public class TestDistinctDataNet
 {
@@ -54,8 +54,7 @@ public class TestDistinctDataNet
     static Binding x10 = build("(?x <http://example/abc>)") ;
 
     @Test
-    public void testDistinct()
-    {
+    public void testDistinct() {
         List<Binding> undistinct = new ArrayList<>();
         undistinct.add(b12);
         undistinct.add(b19);
@@ -74,29 +73,24 @@ public class TestDistinctDataNet
                 new ThresholdPolicyCount<Binding>(2),
                 SerializationFactoryFinder.bindingSerializationFactory(),
                 new BindingComparator(new ArrayList<SortCondition>()));
-        try
-        {
+        try {
             db.addAll(undistinct);
 
             Iterator<Binding> iter = db.iterator();
-            while (iter.hasNext())
-            {
+            while (iter.hasNext()) {
                 distinct.add(iter.next());
             }
             Iter.close(iter);
-        }
-        finally
-        {
+        } finally {
             db.close();
         }
 
         assertEquals(control.size(), distinct.size());
-        assertTrue(ResultSetCompare.equalsByTest(control, distinct, NodeUtils.sameNode));
+        assertTrue(ResultsCompare.equalsExact(control, distinct));
     }
 
     @Test
-    public void testDistinct2()
-    {
+    public void testDistinct2() {
         List<Binding> undistinct = new ArrayList<>();
         undistinct.add(b12);
         undistinct.add(b19);
@@ -110,67 +104,51 @@ public class TestDistinctDataNet
         List<Binding> control = Iter.toList(Iter.distinct(undistinct.iterator()));
         List<Binding> distinct = new ArrayList<>();
 
-
-        DistinctDataNet<Binding> db = new DistinctDataNet<>(
-                new ThresholdPolicyCount<Binding>(2),
-                SerializationFactoryFinder.bindingSerializationFactory(),
-                new BindingComparator(new ArrayList<SortCondition>()));
-        try
-        {
-            for (Binding b : undistinct)
-            {
-                if (db.netAdd(b))
-                {
+        DistinctDataNet<Binding> db = new DistinctDataNet<>(new ThresholdPolicyCount<Binding>(2),
+                                                            SerializationFactoryFinder.bindingSerializationFactory(),
+                                                            new BindingComparator(new ArrayList<SortCondition>()));
+        try {
+            for ( Binding b : undistinct ) {
+                if ( db.netAdd(b) ) {
                     distinct.add(b);
                 }
             }
 
             Iterator<Binding> iter = db.netIterator();
-            while (iter.hasNext())
-            {
+            while (iter.hasNext()) {
                 distinct.add(iter.next());
             }
             Iter.close(iter);
-        }
-        finally
-        {
+        } finally {
             db.close();
         }
 
         assertEquals(control.size(), distinct.size());
-        assertTrue(ResultSetCompare.equalsByTest(control, distinct, NodeUtils.sameNode));
+        assertTrue(ResultsCompare.equalsExact(control, distinct));
     }
 
     @Test
-    public void testTemporaryFilesAreCleanedUpAfterCompletion()
-    {
+    public void testTemporaryFilesAreCleanedUpAfterCompletion() {
         List<Binding> undistinct = new ArrayList<>();
         random = new Random();
-        Var[] vars = new Var[]{
-            Var.alloc("1"), Var.alloc("2"), Var.alloc("3"),
-            Var.alloc("4"), Var.alloc("5"), Var.alloc("6"),
-            Var.alloc("7"), Var.alloc("8"), Var.alloc("9"), Var.alloc("0")
-        };
-        for(int i = 0; i < 500; i++){
+        Var[] vars = new Var[]{Var.alloc("1"), Var.alloc("2"), Var.alloc("3"), Var.alloc("4"), Var.alloc("5"), Var.alloc("6"),
+            Var.alloc("7"), Var.alloc("8"), Var.alloc("9"), Var.alloc("0")};
+        for ( int i = 0 ; i < 500 ; i++ ) {
             undistinct.add(randomBinding(vars));
         }
 
-        DistinctDataNet<Binding> db = new DistinctDataNet<>(
-                new ThresholdPolicyCount<Binding>(10),
-                SerializationFactoryFinder.bindingSerializationFactory(),
-                new BindingComparator(new ArrayList<SortCondition>()));
+        DistinctDataNet<Binding> db = new DistinctDataNet<>(new ThresholdPolicyCount<Binding>(10),
+                                                            SerializationFactoryFinder.bindingSerializationFactory(),
+                                                            new BindingComparator(new ArrayList<SortCondition>()));
 
         List<File> spillFiles = new ArrayList<>();
-        try
-        {
+        try {
             db.addAll(undistinct);
             spillFiles.addAll(db.getSpillFiles());
 
             int count = 0;
-            for (File file : spillFiles)
-            {
-                if (file.exists())
-                {
+            for ( File file : spillFiles ) {
+                if ( file.exists() ) {
                     count++;
                 }
             }
@@ -178,55 +156,42 @@ public class TestDistinctDataNet
             assertEquals(49, count);
 
             Iterator<Binding> iter = db.iterator();
-            while (iter.hasNext())
-            {
+            while (iter.hasNext()) {
                 iter.next();
             }
             Iter.close(iter);
-        }
-        finally
-        {
+        } finally {
             db.close();
         }
 
         int count = 0;
-        for (File file : spillFiles)
-        {
-            if (file.exists())
-            {
+        for ( File file : spillFiles ) {
+            if ( file.exists() ) {
                 count++;
             }
         }
         assertEquals(0, count);
     }
 
-    private void testDiff(String first, String second, String expected)
-    {
-        DistinctDataNet.SortedDiffIterator.create(
-                Arrays.asList(first.split(" ")).iterator(),
-                Arrays.asList(second.split(" ")).iterator());
+    private void testDiff(String first, String second, String expected) {
+        DistinctDataNet.SortedDiffIterator.create(Arrays.asList(first.split(" ")).iterator(), Arrays.asList(second.split(" ")).iterator());
 
     }
 
-    private void testDiff(String[] first, String[] second, String expected)
-    {
-        DistinctDataNet.SortedDiffIterator<String> sdi = DistinctDataNet.SortedDiffIterator.create(
-                Arrays.asList(first).iterator(),
-                Arrays.asList(second).iterator());
+    private void testDiff(String[] first, String[] second, String expected) {
+        DistinctDataNet.SortedDiffIterator<String> sdi = DistinctDataNet.SortedDiffIterator.create(Arrays.asList(first).iterator(),
+                                                                                                   Arrays.asList(second).iterator());
 
         StringBuilder sb = new StringBuilder();
         boolean firstTime = true;
-        while (sdi.hasNext())
-        {
-            if (!firstTime)
-            {
+        while (sdi.hasNext()) {
+            if ( !firstTime ) {
                 sb.append(" ");
             }
             firstTime = false;
 
             String s = sdi.next();
-            if (null == s)
-            {
+            if ( null == s ) {
                 s = "null";
             }
             sb.append(s);
@@ -254,14 +219,12 @@ public class TestDistinctDataNet
     }
 
 
-    private static Binding build(String string)
-    {
-        Item item = SSE.parse("(binding "+string+")") ;
-        return BuilderBinding.build(item) ;
+    private static Binding build(String string) {
+        Item item = SSE.parse("(binding " + string + ")");
+        return BuilderBinding.build(item);
     }
 
-    private Binding randomBinding(Var[] vars)
-    {
+    private Binding randomBinding(Var[] vars) {
         BindingBuilder builder = Binding.builder();
         builder.add(vars[0], NodeFactory.createBlankNode());
         builder.add(vars[1], NodeFactory.createURI(randomURI()));
@@ -276,15 +239,13 @@ public class TestDistinctDataNet
         return builder.build();
     }
 
-    public String randomURI()
-    {
+    public String randomURI() {
         return String.format("http://%s.example.com/%s", randomString(10), randomString(10));
     }
 
-    public String randomString(int length)
-    {
+    public String randomString(int length) {
         StringBuilder builder = new StringBuilder();
-        for(int i = 0; i < length; i++){
+        for ( int i = 0 ; i < length ; i++ ) {
             builder.append(LETTERS.charAt(random.nextInt(LETTERS.length())));
         }
         return builder.toString();
diff --git a/jena-arq/src/test/java/org/apache/jena/atlas/data/TestSortedDataBag.java b/jena-arq/src/test/java/org/apache/jena/atlas/data/TestSortedDataBag.java
index 662a6a4269..68aa27bf02 100644
--- a/jena-arq/src/test/java/org/apache/jena/atlas/data/TestSortedDataBag.java
+++ b/jena-arq/src/test/java/org/apache/jena/atlas/data/TestSortedDataBag.java
@@ -42,13 +42,12 @@ public class TestSortedDataBag
     private static final String LETTERS = "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM";
     private Random random;
 
-    @Test public void testSorting()
-    {
+    @Test
+    public void testSorting() {
         testSorting(500, 10);
     }
 
-    private void testSorting(int numBindings, int threshold)
-    {
+    private void testSorting(int numBindings, int threshold) {
         List<Binding> unsorted = randomBindings(numBindings);
 
         List<SortCondition> conditions = new ArrayList<>();
@@ -59,22 +58,16 @@ public class TestSortedDataBag
 
         List<Binding> sorted = new ArrayList<>();
 
-        SortedDataBag<Binding> db = new SortedDataBag<>(
-                new ThresholdPolicyCount<Binding>(threshold),
-                SerializationFactoryFinder.bindingSerializationFactory(),
-                comparator);
-        try
-        {
+        SortedDataBag<Binding> db = new SortedDataBag<>(new ThresholdPolicyCount<Binding>(threshold),
+                                                        SerializationFactoryFinder.bindingSerializationFactory(), comparator);
+        try {
             db.addAll(unsorted);
             Iterator<Binding> iter = db.iterator();
-            while (iter.hasNext())
-            {
+            while (iter.hasNext()) {
                 sorted.add(iter.next());
             }
             Iter.close(iter);
-        }
-        finally
-        {
+        } finally {
             db.close();
         }
 
@@ -82,13 +75,13 @@ public class TestSortedDataBag
         assertEquals(unsorted, sorted);
     }
 
-    @Test public void testSortingWithPreMerge()
-    {
+    @Test
+    public void testSortingWithPreMerge() {
         // Save the original value...
         int origMaxSpillFiles = SortedDataBag.MAX_SPILL_FILES;
-        try
-        {
+        try {
             // Vary the number of spill files and bindings so we get a variable number of premerge rounds
+            // @formatter:off
             SortedDataBag.MAX_SPILL_FILES = 2;    testSorting(1, 1);
             SortedDataBag.MAX_SPILL_FILES = 2;    testSorting(2, 1);
             SortedDataBag.MAX_SPILL_FILES = 2;    testSorting(3, 1);
@@ -99,37 +92,31 @@ public class TestSortedDataBag
             SortedDataBag.MAX_SPILL_FILES = 100;  testSorting(1000, 10);
             SortedDataBag.MAX_SPILL_FILES = 2;    testSorting(10, 10);
             SortedDataBag.MAX_SPILL_FILES = 5;    testSorting(10, 10);
-        }
-        finally
-        {
+            // @formatter:on
+        } finally {
             SortedDataBag.MAX_SPILL_FILES = origMaxSpillFiles;
         }
     }
 
-    @Test public void testTemporaryFilesAreCleanedUpAfterCompletion()
-    {
+    @Test
+    public void testTemporaryFilesAreCleanedUpAfterCompletion() {
         List<Binding> unsorted = randomBindings(500);
 
         List<SortCondition> conditions = new ArrayList<>();
         conditions.add(new SortCondition(new ExprVar("8"), Query.ORDER_ASCENDING));
         BindingComparator comparator = new BindingComparator(conditions);
 
-        SortedDataBag<Binding> db = new SortedDataBag<>(
-                new ThresholdPolicyCount<Binding>(10),
-                SerializationFactoryFinder.bindingSerializationFactory(),
-                comparator);
+        SortedDataBag<Binding> db = new SortedDataBag<>(new ThresholdPolicyCount<Binding>(10),
+                                                        SerializationFactoryFinder.bindingSerializationFactory(), comparator);
 
         List<File> spillFiles = new ArrayList<>();
-        try
-        {
+        try {
             db.addAll(unsorted);
             spillFiles.addAll(db.getSpillFiles());
 
             int count = 0;
-            for (File file : spillFiles)
-            {
-                if (file.exists())
-                {
+            for ( File file : spillFiles ) {
+                if ( file.exists() ) {
                     count++;
                 }
             }
@@ -137,46 +124,36 @@ public class TestSortedDataBag
             assertEquals(49, count);
 
             Iterator<Binding> iter = db.iterator();
-            while (iter.hasNext())
-            {
+            while (iter.hasNext()) {
                 iter.next();
             }
             Iter.close(iter);
-        }
-        finally
-        {
+        } finally {
             db.close();
         }
 
         int count = 0;
-        for (File file : spillFiles)
-        {
-            if (file.exists())
-            {
+        for ( File file : spillFiles ) {
+            if ( file.exists() ) {
                 count++;
             }
         }
         assertEquals(0, count);
     }
 
-    private List<Binding> randomBindings(int numBindings)
-    {
+    private List<Binding> randomBindings(int numBindings) {
         random = new Random();
-        Var[] vars = new Var[]{
-            Var.alloc("1"), Var.alloc("2"), Var.alloc("3"),
-            Var.alloc("4"), Var.alloc("5"), Var.alloc("6"),
-            Var.alloc("7"), Var.alloc("8"), Var.alloc("9"), Var.alloc("0")
-        };
+        Var[] vars = new Var[]{Var.alloc("1"), Var.alloc("2"), Var.alloc("3"), Var.alloc("4"), Var.alloc("5"), Var.alloc("6"),
+            Var.alloc("7"), Var.alloc("8"), Var.alloc("9"), Var.alloc("0")};
         List<Binding> toReturn = new ArrayList<>();
-        for(int i = 0; i < numBindings; i++){
+        for ( int i = 0 ; i < numBindings ; i++ ) {
             toReturn.add(randomBinding(vars));
         }
 
         return toReturn;
     }
 
-    private Binding randomBinding(Var[] vars)
-    {
+    private Binding randomBinding(Var[] vars) {
         BindingBuilder builder = Binding.builder();
         builder.add(vars[0], NodeFactory.createBlankNode());
         builder.add(vars[1], NodeFactory.createURI(randomURI()));
@@ -191,30 +168,26 @@ public class TestSortedDataBag
         return builder.build();
     }
 
-    private String randomURI()
-    {
+    private String randomURI() {
         return String.format("http://%s.example.com/%s", randomString(10), randomString(10));
     }
 
-    private String randomString(int length)
-    {
+    private String randomString(int length) {
         StringBuilder builder = new StringBuilder();
-        for(int i = 0; i < length; i++){
+        for ( int i = 0 ; i < length ; i++ ) {
             builder.append(LETTERS.charAt(random.nextInt(LETTERS.length())));
         }
         return builder.toString();
     }
 
-    private void getNextAndExpectException(QueryIterator iter)
-    {
-        try{
+    private void getNextAndExpectException(QueryIterator iter) {
+        try {
             iter.hasNext();
             fail("Expected an exception here");
-        }catch(QueryCancelledException e){
+        } catch (QueryCancelledException e) {
             // expected
-        }catch(Exception e){
+        } catch (Exception e) {
             fail("Unexpected exception");
         }
     }
-
 }
diff --git a/jena-arq/src/test/java/org/apache/jena/atlas/data/TestThresholdPolicyCount.java b/jena-arq/src/test/java/org/apache/jena/atlas/data/TestThresholdPolicyCount.java
index 1c6d5dd325..c45f398c3a 100644
--- a/jena-arq/src/test/java/org/apache/jena/atlas/data/TestThresholdPolicyCount.java
+++ b/jena-arq/src/test/java/org/apache/jena/atlas/data/TestThresholdPolicyCount.java
@@ -21,29 +21,24 @@ package org.apache.jena.atlas.data;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
-import org.junit.Test ;
+import org.junit.Test;
 
+public class TestThresholdPolicyCount {
 
-public class TestThresholdPolicyCount
-{
-    
     @Test
-    public void count_01()
-    {
+    public void count_01() {
         ThresholdPolicyCount<String> policy = new ThresholdPolicyCount<>(0);
         assertTrue(policy.isThresholdExceeded());
     }
-    
+
     @Test
-    public void count_02()
-    {
+    public void count_02() {
         ThresholdPolicyCount<String> policy = new ThresholdPolicyCount<>(1);
         assertFalse(policy.isThresholdExceeded());
     }
-    
+
     @Test
-    public void count_03()
-    {
+    public void count_03() {
         ThresholdPolicyCount<String> policy = new ThresholdPolicyCount<>(1);
         assertFalse(policy.isThresholdExceeded());
         policy.increment("a");
diff --git a/jena-arq/src/test/java/org/apache/jena/riot/protobuf/TestProtobufResultSet.java b/jena-arq/src/test/java/org/apache/jena/riot/protobuf/TestProtobufResultSet.java
index 4f554154c0..5cef3bf5fe 100644
--- a/jena-arq/src/test/java/org/apache/jena/riot/protobuf/TestProtobufResultSet.java
+++ b/jena-arq/src/test/java/org/apache/jena/riot/protobuf/TestProtobufResultSet.java
@@ -33,7 +33,7 @@ import org.apache.jena.query.ResultSetFormatter;
 import org.apache.jena.query.ResultSetRewindable;
 import org.apache.jena.riot.resultset.ResultSetLang;
 import org.apache.jena.sparql.exec.RowSet;
-import org.apache.jena.sparql.resultset.ResultSetCompare;
+import org.apache.jena.sparql.resultset.ResultsCompare;
 import org.apache.jena.sparql.sse.Item;
 import org.apache.jena.sparql.sse.SSE;
 import org.apache.jena.sparql.sse.builders.BuilderRowSet;
@@ -75,7 +75,7 @@ public class TestProtobufResultSet {
         // not reordered
         r1.reset();
         rs2.reset() ;
-        assertFalse(ResultSetCompare.equalsByTermAndOrder(r1, rs2)) ;
+        assertFalse(ResultsCompare.equalsByTermAndOrder(r1, rs2)) ;
         rs2.reset() ;
     }
 
@@ -84,7 +84,7 @@ public class TestProtobufResultSet {
         // not reordered
         r2.reset();
         rs1.reset() ;
-        assertFalse(ResultSetCompare.equalsByTermAndOrder(r2, rs1)) ;
+        assertFalse(ResultsCompare.equalsByTermAndOrder(r2, rs1)) ;
         rs1.reset() ;
     }
 
@@ -98,7 +98,7 @@ public class TestProtobufResultSet {
         RowSet rs$ = ProtobufRDF.readRowSet(in) ;
         ResultSetRewindable resultSet2 = ResultSetFactory.makeRewindable(rs$) ;
         // Includes bnode labels.
-        ResultSetCompare.equalsExact(resultSet, resultSet2) ;
+        ResultsCompare.equalsExact(resultSet, resultSet2) ;
         resultSet.reset();
         resultSet2.reset();
         return resultSet2 ;
diff --git a/jena-arq/src/test/java/org/apache/jena/riot/resultset/TestResultSetIO.java b/jena-arq/src/test/java/org/apache/jena/riot/resultset/TestResultSetIO.java
index b3f465619d..4fe4f4f5b0 100644
--- a/jena-arq/src/test/java/org/apache/jena/riot/resultset/TestResultSetIO.java
+++ b/jena-arq/src/test/java/org/apache/jena/riot/resultset/TestResultSetIO.java
@@ -39,7 +39,7 @@ import org.apache.jena.query.ResultSetFactory ;
 import org.apache.jena.query.ResultSetRewindable ;
 import org.apache.jena.riot.Lang ;
 import org.apache.jena.riot.ResultSetMgr ;
-import org.apache.jena.sparql.resultset.ResultSetCompare ;
+import org.apache.jena.sparql.resultset.ResultsCompare ;
 import org.apache.jena.sparql.sse.SSE ;
 import org.apache.jena.sparql.sse.builders.BuilderRowSet;
 
@@ -97,7 +97,7 @@ public class TestResultSetIO {
         ResultSetRewindable rsw = ResultSetFactory.makeRewindable(rs) ;
         if ( ! lang.equals(RS_CSV) ) {
             // CSV is not faithful
-            assertTrue(ResultSetCompare.equalsByTerm(test_rs, rsw)) ;
+            assertTrue(ResultsCompare.equalsByTerm(test_rs, rsw)) ;
         }
 
         rsw.reset();
@@ -109,7 +109,7 @@ public class TestResultSetIO {
         rsw.reset();
         in = new ByteArrayInputStream(out2.toByteArray()) ;
         ResultSet rs2 = ResultSetMgr.read(in, lang) ;
-        assertTrue(ResultSetCompare.equalsByTerm(rsw, rs2)) ;
+        assertTrue(ResultsCompare.equalsByTerm(rsw, rs2)) ;
     }
 }
 
diff --git a/jena-arq/src/test/java/org/apache/jena/riot/thrift/TestThriftResultSet.java b/jena-arq/src/test/java/org/apache/jena/riot/thrift/TestThriftResultSet.java
index c878a96c87..867b87ff8e 100644
--- a/jena-arq/src/test/java/org/apache/jena/riot/thrift/TestThriftResultSet.java
+++ b/jena-arq/src/test/java/org/apache/jena/riot/thrift/TestThriftResultSet.java
@@ -33,7 +33,7 @@ import org.apache.jena.query.ResultSetFormatter;
 import org.apache.jena.query.ResultSetRewindable ;
 import org.apache.jena.riot.resultset.ResultSetLang;
 import org.apache.jena.sparql.exec.RowSet;
-import org.apache.jena.sparql.resultset.ResultSetCompare ;
+import org.apache.jena.sparql.resultset.ResultsCompare ;
 import org.apache.jena.sparql.sse.SSE ;
 import org.junit.Test ;
 
@@ -70,7 +70,7 @@ public class TestThriftResultSet {
         // not reordered
         r1.reset();
         rs2.reset() ;
-        assertFalse(ResultSetCompare.equalsByTermAndOrder(r1, rs2)) ;
+        assertFalse(ResultsCompare.equalsByTermAndOrder(r1, rs2)) ;
         rs2.reset() ;
     }
 
@@ -79,7 +79,7 @@ public class TestThriftResultSet {
         // not reordered
         r2.reset();
         rs1.reset() ;
-        assertFalse(ResultSetCompare.equalsByTermAndOrder(r2, rs1)) ;
+        assertFalse(ResultsCompare.equalsByTermAndOrder(r2, rs1)) ;
         rs1.reset() ;
     }
 
@@ -93,7 +93,7 @@ public class TestThriftResultSet {
         RowSet rs$ = ThriftRDF.readRowSet(in) ;
         ResultSetRewindable resultSet2 = ResultSetFactory.makeRewindable(rs$) ;
         // Includes bnode labels.
-        ResultSetCompare.equalsExact(resultSet, resultSet2) ;
+        ResultsCompare.equalsExact(resultSet, resultSet2) ;
         resultSet.reset();
         resultSet2.reset();
         return resultSet2 ;
diff --git a/jena-arq/src/test/java/org/apache/jena/sparql/AbstractTestAdditional.java b/jena-arq/src/test/java/org/apache/jena/sparql/AbstractTestAdditional.java
index 6e558d8364..3d24699111 100644
--- a/jena-arq/src/test/java/org/apache/jena/sparql/AbstractTestAdditional.java
+++ b/jena-arq/src/test/java/org/apache/jena/sparql/AbstractTestAdditional.java
@@ -27,7 +27,7 @@ import org.apache.jena.sparql.core.Quad;
 import org.apache.jena.sparql.exec.QueryExec;
 import org.apache.jena.sparql.exec.RowSetOps;
 import org.apache.jena.sparql.exec.RowSetRewindable;
-import org.apache.jena.sparql.resultset.ResultSetCompare;
+import org.apache.jena.sparql.resultset.ResultsCompare;
 import org.apache.jena.sparql.sse.SSE;
 import org.apache.jena.system.Txn;
 import org.junit.Test;
@@ -70,7 +70,7 @@ public abstract class AbstractTestAdditional {
     }
 
     private static void testRS(RowSetRewindable rsExpected, RowSetRewindable rsGot) {
-        boolean b = ResultSetCompare.equalsByTerm(rsExpected, rsGot);
+        boolean b = ResultsCompare.equalsByTerm(rsExpected, rsGot);
         if (! b ) {
             rsExpected.reset();
             rsGot.reset();
diff --git a/jena-arq/src/test/java/org/apache/jena/sparql/TC_ARQ.java b/jena-arq/src/test/java/org/apache/jena/sparql/TC_ARQ.java
index 4048ca23b9..6ef2916344 100644
--- a/jena-arq/src/test/java/org/apache/jena/sparql/TC_ARQ.java
+++ b/jena-arq/src/test/java/org/apache/jena/sparql/TC_ARQ.java
@@ -52,6 +52,7 @@ import org.apache.jena.sparql.syntax.TS_Syntax ;
 import org.apache.jena.sparql.transaction.TS_Transaction ;
 import org.apache.jena.sparql.util.TS_DyadicDatasetGraphs;
 import org.apache.jena.sparql.util.TS_Util ;
+import org.apache.jena.sparql.util.iso.TS_Iso ;
 import org.apache.jena.sparql.util.compose.TS_DatasetCollectors;
 import org.junit.AfterClass ;
 import org.junit.BeforeClass ;
@@ -67,6 +68,7 @@ import org.junit.runners.Suite ;
     , TS_DyadicDatasetGraphs.class
     , TS_DatasetCollectors.class
     , TS_Util.class
+    , TS_Iso.class
 
     , TS_Expr.class
     , TS_LibraryFunctions.class
@@ -97,7 +99,7 @@ import org.junit.runners.Suite ;
 
     // Outside org.apache.jena.sparql
     , TS_Query.class
-    
+
     // RDF 1.2 tests. These track the working group.
     , TS_RDF12.class
     // And scripted tests.
diff --git a/jena-arq/src/test/java/org/apache/jena/sparql/algebra/optimize/TestSemanticEquivalence.java b/jena-arq/src/test/java/org/apache/jena/sparql/algebra/optimize/TestSemanticEquivalence.java
index 159aff2697..f9a30565a0 100644
--- a/jena-arq/src/test/java/org/apache/jena/sparql/algebra/optimize/TestSemanticEquivalence.java
+++ b/jena-arq/src/test/java/org/apache/jena/sparql/algebra/optimize/TestSemanticEquivalence.java
@@ -34,7 +34,7 @@ import org.apache.jena.sparql.engine.QueryIterator ;
 import org.apache.jena.sparql.engine.ResultSetStream ;
 import org.apache.jena.sparql.engine.binding.BindingFactory ;
 import org.apache.jena.sparql.engine.main.QueryEngineMain ;
-import org.apache.jena.sparql.resultset.ResultSetCompare ;
+import org.apache.jena.sparql.resultset.ResultsCompare ;
 import org.apache.jena.sparql.sse.SSE ;
 import org.apache.jena.sparql.util.Symbol ;
 import org.junit.AfterClass ;
@@ -204,7 +204,7 @@ public class TestSemanticEquivalence {
                 }
                 Assert.assertEquals(expected, rsOpt.size());
             }
-            Assert.assertTrue(ResultSetCompare.isomorphic(rs, rsOpt));
+            Assert.assertTrue(ResultsCompare.equalsByTerm(rs, rsOpt));
         } finally {
             // Restore previous state
             if (isEnabled) {
@@ -268,7 +268,7 @@ public class TestSemanticEquivalence {
             Assert.assertEquals(expected, rsOpt.size());
             iterOpt.close();
 
-            Assert.assertTrue(ResultSetCompare.isomorphic(rs, rsOpt));
+            Assert.assertTrue(ResultsCompare.equalsByTerm(rs, rsOpt));
         } finally {
             // Restore previous state
             if (isEnabled) {
diff --git a/jena-arq/src/test/java/org/apache/jena/sparql/engine/binding/TestBindingStreams.java b/jena-arq/src/test/java/org/apache/jena/sparql/engine/binding/TestBindingStreams.java
index 69d787d8c6..8f15f9bf90 100644
--- a/jena-arq/src/test/java/org/apache/jena/sparql/engine/binding/TestBindingStreams.java
+++ b/jena-arq/src/test/java/org/apache/jena/sparql/engine/binding/TestBindingStreams.java
@@ -26,6 +26,10 @@ import java.io.ByteArrayOutputStream ;
 import java.util.ArrayList ;
 import java.util.List ;
 
+import org.junit.AfterClass ;
+import org.junit.BeforeClass ;
+import org.junit.Test ;
+
 import org.apache.jena.atlas.legacy.BaseTest2 ;
 import org.apache.jena.graph.Node ;
 import org.apache.jena.graph.NodeFactory ;
@@ -36,15 +40,9 @@ import org.apache.jena.riot.tokens.Tokenizer ;
 import org.apache.jena.riot.tokens.TokenizerText;
 import org.apache.jena.sparql.core.Var ;
 import org.apache.jena.sparql.graph.NodeConst ;
-import org.apache.jena.sparql.resultset.ResultSetCompare ;
 import org.apache.jena.sparql.sse.Item ;
 import org.apache.jena.sparql.sse.SSE ;
 import org.apache.jena.sparql.sse.builders.BuilderBinding ;
-import org.apache.jena.sparql.util.NodeUtils ;
-import org.junit.AfterClass ;
-import org.junit.BeforeClass ;
-import org.junit.Test ;
-
 
 public class TestBindingStreams
 {
@@ -106,82 +104,70 @@ public class TestBindingStreams
     @Test public void bindingStream_60()              { testWriteRead(bb1) ; }
 
     @Test
-    public void bindingStream_61()
-    {
-        Node bn = NodeFactory.createBlankNode("unusual") ;
-        Binding b = BindingFactory.binding(Var.alloc("v"), bn) ;
-        testWriteRead(b) ;
+    public void bindingStream_61() {
+        Node bn = NodeFactory.createBlankNode("unusual");
+        Binding b = BindingFactory.binding(Var.alloc("v"), bn);
+        testWriteRead(b);
     }
 
     @Test public void bindingStream_62()              { testWriteRead(bb2) ; }
 
     @Test public void bindingStream_63()              { testWriteRead(bb3) ; }
 
+    static void testRead(String x, Binding...bindings) {
+        Tokenizer t = TokenizerText.create().fromString(x).build();;
+        BindingInputStream inStream = new BindingInputStream(t);
 
-    static void testRead(String x, Binding ... bindings)
-    {
-        Tokenizer t = TokenizerText.create().fromString(x).build(); ;
-        BindingInputStream inStream = new BindingInputStream(t) ;
-
-        if ( bindings.length == 0 )
-        {
+        if ( bindings.length == 0 ) {
             for ( ; inStream.hasNext() ; )
-                inStream.next() ;
-            return ;
+                inStream.next();
+            return;
         }
 
-        int i ;
-        for ( i = 0 ; inStream.hasNext() ; i++ )
-        {
-            Binding b = inStream.next() ;
-            assertTrue("Bindings do not match: expected="+bindings[i]+" got="+b, equalBindings(bindings[i], b)) ;
+        int i;
+        for ( i = 0 ; inStream.hasNext() ; i++ ) {
+            Binding b = inStream.next();
+            assertTrue("Bindings do not match: expected=" + bindings[i] + " got=" + b, equalBindings(bindings[i], b));
         }
 
-        assertEquals("Wrong length: expect= "+bindings.length+" got="+i,bindings.length, i) ;
+        assertEquals("Wrong length: expect= " + bindings.length + " got=" + i, bindings.length, i);
     }
 
     static void testWriteRead(Binding ... bindings) { testWriteRead(null, bindings) ; }
 
-    static void testWriteRead(PrefixMap prefixMap, Binding ... bindings)
-    {
-        ByteArrayOutputStream out = new ByteArrayOutputStream() ;
-        BindingOutputStream output = new BindingOutputStream(out, prefixMap) ;
+    static void testWriteRead(PrefixMap prefixMap, Binding...bindings) {
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        BindingOutputStream output = new BindingOutputStream(out, prefixMap);
 
         for ( Binding b : bindings )
-            output.write(b) ;
-        output.flush() ;
+            output.write(b);
+        output.flush();
 
         // When the going gets tough, the tough put in trace statements:
-        //System.out.println("T: \n"+out.toString()) ;
+        // System.out.println("T: \n"+out.toString()) ;
 
-        ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()) ;
-        BindingInputStream input = new BindingInputStream(in) ;
+        ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
+        BindingInputStream input = new BindingInputStream(in);
 
-        List<Binding> results = new ArrayList<>() ;
-        for ( ; input.hasNext() ; )
-        {
-            results.add(input.next()) ;
+        List<Binding> results = new ArrayList<>();
+        for ( ; input.hasNext() ; ) {
+            results.add(input.next());
         }
-        assertEquals(bindings.length, results.size()) ;
-        for ( int i = 0 ; i < bindings.length ; i++ )
-        {
-            Binding b1 = bindings[i] ;
-            Binding b2 = results.get(i) ;
-            assertTrue("Bindings do not match: expected="+b1+" got="+b2, equalBindings(b1, b2)) ;
+        assertEquals(bindings.length, results.size());
+        for ( int i = 0 ; i < bindings.length ; i++ ) {
+            Binding b1 = bindings[i];
+            Binding b2 = results.get(i);
+            assertTrue("Bindings do not match: expected=" + b1 + " got=" + b2, equalBindings(b1, b2));
         }
     }
 
-
-    private static boolean equalBindings(Binding binding1, Binding binding2)
-    {
-        // Need to have the exact same terms coming back (therefore we can't use BNodeIso to compare values)
-        return ResultSetCompare.equal(binding1, binding2, NodeUtils.sameNode) ;
+    private static boolean equalBindings(Binding binding1, Binding binding2) {
+        // Identical
+        return BindingLib.equals(binding1, binding2) ;
     }
 
-
-    private static Binding build(String string)
-    {
-        Item item = SSE.parse("(binding "+string+")") ;
-        return BuilderBinding.build(item) ;
+    private static Binding build(String string) {
+        Item item = SSE.parse("(binding " + string + ")");
+        return BuilderBinding.build(item);
     }
 }
diff --git a/jena-arq/src/test/java/org/apache/jena/sparql/engine/join/AbstractTestJoin.java b/jena-arq/src/test/java/org/apache/jena/sparql/engine/join/AbstractTestJoin.java
index 02c84d39f3..b04dfa1a13 100644
--- a/jena-arq/src/test/java/org/apache/jena/sparql/engine/join/AbstractTestJoin.java
+++ b/jena-arq/src/test/java/org/apache/jena/sparql/engine/join/AbstractTestJoin.java
@@ -34,7 +34,7 @@ import org.apache.jena.sparql.core.Var ;
 import org.apache.jena.sparql.engine.QueryIterator ;
 import org.apache.jena.sparql.engine.binding.Binding ;
 import org.apache.jena.sparql.expr.ExprList ;
-import org.apache.jena.sparql.resultset.ResultSetCompare ;
+import org.apache.jena.sparql.resultset.ResultsCompare ;
 import org.apache.jena.sparql.sse.SSE ;
 
 /** Tests for inner/equi joins */
@@ -340,7 +340,7 @@ public abstract class AbstractTestJoin {
     private static boolean equalTables(Table table1, Table table2) {
         ResultSet rs1 =  ResultSetFactory.create(table1.iterator(null), table1.getVarNames()) ;
         ResultSet rs2 =  ResultSetFactory.create(table2.iterator(null), table2.getVarNames()) ;
-        return ResultSetCompare.equalsByTerm(rs1, rs2) ;
+        return ResultsCompare.equalsByTerm(rs1, rs2) ;
     }
 }
 
diff --git a/jena-arq/src/test/java/org/apache/jena/sparql/engine/join/TestTableJoin.java b/jena-arq/src/test/java/org/apache/jena/sparql/engine/join/TestTableJoin.java
index 3b206254b1..77a6358073 100644
--- a/jena-arq/src/test/java/org/apache/jena/sparql/engine/join/TestTableJoin.java
+++ b/jena-arq/src/test/java/org/apache/jena/sparql/engine/join/TestTableJoin.java
@@ -35,7 +35,7 @@ import org.apache.jena.sparql.engine.ExecutionContext ;
 import org.apache.jena.sparql.engine.QueryIterator ;
 import org.apache.jena.sparql.engine.ref.TableJoin;
 import org.apache.jena.sparql.expr.ExprList ;
-import org.apache.jena.sparql.resultset.ResultSetCompare ;
+import org.apache.jena.sparql.resultset.ResultsCompare ;
 import org.apache.jena.sparql.sse.SSE ;
 
 /** Tests on tables and the simple nest loop join (TableJoin) used by the reference query engine */
@@ -115,7 +115,7 @@ public class TestTableJoin
         public static boolean equalsByTerm(Table table1, Table table2) {
             ResultSet rs1 = ResultSetFactory.create(table1.iterator(null), table1.getVarNames()) ;
             ResultSet rs2 = ResultSetFactory.create(table2.iterator(null), table2.getVarNames()) ;
-            return ResultSetCompare.equalsByTerm(rs1, rs2) ;
+            return ResultsCompare.equalsByTerm(rs1, rs2) ;
         }
     }
 
diff --git a/jena-arq/src/test/java/org/apache/jena/sparql/path/TestPathQuery.java b/jena-arq/src/test/java/org/apache/jena/sparql/path/TestPathQuery.java
index 26071d5dc0..26ce2c93ce 100644
--- a/jena-arq/src/test/java/org/apache/jena/sparql/path/TestPathQuery.java
+++ b/jena-arq/src/test/java/org/apache/jena/sparql/path/TestPathQuery.java
@@ -27,7 +27,8 @@ import org.apache.jena.query.QueryFactory;
 import org.apache.jena.riot.Lang;
 import org.apache.jena.riot.RDFParser;
 import org.apache.jena.sparql.exec.*;
-import org.apache.jena.sparql.resultset.ResultSetCompare;
+import org.apache.jena.sparql.resultset.ResultsCompare;
+
 import org.junit.Test;
 
 /** Path tests using queries. */
@@ -75,7 +76,7 @@ public class TestPathQuery {
     }
 
     @Test public void testPathByQuery_unboundEnds_05() {
-        String qsPath     = "PREFIX : <http://example/> SELECT * { ?s :p{1,3} ?o }";
+        String qsPath     = "PREFIX : <http://example/> SELECT ?s ?o { ?s :p{1,3} ?o }";
         // Same results for the test data, not in general.
         String qsExpected = "PREFIX : <http://example/> SELECT ?s ?o { { ?s :p ?o } UNION { ?s :p [:p ?o]} }";
         test(graph1, qsPath, qsExpected);
@@ -91,6 +92,7 @@ public class TestPathQuery {
             System.out.println(qsPath);
             print("Actual", rs1);
             print("Expected", rs2);
+            rowSetEquals(rs1, rs2);
         }
         assertTrue(same);
     }
@@ -102,7 +104,8 @@ public class TestPathQuery {
     }
 
     private boolean rowSetEquals(RowSetRewindable rowset1, RowSetRewindable rowset2) {
-        return ResultSetCompare.equalsByTerm(rowset1, rowset2);
+        // XXX [ISO] Use exact.
+        return ResultsCompare.equalsExact(rowset1, rowset2);
     }
 
     private void print(String label, RowSetRewindable rowset) {
diff --git a/jena-arq/src/test/java/org/apache/jena/sparql/resultset/TestResultSet.java b/jena-arq/src/test/java/org/apache/jena/sparql/resultset/TestResultSet.java
index 1f2cbd9450..5b2d9b0d56 100644
--- a/jena-arq/src/test/java/org/apache/jena/sparql/resultset/TestResultSet.java
+++ b/jena-arq/src/test/java/org/apache/jena/sparql/resultset/TestResultSet.java
@@ -82,7 +82,7 @@ public class TestResultSet {
 
         ByteArrayInputStream ins = new ByteArrayInputStream(arr.toByteArray());
         ResultSet rs2 = ResultSetFactory.fromXML(ins);
-        assertTrue(ResultSetCompare.equalsByTerm(rs1, rs2));
+        assertTrue(ResultsCompare.equalsByTerm(rs1, rs2));
     }
 
     @Test
@@ -92,7 +92,7 @@ public class TestResultSet {
         rs1.reset();
         InputStream in = new ByteArrayInputStream(StrUtils.asUTF8bytes(x));
         ResultSet rs2 = ResultSetFactory.fromXML(in);
-        assertTrue(ResultSetCompare.equalsByTerm(rs1, rs2));
+        assertTrue(ResultsCompare.equalsByTerm(rs1, rs2));
     }
 
     @Test
@@ -103,7 +103,7 @@ public class TestResultSet {
         rs1.reset();
         ByteArrayInputStream ins = new ByteArrayInputStream(arr.toByteArray());
         ResultSet rs2 = ResultSetFactory.fromXML(ins);
-        assertTrue(ResultSetCompare.equalsByTerm(rs1, rs2));
+        assertTrue(ResultsCompare.equalsByTerm(rs1, rs2));
     }
 
     @Test
@@ -113,7 +113,7 @@ public class TestResultSet {
         rs1.reset();
         InputStream in = new ByteArrayInputStream(StrUtils.asUTF8bytes(x));
         ResultSet rs2 = ResultSetFactory.fromXML(in);
-        assertTrue(ResultSetCompare.equalsByTerm(rs1, rs2));
+        assertTrue(ResultsCompare.equalsByTerm(rs1, rs2));
     }
 
     // RDF
@@ -124,7 +124,7 @@ public class TestResultSet {
         Model model = RDFOutput.encodeAsModel(rs1);
         rs1.reset();
         ResultSet rs2 = RDFInput.fromRDF(model);
-        assertTrue(ResultSetCompare.equalsByTerm(rs1, rs2));
+        assertTrue(ResultsCompare.equalsByTerm(rs1, rs2));
     }
 
     @Test
@@ -133,7 +133,7 @@ public class TestResultSet {
         Model model = RDFOutput.encodeAsModel(rs1);
         rs1.reset();
         ResultSetRewindable rs2 = ResultSetFactory.makeRewindable(RDFInput.fromRDF(model));
-        boolean b = ResultSetCompare.equalsByTerm(rs1, rs2);
+        boolean b = ResultsCompare.equalsByTerm(rs1, rs2);
         if ( !b ) {
             rs1.reset();
             rs2.reset();
@@ -154,7 +154,7 @@ public class TestResultSet {
         rs1.reset();
         ByteArrayInputStream ins = new ByteArrayInputStream(arr.toByteArray());
         ResultSet rs2 = ResultSetFactory.fromJSON(ins);
-        assertTrue(ResultSetCompare.equalsByTerm(rs1, rs2));
+        assertTrue(ResultsCompare.equalsByTerm(rs1, rs2));
     }
 
     @Test
@@ -166,7 +166,7 @@ public class TestResultSet {
         ByteArrayInputStream ins = new ByteArrayInputStream(arr.toByteArray());
         ResultSet rs2 = ResultSetFactory.fromJSON(ins);    // Test using the DAWG
                                                            // examples
-        assertTrue(ResultSetCompare.equalsByTerm(rs1, rs2));
+        assertTrue(ResultsCompare.equalsByTerm(rs1, rs2));
     }
 
     // Into some format.
@@ -225,7 +225,7 @@ public class TestResultSet {
         ResultSet rs1 = make("x", org.apache.jena.graph.NodeFactory.createURI("tag:local"));
         ResultSet rs2 = make("x", org.apache.jena.graph.NodeFactory.createURI("tag:local"));
         ResultSet rs3 = make2("x", org.apache.jena.graph.NodeFactory.createURI("tag:local"));
-        assertTrue(ResultSetCompare.equalsByTerm(rs3, ResultSetUtils.merge(rs1, rs2)));
+        assertTrue(ResultsCompare.equalsByTerm(rs3, ResultSetUtils.merge(rs1, rs2)));
     }
 
     private void test_RS_fmt(ResultSet rs, ResultsFormat fmt, boolean ordered) {
@@ -238,11 +238,11 @@ public class TestResultSet {
         ResultSetRewindable rs2 = ResultSetFactory.makeRewindable(ResultSetFactory.load(ins, fmt));
 
         // Ordered? Unordered?
-        boolean b = ResultSetCompare.equalsByTerm(rs1, rs2);
+        boolean b = ResultsCompare.equalsByTerm(rs1, rs2);
         if ( ordered ) {
             rs1.reset();
             rs2.reset();
-            b = b & ResultSetCompare.equalsByTerm(rs1, rs2);
+            b = b & ResultsCompare.equalsByTerm(rs1, rs2);
         }
 
         if ( !b ) {
@@ -261,17 +261,17 @@ public class TestResultSet {
     public void test_RS_cmp_1() {
         ResultSetRewindable rs1 = new ResultSetMem();
         ResultSetRewindable rs2 = new ResultSetMem();
-        assertTrue(ResultSetCompare.equalsByTerm(rs1, rs2));
+        assertTrue(ResultsCompare.equalsByTerm(rs1, rs2));
         rs1.reset();
         rs2.reset();
-        assertTrue(ResultSetCompare.equalsByTerm(rs1, rs2));
+        assertTrue(ResultsCompare.equalsByTerm(rs1, rs2));
     }
 
     @Test
     public void test_RS_cmp_2() {
         ResultSet rs1 = make("x", org.apache.jena.graph.NodeFactory.createURI("tag:local"));
         ResultSet rs2 = new ResultSetMem();
-        assertFalse(ResultSetCompare.equalsByTerm(rs1, rs2));
+        assertFalse(ResultsCompare.equalsByTerm(rs1, rs2));
 
     }
 
@@ -279,15 +279,15 @@ public class TestResultSet {
     public void test_RS_cmp_3() {
         ResultSet rs1 = make("x", org.apache.jena.graph.NodeFactory.createURI("tag:local"));
         ResultSet rs2 = new ResultSetMem();
-        assertFalse(ResultSetCompare.equalsByTerm(rs1, rs2));
+        assertFalse(ResultsCompare.equalsByTerm(rs1, rs2));
     }
 
     @Test
     public void test_RS_cmp_4() {
         ResultSet rs1 = make("x", org.apache.jena.graph.NodeFactory.createURI("tag:local"));
         ResultSet rs2 = make("x", org.apache.jena.graph.NodeFactory.createURI("tag:local"));
-        assertTrue(ResultSetCompare.equalsByTerm(rs1, rs2));
-        assertTrue(ResultSetCompare.equalsByTerm(rs1, rs2));
+        assertTrue(ResultsCompare.equalsByTerm(rs1, rs2));
+        assertTrue(ResultsCompare.equalsByTerm(rs1, rs2));
     }
 
     @Test
@@ -296,13 +296,13 @@ public class TestResultSet {
         ResultSetRewindable rs1 = makeRewindable("x", org.apache.jena.graph.NodeFactory.createURI("tag:local:1"));
         ResultSetRewindable rs2 = makeRewindable("x", org.apache.jena.graph.NodeFactory.createURI("tag:local:2"));
 
-        assertFalse(ResultSetCompare.equalsByTerm(rs1, rs2));
+        assertFalse(ResultsCompare.equalsByTerm(rs1, rs2));
         rs1.reset();
         rs2.reset();
 
         assertTrue(rs1.hasNext());
         assertTrue(rs2.hasNext());
-        assertFalse(ResultSetCompare.equalsByTerm(rs1, rs2));
+        assertFalse(ResultsCompare.equalsByTerm(rs1, rs2));
     }
 
     @Test
@@ -310,12 +310,12 @@ public class TestResultSet {
         // Different variable, same values
         ResultSetRewindable rs1 = makeRewindable("x", org.apache.jena.graph.NodeFactory.createURI("tag:local"));
         ResultSetRewindable rs2 = makeRewindable("y", org.apache.jena.graph.NodeFactory.createURI("tag:local"));
-        assertFalse(ResultSetCompare.equalsByTermAndOrder(rs1, rs2));
+        assertFalse(ResultsCompare.equalsByTermAndOrder(rs1, rs2));
         rs1.reset();
         rs2.reset();
         assertTrue(rs1.hasNext());
         assertTrue(rs2.hasNext());
-        assertFalse(ResultSetCompare.equalsByTerm(rs1, rs2));
+        assertFalse(ResultsCompare.equalsByTerm(rs1, rs2));
     }
 
     // Value based
@@ -323,8 +323,8 @@ public class TestResultSet {
     public void test_RS_cmp_value_1() {
         ResultSetRewindable rs1 = makeRewindable("x", NodeFactoryExtra.parseNode("123"));
         ResultSetRewindable rs2 = makeRewindable("x", NodeFactoryExtra.parseNode("0123"));
-        assertFalse(ResultSetCompare.equalsByTerm(rs1, rs2));
-        assertTrue(ResultSetCompare.equalsByValue(rs1, rs2));
+        assertFalse(ResultsCompare.equalsByTerm(rs1, rs2));
+        assertTrue(ResultsCompare.equalsByValue(rs1, rs2));
     }
 
     // Peeking
@@ -503,25 +503,19 @@ public class TestResultSet {
 
     // ---- Isomorphism.
 
-    /* This is from the DAWG test suite. Result set 1: --------------- | x | y |
-     * =============== | _:b0 | _:b1 | | _:b2 | _:b3 | | _:b1 | _:b0 |
-     * --------------- Result set 2: --------------- | x | y | =============== | _:b1
-     * | _:b0 | | _:b3 | _:b2 | | _:b2 | _:b3 | --------------- */
-
-    // nasty result set.
-    // These are the same but the first row of rs2$ throws in a wrong mapping of
-    // b0/c1
-
-    // Right mapping is:
-    // b0->c3, b1->c2, b2->c1, b3->c0
-    // Currently we get, working simply top to bottom, no backtracking:
-    // b0->c1, b1->c0, b2->c3, b3->c2, then last row fails as _:b1 is mapped to c0,
-    // b0 to c1 not (c2, c3)
-
-    private static String[] rs1$ = {"(resultset (?x ?y)", "   (row (?x _:b0) (?y _:b1))", "   (row (?x _:b2) (?y _:b3))",
-        "   (row (?x _:b1) (?y _:b0))", ")"};
-    private static String[] rs2$ = {"(resultset (?x ?y)", "   (row (?x _:c1) (?y _:c0))", "   (row (?x _:c3) (?y _:c2))",
-        "   (row (?x _:c2) (?y _:c3))", ")"};
+    private static String[] rs1$ = {
+        "(resultset (?x ?y)",
+        "   (row (?x _:b0) (?y _:b1))",
+        "   (row (?x _:b2) (?y _:b3))",
+        "   (row (?x _:b1) (?y _:b0))",
+        ")"
+    };
+    private static String[] rs2$ = {
+        "(resultset (?x ?y)",
+        "   (row (?x _:c1) (?y _:c0))",
+        "   (row (?x _:c3) (?y _:c2))",
+        "   (row (?x _:c2) (?y _:c3))",
+        ")"};
 
     @Test
     public void test_RS_iso_1() {
@@ -531,14 +525,10 @@ public class TestResultSet {
     private void isotest(String[] rs1$2, String[] rs2$2) {
         ResultSetRewindable rs1 = make(StrUtils.strjoinNL(rs1$));
         ResultSetRewindable rs2 = make(StrUtils.strjoinNL(rs2$));
-
-        assertTrue(ResultSetCompare.isomorphic(rs1, rs2));
+        assertTrue(ResultsCompare.equalsByTerm(rs1, rs2));
         rs1.reset();
         rs2.reset();
-        assertTrue(ResultSetCompare.equalsByTerm(rs1, rs2));
-        rs1.reset();
-        rs2.reset();
-        assertTrue(ResultSetCompare.equalsByValue(rs1, rs2));
+        assertTrue(ResultsCompare.equalsByValue(rs1, rs2));
     }
 
     private static ResultSetRewindable make(String x) {
@@ -580,12 +570,14 @@ public class TestResultSet {
         ByteArrayInputStream y = new ByteArrayInputStream(x.toByteArray());
 
         ResultSetRewindable rs2 = ResultSetFactory.copyResults(ResultsReader.create().context(cxt).lang(sparqlresultlang).read(y));
+
         rs1.reset();
         rs2.reset();
+
         if ( same )
-            assertTrue(ResultSetCompare.equalsExact(rs1, rs2));
+            assertTrue(ResultsCompare.equalsExact(rs1, rs2));
         else
-            assertFalse(ResultSetCompare.equalsExact(rs1, rs2));
+            assertFalse(ResultsCompare.equalsExact(rs1, rs2));
     }
 
     // -------- Support functions
diff --git a/jena-arq/src/test/java/org/apache/jena/sparql/resultset/TestResultSetFormat1.java b/jena-arq/src/test/java/org/apache/jena/sparql/resultset/TestResultSetFormat1.java
index 707f195a7f..2bb6fd23a3 100644
--- a/jena-arq/src/test/java/org/apache/jena/sparql/resultset/TestResultSetFormat1.java
+++ b/jena-arq/src/test/java/org/apache/jena/sparql/resultset/TestResultSetFormat1.java
@@ -112,81 +112,72 @@ public class TestResultSetFormat1
     	")" } ;
 
     @Parameters
-    public static Collection<Object[]> data()
-    {
+    public static Collection<Object[]> data() {
         return Arrays.asList(new Object[][] { {$rs0}, {$rs1}, {$rs2}, {$rs3}, {$rs4}, {$rs5}, {$rs6}, {$rs7}, {$rs8}, {$rs9}, {$rs10}, {$rs11} } ) ;
     }
 
     private final String[] $rs ;
 
-    public TestResultSetFormat1(String[] rs)
-    {
-        this.$rs = rs ;
+    public TestResultSetFormat1(String[] rs) {
+        this.$rs = rs;
     }
 
-    static ResultSet make(String... strings)
-    {
+    static ResultSet make(String...strings) {
         if ( strings.length == 0 )
-            throw new IllegalArgumentException() ;
+            throw new IllegalArgumentException();
 
-        String x = StrUtils.strjoinNL(strings) ;
-        Item item = SSE.parse(x) ;
+        String x = StrUtils.strjoinNL(strings);
+        Item item = SSE.parse(x);
         return ResultSetFactory.makeRewindable(BuilderRowSet.build(item));
     }
 
-    @Test public void resultset_01()
-    {
-        ResultSet rs = make($rs) ;
-        ResultSetFormatter.asText(rs) ;
+    @Test
+    public void resultset_01() {
+        ResultSet rs = make($rs);
+        ResultSetFormatter.asText(rs);
     }
 
-    @Test public void resultset_02()
-    {
-        ResultSet rs = make($rs) ;
-        ByteArrayOutputStream out = new ByteArrayOutputStream() ;
-        ResultSetFormatter.outputAsXML(out, rs) ;
-        ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()) ;
-        ResultSet rs2 = ResultSetFactory.fromXML(in) ;
+    @Test
+    public void resultset_02() {
+        ResultSet rs = make($rs);
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        ResultSetFormatter.outputAsXML(out, rs);
+        ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
+        ResultSet rs2 = ResultSetFactory.fromXML(in);
         checkIsomorphic(rs, rs2);
     }
 
-    @Test public void resultset_03()
-    {
-        ResultSet rs = make($rs) ;
-        ByteArrayOutputStream out = new ByteArrayOutputStream() ;
-        ResultSetFormatter.outputAsJSON(out, rs) ;
-        ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()) ;
-        ResultSet rs2 = ResultSetFactory.fromJSON(in) ;
+    @Test
+    public void resultset_03() {
+        ResultSet rs = make($rs);
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        ResultSetFormatter.outputAsJSON(out, rs);
+        ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
+        ResultSet rs2 = ResultSetFactory.fromJSON(in);
         checkIsomorphic(rs, rs2);
     }
 
-    @Test public void resultset_04()
-    {
-        ResultSet rs = make($rs) ;
-        ByteArrayOutputStream out = new ByteArrayOutputStream() ;
-        ResultSetFormatter.outputAsTSV(out, rs) ;
-        ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()) ;
+    @Test
+    public void resultset_04() {
+        ResultSet rs = make($rs);
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        ResultSetFormatter.outputAsTSV(out, rs);
+        ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
         ResultSet rs2 = ResultSetMgr.read(in, ResultSetLang.RS_TSV);
         checkIsomorphic(rs, rs2);
     }
 
-    @Test public void resultset_05()
-    {
-        ResultSet rs = make($rs) ;
-        ByteArrayOutputStream out = new ByteArrayOutputStream() ;
-        ResultSetFormatter.outputAsCSV(out, rs) ;
+    @Test
+    public void resultset_05() {
+        ResultSet rs = make($rs);
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        ResultSetFormatter.outputAsCSV(out, rs);
     }
 
-    private static void checkIsomorphic(ResultSet x, ResultSet y)
-    {
+    private static void checkIsomorphic(ResultSet x, ResultSet y) {
         ResultSetRewindable rs1 = x.rewindable();
         ResultSetRewindable rs2 = y.rewindable();
-//        System.out.println(ResultSetFormatter.asText(rs1));
-//        System.out.println();
-//        System.out.println(ResultSetFormatter.asText(rs2));
-//        rs1.reset();
-//        rs2.reset();
-        Assert.assertTrue(ResultSetCompare.isomorphic(rs1, rs2)) ;
+        Assert.assertTrue(ResultsCompare.equalsByTerm(rs1, rs2));
     }
 
 }
diff --git a/jena-arq/src/test/java/org/apache/jena/sparql/util/TS_Util.java b/jena-arq/src/test/java/org/apache/jena/sparql/util/TS_Util.java
index 248e96e3d9..64e3d9362f 100644
--- a/jena-arq/src/test/java/org/apache/jena/sparql/util/TS_Util.java
+++ b/jena-arq/src/test/java/org/apache/jena/sparql/util/TS_Util.java
@@ -28,7 +28,6 @@ import org.junit.runners.Suite.SuiteClasses ;
     TestList.class ,
     TestFmtUtils.class,
     TestContextUtils.class,
-    TestIsoMatcher.class,
     TestGraphUtils.class,
     TestQueryCheckRW.class
 })
diff --git a/jena-arq/src/test/java/org/apache/jena/sparql/util/TS_Util.java b/jena-arq/src/test/java/org/apache/jena/sparql/util/iso/TS_Iso.java
similarity index 81%
copy from jena-arq/src/test/java/org/apache/jena/sparql/util/TS_Util.java
copy to jena-arq/src/test/java/org/apache/jena/sparql/util/iso/TS_Iso.java
index 248e96e3d9..ee160060dd 100644
--- a/jena-arq/src/test/java/org/apache/jena/sparql/util/TS_Util.java
+++ b/jena-arq/src/test/java/org/apache/jena/sparql/util/iso/TS_Iso.java
@@ -16,7 +16,7 @@
  * limitations under the License.
  */
 
-package org.apache.jena.sparql.util;
+package org.apache.jena.sparql.util.iso;
 
 import org.junit.runner.RunWith ;
 import org.junit.runners.Suite ;
@@ -24,13 +24,9 @@ import org.junit.runners.Suite.SuiteClasses ;
 
 @RunWith(Suite.class)
 @SuiteClasses( {
-    TestDateTimeParsing.class ,
-    TestList.class ,
-    TestFmtUtils.class,
-    TestContextUtils.class,
+    TestIso.class,
     TestIsoMatcher.class,
-    TestGraphUtils.class,
-    TestQueryCheckRW.class
+    TestIsoAlgRows.class
 })
-public class TS_Util
-{ }
+
+public class TS_Iso { }
diff --git a/jena-arq/src/test/java/org/apache/jena/sparql/util/iso/TestIso.java b/jena-arq/src/test/java/org/apache/jena/sparql/util/iso/TestIso.java
new file mode 100644
index 0000000000..0c7dd01d66
--- /dev/null
+++ b/jena-arq/src/test/java/org/apache/jena/sparql/util/iso/TestIso.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.jena.sparql.util.iso;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+import org.apache.jena.graph.Node;
+import org.apache.jena.graph.Triple;
+import org.apache.jena.sparql.sse.SSE;
+import org.apache.jena.sparql.util.Iso;
+import org.apache.jena.sparql.util.NodeIsomorphismMap;
+
+/** {@link Iso} contains atomic and order list matching. */
+public class TestIso {
+
+    @Test public void iso_terms_iri_01() { testTerms(":s", ":s", true); }
+    @Test public void iso_terms_iri_02() { testTerms(":s", ":x", false); }
+
+    @Test public void iso_terms_bnode_01() { testTerms("_:s", "_:x", true); }
+    @Test public void iso_terms_bnode_02() { testTerms("_:s", "_:s", true); }
+
+    @Test public void iso_terms_literal_01() { testTerms("'abc'", "'abc'", true); }
+    @Test public void iso_terms_literal_02() { testTerms("'abc'", "'xyz'", false); }
+
+    @Test public void iso_terms_tripleTerms_01() { testTerms("<<( :s :p :o )>>", "<<( :s :p :o )>>", true); }
+    @Test public void iso_terms_tripleTerms_02() { testTerms("<<(:s :p :z )>>", "<<( :s :p :o )>>", false); }
+
+    @Test public void iso_terms_tripleTerms_03() { testTerms("<<( _:b :p :o )>>", "<<( _:x :p :o )>>", true); }
+    @Test public void iso_terms_tripleTerms_04() { testTerms("<<( _:b :p _:b )>>", "<<( _:x :p _:z )>>", false); }
+
+    @Test public void iso_terms_50() { testTerms(":s", "_:b", false); }
+
+    @Test public void iso_triples_01() { testTriples("(:s :p :o)", "(:s :p :o)", true); }
+    @Test public void iso_triples_02() { testTriples("(_:b :p :o)", "(_:x :p :o)", true); }
+    @Test public void iso_triples_03() { testTriples("(_:b :p :o)", "(:s :p _:b)", false); }
+    @Test public void iso_triples_04() { testTriples("(_:b :p _:b)", "(_:x1 :p _:x2)", false); }
+
+    private void testTerms(String str1, String str2, boolean expected) {
+        Node n1 = SSE.parseNode(str1);
+        Node n2 = SSE.parseNode(str2);
+        NodeIsomorphismMap isoMap = new NodeIsomorphismMap();
+        boolean result = Iso.nodeIso(n1, n2, isoMap);
+        assertEquals(expected, result);
+    }
+
+    private void testTriples(String str1, String str2, boolean expected) {
+        Triple t1 = SSE.parseTriple(str1);
+        Triple t2 = SSE.parseTriple(str2);
+        NodeIsomorphismMap isoMap = new NodeIsomorphismMap();
+        boolean result = Iso.tripleIso(t1, t2, isoMap);
+        assertEquals(expected, result);
+    }
+}
diff --git a/jena-arq/src/test/java/org/apache/jena/sparql/util/iso/TestIsoAlgRows.java b/jena-arq/src/test/java/org/apache/jena/sparql/util/iso/TestIsoAlgRows.java
new file mode 100644
index 0000000000..7dd7dc054e
--- /dev/null
+++ b/jena-arq/src/test/java/org/apache/jena/sparql/util/iso/TestIsoAlgRows.java
@@ -0,0 +1,351 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.jena.sparql.util.iso;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.List;
+
+import org.junit.Test;
+
+import org.apache.jena.graph.Node;
+import org.apache.jena.sparql.engine.binding.Binding;
+import org.apache.jena.sparql.exec.RowSet;
+import org.apache.jena.sparql.exec.RowSetMem;
+import org.apache.jena.sparql.exec.RowSetRewindable;
+import org.apache.jena.sparql.resultset.ResultsCompare;
+import org.apache.jena.sparql.sse.SSE;
+import org.apache.jena.sparql.util.EqualityTest;
+import org.apache.jena.sparql.util.NodeUtils;
+
+public class TestIsoAlgRows {
+
+    @Test public void testIsoTerm_01() { testIsoTerm(":s", ":s", true); }
+    @Test public void testIsoTerm_02() { testIsoTerm(":s", "'s'", false); }
+    @Test public void testIsoTerm_03() { testIsoTerm("_:b0", "_:b1", true); }
+    @Test public void testIsoTerm_04() { testIsoTerm("<<(_:b0 :p :o )>>", "<<(_:b1 :p :o )>>", true); }
+
+    @Test
+    public void testIsoTerm_05() {
+        testIsoTerm("<<( _:b0 :p <<( _:b0 _:b0 _:b0 )>> )>>", "<<( _:b1 :p <<( _:b1 _:b1 _:b1 )>> )>>", true);
+    }
+
+    @Test
+    public void testIsoTerm_06() {
+        testIsoTerm("<<( _:b0 :p <<( _:b0 _:b0 _:b0 )>> )>>", "<<( _:b1 :p <<( _:b1 _:b1 _:b2 )>> )>>", false);
+    }
+
+    @Test
+    public void testIsoTerm_07() {
+        testIsoTerm("<<( _:b1 :p <<( _:b0 _:b0 _:b1 )>> )>>", "<<( _:b2 :p <<( _:b3 _:b3 _:b2 )>> )>>", true);
+    }
+
+    @Test public void testIsoRowSet_01() { testIsoRowSet("(rowset (?x))", "(rowset (?x))", true); }
+    @Test public void testIsoRowSet_02() { testIsoRowSet("(rowset (?x))", "(rowset (?y))", false); }
+    @Test public void testIsoRowSet_03() { testIsoRowSet("(rowset (?x))", "(rowset (?x ?y))", false); }
+    @Test public void testIsoRowSet_04() { testIsoRowSet("(rowset (?x ?y))", "(rowset (?y ?x))", true); }
+
+    @Test public void testIsoRowSet_10() { testIsoRowSet("(rowset (?x) (row (?x 'A')) )", "(rowset (?x) (row (?x 'A')) )", true); }
+    @Test public void testIsoRowSet_11() { testIsoRowSet("(rowset (?x) (row (?x 'A')) )", "(rowset (?y) (row (?y 'A')) )", false); }
+    @Test public void testIsoRowSet_12() { testIsoRowSet("(rowset (?x) (row (?x 'A')) )", "(rowset (?x) (row (?x 'A')) )", true); }
+    @Test public void testIsoRowSet_13() { testIsoRowSet("(rowset (?x) (row (?x 'A')) )", "(rowset (?x) (row (?x 'A')) )", true); }
+
+    @Test public void testIsoRowSet_14() { testIsoRowSet("(rowset (?x ?y))", "(rowset (?y ?x) (row) )", false); }
+    @Test public void testIsoRowSet_15() { testIsoRowSet("(rowset (?x) (row (?x 'A')) )", "(rowset (?x) (row (?x 'A')) (row) )", false); }
+
+    @Test public void testIsoRowSet_16() {
+        testIsoRowSet("(rowset (?x ?y) (row (?x 'A') (?y 'B')) )",
+                      "(rowset (?x ?y) (row (?y 'B') (?x 'A')) )",
+                      true);
+    }
+
+    // Blank nodes, one row
+
+    @Test public void testIsoRowSet_21() {
+        testIsoRowSet("(rowset (?x ?y ?z) (row (?x _:b0) (?y _:b1) (?z _:b2)) )",
+                      "(rowset (?x ?y ?z) (row (?x _:c0) (?y _:c1) (?z _:c2)) )",
+                      true); }
+
+    @Test public void testIsoRowSet_22() {
+        testIsoRowSet("(rowset (?x ?y ?z) (row (?x _:b0) (?y _:b1) (?z _:b0)) )",
+                      "(rowset (?x ?y ?z) (row (?x _:c0) (?y _:c1) (?z _:c0)) )",
+                      true); }
+
+    @Test public void testIsoRowSet_23() {
+        testIsoRowSet("(rowset (?x ?y ?z) (row (?x _:b0) (?y _:b1) (?z _:b0)) )",
+                      "(rowset (?x ?y ?z) (row (?x _:c0) (?y _:c1) (?z _:c2)) )",
+                      false); }
+
+    // Multiple rows
+    @Test public void testIsoRowSet_30() {
+        testIsoRowSet("(rowset (?x ?y) (row (?x 'A')) (row (?y 'B')) )",
+                      "(rowset (?x ?y) (row (?y 'B')) (row (?x 'A')) )",
+                      true);
+    }
+
+    @Test public void testIsoRowSet_31() {
+        testIsoRowSet("(rowset (?x ?y) (row (?x _:b0)) (row (?y _:b0)) )",
+                      "(rowset (?x ?y) (row (?x _:c0)) (row (?y _:c1)) )",
+                      false);
+    }
+
+    @Test public void testIsoRowSet_32() {
+        testIsoRowSet("(rowset (?x ?y) (row (?x _:b0)) (row (?y _:b0)) )",
+                      "(rowset (?x ?y) (row (?y _:c1)) (row (?x _:c0)) )",
+                      false);
+    }
+
+    @Test public void testIsoRowSet_33() {
+        testIsoRowSet("(rowset (?x) (row (?x _:b0)) (row (?x _:b0)) )",
+                      "(rowset (?x) (row (?x _:c0)) (row (?x _:c1)) )",
+                      false);
+    }
+
+    @Test public void testIsoRowSet_34() {
+        testIsoRowSet("(rowset (?x) (row (?x _:b0)) (row (?x _:b0)) )",
+                      "(rowset (?x) (row (?x _:c0)) (row (?x _:c0)) )",
+                      true);
+    }
+
+    //ResultsCompare.equalsExact
+
+    @Test public void testIsoExactRowSet_01() {
+        String x = "(rowset (?x) (row (?x _:b0)) )";
+        // Re-parsed - fresh bnodes.s
+        testIsoExactRowSet(x, x, false);
+    }
+
+    @Test public void testIsoExactRowSet_02() {
+        String x = """
+                (rowset (?x ?y)
+                    (row (?x _:b0) (?y _:b1))
+                    (row (?x _:b2) (?y _:b3))
+                    (row (?x _:b1) (?y _:b0))
+                )
+           """;
+        testIsoExactRowSet(x, x, false);
+    }
+
+    @Test public void testIsoExactRowSet_10() {
+
+        String x = """
+                (rowset (?x ?y)
+                    (row (?x _:b0) (?y _:b1))
+                    (row (?x _:b2) (?y _:b3))
+                    (row (?x _:b1) (?y _:b0))
+                )
+           """;
+        RowSetRewindable rowSet1 = SSE.parseRowSet(x).rewindable();
+        RowSetRewindable rowSet2 =RowSetMem.create(rowSet1);
+        rowSet1.reset();
+        rowSet2.reset();
+        testIsoExactRowSet(rowSet1, x, rowSet2, x, true);
+    }
+
+    @Test public void testIsoRowSet_35() {
+        String x = """
+                (rowset (?x ?y)
+                    (row (?x _:b0) (?y _:b1))
+                    (row (?x _:b2) (?y _:b3))
+                    (row (?x _:b1) (?y _:b0))
+                )
+           """;
+        testIsoRowSet(x, x, true);
+    }
+
+    // Term testing
+
+    @Test public void testIsoRowSet_40() {
+        testIsoRowSet("(rowset (?x) (row (?x <<(:s :p _:b)>>)) )",
+                      "(rowset (?x) (row (?x <<(:s :p _:b)>>)) )",
+                      true);
+    }
+
+    @Test public void testIsoRowSet_41() {
+        testIsoRowSet("(rowset (?x ?y) (row (?x <<(:s :p _:b)>>) (?y <<(:s :p _:b)>>)) )",
+                      "(rowset (?x ?y) (row (?x <<(:s :p _:b)>>) (?y <<(:s :p _:b)>>)) )",
+                      true);
+    }
+
+    @Test public void testIsoRowSet_42() {
+        testIsoRowSet("(rowset (?x ?y) (row (?x <<(:s :p _:b)>>) (?y <<(:s :p _:b)>>)) )",
+                      "(rowset (?x ?y) (row (?x <<(:s :p _:b1)>>) (?y <<(:s :p _:b2)>>)) )",
+                      false);
+    }
+
+    @Test public void testIsoRowSet_50() {
+        testIsoRowSet("(rowset (?x) (row (?x <<(_:b :p <<(_:b :p _:b)>> )>>)) )",
+                      "(rowset (?x) (row (?x <<(_:b :p <<(_:b :p _:b)>> )>>)) )",
+                      true);
+    }
+
+    @Test public void testIsoRowSet_51() {
+        testIsoRowSet("(rowset (?x) (row (?x <<(_:b :p <<(_:b :p _:b)>> )>>)) )",
+                      "(rowset (?x) (row (?x <<(_:b :p <<(_:b1 :p _:b)>> )>>)) )",
+                      false);
+    }
+
+    // ---- Lists of rows.
+    @FunctionalInterface
+    interface ListTest {
+        boolean test(List<Binding> list1, List<Binding> list2);
+    }
+
+    @Test public void testIsoList_01() {
+        testIsoListRows("(row (?x :x0) (?y :x1)) (row (?x :x2) (?y :x3)) ",
+                        "(row (?x :x0) (?y :x1)) (row (?x :x2) (?y :x3)) ",
+                        ResultsCompare::equalsExact,
+                        true);
+    }
+
+    @Test public void testIsoList_02() {
+        String arg1 = "(row (?x :x0) (?y :x1)) (row (?x :x2) (?y :x3))";
+        String arg2 = "(row (?x :x2) (?y :x3)) (row (?x :x0) (?y :x1))";
+
+        testIsoListRows(arg1, arg2, ResultsCompare::equalsExact, true);
+        testIsoListRows(arg1, arg2, ResultsCompare::equalsExactAndOrder, false);
+    }
+
+    // ---- Machinery
+
+    private static void testIsoTerm(String str1, String str2, boolean expected) {
+        Node n1 = SSE.parseNode(str1);
+        Node n2 = SSE.parseNode(str2);
+
+        testIsoTerm(n1, str1, n2, str2, expected);
+        testIsoTerm(n2, str2, n1, str1, expected);
+
+        boolean b = equalsIsoByTerm(n1, n2);
+        if ( expected != b ) {
+            String msg = expected ? "(expected match)" : "(expected no match)";
+            System.out.println("== ** BAD "+msg);
+            System.out.println("== Term1");
+            System.out.println("  "+str1);
+            System.out.println("== Term2");
+            System.out.println("  "+str2);
+            System.out.println();
+        }
+        assertEquals(expected, b);
+    }
+
+    private static void testIsoTerm(Node n2, String str1, Node n1, String str2, boolean expected) {
+        boolean b = equalsIsoByTerm(n1, n2);
+        if ( expected != b ) {
+            String msg = expected ? "(expected match)" : "(expected no match)";
+            System.out.println("== ** BAD "+msg);
+            System.out.println("== Term1");
+            System.out.println("  "+str1);
+            System.out.println("== Term2");
+            System.out.println("  "+str2);
+            System.out.println();
+        }
+        assertEquals(expected, b);
+    }
+
+    private static boolean equalsIsoByTerm(Node n1, Node n2) {
+        IsoMapping mapping = IsoAlgRows.matchTermsTest(n1, n2, NodeUtils.sameNode);
+        return mapping != null;
+    }
+
+    // RowSet testing.
+
+    private static void testIsoRowSet(String str1, String str2, boolean expected) {
+        RowSetRewindable rowSet1 = SSE.parseRowSet(str1).rewindable();
+        RowSetRewindable rowSet2 = SSE.parseRowSet(str2).rewindable();
+        // Both ways round.
+        testIsoRowSet(rowSet1, str1, rowSet2, str2, expected);
+        rowSet1.reset();
+        rowSet2.reset();
+        testIsoRowSet(rowSet2, str2, rowSet1, str1, expected);
+    }
+
+    private static void testIsoRowSet(RowSet rowSet1, String str1, RowSet rowSet2, String str2, boolean expected) {
+        RowSetRewindable rsw1 = rowSet1.rewindable();
+        RowSetRewindable rsw2 = rowSet2.rewindable();
+        boolean b = equalsIsoByTerm(rsw1, rsw2);
+        if ( expected != b ) {
+            System.out.println("== ** BAD");
+            System.out.println("== rowSet1");
+            System.out.println(str1);
+            System.out.println("== rowSet2");
+            System.out.println(str2);
+        }
+        assertEquals(expected, b);
+    }
+
+    private static boolean equalsIsoByTerm(RowSetRewindable rowSet1, RowSetRewindable rowSet2) {
+        return ResultsCompare.equalsByTerm(rowSet1, rowSet2);
+    }
+
+    private void testIsoExactRowSet(String str1, String str2, boolean expected) {
+        RowSetRewindable rowSet1 = SSE.parseRowSet(str1).rewindable();
+        RowSetRewindable rowSet2 = SSE.parseRowSet(str2).rewindable();
+        // Both ways round.
+        testIsoExactRowSet(rowSet1, str1, rowSet2, str2, expected);
+        rowSet1.reset();
+        rowSet2.reset();
+        testIsoExactRowSet(rowSet2, str2, rowSet1, str1, expected);
+    }
+
+    private void testIsoExactRowSet(RowSet rowSet1, String str1, RowSet rowSet2, String str2, boolean expected) {
+        RowSetRewindable rsw1 = rowSet1.rewindable();
+        RowSetRewindable rsw2 = rowSet2.rewindable();
+        EqualityTest eqtest = NodeUtils.sameRdfTerm;
+        boolean b = ResultsCompare.equalsExact(rowSet1, rowSet2);
+        if ( expected != b ) {
+            System.out.println("== ** BAD");
+            System.out.println("== rowSet1");
+            rsw1.reset();
+            rsw1 = IsoLib.print(rsw1);
+            System.out.println("== rowSet2");
+            rsw2.reset();
+            rsw2 = IsoLib.print(rsw2);
+        }
+        assertEquals(expected, b);
+    }
+
+    // Lists
+
+    private static List<Binding> parseRows(String str) {
+        RowSet rowSet = SSE.parseRowSet("(rowset () "+str+" )");
+        Binding[] x = rowSet.stream().toArray(Binding[]::new);
+        return List.of(x);  // Immutable, no nulls.
+    }
+
+    private static void testIsoListRows(String str1, String str2, ListTest predicate, boolean expected) {
+        List<Binding> rows1 = parseRows(str1);
+        List<Binding> rows2 = parseRows(str2);
+        testIsoListRows(rows1, str1, rows2, str2, predicate, expected);
+        testIsoListRows(rows2, str2, rows1, str1, predicate, expected);
+    }
+
+    private static void testIsoListRows(List<Binding> rows1, String str1, List<Binding> rows2, String str2, ListTest predicate, boolean expected) {
+        boolean b = predicate.test(rows1, rows2);
+        if ( expected != b ) {
+            String msg = expected ? "(expected match)" : "(expected no match)";
+            System.out.println("== ** BAD "+msg);
+            System.out.println("== Row list 1");
+            System.out.println("  "+str1);
+            System.out.println("== Row list 12");
+            System.out.println("  "+str2);
+            System.out.println();
+        }
+        assertEquals(expected, b);
+    }
+}
diff --git a/jena-arq/src/test/java/org/apache/jena/sparql/util/TestIsoMatcher.java b/jena-arq/src/test/java/org/apache/jena/sparql/util/iso/TestIsoMatcher.java
similarity index 94%
rename from jena-arq/src/test/java/org/apache/jena/sparql/util/TestIsoMatcher.java
rename to jena-arq/src/test/java/org/apache/jena/sparql/util/iso/TestIsoMatcher.java
index 391ba8529d..0d2ff1a911 100644
--- a/jena-arq/src/test/java/org/apache/jena/sparql/util/TestIsoMatcher.java
+++ b/jena-arq/src/test/java/org/apache/jena/sparql/util/iso/TestIsoMatcher.java
@@ -16,8 +16,9 @@
  * limitations under the License.
  */
 
-package org.apache.jena.sparql.util;
+package org.apache.jena.sparql.util.iso;
 
+import static org.apache.jena.atlas.lib.tuple.TupleFactory.tuple;
 import static org.junit.Assert.assertEquals;
 
 import java.util.ArrayList;
@@ -25,15 +26,21 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 
+import org.junit.Test;
+
+import org.apache.jena.atlas.iterator.Iter;
 import org.apache.jena.atlas.lib.tuple.Tuple;
 import org.apache.jena.atlas.lib.tuple.TupleFactory;
 import org.apache.jena.graph.Graph;
 import org.apache.jena.graph.Node;
+import org.apache.jena.graph.Triple;
 import org.apache.jena.sparql.core.DatasetGraph;
 import org.apache.jena.sparql.sse.Item;
 import org.apache.jena.sparql.sse.SSE;
 import org.apache.jena.sparql.sse.builders.BuilderNode;
-import org.junit.Test;
+import org.apache.jena.sparql.util.IsoMatcher;
+import org.apache.jena.sparql.util.NodeUtils;
+import org.apache.jena.util.iterator.ExtendedIterator;
 
 public class TestIsoMatcher
 {
@@ -336,10 +343,10 @@ public class TestIsoMatcher
         Graph g1 = SSE.parseGraph(s1);
         Graph g2 = SSE.parseGraph(s2);
 
-        Collection<Tuple<Node>> x1 = IsoMatcher.tuplesTriples(g1.find());
-        Collection<Tuple<Node>> x2 = IsoMatcher.tuplesTriples(g2.find());
+        Collection<Tuple<Node>> x1 = tuplesTriples(g1.find());
+        Collection<Tuple<Node>> x2 = tuplesTriples(g2.find());
 
-        boolean b = IsoAlg.isIsomorphic(x1, x2, Iso.mappableBlankNodesVariables, NodeUtils.sameRdfTerm);
+        boolean b = IsoAlgTuple.isIsomorphic(x1, x2, IsoLib.mappableBlankNodesVariables, NodeUtils.sameRdfTerm);
         if ( b != expected ) {
             System.out.println("====");
             SSE.write(g1);
@@ -371,4 +378,11 @@ public class TestIsoMatcher
         }
         assertEquals(iso, b);
     }
+
+    /*package*/ static List<Tuple<Node>> tuplesTriples(ExtendedIterator<Triple> iter) {
+        try {
+            return Iter.iter(iter).map(t->tuple(t.getSubject(), t.getPredicate(), t.getObject())).toList();
+        }
+        finally { iter.close(); }
+    }
 }
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/ListUtils.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/ListUtils.java
index bde33c4d61..1eca2b5154 100644
--- a/jena-base/src/main/java/org/apache/jena/atlas/lib/ListUtils.java
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/ListUtils.java
@@ -87,9 +87,9 @@ public class ListUtils
         List<T> list2a = new ArrayList<>(list2);
         for ( T elt : list1 )
             list2a.remove(elt);
-        if ( list2a.size() != 0 )
-            return false;
-        return true;
+        if ( list2a.isEmpty() )
+            return true;
+        return false;
     }
 
     /**
diff --git a/jena-core/src/main/java/org/apache/jena/graph/Node.java b/jena-core/src/main/java/org/apache/jena/graph/Node.java
index dd68883648..2f34f2ed71 100644
--- a/jena-core/src/main/java/org/apache/jena/graph/Node.java
+++ b/jena-core/src/main/java/org/apache/jena/graph/Node.java
@@ -246,12 +246,15 @@ public abstract class Node implements Serializable {
     /*package*/ Node( ) {}
 
     /**
-     * Java rules for equals. See also {#sameTermAs} and {#sameValueAs} Nodes only equal
-     * other Nodes that have equal labels.
+     * Java rules for equals. See also {#sameTermAs} and {#sameValueAs}.
+     * Nodes only equal other Nodes that have equal labels.
      */
     @Override
     public abstract boolean equals(Object o);
 
+    /**
+     * RDF term equality.
+     */
     public boolean sameTermAs(Object o)
     { return equals( o ); }
 
diff --git a/jena-extras/jena-serviceenhancer/src/test/java/org/apache/jena/sparql/service/enhancer/impl/TestServiceEnhancerCachedVsUncached.java b/jena-extras/jena-serviceenhancer/src/test/java/org/apache/jena/sparql/service/enhancer/impl/TestServiceEnhancerCachedVsUncached.java
index 1e93278c63..7558955607 100644
--- a/jena-extras/jena-serviceenhancer/src/test/java/org/apache/jena/sparql/service/enhancer/impl/TestServiceEnhancerCachedVsUncached.java
+++ b/jena-extras/jena-serviceenhancer/src/test/java/org/apache/jena/sparql/service/enhancer/impl/TestServiceEnhancerCachedVsUncached.java
@@ -25,24 +25,20 @@ import java.util.Random;
 import java.util.function.Consumer;
 import java.util.regex.Pattern;
 
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
 import org.apache.jena.atlas.logging.Log;
-import org.apache.jena.query.Query;
-import org.apache.jena.query.QueryExecution;
-import org.apache.jena.query.QueryFactory;
-import org.apache.jena.query.ResultSetFactory;
-import org.apache.jena.query.ResultSetFormatter;
-import org.apache.jena.query.ResultSetRewindable;
+import org.apache.jena.query.*;
 import org.apache.jena.rdf.model.Model;
 import org.apache.jena.sparql.engine.iterator.QueryIterSlice;
-import org.apache.jena.sparql.resultset.ResultSetCompare;
+import org.apache.jena.sparql.resultset.ResultsCompare;
 import org.apache.jena.sparql.service.ServiceExecutorRegistry;
 import org.apache.jena.sparql.service.enhancer.init.ServiceEnhancerInit;
 import org.apache.jena.sparql.util.Context;
-import org.junit.Assert;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
 
 
 /**
@@ -90,7 +86,7 @@ public class TestServiceEnhancerCachedVsUncached {
                     cxtMutator.accept(qeB.getContext());
                     rsB = ResultSetFactory.makeRewindable(qeB.execSelect());
 
-                    boolean isEqual = ResultSetCompare.equalsByValue(rsA, rsB);
+                    boolean isEqual = ResultsCompare.equalsByValue(rsA, rsB);
                     if (!isEqual) {
                         rsA.reset();
                         ResultSetFormatter.out(System.out, rsA);
diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestQuery.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestQuery.java
index 60d4c392e3..54f8e8db4a 100644
--- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestQuery.java
+++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestQuery.java
@@ -18,8 +18,9 @@
 
 package org.apache.jena.fuseki.main;
 
-import static org.junit.jupiter.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.io.*;
@@ -47,7 +48,7 @@ import org.apache.jena.sparql.core.Var;
 import org.apache.jena.sparql.engine.binding.Binding;
 import org.apache.jena.sparql.exec.QueryExec;
 import org.apache.jena.sparql.exec.http.*;
-import org.apache.jena.sparql.resultset.ResultSetCompare;
+import org.apache.jena.sparql.resultset.ResultsCompare;
 import org.apache.jena.sparql.sse.SSE;
 import org.apache.jena.sparql.util.Convert;
 
@@ -316,7 +317,7 @@ public class TestQuery extends AbstractFusekiTest {
     private void execQuery(String queryString, ResultSet expectedResultSet) {
         try ( QueryExecution qExec = QueryExecution.service(serviceQuery(), queryString) ) {
             ResultSet rs = qExec.execSelect();
-            boolean b = ResultSetCompare.equalsByTerm(rs, expectedResultSet);
+            boolean b = ResultsCompare.equalsByTerm(rs, expectedResultSet);
             assertTrue(b, "Result sets different");
         }
     }
diff --git a/jena-integration-tests/src/test/java/org/apache/jena/test/service/TestCustomServiceExecutor.java b/jena-integration-tests/src/test/java/org/apache/jena/test/service/TestCustomServiceExecutor.java
index b02c72f43f..60a93b4692 100644
--- a/jena-integration-tests/src/test/java/org/apache/jena/test/service/TestCustomServiceExecutor.java
+++ b/jena-integration-tests/src/test/java/org/apache/jena/test/service/TestCustomServiceExecutor.java
@@ -23,10 +23,13 @@ import static org.junit.Assert.assertTrue;
 
 import java.util.function.Consumer;
 
+import org.junit.Assert;
+import org.junit.Test;
+
 import org.apache.jena.atlas.logging.LogCtl;
-import org.apache.jena.query.*;
 import org.apache.jena.graph.Node;
 import org.apache.jena.graph.NodeFactory;
+import org.apache.jena.query.*;
 import org.apache.jena.rdf.model.Model;
 import org.apache.jena.rdf.model.ModelFactory;
 import org.apache.jena.riot.ResultSetMgr;
@@ -41,20 +44,20 @@ import org.apache.jena.sparql.engine.QueryIterator;
 import org.apache.jena.sparql.engine.binding.Binding;
 import org.apache.jena.sparql.engine.iterator.QueryIter;
 import org.apache.jena.sparql.exec.QueryExec;
-import org.apache.jena.sparql.resultset.ResultSetCompare;
+import org.apache.jena.sparql.resultset.ResultsCompare;
 import org.apache.jena.sparql.service.ServiceExec;
 import org.apache.jena.sparql.service.ServiceExecutorRegistry;
 import org.apache.jena.sparql.service.single.ServiceExecutor;
 import org.apache.jena.sparql.sse.SSE;
-import org.junit.Assert;
-import org.junit.Test;
 
 public class TestCustomServiceExecutor {
 
     static Table table = SSE.parseTable("(table (row (?s 1) (?p 2) (?o 3) ) )");
 
-    /** A custom service factory that yields the above table for any request
-     *  to urn:customService */
+    /**
+     * A custom service factory that yields the above table
+     * for any request to urn:customService
+     */
     static ServiceExecutor factory = (op, opOriginal, binding, execCxt) ->
         op.getService().getURI().equals("urn:customService")
             ? table.iterator(execCxt)
@@ -251,7 +254,7 @@ public class TestCustomServiceExecutor {
 
     public static void assertResult(String serviceIri, Consumer<QueryExecution> qePostProcessor, boolean withSilent) {
         ResultSetRewindable actual = runTestQuery(serviceIri, qePostProcessor, withSilent);
-        boolean isEqual = ResultSetCompare.equalsExact(actual, ResultSet.adapt(table.toRowSet()));
+        boolean isEqual = ResultsCompare.equalsExact(actual, ResultSet.adapt(table.toRowSet()));
         if (!isEqual) {
             actual.reset();
             ResultSetMgr.write(System.err, actual, ResultSetLang.RS_Text);
diff --git a/jena-tdb1/src/test/java/org/apache/jena/tdb1/solver/TestSolverTDB.java b/jena-tdb1/src/test/java/org/apache/jena/tdb1/solver/TestSolverTDB.java
index d8c30237bc..c3f4e444d7 100644
--- a/jena-tdb1/src/test/java/org/apache/jena/tdb1/solver/TestSolverTDB.java
+++ b/jena-tdb1/src/test/java/org/apache/jena/tdb1/solver/TestSolverTDB.java
@@ -24,10 +24,14 @@ import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
 
+import org.junit.BeforeClass;
+import org.junit.Test;
+
 import org.apache.jena.graph.Graph;
 import org.apache.jena.graph.Node;
 import org.apache.jena.graph.Triple;
-import org.apache.jena.query.*;
+import org.apache.jena.query.Dataset;
+import org.apache.jena.query.ReadWrite;
 import org.apache.jena.riot.RDFDataMgr;
 import org.apache.jena.shared.PrefixMapping;
 import org.apache.jena.shared.impl.PrefixMappingImpl;
@@ -40,12 +44,10 @@ import org.apache.jena.sparql.engine.binding.Binding;
 import org.apache.jena.sparql.exec.RowSet;
 import org.apache.jena.sparql.exec.RowSetOps;
 import org.apache.jena.sparql.exec.RowSetRewindable;
-import org.apache.jena.sparql.resultset.ResultSetCompare;
+import org.apache.jena.sparql.resultset.ResultsCompare;
 import org.apache.jena.sparql.sse.SSE;
 import org.apache.jena.tdb1.ConfigTest;
 import org.apache.jena.tdb1.TDB1Factory;
-import org.junit.BeforeClass;
-import org.junit.Test;
 
 public class TestSolverTDB {
     static String graphData = null;
@@ -131,7 +133,7 @@ public class TestSolverTDB {
         RowSetRewindable rsw1 = rs1.rewindable();
         RowSetRewindable rsw2 = rs2.rewindable();
 
-        boolean b = ResultSetCompare.equalsByValue(rsw1, rsw2);
+        boolean b = ResultsCompare.equalsByValue(rsw1, rsw2);
         if ( b != result ) {
             System.out.println("Different: ");
             rsw1.reset();
diff --git a/jena-tdb2/src/test/java/org/apache/jena/tdb2/solver/TestSolverTDB.java b/jena-tdb2/src/test/java/org/apache/jena/tdb2/solver/TestSolverTDB.java
index 8ffc94c0f8..1ffa2ef12a 100644
--- a/jena-tdb2/src/test/java/org/apache/jena/tdb2/solver/TestSolverTDB.java
+++ b/jena-tdb2/src/test/java/org/apache/jena/tdb2/solver/TestSolverTDB.java
@@ -24,10 +24,15 @@ import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
 
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
 import org.apache.jena.graph.Graph;
 import org.apache.jena.graph.Node;
 import org.apache.jena.graph.Triple;
-import org.apache.jena.query.*;
+import org.apache.jena.query.Dataset;
+import org.apache.jena.query.ReadWrite;
 import org.apache.jena.riot.RDFDataMgr;
 import org.apache.jena.shared.PrefixMapping;
 import org.apache.jena.shared.impl.PrefixMappingImpl;
@@ -40,13 +45,10 @@ import org.apache.jena.sparql.engine.binding.Binding;
 import org.apache.jena.sparql.exec.RowSet;
 import org.apache.jena.sparql.exec.RowSetOps;
 import org.apache.jena.sparql.exec.RowSetRewindable;
-import org.apache.jena.sparql.resultset.ResultSetCompare;
+import org.apache.jena.sparql.resultset.ResultsCompare;
 import org.apache.jena.sparql.sse.SSE;
 import org.apache.jena.tdb2.ConfigTest;
 import org.apache.jena.tdb2.junit.TL;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.Test;
 
 public class TestSolverTDB {
     static Dataset dataset = null;
@@ -137,7 +139,7 @@ public class TestSolverTDB {
         RowSetRewindable rsw1 = rs1.rewindable();
         RowSetRewindable rsw2 = rs2.rewindable();
 
-        boolean b = ResultSetCompare.equalsByValue(rsw1, rsw2);
+        boolean b = ResultsCompare.equalsByValue(rsw1, rsw2);
         if ( b != result ) {
             System.out.println("Different: ");
             rsw1.reset();