/**
* 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 ;
}
}