/** * 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.engine.index ; import static org.apache.jena.reasoner.rulesys.Util.makeIntNode ; import static org.junit.Assert.assertEquals ; import static org.junit.Assert.assertTrue ; import static org.junit.Assert.assertFalse ; import static org.junit.Assert.fail ; import java.util.ArrayList ; import java.util.Collections ; import java.util.LinkedHashSet ; import java.util.List ; import java.util.Map ; import java.util.Set ; 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.engine.binding.BindingHashMap ; import org.apache.jena.sparql.engine.index.HashIndexTable ; import org.apache.jena.sparql.engine.index.IndexFactory ; import org.apache.jena.sparql.engine.index.IndexTable ; import org.apache.jena.sparql.engine.index.LinearIndex ; import org.apache.jena.sparql.engine.index.HashIndexTable.Key ; import org.apache.jena.sparql.engine.index.HashIndexTable.MissingBindingException ; import org.apache.jena.sparql.engine.iterator.QueryIterPlainWrapper ; import org.junit.Test ; import org.junit.Before ; /** * Tests the {@link HashIndexTable} and * {@link LinearIndex} classes. Also tests * that the {@link IndexFactory} instantiates * the correct type of index depending on the data. */ public class TestIndexTable { // Contribution from P Gearon (@quoll) private Var[] vars ; // sets of vars with different iteration orders private Set<Var> order1 ; private Set<Var> order2 ; private List<Binding> fData ; private List<Binding> pData ; @Before public void setup() { vars = new Var[] { Var.alloc("a"), Var.alloc("b"), Var.alloc("c") } ; order1 = new LinkedHashSet<>() ; order2 = new LinkedHashSet<>() ; for ( int i = 0 ; i < vars.length ; i++ ) { order1.add(vars[i]) ; order2.add(vars[vars.length - i - 1]) ; } fData = new ArrayList<>() ; pData = new ArrayList<>() ; for ( int i = 10 ; i <= 100 ; i += 10 ) { BindingHashMap bindingFull = new BindingHashMap() ; BindingHashMap bindingPart = new BindingHashMap() ; for ( int b = 0 ; b < vars.length ; b++ ) { bindingFull.add(vars[b], makeIntNode(i + b)) ; // 10,11,12 - 20,21,22 - 30,31,32 ... 100,101,102 if ( (i + b) % 7 != 0 ) bindingPart.add(vars[b], makeIntNode(i + b)) ; // skips 21, 42, 70, 91 } fData.add(bindingFull) ; pData.add(bindingPart) ; } } @Test public void testHashIndexTableConstruction() throws Exception { new HashIndexTable(order1, fullData()) ; assertTrue(IndexFactory.createIndex(order1, fullData()) instanceof HashIndexTable) ; assertTrue(IndexFactory.createIndex(order1, partData()) instanceof LinearIndex) ; try { new HashIndexTable(order1, partData()) ; fail("Index built without failure on partial bindings") ; } catch (MissingBindingException e) { // check that the expected mapping occurred Map<Var,Integer> map = e.getMap() ; for ( int i = 0 ; i < vars.length ; i++ ) { assertEquals(Integer.valueOf(i), map.get(vars[i])) ; } // check for rows of {a=10,b=11,c=12}, {a=20,c=22} Set<Key> data = e.getData() ; assertEquals(2, data.size()) ; for ( Key key: data ) { Binding b = LinearIndex.toBinding(key, map) ; if ( b.size() == 3 ) { for ( int i = 0 ; i < vars.length ; i++ ) assertEquals(b.get(vars[i]), makeIntNode(10 + i)) ; } else { assertEquals(b.get(vars[0]), makeIntNode(20)) ; assertEquals(b.get(vars[2]), makeIntNode(22)) ; } } } } @Test public void testHashIndexTableData() throws Exception { // test twice with different internal mappings testTableData(new HashIndexTable(order1, fullData())) ; testTableData(new HashIndexTable(order2, fullData())) ; } @Test public void testLinearIndexTableData() { // test twice with different internal mappings testTableData(IndexFactory.createIndex(order1, partData())) ; testTableData(IndexFactory.createIndex(order2, partData())) ; // test the linear index with full data, since this should also work Set<Key> emptyKeys = Collections.emptySet() ; Map<Var,Integer> emptyMapping = Collections.emptyMap() ; testTableData(new LinearIndex(order1, fullData(), emptyKeys, emptyMapping)) ; testTableData(new LinearIndex(order2, fullData(), emptyKeys, emptyMapping)) ; // construction directly from part data should also work testTableData(new LinearIndex(order1, partData(), emptyKeys, emptyMapping)) ; testTableData(new LinearIndex(order2, partData(), emptyKeys, emptyMapping)) ; } private void testTableData(IndexTable index) { // positive test for matching for ( Binding b: fData ) assertTrue(index.containsCompatibleWithSharedDomain(b)) ; assertTrue(index.containsCompatibleWithSharedDomain(binding("abcd", 10, 11, 12, 13))) ; assertTrue(index.containsCompatibleWithSharedDomain(binding("ab", 10, 11))) ; assertTrue(index.containsCompatibleWithSharedDomain(binding("bc", 11, 12))) ; assertTrue(index.containsCompatibleWithSharedDomain(binding("ac", 10, 12))) ; assertTrue(index.containsCompatibleWithSharedDomain(binding("a", 10))) ; assertTrue(index.containsCompatibleWithSharedDomain(binding("ab", 70, 71))) ; assertTrue(index.containsCompatibleWithSharedDomain(binding("bc", 71, 72))) ; assertTrue(index.containsCompatibleWithSharedDomain(binding("ac", 70, 72))) ; assertTrue(index.containsCompatibleWithSharedDomain(binding("a", 80))) ; // a=70 won't match for partData // negative test for matching assertFalse(index.containsCompatibleWithSharedDomain(binding("abc", 10, 11, 11))) ; assertFalse(index.containsCompatibleWithSharedDomain(binding("d", 10))) ; assertFalse(index.containsCompatibleWithSharedDomain(binding("abc", 10, 21, 32))) ; assertFalse(index.containsCompatibleWithSharedDomain(binding("xyz", 10, 11, 12))) ; } private QueryIterator fullData() { return new QueryIterPlainWrapper(fData.iterator()) ; } private QueryIterator partData() { return new QueryIterPlainWrapper(pData.iterator()) ; } /** * A convenience method that creates a binding of Vars with single letter names bound to integers. * @param varNames A string of variable names. The length must match the number of integers to bind to. * @param ints The values of the integers to be bound to the variables. */ private static Binding binding(String varNames, Integer... ints) { assert varNames.length() == ints.length ; BindingHashMap b = new BindingHashMap() ; for ( int s = 0 ; s < varNames.length() ; s++ ) b.add(Var.alloc(varNames.substring(s, s + 1)), makeIntNode(ints[s])) ; return b ; } }