/** * 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.join; import java.util.List ; import org.apache.jena.atlas.io.IndentedWriter ; import org.apache.jena.atlas.iterator.Iter ; import org.apache.jena.atlas.lib.StrUtils ; import org.apache.jena.query.ResultSet ; import org.apache.jena.query.ResultSetFactory ; import org.apache.jena.sparql.algebra.Table ; import org.apache.jena.sparql.algebra.TableFactory ; 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.sse.SSE ; import org.junit.Assert ; /** Tests for inner/equi joins */ public abstract class AbstractTestJoin extends Assert { protected static Table table0() { return parseTableInt("(table)") ; } // Table of one row and no colums. protected static Table table1() { return parseTableInt("(table (row))") ; } protected static Table tableD1() { return parseTableInt("(table", " (row (?a 1) (?b 2))", " (row (?a 1) (?b 3))", " (row (?a 1) (?b 2))", ")") ; } protected static Table tableD2() { return parseTableInt("(table", " (row (?a 0) (?d 8))", " (row (?a 1) (?c 9))", ")") ; } protected static Table tableD3() { return parseTableInt("(table", " (row (?a 1) (?c 9) (?b 2))", " (row (?a 1) (?c 9) (?b 3))", " (row (?a 1) (?c 9) (?b 2))", ")") ; } protected static Table tableD3_LJ() { return parseTableInt("(table", " (row (?d 8) (?a 0))", " (row (?a 1) (?c 9) (?b 2))", " (row (?a 1) (?c 9) (?b 3))", " (row (?a 1) (?c 9) (?b 2))", ")") ; } protected static Table tableD4() { return parseTableInt("(table", " (row (?a 1) (?b 2))", " (row (?a 1) (?b 3))", " (row (?a 4) (?b 4))", " (row (?a 4) (?b 5))", ")") ; } protected static Table tableD5() { return parseTableInt("(table", " (row (?a 4) (?c 4))", " (row (?a 4) (?c 5))", " (row (?a 6) (?c 5))", ")") ; } protected static Table tableD6() { return parseTableInt("(table", " (row (?a 1) (?c 2))", " (row (?a 1) (?c 3))", " (row (?a 4) (?c 4))", " (row (?a 4) (?c 5))", ")") ; } protected static Table tableD4x5() { return parseTableInt("(table", " (row (?a 4) (?c 4) (?b 4))", " (row (?a 4) (?c 4) (?b 5))", " (row (?a 4) (?c 5) (?b 4))", " (row (?a 4) (?c 5) (?b 5))", ")") ; } protected static Table tableD4x5_LJ() { return parseTableInt("(table", " (row (?a 4) (?c 4) (?b 4))", " (row (?a 4) (?c 4) (?b 5))", " (row (?a 4) (?c 5) (?b 4))", " (row (?a 4) (?c 5) (?b 5))", " (row (?b 2) (?a 1))", " (row (?b 3) (?a 1))", ")") ; } protected static Table tableD5x4_LJ() { return parseTableInt("(table", " (row (?a 4) (?c 4) (?b 4))", " (row (?a 4) (?c 4) (?b 5))", " (row (?a 4) (?c 5) (?b 4))", " (row (?a 4) (?c 5) (?b 5))", " (row (?a 6) (?c 5))", ")") ; } protected static Table tableD4x6() { return parseTableInt("(table", " (row (?a 1) (?c 2) (?b 2))", " (row (?a 1) (?c 2) (?b 3))", " (row (?a 1) (?c 3) (?b 2))", " (row (?a 1) (?c 3) (?b 3))", " (row (?a 4) (?c 4) (?b 4))", " (row (?a 4) (?c 4) (?b 5))", " (row (?a 4) (?c 5) (?b 4))", " (row (?a 4) (?c 5) (?b 5))", ")") ; } // Disjoint. protected static Table tableD8() { return parseTableInt("(table", " (row (?x 10))", " (row (?z 11))", ")") ; } // Table8 crossproduct table2 protected static Table tableD8x2() { return parseTableInt("(table", " (row (?a 0) (?d 8) (?z 11))", " (row (?a 0) (?d 8) (?x 10))", " (row (?a 1) (?c 9) (?z 11))", " (row (?a 1) (?c 9) (?x 10))", ")") ; } // Left join data tables. protected static Table tableL1() { return parseTableInt("(table", " (row (?a 0) (?d 8))", " (row (?a 3) (?d 9))", ")") ; } protected static Table tableL2() { return parseTableInt("(table", " (row (?a 0) (?z 11))", " (row (?a 1) (?c 9) (?z 11))", ")") ; } // L3 := L1 leftjoin L2 protected static Table table1LJ2() { return parseTableInt("(table", " (row (?a 0) (?d 8) (?z 11))", " (row (?a 3) (?d 9))", ")") ; } protected static Table tableL4() { return parseTableInt("(table", " (row (?a 0) (?z 11))", " (row (?a 0) (?z 12))", " (row (?r 99))", " (row (?c 9) (?z 11))", ")") ; } protected static Table tableL5() { return parseTableInt("(table", " (row (?a 0) (?d 8))", " (row (?a 1) (?c 9) (?z 11))", ")") ; } // L3 := L1 leftjoin L2 protected static Table table4LJ5() { return parseTableInt("(table", " (row (?a 0) (?d 8) (?z 11))", " (row (?a 0) (?d 8) (?z 12))", " (row (?a 0) (?d 8) (?r 99))", " (row (?a 1) (?c 9) (?z 11) (?r 99))", " (row (?a 0) (?d 8) (?c 9) (?z 11))", " (row (?a 1) (?c 9) (?z 11))", ")") ; } // Skew tables for join testing. // Join keys of ?x ?w and [?x , ?w] protected static Table tableS1() { return parseTableInt("(table" ," (row (?z <http://example/z1>) (?x <http://example/x>) (?w 'w11-1'))" ," (row (?z <http://example/z4>) (?x <http://example/x>)))" ); } protected static Table tableS2() { return parseTableInt("(table (row (?x <http://example/x>) (?w <http://example/z1>)))") ; } protected static Table tableS1J2() { return parseTableInt("(table" ," (row (?z <http://example/z4>) (?x <http://example/x>) (?w <http://example/z1>) ))" ); } // Code protected static Table parseTableInt(String... strings) { String x = StrUtils.strjoinNL(strings) ; return SSE.parseTable(x) ; } protected void testJoin(String var, Table left, Table right, Table tableOut) { testJoin(var, left, right, null, tableOut); } protected void testJoin(String var, Table left, Table right, String conditions, Table tableOut) { JoinKey joinKey ; if ( var != null ) { if ( var.startsWith("?") ) var = var.substring(1) ; joinKey = JoinKey.create(Var.alloc(var)) ; } else { // No vars in join key. Allow implementation to decide // if needed. Join keys are only needed for hash join // (and related algorithms). joinKey = null ; } ExprList exprs = null ; if ( conditions != null ) exprs = SSE.parseExprList(conditions) ; executeTest(joinKey, left, right, exprs, tableOut) ; } protected void testJoinWithKey(JoinKey joinKey, Table left, Table right, Table tableOut) { executeTest(joinKey, left, right, null, tableOut) ; } protected void testJoinWithKey(JoinKey joinKey, Table left, Table right, ExprList conditions, Table tableOut) { executeTest(joinKey, left, right, conditions, tableOut) ; } // Any kind of join (choose by abstract join() operation). protected abstract void executeTest(JoinKey joinKey, Table left, Table right, ExprList conditions, Table expectedResults) ; private List<Binding> toList(Table table) { return Iter.toList(table.rows()) ; } protected void executeTestJoin(String msg, JoinKey joinKey, Table left, Table right, ExprList conditions, Table expectedResults) { Table x1 = joinMaterialize(joinKey, left, right, conditions) ; assertNotNull("Null table from join ("+msg+")", x1) ; if ( false ) print(msg, joinKey, left, right, conditions, expectedResults, x1) ; check("Results not equal ("+msg+")", joinKey, left, right, conditions, expectedResults, x1) ; } private Table joinMaterialize(JoinKey joinKey, Table left, Table right, ExprList conditions) { QueryIterator qIter = join(joinKey, left , right, conditions) ; return TableFactory.create(qIter) ; } public abstract QueryIterator join(JoinKey joinKey, Table left , Table right, ExprList conditions) ; private static void check(String msg, JoinKey joinKey, Table left, Table right, ExprList conditions, Table expected, Table actual) { boolean b = equalTables(expected, actual) ; if ( ! b ) print(msg, joinKey, left, right, conditions, expected, actual); assertTrue(msg, b) ; } protected static void print(String msg, JoinKey joinKey, Table left, Table right, ExprList conditions, Table expected, Table actual) { System.err.flush() ; System.out.flush() ; IndentedWriter out = IndentedWriter.stderr ; out.println("Test : "+msg) ; out.println("Joinkey: "+joinKey) ; print(out, "Left:", left) ; print(out, "Right:", right) ; if ( conditions != null ) out.println("Conditions: "+conditions) ; print(out, "Expected:", expected) ; print(out, "Actual:", actual) ; out.println() ; out.flush() ; } protected static void print(IndentedWriter out, String label, Table table) { out.println(label) ; out.incIndent(); out.println(table.toString()) ; out.decIndent(); } 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) ; } }