http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/server/src/test/java/org/apache/calcite/avatica/remote/RemoteMetaTest.java ---------------------------------------------------------------------- diff --git a/avatica/server/src/test/java/org/apache/calcite/avatica/remote/RemoteMetaTest.java b/avatica/server/src/test/java/org/apache/calcite/avatica/remote/RemoteMetaTest.java new file mode 100644 index 0000000..ff95a45 --- /dev/null +++ b/avatica/server/src/test/java/org/apache/calcite/avatica/remote/RemoteMetaTest.java @@ -0,0 +1,626 @@ +/* + * 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.avatica.remote; + +import org.apache.calcite.avatica.AvaticaConnection; +import org.apache.calcite.avatica.AvaticaSqlException; +import org.apache.calcite.avatica.AvaticaStatement; +import org.apache.calcite.avatica.AvaticaUtils; +import org.apache.calcite.avatica.ConnectionPropertiesImpl; +import org.apache.calcite.avatica.ConnectionSpec; +import org.apache.calcite.avatica.Meta; +import org.apache.calcite.avatica.jdbc.JdbcMeta; +import org.apache.calcite.avatica.remote.Service.ErrorResponse; +import org.apache.calcite.avatica.remote.Service.Response; +import org.apache.calcite.avatica.server.AvaticaJsonHandler; +import org.apache.calcite.avatica.server.AvaticaProtobufHandler; +import org.apache.calcite.avatica.server.HttpServer; +import org.apache.calcite.avatica.server.Main; +import org.apache.calcite.avatica.server.Main.HandlerFactory; +import org.apache.calcite.avatica.util.ArrayImpl; + +import com.google.common.base.Throwables; +import com.google.common.cache.Cache; + +import org.junit.AfterClass; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.net.InetAddress; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.sql.Array; +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.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.UUID; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +/** Tests covering {@link RemoteMeta}. */ +@RunWith(Parameterized.class) +public class RemoteMetaTest { + private static final Random RANDOM = new Random(); + private static final ConnectionSpec CONNECTION_SPEC = ConnectionSpec.HSQLDB; + + // Keep a reference to the servers we start to clean them up after + private static final List<HttpServer> ACTIVE_SERVERS = new ArrayList<>(); + + private final HttpServer server; + private final String url; + private final int port; + private final Driver.Serialization serialization; + + @Parameters + public static List<Object[]> parameters() throws Exception { + List<Object[]> params = new ArrayList<>(); + + final String[] mainArgs = { FullyRemoteJdbcMetaFactory.class.getName() }; + + // Bind to '0' to pluck an ephemeral port instead of expecting a certain one to be free + + final HttpServer jsonServer = Main.start(mainArgs, 0, new HandlerFactory() { + @Override public AvaticaJsonHandler createHandler(Service service) { + return new AvaticaJsonHandler(service); + } + }); + params.add(new Object[] {jsonServer, Driver.Serialization.JSON}); + ACTIVE_SERVERS.add(jsonServer); + + final HttpServer protobufServer = Main.start(mainArgs, 0, new HandlerFactory() { + @Override public AvaticaProtobufHandler createHandler(Service service) { + return new AvaticaProtobufHandler(service); + } + }); + params.add(new Object[] {protobufServer, Driver.Serialization.PROTOBUF}); + + ACTIVE_SERVERS.add(protobufServer); + + return params; + } + + public RemoteMetaTest(HttpServer server, Driver.Serialization serialization) { + this.server = server; + this.port = this.server.getPort(); + this.serialization = serialization; + url = "jdbc:avatica:remote:url=http://localhost:" + port + ";serialization=" + + serialization.name(); + } + + @AfterClass public static void afterClass() throws Exception { + for (HttpServer server : ACTIVE_SERVERS) { + if (server != null) { + server.stop(); + } + } + } + + private static Meta getMeta(AvaticaConnection conn) throws Exception { + Field f = AvaticaConnection.class.getDeclaredField("meta"); + f.setAccessible(true); + return (Meta) f.get(conn); + } + + private static Meta.ExecuteResult prepareAndExecuteInternal(AvaticaConnection conn, + final AvaticaStatement statement, String sql, int maxRowCount) throws Exception { + Method m = + AvaticaConnection.class.getDeclaredMethod("prepareAndExecuteInternal", + AvaticaStatement.class, String.class, long.class); + m.setAccessible(true); + return (Meta.ExecuteResult) m.invoke(conn, statement, sql, maxRowCount); + } + + private static Connection getConnection(JdbcMeta m, String id) throws Exception { + Field f = JdbcMeta.class.getDeclaredField("connectionCache"); + f.setAccessible(true); + //noinspection unchecked + Cache<String, Connection> connectionCache = (Cache<String, Connection>) f.get(m); + return connectionCache.getIfPresent(id); + } + + @Test public void testRemoteExecuteMaxRowCount() throws Exception { + ConnectionSpec.getDatabaseLock().lock(); + try (AvaticaConnection conn = (AvaticaConnection) DriverManager.getConnection(url)) { + final AvaticaStatement statement = conn.createStatement(); + prepareAndExecuteInternal(conn, statement, + "select * from (values ('a', 1), ('b', 2))", 0); + ResultSet rs = statement.getResultSet(); + int count = 0; + while (rs.next()) { + count++; + } + assertEquals("Check maxRowCount=0 and ResultSets is 0 row", count, 0); + assertEquals("Check result set meta is still there", + rs.getMetaData().getColumnCount(), 2); + rs.close(); + statement.close(); + conn.close(); + } finally { + ConnectionSpec.getDatabaseLock().unlock(); + } + } + + /** Test case for + * <a href="https://issues.apache.org/jira/browse/CALCITE-780">[CALCITE-780] + * HTTP error 413 when sending a long string to the Avatica server</a>. */ + @Test public void testRemoteExecuteVeryLargeQuery() throws Exception { + ConnectionSpec.getDatabaseLock().lock(); + try { + // Before the bug was fixed, a value over 7998 caused an HTTP 413. + // 16K bytes, I guess. + checkLargeQuery(8); + checkLargeQuery(240); + checkLargeQuery(8000); + checkLargeQuery(240000); + } finally { + ConnectionSpec.getDatabaseLock().unlock(); + } + } + + private void checkLargeQuery(int n) throws Exception { + try (AvaticaConnection conn = (AvaticaConnection) DriverManager.getConnection(url)) { + final AvaticaStatement statement = conn.createStatement(); + final String frenchDisko = "It said human existence is pointless\n" + + "As acts of rebellious solidarity\n" + + "Can bring sense in this world\n" + + "La resistance!\n"; + final String sql = "select '" + + longString(frenchDisko, n) + + "' as s from (values 'x')"; + prepareAndExecuteInternal(conn, statement, sql, -1); + ResultSet rs = statement.getResultSet(); + int count = 0; + while (rs.next()) { + count++; + } + assertThat(count, is(1)); + rs.close(); + statement.close(); + conn.close(); + } + } + + /** Creates a string of exactly {@code length} characters by concatenating + * {@code fragment}. */ + private static String longString(String fragment, int length) { + assert fragment.length() > 0; + final StringBuilder buf = new StringBuilder(); + while (buf.length() < length) { + buf.append(fragment); + } + buf.setLength(length); + return buf.toString(); + } + + @Test public void testRemoteConnectionProperties() throws Exception { + ConnectionSpec.getDatabaseLock().lock(); + try (AvaticaConnection conn = (AvaticaConnection) DriverManager.getConnection(url)) { + String id = conn.id; + final Map<String, ConnectionPropertiesImpl> m = ((RemoteMeta) getMeta(conn)).propsMap; + assertFalse("remote connection map should start ignorant", m.containsKey(id)); + // force creating a connection object on the remote side. + try (final Statement stmt = conn.createStatement()) { + assertTrue("creating a statement starts a local object.", m.containsKey(id)); + assertTrue(stmt.execute("select count(1) from EMP")); + } + Connection remoteConn = getConnection(FullyRemoteJdbcMetaFactory.getInstance(), id); + final boolean defaultRO = remoteConn.isReadOnly(); + final boolean defaultAutoCommit = remoteConn.getAutoCommit(); + final String defaultCatalog = remoteConn.getCatalog(); + final String defaultSchema = remoteConn.getSchema(); + conn.setReadOnly(!defaultRO); + assertTrue("local changes dirty local state", m.get(id).isDirty()); + assertEquals("remote connection has not been touched", defaultRO, remoteConn.isReadOnly()); + conn.setAutoCommit(!defaultAutoCommit); + assertEquals("remote connection has not been touched", + defaultAutoCommit, remoteConn.getAutoCommit()); + + // further interaction with the connection will force a sync + try (final Statement stmt = conn.createStatement()) { + assertEquals(!defaultAutoCommit, remoteConn.getAutoCommit()); + assertFalse("local values should be clean", m.get(id).isDirty()); + } + } finally { + ConnectionSpec.getDatabaseLock().unlock(); + } + } + + @Test public void testRemoteStatementInsert() throws Exception { + final String t = AvaticaUtils.unique("TEST_TABLE2"); + AvaticaConnection conn = (AvaticaConnection) DriverManager.getConnection(url); + Statement statement = conn.createStatement(); + final String create = + String.format("create table if not exists %s (" + + " id int not null, msg varchar(255) not null)", t); + int status = statement.executeUpdate(create); + assertEquals(status, 0); + + statement = conn.createStatement(); + final String update = String.format("insert into %s values ('%d', '%s')", + t, RANDOM.nextInt(Integer.MAX_VALUE), UUID.randomUUID()); + status = statement.executeUpdate(update); + assertEquals(status, 1); + } + + @Test public void testBigints() throws Exception { + final String table = "TESTBIGINTS"; + ConnectionSpec.getDatabaseLock().lock(); + try (AvaticaConnection conn = (AvaticaConnection) DriverManager.getConnection(url); + Statement stmt = conn.createStatement()) { + assertFalse(stmt.execute("DROP TABLE IF EXISTS " + table)); + assertFalse(stmt.execute("CREATE TABLE " + table + " (id BIGINT)")); + assertFalse(stmt.execute("INSERT INTO " + table + " values(10)")); + ResultSet results = conn.getMetaData().getColumns(null, null, table, null); + assertTrue(results.next()); + assertEquals(table, results.getString(3)); + // ordinal position + assertEquals(1L, results.getLong(17)); + } finally { + ConnectionSpec.getDatabaseLock().unlock(); + } + } + + @Test public void testOpenConnectionWithProperties() throws Exception { + // This tests that username and password are used for creating a connection on the + // server. If this was not the case, it would succeed. + try { + DriverManager.getConnection(url, "john", "doe"); + fail("expected exception"); + } catch (RuntimeException e) { + assertEquals("Remote driver error: RuntimeException: " + + "java.sql.SQLInvalidAuthorizationSpecException: invalid authorization specification" + + " - not found: john" + + " -> SQLInvalidAuthorizationSpecException: invalid authorization specification - " + + "not found: john" + + " -> HsqlException: invalid authorization specification - not found: john", + e.getMessage()); + } + } + + @Test public void testRemoteConnectionsAreDifferent() throws SQLException { + Connection conn1 = DriverManager.getConnection(url); + Statement stmt = conn1.createStatement(); + stmt.execute("DECLARE LOCAL TEMPORARY TABLE" + + " buffer (id INTEGER PRIMARY KEY, textdata VARCHAR(100))"); + stmt.execute("insert into buffer(id, textdata) values(1, 'abc')"); + stmt.executeQuery("select * from buffer"); + + // The local temporary table is local to the connection above, and should + // not be visible on another connection + Connection conn2 = DriverManager.getConnection(url); + Statement stmt2 = conn2.createStatement(); + try { + stmt2.executeQuery("select * from buffer"); + fail("expected exception"); + } catch (Exception e) { + assertEquals("Error -1 (00000) : Error while executing SQL \"select * from buffer\": " + + "Remote driver error: RuntimeException: java.sql.SQLSyntaxErrorException: " + + "user lacks privilege or object not found: BUFFER -> " + + "SQLSyntaxErrorException: user lacks privilege or object not found: BUFFER -> " + + "HsqlException: user lacks privilege or object not found: BUFFER", + e.getMessage()); + } + } + + @Ignore("[CALCITE-942] AvaticaConnection should fail-fast when closed.") + @Test public void testRemoteConnectionClosing() throws Exception { + AvaticaConnection conn = (AvaticaConnection) DriverManager.getConnection(url); + // Verify connection is usable + conn.createStatement(); + conn.close(); + + // After closing the connection, it should not be usable anymore + try { + conn.createStatement(); + fail("expected exception"); + } catch (SQLException e) { + assertThat(e.getMessage(), + containsString("Connection is closed")); + } + } + + @Test public void testExceptionPropagation() throws Exception { + final String sql = "SELECT * from EMP LIMIT FOOBARBAZ"; + ConnectionSpec.getDatabaseLock().lock(); + try (final AvaticaConnection conn = (AvaticaConnection) DriverManager.getConnection(url); + final Statement stmt = conn.createStatement()) { + try { + // invalid SQL + stmt.execute(sql); + fail("Expected an AvaticaSqlException"); + } catch (AvaticaSqlException e) { + assertEquals(ErrorResponse.UNKNOWN_ERROR_CODE, e.getErrorCode()); + assertEquals(ErrorResponse.UNKNOWN_SQL_STATE, e.getSQLState()); + assertTrue("Message should contain original SQL, was '" + e.getMessage() + "'", + e.getMessage().contains(sql)); + assertEquals(1, e.getStackTraces().size()); + final String stacktrace = e.getStackTraces().get(0); + final String substring = "unexpected token: FOOBARBAZ"; + assertTrue("Message should contain '" + substring + "', was '" + e.getMessage() + ",", + stacktrace.contains(substring)); + } + } finally { + ConnectionSpec.getDatabaseLock().unlock(); + } + } + + @Test public void testRemoteColumnsMeta() throws Exception { + // Verify all columns are retrieved, thus that frame-based fetching works correctly for columns + int rowCount = 0; + try (AvaticaConnection conn = (AvaticaConnection) DriverManager.getConnection(url)) { + ResultSet rs = conn.getMetaData().getColumns(null, null, null, null); + while (rs.next()) { + rowCount++; + } + rs.close(); + + // The implicitly created statement should have been closed + assertTrue(rs.getStatement().isClosed()); + } + // default fetch size is 100, we are well beyond it + assertTrue(rowCount > 900); + } + + @Test public void testArrays() throws SQLException { + ConnectionSpec.getDatabaseLock().lock(); + try (AvaticaConnection conn = (AvaticaConnection) DriverManager.getConnection(url); + Statement stmt = conn.createStatement()) { + ResultSet resultSet = + stmt.executeQuery("select * from (values ('a', array['b', 'c']));"); + + assertTrue(resultSet.next()); + assertEquals("a", resultSet.getString(1)); + Array arr = resultSet.getArray(2); + assertTrue(arr instanceof ArrayImpl); + Object[] values = (Object[]) ((ArrayImpl) arr).getArray(); + assertArrayEquals(new String[]{"b", "c"}, values); + } finally { + ConnectionSpec.getDatabaseLock().unlock(); + } + } + + @Test public void testBinaryAndStrings() throws Exception { + final String tableName = "testbinaryandstrs"; + final byte[] data = "asdf".getBytes(StandardCharsets.UTF_8); + ConnectionSpec.getDatabaseLock().lock(); + try (final Connection conn = DriverManager.getConnection(url); + final Statement stmt = conn.createStatement()) { + assertFalse(stmt.execute("DROP TABLE IF EXISTS " + tableName)); + assertFalse(stmt.execute("CREATE TABLE " + tableName + "(id int, bin BINARY(4))")); + try (final PreparedStatement prepStmt = conn.prepareStatement( + "INSERT INTO " + tableName + " values(1, ?)")) { + prepStmt.setBytes(1, data); + assertFalse(prepStmt.execute()); + } + try (ResultSet results = stmt.executeQuery("SELECT id, bin from " + tableName)) { + assertTrue(results.next()); + assertEquals(1, results.getInt(1)); + // byte comparison should work + assertArrayEquals("Bytes were " + Arrays.toString(results.getBytes(2)), + data, results.getBytes(2)); + // as should string + assertEquals(new String(data, StandardCharsets.UTF_8), results.getString(2)); + assertFalse(results.next()); + } + } finally { + ConnectionSpec.getDatabaseLock().unlock(); + } + } + + @Test public void testLocalStackTraceHasServerStackTrace() { + ConnectionSpec.getDatabaseLock().lock(); + try { + Statement statement = DriverManager.getConnection(url).createStatement(); + statement.executeQuery("SELECT * FROM BOGUS_TABLE_DEF_DOESNT_EXIST"); + } catch (SQLException e) { + // Verify that we got the expected exception + assertThat(e, instanceOf(AvaticaSqlException.class)); + + // Attempt to verify that we got a "server-side" class in the stack. + assertThat(Throwables.getStackTraceAsString(e), + containsString(JdbcMeta.class.getName())); + } finally { + ConnectionSpec.getDatabaseLock().unlock(); + } + } + + @Test public void testServerAddressInResponse() throws Exception { + ConnectionSpec.getDatabaseLock().lock(); + try { + URL url = new URL("http://localhost:" + this.port); + AvaticaHttpClient httpClient = new AvaticaHttpClientImpl(url); + byte[] request; + + Service.OpenConnectionRequest jsonReq = new Service.OpenConnectionRequest( + UUID.randomUUID().toString(), Collections.<String, String>emptyMap()); + switch (this.serialization) { + case JSON: + request = JsonService.MAPPER.writeValueAsBytes(jsonReq); + break; + case PROTOBUF: + ProtobufTranslation pbTranslation = new ProtobufTranslationImpl(); + request = pbTranslation.serializeRequest(jsonReq); + break; + default: + throw new IllegalStateException("Should not reach here"); + } + + byte[] response = httpClient.send(request); + Service.OpenConnectionResponse openCnxnResp; + switch (this.serialization) { + case JSON: + openCnxnResp = JsonService.MAPPER.readValue(response, + Service.OpenConnectionResponse.class); + break; + case PROTOBUF: + ProtobufTranslation pbTranslation = new ProtobufTranslationImpl(); + Response genericResp = pbTranslation.parseResponse(response); + assertTrue("Expected an OpenConnnectionResponse, but got " + genericResp.getClass(), + genericResp instanceof Service.OpenConnectionResponse); + openCnxnResp = (Service.OpenConnectionResponse) genericResp; + break; + default: + throw new IllegalStateException("Should not reach here"); + } + + String hostname = InetAddress.getLocalHost().getHostName(); + + assertNotNull(openCnxnResp.rpcMetadata); + assertEquals(hostname + ":" + this.port, openCnxnResp.rpcMetadata.serverAddress); + } finally { + ConnectionSpec.getDatabaseLock().unlock(); + } + } + + @Test public void testCommitRollback() throws Exception { + final String productTable = "commitrollback_products"; + final String salesTable = "commitrollback_sales"; + ConnectionSpec.getDatabaseLock().lock(); + try (final Connection conn = DriverManager.getConnection(url); + final Statement stmt = conn.createStatement()) { + assertFalse(stmt.execute("DROP TABLE IF EXISTS " + productTable)); + assertFalse( + stmt.execute( + String.format("CREATE TABLE %s(id integer, stock integer)", productTable))); + assertFalse(stmt.execute("DROP TABLE IF EXISTS " + salesTable)); + assertFalse( + stmt.execute( + String.format("CREATE TABLE %s(id integer, units_sold integer)", salesTable))); + + final int productId = 1; + // No products and no sales + assertFalse( + stmt.execute( + String.format("INSERT INTO %s VALUES(%d, 0)", productTable, productId))); + assertFalse( + stmt.execute( + String.format("INSERT INTO %s VALUES(%d, 0)", salesTable, productId))); + + conn.setAutoCommit(false); + PreparedStatement productStmt = conn.prepareStatement( + String.format("UPDATE %s SET stock = stock + ? WHERE id = ?", productTable)); + PreparedStatement salesStmt = conn.prepareStatement( + String.format("UPDATE %s SET units_sold = units_sold + ? WHERE id = ?", salesTable)); + + // No stock + assertEquals(0, getInventory(conn, productTable, productId)); + + // Set a stock of 10 for product 1 + productStmt.setInt(1, 10); + productStmt.setInt(2, productId); + productStmt.executeUpdate(); + + conn.commit(); + assertEquals(10, getInventory(conn, productTable, productId)); + + // Sold 5 items (5 in stock, 5 sold) + productStmt.setInt(1, -5); + productStmt.setInt(2, productId); + productStmt.executeUpdate(); + salesStmt.setInt(1, 5); + salesStmt.setInt(2, productId); + salesStmt.executeUpdate(); + + conn.commit(); + // We will definitely see the updated values + assertEquals(5, getInventory(conn, productTable, productId)); + assertEquals(5, getSales(conn, salesTable, productId)); + + // Update some "bad" values + productStmt.setInt(1, -10); + productStmt.setInt(2, productId); + productStmt.executeUpdate(); + salesStmt.setInt(1, 10); + salesStmt.setInt(2, productId); + salesStmt.executeUpdate(); + + // We just went negative, nonsense. Better rollback. + conn.rollback(); + + // Should still have 5 and 5 + assertEquals(5, getInventory(conn, productTable, productId)); + assertEquals(5, getSales(conn, salesTable, productId)); + } finally { + ConnectionSpec.getDatabaseLock().unlock(); + } + } + + private int getInventory(Connection conn, String productTable, int productId) throws Exception { + try (Statement stmt = conn.createStatement()) { + ResultSet results = stmt.executeQuery( + String.format("SELECT stock FROM %s WHERE id = %d", productTable, productId)); + assertTrue(results.next()); + return results.getInt(1); + } + } + + private int getSales(Connection conn, String salesTable, int productId) throws Exception { + try (Statement stmt = conn.createStatement()) { + ResultSet results = stmt.executeQuery( + String.format("SELECT units_sold FROM %s WHERE id = %d", salesTable, productId)); + assertTrue(results.next()); + return results.getInt(1); + } + } + + /** Factory that provides a {@link JdbcMeta}. */ + public static class FullyRemoteJdbcMetaFactory implements Meta.Factory { + + private static JdbcMeta instance = null; + + private static JdbcMeta getInstance() { + if (instance == null) { + try { + instance = new JdbcMeta(CONNECTION_SPEC.url, CONNECTION_SPEC.username, + CONNECTION_SPEC.password); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + return instance; + } + + @Override public Meta create(List<String> args) { + return getInstance(); + } + } +} + +// End RemoteMetaTest.java
http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/server/src/test/java/org/apache/calcite/avatica/server/HandlerFactoryTest.java ---------------------------------------------------------------------- diff --git a/avatica/server/src/test/java/org/apache/calcite/avatica/server/HandlerFactoryTest.java b/avatica/server/src/test/java/org/apache/calcite/avatica/server/HandlerFactoryTest.java new file mode 100644 index 0000000..3504e02 --- /dev/null +++ b/avatica/server/src/test/java/org/apache/calcite/avatica/server/HandlerFactoryTest.java @@ -0,0 +1,58 @@ +/* + * 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.avatica.server; + +import org.apache.calcite.avatica.remote.Driver.Serialization; +import org.apache.calcite.avatica.remote.Service; + +import org.eclipse.jetty.server.Handler; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +import static org.junit.Assert.assertTrue; + +/** + * Tests the {@link HandlerFactory} implementation. + */ +public class HandlerFactoryTest { + + private HandlerFactory factory; + private Service service; + + @Before + public void setup() { + this.factory = new HandlerFactory(); + this.service = Mockito.mock(Service.class); + } + + @Test + public void testJson() { + Handler handler = factory.getHandler(service, Serialization.JSON); + assertTrue("Expected an implementation of the AvaticaHandler, " + + "but got " + handler.getClass(), handler instanceof AvaticaJsonHandler); + } + + @Test + public void testProtobuf() { + Handler handler = factory.getHandler(service, Serialization.PROTOBUF); + assertTrue("Expected an implementation of the AvaticaProtobufHandler, " + + "but got " + handler.getClass(), handler instanceof AvaticaProtobufHandler); + } +} + +// End HandlerFactoryTest.java http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/server/src/test/java/org/apache/calcite/avatica/test/AvaticaSuite.java ---------------------------------------------------------------------- diff --git a/avatica/server/src/test/java/org/apache/calcite/avatica/test/AvaticaSuite.java b/avatica/server/src/test/java/org/apache/calcite/avatica/test/AvaticaSuite.java new file mode 100644 index 0000000..4a0c26c --- /dev/null +++ b/avatica/server/src/test/java/org/apache/calcite/avatica/test/AvaticaSuite.java @@ -0,0 +1,37 @@ +/* + * 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.avatica.test; + +import org.apache.calcite.avatica.RemoteDriverTest; + +import org.junit.runner.RunWith; + +import org.junit.runners.Suite; + +/** + * Avatica test suite. + */ +@RunWith(Suite.class) [email protected]({ + AvaticaUtilsTest.class, + ConnectStringParserTest.class, + RemoteDriverTest.class +}) +public class AvaticaSuite { +} + +// End AvaticaSuite.java http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/server/src/test/resources/log4j.properties ---------------------------------------------------------------------- diff --git a/avatica/server/src/test/resources/log4j.properties b/avatica/server/src/test/resources/log4j.properties new file mode 100644 index 0000000..834e2db --- /dev/null +++ b/avatica/server/src/test/resources/log4j.properties @@ -0,0 +1,24 @@ +# 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. + +# Root logger is configured at INFO and is sent to A1 +log4j.rootLogger=INFO, A1 + +# A1 goes to the console +log4j.appender.A1=org.apache.log4j.ConsoleAppender + +# Set the pattern for each log message +log4j.appender.A1.layout=org.apache.log4j.PatternLayout +log4j.appender.A1.layout.ConversionPattern=%d [%t] %-5p - %m%n http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/site/.gitignore ---------------------------------------------------------------------- diff --git a/avatica/site/.gitignore b/avatica/site/.gitignore new file mode 100644 index 0000000..09c86a2 --- /dev/null +++ b/avatica/site/.gitignore @@ -0,0 +1,2 @@ +.sass-cache +Gemfile.lock http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/site/Gemfile ---------------------------------------------------------------------- diff --git a/avatica/site/Gemfile b/avatica/site/Gemfile new file mode 100644 index 0000000..c13c470 --- /dev/null +++ b/avatica/site/Gemfile @@ -0,0 +1,20 @@ +# 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. +# +source 'https://rubygems.org' +gem 'github-pages' +gem 'rouge' +gem 'jekyll-oembed', :require => 'jekyll_oembed' +# End Gemfile http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/site/README.md ---------------------------------------------------------------------- diff --git a/avatica/site/README.md b/avatica/site/README.md new file mode 100644 index 0000000..ea3f212 --- /dev/null +++ b/avatica/site/README.md @@ -0,0 +1,57 @@ +<!-- +{% comment %} +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. +{% endcomment %} +--> + +# Apache Calcite docs site + +This directory contains the code for the Apache Calcite web site, +[calcite.apache.org](https://calcite.apache.org/). + +## Setup + +1. `cd site` +2. `svn co https://svn.apache.org/repos/asf/calcite/site target` +3. `sudo apt-get install rubygems ruby2.1-dev zlib1g-dev` (linux) +4. `sudo gem install bundler github-pages jekyll jekyll-oembed` +5. `bundle install` + +## Add javadoc + +1. `cd ..` +2. `mvn -DskipTests site` +3. `rm -rf site/target/apidocs site/target/testapidocs` +4. `mv target/site/apidocs target/site/testapidocs site/target` + +## Running locally + +Before opening a pull request, you can preview your contributions by +running from within the directory: + +1. `bundle exec jekyll serve` +2. Open [http://localhost:4000](http://localhost:4000) + +## Pushing to site + +1. `cd site/target` +2. `svn status` +3. You'll need to `svn add` any new files +4. `svn ci` + +Within a few minutes, svnpubsub should kick in and you'll be able to +see the results at +[calcite.apache.org](https://calcite.apache.org/). http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/site/_config.yml ---------------------------------------------------------------------- diff --git a/avatica/site/_config.yml b/avatica/site/_config.yml new file mode 100644 index 0000000..9b8de85 --- /dev/null +++ b/avatica/site/_config.yml @@ -0,0 +1,43 @@ +# 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. +# +markdown: kramdown +permalink: /news/:year/:month/:day/:title/ +excerpt_separator: "" + +repository: https://github.com/apache/calcite +destination: target +exclude: [README.md,Gemfile*] +keep_files: [".git", ".svn", "apidocs", "testapidocs"] + +collections: + docs: + output: true + +# The URL where the code can be found +sourceRoot: https://github.com/apache/calcite/blob/master + +# The URL where Javadocs are located +apiRoot: /apidocs +# apiRoot: http://calcite.apache.org/apidocs + +# The URL where Test Javadocs are located +testApiRoot: /testapidocs +# testApiRoot: http://calcite.apache.org/testapidocs + +# The base path where the website is deployed +baseurl: + +# End _config.yml http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/site/_data/contributors.yml ---------------------------------------------------------------------- diff --git a/avatica/site/_data/contributors.yml b/avatica/site/_data/contributors.yml new file mode 100644 index 0000000..98f8bf9 --- /dev/null +++ b/avatica/site/_data/contributors.yml @@ -0,0 +1,96 @@ +# 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. +# +# Database of contributors to Apache Calcite. +# Pages such as developer.md use this data. +# +- name: Alan Gates + apacheId: gates + githubId: alanfgates + org: Hortonworks + role: PMC +- name: Aman Sinha + apacheId: amansinha + githubId: amansinha100 + org: MapR + role: PMC +- name: Ashutosh Chauhan + apacheId: hashutosh + githubId: ashutoshc + org: Hortonworks + role: PMC +- name: James R. Taylor + apacheId: jamestaylor + githubId: JamesRTaylor + org: Salesforce + role: PMC +- name: Jacques Nadeau + apacheId: jacques + githubId: jacques-n + org: Dremio + role: PMC +- name: Jesús Camacho RodrÃguez + apacheId: jcamacho + githubId: jcamachor + org: Hortonworks + role: PMC +- name: Jinfeng Ni + apacheId: jni + githubId: jinfengni + org: MapR + role: PMC +- name: John Pullokkaran + apacheId: jpullokk + githubId: jpullokkaran + org: Hortonworks + role: PMC +- name: Josh Elser + apacheId: elserj + githubId: joshelser + org: Hortonworks + role: PMC +- name: Julian Hyde + apacheId: jhyde + githubId: julianhyde + org: Hortonworks + role: PMC Chair + homepage: http://people.apache.org/~jhyde +- name: Maryann Xue + apacheId: maryannxue + githubId: maryannxue + org: Intel + role: Committer +- name: Nick Dimiduk + apacheId: ndimiduk + githubId: ndimiduk + org: Hortonworks + role: PMC +- name: Steven Noels + apacheId: stevenn + githubId: stevenn + org: NGData + role: PMC +- name: Ted Dunning + apacheId: tdunning + githubId: tdunning + org: MapR + role: PMC + avatar: https://www.mapr.com/sites/default/files/otherpageimages/ted-circle-80.png +- name: Vladimir Sitnikov + apacheId: vladimirsitnikov + githubId: vlsi + org: NetCracker + role: PMC +# End contributors.yml http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/site/_data/docs.yml ---------------------------------------------------------------------- diff --git a/avatica/site/_data/docs.yml b/avatica/site/_data/docs.yml new file mode 100644 index 0000000..a996097 --- /dev/null +++ b/avatica/site/_data/docs.yml @@ -0,0 +1,48 @@ +# 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. +# +# Data that defines menu structure +# +- title: Overview + docs: + - index + - tutorial + - algebra + +- title: Advanced + docs: + - adapter + - stream + - lattice + +- title: Avatica + docs: + - avatica_overview + - avatica_roadmap + - avatica_json_reference + - avatica_protobuf_reference + +- title: Reference + docs: + - reference + - model + - howto + +- title: Meta + docs: + - history + - api + - testapi +# End docs.yml http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/site/_docs/adapter.md ---------------------------------------------------------------------- diff --git a/avatica/site/_docs/adapter.md b/avatica/site/_docs/adapter.md new file mode 100644 index 0000000..fa8eb21 --- /dev/null +++ b/avatica/site/_docs/adapter.md @@ -0,0 +1,38 @@ +--- +layout: docs +title: Adapters +permalink: /docs/adapter.html +--- +<!-- +{% comment %} +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. +{% endcomment %} +--> + +* <a href="https://github.com/apache/drill">Apache Drill adapter</a> +* Cascading adapter (<a href="https://github.com/Cascading/lingual">Lingual</a>) +* Cassandra adapter (<a href="{{ site.apiRoot }}/org/apache/calcite/adapter/cassandra/package-summary.html">calcite-cassandra</a>) +* CSV adapter (<a href="{{ site.apiRoot }}/org/apache/calcite/adapter/csv/package-summary.html">example/csv</a>) +* JDBC adapter (part of <a href="{{ site.apiRoot }}/org/apache/calcite/adapter/jdbc/package-summary.html">calcite-core</a>) +* MongoDB adapter (<a href="{{ site.apiRoot }}/org/apache/calcite/adapter/mongodb/package-summary.html">calcite-mongodb</a>) +* Spark adapter (<a href="{{ site.apiRoot }}/org/apache/calcite/adapter/spark/package-summary.html">calcite-spark</a>) +* Splunk adapter (<a href="{{ site.apiRoot }}/org/apache/calcite/adapter/splunk/package-summary.html">calcite-splunk</a>) +* Eclipse Memory Analyzer (MAT) adapter (<a href="https://github.com/vlsi/mat-calcite-plugin">mat-calcite-plugin</a>) + +## Drivers + +* <a href="{{ site.apiRoot }}/org/apache/calcite/jdbc/package-summary.html">JDBC driver</a> + http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/site/_docs/algebra.md ---------------------------------------------------------------------- diff --git a/avatica/site/_docs/algebra.md b/avatica/site/_docs/algebra.md new file mode 100644 index 0000000..e651dd0 --- /dev/null +++ b/avatica/site/_docs/algebra.md @@ -0,0 +1,369 @@ +--- +layout: docs +title: Algebra +permalink: /docs/algebra.html +--- +<!-- +{% comment %} +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. +{% endcomment %} +--> + +Relational algebra is at the heart of Calcite. Every query is +represented as a tree of relational operators. You can translate from +SQL to relational algebra, or you can build the tree directly. + +Planner rules transform expression trees using mathematical identities +that preserve semantics. For example, it is valid to push a filter +into an input of an inner join if the filter does not reference +columns from the other input. + +Calcite optimizes queries by repeatedly applying planner rules to a +relational expression. A cost model guides the process, and the +planner engine generates an alternative expression that has the same +semantics as the original but a lower cost. + +The planning process is extensible. You can add your own relational +operators, planner rules, cost model, and statistics. + +## Algebra builder + +The simplest way to build a relational expression is to use the algebra builder, +[RelBuilder]({{ site.apiRoot }}/org/apache/calcite/tools/RelBuilder.html). +Here is an example: + +### TableScan + +{% highlight java %} +final FrameworkConfig config; +final RelBuilder builder = RelBuilder.create(config); +final RelNode node = builder + .scan("EMP") + .build(); +System.out.println(RelOptUtil.toString(node)); +{% endhighlight %} + +(You can find the full code for this and other examples in +[RelBuilderExample.java]({{ site.sourceRoot }}/core/src/test/java/org/apache/calcite/examples/RelBuilderExample.java).) + +The code prints + +{% highlight text %} +LogicalTableScan(table=[[scott, EMP]]) +{% endhighlight %} + +It has created a scan of the `EMP` table; equivalent to the SQL + +{% highlight sql %} +SELECT * +FROM scott.EMP; +{% endhighlight %} + +### Adding a Project + +Now, let's add a Project, the equivalent of + +{% highlight sql %} +SELECT ename, deptno +FROM scott.EMP; +{% endhighlight %} + +We just add a call to the `project` method before calling +`build`: + +{% highlight java %} +final RelNode node = builder + .scan("EMP") + .project(builder.field("DEPTNO"), builder.field("ENAME")) + .build(); +System.out.println(RelOptUtil.toString(node)); +{% endhighlight %} + +and the output is + +{% highlight text %} +LogicalProject(DEPTNO=[$7], ENAME=[$1]) + LogicalTableScan(table=[[scott, EMP]]) +{% endhighlight %} + +The two calls to `builder.field` create simple expressions +that return the fields from the input relational expression, +namely the TableScan created by the `scan` call. + +Calcite has converted them to field references by ordinal, +`$7` and `$1`. + +### Adding a Filter and Aggregate + +A query with an Aggregate, and a Filter: + +{% highlight java %} +final RelNode node = builder + .scan("EMP") + .aggregate(builder.groupKey("DEPTNO"), + builder.count(false, "C"), + builder.sum(false, "S", builder.field("SAL"))) + .filter( + builder.call(SqlStdOperatorTable.GREATER_THAN, + builder.field("C"), + builder.literal(10))) + .build(); +System.out.println(RelOptUtil.toString(node)); +{% endhighlight %} + +is equivalent to SQL + +{% highlight sql %} +SELECT deptno, count(*) AS c, sum(sal) AS s +FROM emp +GROUP BY deptno +HAVING count(*) > 10 +{% endhighlight %} + +and produces + +{% highlight text %} +LogicalFilter(condition=[>($1, 10)]) + LogicalAggregate(group=[{7}], C=[COUNT()], S=[SUM($5)]) + LogicalTableScan(table=[[scott, EMP]]) +{% endhighlight %} + +### Push and pop + +The builder uses a stack to store the relational expression produced by +one step and pass it as an input to the next step. This allows the +methods that produce relational expressions to produce a builder. + +Most of the time, the only stack method you will use is `build()`, to get the +last relational expression, namely the root of the tree. + +Sometimes the stack becomes so deeply nested it gets confusing. To keep things +straight, you can remove expressions from the stack. For example, here we are +building a bushy join: + +{% highlight text %} +. + join + / \ + join join + / \ / \ +CUSTOMERS ORDERS LINE_ITEMS PRODUCTS +{% endhighlight %} + +We build it in three stages. Store the intermediate results in variables +`left` and `right`, and use `push()` to put them back on the stack when it is +time to create the final `Join`: + +{% highlight java %} +final RelNode left = builder + .scan("CUSTOMERS") + .scan("ORDERS") + .join(JoinRelType.INNER, "ORDER_ID") + .build(); + +final RelNode right = builder + .scan("LINE_ITEMS") + .scan("PRODUCTS") + .join(JoinRelType.INNER, "PRODUCT_ID") + .build(); + +final RelNode result = builder + .push(left) + .push(right) + .join(JoinRelType.INNER, "ORDER_ID") + .build(); +{% endhighlight %} + +### Field names and ordinals + +You can reference a field by name or ordinal. + +Ordinals are zero-based. Each operator guarantees the order in which its output +fields occur. For example, `Project` returns the fields in the generated by +each of the scalar expressions. + +The field names of an operator are guaranteed to be unique, but sometimes that +means that the names are not exactly what you expect. For example, when you +join EMP to DEPT, one of the output fields will be called DEPTNO and another +will be called something like DEPTNO_1. + +Some relational expression methods give you more control over field names: + +* `project` lets you wrap expressions using `alias(expr, fieldName)`. It + removes the wrapper but keeps the suggested name (as long as it is unique). +* `values(String[] fieldNames, Object... values)` accepts an array of field + names. If any element of the array is null, the builder will generate a unique + name. + +If an expression projects an input field, or a cast of an input field, it will +use the name of that input field. + +Once the unique field names have been assigned, the names are immutable. +If you have a particular `RelNode` instance, you can rely on the field names not +changing. In fact, the whole relational expression is immutable. + +But if a relational expression has passed through several rewrite rules (see +([RelOptRule]({{ site.apiRoot }}/org/apache/calcite/plan/RelOptRule.html)), the field +names of the resulting expression might not look much like the originals. +At that point it is better to reference fields by ordinal. + +When you are building a relational expression that accepts multiple inputs, +you need to build field references that take that into account. This occurs +most often when building join conditions. + +Suppose you are building a join on EMP, +which has 8 fields [EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM, DEPTNO] +and DEPT, +which has 3 fields [DEPTNO, DNAME, LOC]. +Internally, Calcite represents those fields as offsets into +a combined input row with 11 fields: the first field of the left input is +field #0 (0-based, remember), and the first field of the right input is +field #8. + +But through the builder API, you specify which field of which input. +To reference "SAL", internal field #5, +write `builder.field(2, 0, "SAL")` +or `builder.field(2, 0, 5)`. +This means "the field #5 of input #0 of two inputs". +(Why does it need to know that there are two inputs? Because they are stored on +the stack; input #1 is at the top of the stack, and input #0 is below it. +If we did not tell the builder that were two inputs, it would not know how deep +to go for input #0.) + +Similarly, to reference "DNAME", internal field #9 (8 + 1), +write `builder.field(2, 1, "DNAME")` +or `builder.field(2, 1, 1)`. + +### API summary + +#### Relational operators + +The following methods create a relational expression +([RelNode]({{ site.apiRoot }}/org/apache/calcite/rel/RelNode.html)), +push it onto the stack, and +return the `RelBuilder`. + +| Method | Description +|:------------------- |:----------- +| `scan(tableName)` | Creates a [TableScan]({{ site.apiRoot }}/org/apache/calcite/rel/core/TableScan.html). +| `values(fieldNames, value...)`<br/>`values(rowType, tupleList)` | Creates a [Values]({{ site.apiRoot }}/org/apache/calcite/rel/core/Values.html). +| `filter(expr...)`<br/>`filter(exprList)` | Creates a [Filter]({{ site.apiRoot }}/org/apache/calcite/rel/core/Filter.html) over the AND of the given predicates. +| `project(expr...)`<br/>`project(exprList [, fieldNames])` | Creates a [Project]({{ site.apiRoot }}/org/apache/calcite/rel/core/Project.html). To override the default name, wrap expressions using `alias`, or specify the `fieldNames` argument. +| `permute(mapping)` | Creates a [Project]({{ site.apiRoot }}/org/apache/calcite/rel/core/Project.html) that permutes the fields using `mapping`. +| `convert(rowType [, rename])` | Creates a [Project]({{ site.apiRoot }}/org/apache/calcite/rel/core/Project.html) that converts the fields to the given types, optionally also renaming them. +| `aggregate(groupKey, aggCall...)`<br/>`aggregate(groupKey, aggCallList)` | Creates an [Aggregate]({{ site.apiRoot }}/org/apache/calcite/rel/core/Aggregate.html). +| `distinct()` | Creates an [Aggregate]({{ site.apiRoot }}/org/apache/calcite/rel/core/Aggregate.html) that eliminates duplicate records. +| `sort(fieldOrdinal...)`<br/>`sort(expr...)`<br/>`sort(exprList)` | Creates a [Sort]({{ site.apiRoot }}/org/apache/calcite/rel/core/Sort.html).<br/><br/>In the first form, field ordinals are 0-based, and a negative ordinal indicates descending; for example, -2 means field 1 descending.<br/><br/>In the other forms, you can wrap expressions in `as`, `nullsFirst` or `nullsLast`. +| `sortLimit(offset, fetch, expr...)`<br/>`sortLimit(offset, fetch, exprList)` | Creates a [Sort]({{ site.apiRoot }}/org/apache/calcite/rel/core/Sort.html) with offset and limit. +| `limit(offset, fetch)` | Creates a [Sort]({{ site.apiRoot }}/org/apache/calcite/rel/core/Sort.html) that does not sort, only applies with offset and limit. +| `join(joinType, expr...)`<br/>`join(joinType, exprList)`<br/>`join(joinType, fieldName...)` | Creates a [Join]({{ site.apiRoot }}/org/apache/calcite/rel/core/Join.html) of the two most recent relational expressions.<br/><br/>The first form joins on a boolean expression (multiple conditions are combined using AND).<br/><br/>The last form joins on named fields; each side must have a field of each name. +| `semiJoin(expr)` | Creates a [SemiJoin]({{ site.apiRoot }}/org/apache/calcite/rel/core/SemiJoin.html) of the two most recent relational expressions. +| `union(all [, n])` | Creates a [Union]({{ site.apiRoot }}/org/apache/calcite/rel/core/Union.html) of the `n` (default two) most recent relational expressions. +| `intersect(all [, n])` | Creates an [Intersect]({{ site.apiRoot }}/org/apache/calcite/rel/core/Intersect.html) of the `n` (default two) most recent relational expressions. +| `minus(all)` | Creates a [Minus]({{ site.apiRoot }}/org/apache/calcite/rel/core/Minus.html) of the two most recent relational expressions. + +Argument types: + +* `expr` [RexNode]({{ site.apiRoot }}/org/apache/calcite/rex/RexNode.html) +* `expr...` Array of [RexNode]({{ site.apiRoot }}/org/apache/calcite/rex/RexNode.html) +* `exprList` Iterable of [RexNode]({{ site.apiRoot }}/org/apache/calcite/rex/RexNode.html) +* `fieldOrdinal` Ordinal of a field within its row (starting from 0) +* `fieldName` Name of a field, unique within its row +* `fieldName...` Array of String +* `fieldNames` Iterable of String +* `rowType` [RelDataType]({{ site.apiRoot }}/org/apache/calcite/rel/type/RelDataType.html) +* `groupKey` [RelBuilder.GroupKey]({{ site.apiRoot }}/org/apache/calcite/tools/RelBuilder.GroupKey.html) +* `aggCall...` Array of [RelBuilder.AggCall]({{ site.apiRoot }}/org/apache/calcite/tools/RelBuilder.AggCall.html) +* `aggCallList` Iterable of [RelBuilder.AggCall]({{ site.apiRoot }}/org/apache/calcite/tools/RelBuilder.AggCall.html) +* `value...` Array of Object +* `value` Object +* `tupleList` Iterable of List of [RexLiteral]({{ site.apiRoot }}/org/apache/calcite/rex/RexLiteral.html) +* `all` boolean +* `distinct` boolean +* `alias` String + +The builder methods perform various optimizations, including: +* `project` returns its input if asked to project all columns in order +* `filter` flattens the condition (so an `AND` and `OR` may have more than 2 children), + simplifies (converting say `x = 1 AND TRUE` to `x = 1`) +* If you apply `sort` then `limit`, the effect is as if you had called `sortLimit` + +### Stack methods + + +| Method | Description +|:------------------- |:----------- +| `build()` | Pops the most recently created relational expression off the stack +| `push(rel)` | Pushes a relational expression onto the stack. Relational methods such as `scan`, above, call this method, but user code generally does not +| `pushAll(collection)` | Pushes a collection of relational expressions onto the stack +| `peek()` | Returns the relational expression most recently put onto the stack, but does not remove it + +#### Scalar expression methods + +The following methods return a scalar expression +([RexNode]({{ site.apiRoot }}/org/apache/calcite/rex/RexNode.html)). + +Many of them use the contents of the stack. For example, `field("DEPTNO")` +returns a reference to the "DEPTNO" field of the relational expression just +added to the stack. + +| Method | Description +|:------------------- |:----------- +| `literal(value)` | Constant +| `field(fieldName)` | Reference, by name, to a field of the top-most relational expression +| `field(fieldOrdinal)` | Reference, by ordinal, to a field of the top-most relational expression +| `field(inputCount, inputOrdinal, fieldName)` | Reference, by name, to a field of the (`inputCount` - `inputOrdinal`)th relational expression +| `field(inputCount, inputOrdinal, fieldOrdinal)` | Reference, by ordinal, to a field of the (`inputCount` - `inputOrdinal`)th relational expression +| `fields(fieldOrdinalList)` | List of expressions referencing input fields by ordinal +| `fields(mapping)` | List of expressions referencing input fields by a given mapping +| `fields(collation)` | List of expressions, `exprList`, such that `sort(exprList)` would replicate collation +| `call(op, expr...)`<br/>`call(op, exprList)` | Call to a function or operator +| `and(expr...)`<br/>`and(exprList)` | Logical AND. Flattens nested ANDs, and optimizes cases involving TRUE and FALSE. +| `or(expr...)`<br/>`or(exprList)` | Logical OR. Flattens nested ORs, and optimizes cases involving TRUE and FALSE. +| `not(expr)` | Logical NOT +| `equals(expr, expr)` | Equals +| `isNull(expr)` | Checks whether an expression is null +| `isNotNull(expr)` | Checks whether an expression is not null +| `alias(expr, fieldName)` | Renames an expression (only valid as an argument to `project`) +| `cast(expr, typeName)`<br/>`cast(expr, typeName, precision)`<br/>`cast(expr, typeName, precision, scale)`<br/> | Converts an expression to a given type +| `desc(expr)` | Changes sort direction to descending (only valid as an argument to `sort` or `sortLimit`) +| `nullsFirst(expr)` | Changes sort order to nulls first (only valid as an argument to `sort` or `sortLimit`) +| `nullsLast(expr)` | Changes sort order to nulls last (only valid as an argument to `sort` or `sortLimit`) + +### Group key methods + +The following methods return a +[RelBuilder.GroupKey]({{ site.apiRoot }}/org/apache/calcite/tools/RelBuilder.GroupKey.html). + +| Method | Description +|:------------------- |:----------- +| `groupKey(fieldName...)`<br/>`groupKey(fieldOrdinal...)`<br/>`groupKey(expr...)`<br/>`groupKey(exprList)` | Creates a group key of the given expressions +| `groupKey(exprList, exprListList)` | Creates a group key of the given expressions with grouping sets +| `groupKey(bitSet, bitSets)` | Creates a group key of the given input columns with grouping sets + +### Aggregate call methods + +The following methods return an +[RelBuilder.AggCall]({{ site.apiRoot }}/org/apache/calcite/tools/RelBuilder.AggCall.html). + +| Method | Description +|:------------------- |:----------- +| `aggregateCall(op, distinct, filter, alias, expr...)`<br/>`aggregateCall(op, distinct, filter, alias, exprList)` | Creates a call to a given aggregate function, with an optional filter expression +| `count(distinct, alias, expr...)` | Creates a call to the COUNT aggregate function +| `countStar(alias)` | Creates a call to the COUNT(*) aggregate function +| `sum(distinct, alias, expr)` | Creates a call to the SUM aggregate function +| `min(alias, expr)` | Creates a call to the MIN aggregate function +| `max(alias, expr)` | Creates a call to the MAX aggregate function http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/site/_docs/api.md ---------------------------------------------------------------------- diff --git a/avatica/site/_docs/api.md b/avatica/site/_docs/api.md new file mode 100644 index 0000000..49f456b --- /dev/null +++ b/avatica/site/_docs/api.md @@ -0,0 +1,28 @@ +--- +title: API +layout: external +external_url: http://calcite.apache.org/apidocs +--- +{% comment %} +Ideally, we want to use {{ site.apiRoot }} instead of hardcoding +the above external_url value, but I don't believe there's a way to do that +{% endcomment %} + +<!-- +{% comment %} +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. +{% endcomment %} +-->
