| /* |
| * 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.calcite.test; |
| |
| import org.apache.calcite.DataContext; |
| import org.apache.calcite.jdbc.CalciteConnection; |
| import org.apache.calcite.linq4j.AbstractEnumerable; |
| import org.apache.calcite.linq4j.DelegatingEnumerator; |
| import org.apache.calcite.linq4j.Enumerable; |
| import org.apache.calcite.linq4j.Enumerator; |
| import org.apache.calcite.rel.type.RelDataType; |
| import org.apache.calcite.rel.type.RelDataTypeFactory; |
| import org.apache.calcite.rex.RexCall; |
| import org.apache.calcite.rex.RexInputRef; |
| import org.apache.calcite.rex.RexLiteral; |
| import org.apache.calcite.rex.RexNode; |
| import org.apache.calcite.runtime.Hook; |
| import org.apache.calcite.schema.FilterableTable; |
| import org.apache.calcite.schema.ProjectableFilterableTable; |
| import org.apache.calcite.schema.ScannableTable; |
| import org.apache.calcite.schema.Schema; |
| import org.apache.calcite.schema.SchemaPlus; |
| import org.apache.calcite.schema.Table; |
| import org.apache.calcite.schema.impl.AbstractSchema; |
| import org.apache.calcite.schema.impl.AbstractTable; |
| import org.apache.calcite.sql.fun.SqlStdOperatorTable; |
| import org.apache.calcite.sql.type.SqlTypeName; |
| import org.apache.calcite.test.CalciteAssert.ConnectionPostProcessor; |
| import org.apache.calcite.util.NlsString; |
| import org.apache.calcite.util.Pair; |
| |
| import com.google.common.collect.ImmutableMap; |
| |
| import org.checkerframework.checker.nullness.qual.Nullable; |
| import org.jetbrains.annotations.NotNull; |
| import org.junit.jupiter.api.Test; |
| |
| import java.math.BigDecimal; |
| import java.sql.Connection; |
| import java.sql.DriverManager; |
| import java.sql.PreparedStatement; |
| import java.sql.ResultSet; |
| import java.sql.SQLException; |
| import java.util.Arrays; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Properties; |
| import java.util.concurrent.atomic.AtomicInteger; |
| |
| import static org.hamcrest.CoreMatchers.equalTo; |
| import static org.hamcrest.CoreMatchers.is; |
| import static org.hamcrest.MatcherAssert.assertThat; |
| import static org.junit.jupiter.api.Assertions.assertFalse; |
| import static org.junit.jupiter.api.Assertions.assertTrue; |
| |
| /** |
| * Unit test for {@link org.apache.calcite.schema.ScannableTable}. |
| */ |
| public class ScannableTableTest { |
| @Test void testTens() throws SQLException { |
| final Enumerator<Object[]> cursor = tens(); |
| assertTrue(cursor.moveNext()); |
| assertThat(cursor.current()[0], equalTo((Object) 0)); |
| assertThat(cursor.current().length, equalTo(1)); |
| assertTrue(cursor.moveNext()); |
| assertThat(cursor.current()[0], equalTo((Object) 10)); |
| assertTrue(cursor.moveNext()); |
| assertThat(cursor.current()[0], equalTo((Object) 20)); |
| assertTrue(cursor.moveNext()); |
| assertThat(cursor.current()[0], equalTo((Object) 30)); |
| assertFalse(cursor.moveNext()); |
| } |
| |
| /** A table with one column. */ |
| @Test void testSimple() throws Exception { |
| CalciteAssert.that() |
| .with(newSchema("s", Pair.of("simple", new SimpleTable()))) |
| .query("select * from \"s\".\"simple\"") |
| .returnsUnordered("i=0", "i=10", "i=20", "i=30"); |
| } |
| |
| /** A table with two columns. */ |
| @Test void testSimple2() throws Exception { |
| CalciteAssert.that() |
| .with(newSchema("s", Pair.of("beatles", new BeatlesTable()))) |
| .query("select * from \"s\".\"beatles\"") |
| .returnsUnordered("i=4; j=John", |
| "i=4; j=Paul", |
| "i=6; j=George", |
| "i=5; j=Ringo"); |
| } |
| |
| /** A filter on a {@link FilterableTable} with two columns (cooperative). */ |
| @Test void testFilterableTableCooperative() throws Exception { |
| final StringBuilder buf = new StringBuilder(); |
| final Table table = new BeatlesFilterableTable(buf, true); |
| final String explain = "PLAN=" |
| + "EnumerableInterpreter\n" |
| + " BindableTableScan(table=[[s, beatles]], filters=[[=($0, 4)]])"; |
| CalciteAssert.that() |
| .with(newSchema("s", Pair.of("beatles", table))) |
| .query("select * from \"s\".\"beatles\" where \"i\" = 4") |
| .explainContains(explain) |
| .returnsUnordered("i=4; j=John; k=1940", |
| "i=4; j=Paul; k=1942"); |
| // Only 2 rows came out of the table. If the value is 4, it means that the |
| // planner did not pass the filter down. |
| assertThat(buf.toString(), is("returnCount=2, filter=<0, 4>")); |
| } |
| |
| /** A filter on a {@link FilterableTable} with two columns (noncooperative). */ |
| @Test void testFilterableTableNonCooperative() throws Exception { |
| final StringBuilder buf = new StringBuilder(); |
| final Table table = new BeatlesFilterableTable(buf, false); |
| final String explain = "PLAN=" |
| + "EnumerableInterpreter\n" |
| + " BindableTableScan(table=[[s, beatles2]], filters=[[=($0, 4)]])"; |
| CalciteAssert.that() |
| .with(newSchema("s", Pair.of("beatles2", table))) |
| .query("select * from \"s\".\"beatles2\" where \"i\" = 4") |
| .explainContains(explain) |
| .returnsUnordered("i=4; j=John; k=1940", |
| "i=4; j=Paul; k=1942"); |
| assertThat(buf.toString(), is("returnCount=4")); |
| } |
| |
| /** A filter on a {@link org.apache.calcite.schema.ProjectableFilterableTable} |
| * with two columns (cooperative). */ |
| @Test void testProjectableFilterableCooperative() throws Exception { |
| final StringBuilder buf = new StringBuilder(); |
| final Table table = new BeatlesProjectableFilterableTable(buf, true); |
| final String explain = "PLAN=" |
| + "EnumerableInterpreter\n" |
| + " BindableTableScan(table=[[s, beatles]], filters=[[=($0, 4)]], projects=[[1]])"; |
| CalciteAssert.that() |
| .with(newSchema("s", Pair.of("beatles", table))) |
| .query("select \"j\" from \"s\".\"beatles\" where \"i\" = 4") |
| .explainContains(explain) |
| .returnsUnordered("j=John", |
| "j=Paul"); |
| // Only 2 rows came out of the table. If the value is 4, it means that the |
| // planner did not pass the filter down. |
| assertThat(buf.toString(), is("returnCount=2, filter=<0, 4>, projects=[1]")); |
| } |
| |
| @Test void testProjectableFilterableNonCooperative() throws Exception { |
| final StringBuilder buf = new StringBuilder(); |
| final Table table = new BeatlesProjectableFilterableTable(buf, false); |
| final String explain = "PLAN=" |
| + "EnumerableInterpreter\n" |
| + " BindableTableScan(table=[[s, beatles2]], filters=[[=($0, 4)]], projects=[[1]]"; |
| CalciteAssert.that() |
| .with(newSchema("s", Pair.of("beatles2", table))) |
| .query("select \"j\" from \"s\".\"beatles2\" where \"i\" = 4") |
| .explainContains(explain) |
| .returnsUnordered("j=John", |
| "j=Paul"); |
| assertThat(buf.toString(), is("returnCount=4, projects=[1, 0]")); |
| } |
| |
| /** A filter on a {@link org.apache.calcite.schema.ProjectableFilterableTable} |
| * with two columns, and a project in the query. (Cooperative)*/ |
| @Test void testProjectableFilterableWithProjectAndFilter() throws Exception { |
| final StringBuilder buf = new StringBuilder(); |
| final Table table = new BeatlesProjectableFilterableTable(buf, true); |
| final String explain = "PLAN=" |
| + "EnumerableInterpreter\n" |
| + " BindableTableScan(table=[[s, beatles]], filters=[[=($0, 4)]], projects=[[2, 1]]"; |
| CalciteAssert.that() |
| .with(newSchema("s", Pair.of("beatles", table))) |
| .query("select \"k\",\"j\" from \"s\".\"beatles\" where \"i\" = 4") |
| .explainContains(explain) |
| .returnsUnordered("k=1940; j=John", |
| "k=1942; j=Paul"); |
| assertThat(buf.toString(), |
| is("returnCount=2, filter=<0, 4>, projects=[2, 1]")); |
| } |
| |
| /** A filter on a {@link org.apache.calcite.schema.ProjectableFilterableTable} |
| * with two columns, and a project in the query (NonCooperative). */ |
| @Test void testProjectableFilterableWithProjectFilterNonCooperative() |
| throws Exception { |
| final StringBuilder buf = new StringBuilder(); |
| final Table table = new BeatlesProjectableFilterableTable(buf, false); |
| final String explain = "PLAN=" |
| + "EnumerableInterpreter\n" |
| + " BindableTableScan(table=[[s, beatles]], filters=[[>($2, 1941)]], " |
| + "projects=[[0, 2]])"; |
| CalciteAssert.that() |
| .with(newSchema("s", Pair.of("beatles", table))) |
| .query("select \"i\",\"k\" from \"s\".\"beatles\" where \"k\" > 1941") |
| .explainContains(explain) |
| .returnsUnordered("i=4; k=1942", |
| "i=6; k=1943"); |
| assertThat(buf.toString(), |
| is("returnCount=4, projects=[0, 2]")); |
| } |
| |
| /** A filter and project on a |
| * {@link org.apache.calcite.schema.ProjectableFilterableTable}. The table |
| * refuses to execute the filter, so Calcite should add a pull up and |
| * transform the filter (projecting the column needed by the filter). */ |
| @Test void testPFTableRefusesFilterCooperative() throws Exception { |
| final StringBuilder buf = new StringBuilder(); |
| final Table table = new BeatlesProjectableFilterableTable(buf, false); |
| final String explain = "PLAN=EnumerableInterpreter\n" |
| + " BindableTableScan(table=[[s, beatles2]], filters=[[=($0, 4)]], projects=[[2]])"; |
| CalciteAssert.that() |
| .with(newSchema("s", Pair.of("beatles2", table))) |
| .query("select \"k\" from \"s\".\"beatles2\" where \"i\" = 4") |
| .explainContains(explain) |
| .returnsUnordered("k=1940", |
| "k=1942"); |
| assertThat(buf.toString(), |
| is("returnCount=4, projects=[2, 0]")); |
| } |
| |
| @Test void testPFPushDownProjectFilterInAggregateNoGroup() { |
| final StringBuilder buf = new StringBuilder(); |
| final Table table = new BeatlesProjectableFilterableTable(buf, false); |
| final String explain = "PLAN=EnumerableAggregate(group=[{}], M=[MAX($0)])\n" |
| + " EnumerableInterpreter\n" |
| + " BindableTableScan(table=[[s, beatles]], filters=[[>($0, 1)]], projects=[[2]])"; |
| CalciteAssert.that() |
| .with(newSchema("s", Pair.of("beatles", table))) |
| .query("select max(\"k\") as m from \"s\".\"beatles\" where \"i\" > 1") |
| .explainContains(explain) |
| .returnsUnordered("M=1943"); |
| } |
| |
| @Test void testPFPushDownProjectFilterAggregateGroup() { |
| final String sql = "select \"i\", count(*) as c\n" |
| + "from \"s\".\"beatles\"\n" |
| + "where \"k\" > 1900\n" |
| + "group by \"i\""; |
| final StringBuilder buf = new StringBuilder(); |
| final Table table = new BeatlesProjectableFilterableTable(buf, false); |
| final String explain = "PLAN=" |
| + "EnumerableAggregate(group=[{0}], C=[COUNT()])\n" |
| + " EnumerableInterpreter\n" |
| + " BindableTableScan(table=[[s, beatles]], filters=[[>($2, 1900)]], " |
| + "projects=[[0]])"; |
| CalciteAssert.that() |
| .with(newSchema("s", Pair.of("beatles", table))) |
| .query(sql) |
| .explainContains(explain) |
| .returnsUnordered("i=4; C=2", |
| "i=5; C=1", |
| "i=6; C=1"); |
| } |
| |
| @Test void testPFPushDownProjectFilterAggregateNested() { |
| final StringBuilder buf = new StringBuilder(); |
| final String sql = "select \"k\", count(*) as c\n" |
| + "from (\n" |
| + " select \"k\", \"i\" from \"s\".\"beatles\" group by \"k\", \"i\") t\n" |
| + "where \"k\" = 1940\n" |
| + "group by \"k\""; |
| final Table table = new BeatlesProjectableFilterableTable(buf, false); |
| final String explain = "PLAN=" |
| + "EnumerableAggregate(group=[{0}], C=[COUNT()])\n" |
| + " EnumerableAggregate(group=[{0, 1}])\n" |
| + " EnumerableInterpreter\n" |
| + " BindableTableScan(table=[[s, beatles]], filters=[[=($2, 1940)]], projects=[[2, 0]])"; |
| CalciteAssert.that() |
| .with(newSchema("s", Pair.of("beatles", table))) |
| .query(sql) |
| .explainContains(explain) |
| .returnsUnordered("k=1940; C=2"); |
| } |
| |
| private static Pair<Integer, Object> getFilter(boolean cooperative, List<RexNode> filters) { |
| final Iterator<RexNode> filterIter = filters.iterator(); |
| while (filterIter.hasNext()) { |
| final RexNode node = filterIter.next(); |
| if (cooperative |
| && node instanceof RexCall |
| && ((RexCall) node).getOperator() == SqlStdOperatorTable.EQUALS |
| && ((RexCall) node).getOperands().get(0) instanceof RexInputRef |
| && ((RexCall) node).getOperands().get(1) instanceof RexLiteral) { |
| filterIter.remove(); |
| final int pos = ((RexInputRef) ((RexCall) node).getOperands().get(0)).getIndex(); |
| final RexLiteral op1 = (RexLiteral) ((RexCall) node).getOperands().get(1); |
| switch (pos) { |
| case 0: |
| case 2: |
| return Pair.of(pos, ((BigDecimal) op1.getValue()).intValue()); |
| case 1: |
| return Pair.of(pos, ((NlsString) op1.getValue()).getValue()); |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** Test case for |
| * <a href="https://issues.apache.org/jira/browse/CALCITE-458">[CALCITE-458] |
| * ArrayIndexOutOfBoundsException when using just a single column in |
| * interpreter</a>. */ |
| @Test void testPFTableRefusesFilterSingleColumn() throws Exception { |
| final StringBuilder buf = new StringBuilder(); |
| final Table table = new BeatlesProjectableFilterableTable(buf, false); |
| final String explain = "PLAN=" |
| + "EnumerableInterpreter\n" |
| + " BindableTableScan(table=[[s, beatles2]], filters=[[>($2, 1941)]], projects=[[2]])"; |
| CalciteAssert.that() |
| .with(newSchema("s", Pair.of("beatles2", table))) |
| .query("select \"k\" from \"s\".\"beatles2\" where \"k\" > 1941") |
| .explainContains(explain) |
| .returnsUnordered("k=1942", |
| "k=1943"); |
| assertThat(buf.toString(), is("returnCount=4, projects=[2]")); |
| } |
| |
| /** Test case for |
| * <a href="https://issues.apache.org/jira/browse/CALCITE-3405">[CALCITE-3405] |
| * Prune columns for ProjectableFilterable when project is not simple mapping</a>. */ |
| @Test void testPushNonSimpleMappingProject() throws Exception { |
| final StringBuilder buf = new StringBuilder(); |
| final Table table = new BeatlesProjectableFilterableTable(buf, true); |
| final String explain = "PLAN=" |
| + "EnumerableCalc(expr#0..1=[{inputs}], expr#2=[+($t1, $t1)], expr#3=[3]," |
| + " proj#0..1=[{exprs}], k0=[$t0], $f3=[$t2], $f4=[$t3])\n" |
| + " EnumerableInterpreter\n" |
| + " BindableTableScan(table=[[s, beatles]], projects=[[2, 0]])"; |
| CalciteAssert.that() |
| .with(newSchema("s", Pair.of("beatles", table))) |
| .query("select \"k\", \"i\", \"k\", \"i\"+\"i\" \"ii\", 3 from \"s\".\"beatles\"") |
| .explainContains(explain) |
| .returnsUnordered( |
| "k=1940; i=4; k=1940; ii=8; EXPR$3=3", |
| "k=1940; i=5; k=1940; ii=10; EXPR$3=3", |
| "k=1942; i=4; k=1942; ii=8; EXPR$3=3", |
| "k=1943; i=6; k=1943; ii=12; EXPR$3=3"); |
| assertThat(buf.toString(), is("returnCount=4, projects=[2, 0]")); |
| } |
| |
| /** Test case for |
| * <a href="https://issues.apache.org/jira/browse/CALCITE-3405">[CALCITE-3405] |
| * Prune columns for ProjectableFilterable when project is not simple mapping</a>. */ |
| @Test void testPushSimpleMappingProject() throws Exception { |
| final StringBuilder buf = new StringBuilder(); |
| final Table table = new BeatlesProjectableFilterableTable(buf, true); |
| // Note that no redundant Project on EnumerableInterpreter |
| final String explain = "PLAN=" |
| + "EnumerableInterpreter\n" |
| + " BindableTableScan(table=[[s, beatles]], projects=[[2, 0]])"; |
| CalciteAssert.that() |
| .with(newSchema("s", Pair.of("beatles", table))) |
| .query("select \"k\", \"i\" from \"s\".\"beatles\"") |
| .explainContains(explain) |
| .returnsUnordered( |
| "k=1940; i=4", |
| "k=1940; i=5", |
| "k=1942; i=4", |
| "k=1943; i=6"); |
| assertThat(buf.toString(), is("returnCount=4, projects=[2, 0]")); |
| } |
| |
| /** Test case for |
| * <a href="https://issues.apache.org/jira/browse/CALCITE-3479">[CALCITE-3479] |
| * Stack overflow error thrown when running join query</a> |
| * Test two ProjectableFilterableTable can join and produce right plan. |
| */ |
| @Test void testProjectableFilterableTableJoin() throws Exception { |
| final StringBuilder buf = new StringBuilder(); |
| final String explain = "PLAN=" |
| + "EnumerableNestedLoopJoin(condition=[true], joinType=[inner])\n" |
| + " EnumerableInterpreter\n" |
| + " BindableTableScan(table=[[s, b1]], filters=[[=($0, 10)]])\n" |
| + " EnumerableInterpreter\n" |
| + " BindableTableScan(table=[[s, b2]], filters=[[=($0, 10)]])"; |
| CalciteAssert.that() |
| .with( |
| newSchema("s", |
| Pair.of("b1", new BeatlesProjectableFilterableTable(buf, true)), |
| Pair.of("b2", new BeatlesProjectableFilterableTable(buf, true)))) |
| .query("select * from \"s\".\"b1\", \"s\".\"b2\" " |
| + "where \"s\".\"b1\".\"i\" = 10 and \"s\".\"b2\".\"i\" = 10 " |
| + "and \"s\".\"b1\".\"i\" = \"s\".\"b2\".\"i\"") |
| .explainContains(explain); |
| } |
| |
| /** Test case for |
| * <a href="https://issues.apache.org/jira/browse/CALCITE-1031">[CALCITE-1031] |
| * In prepared statement, CsvScannableTable.scan is called twice</a>. */ |
| @Test void testPrepared2() throws SQLException { |
| final Properties properties = new Properties(); |
| properties.setProperty("caseSensitive", "true"); |
| try (Connection connection = |
| DriverManager.getConnection("jdbc:calcite:", properties)) { |
| final CalciteConnection calciteConnection = connection.unwrap( |
| CalciteConnection.class); |
| |
| final AtomicInteger scanCount = new AtomicInteger(); |
| final AtomicInteger enumerateCount = new AtomicInteger(); |
| final AtomicInteger closeCount = new AtomicInteger(); |
| final Schema schema = |
| new AbstractSchema() { |
| @Override protected Map<String, Table> getTableMap() { |
| return ImmutableMap.of("TENS", |
| countingTable(scanCount, enumerateCount, closeCount)); |
| } |
| }; |
| calciteConnection.getRootSchema().add("TEST", schema); |
| final String sql = "select * from \"TEST\".\"TENS\" where \"i\" < ?"; |
| final PreparedStatement statement = |
| calciteConnection.prepareStatement(sql); |
| assertThat(scanCount.get(), is(0)); |
| assertThat(enumerateCount.get(), is(0)); |
| |
| // First execute |
| statement.setInt(1, 20); |
| assertThat(scanCount.get(), is(0)); |
| ResultSet resultSet = statement.executeQuery(); |
| assertThat(scanCount.get(), is(1)); |
| assertThat(enumerateCount.get(), is(1)); |
| assertThat(resultSet, |
| Matchers.returnsUnordered("i=0", "i=10")); |
| assertThat(scanCount.get(), is(1)); |
| assertThat(enumerateCount.get(), is(1)); |
| |
| // Second execute |
| resultSet = statement.executeQuery(); |
| assertThat(scanCount.get(), is(2)); |
| assertThat(resultSet, |
| Matchers.returnsUnordered("i=0", "i=10")); |
| assertThat(scanCount.get(), is(2)); |
| |
| // Third execute |
| statement.setInt(1, 30); |
| resultSet = statement.executeQuery(); |
| assertThat(scanCount.get(), is(3)); |
| assertThat(resultSet, |
| Matchers.returnsUnordered("i=0", "i=10", "i=20")); |
| assertThat(scanCount.get(), is(3)); |
| } |
| } |
| |
| /** Test case for |
| * <a href="https://issues.apache.org/jira/browse/CALCITE-3758">[CALCITE-3758] |
| * FilterTableScanRule generate wrong mapping for filter condition |
| * when underlying is BindableTableScan</a>. */ |
| @Test public void testPFTableInBindableConvention() { |
| final StringBuilder buf = new StringBuilder(); |
| final Table table = new BeatlesProjectableFilterableTable(buf, true); |
| try (Hook.Closeable ignored = Hook.ENABLE_BINDABLE.addThread(Hook.propertyJ(true))) { |
| final String explain = "PLAN=" |
| + "BindableTableScan(table=[[s, beatles]], filters=[[=($1, 'John')]], projects=[[1]])"; |
| CalciteAssert.that() |
| .with(newSchema("s", Pair.of("beatles", table))) |
| .query("select \"j\" from \"s\".\"beatles\" where \"j\" = 'John'") |
| .explainContains(explain) |
| .returnsUnordered("j=John"); |
| assertThat(buf.toString(), |
| is("returnCount=1, filter=<1, John>, projects=[1]")); |
| } |
| } |
| |
| protected ConnectionPostProcessor newSchema(final String schemaName, |
| Pair<String, Table>... tables) { |
| return connection -> { |
| CalciteConnection con = connection.unwrap(CalciteConnection.class); |
| SchemaPlus rootSchema = con.getRootSchema(); |
| SchemaPlus schema = rootSchema.add(schemaName, new AbstractSchema()); |
| for (Pair<String, Table> t : tables) { |
| schema.add(t.left, t.right); |
| } |
| connection.setSchema(schemaName); |
| return connection; |
| }; |
| } |
| |
| /** Table that returns one column via the {@link ScannableTable} interface. */ |
| public static class SimpleTable extends AbstractTable |
| implements ScannableTable { |
| public RelDataType getRowType(RelDataTypeFactory typeFactory) { |
| return typeFactory.builder() |
| .add("i", SqlTypeName.INTEGER) |
| .build(); |
| } |
| |
| public Enumerable<@Nullable Object[]> scan(DataContext root) { |
| return new AbstractEnumerable<Object[]>() { |
| public Enumerator<Object[]> enumerator() { |
| return tens(); |
| } |
| }; |
| } |
| |
| } |
| |
| /** Table that returns two columns via the ScannableTable interface. */ |
| public static class BeatlesTable extends AbstractTable |
| implements ScannableTable { |
| public RelDataType getRowType(RelDataTypeFactory typeFactory) { |
| return typeFactory.builder() |
| .add("i", SqlTypeName.INTEGER) |
| .add("j", SqlTypeName.VARCHAR) |
| .build(); |
| } |
| |
| public Enumerable<@Nullable Object[]> scan(DataContext root) { |
| return new AbstractEnumerable<Object[]>() { |
| public Enumerator<Object[]> enumerator() { |
| return beatles(new StringBuilder(), null, null); |
| } |
| }; |
| } |
| } |
| |
| /** Table that returns two columns via the {@link FilterableTable} |
| * interface. */ |
| public static class BeatlesFilterableTable extends AbstractTable |
| implements FilterableTable { |
| private final StringBuilder buf; |
| private final boolean cooperative; |
| |
| public BeatlesFilterableTable(StringBuilder buf, boolean cooperative) { |
| this.buf = buf; |
| this.cooperative = cooperative; |
| } |
| |
| public RelDataType getRowType(RelDataTypeFactory typeFactory) { |
| return typeFactory.builder() |
| .add("i", SqlTypeName.INTEGER) |
| .add("j", SqlTypeName.VARCHAR) |
| .add("k", SqlTypeName.INTEGER) |
| .build(); |
| } |
| |
| public Enumerable<@Nullable Object[]> scan(DataContext root, List<RexNode> filters) { |
| final Pair<Integer, Object> filter = getFilter(cooperative, filters); |
| return new AbstractEnumerable<Object[]>() { |
| public Enumerator<Object[]> enumerator() { |
| return beatles(buf, filter, null); |
| } |
| }; |
| } |
| } |
| |
| /** Table that returns two columns via the {@link FilterableTable} |
| * interface. */ |
| public static class BeatlesProjectableFilterableTable |
| extends AbstractTable implements ProjectableFilterableTable { |
| private final StringBuilder buf; |
| private final boolean cooperative; |
| |
| BeatlesProjectableFilterableTable(StringBuilder buf, |
| boolean cooperative) { |
| this.buf = buf; |
| this.cooperative = cooperative; |
| } |
| |
| public RelDataType getRowType(RelDataTypeFactory typeFactory) { |
| return typeFactory.builder() |
| .add("i", SqlTypeName.INTEGER) |
| .add("j", SqlTypeName.VARCHAR) |
| .add("k", SqlTypeName.INTEGER) |
| .build(); |
| } |
| |
| public Enumerable<@Nullable Object[]> scan(DataContext root, List<RexNode> filters, |
| final int @Nullable [] projects) { |
| final Pair<Integer, Object> filter = getFilter(cooperative, filters); |
| return new AbstractEnumerable<Object[]>() { |
| public Enumerator<Object[]> enumerator() { |
| return beatles(buf, filter, projects); |
| } |
| }; |
| } |
| } |
| |
| private static Enumerator<Object[]> tens() { |
| return new Enumerator<Object[]>() { |
| int row = -1; |
| Object[] current; |
| |
| public Object[] current() { |
| return current; |
| } |
| |
| public boolean moveNext() { |
| if (++row < 4) { |
| current = new Object[] {row * 10}; |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| public void reset() { |
| row = -1; |
| } |
| |
| public void close() { |
| current = null; |
| } |
| }; |
| } |
| |
| /** Returns a table that counts the number of calls to |
| * {@link ScannableTable#scan}, {@link Enumerable#enumerator()}, |
| * and {@link Enumerator#close()}. */ |
| static SimpleTable countingTable(AtomicInteger scanCount, |
| AtomicInteger enumerateCount, AtomicInteger closeCount) { |
| return new SimpleTable() { |
| private Enumerable<Object[]> superScan(DataContext root) { |
| return super.scan(root); |
| } |
| |
| @Override public Enumerable<@Nullable Object[]> scan(DataContext root) { |
| scanCount.incrementAndGet(); |
| return new AbstractEnumerable<Object[]>() { |
| @NotNull @Override public Enumerator<Object[]> enumerator() { |
| enumerateCount.incrementAndGet(); |
| final Enumerator<Object[]> enumerator = |
| superScan(root).enumerator(); |
| return new DelegatingEnumerator<Object[]>(enumerator) { |
| @Override public void close() { |
| closeCount.incrementAndGet(); |
| super.close(); |
| } |
| }; |
| } |
| }; |
| } |
| }; |
| } |
| |
| private static final Object[][] BEATLES = { |
| {4, "John", 1940}, |
| {4, "Paul", 1942}, |
| {6, "George", 1943}, |
| {5, "Ringo", 1940} |
| }; |
| |
| private static Enumerator<Object[]> beatles(final StringBuilder buf, |
| final Pair<Integer, Object> filter, final int[] projects) { |
| return new Enumerator<Object[]>() { |
| int row = -1; |
| int returnCount = 0; |
| Object[] current; |
| |
| public Object[] current() { |
| return current; |
| } |
| |
| public boolean moveNext() { |
| while (++row < 4) { |
| Object[] current = BEATLES[row % 4]; |
| if (filter == null || filter.right.equals(current[filter.left])) { |
| if (projects == null) { |
| this.current = current; |
| } else { |
| Object[] newCurrent = new Object[projects.length]; |
| for (int i = 0; i < projects.length; i++) { |
| newCurrent[i] = current[projects[i]]; |
| } |
| this.current = newCurrent; |
| } |
| ++returnCount; |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| public void reset() { |
| row = -1; |
| } |
| |
| public void close() { |
| current = null; |
| buf.append("returnCount=").append(returnCount); |
| if (filter != null) { |
| buf.append(", filter=").append(filter); |
| } |
| if (projects != null) { |
| buf.append(", projects=").append(Arrays.toString(projects)); |
| } |
| } |
| }; |
| } |
| } |