/* * Copyright 2012 - 2014 Maginatics, Inc. * * Licensed 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 com.maginatics.jdbclint; import java.lang.reflect.Proxy; import java.sql.Blob; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Arrays; import java.util.EnumSet; import javax.sql.DataSource; import org.h2.jdbcx.JdbcDataSource; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; /** Test JDBC lint checks. */ public final class JdbcLintTest { private static final Configuration CONFIGURATION = new Configuration( EnumSet.allOf(Configuration.Check.class), Arrays.asList( Configuration.PRINT_STACK_TRACE_ACTION, Configuration.THROW_SQL_EXCEPTION_ACTION)); private static final String DATABASE_NAME = "jdbclinttest"; private DataSource dataSource; /** Helper to match arbitrary exceptions in tests. */ @Rule public final ExpectedException thrown = ExpectedException.none(); /** Create database with a single-column table. */ @Before public void setUp() throws SQLException { dataSource = getDataSource(); Connection conn = dataSource.getConnection(); try { Statement stmt = conn.createStatement(); try { stmt.execute("DROP TABLE IF EXISTS blob_table"); stmt.execute("DROP TABLE IF EXISTS int_table"); stmt.execute("CREATE TABLE int_table (int_column INT)"); } finally { stmt.close(); } } finally { conn.close(); } } @Test public void testConnectionDoubleClose() throws SQLException { Connection conn = dataSource.getConnection(); PreparedStatement stmt = conn.prepareStatement( "INSERT INTO int_table (int_column) VALUES (?)"); stmt.setInt(1, 0); stmt.executeUpdate(); stmt.close(); conn.close(); thrown.expect(SQLException.class); thrown.expectMessage("Connection already closed"); conn.close(); } @Test public void testConnectionMissingClose() throws SQLException { Connection conn = dataSource.getConnection(); ConnectionProxy proxy = (ConnectionProxy) Proxy.getInvocationHandler( conn); thrown.expect(SQLException.class); thrown.expectMessage("Connection not closed"); proxy.finalize(); } @Test public void testConnectionMissingCommitOrRollback() throws SQLException { Connection conn = dataSource.getConnection(); conn.setAutoCommit(false); PreparedStatement stmt = conn.prepareStatement( "INSERT INTO int_table (int_column) VALUES (?)"); stmt.setInt(1, 0); stmt.executeUpdate(); stmt.close(); thrown.expect(SQLException.class); thrown.expectMessage("Connection did not commit or roll back"); conn.close(); } @Test public void testConnectionMissingPrepareStatement() throws SQLException { Connection conn = dataSource.getConnection(); thrown.expect(SQLException.class); thrown.expectMessage("Connection without prepareStatement"); conn.close(); } @Test public void testPreparedStatementDoubleClose() throws SQLException { Connection conn = dataSource.getConnection(); PreparedStatement stmt = conn.prepareStatement( "INSERT INTO int_table (int_column) VALUES (?)"); stmt.setInt(1, 0); stmt.executeUpdate(); stmt.close(); thrown.expect(SQLException.class); thrown.expectMessage("PreparedStatement already closed"); stmt.close(); } @Test public void testPreparedStatementMissingClose() throws SQLException { Connection conn = dataSource.getConnection(); PreparedStatement stmt = conn.prepareStatement( "INSERT INTO int_table (int_column) VALUES (?)"); StatementProxy proxy = (StatementProxy) Proxy.getInvocationHandler( stmt); stmt.setInt(1, 0); stmt.executeUpdate(); thrown.expect(SQLException.class); thrown.expectMessage("PreparedStatement not closed"); proxy.finalize(); } @Test public void testPreparedStatementMissingExecute() throws SQLException { Connection conn = dataSource.getConnection(); PreparedStatement stmt = conn.prepareStatement( "INSERT INTO int_table (int_column) VALUES (?)"); stmt.setInt(1, 0); thrown.expect(SQLException.class); thrown.expectMessage("PreparedStatement without execute"); stmt.close(); } @Test public void testPreparedStatementMissingExecuteBatch() throws SQLException { Connection conn = dataSource.getConnection(); PreparedStatement stmt = conn.prepareStatement( "INSERT INTO int_table (int_column) VALUES (?)"); stmt.setInt(1, 0); stmt.addBatch(); thrown.expect(SQLException.class); thrown.expectMessage("PreparedStatement addBatch without executeBatch"); stmt.close(); } @Test public void testResultSetDoubleClose() throws SQLException { Connection conn = dataSource.getConnection(); PreparedStatement stmt = conn.prepareStatement( "SELECT * FROM int_table"); ResultSet rs = stmt.executeQuery(); rs.next(); rs.close(); thrown.expect(SQLException.class); thrown.expectMessage("ResultSet already closed"); rs.close(); } @Test public void testResultSetMissingClose() throws SQLException { Connection conn = dataSource.getConnection(); PreparedStatement stmt = conn.prepareStatement( "SELECT * FROM int_table"); ResultSet rs = stmt.executeQuery(); ResultSetProxy proxy = (ResultSetProxy) Proxy.getInvocationHandler(rs); rs.next(); thrown.expect(SQLException.class); thrown.expectMessage("ResultSet not closed"); proxy.finalize(); } @Test public void testResultSetUnreadColumn() throws SQLException { Connection conn = dataSource.getConnection(); PreparedStatement stmt = conn.prepareStatement( "INSERT INTO int_table (int_column) VALUES (?)"); stmt.setInt(1, 0); stmt.executeUpdate(); stmt.close(); stmt = conn.prepareStatement("SELECT * FROM int_table"); ResultSet rs = stmt.executeQuery(); rs.next(); thrown.expect(SQLException.class); thrown.expectMessage("ResultSet has unread column: int_column"); rs.next(); } @Test public void testStatementDoubleClose() throws SQLException { Connection conn = dataSource.getConnection(); Statement stmt = conn.createStatement(); stmt.executeUpdate("INSERT INTO int_table (int_column) VALUES (0)"); stmt.close(); thrown.expect(SQLException.class); thrown.expectMessage("Statement already closed"); stmt.close(); } @Test public void testStatementMissingClose() throws SQLException { Connection conn = dataSource.getConnection(); Statement stmt = conn.createStatement(); StatementProxy proxy = (StatementProxy) Proxy.getInvocationHandler( stmt); stmt.executeUpdate("INSERT INTO int_table (int_column) VALUES (0)"); thrown.expect(SQLException.class); thrown.expectMessage("Statement not closed"); proxy.finalize(); } @Test public void testStatementMissingExecute() throws SQLException { Connection conn = dataSource.getConnection(); Statement stmt = conn.createStatement(); thrown.expect(SQLException.class); thrown.expectMessage("Statement without execute"); stmt.close(); } @Test public void testStatementMissingExecuteBatch() throws SQLException { Connection conn = dataSource.getConnection(); Statement stmt = conn.createStatement(); stmt.addBatch("INSERT INTO int_table (int_column) VALUES (0)"); thrown.expect(SQLException.class); thrown.expectMessage("Statement addBatch without executeBatch"); stmt.close(); } @Test public void testBlobDoubleFree() throws SQLException { Connection conn = dataSource.getConnection(); PreparedStatement stmt = conn.prepareStatement( "CREATE TABLE blob_table (blob_column BLOB)"); stmt.executeUpdate(); stmt.close(); stmt = conn.prepareStatement( "INSERT INTO blob_table (blob_column) VALUES (?)"); stmt.setBytes(1, new byte[1]); stmt.executeUpdate(); stmt.close(); stmt = conn.prepareStatement("SELECT blob_column FROM blob_table"); ResultSet rs = stmt.executeQuery(); rs.next(); Blob blob = rs.getBlob("blob_column"); blob.free(); thrown.expect(SQLException.class); thrown.expectMessage("Blob already freed"); blob.free(); } @Test public void testBlobMissingFree() throws SQLException { Connection conn = dataSource.getConnection(); PreparedStatement stmt = conn.prepareStatement( "CREATE TABLE blob_table (blob_column BLOB)"); stmt.executeUpdate(); stmt.close(); stmt = conn.prepareStatement( "INSERT INTO blob_table (blob_column) VALUES (?)"); stmt.setBytes(1, new byte[1]); stmt.executeUpdate(); stmt.close(); stmt = conn.prepareStatement("SELECT blob_column FROM blob_table"); ResultSet rs = stmt.executeQuery(); rs.next(); Blob blob = rs.getBlob("blob_column"); BlobProxy proxy = (BlobProxy) Proxy.getInvocationHandler(blob); thrown.expect(SQLException.class); thrown.expectMessage("Blob not freed"); proxy.finalize(); } @Test public void testReadOnlyConnection() throws SQLException { Connection conn = dataSource.getConnection(); PreparedStatement stmt = conn.prepareStatement("SELECT 1"); stmt.executeQuery(); stmt.close(); thrown.expect(SQLException.class); thrown.expectMessage("Connection did not execute updates, " + "consider calling setReadOnly"); conn.close(); } private static DataSource getDataSource() { JdbcDataSource jdbcDataSource = new JdbcDataSource(); jdbcDataSource.setURL("jdbc:h2:mem:" + DATABASE_NAME + ";DB_CLOSE_DELAY=-1"); return DataSourceProxy.newInstance(jdbcDataSource, CONFIGURATION); } }