| /* |
| * 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.ReflectiveSchema; |
| import org.apache.calcite.jdbc.CalciteConnection; |
| import org.apache.calcite.rel.RelNode; |
| import org.apache.calcite.schema.SchemaPlus; |
| import org.apache.calcite.sql.fun.SqlStdOperatorTable; |
| import org.apache.calcite.tools.FrameworkConfig; |
| import org.apache.calcite.tools.Frameworks; |
| import org.apache.calcite.tools.RelBuilder; |
| import org.apache.calcite.tools.RelRunner; |
| |
| import org.junit.jupiter.api.AfterEach; |
| import org.junit.jupiter.api.BeforeEach; |
| import org.junit.jupiter.api.Test; |
| |
| import java.sql.Connection; |
| import java.sql.DriverManager; |
| import java.sql.PreparedStatement; |
| import java.sql.SQLException; |
| import java.sql.Statement; |
| import java.util.function.Function; |
| |
| import static org.hamcrest.CoreMatchers.containsString; |
| import static org.hamcrest.CoreMatchers.equalTo; |
| import static org.hamcrest.MatcherAssert.assertThat; |
| import static org.junit.jupiter.api.Assertions.fail; |
| |
| /** |
| * Test cases to check that necessary information from underlying exceptions |
| * is correctly propagated via {@link SQLException}s. |
| */ |
| public class ExceptionMessageTest { |
| private Connection conn; |
| |
| /** |
| * Simple reflective schema that provides valid and invalid entries. |
| */ |
| @SuppressWarnings("UnusedDeclaration") |
| public static class TestSchema { |
| public Entry[] entries = { |
| new Entry(1, "name1"), |
| new Entry(2, "name2") |
| }; |
| |
| public Iterable<Entry> badEntries = () -> { |
| throw new IllegalStateException("Can't iterate over badEntries"); |
| }; |
| } |
| |
| /** |
| * Entries made available in the reflective TestSchema. |
| */ |
| public static class Entry { |
| public int id; |
| public String name; |
| |
| public Entry(int id, String name) { |
| this.id = id; |
| this.name = name; |
| } |
| } |
| |
| @BeforeEach |
| public void setUp() throws SQLException { |
| Connection connection = DriverManager.getConnection("jdbc:calcite:"); |
| CalciteConnection calciteConnection = |
| connection.unwrap(CalciteConnection.class); |
| SchemaPlus rootSchema = calciteConnection.getRootSchema(); |
| rootSchema.add("test", new ReflectiveSchema(new TestSchema())); |
| calciteConnection.setSchema("test"); |
| this.conn = calciteConnection; |
| } |
| |
| @AfterEach |
| public void tearDown() throws SQLException { |
| if (conn != null) { |
| Connection c = conn; |
| conn = null; |
| c.close(); |
| } |
| } |
| |
| private void runQuery(String sql) throws SQLException { |
| Statement stmt = conn.createStatement(); |
| try { |
| stmt.executeQuery(sql); |
| } finally { |
| try { |
| stmt.close(); |
| } catch (Exception e) { |
| // We catch a possible exception on close so that we know we're not |
| // masking the query exception with the close exception |
| fail("Error on close"); |
| } |
| } |
| } |
| |
| /** Performs an action that requires a {@link RelBuilder}, and returns the |
| * result. */ |
| private <T> T withRelBuilder(Function<RelBuilder, T> fn) throws SQLException { |
| final SchemaPlus rootSchema = |
| conn.unwrap(CalciteConnection.class).getRootSchema(); |
| final FrameworkConfig config = Frameworks.newConfigBuilder() |
| .defaultSchema(rootSchema) |
| .build(); |
| final RelBuilder relBuilder = RelBuilder.create(config); |
| return fn.apply(relBuilder); |
| } |
| |
| private void runQuery(Function<RelBuilder, RelNode> relFn) throws SQLException { |
| final RelRunner relRunner = conn.unwrap(RelRunner.class); |
| final RelNode relNode = withRelBuilder(relFn); |
| final PreparedStatement preparedStatement = |
| relRunner.prepareStatement(relNode); |
| try { |
| preparedStatement.executeQuery(); |
| } finally { |
| try { |
| preparedStatement.close(); |
| } catch (Exception e) { |
| fail("Error on close"); |
| } |
| } |
| } |
| |
| @Test void testValidQuery() throws SQLException { |
| // Just ensure that we're actually dealing with a valid connection |
| // to be sure that the results of the other tests can be trusted |
| runQuery("select * from \"entries\""); |
| } |
| |
| @Test void testNonSqlException() throws SQLException { |
| try { |
| runQuery("select * from \"badEntries\""); |
| fail("Query badEntries should result in an exception"); |
| } catch (SQLException e) { |
| assertThat(e.getMessage(), |
| equalTo("Error while executing SQL \"select * from \"badEntries\"\": " |
| + "Can't iterate over badEntries")); |
| } |
| } |
| |
| @Test void testSyntaxError() { |
| try { |
| runQuery("invalid sql"); |
| fail("Query should fail"); |
| } catch (SQLException e) { |
| assertThat(e.getMessage(), |
| equalTo("Error while executing SQL \"invalid sql\": parse failed: " |
| + "Non-query expression encountered in illegal context")); |
| } |
| } |
| |
| @Test void testSemanticError() { |
| try { |
| // implicit type coercion. |
| runQuery("select \"name\" - \"id\" from \"entries\""); |
| } catch (SQLException e) { |
| assertThat(e.getMessage(), |
| containsString("Cannot apply '-' to arguments")); |
| } |
| } |
| |
| @Test void testNonexistentTable() { |
| try { |
| runQuery("select name from \"nonexistentTable\""); |
| fail("Query should fail"); |
| } catch (SQLException e) { |
| assertThat(e.getMessage(), |
| containsString("Object 'nonexistentTable' not found")); |
| } |
| } |
| |
| /** Runs a query via {@link RelRunner}. */ |
| @Test void testValidRelNodeQuery() throws SQLException { |
| final Function<RelBuilder, RelNode> relFn = b -> |
| b.scan("test", "entries") |
| .project(b.field("name")) |
| .build(); |
| runQuery(relFn); |
| } |
| |
| /** Runs a query via {@link RelRunner} that is expected to fail, |
| * and checks that the exception correctly describes the RelNode tree. |
| * |
| * <p>Test case for |
| * <a href="https://issues.apache.org/jira/browse/CALCITE-4585">[CALCITE-4585] |
| * If a query is executed via RelRunner.prepare(RelNode) and fails, the |
| * exception should report the RelNode plan, not the SQL</a>. */ |
| @Test void testRelNodeQueryException() { |
| try { |
| final Function<RelBuilder, RelNode> relFn = b -> |
| b.scan("test", "entries") |
| .project(b.call(SqlStdOperatorTable.ABS, b.field("name"))) |
| .build(); |
| runQuery(relFn); |
| fail("RelNode query about entries should result in an exception"); |
| } catch (SQLException e) { |
| String message = "Error while preparing plan [" |
| + "LogicalProject($f0=[ABS($1)])\n" |
| + " LogicalTableScan(table=[[test, entries]])\n" |
| + "]"; |
| assertThat(e.getMessage(), Matchers.isLinux(message)); |
| } |
| } |
| } |