/*
* 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.drill.jdbc.proxy;
import org.apache.drill.test.DrillTest;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.hamcrest.CoreMatchers.*;
/**
* Test of TracingProxyDriver other than loading of driver classes.
*/
public class TracingProxyDriverTest extends DrillTest {
private static Driver proxyDriver;
private static Connection proxyConnection;
@BeforeClass
public static void setUpTestCase() throws SQLException,
ClassNotFoundException {
Class.forName( "org.apache.drill.jdbc.proxy.TracingProxyDriver" );
proxyDriver =
DriverManager.getDriver(
"jdbc:proxy:org.apache.drill.jdbc.Driver:jdbc:drill:zk=local" );
proxyConnection =
DriverManager.getConnection( "jdbc:proxy::jdbc:drill:zk=local" );
}
@Test
public void testBasicProxying() throws SQLException {
try ( final Statement stmt = proxyConnection.createStatement() ) {
final ResultSet rs =
stmt.executeQuery( "SELECT * FROM INFORMATION_SCHEMA.CATALOGS" );
assertTrue( rs.next() );
assertThat( rs.getString( 1 ), equalTo( "DRILL" ) );
assertThat( rs.getObject( 1 ), equalTo( (Object) "DRILL" ) );
}
}
private static class StdErrCapturer {
private final PrintStream savedStdErr;
private final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
private final PrintStream capturingStream = new PrintStream( buffer );
private boolean redirected;
StdErrCapturer() {
savedStdErr = System.err;
}
void redirect() {
assertFalse( redirected );
redirected = true;
System.setErr( capturingStream );
}
void unredirect() {
assertTrue( redirected );
redirected = false;
System.setErr( savedStdErr );
}
String getOutput() {
assertFalse( redirected );
return new String( buffer.toByteArray(), StandardCharsets.UTF_8 );
}
}
@Test
public void testBasicReturnTrace() throws SQLException {
final StdErrCapturer nameThis = new StdErrCapturer();
try {
nameThis.redirect();
proxyConnection.isClosed();
}
finally {
nameThis.unredirect();
}
// Check captured System.err:
final String output = nameThis.getOutput();
final String[] lines = output.split( "\n" );
assertThat( "Not 2 lines: \"\"\"" + output + "\"\"\"",
lines.length, equalTo( 2 ) );
final String callLine = lines[ 0 ];
final String returnLine = lines[ 1 ];
// Expect something like current:
// TRACER: CALL: ((Connection) <id=3> ...) . isClosed()
// TRACER: RETURN: ((Connection) <id=3> ...) . isClosed(), RESULT: (boolean) false
assertThat( callLine, containsString( " CALL:" ) );
assertThat( returnLine, containsString( " RETURN:" ) );
assertThat( callLine, containsString( "(Connection)" ) );
assertThat( returnLine, containsString( "(Connection)" ) );
assertThat( callLine, containsString( "isClosed()" ) );
assertThat( returnLine, containsString( "isClosed()" ) );
assertThat( callLine, not( containsString( " (boolean) " ) ) );
assertThat( returnLine, containsString( " (boolean) " ) );
assertThat( callLine, not( containsString( "false" ) ) );
assertThat( returnLine, containsString( "false" ) );
}
@Test
public void testBasicThrowTrace() throws SQLException {
final StdErrCapturer stdErrCapturer = new StdErrCapturer();
final Statement statement = proxyConnection.createStatement();
statement.close();
try {
stdErrCapturer.redirect();
statement.execute( "" );
}
catch ( final SQLException e ) {
// "already closed" is expected
}
finally {
stdErrCapturer.unredirect();
}
// Check captured System.err:
final String output = stdErrCapturer.getOutput();
final String[] lines = output.split( "\n" );
assertThat( "Not 2 lines: \"\"\"" + output + "\"\"\"",
lines.length, equalTo( 2 ) );
final String callLine = lines[ 0 ];
final String returnLine = lines[ 1 ];
// Expect something like current:
// TRACER: CALL: ((Statement) <id=6> ...) . execute( (String) "" )
// TRACER: THROW: ((Statement) <id=6> ...) . execute( (String) "" ), th\
// rew: (org.apache.drill.jdbc.AlreadyClosedSqlException) org.apache.dri\
// ll.jdbc.AlreadyClosedSqlException: Statement is already closed.
assertThat( callLine, containsString( " CALL:" ) );
assertThat( returnLine, containsString( " THROW:" ) );
assertThat( callLine, containsString( "(Statement)" ) );
assertThat( returnLine, containsString( "(Statement)" ) );
assertThat( callLine, containsString( "execute(" ) );
assertThat( returnLine, containsString( "execute(" ) );
assertThat( callLine, not( containsString( "threw:" ) ) );
assertThat( returnLine, containsString( "threw:" ) );
assertThat( callLine, not( anyOf( containsString( "exception" ),
containsString( "Exception" ) ) ) );
assertThat( returnLine, anyOf( containsString( "exception" ),
containsString( "Exception" ) ) );
assertThat( callLine, not( anyOf( containsString( "closed" ),
containsString( "Closed" ) ) ) );
assertThat( returnLine, anyOf( containsString( "closed" ),
containsString( "Closed" ) ) );
}
// TODO: Clean up these assorted remnants; probably move into separate test
// methods.
@Test
public void testUnsortedMethods() throws SQLException {
// Exercise these, even though we don't check results.
proxyDriver.getMajorVersion();
proxyDriver.getMinorVersion();
proxyDriver.jdbcCompliant();
proxyDriver.getParentLogger();
proxyDriver.getPropertyInfo( "jdbc:proxy::jdbc:drill:zk=local", new Properties() );
final DatabaseMetaData dbMetaData = proxyConnection.getMetaData();
assertThat( dbMetaData, instanceOf( DatabaseMetaData.class ) );
assertThat( dbMetaData, notNullValue() );
assertThat( dbMetaData.getConnection(), sameInstance( proxyConnection ) );
dbMetaData.allTablesAreSelectable();
try {
dbMetaData.ownUpdatesAreVisible( ResultSet.TYPE_FORWARD_ONLY );
fail();
}
catch ( SQLException | RuntimeException e ) {
// expected
}
final ResultSet catalogsResultSet = dbMetaData.getCatalogs();
assertThat( catalogsResultSet, notNullValue() );
assertThat( catalogsResultSet, instanceOf( ResultSet.class ) );
catalogsResultSet.next();
catalogsResultSet.getString( 1 );
catalogsResultSet.getObject( 1 );
final ResultSetMetaData rsMetaData = catalogsResultSet.getMetaData();
assertThat( rsMetaData, notNullValue() );
assertThat( rsMetaData, instanceOf( ResultSetMetaData.class ) );
int colCount = rsMetaData.getColumnCount();
for ( int cx = 1; cx <= colCount; cx++ ) {
catalogsResultSet.getObject( cx );
catalogsResultSet.getString( cx );
try {
catalogsResultSet.getInt( cx );
fail( "Expected some kind of string-to-int exception.");
}
catch ( SQLException e ) {
// expected;
}
}
assertThat( proxyConnection.getMetaData(), sameInstance( dbMetaData ) );
assertThat( catalogsResultSet.getMetaData(), sameInstance( rsMetaData ) );
}
} // class ProxyDriverTest