blob: 6fd93e09d134846fea789d0db6adfaef5e670dd7 [file] [log] [blame]
/*
* 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.ignite.internal.sql.engine.planner;
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.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.List;
import java.util.function.UnaryOperator;
import org.apache.calcite.plan.RelOptUtil;
import org.apache.calcite.rex.RexFieldAccess;
import org.apache.ignite.internal.sql.engine.framework.TestBuilders.TableBuilder;
import org.apache.ignite.internal.sql.engine.prepare.bounds.ExactBounds;
import org.apache.ignite.internal.sql.engine.prepare.bounds.RangeBounds;
import org.apache.ignite.internal.sql.engine.prepare.bounds.SearchBounds;
import org.apache.ignite.internal.sql.engine.rel.IgniteCorrelatedNestedLoopJoin;
import org.apache.ignite.internal.sql.engine.rel.IgniteRel;
import org.apache.ignite.internal.sql.engine.rel.IgniteSortedIndexSpool;
import org.apache.ignite.internal.sql.engine.schema.IgniteIndex.Collation;
import org.apache.ignite.internal.sql.engine.schema.IgniteSchema;
import org.apache.ignite.internal.type.NativeTypes;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
/**
* SortedIndexSpoolPlannerTest.
* TODO Documentation https://issues.apache.org/jira/browse/IGNITE-15859
*/
@Disabled("https://issues.apache.org/jira/browse/IGNITE-18689")
public class SortedIndexSpoolPlannerTest extends AbstractPlannerTest {
/**
* Check equi-join on not colocated fields. CorrelatedNestedLoopJoinTest is applicable for this case only with IndexSpool.
*/
@Test
public void testNotColocatedEqJoin() throws Exception {
IgniteSchema publicSchema = createSchemaFrom(
tableA("T0").andThen(addSortIndex("JID", "ID")),
tableA("T1").andThen(addSortIndex("JID", "ID"))
);
String sql = "select * "
+ "from t0 "
+ "join t1 on t0.jid = t1.jid";
IgniteRel phys = physicalPlan(
sql,
publicSchema,
"HashJoinConverter", "MergeJoinConverter", "NestedLoopJoinConverter", "FilterSpoolMergeToHashIndexSpoolRule"
);
IgniteSortedIndexSpool idxSpool = findFirstNode(phys, byClass(IgniteSortedIndexSpool.class));
List<SearchBounds> searchBounds = idxSpool.searchBounds();
assertNotNull(searchBounds, "Invalid plan\n" + RelOptUtil.toString(phys));
assertEquals(2, searchBounds.size());
assertTrue(searchBounds.get(0) instanceof ExactBounds);
assertTrue(((ExactBounds) searchBounds.get(0)).bound() instanceof RexFieldAccess);
assertNull(searchBounds.get(1));
}
/**
* Check case when exists index (collation) isn't applied not for whole join condition but may be used by part of condition.
*/
@Test
public void testPartialIndexForCondition() throws Exception {
IgniteSchema publicSchema = createSchemaFrom(
tableB("T0"),
tableB("T1").andThen(addSortIndex("JID2", "JID1"))
);
String sql = "select * "
+ "from t0 "
+ "join t1 on t0.jid0 = t1.jid0 and t0.jid1 = t1.jid1";
IgniteRel phys = physicalPlan(
sql,
publicSchema,
"HashJoinConverter", "MergeJoinConverter", "NestedLoopJoinConverter", "FilterSpoolMergeToHashIndexSpoolRule"
);
System.out.println("+++ \n" + RelOptUtil.toString(phys));
IgniteSortedIndexSpool idxSpool = findFirstNode(phys, byClass(IgniteSortedIndexSpool.class));
List<SearchBounds> searchBounds = idxSpool.searchBounds();
assertNotNull(searchBounds, "Invalid plan\n" + RelOptUtil.toString(phys));
assertEquals(2, searchBounds.size());
assertTrue(searchBounds.get(0) instanceof ExactBounds);
assertTrue(((ExactBounds) searchBounds.get(0)).bound() instanceof RexFieldAccess);
assertTrue(searchBounds.get(1) instanceof ExactBounds);
assertTrue(((ExactBounds) searchBounds.get(1)).bound() instanceof RexFieldAccess);
}
/**
* Check colocated fields with DESC ordering.
*/
@Test
public void testDescFields() throws Exception {
IgniteSchema publicSchema = createSchemaFrom(
tableA("T0").andThen(setSize(10)),
tableA("T1").andThen(setSize(100)).andThen(index("jid", Collation.DESC_NULLS_LAST))
);
String sql = "select * "
+ "from t0 "
+ "join t1 on t1.jid < t0.jid";
assertPlan(sql, publicSchema,
isInstanceOf(IgniteCorrelatedNestedLoopJoin.class)
.and(input(1, isInstanceOf(IgniteSortedIndexSpool.class)
.and(spool -> {
List<SearchBounds> searchBounds = spool.searchBounds();
// Condition is LESS_THEN, but we have DESC field and condition should be in lower bound
// instead of upper bound.
assertNotNull(searchBounds);
assertEquals(1, searchBounds.size());
assertTrue(searchBounds.get(0) instanceof RangeBounds);
RangeBounds fld1Bounds = (RangeBounds) searchBounds.get(0);
assertTrue(fld1Bounds.lowerBound() instanceof RexFieldAccess);
assertFalse(fld1Bounds.lowerInclude());
// NULLS LAST in collation, so nulls can be skipped by upper bound.
assertEquals("$NULL_BOUND()", fld1Bounds.upperBound().toString());
assertFalse(fld1Bounds.upperInclude());
return true;
})
.and(hasChildThat(isIndexScan("T1", "idx_jid")))
)),
"HashJoinConverter", "MergeJoinConverter", "NestedLoopJoinConverter", "FilterSpoolMergeToHashIndexSpoolRule"
);
}
private static UnaryOperator<TableBuilder> tableA(String tableName) {
return tableBuilder -> tableBuilder
.name(tableName)
.addColumn("ID", NativeTypes.INT32)
.addColumn("JID", NativeTypes.INT32)
.addColumn("VAL", NativeTypes.STRING)
.distribution(someAffinity());
}
private static UnaryOperator<TableBuilder> tableB(String tableName) {
return tableBuilder -> tableBuilder
.name(tableName)
.addColumn("ID", NativeTypes.INT32)
.addColumn("JID0", NativeTypes.INT32)
.addColumn("JID1", NativeTypes.INT32)
.addColumn("VAL", NativeTypes.STRING)
.distribution(someAffinity());
}
private static UnaryOperator<TableBuilder> index(String column, Collation collation) {
return tableBuilder -> tableBuilder.sortedIndex()
.name("idx_" + column)
.addColumn(column, collation)
.end();
}
}