/* * JBoss, Home of Professional Open Source. * See the COPYRIGHT.txt file distributed with this work for information * regarding copyright ownership. Some portions may be licensed * to Red Hat, Inc. under one or more contributor license agreements. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ package org.teiid.transport; import static org.junit.Assert.*; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.NotSerializableException; import java.io.StringReader; import java.net.InetSocketAddress; import java.sql.BatchUpdateException; 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.sql.Types; import java.util.Arrays; import java.util.Properties; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.teiid.common.buffer.BufferManagerFactory; import org.teiid.common.buffer.impl.BufferManagerImpl; import org.teiid.core.types.ArrayImpl; import org.teiid.core.types.BlobImpl; import org.teiid.core.types.BlobType; import org.teiid.core.types.ClobImpl; import org.teiid.core.types.ClobType; import org.teiid.core.types.GeometryType; import org.teiid.core.types.InputStreamFactory; import org.teiid.core.util.ObjectConverterUtil; import org.teiid.core.util.UnitTestUtil; import org.teiid.dqp.internal.datamgr.ConnectorManagerRepository; import org.teiid.dqp.service.AutoGenDataService; import org.teiid.jdbc.ConnectionImpl; import org.teiid.jdbc.ConnectionProfile; import org.teiid.jdbc.FakeServer; import org.teiid.jdbc.TeiidDriver; import org.teiid.jdbc.TeiidSQLException; import org.teiid.jdbc.TestMMDatabaseMetaData; import org.teiid.net.CommunicationException; import org.teiid.net.ConnectionException; import org.teiid.net.TeiidURL; import org.teiid.net.socket.SocketServerConnectionFactory; import org.teiid.query.function.GeometryUtils; import org.teiid.runtime.EmbeddedConfiguration; import org.teiid.runtime.HardCodedExecutionFactory; import org.teiid.translator.TranslatorException; import org.teiid.translator.ws.BinaryWSProcedureExecution; @SuppressWarnings("nls") public class TestJDBCSocketTransport { private static final int MAX_MESSAGE = 100000; private static final int MAX_LOB = 10000; static InetSocketAddress addr; static SocketListener jdbcTransport; static FakeServer server; static int delay; @BeforeClass public static void oneTimeSetup() throws Exception { SocketConfiguration config = new SocketConfiguration(); config.setSSLConfiguration(new SSLConfiguration()); addr = new InetSocketAddress(0); config.setBindAddress(addr.getHostName()); config.setPortNumber(0); EmbeddedConfiguration dqpConfig = new EmbeddedConfiguration(); dqpConfig.setMaxActivePlans(2); server = new FakeServer(false); server.start(dqpConfig, false); server.deployVDB("parts", UnitTestUtil.getTestDataPath() + "/PartsSupplier.vdb"); jdbcTransport = new SocketListener(addr, config, server.getClientServiceRegistry(), BufferManagerFactory.getStandaloneBufferManager()) { @Override protected SSLAwareChannelHandler createChannelHandler() { SSLAwareChannelHandler result = new SSLAwareChannelHandler(this) { public void messageReceived(io.netty.channel.ChannelHandlerContext ctx, Object msg) throws Exception { if (delay > 0) { Thread.sleep(delay); } super.messageReceived(ctx, msg); } }; return result; } }; jdbcTransport.setMaxMessageSize(MAX_MESSAGE); jdbcTransport.setMaxLobSize(MAX_LOB); } @AfterClass public static void oneTimeTearDown() throws Exception { if (jdbcTransport != null) { jdbcTransport.stop(); } server.stop(); } Connection conn; @Before public void setUp() throws Exception { toggleInline(true); Properties p = new Properties(); p.setProperty("user", "testuser"); p.setProperty("password", "testpassword"); conn = TeiidDriver.getInstance().connect("jdbc:teiid:parts@mm://"+addr.getHostName()+":" +jdbcTransport.getPort(), p); } private void toggleInline(boolean inline) { ((BufferManagerImpl)server.getDqp().getBufferManager()).setInlineLobs(inline); } @After public void tearDown() throws Exception { if (conn != null) { conn.close(); } } @Test public void testSelect() throws Exception { Statement s = conn.createStatement(); assertTrue(s.execute("select * from tables order by name")); TestMMDatabaseMetaData.compareResultSet(s.getResultSet()); } @Test public void testLobStreaming() throws Exception { Statement s = conn.createStatement(); assertTrue(s.execute("select xmlelement(name \"root\") from tables")); s.getResultSet().next(); assertEquals("<root></root>", s.getResultSet().getString(1)); toggleInline(false); assertTrue(s.execute("select xmlelement(name \"root\") from tables")); s.getResultSet().next(); assertEquals("<root></root>", s.getResultSet().getString(1)); } @Test public void testLobStreaming1() throws Exception { Statement s = conn.createStatement(); assertTrue(s.execute("select cast('' as clob) from tables")); s.getResultSet().next(); assertEquals("", s.getResultSet().getString(1)); toggleInline(false); assertTrue(s.execute("select cast('' as clob) from tables")); s.getResultSet().next(); assertEquals("", s.getResultSet().getString(1)); } @Test public void testVarbinary() throws Exception { Statement s = conn.createStatement(); assertTrue(s.execute("select X'aab1'")); s.getResultSet().next(); byte[] bytes = s.getResultSet().getBytes(1); assertArrayEquals(new byte[] {(byte)0xaa, (byte)0xb1}, bytes); assertArrayEquals(bytes, s.getResultSet().getBlob(1).getBytes(1, 2)); } @Test public void testVarbinaryPrepared() throws Exception { PreparedStatement s = conn.prepareStatement("select cast(? as varbinary)"); s.setBytes(1, "hello".getBytes()); assertTrue(s.execute()); s.getResultSet().next(); byte[] bytes = s.getResultSet().getBytes(1); assertEquals("hello", new String(bytes)); } @Test public void testLargeVarbinaryPrepared() throws Exception { PreparedStatement s = conn.prepareStatement("select cast(? as varbinary)"); s.setBytes(1, new byte[1 << 16]); assertTrue(s.execute()); s.getResultSet().next(); byte[] bytes = s.getResultSet().getBytes(1); assertArrayEquals(new byte[1 << 16], bytes); } @Test public void testXmlTableScrollable() throws Exception { Statement s = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); assertTrue(s.execute("select * from xmltable('/root/row' passing (select xmlelement(name \"root\", xmlagg(xmlelement(name \"row\", xmlforest(t.name)) order by t.name)) from (select t.* from tables as t, columns as t1 limit 7000) as t) columns \"Name\" string) as x")); ResultSet rs = s.getResultSet(); int count = 0; while (rs.next()) { count++; } assertEquals(7000, count); rs.beforeFirst(); while (rs.next()) { count--; } assertEquals(0, count); } @Test public void testGeneratedKeys() throws Exception { Statement s = conn.createStatement(); s.execute("set showplan debug"); s.execute("create local temporary table x (y serial, z integer, primary key (y))"); assertFalse(s.execute("insert into x (z) values (1)", Statement.RETURN_GENERATED_KEYS)); ResultSet rs = s.getGeneratedKeys(); rs.next(); assertEquals(1, rs.getInt(1)); } /** * Ensures if you start more than the maxActivePlans * where all the plans take up more than output buffer limit * that processing still proceeds * @throws Exception */ @Test public void testSimultaneousLargeSelects() throws Exception { for (int j = 0; j < 3; j++) { Statement s = conn.createStatement(); assertTrue(s.execute("select * from columns c1, columns c2")); } } /** * Tests to ensure that a SynchronousTtl/synchTimeout does * not cause a cancel. * TODO: had to increase the values since the test would fail on slow machine runs * @throws Exception */ @Test public void testSyncTimeout() throws Exception { TeiidDriver td = new TeiidDriver(); td.setSocketProfile(new ConnectionProfile() { @Override public ConnectionImpl connect(String url, Properties info) throws TeiidSQLException { SocketServerConnectionFactory sscf = new SocketServerConnectionFactory(); sscf.initialize(info); try { return new ConnectionImpl(sscf.getConnection(info), info, url); } catch (CommunicationException e) { throw TeiidSQLException.create(e); } catch (ConnectionException e) { throw TeiidSQLException.create(e); } } }); Properties p = new Properties(); p.setProperty("user", "testuser"); p.setProperty("password", "testpassword"); ConnectorManagerRepository cmr = server.getConnectorManagerRepository(); AutoGenDataService agds = new AutoGenDataService() { @Override public Object getConnectionFactory() throws TranslatorException { return null; } }; agds.setSleep(2000); //wait longer than the synch ttl/soTimeout, we should still succeed cmr.addConnectorManager("source", agds); try { conn = td.connect("jdbc:teiid:parts@mm://"+addr.getHostName()+":" +jdbcTransport.getPort(), p); Statement s = conn.createStatement(); assertTrue(s.execute("select * from parts")); } finally { server.setConnectorManagerRepository(cmr); } } @Test public void testProtocolException() throws Exception { Statement s = conn.createStatement(); s.execute("set showplan debug"); try { s.execute("select * from objecttable('teiid_context' columns teiid_row object 'teiid_row') as x"); fail(); } catch (SQLException e) { assertTrue(e.getCause() instanceof NotSerializableException); } //make sure the connection is still alive s.execute("select 1"); ResultSet rs = s.getResultSet(); rs.next(); assertEquals(1, rs.getInt(1)); } @Test public void testStreamingLob() throws Exception { HardCodedExecutionFactory ef = new HardCodedExecutionFactory(); ef.addData("SELECT helloworld.x FROM helloworld", Arrays.asList(Arrays.asList(new BlobType(new BinaryWSProcedureExecution.StreamingBlob(new ByteArrayInputStream(new byte[100])))))); server.addTranslator("custom", ef); server.deployVDB(new ByteArrayInputStream("<vdb name=\"test\" version=\"1\"><model name=\"test\"><source name=\"test\" translator-name=\"custom\"/><metadata type=\"DDL\"><![CDATA[CREATE foreign table helloworld (x blob);]]> </metadata></model></vdb>".getBytes("UTF-8"))); conn = TeiidDriver.getInstance().connect("jdbc:teiid:test@mm://"+addr.getHostName()+":" +jdbcTransport.getPort(), null); Statement s = conn.createStatement(); ResultSet rs = s.executeQuery("select to_chars(x, 'UTF-8') from helloworld"); rs.next(); //TODO: if we use getString streaming will still fail because the string logic gets the length first assertEquals(100, ObjectConverterUtil.convertToCharArray(rs.getCharacterStream(1), -1).length); } @Test public void testArray() throws Exception { Statement s = conn.createStatement(); ResultSet rs = s.executeQuery("SELECT (1, (1,2))"); rs.next(); assertEquals(new ArrayImpl(new Object[] {1, new Object[] {1, 2}}), rs.getArray(1)); assertEquals("java.sql.Array", rs.getMetaData().getColumnClassName(1)); assertEquals(Types.ARRAY, rs.getMetaData().getColumnType(1)); assertEquals("object[]", rs.getMetaData().getColumnTypeName(1)); } @Test public void testLargeMessage() throws Exception { Statement s = conn.createStatement(); StringBuilder sb = new StringBuilder(); sb.append("SELECT '"); for (int i = 0; i < MAX_MESSAGE; i++) { sb.append('a'); } sb.append('\''); try { s.executeQuery(sb.toString()); fail(); } catch (SQLException e) { } ResultSet rs = s.executeQuery("select 1"); rs.next(); assertEquals(1, rs.getInt(1)); } @Test(expected=TeiidSQLException.class) public void testLoginTimeout() throws SQLException { Properties p = new Properties(); p.setProperty(TeiidURL.CONNECTION.LOGIN_TIMEOUT, "1"); delay = 1500; try { conn = TeiidDriver.getInstance().connect("jdbc:teiid:parts@mm://"+addr.getHostName()+":" +(jdbcTransport.getPort()), p); } finally { delay = 0; } } @Test(expected=TeiidSQLException.class) public void testLargeLob() throws Exception { PreparedStatement s = conn.prepareStatement("select to_bytes(?, 'ascii')"); s.setCharacterStream(1, new StringReader(new String(new char[200000]))); s.execute(); } @Test public void testLobCase() throws Exception { Statement s = conn.createStatement(); s.execute("select ucase(cast('abc' as clob))"); s.getResultSet().next(); assertEquals("ABC", s.getResultSet().getString(1)); } @Test public void testGeometryStreaming() throws Exception { StringBuilder geomString = new StringBuilder(); for (int i = 0; i < 600; i++) { geomString.append("100 100,"); } geomString.append("100 100"); final GeometryType geo = GeometryUtils.geometryFromClob(new ClobType(new ClobImpl("POLYGON ((" + geomString + "))"))); long length = geo.length(); PreparedStatement s = conn.prepareStatement("select st_geomfrombinary(?)"); s.setBlob(1, new BlobImpl(new InputStreamFactory() { @Override public InputStream getInputStream() throws IOException { try { return geo.getBinaryStream(); } catch (SQLException e) { throw new IOException(e); } } })); ResultSet rs = s.executeQuery(); rs.next(); Blob b = rs.getBlob(1); assertEquals(length, b.length()); b.getBytes(1, (int) b.length()); toggleInline(false); rs = s.executeQuery(); rs.next(); b = rs.getBlob(1); assertEquals(length, b.length()); b.getBytes(1, (int) b.length()); } @Test public void testBatchedUpdateException() throws Exception { Statement s = conn.createStatement(); s.execute("create local temporary table x (y integer, primary key (y))"); s.addBatch("insert into x values (1)"); s.addBatch("insert into x values (1)"); try { s.executeBatch(); fail(); } catch (BatchUpdateException e) { assertEquals(1, e.getUpdateCounts()[0]); } PreparedStatement ps = conn.prepareStatement("insert into x values (?)"); ps.setInt(1, 2); ps.addBatch(); ps.setInt(1, 2); ps.addBatch(); try { ps.executeBatch(); fail(); } catch (BatchUpdateException e) { assertEquals(1, e.getUpdateCounts()[0]); } //make sure no update counts are reported when there's an issue on the first item ps = conn.prepareStatement("insert into x values (?)"); ps.setInt(1, 2); ps.addBatch(); ps.setInt(1, 2); ps.addBatch(); try { ps.executeBatch(); fail(); } catch (BatchUpdateException e) { assertEquals(0, e.getUpdateCounts().length); } } }