From 9bf3e89693ebba06619459e9b2ed21505ab5047a Mon Sep 17 00:00:00 2001 From: dmfay Date: Tue, 7 Aug 2012 22:33:17 -0400 Subject: [PATCH] Intersect and Except were not advancing properly on successful compare; tests for all three set ops --- .../graph/query/process/ExceptComponent.java | 7 + .../graph/query/process/IntersectComponent.java | 8 +- .../query/process/SetOperationComponentsTest.java | 213 ++++++++++++++++++++ 3 files changed, 227 insertions(+), 1 deletions(-) create mode 100644 modeshape-graph/src/test/java/org/modeshape/graph/query/process/SetOperationComponentsTest.java diff --git a/modeshape-graph/src/main/java/org/modeshape/graph/query/process/ExceptComponent.java b/modeshape-graph/src/main/java/org/modeshape/graph/query/process/ExceptComponent.java index 79044e9..803216e 100644 --- a/modeshape-graph/src/main/java/org/modeshape/graph/query/process/ExceptComponent.java +++ b/modeshape-graph/src/main/java/org/modeshape/graph/query/process/ExceptComponent.java @@ -93,6 +93,13 @@ public class ExceptComponent extends SetOperationComponent { if (comparison == 0) { // Both match, so remove the tuple from 'tuples' and go on ... tupleIter.remove(); + + if (!tupleIter.hasNext()) { + // This was the last one + break; + } + tuple1 = tupleIter.next(); + continue; } // No match, so leave tuple1 in 'tuples' diff --git a/modeshape-graph/src/main/java/org/modeshape/graph/query/process/IntersectComponent.java b/modeshape-graph/src/main/java/org/modeshape/graph/query/process/IntersectComponent.java index 12e3a2c..bf8e46b 100644 --- a/modeshape-graph/src/main/java/org/modeshape/graph/query/process/IntersectComponent.java +++ b/modeshape-graph/src/main/java/org/modeshape/graph/query/process/IntersectComponent.java @@ -91,7 +91,13 @@ public class IntersectComponent extends SetOperationComponent { while (true) { int comparison = comparator.compare(tuple1, tuple2); if (comparison == 0) { - // Both match, so leave the tuple in 'tuples' and go on ... + // Both match, so leave the tuple in 'tuples' and advance to next comparison ... + if (!tupleIter.hasNext() || !nextIter.hasNext()) { + break; + } + + tuple1 = tupleIter.next(); + tuple2 = nextIter.next(); continue; } // No match, so remove tuple1 from 'tuples' diff --git a/modeshape-graph/src/test/java/org/modeshape/graph/query/process/SetOperationComponentsTest.java b/modeshape-graph/src/test/java/org/modeshape/graph/query/process/SetOperationComponentsTest.java new file mode 100644 index 0000000..c676510 --- /dev/null +++ b/modeshape-graph/src/test/java/org/modeshape/graph/query/process/SetOperationComponentsTest.java @@ -0,0 +1,213 @@ +/* + * ModeShape (http://www.modeshape.org) + * See the COPYRIGHT.txt file distributed with this work for information + * regarding copyright ownership. Some portions may be licensed + * to Red Hat, Inc. under one or more contributor license agreements. + * See the AUTHORS.txt file in the distribution for a full listing of + * individual contributors. + * + * ModeShape is free software. Unless otherwise indicated, all code in ModeShape + * is licensed to you under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * ModeShape is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.modeshape.graph.query.process; + +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; +import java.util.ArrayList; +import java.util.List; +import org.junit.Before; +import org.junit.Test; +import org.modeshape.graph.ExecutionContext; +import org.modeshape.graph.property.PropertyType; +import org.modeshape.graph.query.QueryContext; +import org.modeshape.graph.query.QueryResults.Columns; +import org.modeshape.graph.query.model.Constraint; +import org.modeshape.graph.query.model.PropertyExistence; +import org.modeshape.graph.query.validate.Schemata; + +/** + * + */ +public class SetOperationComponentsTest extends AbstractQueryResultsTest { + + private SetOperationComponent component; + private List selects; + private QueryContext context; + private Columns columns; + private List tuplesA; + private List tuplesB; + + @Before + public void beforeEach() { + context = new QueryContext(new ExecutionContext(), mock(Schemata.class), typeSystem); + + tuplesA = new ArrayList(); + tuplesB = new ArrayList(); + + // Define the columns for the results ... + columns = resultColumns("Selector1", + new String[] {"ColA", "ColB", "ColC"}, + PropertyType.STRING, + PropertyType.STRING, + PropertyType.STRING); + + // And define the delegating components ... + ProcessingComponent delegateA = new ProcessingComponent(context, columns) { + @SuppressWarnings( "synthetic-access" ) + @Override + public List execute() { + return new ArrayList(tuplesA); + } + }; + + ProcessingComponent delegateB = new ProcessingComponent(context, columns) { + @SuppressWarnings( "synthetic-access" ) + @Override + public List execute() { + return new ArrayList(tuplesB); + } + }; + + Constraint constraint = new PropertyExistence(selector("Selector1"), "ColA"); + + // Create the sets ... + selects = new ArrayList(); + + selects.add(new SelectComponent(delegateA, constraint, null)); + selects.add(new SelectComponent(delegateB, constraint, null)); + } + + @Test + public void shouldUnionResultsForCompatibleSets() { + tuplesA.add(tuple(columns, "/a/b/c1", "v1", "v2", "v3")); + tuplesA.add(tuple(columns, "/a/b/c2", "v1", "v2", "v3")); + tuplesB.add(tuple(columns, "/a/b/c1", "v1", "v2", "v3")); + tuplesB.add(tuple(columns, "/a/b/c3", "v4", "v5", "v6")); + + component = new UnionComponent(context, columns, selects, false, false); + + final List results = component.execute(); + + /* + * Should remove the duplicate row (row 0 in tuplesB) for 3 total. + */ + + assertThat(results.size(), is(3)); + } + + @Test + public void shouldUnionAllResultsForCompatibleSets() { + tuplesA.add(tuple(columns, "/a/b/c1", "v1", "v2", "v3")); + tuplesA.add(tuple(columns, "/a/b/c2", "v1", "v2", "v3")); + tuplesB.add(tuple(columns, "/a/b/c1", "v1", "v2", "v3")); + tuplesB.add(tuple(columns, "/a/b/c3", "v4", "v5", "v6")); + + component = new UnionComponent(context, columns, selects, false, true); + + final List results = component.execute(); + + /* + * Should keep all 4 rows. + */ + + assertThat(results.size(), is(4)); + } + + @Test + public void shouldIntersectResultsForCompatibleSets() { + tuplesA.add(tuple(columns, "/a/b/c1", "v1", "v2", "v3")); + tuplesA.add(tuple(columns, "/a/b/c2", "v1", "v2", "v3")); + tuplesB.add(tuple(columns, "/a/b/c1", "v1", "v2", "v3")); + tuplesB.add(tuple(columns, "/a/b/c3", "v4", "v5", "v6")); + + component = new IntersectComponent(context, columns, selects, false, false); + + final List results = component.execute(); + + /* + * Should get a single row /a/b/c1, v1, v2, v3 + */ + + assertThat(results.size(), is(1)); + } + + @Test + public void shouldIntersectAllResultsForCompatibleSets() { + tuplesA.add(tuple(columns, "/a/b/c1", "v1", "v2", "v3")); + tuplesA.add(tuple(columns, "/a/b/c2", "v1", "v2", "v3")); + tuplesB.add(tuple(columns, "/a/b/c1", "v1", "v2", "v3")); + + /* + * Note that unlike the previous sample sets this entry is pathed to + * /c2 in order to duplicate the c2 entry for tuplesA, but the fields + * are different. The comparator used by the IntersectComponent (and + * all other SetOperationComponents) compares on path ONLY. + */ + + tuplesB.add(tuple(columns, "/a/b/c2", "v4", "v5", "v6")); + + component = new IntersectComponent(context, columns, selects, false, true); + + final List results = component.execute(); + + /* + * Should get double rows /a/b/c1, v1, v2, v3 + */ + + assertThat(results.size(), is(2)); + } + + @Test + public void shouldExceptResultsForCompatibleSets() { + tuplesA.add(tuple(columns, "/a/b/c1", "v1", "v2", "v3")); + tuplesA.add(tuple(columns, "/a/b/c2", "v1", "v2", "v3")); + tuplesB.add(tuple(columns, "/a/b/c1", "v1", "v2", "v3")); + tuplesB.add(tuple(columns, "/a/b/c3", "v4", "v5", "v6")); + + component = new ExceptComponent(context, columns, selects, false, false); + + final List results = component.execute(); + + /* + * Should get single row /a/b/c2, v1, v2, v3 from tuplesA + */ + + assertThat(results.size(), is(1)); + } + + @Test + public void shouldExceptAllResultsForCompatibleSets() { + /* + * In order to test Except All, the A set needs to contain duplicates. + */ + + tuplesA.add(tuple(columns, "/a/b/c1", "v1", "v2", "v3")); + tuplesA.add(tuple(columns, "/a/b/c1", "v1", "v2", "v3")); + tuplesB.add(tuple(columns, "/a/b/c2", "v1", "v2", "v3")); + tuplesB.add(tuple(columns, "/a/b/c3", "v4", "v5", "v6")); + + component = new ExceptComponent(context, columns, selects, false, true); + + final List results = component.execute(); + + /* + * Should get both duplicate rows from tuplesA + */ + + assertThat(results.size(), is(2)); + } + +} -- 1.7.8.msysgit.0