blob: 5554c7195c4b8b3bf5f7884843407f64cdce7286 [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.adapter.java.JavaTypeFactory;
import org.apache.calcite.jdbc.CalciteConnection;
import org.apache.calcite.linq4j.Enumerator;
import org.apache.calcite.linq4j.Linq4j;
import org.apache.calcite.linq4j.QueryProvider;
import org.apache.calcite.linq4j.Queryable;
import org.apache.calcite.linq4j.tree.Expression;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeFactory;
import org.apache.calcite.schema.SchemaPlus;
import org.apache.calcite.schema.Schemas;
import org.apache.calcite.schema.impl.AbstractSchema;
import org.apache.calcite.schema.impl.AbstractTableQueryable;
import org.apache.calcite.test.schemata.hr.Employee;
import org.apache.calcite.util.TestUtil;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Type;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Properties;
import static org.apache.calcite.test.CalciteAssert.hr;
import static org.apache.calcite.test.CalciteAssert.that;
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;
/**
* Tests for a JDBC front-end (with some quite complex SQL) and Linq4j back-end
* (based on in-memory collections).
*/
public class JdbcFrontLinqBackTest {
/**
* Runs a simple query that reads from a table in an in-memory schema.
*/
@Test void testSelect() {
hr()
.query("select *\n"
+ "from \"foodmart\".\"sales_fact_1997\" as s\n"
+ "where s.\"cust_id\" = 100")
.returns("cust_id=100; prod_id=10\n");
}
/**
* Runs a simple query that joins between two in-memory schemas.
*/
@Test void testJoin() {
hr()
.query("select *\n"
+ "from \"foodmart\".\"sales_fact_1997\" as s\n"
+ "join \"hr\".\"emps\" as e\n"
+ "on e.\"empid\" = s.\"cust_id\"")
.returnsUnordered(
"cust_id=100; prod_id=10; empid=100; deptno=10; name=Bill; salary=10000.0; commission=1000",
"cust_id=150; prod_id=20; empid=150; deptno=10; name=Sebastian; salary=7000.0; commission=null");
}
/**
* Simple GROUP BY.
*/
@Test void testGroupBy() {
hr()
.query("select \"deptno\", sum(\"empid\") as s, count(*) as c\n"
+ "from \"hr\".\"emps\" as e\n"
+ "group by \"deptno\"")
.returns("deptno=20; S=200; C=1\n"
+ "deptno=10; S=360; C=3\n");
}
/**
* Simple ORDER BY.
*/
@Test void testOrderBy() {
hr()
.query("select upper(\"name\") as un, \"deptno\"\n"
+ "from \"hr\".\"emps\" as e\n"
+ "order by \"deptno\", \"name\" desc")
.explainContains(""
+ "EnumerableSort(sort0=[$1], sort1=[$2], dir0=[ASC], dir1=[DESC])\n"
+ " EnumerableCalc(expr#0..4=[{inputs}], expr#5=[UPPER($t2)], UN=[$t5], deptno=[$t1], name=[$t2])\n"
+ " EnumerableTableScan(table=[[hr, emps]])")
.returns("UN=THEODORE; deptno=10\n"
+ "UN=SEBASTIAN; deptno=10\n"
+ "UN=BILL; deptno=10\n"
+ "UN=ERIC; deptno=20\n");
}
/**
* Simple UNION, plus ORDER BY.
*
* <p>Also tests a query that returns a single column. We optimize this case
* internally, using non-array representations for rows.</p>
*/
@Test void testUnionAllOrderBy() {
hr()
.query("select \"name\"\n"
+ "from \"hr\".\"emps\" as e\n"
+ "union all\n"
+ "select \"name\"\n"
+ "from \"hr\".\"depts\"\n"
+ "order by 1 desc")
.returns("name=Theodore\n"
+ "name=Sebastian\n"
+ "name=Sales\n"
+ "name=Marketing\n"
+ "name=HR\n"
+ "name=Eric\n"
+ "name=Bill\n");
}
/**
* Tests UNION.
*/
@Test void testUnion() {
hr()
.query("select substring(\"name\" from 1 for 1) as x\n"
+ "from \"hr\".\"emps\" as e\n"
+ "union\n"
+ "select substring(\"name\" from 1 for 1) as y\n"
+ "from \"hr\".\"depts\"")
.returnsUnordered(
"X=T",
"X=E",
"X=S",
"X=B",
"X=M",
"X=H");
}
/**
* Tests INTERSECT.
*/
@Test void testIntersect() {
hr()
.query("select substring(\"name\" from 1 for 1) as x\n"
+ "from \"hr\".\"emps\" as e\n"
+ "intersect\n"
+ "select substring(\"name\" from 1 for 1) as y\n"
+ "from \"hr\".\"depts\"")
.returns("X=S\n");
}
/**
* Tests EXCEPT.
*/
@Disabled
@Test void testExcept() {
hr()
.query("select substring(\"name\" from 1 for 1) as x\n"
+ "from \"hr\".\"emps\" as e\n"
+ "except\n"
+ "select substring(\"name\" from 1 for 1) as y\n"
+ "from \"hr\".\"depts\"")
.returnsUnordered(
"X=T",
"X=E",
"X=B");
}
@Test void testWhereBad() {
hr()
.query("select *\n"
+ "from \"foodmart\".\"sales_fact_1997\" as s\n"
+ "where empid > 120")
.throws_("Column 'EMPID' not found in any table");
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-9">[CALCITE-9]
* RexToLixTranslator not incrementing local variable name counter</a>. */
@Test void testWhereOr() {
hr()
.query("select * from \"hr\".\"emps\"\n"
+ "where (\"empid\" = 100 or \"empid\" = 200)\n"
+ "and \"deptno\" = 10")
.returns(
"empid=100; deptno=10; name=Bill; salary=10000.0; commission=1000\n");
}
@Test void testWhereLike() {
hr()
.query("select *\n"
+ "from \"hr\".\"emps\" as e\n"
+ "where e.\"empid\" < 120 or e.\"name\" like 'S%'")
.returns(""
+ "empid=100; deptno=10; name=Bill; salary=10000.0; commission=1000\n"
+ "empid=150; deptno=10; name=Sebastian; salary=7000.0; commission=null\n"
+ "empid=110; deptno=10; name=Theodore; salary=11500.0; commission=250\n");
}
@Test void testInsert() {
final List<Employee> employees = new ArrayList<>();
CalciteAssert.AssertThat with = mutable(employees);
with.query("select * from \"foo\".\"bar\"")
.returns(
"empid=0; deptno=0; name=first; salary=0.0; commission=null\n");
with.query("insert into \"foo\".\"bar\" select * from \"hr\".\"emps\"")
.updates(4);
with.query("select count(*) as c from \"foo\".\"bar\"")
.returns("C=5\n");
with.query("insert into \"foo\".\"bar\" "
+ "select * from \"hr\".\"emps\" where \"deptno\" = 10")
.updates(3);
with.query("select \"name\", count(*) as c from \"foo\".\"bar\" "
+ "group by \"name\"")
.returnsUnordered(
"name=Bill; C=2",
"name=Eric; C=1",
"name=Theodore; C=2",
"name=first; C=1",
"name=Sebastian; C=2");
}
@Test void testInsertBind() throws Exception {
final List<Employee> employees = new ArrayList<>();
CalciteAssert.AssertThat with = mutable(employees);
with.query("select count(*) as c from \"foo\".\"bar\"")
.returns("C=1\n");
with.doWithConnection(c -> {
try {
final String sql = "insert into \"foo\".\"bar\"\n"
+ "values (?, 0, ?, 10.0, null)";
try (PreparedStatement p = c.prepareStatement(sql)) {
p.setInt(1, 1);
p.setString(2, "foo");
final int count = p.executeUpdate();
assertThat(count, is(1));
}
} catch (SQLException e) {
throw TestUtil.rethrow(e);
}
});
with.query("select count(*) as c from \"foo\".\"bar\"")
.returns("C=2\n");
with.query("select * from \"foo\".\"bar\"")
.returnsUnordered("empid=0; deptno=0; name=first; salary=0.0; commission=null",
"empid=1; deptno=0; name=foo; salary=10.0; commission=null");
}
@Test void testDelete() {
final List<Employee> employees = new ArrayList<>();
CalciteAssert.AssertThat with = mutable(employees);
with.query("select * from \"foo\".\"bar\"")
.returnsUnordered(
"empid=0; deptno=0; name=first; salary=0.0; commission=null");
with.query("insert into \"foo\".\"bar\" select * from \"hr\".\"emps\"")
.updates(4);
with.query("select count(*) as c from \"foo\".\"bar\"")
.returnsUnordered("C=5");
final String deleteSql = "delete from \"foo\".\"bar\" "
+ "where \"deptno\" = 10";
with.query(deleteSql)
.updates(3);
final String sql = "select \"name\", count(*) as c\n"
+ "from \"foo\".\"bar\"\n"
+ "group by \"name\"";
with.query(sql)
.returnsUnordered(
"name=Eric; C=1",
"name=first; C=1");
}
/**
* Creates the post processor routine to be applied against a Connection.
*
* <p>Table schema is based on JdbcTest#Employee
* (refer to {@link JdbcFrontLinqBackTest#mutable}).
*
* @param initialData records to be presented in table
* @return a connection post-processor
*/
private static CalciteAssert.ConnectionPostProcessor makePostProcessor(
final List<Employee> initialData) {
return connection -> {
CalciteConnection calciteConnection =
connection.unwrap(CalciteConnection.class);
SchemaPlus rootSchema = calciteConnection.getRootSchema();
SchemaPlus mapSchema = rootSchema.add("foo", new AbstractSchema());
final String tableName = "bar";
final AbstractModifiableTable table =
mutable(tableName, initialData);
mapSchema.add(tableName, table);
return calciteConnection;
};
}
/**
* Method to be shared with {@code RemoteDriverTest}.
*
* @param initialData record to be presented in table
*/
public static Connection makeConnection(
final List<Employee> initialData) throws Exception {
Properties info = new Properties();
Connection connection = DriverManager.getConnection("jdbc:calcite:", info);
connection = makePostProcessor(initialData).apply(connection);
return connection;
}
/**
* Creates a connection with an empty modifiable table with
* {@link Employee} schema.
*/
public static Connection makeConnection() throws Exception {
return makeConnection(new ArrayList<Employee>());
}
private CalciteAssert.AssertThat mutable(
final List<Employee> employees) {
employees.add(new Employee(0, 0, "first", 0f, null));
return that()
.with(CalciteAssert.Config.REGULAR)
.with(makePostProcessor(employees));
}
static AbstractModifiableTable mutable(String tableName,
final List<Employee> employees) {
return new AbstractModifiableTable(tableName) {
public RelDataType getRowType(
RelDataTypeFactory typeFactory) {
return ((JavaTypeFactory) typeFactory)
.createType(Employee.class);
}
public <T> Queryable<T> asQueryable(QueryProvider queryProvider,
SchemaPlus schema, String tableName) {
return new AbstractTableQueryable<T>(queryProvider, schema, this,
tableName) {
public Enumerator<T> enumerator() {
//noinspection unchecked
return (Enumerator<T>) Linq4j.enumerator(employees);
}
};
}
public Type getElementType() {
return Employee.class;
}
public Expression getExpression(SchemaPlus schema, String tableName,
Class clazz) {
return Schemas.tableExpression(schema, getElementType(), tableName,
clazz);
}
public Collection getModifiableCollection() {
return employees;
}
};
}
@Test void testInsert2() {
final List<Employee> employees = new ArrayList<>();
CalciteAssert.AssertThat with = mutable(employees);
with.query("insert into \"foo\".\"bar\" values (1, 1, 'second', 2, 2)")
.updates(1);
with.query("insert into \"foo\".\"bar\"\n"
+ "values (1, 3, 'third', 0, 3), (1, 4, 'fourth', 0, 4), (1, 5, 'fifth ', 0, 3)")
.updates(3);
with.query("select count(*) as c from \"foo\".\"bar\"")
.returns("C=5\n");
with.query("insert into \"foo\".\"bar\" values (1, 6, null, 0, null)")
.updates(1);
with.query("select count(*) as c from \"foo\".\"bar\"")
.returns("C=6\n");
}
/** Local Statement insert. */
@Test void testInsert3() throws Exception {
Connection connection = makeConnection(new ArrayList<Employee>());
String sql = "insert into \"foo\".\"bar\" values (1, 1, 'second', 2, 2)";
Statement statement = connection.createStatement();
boolean status = statement.execute(sql);
assertFalse(status);
ResultSet resultSet = statement.getResultSet();
assertTrue(resultSet == null);
int updateCount = statement.getUpdateCount();
assertTrue(updateCount == 1);
}
/** Local PreparedStatement insert WITHOUT bind variables. */
@Test void testPreparedStatementInsert() throws Exception {
Connection connection = makeConnection(new ArrayList<Employee>());
assertFalse(connection.isClosed());
String sql = "insert into \"foo\".\"bar\" values (1, 1, 'second', 2, 2)";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
assertFalse(preparedStatement.isClosed());
boolean status = preparedStatement.execute();
assertFalse(status);
ResultSet resultSet = preparedStatement.getResultSet();
assertTrue(resultSet == null);
int updateCount = preparedStatement.getUpdateCount();
assertTrue(updateCount == 1);
}
/** Local PreparedStatement insert WITH bind variables. */
@Test void testPreparedStatementInsert2() throws Exception {
}
/** Some of the rows have the wrong number of columns. */
@Test void testInsertMultipleRowMismatch() {
final List<Employee> employees = new ArrayList<>();
CalciteAssert.AssertThat with = mutable(employees);
with.query("insert into \"foo\".\"bar\" values\n"
+ " (1, 3, 'third'),\n"
+ " (1, 4, 'fourth'),\n"
+ " (1, 5, 'fifth ', 3)")
.throws_("Incompatible types");
}
}