blob: 5299b6074e4c9d9873d26cdbc04a5613b1a0bbeb [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.calcite.test;
import org.apache.calcite.config.CalciteConnectionProperty;
import org.apache.calcite.config.Lex;
import org.apache.calcite.test.CalciteAssert.AssertThat;
import org.apache.calcite.test.CalciteAssert.DatabaseInstance;
import org.apache.calcite.test.schemata.foodmart.FoodmartSchema;
import org.apache.calcite.util.Smalls;
import org.apache.calcite.util.TestUtil;
import org.hsqldb.jdbcDriver;
import org.junit.jupiter.api.Test;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
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;
/**
* Tests for the {@code org.apache.calcite.adapter.jdbc} package.
*/
class JdbcAdapterTest {
/** Ensures that tests that are modifying data (doing DML) do not run at the
* same time. */
private static final ReentrantLock LOCK = new ReentrantLock();
/** VALUES is not pushed down, currently. */
@Test void testValuesPlan() {
final String sql = "select * from \"days\", (values 1, 2) as t(c)";
final String explain = "PLAN="
+ "EnumerableNestedLoopJoin(condition=[true], joinType=[inner])\n"
+ " JdbcToEnumerableConverter\n"
+ " JdbcTableScan(table=[[foodmart, days]])\n"
+ " EnumerableValues(tuples=[[{ 1 }, { 2 }]])";
final String jdbcSql = "SELECT *\n"
+ "FROM \"foodmart\".\"days\"";
CalciteAssert.model(FoodmartSchema.FOODMART_MODEL)
.query(sql)
.explainContains(explain)
.runs()
.enable(CalciteAssert.DB == CalciteAssert.DatabaseInstance.HSQLDB
|| CalciteAssert.DB == DatabaseInstance.POSTGRESQL)
.planHasSql(jdbcSql);
}
@Test void testUnionPlan() {
CalciteAssert.model(FoodmartSchema.FOODMART_MODEL)
.query("select * from \"sales_fact_1997\"\n"
+ "union all\n"
+ "select * from \"sales_fact_1998\"")
.explainContains("PLAN=JdbcToEnumerableConverter\n"
+ " JdbcUnion(all=[true])\n"
+ " JdbcTableScan(table=[[foodmart, sales_fact_1997]])\n"
+ " JdbcTableScan(table=[[foodmart, sales_fact_1998]])")
.runs()
.enable(CalciteAssert.DB == CalciteAssert.DatabaseInstance.HSQLDB)
.planHasSql("SELECT *\n"
+ "FROM \"foodmart\".\"sales_fact_1997\"\n"
+ "UNION ALL\n"
+ "SELECT *\n"
+ "FROM \"foodmart\".\"sales_fact_1998\"");
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-3115">[CALCITE-3115]
* Cannot add JdbcRules which have different JdbcConvention
* to same VolcanoPlanner's RuleSet</a>. */
@Test void testUnionPlan2() {
CalciteAssert.model(JdbcTest.FOODMART_SCOTT_MODEL)
.query("select \"store_name\" from \"foodmart\".\"store\" where \"store_id\" < 10\n"
+ "union all\n"
+ "select ename from SCOTT.emp where empno > 10")
.explainContains("PLAN=EnumerableUnion(all=[true])\n"
+ " JdbcToEnumerableConverter\n"
+ " JdbcProject(store_name=[$3])\n"
+ " JdbcFilter(condition=[<($0, 10)])\n"
+ " JdbcTableScan(table=[[foodmart, store]])\n"
+ " JdbcToEnumerableConverter\n"
+ " JdbcProject(ENAME=[$1])\n"
+ " JdbcFilter(condition=[>($0, 10)])\n"
+ " JdbcTableScan(table=[[SCOTT, EMP]])")
.runs()
.enable(CalciteAssert.DB == CalciteAssert.DatabaseInstance.HSQLDB)
.planHasSql("SELECT \"store_name\"\n"
+ "FROM \"foodmart\".\"store\"\n"
+ "WHERE \"store_id\" < 10")
.planHasSql("SELECT \"ENAME\"\n"
+ "FROM \"SCOTT\".\"EMP\"\n"
+ "WHERE \"EMPNO\" > 10");
}
@Test void testFilterUnionPlan() {
CalciteAssert.model(FoodmartSchema.FOODMART_MODEL)
.query("select * from (\n"
+ " select * from \"sales_fact_1997\"\n"
+ " union all\n"
+ " select * from \"sales_fact_1998\")\n"
+ "where \"product_id\" = 1")
.runs()
.enable(CalciteAssert.DB == CalciteAssert.DatabaseInstance.HSQLDB)
.planHasSql("SELECT *\n"
+ "FROM \"foodmart\".\"sales_fact_1997\"\n"
+ "WHERE \"product_id\" = 1\n"
+ "UNION ALL\n"
+ "SELECT *\n"
+ "FROM \"foodmart\".\"sales_fact_1998\"\n"
+ "WHERE \"product_id\" = 1");
}
@Test void testInPlan() {
CalciteAssert.model(FoodmartSchema.FOODMART_MODEL)
.query("select \"store_id\", \"store_name\" from \"store\"\n"
+ "where \"store_name\" in ('Store 1', 'Store 10', 'Store 11', 'Store 15', 'Store 16', 'Store 24', 'Store 3', 'Store 7')")
.runs()
.enable(CalciteAssert.DB == CalciteAssert.DatabaseInstance.HSQLDB)
.planHasSql("SELECT \"store_id\", \"store_name\"\n"
+ "FROM \"foodmart\".\"store\"\n"
+ "WHERE \"store_name\" IN ('Store 1', 'Store 10', 'Store 11',"
+ " 'Store 15', 'Store 16', 'Store 24', 'Store 3', 'Store 7')")
.returns("store_id=1; store_name=Store 1\n"
+ "store_id=3; store_name=Store 3\n"
+ "store_id=7; store_name=Store 7\n"
+ "store_id=10; store_name=Store 10\n"
+ "store_id=11; store_name=Store 11\n"
+ "store_id=15; store_name=Store 15\n"
+ "store_id=16; store_name=Store 16\n"
+ "store_id=24; store_name=Store 24\n");
}
@Test void testEquiJoinPlan() {
CalciteAssert.model(JdbcTest.SCOTT_MODEL)
.query("select empno, ename, e.deptno, dname\n"
+ "from scott.emp e inner join scott.dept d\n"
+ "on e.deptno = d.deptno")
.explainContains("PLAN=JdbcToEnumerableConverter\n"
+ " JdbcProject(EMPNO=[$0], ENAME=[$1], DEPTNO=[$2], DNAME=[$4])\n"
+ " JdbcJoin(condition=[=($2, $3)], joinType=[inner])\n"
+ " JdbcProject(EMPNO=[$0], ENAME=[$1], DEPTNO=[$7])\n"
+ " JdbcTableScan(table=[[SCOTT, EMP]])\n"
+ " JdbcProject(DEPTNO=[$0], DNAME=[$1])\n"
+ " JdbcTableScan(table=[[SCOTT, DEPT]])")
.runs()
.enable(CalciteAssert.DB == CalciteAssert.DatabaseInstance.HSQLDB)
.planHasSql("SELECT \"t\".\"EMPNO\", \"t\".\"ENAME\", "
+ "\"t\".\"DEPTNO\", \"t0\".\"DNAME\"\n"
+ "FROM (SELECT \"EMPNO\", \"ENAME\", \"DEPTNO\"\n"
+ "FROM \"SCOTT\".\"EMP\") AS \"t\"\n"
+ "INNER JOIN (SELECT \"DEPTNO\", \"DNAME\"\n"
+ "FROM \"SCOTT\".\"DEPT\") AS \"t0\" "
+ "ON \"t\".\"DEPTNO\" = \"t0\".\"DEPTNO\"");
}
@Test void testPushDownSort() {
CalciteAssert.model(JdbcTest.SCOTT_MODEL)
.with(CalciteConnectionProperty.TOPDOWN_OPT.camelName(), false)
.query("select ename\n"
+ "from scott.emp\n"
+ "order by empno")
.explainContains("PLAN=JdbcToEnumerableConverter\n"
+ " JdbcSort(sort0=[$1], dir0=[ASC])\n"
+ " JdbcProject(ENAME=[$1], EMPNO=[$0])\n"
+ " JdbcTableScan(table=[[SCOTT, EMP]])")
.runs()
.enable(CalciteAssert.DB == CalciteAssert.DatabaseInstance.HSQLDB)
.planHasSql("SELECT \"ENAME\", \"EMPNO\"\n"
+ "FROM \"SCOTT\".\"EMP\"\n"
+ "ORDER BY \"EMPNO\" NULLS LAST");
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-3751">[CALCITE-3751]
* JDBC adapter wrongly pushes ORDER BY into sub-query</a>. */
@Test void testOrderByPlan() {
final String sql = "select deptno, job, sum(sal)\n"
+ "from \"EMP\"\n"
+ "group by deptno, job\n"
+ "order by 1, 2";
final String explain = "PLAN=JdbcToEnumerableConverter\n"
+ " JdbcSort(sort0=[$0], sort1=[$1], dir0=[ASC], dir1=[ASC])\n"
+ " JdbcProject(DEPTNO=[$1], JOB=[$0], EXPR$2=[$2])\n"
+ " JdbcAggregate(group=[{2, 7}], EXPR$2=[SUM($5)])\n"
+ " JdbcTableScan(table=[[SCOTT, EMP]])";
final String sqlHsqldb = "SELECT \"DEPTNO\", \"JOB\", SUM(\"SAL\")\n"
+ "FROM \"SCOTT\".\"EMP\"\n"
+ "GROUP BY \"JOB\", \"DEPTNO\"\n"
+ "ORDER BY \"DEPTNO\" NULLS LAST, \"JOB\" NULLS LAST";
CalciteAssert.model(JdbcTest.SCOTT_MODEL)
.with(CalciteConnectionProperty.TOPDOWN_OPT.camelName(), false)
.query(sql)
.explainContains(explain)
.runs()
.enable(CalciteAssert.DB == CalciteAssert.DatabaseInstance.HSQLDB)
.planHasSql(sqlHsqldb);
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-631">[CALCITE-631]
* Push theta joins down to JDBC adapter</a>. */
@Test void testNonEquiJoinPlan() {
CalciteAssert.model(JdbcTest.SCOTT_MODEL)
.query("select empno, ename, grade\n"
+ "from scott.emp e inner join scott.salgrade s\n"
+ "on e.sal > s.losal and e.sal < s.hisal")
.explainContains("PLAN=JdbcToEnumerableConverter\n"
+ " JdbcProject(EMPNO=[$0], ENAME=[$1], GRADE=[$3])\n"
+ " JdbcJoin(condition=[AND(>($2, $4), <($2, $5))], joinType=[inner])\n"
+ " JdbcProject(EMPNO=[$0], ENAME=[$1], SAL=[$5])\n"
+ " JdbcTableScan(table=[[SCOTT, EMP]])\n"
+ " JdbcTableScan(table=[[SCOTT, SALGRADE]])")
.runs()
.enable(CalciteAssert.DB == CalciteAssert.DatabaseInstance.HSQLDB)
.planHasSql("SELECT \"t\".\"EMPNO\", \"t\".\"ENAME\", \"SALGRADE\".\"GRADE\"\n"
+ "FROM (SELECT \"EMPNO\", \"ENAME\", \"SAL\"\n"
+ "FROM \"SCOTT\".\"EMP\") AS \"t\"\n"
+ "INNER JOIN \"SCOTT\".\"SALGRADE\" "
+ "ON \"t\".\"SAL\" > \"SALGRADE\".\"LOSAL\" "
+ "AND \"t\".\"SAL\" < \"SALGRADE\".\"HISAL\"");
}
@Test void testNonEquiJoinReverseConditionPlan() {
CalciteAssert.model(JdbcTest.SCOTT_MODEL)
.query("select empno, ename, grade\n"
+ "from scott.emp e inner join scott.salgrade s\n"
+ "on s.losal <= e.sal and s.hisal >= e.sal")
.explainContains("PLAN=JdbcToEnumerableConverter\n"
+ " JdbcProject(EMPNO=[$0], ENAME=[$1], GRADE=[$3])\n"
+ " JdbcJoin(condition=[AND(<=($4, $2), >=($5, $2))], joinType=[inner])\n"
+ " JdbcProject(EMPNO=[$0], ENAME=[$1], SAL=[$5])\n"
+ " JdbcTableScan(table=[[SCOTT, EMP]])\n"
+ " JdbcTableScan(table=[[SCOTT, SALGRADE]])")
.runs()
.enable(CalciteAssert.DB == CalciteAssert.DatabaseInstance.HSQLDB)
.planHasSql("SELECT \"t\".\"EMPNO\", \"t\".\"ENAME\", \"SALGRADE\".\"GRADE\"\n"
+ "FROM (SELECT \"EMPNO\", \"ENAME\", \"SAL\"\n"
+ "FROM \"SCOTT\".\"EMP\") AS \"t\"\n"
+ "INNER JOIN \"SCOTT\".\"SALGRADE\" ON \"t\".\"SAL\" >= \"SALGRADE\".\"LOSAL\" "
+ "AND \"t\".\"SAL\" <= \"SALGRADE\".\"HISAL\"");
}
@Test void testMixedJoinPlan() {
CalciteAssert.model(JdbcTest.SCOTT_MODEL)
.query("select e.empno, e.ename, e.empno, e.ename\n"
+ "from scott.emp e inner join scott.emp m on\n"
+ "e.mgr = m.empno and e.sal > m.sal")
.explainContains("PLAN=JdbcToEnumerableConverter\n"
+ " JdbcProject(EMPNO=[$0], ENAME=[$1], EMPNO0=[$0], ENAME0=[$1])\n"
+ " JdbcJoin(condition=[AND(=($2, $4), >($3, $5))], joinType=[inner])\n"
+ " JdbcProject(EMPNO=[$0], ENAME=[$1], MGR=[$3], SAL=[$5])\n"
+ " JdbcTableScan(table=[[SCOTT, EMP]])\n"
+ " JdbcProject(EMPNO=[$0], SAL=[$5])\n"
+ " JdbcTableScan(table=[[SCOTT, EMP]])")
.runs()
.enable(CalciteAssert.DB == CalciteAssert.DatabaseInstance.HSQLDB)
.planHasSql("SELECT \"t\".\"EMPNO\", \"t\".\"ENAME\", "
+ "\"t\".\"EMPNO\" AS \"EMPNO0\", \"t\".\"ENAME\" AS \"ENAME0\"\n"
+ "FROM (SELECT \"EMPNO\", \"ENAME\", \"MGR\", \"SAL\"\n"
+ "FROM \"SCOTT\".\"EMP\") AS \"t\"\n"
+ "INNER JOIN (SELECT \"EMPNO\", \"SAL\"\n"
+ "FROM \"SCOTT\".\"EMP\") AS \"t0\" "
+ "ON \"t\".\"MGR\" = \"t0\".\"EMPNO\" AND \"t\".\"SAL\" > \"t0\".\"SAL\"");
}
@Test void testMixedJoinWithOrPlan() {
CalciteAssert.model(JdbcTest.SCOTT_MODEL)
.query("select e.empno, e.ename, e.empno, e.ename\n"
+ "from scott.emp e inner join scott.emp m on\n"
+ "e.mgr = m.empno and (e.sal > m.sal or m.hiredate > e.hiredate)")
.explainContains("PLAN=JdbcToEnumerableConverter\n"
+ " JdbcProject(EMPNO=[$0], ENAME=[$1], EMPNO0=[$0], ENAME0=[$1])\n"
+ " JdbcJoin(condition=[AND(=($2, $5), OR(>($4, $7), >($6, $3)))], joinType=[inner])\n"
+ " JdbcProject(EMPNO=[$0], ENAME=[$1], MGR=[$3], HIREDATE=[$4], SAL=[$5])\n"
+ " JdbcTableScan(table=[[SCOTT, EMP]])\n"
+ " JdbcProject(EMPNO=[$0], HIREDATE=[$4], SAL=[$5])\n"
+ " JdbcTableScan(table=[[SCOTT, EMP]])")
.runs()
.enable(CalciteAssert.DB == CalciteAssert.DatabaseInstance.HSQLDB)
.planHasSql("SELECT \"t\".\"EMPNO\", \"t\".\"ENAME\", "
+ "\"t\".\"EMPNO\" AS \"EMPNO0\", \"t\".\"ENAME\" AS \"ENAME0\"\n"
+ "FROM (SELECT \"EMPNO\", \"ENAME\", \"MGR\", \"HIREDATE\", \"SAL\"\n"
+ "FROM \"SCOTT\".\"EMP\") AS \"t\"\n"
+ "INNER JOIN (SELECT \"EMPNO\", \"HIREDATE\", \"SAL\"\n"
+ "FROM \"SCOTT\".\"EMP\") AS \"t0\" "
+ "ON \"t\".\"MGR\" = \"t0\".\"EMPNO\" "
+ "AND (\"t\".\"SAL\" > \"t0\".\"SAL\" OR \"t\".\"HIREDATE\" < \"t0\".\"HIREDATE\")");
}
@Test void testJoin3TablesPlan() {
CalciteAssert.model(JdbcTest.SCOTT_MODEL)
.query("select empno, ename, dname, grade\n"
+ "from scott.emp e inner join scott.dept d\n"
+ "on e.deptno = d.deptno\n"
+ "inner join scott.salgrade s\n"
+ "on e.sal > s.losal and e.sal < s.hisal")
.explainContains("PLAN=JdbcToEnumerableConverter\n"
+ " JdbcProject(EMPNO=[$0], ENAME=[$1], DNAME=[$5], GRADE=[$6])\n"
+ " JdbcJoin(condition=[AND(>($2, $7), <($2, $8))], joinType=[inner])\n"
+ " JdbcJoin(condition=[=($3, $4)], joinType=[inner])\n"
+ " JdbcProject(EMPNO=[$0], ENAME=[$1], SAL=[$5], DEPTNO=[$7])\n"
+ " JdbcTableScan(table=[[SCOTT, EMP]])\n"
+ " JdbcProject(DEPTNO=[$0], DNAME=[$1])\n"
+ " JdbcTableScan(table=[[SCOTT, DEPT]])\n"
+ " JdbcTableScan(table=[[SCOTT, SALGRADE]])\n")
.runs()
.enable(CalciteAssert.DB == CalciteAssert.DatabaseInstance.HSQLDB)
.planHasSql("SELECT \"t\".\"EMPNO\", \"t\".\"ENAME\", "
+ "\"t0\".\"DNAME\", \"SALGRADE\".\"GRADE\"\n"
+ "FROM (SELECT \"EMPNO\", \"ENAME\", \"SAL\", \"DEPTNO\"\n"
+ "FROM \"SCOTT\".\"EMP\") AS \"t\"\n"
+ "INNER JOIN (SELECT \"DEPTNO\", \"DNAME\"\n"
+ "FROM \"SCOTT\".\"DEPT\") AS \"t0\" ON \"t\".\"DEPTNO\" = \"t0\".\"DEPTNO\"\n"
+ "INNER JOIN \"SCOTT\".\"SALGRADE\" "
+ "ON \"t\".\"SAL\" > \"SALGRADE\".\"LOSAL\" "
+ "AND \"t\".\"SAL\" < \"SALGRADE\".\"HISAL\"");
}
@Test void testCrossJoinWithJoinKeyPlan() {
CalciteAssert.model(JdbcTest.SCOTT_MODEL)
.query("select empno, ename, d.deptno, dname\n"
+ "from scott.emp e,scott.dept d\n"
+ "where e.deptno = d.deptno")
.explainContains("PLAN=JdbcToEnumerableConverter\n"
+ " JdbcProject(EMPNO=[$0], ENAME=[$1], DEPTNO=[$3], DNAME=[$4])\n"
+ " JdbcJoin(condition=[=($2, $3)], joinType=[inner])\n"
+ " JdbcProject(EMPNO=[$0], ENAME=[$1], DEPTNO=[$7])\n"
+ " JdbcTableScan(table=[[SCOTT, EMP]])\n"
+ " JdbcProject(DEPTNO=[$0], DNAME=[$1])\n"
+ " JdbcTableScan(table=[[SCOTT, DEPT]])")
.runs()
.enable(CalciteAssert.DB == CalciteAssert.DatabaseInstance.HSQLDB)
.planHasSql("SELECT \"t\".\"EMPNO\", \"t\".\"ENAME\", "
+ "\"t0\".\"DEPTNO\", \"t0\".\"DNAME\"\n"
+ "FROM (SELECT \"EMPNO\", \"ENAME\", \"DEPTNO\"\nFROM \"SCOTT\".\"EMP\") AS \"t\"\n"
+ "INNER JOIN (SELECT \"DEPTNO\", \"DNAME\"\n"
+ "FROM \"SCOTT\".\"DEPT\") AS \"t0\" ON \"t\".\"DEPTNO\" = \"t0\".\"DEPTNO\"");
}
// JdbcJoin not used for this
@Test void testCartesianJoinWithoutKeyPlan() {
CalciteAssert.model(JdbcTest.SCOTT_MODEL)
.query("select empno, ename, d.deptno, dname\n"
+ "from scott.emp e,scott.dept d")
.explainContains("PLAN=EnumerableNestedLoopJoin(condition=[true], "
+ "joinType=[inner])\n"
+ " JdbcToEnumerableConverter\n"
+ " JdbcProject(EMPNO=[$0], ENAME=[$1])\n"
+ " JdbcTableScan(table=[[SCOTT, EMP]])\n"
+ " JdbcToEnumerableConverter\n"
+ " JdbcProject(DEPTNO=[$0], DNAME=[$1])\n"
+ " JdbcTableScan(table=[[SCOTT, DEPT]])")
.runs()
.enable(CalciteAssert.DB == CalciteAssert.DatabaseInstance.HSQLDB);
}
@Test void testCrossJoinWithJoinKeyAndFilterPlan() {
CalciteAssert.model(JdbcTest.SCOTT_MODEL)
.query("select empno, ename, d.deptno, dname\n"
+ "from scott.emp e,scott.dept d\n"
+ "where e.deptno = d.deptno\n"
+ "and e.deptno=20")
.explainContains("PLAN=JdbcToEnumerableConverter\n"
+ " JdbcProject(EMPNO=[$0], ENAME=[$1], DEPTNO=[$3], DNAME=[$4])\n"
+ " JdbcJoin(condition=[=($2, $3)], joinType=[inner])\n"
+ " JdbcProject(EMPNO=[$0], ENAME=[$1], DEPTNO=[$7])\n"
+ " JdbcFilter(condition=[=(CAST($7):INTEGER, 20)])\n"
+ " JdbcTableScan(table=[[SCOTT, EMP]])\n"
+ " JdbcProject(DEPTNO=[$0], DNAME=[$1])\n"
+ " JdbcTableScan(table=[[SCOTT, DEPT]])")
.runs()
.enable(CalciteAssert.DB == CalciteAssert.DatabaseInstance.HSQLDB)
.planHasSql("SELECT \"t0\".\"EMPNO\", \"t0\".\"ENAME\", "
+ "\"t1\".\"DEPTNO\", \"t1\".\"DNAME\"\n"
+ "FROM (SELECT \"EMPNO\", \"ENAME\", \"DEPTNO\"\n"
+ "FROM \"SCOTT\".\"EMP\"\n"
+ "WHERE CAST(\"DEPTNO\" AS INTEGER) = 20) AS \"t0\"\n"
+ "INNER JOIN (SELECT \"DEPTNO\", \"DNAME\"\n"
+ "FROM \"SCOTT\".\"DEPT\") AS \"t1\" ON \"t0\".\"DEPTNO\" = \"t1\".\"DEPTNO\"");
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-893">[CALCITE-893]
* Theta join in JdbcAdapter</a>. */
@Test void testJoinPlan() {
final String sql = "SELECT T1.\"brand_name\"\n"
+ "FROM \"foodmart\".\"product\" AS T1\n"
+ " INNER JOIN \"foodmart\".\"product_class\" AS T2\n"
+ " ON T1.\"product_class_id\" = T2.\"product_class_id\"\n"
+ "WHERE T2.\"product_department\" = 'Frozen Foods'\n"
+ " OR T2.\"product_department\" = 'Baking Goods'\n"
+ " AND T1.\"brand_name\" <> 'King'";
CalciteAssert.model(FoodmartSchema.FOODMART_MODEL)
.query(sql).runs()
.returnsCount(275);
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-1372">[CALCITE-1372]
* JDBC adapter generates SQL with wrong field names</a>. */
@Test void testJoinPlan2() {
final String sql = "SELECT v1.deptno, v2.deptno\n"
+ "FROM Scott.dept v1 LEFT JOIN Scott.emp v2 ON v1.deptno = v2.deptno\n"
+ "WHERE v2.job LIKE 'PRESIDENT'";
CalciteAssert.model(JdbcTest.SCOTT_MODEL)
.with(Lex.MYSQL)
.query(sql).runs()
.returnsCount(1);
}
@Test void testJoinCartesian() {
final String sql = "SELECT *\n"
+ "FROM Scott.dept, Scott.emp";
CalciteAssert.model(JdbcTest.SCOTT_MODEL).query(sql).returnsCount(56);
}
@Test void testJoinCartesianCount() {
final String sql = "SELECT count(*) as c\n"
+ "FROM Scott.dept, Scott.emp";
CalciteAssert.model(JdbcTest.SCOTT_MODEL).query(sql).returns("C=56\n");
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-1382">[CALCITE-1382]
* ClassCastException in JDBC adapter</a>. */
@Test public void testJoinPlan3() {
final String sql = "SELECT count(*) AS c FROM (\n"
+ " SELECT count(emp.empno) `Count Emp`,\n"
+ " dept.dname `Department Name`\n"
+ " FROM emp emp\n"
+ " JOIN dept dept ON emp.deptno = dept.deptno\n"
+ " JOIN salgrade salgrade ON emp.comm = salgrade.hisal\n"
+ " WHERE dept.dname LIKE '%A%'\n"
+ " GROUP BY emp.deptno, dept.dname)";
final String expected = "c=1\n";
final String expectedSql = "SELECT COUNT(*) AS \"c\"\n"
+ "FROM (SELECT \"t0\".\"DEPTNO\", \"t2\".\"DNAME\"\n"
+ "FROM (SELECT \"HISAL\"\n"
+ "FROM \"SCOTT\".\"SALGRADE\") AS \"t\"\n"
+ "INNER JOIN ((SELECT \"COMM\", \"DEPTNO\"\n"
+ "FROM \"SCOTT\".\"EMP\") AS \"t0\" "
+ "INNER JOIN (SELECT \"DEPTNO\", \"DNAME\"\n"
+ "FROM \"SCOTT\".\"DEPT\"\n"
+ "WHERE \"DNAME\" LIKE '%A%') AS \"t2\" "
+ "ON \"t0\".\"DEPTNO\" = \"t2\".\"DEPTNO\") "
+ "ON \"t\".\"HISAL\" = \"t0\".\"COMM\"\n"
+ "GROUP BY \"t0\".\"DEPTNO\", \"t2\".\"DNAME\") AS \"t3\"";
CalciteAssert.model(JdbcTest.SCOTT_MODEL)
.with(Lex.MYSQL)
.query(sql)
.returns(expected)
.planHasSql(expectedSql);
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-657">[CALCITE-657]
* NullPointerException when executing JdbcAggregate implement method</a>. */
@Test void testJdbcAggregate() throws Exception {
final String url = MultiJdbcSchemaJoinTest.TempDb.INSTANCE.getUrl();
Connection baseConnection = DriverManager.getConnection(url);
Statement baseStmt = baseConnection.createStatement();
baseStmt.execute("CREATE TABLE T2 (\n"
+ "ID INTEGER,\n"
+ "VALS INTEGER)");
baseStmt.execute("INSERT INTO T2 VALUES (1, 1)");
baseStmt.execute("INSERT INTO T2 VALUES (2, null)");
baseStmt.close();
baseConnection.commit();
Properties info = new Properties();
info.put("model",
"inline:"
+ "{\n"
+ " version: '1.0',\n"
+ " defaultSchema: 'BASEJDBC',\n"
+ " schemas: [\n"
+ " {\n"
+ " type: 'jdbc',\n"
+ " name: 'BASEJDBC',\n"
+ " jdbcDriver: '" + jdbcDriver.class.getName() + "',\n"
+ " jdbcUrl: '" + url + "',\n"
+ " jdbcCatalog: null,\n"
+ " jdbcSchema: null\n"
+ " }\n"
+ " ]\n"
+ "}");
final Connection calciteConnection =
DriverManager.getConnection("jdbc:calcite:", info);
ResultSet rs = calciteConnection
.prepareStatement("select 10 * count(ID) from t2").executeQuery();
assertThat(rs.next(), is(true));
assertThat(rs.getObject(1), equalTo(20L));
assertThat(rs.next(), is(false));
rs.close();
calciteConnection.close();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-2206">[CALCITE-2206]
* JDBC adapter incorrectly pushes windowed aggregates down to HSQLDB</a>. */
@Test void testOverNonSupportedDialect() {
final String sql = "select \"store_id\", \"account_id\", \"exp_date\",\n"
+ " \"time_id\", \"category_id\", \"currency_id\", \"amount\",\n"
+ " last_value(\"time_id\") over () as \"last_version\"\n"
+ "from \"expense_fact\"";
final String explain = "PLAN="
+ "EnumerableWindow(window#0=[window(aggs [LAST_VALUE($3)])])\n"
+ " JdbcToEnumerableConverter\n"
+ " JdbcTableScan(table=[[foodmart, expense_fact]])\n";
CalciteAssert
.model(FoodmartSchema.FOODMART_MODEL)
.enable(CalciteAssert.DB == DatabaseInstance.HSQLDB)
.query(sql)
.explainContains(explain)
.runs()
.planHasSql("SELECT *\n"
+ "FROM \"foodmart\".\"expense_fact\"");
}
@Test void testTablesNoCatalogSchema() {
final String model =
FoodmartSchema.FOODMART_MODEL
.replace("jdbcCatalog: 'foodmart'", "jdbcCatalog: null")
.replace("jdbcSchema: 'foodmart'", "jdbcSchema: null");
// Since Calcite uses PostgreSQL JDBC driver version >= 4.1,
// catalog/schema can be retrieved from JDBC connection and
// this test succeeds
CalciteAssert.model(model)
// Calcite uses PostgreSQL JDBC driver version >= 4.1
.enable(CalciteAssert.DB == DatabaseInstance.POSTGRESQL)
.query("select \"store_id\", \"account_id\", \"exp_date\","
+ " \"time_id\", \"category_id\", \"currency_id\", \"amount\","
+ " last_value(\"time_id\") over ()"
+ " as \"last_version\" from \"expense_fact\"")
.runs();
// Since Calcite uses HSQLDB JDBC driver version < 4.1,
// catalog/schema cannot be retrieved from JDBC connection and
// this test fails
CalciteAssert.model(model)
.enable(CalciteAssert.DB == DatabaseInstance.HSQLDB)
.query("select \"store_id\", \"account_id\", \"exp_date\","
+ " \"time_id\", \"category_id\", \"currency_id\", \"amount\","
+ " last_value(\"time_id\") over ()"
+ " as \"last_version\" from \"expense_fact\"")
.throws_("'expense_fact' not found");
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-1506">[CALCITE-1506]
* Push OVER Clause to underlying SQL via JDBC adapter</a>.
*
* <p>Test runs only on Postgres; the default database, Hsqldb, does not
* support OVER. */
@Test void testOverDefault() {
CalciteAssert
.model(FoodmartSchema.FOODMART_MODEL)
.enable(CalciteAssert.DB == CalciteAssert.DatabaseInstance.POSTGRESQL)
.query("select \"store_id\", \"account_id\", \"exp_date\","
+ " \"time_id\", \"category_id\", \"currency_id\", \"amount\","
+ " last_value(\"time_id\") over ()"
+ " as \"last_version\" from \"expense_fact\"")
.explainContains("PLAN=JdbcToEnumerableConverter\n"
+ " JdbcProject(store_id=[$0], account_id=[$1], exp_date=[$2], "
+ "time_id=[$3], category_id=[$4], currency_id=[$5], amount=[$6],"
+ " last_version=[LAST_VALUE($3) OVER (RANGE BETWEEN UNBOUNDED"
+ " PRECEDING AND UNBOUNDED FOLLOWING)])\n"
+ " JdbcTableScan(table=[[foodmart, expense_fact]])\n")
.runs()
.planHasSql("SELECT \"store_id\", \"account_id\", \"exp_date\","
+ " \"time_id\", \"category_id\", \"currency_id\", \"amount\","
+ " LAST_VALUE(\"time_id\") OVER (RANGE BETWEEN UNBOUNDED"
+ " PRECEDING AND UNBOUNDED FOLLOWING) AS \"last_version\"\n"
+ "FROM \"foodmart\".\"expense_fact\"");
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-2305">[CALCITE-2305]
* JDBC adapter generates invalid casts on PostgreSQL, because PostgreSQL does
* not have TINYINT and DOUBLE types</a>. */
@Test void testCast() {
CalciteAssert
.model(FoodmartSchema.FOODMART_MODEL)
.enable(CalciteAssert.DB == CalciteAssert.DatabaseInstance.POSTGRESQL)
.query("select cast(\"store_id\" as TINYINT),"
+ "cast(\"store_id\" as DOUBLE)"
+ " from \"expense_fact\"")
.runs()
.planHasSql("SELECT CAST(\"store_id\" AS SMALLINT),"
+ " CAST(\"store_id\" AS DOUBLE PRECISION)\n"
+ "FROM \"foodmart\".\"expense_fact\"");
}
@Test void testOverRowsBetweenBoundFollowingAndFollowing() {
CalciteAssert
.model(FoodmartSchema.FOODMART_MODEL)
.enable(CalciteAssert.DB == CalciteAssert.DatabaseInstance.POSTGRESQL)
.query("select \"store_id\", \"account_id\", \"exp_date\","
+ " \"time_id\", \"category_id\", \"currency_id\", \"amount\","
+ " last_value(\"time_id\") over (partition by \"account_id\""
+ " order by \"time_id\" rows between 1 following and 10 following)"
+ " as \"last_version\" from \"expense_fact\"")
.explainContains("PLAN=JdbcToEnumerableConverter\n"
+ " JdbcProject(store_id=[$0], account_id=[$1], exp_date=[$2], "
+ "time_id=[$3], category_id=[$4], currency_id=[$5], amount=[$6],"
+ " last_version=[LAST_VALUE($3) OVER (PARTITION BY $1"
+ " ORDER BY $3 ROWS BETWEEN 1 FOLLOWING AND 10 FOLLOWING)])\n"
+ " JdbcTableScan(table=[[foodmart, expense_fact]])\n")
.runs()
.planHasSql("SELECT \"store_id\", \"account_id\", \"exp_date\","
+ " \"time_id\", \"category_id\", \"currency_id\", \"amount\","
+ " LAST_VALUE(\"time_id\") OVER (PARTITION BY \"account_id\""
+ " ORDER BY \"time_id\" ROWS BETWEEN 1 FOLLOWING"
+ " AND 10 FOLLOWING) AS \"last_version\"\n"
+ "FROM \"foodmart\".\"expense_fact\"");
}
@Test void testOverRowsBetweenBoundPrecedingAndCurrent() {
CalciteAssert
.model(FoodmartSchema.FOODMART_MODEL)
.enable(CalciteAssert.DB == CalciteAssert.DatabaseInstance.POSTGRESQL)
.query("select \"store_id\", \"account_id\", \"exp_date\","
+ " \"time_id\", \"category_id\", \"currency_id\", \"amount\","
+ " last_value(\"time_id\") over (partition by \"account_id\""
+ " order by \"time_id\" rows between 3 preceding and current row)"
+ " as \"last_version\" from \"expense_fact\"")
.explainContains("PLAN=JdbcToEnumerableConverter\n"
+ " JdbcProject(store_id=[$0], account_id=[$1], exp_date=[$2], "
+ "time_id=[$3], category_id=[$4], currency_id=[$5], amount=[$6],"
+ " last_version=[LAST_VALUE($3) OVER (PARTITION BY $1"
+ " ORDER BY $3 ROWS BETWEEN 3 PRECEDING AND CURRENT ROW)])\n"
+ " JdbcTableScan(table=[[foodmart, expense_fact]])\n")
.runs()
.planHasSql("SELECT \"store_id\", \"account_id\", \"exp_date\","
+ " \"time_id\", \"category_id\", \"currency_id\", \"amount\","
+ " LAST_VALUE(\"time_id\") OVER (PARTITION BY \"account_id\""
+ " ORDER BY \"time_id\" ROWS BETWEEN 3 PRECEDING"
+ " AND CURRENT ROW) AS \"last_version\"\n"
+ "FROM \"foodmart\".\"expense_fact\"");
}
@Test void testOverDisallowPartial() {
CalciteAssert
.model(FoodmartSchema.FOODMART_MODEL)
.enable(CalciteAssert.DB == CalciteAssert.DatabaseInstance.POSTGRESQL)
.query("select \"store_id\", \"account_id\", \"exp_date\","
+ " \"time_id\", \"category_id\", \"currency_id\", \"amount\","
+ " last_value(\"time_id\") over (partition by \"account_id\""
+ " order by \"time_id\" rows 3 preceding disallow partial)"
+ " as \"last_version\" from \"expense_fact\"")
.explainContains("PLAN=JdbcToEnumerableConverter\n"
+ " JdbcProject(store_id=[$0], account_id=[$1], exp_date=[$2],"
+ " time_id=[$3], category_id=[$4], currency_id=[$5],"
+ " amount=[$6], last_version=[CASE(>=(COUNT() OVER"
+ " (PARTITION BY $1 ORDER BY $3 ROWS BETWEEN 3 PRECEDING AND"
+ " CURRENT ROW), 2), LAST_VALUE($3) OVER (PARTITION BY $1"
+ " ORDER BY $3 ROWS BETWEEN 3 PRECEDING AND CURRENT ROW),"
+ " null)])\n JdbcTableScan(table=[[foodmart,"
+ " expense_fact]])\n")
.runs()
.planHasSql("SELECT \"store_id\", \"account_id\", \"exp_date\","
+ " \"time_id\", \"category_id\", \"currency_id\", \"amount\","
+ " CASE WHEN (COUNT(*) OVER (PARTITION BY \"account_id\""
+ " ORDER BY \"time_id\" ROWS BETWEEN 3 PRECEDING"
+ " AND CURRENT ROW)) >= 2 THEN LAST_VALUE(\"time_id\")"
+ " OVER (PARTITION BY \"account_id\" ORDER BY \"time_id\""
+ " ROWS BETWEEN 3 PRECEDING AND CURRENT ROW)"
+ " ELSE NULL END AS \"last_version\"\n"
+ "FROM \"foodmart\".\"expense_fact\"");
}
@Test void testLastValueOver() {
CalciteAssert
.model(FoodmartSchema.FOODMART_MODEL)
.enable(CalciteAssert.DB == CalciteAssert.DatabaseInstance.POSTGRESQL)
.query("select \"store_id\", \"account_id\", \"exp_date\","
+ " \"time_id\", \"category_id\", \"currency_id\", \"amount\","
+ " last_value(\"time_id\") over (partition by \"account_id\""
+ " order by \"time_id\") as \"last_version\""
+ " from \"expense_fact\"")
.explainContains("PLAN=JdbcToEnumerableConverter\n"
+ " JdbcProject(store_id=[$0], account_id=[$1], exp_date=[$2],"
+ " time_id=[$3], category_id=[$4], currency_id=[$5], amount=[$6],"
+ " last_version=[LAST_VALUE($3) OVER (PARTITION BY $1 ORDER BY $3"
+ " RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)])\n"
+ " JdbcTableScan(table=[[foodmart, expense_fact]])\n")
.runs()
.planHasSql("SELECT \"store_id\", \"account_id\", \"exp_date\","
+ " \"time_id\", \"category_id\", \"currency_id\", \"amount\","
+ " LAST_VALUE(\"time_id\") OVER (PARTITION BY \"account_id\""
+ " ORDER BY \"time_id\" RANGE BETWEEN UNBOUNDED PRECEDING AND"
+ " CURRENT ROW) AS \"last_version\""
+ "\nFROM \"foodmart\".\"expense_fact\"");
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-259">[CALCITE-259]
* Using sub-queries in CASE statement against JDBC tables generates invalid
* Oracle SQL</a>. */
@Test void testSubQueryWithSingleValue() {
final String expected;
switch (CalciteAssert.DB) {
case MYSQL:
expected = "Sub"
+ "query returns more than 1 row";
break;
default:
expected = "more than one value in agg SINGLE_VALUE";
}
CalciteAssert.model(FoodmartSchema.FOODMART_MODEL)
.query("SELECT \"full_name\" FROM \"employee\" WHERE "
+ "\"employee_id\" = (SELECT \"employee_id\" FROM \"salary\")")
.explainContains("SINGLE_VALUE")
.throws_(expected);
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-865">[CALCITE-865]
* Unknown table type causes NullPointerException in JdbcSchema</a>. The issue
* occurred because of the "SYSTEM_INDEX" table type when run against
* PostgreSQL. */
@Test void testMetadataTables() throws Exception {
// The troublesome tables occur in PostgreSQL's system schema.
final String model =
FoodmartSchema.FOODMART_MODEL.replace("jdbcSchema: 'foodmart'",
"jdbcSchema: null");
CalciteAssert.model(
model)
.doWithConnection(connection -> {
try {
final ResultSet resultSet =
connection.getMetaData().getTables(null, null, "%", null);
assertFalse(CalciteAssert.toString(resultSet).isEmpty());
} catch (SQLException e) {
throw TestUtil.rethrow(e);
}
});
}
@Test void testMetadataFunctions() {
final String model = ""
+ "{\n"
+ " version: '1.0',\n"
+ " schemas: [\n"
+ " {\n"
+ " name: 'adhoc',\n"
+ " functions: [\n"
+ " {\n"
+ " name: 'MY_STR',\n"
+ " className: '" + Smalls.MyToStringFunction.class.getName() + "'\n"
+ " },\n"
+ " {\n"
+ " name: 'FIBONACCI_TABLE',\n"
+ " className: '" + Smalls.class.getName() + "',\n"
+ " methodName: 'fibonacciTable'\n"
+ " }\n"
+ " ],\n"
+ " materializations: [\n"
+ " {\n"
+ " table: 'TEST_VIEW',\n"
+ " sql: 'SELECT 1'\n"
+ " }\n"
+ " ]\n"
+ " }\n"
+ " ]\n"
+ "}";
CalciteAssert.model(model)
.withDefaultSchema("adhoc")
.metaData(connection -> {
try {
return connection.getMetaData().getFunctions(null, "adhoc", "%");
} catch (SQLException e) {
throw TestUtil.rethrow(e);
}
})
.returns(""
+ "FUNCTION_CAT=null; FUNCTION_SCHEM=adhoc; FUNCTION_NAME=FIBONACCI_TABLE; REMARKS=null; FUNCTION_TYPE=0; SPECIFIC_NAME=FIBONACCI_TABLE\n"
+ "FUNCTION_CAT=null; FUNCTION_SCHEM=adhoc; FUNCTION_NAME=MY_STR; REMARKS=null; FUNCTION_TYPE=0; SPECIFIC_NAME=MY_STR\n");
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-666">[CALCITE-666]
* Anti-semi-joins against JDBC adapter give wrong results</a>. */
@Test void testScalarSubQuery() {
CalciteAssert.model(JdbcTest.SCOTT_MODEL)
.query("SELECT COUNT(empno) AS cEmpNo FROM \"SCOTT\".\"EMP\" "
+ "WHERE DEPTNO <> (SELECT * FROM (VALUES 1))")
.enable(CalciteAssert.DB == CalciteAssert.DatabaseInstance.HSQLDB)
.returns("CEMPNO=14\n");
CalciteAssert.model(JdbcTest.SCOTT_MODEL)
.query("SELECT ename FROM \"SCOTT\".\"EMP\" "
+ "WHERE DEPTNO = (SELECT deptno FROM \"SCOTT\".\"DEPT\" "
+ "WHERE dname = 'ACCOUNTING')")
.enable(CalciteAssert.DB == CalciteAssert.DatabaseInstance.HSQLDB)
.returns("ENAME=CLARK\nENAME=KING\nENAME=MILLER\n");
CalciteAssert.model(JdbcTest.SCOTT_MODEL)
.query("SELECT COUNT(ename) AS cEname FROM \"SCOTT\".\"EMP\" "
+ "WHERE DEPTNO > (SELECT deptno FROM \"SCOTT\".\"DEPT\" "
+ "WHERE dname = 'ACCOUNTING')")
.enable(CalciteAssert.DB == CalciteAssert.DatabaseInstance.HSQLDB)
.returns("CENAME=11\n");
CalciteAssert.model(JdbcTest.SCOTT_MODEL)
.query("SELECT COUNT(ename) AS cEname FROM \"SCOTT\".\"EMP\" "
+ "WHERE DEPTNO < (SELECT deptno FROM \"SCOTT\".\"DEPT\" "
+ "WHERE dname = 'ACCOUNTING')")
.enable(CalciteAssert.DB == CalciteAssert.DatabaseInstance.HSQLDB)
.returns("CENAME=0\n");
}
/**
* Acquires an exclusive connection to a test database, and cleans it.
*
* <p>Cleans any previous TableModify states and creates
* one expense_fact instance with store_id = 666.
*
* <p>Caller must close the returned wrapper, so that the next test can
* acquire the lock and use the database.
*
* @param c JDBC connection
*/
private LockWrapper exclusiveCleanDb(Connection c) throws SQLException {
final LockWrapper wrapper = LockWrapper.lock(LOCK);
try (Statement statement = c.createStatement()) {
final String dSql = "DELETE FROM \"foodmart\".\"expense_fact\""
+ " WHERE \"store_id\"=666\n";
final String iSql = "INSERT INTO \"foodmart\".\"expense_fact\"(\n"
+ " \"store_id\", \"account_id\", \"exp_date\", \"time_id\","
+ " \"category_id\", \"currency_id\", \"amount\")\n"
+ " VALUES (666, 666, TIMESTAMP '1997-01-01 00:00:00',"
+ " 666, '666', 666, 666)";
statement.executeUpdate(dSql);
int rowCount = statement.executeUpdate(iSql);
assertThat(rowCount, is(1));
return wrapper;
} catch (SQLException | RuntimeException | Error e) {
wrapper.close();
throw e;
}
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-1527">[CALCITE-1527]
* Support DML in the JDBC adapter</a>. */
@Test void testTableModifyInsert() throws Exception {
final String sql = "INSERT INTO \"foodmart\".\"expense_fact\"(\n"
+ " \"store_id\", \"account_id\", \"exp_date\", \"time_id\","
+ " \"category_id\", \"currency_id\", \"amount\")\n"
+ "VALUES (666, 666, TIMESTAMP '1997-01-01 00:00:00',"
+ " 666, '666', 666, 666)";
final String explain = "PLAN=JdbcToEnumerableConverter\n"
+ " JdbcTableModify(table=[[foodmart, expense_fact]], "
+ "operation=[INSERT], flattened=[false])\n"
+ " JdbcValues(tuples=[[{ 666, 666, 1997-01-01 00:00:00, 666, "
+ "'666', 666, 666 }]])\n\n";
final String jdbcSql = "INSERT INTO \"foodmart\".\"expense_fact\" (\"store_id\", "
+ "\"account_id\", \"exp_date\", \"time_id\", \"category_id\", \"currency_id\", "
+ "\"amount\")\n"
+ "VALUES (666, 666, TIMESTAMP '1997-01-01 00:00:00', 666, '666', "
+ "666, 666)";
final AssertThat that =
CalciteAssert.model(FoodmartSchema.FOODMART_MODEL)
.enable(CalciteAssert.DB == DatabaseInstance.HSQLDB
|| CalciteAssert.DB == DatabaseInstance.POSTGRESQL);
that.doWithConnection(connection -> {
try (LockWrapper ignore = exclusiveCleanDb(connection)) {
that.query(sql)
.explainContains(explain)
.planUpdateHasSql(jdbcSql, 1);
} catch (SQLException e) {
throw TestUtil.rethrow(e);
}
});
}
@Test void testTableModifyInsertMultiValues() throws Exception {
final String sql = "INSERT INTO \"foodmart\".\"expense_fact\"(\n"
+ " \"store_id\", \"account_id\", \"exp_date\", \"time_id\","
+ " \"category_id\", \"currency_id\", \"amount\")\n"
+ "VALUES (666, 666, TIMESTAMP '1997-01-01 00:00:00',"
+ " 666, '666', 666, 666),\n"
+ " (666, 777, TIMESTAMP '1997-01-01 00:00:00',"
+ " 666, '666', 666, 666)";
final String explain = "PLAN=JdbcToEnumerableConverter\n"
+ " JdbcTableModify(table=[[foodmart, expense_fact]], "
+ "operation=[INSERT], flattened=[false])\n"
+ " JdbcValues(tuples=[["
+ "{ 666, 666, 1997-01-01 00:00:00, 666, '666', 666, 666 }, "
+ "{ 666, 777, 1997-01-01 00:00:00, 666, '666', 666, 666 }]])\n\n";
final String jdbcSql = "INSERT INTO \"foodmart\".\"expense_fact\""
+ " (\"store_id\", \"account_id\", \"exp_date\", \"time_id\", "
+ "\"category_id\", \"currency_id\", \"amount\")\n"
+ "VALUES "
+ "(666, 666, TIMESTAMP '1997-01-01 00:00:00', 666, '666', 666, 666),\n"
+ "(666, 777, TIMESTAMP '1997-01-01 00:00:00', 666, '666', 666, 666)";
final AssertThat that =
CalciteAssert.model(FoodmartSchema.FOODMART_MODEL)
.enable(CalciteAssert.DB == DatabaseInstance.HSQLDB
|| CalciteAssert.DB == DatabaseInstance.POSTGRESQL);
that.doWithConnection(connection -> {
try (LockWrapper ignore = exclusiveCleanDb(connection)) {
that.query(sql)
.explainContains(explain)
.planUpdateHasSql(jdbcSql, 2);
} catch (SQLException e) {
throw TestUtil.rethrow(e);
}
});
}
@Test void testTableModifyInsertWithSubQuery() throws Exception {
final AssertThat that = CalciteAssert
.model(FoodmartSchema.FOODMART_MODEL)
.enable(CalciteAssert.DB == DatabaseInstance.HSQLDB);
that.doWithConnection(connection -> {
try (LockWrapper ignore = exclusiveCleanDb(connection)) {
final String sql = "INSERT INTO \"foodmart\".\"expense_fact\"(\n"
+ " \"store_id\", \"account_id\", \"exp_date\", \"time_id\","
+ " \"category_id\", \"currency_id\", \"amount\")\n"
+ "SELECT \"store_id\", \"account_id\", \"exp_date\","
+ " \"time_id\" + 1, \"category_id\", \"currency_id\","
+ " \"amount\"\n"
+ "FROM \"foodmart\".\"expense_fact\"\n"
+ "WHERE \"store_id\" = 666";
final String explain = "PLAN=JdbcToEnumerableConverter\n"
+ " JdbcTableModify(table=[[foodmart, expense_fact]], operation=[INSERT], flattened=[false])\n"
+ " JdbcProject(store_id=[$0], account_id=[$1], exp_date=[$2], time_id=[+($3, 1)], category_id=[$4], currency_id=[$5], amount=[$6])\n"
+ " JdbcFilter(condition=[=($0, 666)])\n"
+ " JdbcTableScan(table=[[foodmart, expense_fact]])\n";
final String jdbcSql = "INSERT INTO \"foodmart\".\"expense_fact\""
+ " (\"store_id\", \"account_id\", \"exp_date\", \"time_id\","
+ " \"category_id\", \"currency_id\", \"amount\")\n"
+ "(SELECT \"store_id\", \"account_id\", \"exp_date\","
+ " \"time_id\" + 1 AS \"time_id\", \"category_id\","
+ " \"currency_id\", \"amount\"\n"
+ "FROM \"foodmart\".\"expense_fact\"\n"
+ "WHERE \"store_id\" = 666)";
that.query(sql)
.explainContains(explain)
.planUpdateHasSql(jdbcSql, 1);
} catch (SQLException e) {
throw TestUtil.rethrow(e);
}
});
}
@Test void testTableModifyUpdate() throws Exception {
final AssertThat that = CalciteAssert
.model(FoodmartSchema.FOODMART_MODEL)
.enable(CalciteAssert.DB == DatabaseInstance.HSQLDB);
that.doWithConnection(connection -> {
try (LockWrapper ignore = exclusiveCleanDb(connection)) {
final String sql = "UPDATE \"foodmart\".\"expense_fact\"\n"
+ " SET \"account_id\"=888\n"
+ " WHERE \"store_id\"=666\n";
final String explain = "PLAN=JdbcToEnumerableConverter\n"
+ " JdbcTableModify(table=[[foodmart, expense_fact]], operation=[UPDATE], updateColumnList=[[account_id]], sourceExpressionList=[[888]], flattened=[false])\n"
+ " JdbcProject(store_id=[$0], account_id=[$1], exp_date=[$2], time_id=[$3], category_id=[$4], currency_id=[$5], amount=[$6], EXPR$0=[888])\n"
+ " JdbcFilter(condition=[=($0, 666)])\n"
+ " JdbcTableScan(table=[[foodmart, expense_fact]])";
final String jdbcSql = "UPDATE \"foodmart\".\"expense_fact\""
+ " SET \"account_id\" = 888\n"
+ "WHERE \"store_id\" = 666";
that.query(sql)
.explainContains(explain)
.planUpdateHasSql(jdbcSql, 1);
return null;
} catch (SQLException e) {
throw TestUtil.rethrow(e);
}
});
}
@Test void testTableModifyDelete() throws Exception {
final AssertThat that = CalciteAssert
.model(FoodmartSchema.FOODMART_MODEL)
.enable(CalciteAssert.DB == DatabaseInstance.HSQLDB);
that.doWithConnection(connection -> {
try (LockWrapper ignore = exclusiveCleanDb(connection)) {
final String sql = "DELETE FROM \"foodmart\".\"expense_fact\"\n"
+ "WHERE \"store_id\"=666\n";
final String explain = "PLAN=JdbcToEnumerableConverter\n"
+ " JdbcTableModify(table=[[foodmart, expense_fact]], operation=[DELETE], flattened=[false])\n"
+ " JdbcFilter(condition=[=($0, 666)])\n"
+ " JdbcTableScan(table=[[foodmart, expense_fact]]";
final String jdbcSql = "DELETE FROM \"foodmart\".\"expense_fact\"\n"
+ "WHERE \"store_id\" = 666";
that.query(sql)
.explainContains(explain)
.planUpdateHasSql(jdbcSql, 1);
} catch (SQLException e) {
throw TestUtil.rethrow(e);
}
});
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-1572">[CALCITE-1572]
* JdbcSchema throws exception when detecting nullable columns</a>. */
@Test void testColumnNullability() {
final String sql = "select \"employee_id\", \"position_id\"\n"
+ "from \"foodmart\".\"employee\" limit 10";
CalciteAssert.model(FoodmartSchema.FOODMART_MODEL)
.query(sql)
.runs()
.returnsCount(10)
.typeIs("[employee_id INTEGER NOT NULL, position_id INTEGER]");
}
@Test void pushBindParameters() {
final String sql = "select empno, ename from emp where empno = ?";
CalciteAssert.model(JdbcTest.SCOTT_MODEL)
.query(sql)
.consumesPreparedStatement(p -> p.setInt(1, 7566))
.returnsCount(1)
.planHasSql("SELECT \"EMPNO\", \"ENAME\"\nFROM \"SCOTT\".\"EMP\"\nWHERE \"EMPNO\" = ?");
}
/**
* Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-4619">[CALCITE-4619]
* "Full join" generates an incorrect execution plan under mysql</a>. */
@Test void testFullJoinNonSupportedDialect() {
CalciteAssert.model(JdbcTest.SCOTT_MODEL)
.enable(CalciteAssert.DB == CalciteAssert.DatabaseInstance.H2
|| CalciteAssert.DB == CalciteAssert.DatabaseInstance.MYSQL)
.query("select empno, ename, e.deptno, dname\n"
+ "from scott.emp e full join scott.dept d\n"
+ "on e.deptno = d.deptno")
.explainContains("PLAN=EnumerableCalc(expr#0..4=[{inputs}], proj#0..2=[{exprs}],"
+ " DNAME=[$t4])\n"
+ " EnumerableHashJoin(condition=[=($2, $3)], joinType=[full])\n"
+ " JdbcToEnumerableConverter\n"
+ " JdbcProject(EMPNO=[$0], ENAME=[$1], DEPTNO=[$7])\n"
+ " JdbcTableScan(table=[[SCOTT, EMP]])\n"
+ " JdbcToEnumerableConverter\n"
+ " JdbcProject(DEPTNO=[$0], DNAME=[$1])\n"
+ " JdbcTableScan(table=[[SCOTT, DEPT]])")
.runs();
}
/** Acquires a lock, and releases it when closed. */
static class LockWrapper implements AutoCloseable {
private final Lock lock;
LockWrapper(Lock lock) {
this.lock = lock;
}
/** Acquires a lock and returns a closeable wrapper. */
static LockWrapper lock(Lock lock) {
lock.lock();
return new LockWrapper(lock);
}
public void close() {
lock.unlock();
}
}
}