| /* |
| * 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.phoenix.end2end.index; |
| |
| import static org.apache.phoenix.query.QueryConstants.MILLIS_IN_DAY; |
| import static org.apache.phoenix.util.TestUtil.INDEX_DATA_SCHEMA; |
| import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES; |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertFalse; |
| import static org.junit.Assert.assertNull; |
| import static org.junit.Assert.assertTrue; |
| import static org.junit.Assert.fail; |
| |
| import java.math.BigDecimal; |
| import java.sql.Connection; |
| import java.sql.Date; |
| import java.sql.DriverManager; |
| import java.sql.PreparedStatement; |
| import java.sql.ResultSet; |
| import java.sql.SQLException; |
| import java.util.Properties; |
| |
| import org.apache.commons.lang.StringUtils; |
| import org.apache.phoenix.end2end.ParallelStatsDisabledIT; |
| import org.apache.phoenix.exception.SQLExceptionCode; |
| import org.apache.phoenix.execute.CommitException; |
| import org.apache.phoenix.query.QueryConstants; |
| import org.apache.phoenix.util.DateUtil; |
| import org.apache.phoenix.util.IndexUtil; |
| import org.apache.phoenix.util.PropertiesUtil; |
| import org.apache.phoenix.util.QueryUtil; |
| import org.apache.phoenix.util.TestUtil; |
| import org.junit.Test; |
| |
| public class IndexExpressionIT extends ParallelStatsDisabledIT { |
| |
| @Test |
| public void testImmutableIndexCreateAndUpdate() throws Exception { |
| helpTestCreateAndUpdate(false, false); |
| } |
| |
| @Test |
| public void testImmutableLocalIndexCreateAndUpdate() throws Exception { |
| helpTestCreateAndUpdate(false, true); |
| } |
| |
| @Test |
| public void testMutableIndexCreateAndUpdate() throws Exception { |
| helpTestCreateAndUpdate(true, false); |
| } |
| |
| @Test |
| public void testMutableLocalIndexCreateAndUpdate() throws Exception { |
| helpTestCreateAndUpdate(true, true); |
| } |
| |
| /** |
| * Adds a row to the index data table |
| * |
| * @param i |
| * row number |
| */ |
| private void insertRow(PreparedStatement stmt, int i) throws SQLException { |
| // insert row |
| stmt.setString(1, "varchar" + String.valueOf(i)); |
| stmt.setString(2, "char" + String.valueOf(i)); |
| stmt.setInt(3, i); |
| stmt.setLong(4, i); |
| stmt.setBigDecimal(5, new BigDecimal(i*0.5d)); |
| Date date = new Date(DateUtil.parseDate("2015-01-01 00:00:00").getTime() + (i - 1) * MILLIS_IN_DAY); |
| stmt.setDate(6, date); |
| stmt.setString(7, "a.varchar" + String.valueOf(i)); |
| stmt.setString(8, "a.char" + String.valueOf(i)); |
| stmt.setInt(9, i); |
| stmt.setLong(10, i); |
| stmt.setBigDecimal(11, new BigDecimal(i*0.5d)); |
| stmt.setDate(12, date); |
| stmt.setString(13, "b.varchar" + String.valueOf(i)); |
| stmt.setString(14, "b.char" + String.valueOf(i)); |
| stmt.setInt(15, i); |
| stmt.setLong(16, i); |
| stmt.setBigDecimal(17, new BigDecimal(i*0.5d)); |
| stmt.setDate(18, date); |
| stmt.executeUpdate(); |
| } |
| |
| private void verifyResult(ResultSet rs, int i) throws SQLException { |
| assertTrue(rs.next()); |
| assertEquals("VARCHAR" + String.valueOf(i) + "_" + StringUtils.rightPad("CHAR" + String.valueOf(i), 10, ' ') |
| + "_A.VARCHAR" + String.valueOf(i) + "_" + StringUtils.rightPad("B.CHAR" + String.valueOf(i), 10, ' '), |
| rs.getString(1)); |
| assertEquals(i * 3, rs.getInt(2)); |
| Date date = new Date(DateUtil.parseDate("2015-01-01 00:00:00").getTime() + (i) * MILLIS_IN_DAY); |
| assertEquals(date, rs.getDate(3)); |
| assertEquals(date, rs.getDate(4)); |
| assertEquals(date, rs.getDate(5)); |
| assertEquals("varchar" + String.valueOf(i), rs.getString(6)); |
| assertEquals("char" + String.valueOf(i), rs.getString(7)); |
| assertEquals(i, rs.getInt(8)); |
| assertEquals(i, rs.getLong(9)); |
| assertEquals(i*0.5d, rs.getDouble(10), 0.000001); |
| assertEquals(i, rs.getLong(11)); |
| assertEquals(i, rs.getLong(12)); |
| } |
| |
| private void createDataTable(Connection conn, String dataTableName, String tableProps) throws SQLException { |
| String tableDDL = "create table " + dataTableName + TestUtil.TEST_TABLE_SCHEMA + tableProps; |
| conn.createStatement().execute(tableDDL); |
| } |
| |
| protected void helpTestCreateAndUpdate(boolean mutable, boolean localIndex) throws Exception { |
| String dataTableName = generateUniqueName(); |
| String fullDataTableName = INDEX_DATA_SCHEMA + QueryConstants.NAME_SEPARATOR + dataTableName; |
| String indexName = generateUniqueName(); |
| Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); |
| Connection conn = DriverManager.getConnection(getUrl(), props); |
| try { |
| conn.setAutoCommit(false); |
| createDataTable(conn, fullDataTableName, mutable ? "" : "IMMUTABLE_ROWS=true"); |
| populateDataTable(conn, fullDataTableName); |
| |
| // create an expression index |
| String ddl = "CREATE " |
| + (localIndex ? "LOCAL" : "") |
| + " INDEX " + indexName + " ON " |
| + fullDataTableName |
| + " ((UPPER(varchar_pk) || '_' || UPPER(char_pk) || '_' || UPPER(varchar_col1) || '_' || UPPER(b.char_col2))," |
| + " (decimal_pk+int_pk+decimal_col2+int_col1)," + " date_pk+1, date1+1, date2+1 )" |
| + " INCLUDE (long_col1, long_col2)"; |
| conn.createStatement().execute(ddl); |
| |
| // run select query with expression in WHERE clause |
| String whereSql = "SELECT long_col1, long_col2 from " |
| + fullDataTableName |
| + " WHERE UPPER(varchar_pk) || '_' || UPPER(char_pk) || '_' || UPPER(varchar_col1) || '_' || UPPER(b.char_col2) = ?" |
| + " AND decimal_pk+int_pk+decimal_col2+int_col1=?" |
| // since a.date1 and b.date2 are NULLABLE and date is fixed width, these expressions are stored as |
| // DECIMAL in the index (which is not fixed width) |
| + " AND date_pk+1=? AND date1+1=? AND date2+1=?"; |
| PreparedStatement stmt = conn.prepareStatement(whereSql); |
| stmt.setString(1, "VARCHAR1_CHAR1 _A.VARCHAR1_B.CHAR1 "); |
| stmt.setInt(2, 3); |
| Date date = DateUtil.parseDate("2015-01-02 00:00:00"); |
| stmt.setDate(3, date); |
| stmt.setDate(4, date); |
| stmt.setDate(5, date); |
| |
| // verify that the query does a range scan on the index table |
| ResultSet rs = stmt.executeQuery("EXPLAIN " + whereSql); |
| assertEquals( |
| localIndex ? "CLIENT PARALLEL 1-WAY RANGE SCAN OVER INDEX_TEST." |
| + dataTableName |
| + " [1,'VARCHAR1_CHAR1 _A.VARCHAR1_B.CHAR1 ',3,'2015-01-02 00:00:00.000',1,420,156,800,000,1,420,156,800,000]\nCLIENT MERGE SORT" |
| : "CLIENT PARALLEL 1-WAY RANGE SCAN OVER INDEX_TEST." + indexName + " ['VARCHAR1_CHAR1 _A.VARCHAR1_B.CHAR1 ',3,'2015-01-02 00:00:00.000',1,420,156,800,000,1,420,156,800,000]", |
| QueryUtil.getExplainPlan(rs)); |
| |
| // verify that the correct results are returned |
| rs = stmt.executeQuery(); |
| assertTrue(rs.next()); |
| assertEquals(1, rs.getInt(1)); |
| assertEquals(1, rs.getInt(2)); |
| assertFalse(rs.next()); |
| |
| // verify all rows in data table are present in index table |
| String indexSelectSql = "SELECT UPPER(varchar_pk) || '_' || UPPER(char_pk) || '_' || UPPER(varchar_col1) || '_' || UPPER(b.char_col2), " |
| + "decimal_pk+int_pk+decimal_col2+int_col1, " |
| + "date_pk+1, date1+1, date2+1, " |
| + "varchar_pk, char_pk, int_pk, long_pk, decimal_pk, " |
| + "long_col1, long_col2 " |
| + "from " |
| + fullDataTableName; |
| rs = conn.createStatement().executeQuery("EXPLAIN " + indexSelectSql); |
| assertEquals(localIndex ? "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + fullDataTableName |
| + " [1]\nCLIENT MERGE SORT" : "CLIENT PARALLEL 1-WAY FULL SCAN OVER INDEX_TEST." + indexName, |
| QueryUtil.getExplainPlan(rs)); |
| rs = conn.createStatement().executeQuery(indexSelectSql); |
| verifyResult(rs, 1); |
| verifyResult(rs, 2); |
| |
| // Insert two more rows to the index data table |
| String upsert = "UPSERT INTO " + fullDataTableName |
| + " VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; |
| stmt = conn.prepareStatement(upsert); |
| insertRow(stmt, 3); |
| insertRow(stmt, 4); |
| conn.commit(); |
| |
| rs = conn.createStatement().executeQuery(indexSelectSql); |
| verifyResult(rs, 1); |
| verifyResult(rs, 2); |
| // verify that two rows added after index was created were also added to |
| // the index table |
| verifyResult(rs, 3); |
| verifyResult(rs, 4); |
| |
| conn.createStatement().execute("DROP INDEX " + indexName + " ON " + fullDataTableName); |
| } finally { |
| conn.close(); |
| } |
| } |
| |
| @Test |
| public void testMutableIndexUpdate() throws Exception { |
| String dataTableName = generateUniqueName(); |
| String fullDataTableName = INDEX_DATA_SCHEMA + QueryConstants.NAME_SEPARATOR + dataTableName; |
| String indexName = generateUniqueName(); |
| helpTestUpdate(fullDataTableName, indexName, false); |
| } |
| |
| @Test |
| public void testMutableLocalIndexUpdate() throws Exception { |
| String dataTableName = generateUniqueName(); |
| String fullDataTableName = INDEX_DATA_SCHEMA + QueryConstants.NAME_SEPARATOR + dataTableName; |
| String indexName = generateUniqueName(); |
| helpTestUpdate(fullDataTableName, indexName, true); |
| } |
| |
| protected void helpTestUpdate(String fullDataTableName, String indexName, boolean localIndex) throws Exception { |
| Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); |
| Connection conn = DriverManager.getConnection(getUrl(), props); |
| try { |
| conn.setAutoCommit(false); |
| createDataTable(conn, fullDataTableName, ""); |
| populateDataTable(conn, fullDataTableName); |
| |
| // create an expression index |
| String ddl = "CREATE " |
| + (localIndex ? "LOCAL" : "") |
| + " INDEX " + indexName + " ON " |
| + fullDataTableName |
| + " ((UPPER(varchar_pk) || '_' || UPPER(char_pk) || '_' || UPPER(varchar_col1) || '_' || UPPER(char_col2))," |
| + " (decimal_pk+int_pk+decimal_col2+int_col1)," + " date_pk+1, date1+1, date2+1 )" |
| + " INCLUDE (long_col1, long_col2)"; |
| PreparedStatement stmt = conn.prepareStatement(ddl); |
| stmt.execute(); |
| |
| // update index pk column and covered column |
| String upsert = "UPSERT INTO " |
| + fullDataTableName |
| + "(varchar_pk, char_pk, int_pk, long_pk, decimal_pk, date_pk, varchar_col1, long_col1) VALUES(?, ?, ?, ?, ?, ?, ?, ?)"; |
| |
| stmt = conn.prepareStatement(upsert); |
| stmt.setString(1, "varchar1"); |
| stmt.setString(2, "char1"); |
| stmt.setInt(3, 1); |
| stmt.setLong(4, 1l); |
| stmt.setBigDecimal(5, new BigDecimal(0.5)); |
| stmt.setDate(6, DateUtil.parseDate("2015-01-01 00:00:00")); |
| stmt.setString(7, "a.varchar_updated"); |
| stmt.setLong(8, 101); |
| stmt.executeUpdate(); |
| conn.commit(); |
| |
| // verify only one row was updated in the data table |
| String selectSql = "UPPER(varchar_pk) || '_' || UPPER(char_pk) || '_' || UPPER(varchar_col1) || '_' || UPPER(char_col2), long_col1 from " |
| + fullDataTableName; |
| ResultSet rs = conn.createStatement().executeQuery("SELECT /*+ NO_INDEX */ " + selectSql); |
| assertTrue(rs.next()); |
| assertEquals("VARCHAR1_CHAR1 _A.VARCHAR_UPDATED_B.CHAR1 ", rs.getString(1)); |
| assertEquals(101, rs.getLong(2)); |
| assertTrue(rs.next()); |
| assertEquals("VARCHAR2_CHAR2 _A.VARCHAR2_B.CHAR2 ", rs.getString(1)); |
| assertEquals(2, rs.getLong(2)); |
| assertFalse(rs.next()); |
| |
| // verify that the rows in the index table are also updated |
| rs = conn.createStatement().executeQuery("SELECT " + selectSql); |
| assertTrue(rs.next()); |
| assertEquals("VARCHAR1_CHAR1 _A.VARCHAR_UPDATED_B.CHAR1 ", rs.getString(1)); |
| assertEquals(101, rs.getLong(2)); |
| assertTrue(rs.next()); |
| assertEquals("VARCHAR2_CHAR2 _A.VARCHAR2_B.CHAR2 ", rs.getString(1)); |
| assertEquals(2, rs.getLong(2)); |
| assertFalse(rs.next()); |
| conn.createStatement().execute("DROP INDEX " + indexName + " ON " + fullDataTableName); |
| } finally { |
| conn.close(); |
| } |
| } |
| |
| private void populateDataTable(Connection conn, String dataTable) throws SQLException { |
| String upsert = "UPSERT INTO " + dataTable |
| + " VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; |
| PreparedStatement stmt1 = conn.prepareStatement(upsert); |
| // insert two rows |
| insertRow(stmt1, 1); |
| insertRow(stmt1, 2); |
| conn.commit(); |
| } |
| |
| @Test |
| public void testDeleteIndexedExpressionImmutableIndex() throws Exception { |
| helpTestDeleteIndexedExpression(false, false); |
| } |
| |
| @Test |
| public void testDeleteIndexedExpressionImmutableLocalIndex() throws Exception { |
| helpTestDeleteIndexedExpression(false, true); |
| } |
| |
| @Test |
| public void testDeleteIndexedExpressionMutableIndex() throws Exception { |
| helpTestDeleteIndexedExpression(true, false); |
| } |
| |
| @Test |
| public void testDeleteIndexedExpressionMutableLocalIndex() throws Exception { |
| helpTestDeleteIndexedExpression(true, true); |
| } |
| |
| protected void helpTestDeleteIndexedExpression(boolean mutable, boolean localIndex) throws Exception { |
| String dataTableName = generateUniqueName(); |
| String fullDataTableName = INDEX_DATA_SCHEMA + QueryConstants.NAME_SEPARATOR + dataTableName; |
| String indexName = generateUniqueName(); |
| String fullIndexTableName = INDEX_DATA_SCHEMA + QueryConstants.NAME_SEPARATOR + indexName; |
| Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); |
| Connection conn = DriverManager.getConnection(getUrl(), props); |
| try { |
| conn.setAutoCommit(false); |
| createDataTable(conn, fullDataTableName, mutable ? "" : "IMMUTABLE_ROWS=true"); |
| populateDataTable(conn, fullDataTableName); |
| String ddl = "CREATE " + (localIndex ? "LOCAL" : "") + " INDEX " + indexName + " ON " + fullDataTableName |
| + " (2*long_col2)"; |
| conn.createStatement().execute(ddl); |
| |
| ResultSet rs; |
| rs = conn.createStatement().executeQuery("SELECT COUNT(*) FROM " + fullDataTableName); |
| assertTrue(rs.next()); |
| assertEquals(2, rs.getInt(1)); |
| rs = conn.createStatement().executeQuery("SELECT COUNT(*) FROM " + fullIndexTableName); |
| assertTrue(rs.next()); |
| assertEquals(2, rs.getInt(1)); |
| |
| conn.setAutoCommit(true); |
| String dml = "DELETE from " + fullDataTableName + " WHERE long_col2 = 2"; |
| try { |
| conn.createStatement().execute(dml); |
| if (!mutable) { |
| fail(); |
| } |
| } catch (SQLException e) { |
| if (!mutable) { |
| assertEquals(SQLExceptionCode.INVALID_FILTER_ON_IMMUTABLE_ROWS.getErrorCode(), e.getErrorCode()); |
| } |
| } |
| |
| if (!mutable) { |
| dml = "DELETE from " + fullDataTableName + " WHERE 2*long_col2 = 4"; |
| conn.createStatement().execute(dml); |
| } |
| |
| rs = conn.createStatement().executeQuery("SELECT COUNT(*) FROM " + fullDataTableName); |
| assertTrue(rs.next()); |
| assertEquals(1, rs.getInt(1)); |
| rs = conn.createStatement().executeQuery("SELECT COUNT(*) FROM " + fullIndexTableName); |
| assertTrue(rs.next()); |
| assertEquals(1, rs.getInt(1)); |
| conn.createStatement().execute("DROP INDEX " + indexName + " ON " + fullDataTableName); |
| } finally { |
| conn.close(); |
| } |
| } |
| |
| @Test |
| public void testDeleteCoveredColImmutableIndex() throws Exception { |
| helpTestDeleteCoveredCol(false, false); |
| } |
| |
| @Test |
| public void testDeleteCoveredColImmutableLocalIndex() throws Exception { |
| helpTestDeleteCoveredCol(false, true); |
| } |
| |
| @Test |
| public void testDeleteCoveredColMutableIndex() throws Exception { |
| helpTestDeleteCoveredCol(true, false); |
| } |
| |
| @Test |
| public void testDeleteCoveredColMutableLocalIndex() throws Exception { |
| helpTestDeleteCoveredCol(true, true); |
| } |
| |
| protected void helpTestDeleteCoveredCol(boolean mutable, boolean localIndex) throws Exception { |
| String dataTableName = generateUniqueName(); |
| String fullDataTableName = INDEX_DATA_SCHEMA + QueryConstants.NAME_SEPARATOR + dataTableName; |
| String indexName = generateUniqueName(); |
| String fullIndexTableName = INDEX_DATA_SCHEMA + QueryConstants.NAME_SEPARATOR + indexName; |
| Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); |
| Connection conn = DriverManager.getConnection(getUrl(), props); |
| try { |
| conn.setAutoCommit(false); |
| createDataTable(conn, fullDataTableName, mutable ? "" : "IMMUTABLE_ROWS=true"); |
| populateDataTable(conn, fullDataTableName); |
| String ddl = "CREATE " + (localIndex ? "LOCAL" : "") + " INDEX " + indexName + " ON " + fullDataTableName |
| + " (long_pk, varchar_pk, 1+long_pk, UPPER(varchar_pk) )" + " INCLUDE (long_col1, long_col2)"; |
| conn.createStatement().execute(ddl); |
| |
| ResultSet rs; |
| rs = conn.createStatement().executeQuery("SELECT COUNT(*) FROM " + fullDataTableName); |
| assertTrue(rs.next()); |
| assertEquals(2, rs.getInt(1)); |
| rs = conn.createStatement().executeQuery("SELECT COUNT(*) FROM " + fullIndexTableName); |
| assertTrue(rs.next()); |
| assertEquals(2, rs.getInt(1)); |
| |
| String sql = "SELECT LONG_COL1 from " + fullDataTableName + " WHERE LONG_COL2 = 2"; |
| rs = conn.createStatement().executeQuery(sql); |
| assertTrue(rs.next()); |
| assertFalse(rs.next()); |
| |
| String dml = "DELETE from " + fullDataTableName + " WHERE long_col2 = 2"; |
| assertEquals(1, conn.createStatement().executeUpdate(dml)); |
| conn.commit(); |
| |
| String query = "SELECT /*+ NO_INDEX */ long_pk, varchar_pk, 1+long_pk, UPPER(varchar_pk) FROM " |
| + fullDataTableName; |
| rs = conn.createStatement().executeQuery(query); |
| assertTrue(rs.next()); |
| assertEquals(1L, rs.getLong(1)); |
| assertEquals("varchar1", rs.getString(2)); |
| assertEquals(2L, rs.getLong(3)); |
| assertEquals("VARCHAR1", rs.getString(4)); |
| assertFalse(rs.next()); |
| |
| query = "SELECT long_pk, varchar_pk, 1+long_pk, UPPER(varchar_pk) FROM " + fullDataTableName; |
| rs = conn.createStatement().executeQuery(query); |
| assertTrue(rs.next()); |
| assertEquals(1L, rs.getLong(1)); |
| assertEquals("varchar1", rs.getString(2)); |
| assertEquals(2L, rs.getLong(3)); |
| assertEquals("VARCHAR1", rs.getString(4)); |
| assertFalse(rs.next()); |
| |
| query = "SELECT * FROM " + fullIndexTableName; |
| rs = conn.createStatement().executeQuery(query); |
| assertTrue(rs.next()); |
| |
| assertEquals(1L, rs.getLong(1)); |
| assertEquals("varchar1", rs.getString(2)); |
| assertEquals(2L, rs.getLong(3)); |
| assertEquals("VARCHAR1", rs.getString(4)); |
| assertFalse(rs.next()); |
| conn.createStatement().execute("DROP INDEX " + indexName + " ON " + fullDataTableName); |
| } finally { |
| conn.close(); |
| } |
| } |
| |
| @Test |
| public void testGroupByCountImmutableIndex() throws Exception { |
| helpTestGroupByCount(false, false); |
| } |
| |
| @Test |
| public void testGroupByCountImmutableLocalIndex() throws Exception { |
| helpTestGroupByCount(false, true); |
| } |
| |
| @Test |
| public void testGroupByCountMutableIndex() throws Exception { |
| helpTestGroupByCount(true, false); |
| } |
| |
| @Test |
| public void testGroupByCountMutableLocalIndex() throws Exception { |
| helpTestGroupByCount(true, true); |
| } |
| |
| protected void helpTestGroupByCount(boolean mutable, boolean localIndex) throws Exception { |
| String dataTableName = generateUniqueName(); |
| String fullDataTableName = INDEX_DATA_SCHEMA + QueryConstants.NAME_SEPARATOR + dataTableName; |
| String indexName = generateUniqueName(); |
| Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); |
| Connection conn = DriverManager.getConnection(getUrl(), props); |
| try { |
| conn.setAutoCommit(false); |
| createDataTable(conn, fullDataTableName, mutable ? "" : "IMMUTABLE_ROWS=true"); |
| populateDataTable(conn, fullDataTableName); |
| String ddl = "CREATE " + (localIndex ? "LOCAL" : "") + " INDEX " + indexName + " ON " + fullDataTableName |
| + " (int_col1+int_col2)"; |
| conn.createStatement().execute(ddl); |
| |
| String groupBySql = "SELECT (int_col1+int_col2), COUNT(*) FROM " + fullDataTableName |
| + " GROUP BY (int_col1+int_col2)"; |
| ResultSet rs = conn.createStatement().executeQuery("EXPLAIN " + groupBySql); |
| String expectedPlan = "CLIENT PARALLEL 1-WAY " |
| + (localIndex ? "RANGE SCAN OVER " + fullDataTableName + " [1]" |
| : "FULL SCAN OVER INDEX_TEST." + indexName) |
| + "\n SERVER FILTER BY FIRST KEY ONLY\n SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [TO_BIGINT(\"(A.INT_COL1 + B.INT_COL2)\")]" |
| + (localIndex ? "\nCLIENT MERGE SORT" : ""); |
| assertEquals(expectedPlan, QueryUtil.getExplainPlan(rs)); |
| rs = conn.createStatement().executeQuery(groupBySql); |
| assertTrue(rs.next()); |
| assertEquals(1, rs.getInt(2)); |
| assertTrue(rs.next()); |
| assertEquals(1, rs.getInt(2)); |
| assertFalse(rs.next()); |
| conn.createStatement().execute("DROP INDEX " + indexName + " ON " + fullDataTableName); |
| } finally { |
| conn.close(); |
| } |
| } |
| |
| @Test |
| public void testSelectDistinctImmutableIndex() throws Exception { |
| helpTestSelectDistinct(false, false); |
| } |
| |
| @Test |
| public void testSelectDistinctImmutableIndexLocal() throws Exception { |
| helpTestSelectDistinct(false, true); |
| } |
| |
| @Test |
| public void testSelectDistinctMutableIndex() throws Exception { |
| helpTestSelectDistinct(true, false); |
| } |
| |
| @Test |
| public void testSelectDistinctMutableLocalIndex() throws Exception { |
| helpTestSelectDistinct(true, true); |
| } |
| |
| protected void helpTestSelectDistinct(boolean mutable, boolean localIndex) throws Exception { |
| String dataTableName = generateUniqueName(); |
| String fullDataTableName = INDEX_DATA_SCHEMA + QueryConstants.NAME_SEPARATOR + dataTableName; |
| String indexName = generateUniqueName(); |
| Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); |
| Connection conn = DriverManager.getConnection(getUrl(), props); |
| try { |
| conn.setAutoCommit(false); |
| createDataTable(conn, fullDataTableName, mutable ? "" : "IMMUTABLE_ROWS=true"); |
| populateDataTable(conn, fullDataTableName); |
| String ddl = "CREATE " + (localIndex ? "LOCAL" : "") + " INDEX " + indexName + " ON " + fullDataTableName |
| + " (int_col1+1)"; |
| conn.createStatement().execute(ddl); |
| String sql = "SELECT distinct int_col1+1 FROM " + fullDataTableName + " where int_col1+1 > 0"; |
| ResultSet rs = conn.createStatement().executeQuery("EXPLAIN " + sql); |
| String expectedPlan = "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " |
| + (localIndex ? fullDataTableName + " [1,0] - [1,*]" |
| : "INDEX_TEST." + indexName + " [0] - [*]") |
| + "\n SERVER FILTER BY FIRST KEY ONLY\n SERVER DISTINCT PREFIX FILTER OVER [TO_BIGINT(\"(A.INT_COL1 + 1)\")]\n SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [TO_BIGINT(\"(A.INT_COL1 + 1)\")]" |
| + (localIndex ? "\nCLIENT MERGE SORT" : ""); |
| assertEquals(expectedPlan, QueryUtil.getExplainPlan(rs)); |
| rs = conn.createStatement().executeQuery(sql); |
| assertTrue(rs.next()); |
| assertEquals(2, rs.getInt(1)); |
| assertTrue(rs.next()); |
| assertEquals(3, rs.getInt(1)); |
| assertFalse(rs.next()); |
| conn.createStatement().execute("DROP INDEX " + indexName + " ON " + fullDataTableName); |
| } finally { |
| conn.close(); |
| } |
| } |
| |
| @Test |
| public void testInClauseWithImmutableIndex() throws Exception { |
| helpTestInClauseWithIndex(false, false); |
| } |
| |
| @Test |
| public void testInClauseWithImmutableLocalIndex() throws Exception { |
| helpTestInClauseWithIndex(false, true); |
| } |
| |
| @Test |
| public void testInClauseWithMutableIndex() throws Exception { |
| helpTestInClauseWithIndex(true, false); |
| } |
| |
| @Test |
| public void testInClauseWithMutableLocalIndex() throws Exception { |
| helpTestInClauseWithIndex(true, false); |
| } |
| |
| protected void helpTestInClauseWithIndex(boolean mutable, boolean localIndex) throws Exception { |
| String dataTableName = generateUniqueName(); |
| String fullDataTableName = INDEX_DATA_SCHEMA + QueryConstants.NAME_SEPARATOR + dataTableName; |
| String indexName = generateUniqueName(); |
| |
| Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); |
| Connection conn = DriverManager.getConnection(getUrl(), props); |
| try { |
| conn.setAutoCommit(false); |
| createDataTable(conn, fullDataTableName, mutable ? "" : "IMMUTABLE_ROWS=true"); |
| populateDataTable(conn, fullDataTableName); |
| String ddl = "CREATE " + (localIndex ? "LOCAL" : "") + " INDEX " + indexName + " ON " + fullDataTableName |
| + " (int_col1+1)"; |
| |
| conn.createStatement().execute(ddl); |
| String sql = "SELECT int_col1+1 FROM " + fullDataTableName + " where int_col1+1 IN (2)"; |
| ResultSet rs = conn.createStatement().executeQuery("EXPLAIN " + sql); |
| assertEquals("CLIENT PARALLEL 1-WAY RANGE SCAN OVER " |
| + (localIndex ? fullDataTableName + " [1,2]\n SERVER FILTER BY FIRST KEY ONLY\nCLIENT MERGE SORT" |
| : "INDEX_TEST." + indexName + " [2]\n SERVER FILTER BY FIRST KEY ONLY"), QueryUtil.getExplainPlan(rs)); |
| rs = conn.createStatement().executeQuery(sql); |
| assertTrue(rs.next()); |
| assertEquals(2, rs.getInt(1)); |
| assertFalse(rs.next()); |
| conn.createStatement().execute("DROP INDEX " + indexName + " ON " + fullDataTableName); |
| } finally { |
| conn.close(); |
| } |
| } |
| |
| @Test |
| public void testOrderByWithImmutableIndex() throws Exception { |
| helpTestSelectAliasAndOrderByWithIndex(false, false); |
| } |
| |
| @Test |
| public void testOrderByWithImmutableLocalIndex() throws Exception { |
| helpTestSelectAliasAndOrderByWithIndex(false, true); |
| } |
| |
| @Test |
| public void testOrderByWithMutableIndex() throws Exception { |
| helpTestSelectAliasAndOrderByWithIndex(true, false); |
| } |
| |
| @Test |
| public void testOrderByWithMutableLocalIndex() throws Exception { |
| helpTestSelectAliasAndOrderByWithIndex(true, false); |
| } |
| |
| protected void helpTestSelectAliasAndOrderByWithIndex(boolean mutable, boolean localIndex) throws Exception { |
| String dataTableName = generateUniqueName(); |
| String fullDataTableName = INDEX_DATA_SCHEMA + QueryConstants.NAME_SEPARATOR + dataTableName; |
| String indexName = generateUniqueName(); |
| |
| Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); |
| Connection conn = DriverManager.getConnection(getUrl(), props); |
| try { |
| conn.setAutoCommit(false); |
| createDataTable(conn, fullDataTableName, mutable ? "" : "IMMUTABLE_ROWS=true"); |
| populateDataTable(conn, fullDataTableName); |
| String ddl = "CREATE " + (localIndex ? "LOCAL" : "") + " INDEX " + indexName + " ON " + fullDataTableName |
| + " (int_col1+1)"; |
| |
| conn.createStatement().execute(ddl); |
| String sql = "SELECT int_col1+1 AS foo FROM " + fullDataTableName + " ORDER BY foo"; |
| ResultSet rs = conn.createStatement().executeQuery("EXPLAIN " + sql); |
| assertEquals("CLIENT PARALLEL 1-WAY " |
| + (localIndex ? "RANGE SCAN OVER " + fullDataTableName |
| + " [1]\n SERVER FILTER BY FIRST KEY ONLY\nCLIENT MERGE SORT" |
| : "FULL SCAN OVER INDEX_TEST." + indexName + "\n SERVER FILTER BY FIRST KEY ONLY"), |
| QueryUtil.getExplainPlan(rs)); |
| rs = conn.createStatement().executeQuery(sql); |
| assertTrue(rs.next()); |
| assertEquals(2, rs.getInt(1)); |
| assertTrue(rs.next()); |
| assertEquals(3, rs.getInt(1)); |
| assertFalse(rs.next()); |
| conn.createStatement().execute("DROP INDEX " + indexName + " ON " + fullDataTableName); |
| } finally { |
| conn.close(); |
| } |
| } |
| |
| @Test |
| public void testImmutableIndexWithCaseSensitiveCols() throws Exception { |
| helpTestIndexWithCaseSensitiveCols(false, false); |
| } |
| |
| @Test |
| public void testImmutableLocalIndexWithCaseSensitiveCols() throws Exception { |
| helpTestIndexWithCaseSensitiveCols(false, true); |
| } |
| |
| @Test |
| public void testMutableIndexWithCaseSensitiveCols() throws Exception { |
| helpTestIndexWithCaseSensitiveCols(true, false); |
| } |
| |
| @Test |
| public void testMutableLocalIndexWithCaseSensitiveCols() throws Exception { |
| helpTestIndexWithCaseSensitiveCols(true, true); |
| } |
| |
| protected void helpTestIndexWithCaseSensitiveCols(boolean mutable, boolean localIndex) throws Exception { |
| String dataTableName = generateUniqueName(); |
| String indexName = generateUniqueName(); |
| |
| Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); |
| Connection conn = DriverManager.getConnection(getUrl(), props); |
| try { |
| conn.createStatement().execute("CREATE TABLE " + dataTableName + " (k VARCHAR NOT NULL PRIMARY KEY, \"cf1\".\"V1\" VARCHAR, \"CF2\".\"v2\" VARCHAR) "+ (mutable ? "IMMUTABLE_ROWS=true" : "")); |
| String query = "SELECT * FROM " + dataTableName; |
| ResultSet rs = conn.createStatement().executeQuery(query); |
| assertFalse(rs.next()); |
| String ddl = "CREATE " + (localIndex ? "LOCAL" : "") + " INDEX " + indexName + " ON " + dataTableName + " (\"cf1\".\"V1\" || '_' || \"CF2\".\"v2\") INCLUDE (\"V1\",\"v2\")"; |
| conn.createStatement().execute(ddl); |
| query = "SELECT * FROM " + indexName; |
| rs = conn.createStatement().executeQuery(query); |
| assertFalse(rs.next()); |
| |
| PreparedStatement stmt = conn.prepareStatement("UPSERT INTO " + dataTableName + " VALUES(?,?,?)"); |
| stmt.setString(1,"a"); |
| stmt.setString(2, "x"); |
| stmt.setString(3, "1"); |
| stmt.execute(); |
| stmt.setString(1,"b"); |
| stmt.setString(2, "y"); |
| stmt.setString(3, "2"); |
| stmt.execute(); |
| conn.commit(); |
| |
| query = "SELECT (\"V1\" || '_' || \"v2\"), k, \"V1\", \"v2\" FROM " + dataTableName + " WHERE (\"V1\" || '_' || \"v2\") = 'x_1'"; |
| rs = conn.createStatement().executeQuery("EXPLAIN " + query); |
| if(localIndex){ |
| assertEquals("CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + dataTableName + " [1,'x_1']\n" |
| + "CLIENT MERGE SORT", QueryUtil.getExplainPlan(rs)); |
| } else { |
| assertEquals("CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + indexName + " ['x_1']", QueryUtil.getExplainPlan(rs)); |
| } |
| |
| rs = conn.createStatement().executeQuery(query); |
| assertTrue(rs.next()); |
| assertEquals("x_1",rs.getString(1)); |
| assertEquals("a",rs.getString(2)); |
| assertEquals("x",rs.getString(3)); |
| assertEquals("1",rs.getString(4)); |
| //TODO figure out why this " " is needed |
| assertEquals("x_1",rs.getString("\"('cf1'.'V1' || '_' || 'CF2'.'v2')\"")); |
| assertEquals("a",rs.getString("k")); |
| assertEquals("x",rs.getString("V1")); |
| assertEquals("1",rs.getString("v2")); |
| assertFalse(rs.next()); |
| |
| query = "SELECT \"V1\", \"V1\" as foo1, (\"V1\" || '_' || \"v2\") as foo, (\"V1\" || '_' || \"v2\") as \"Foo1\", (\"V1\" || '_' || \"v2\") FROM " + dataTableName + " ORDER BY foo"; |
| rs = conn.createStatement().executeQuery("EXPLAIN " + query); |
| if(localIndex){ |
| assertEquals("CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + dataTableName + " [1]\nCLIENT MERGE SORT", |
| QueryUtil.getExplainPlan(rs)); |
| } else { |
| assertEquals("CLIENT PARALLEL 1-WAY FULL SCAN OVER " + indexName, QueryUtil.getExplainPlan(rs)); |
| } |
| |
| rs = conn.createStatement().executeQuery(query); |
| assertTrue(rs.next()); |
| assertEquals("x",rs.getString(1)); |
| assertEquals("x",rs.getString("V1")); |
| assertEquals("x",rs.getString(2)); |
| assertEquals("x",rs.getString("foo1")); |
| assertEquals("x_1",rs.getString(3)); |
| assertEquals("x_1",rs.getString("Foo")); |
| assertEquals("x_1",rs.getString(4)); |
| assertEquals("x_1",rs.getString("Foo1")); |
| assertEquals("x_1",rs.getString(5)); |
| assertEquals("x_1",rs.getString("\"('cf1'.'V1' || '_' || 'CF2'.'v2')\"")); |
| assertTrue(rs.next()); |
| assertEquals("y",rs.getString(1)); |
| assertEquals("y",rs.getString("V1")); |
| assertEquals("y",rs.getString(2)); |
| assertEquals("y",rs.getString("foo1")); |
| assertEquals("y_2",rs.getString(3)); |
| assertEquals("y_2",rs.getString("Foo")); |
| assertEquals("y_2",rs.getString(4)); |
| assertEquals("y_2",rs.getString("Foo1")); |
| assertEquals("y_2",rs.getString(5)); |
| assertEquals("y_2",rs.getString("\"('cf1'.'V1' || '_' || 'CF2'.'v2')\"")); |
| assertFalse(rs.next()); |
| conn.createStatement().execute("DROP INDEX " + indexName + " ON " + dataTableName); |
| } finally { |
| conn.close(); |
| } |
| } |
| |
| @Test |
| public void testSelectColOnlyInDataTableImmutableIndex() throws Exception { |
| helpTestSelectColOnlyInDataTable(false, false); |
| } |
| |
| @Test |
| public void testSelectColOnlyInDataTableImmutableLocalIndex() throws Exception { |
| helpTestSelectColOnlyInDataTable(false, true); |
| } |
| |
| @Test |
| public void testSelectColOnlyInDataTableMutableIndex() throws Exception { |
| helpTestSelectColOnlyInDataTable(true, false); |
| } |
| |
| @Test |
| public void testSelectColOnlyInDataTableMutableLocalIndex() throws Exception { |
| helpTestSelectColOnlyInDataTable(true, true); |
| } |
| |
| protected void helpTestSelectColOnlyInDataTable(boolean mutable, boolean localIndex) throws Exception { |
| String dataTableName = generateUniqueName(); |
| String fullDataTableName = INDEX_DATA_SCHEMA + QueryConstants.NAME_SEPARATOR + dataTableName; |
| String indexName = generateUniqueName(); |
| |
| Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); |
| Connection conn = DriverManager.getConnection(getUrl(), props); |
| try { |
| conn.setAutoCommit(false); |
| createDataTable(conn, fullDataTableName, mutable ? "" : "IMMUTABLE_ROWS=true"); |
| populateDataTable(conn, fullDataTableName); |
| String ddl = "CREATE " + (localIndex ? "LOCAL" : "") + " INDEX " + indexName + " ON " + fullDataTableName |
| + " (int_col1+1)"; |
| |
| conn = DriverManager.getConnection(getUrl(), props); |
| conn.setAutoCommit(false); |
| conn.createStatement().execute(ddl); |
| String sql = "SELECT int_col1+1, int_col2 FROM " + fullDataTableName + " WHERE int_col1+1=2"; |
| ResultSet rs = conn.createStatement().executeQuery("EXPLAIN " + sql); |
| assertEquals("CLIENT PARALLEL 1-WAY " |
| + (localIndex ? "RANGE SCAN OVER " + fullDataTableName |
| + " [1,2]\n SERVER FILTER BY FIRST KEY ONLY\nCLIENT MERGE SORT" : "FULL SCAN OVER " |
| + fullDataTableName + "\n SERVER FILTER BY (A.INT_COL1 + 1) = 2"), |
| QueryUtil.getExplainPlan(rs)); |
| rs = conn.createStatement().executeQuery(sql); |
| assertTrue(rs.next()); |
| assertEquals(2, rs.getInt(1)); |
| assertEquals(1, rs.getInt(2)); |
| assertFalse(rs.next()); |
| conn.createStatement().execute("DROP INDEX " + indexName + " ON " + fullDataTableName); |
| } finally { |
| conn.close(); |
| } |
| } |
| |
| @Test |
| public void testImmutableIndexDropIndexedColumn() throws Exception { |
| helpTestDropIndexedColumn(false, false); |
| } |
| |
| @Test |
| public void testImmutableLocalIndexDropIndexedColumn() throws Exception { |
| helpTestDropIndexedColumn(false, true); |
| } |
| |
| @Test |
| public void testMutableIndexDropIndexedColumn() throws Exception { |
| helpTestDropIndexedColumn(true, false); |
| } |
| |
| @Test |
| public void testMutableLocalIndexDropIndexedColumn() throws Exception { |
| helpTestDropIndexedColumn(true, true); |
| } |
| |
| public void helpTestDropIndexedColumn(boolean mutable, boolean local) throws Exception { |
| String query; |
| ResultSet rs; |
| PreparedStatement stmt; |
| |
| Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); |
| Connection conn = DriverManager.getConnection(getUrl(), props); |
| |
| String dataTableName = generateUniqueName(); |
| String fullDataTableName = INDEX_DATA_SCHEMA + QueryConstants.NAME_SEPARATOR + dataTableName; |
| String indexName = generateUniqueName(); |
| |
| try { |
| conn.setAutoCommit(false); |
| |
| // make sure that the tables are empty, but reachable |
| conn.createStatement().execute( |
| "CREATE TABLE " + dataTableName |
| + " (k VARCHAR NOT NULL PRIMARY KEY, v1 VARCHAR, v2 VARCHAR)" |
| + (!mutable ? " IMMUTABLE_ROWS=true" : "")); |
| query = "SELECT * FROM " + dataTableName ; |
| rs = conn.createStatement().executeQuery(query); |
| assertFalse(rs.next()); |
| conn.createStatement().execute("CREATE " + ( local ? "LOCAL" : "") + " INDEX " + indexName + " ON " + dataTableName + " (v1 || '_' || v2)"); |
| |
| query = "SELECT * FROM " + dataTableName; |
| rs = conn.createStatement().executeQuery(query); |
| assertFalse(rs.next()); |
| |
| // load some data into the table |
| stmt = conn.prepareStatement("UPSERT INTO " + dataTableName + " VALUES(?,?,?)"); |
| stmt.setString(1, "a"); |
| stmt.setString(2, "x"); |
| stmt.setString(3, "1"); |
| stmt.execute(); |
| conn.commit(); |
| |
| assertIndexExists(conn, dataTableName, true); |
| conn.createStatement().execute("ALTER TABLE " + dataTableName + " DROP COLUMN v1"); |
| assertIndexExists(conn, dataTableName, false); |
| |
| query = "SELECT * FROM " + dataTableName; |
| rs = conn.createStatement().executeQuery(query); |
| assertTrue(rs.next()); |
| assertEquals("a",rs.getString(1)); |
| assertEquals("1",rs.getString(2)); |
| assertFalse(rs.next()); |
| |
| // load some data into the table |
| stmt = conn.prepareStatement("UPSERT INTO " + dataTableName + " VALUES(?,?)"); |
| stmt.setString(1, "a"); |
| stmt.setString(2, "2"); |
| stmt.execute(); |
| conn.commit(); |
| |
| query = "SELECT * FROM " + dataTableName; |
| rs = conn.createStatement().executeQuery(query); |
| assertTrue(rs.next()); |
| assertEquals("a",rs.getString(1)); |
| assertEquals("2",rs.getString(2)); |
| assertFalse(rs.next()); |
| } |
| finally { |
| conn.close(); |
| } |
| } |
| |
| private static void assertIndexExists(Connection conn, String tableName, boolean exists) throws SQLException { |
| ResultSet rs = conn.getMetaData().getIndexInfo(null, null, tableName, false, false); |
| assertEquals(exists, rs.next()); |
| } |
| |
| @Test |
| public void testImmutableIndexDropCoveredColumn() throws Exception { |
| helpTestDropCoveredColumn(false, false); |
| } |
| |
| @Test |
| public void testImmutableLocalIndexDropCoveredColumn() throws Exception { |
| helpTestDropCoveredColumn(false, true); |
| } |
| |
| @Test |
| public void testMutableIndexDropCoveredColumn() throws Exception { |
| helpTestDropCoveredColumn(true, false); |
| } |
| |
| @Test |
| public void testMutableLocalIndexDropCoveredColumn() throws Exception { |
| helpTestDropCoveredColumn(true, true); |
| } |
| |
| public void helpTestDropCoveredColumn(boolean mutable, boolean local) throws Exception { |
| ResultSet rs; |
| PreparedStatement stmt; |
| String dataTableName = generateUniqueName(); |
| String fullDataTableName = INDEX_DATA_SCHEMA + QueryConstants.NAME_SEPARATOR + dataTableName; |
| String indexName = generateUniqueName(); |
| |
| Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); |
| Connection conn = DriverManager.getConnection(getUrl(), props); |
| try { |
| conn.setAutoCommit(false); |
| |
| // make sure that the tables are empty, but reachable |
| conn.createStatement().execute( |
| "CREATE TABLE " + dataTableName |
| + " (k VARCHAR NOT NULL PRIMARY KEY, v1 VARCHAR, v2 VARCHAR, v3 VARCHAR)"); |
| String dataTableQuery = "SELECT * FROM " + dataTableName; |
| rs = conn.createStatement().executeQuery(dataTableQuery); |
| assertFalse(rs.next()); |
| |
| conn.createStatement().execute("CREATE " + ( local ? "LOCAL" : "") + " INDEX " + indexName + " ON " + dataTableName + " (k || '_' || v1) include (v2, v3)"); |
| String indexTableQuery = "SELECT * FROM " + indexName; |
| rs = conn.createStatement().executeQuery(indexTableQuery); |
| assertFalse(rs.next()); |
| |
| // load some data into the table |
| stmt = conn.prepareStatement("UPSERT INTO " + dataTableName + " VALUES(?,?,?,?)"); |
| stmt.setString(1, "a"); |
| stmt.setString(2, "x"); |
| stmt.setString(3, "1"); |
| stmt.setString(4, "j"); |
| stmt.execute(); |
| conn.commit(); |
| |
| assertIndexExists(conn, dataTableName, true); |
| conn.createStatement().execute("ALTER TABLE " + dataTableName + " DROP COLUMN v2"); |
| assertIndexExists(conn, dataTableName, true); |
| |
| // verify data table rows |
| rs = conn.createStatement().executeQuery(dataTableQuery); |
| assertTrue(rs.next()); |
| assertEquals("a",rs.getString(1)); |
| assertEquals("x",rs.getString(2)); |
| assertEquals("j",rs.getString(3)); |
| assertFalse(rs.next()); |
| |
| // verify index table rows |
| rs = conn.createStatement().executeQuery(indexTableQuery); |
| assertTrue(rs.next()); |
| assertEquals("a_x",rs.getString(1)); |
| assertEquals("a",rs.getString(2)); |
| assertEquals("j",rs.getString(3)); |
| assertFalse(rs.next()); |
| |
| // add another row |
| stmt = conn.prepareStatement("UPSERT INTO " + dataTableName + " VALUES(?,?,?)"); |
| stmt.setString(1, "b"); |
| stmt.setString(2, "y"); |
| stmt.setString(3, "k"); |
| stmt.execute(); |
| conn.commit(); |
| |
| // verify data table rows |
| rs = conn.createStatement().executeQuery(dataTableQuery); |
| assertTrue(rs.next()); |
| assertEquals("a",rs.getString(1)); |
| assertEquals("x",rs.getString(2)); |
| assertEquals("j",rs.getString(3)); |
| assertTrue(rs.next()); |
| assertEquals("b",rs.getString(1)); |
| assertEquals("y",rs.getString(2)); |
| assertEquals("k",rs.getString(3)); |
| assertFalse(rs.next()); |
| |
| // verify index table rows |
| rs = conn.createStatement().executeQuery(indexTableQuery); |
| assertTrue(rs.next()); |
| assertEquals("a_x",rs.getString(1)); |
| assertEquals("a",rs.getString(2)); |
| assertEquals("j",rs.getString(3)); |
| assertTrue(rs.next()); |
| assertEquals("b_y",rs.getString(1)); |
| assertEquals("b",rs.getString(2)); |
| assertEquals("k",rs.getString(3)); |
| assertFalse(rs.next()); |
| } |
| finally { |
| conn.close(); |
| } |
| } |
| |
| @Test |
| public void testImmutableIndexAddPKColumnToTable() throws Exception { |
| helpTestAddPKColumnToTable(false, false); |
| } |
| |
| @Test |
| public void testImmutableLocalIndexAddPKColumnToTable() throws Exception { |
| helpTestAddPKColumnToTable(false, true); |
| } |
| |
| @Test |
| public void testMutableIndexAddPKColumnToTable() throws Exception { |
| helpTestAddPKColumnToTable(true, false); |
| } |
| |
| @Test |
| public void testMutableLocalIndexAddPKColumnToTable() throws Exception { |
| helpTestAddPKColumnToTable(true, true); |
| } |
| |
| public void helpTestAddPKColumnToTable(boolean mutable, boolean local) throws Exception { |
| ResultSet rs; |
| PreparedStatement stmt; |
| |
| String dataTableName = generateUniqueName(); |
| String fullDataTableName = INDEX_DATA_SCHEMA + QueryConstants.NAME_SEPARATOR + dataTableName; |
| String indexName = generateUniqueName(); |
| |
| Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); |
| Connection conn = DriverManager.getConnection(getUrl(), props); |
| try { |
| conn.setAutoCommit(false); |
| |
| // make sure that the tables are empty, but reachable |
| conn.createStatement().execute( |
| "CREATE TABLE " + dataTableName |
| + " (k VARCHAR NOT NULL PRIMARY KEY, v1 VARCHAR, v2 VARCHAR)"); |
| String dataTableQuery = "SELECT * FROM " + dataTableName; |
| rs = conn.createStatement().executeQuery(dataTableQuery); |
| assertFalse(rs.next()); |
| |
| conn.createStatement().execute("CREATE " + ( local ? "LOCAL" : "") + " INDEX " + indexName + " ON " + dataTableName + " (v1 || '_' || v2)"); |
| String indexTableQuery = "SELECT * FROM " + indexName; |
| rs = conn.createStatement().executeQuery(indexTableQuery); |
| assertFalse(rs.next()); |
| |
| // load some data into the table |
| stmt = conn.prepareStatement("UPSERT INTO " + dataTableName + " VALUES(?,?,?)"); |
| stmt.setString(1, "a"); |
| stmt.setString(2, "x"); |
| stmt.setString(3, "1"); |
| stmt.execute(); |
| conn.commit(); |
| |
| assertIndexExists(conn, dataTableName, true); |
| conn.createStatement().execute("ALTER TABLE " + dataTableName + " ADD v3 VARCHAR, k2 DECIMAL PRIMARY KEY"); |
| rs = conn.getMetaData().getPrimaryKeys("", "", dataTableName); |
| assertTrue(rs.next()); |
| assertEquals("K",rs.getString("COLUMN_NAME")); |
| assertEquals(1, rs.getShort("KEY_SEQ")); |
| assertTrue(rs.next()); |
| assertEquals("K2",rs.getString("COLUMN_NAME")); |
| assertEquals(2, rs.getShort("KEY_SEQ")); |
| |
| rs = conn.getMetaData().getPrimaryKeys("", "", indexName); |
| assertTrue(rs.next()); |
| assertEquals(IndexUtil.INDEX_COLUMN_NAME_SEP + "(V1 || '_' || V2)",rs.getString("COLUMN_NAME")); |
| int offset = local ? 1 : 0; |
| assertEquals(offset+1, rs.getShort("KEY_SEQ")); |
| assertTrue(rs.next()); |
| assertEquals(IndexUtil.INDEX_COLUMN_NAME_SEP + "K",rs.getString("COLUMN_NAME")); |
| assertEquals(offset+2, rs.getShort("KEY_SEQ")); |
| assertTrue(rs.next()); |
| assertEquals(IndexUtil.INDEX_COLUMN_NAME_SEP + "K2",rs.getString("COLUMN_NAME")); |
| assertEquals(offset+3, rs.getShort("KEY_SEQ")); |
| |
| // verify data table rows |
| rs = conn.createStatement().executeQuery(dataTableQuery); |
| assertTrue(rs.next()); |
| assertEquals("a",rs.getString(1)); |
| assertEquals("x",rs.getString(2)); |
| assertEquals("1",rs.getString(3)); |
| assertNull(rs.getBigDecimal(4)); |
| assertFalse(rs.next()); |
| |
| // verify index table rows |
| rs = conn.createStatement().executeQuery(indexTableQuery); |
| assertTrue(rs.next()); |
| assertEquals("x_1",rs.getString(1)); |
| assertEquals("a",rs.getString(2)); |
| assertNull(rs.getBigDecimal(3)); |
| assertFalse(rs.next()); |
| |
| // load some data into the table |
| stmt = conn.prepareStatement("UPSERT INTO " + dataTableName + "(K,K2,V1,V2) VALUES(?,?,?,?)"); |
| stmt.setString(1, "b"); |
| stmt.setBigDecimal(2, BigDecimal.valueOf(2)); |
| stmt.setString(3, "y"); |
| stmt.setString(4, "2"); |
| stmt.execute(); |
| conn.commit(); |
| |
| // verify data table rows |
| rs = conn.createStatement().executeQuery(dataTableQuery); |
| assertTrue(rs.next()); |
| assertEquals("a",rs.getString(1)); |
| assertEquals("x",rs.getString(2)); |
| assertEquals("1",rs.getString(3)); |
| assertNull(rs.getString(4)); |
| assertNull(rs.getBigDecimal(5)); |
| assertTrue(rs.next()); |
| assertEquals("b",rs.getString(1)); |
| assertEquals("y",rs.getString(2)); |
| assertEquals("2",rs.getString(3)); |
| assertNull(rs.getString(4)); |
| assertEquals(BigDecimal.valueOf(2),rs.getBigDecimal(5)); |
| assertFalse(rs.next()); |
| |
| // verify index table rows |
| rs = conn.createStatement().executeQuery(indexTableQuery); |
| assertTrue(rs.next()); |
| assertEquals("x_1",rs.getString(1)); |
| assertEquals("a",rs.getString(2)); |
| assertNull(rs.getBigDecimal(3)); |
| assertTrue(rs.next()); |
| assertEquals("y_2",rs.getString(1)); |
| assertEquals("b",rs.getString(2)); |
| assertEquals(BigDecimal.valueOf(2),rs.getBigDecimal(3)); |
| assertFalse(rs.next()); |
| } |
| finally { |
| conn.close(); |
| } |
| } |
| |
| @Test |
| public void testUpdatableViewWithIndex() throws Exception { |
| helpTestUpdatableViewIndex(false); |
| } |
| |
| @Test |
| public void testUpdatableViewWithLocalIndex() throws Exception { |
| helpTestUpdatableViewIndex(true); |
| } |
| |
| private void helpTestUpdatableViewIndex(boolean local) throws Exception { |
| Connection conn = DriverManager.getConnection(getUrl()); |
| String dataTableName = generateUniqueName(); |
| String fullDataTableName = INDEX_DATA_SCHEMA + QueryConstants.NAME_SEPARATOR + dataTableName; |
| String indexName1 = generateUniqueName(); |
| String viewName = generateUniqueName(); |
| String indexName2 = generateUniqueName(); |
| try { |
| String ddl = "CREATE TABLE " + dataTableName + " (k1 INTEGER NOT NULL, k2 INTEGER NOT NULL, k3 DECIMAL, s1 VARCHAR, s2 VARCHAR CONSTRAINT pk PRIMARY KEY (k1, k2, k3))"; |
| conn.createStatement().execute(ddl); |
| ddl = "CREATE VIEW " + viewName + " AS SELECT * FROM " + dataTableName + " WHERE k1 = 1"; |
| conn.createStatement().execute(ddl); |
| conn.createStatement().execute("UPSERT INTO " + viewName + "(k2,s1,s2,k3) VALUES(120,'foo0','bar0',50.0)"); |
| conn.createStatement().execute("UPSERT INTO " + viewName + "(k2,s1,s2,k3) VALUES(121,'foo1','bar1',51.0)"); |
| conn.commit(); |
| |
| ResultSet rs; |
| conn.createStatement().execute("CREATE " + (local ? "LOCAL" : "") + " INDEX " + indexName1 + " on " + viewName + "(k1+k2+k3) include (s1, s2)"); |
| conn.createStatement().execute("UPSERT INTO " + viewName + "(k2,s1,s2,k3) VALUES(120,'foo2','bar2',50.0)"); |
| conn.commit(); |
| |
| String query = "SELECT k1, k2, k3, s1, s2 FROM " + viewName + " WHERE k1+k2+k3 = 173.0"; |
| rs = conn.createStatement().executeQuery("EXPLAIN " + query); |
| String queryPlan = QueryUtil.getExplainPlan(rs); |
| if (local) { |
| assertEquals("CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + dataTableName + " [1,173]\n" + "CLIENT MERGE SORT", |
| queryPlan); |
| } else { |
| assertEquals("CLIENT PARALLEL 1-WAY RANGE SCAN OVER _IDX_" + dataTableName + " [" + Short.MIN_VALUE + ",173]", queryPlan); |
| } |
| rs = conn.createStatement().executeQuery(query); |
| assertTrue(rs.next()); |
| assertEquals(1, rs.getInt(1)); |
| assertEquals(121, rs.getInt(2)); |
| assertTrue(BigDecimal.valueOf(51.0).compareTo(rs.getBigDecimal(3))==0); |
| assertEquals("foo1", rs.getString(4)); |
| assertEquals("bar1", rs.getString(5)); |
| assertFalse(rs.next()); |
| |
| conn.createStatement().execute("CREATE " + (local ? "LOCAL" : "") + " INDEX " + indexName2 + " on " + viewName + "(s1||'_'||s2)"); |
| |
| query = "SELECT k1, k2, s1||'_'||s2 FROM " + viewName + " WHERE (s1||'_'||s2)='foo2_bar2'"; |
| rs = conn.createStatement().executeQuery("EXPLAIN " + query); |
| if (local) { |
| assertEquals("CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + dataTableName + " [" + (2) |
| + ",'foo2_bar2']\n" + " SERVER FILTER BY FIRST KEY ONLY\n" + "CLIENT MERGE SORT", |
| QueryUtil.getExplainPlan(rs)); |
| } else { |
| assertEquals("CLIENT PARALLEL 1-WAY RANGE SCAN OVER _IDX_" + dataTableName + " [" + (Short.MIN_VALUE + 1) + ",'foo2_bar2']\n" |
| + " SERVER FILTER BY FIRST KEY ONLY", QueryUtil.getExplainPlan(rs)); |
| } |
| rs = conn.createStatement().executeQuery(query); |
| assertTrue(rs.next()); |
| assertEquals(1, rs.getInt(1)); |
| assertEquals(120, rs.getInt(2)); |
| assertEquals("foo2_bar2", rs.getString(3)); |
| assertFalse(rs.next()); |
| } |
| finally { |
| conn.close(); |
| } |
| } |
| |
| @Test |
| public void testViewUsesMutableTableIndex() throws Exception { |
| helpTestViewUsesTableIndex(false); |
| } |
| |
| @Test |
| public void testViewUsesImmutableTableIndex() throws Exception { |
| helpTestViewUsesTableIndex(true); |
| } |
| |
| private void helpTestViewUsesTableIndex(boolean immutable) throws Exception { |
| Connection conn = DriverManager.getConnection(getUrl()); |
| try |
| { |
| String dataTableName = generateUniqueName(); |
| String indexName1 = generateUniqueName(); |
| String viewName = generateUniqueName(); |
| String indexName2 = generateUniqueName(); |
| ResultSet rs; |
| String ddl = "CREATE TABLE " + dataTableName + " (k1 INTEGER NOT NULL, k2 INTEGER NOT NULL, s1 VARCHAR, s2 VARCHAR, s3 VARCHAR, s4 VARCHAR CONSTRAINT pk PRIMARY KEY (k1, k2)) " + (immutable ? "IMMUTABLE_ROWS = true" : ""); |
| conn.createStatement().execute(ddl); |
| conn.createStatement().execute("CREATE INDEX " + indexName1 + " ON " + dataTableName + "(k2, s2, s3, s1)"); |
| conn.createStatement().execute("CREATE INDEX " + indexName2 + " ON " + dataTableName + "(k2, s2||'_'||s3, s1, s4)"); |
| |
| ddl = "CREATE VIEW " + viewName + " AS SELECT * FROM " + dataTableName + " WHERE s1 = 'foo'"; |
| conn.createStatement().execute(ddl); |
| conn.createStatement().execute("UPSERT INTO " + dataTableName + " VALUES(1,1,'foo','abc','cab')"); |
| conn.createStatement().execute("UPSERT INTO " + dataTableName + " VALUES(2,2,'bar','xyz','zyx')"); |
| conn.commit(); |
| |
| rs = conn.createStatement().executeQuery("SELECT count(*) FROM " + viewName); |
| assertTrue(rs.next()); |
| assertEquals(1, rs.getLong(1)); |
| assertFalse(rs.next()); |
| |
| //i2 should be used since it contains s3||'_'||s4 i |
| String query = "SELECT s2||'_'||s3 FROM " + viewName + " WHERE k2=1 AND (s2||'_'||s3)='abc_cab'"; |
| rs = conn.createStatement( ).executeQuery("EXPLAIN " + query); |
| String queryPlan = QueryUtil.getExplainPlan(rs); |
| assertEquals( |
| "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + indexName2 + " [1,'abc_cab','foo']\n" + |
| " SERVER FILTER BY FIRST KEY ONLY", queryPlan); |
| rs = conn.createStatement().executeQuery(query); |
| assertTrue(rs.next()); |
| assertEquals("abc_cab", rs.getString(1)); |
| assertFalse(rs.next()); |
| |
| conn.createStatement().execute("ALTER VIEW " + viewName + " DROP COLUMN s4"); |
| //i2 cannot be used since s4 has been dropped from the view, so i1 will be used |
| rs = conn.createStatement().executeQuery("EXPLAIN " + query); |
| queryPlan = QueryUtil.getExplainPlan(rs); |
| assertEquals( |
| "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + indexName1 + " [1]\n" + |
| " SERVER FILTER BY FIRST KEY ONLY AND ((\"S2\" || '_' || \"S3\") = 'abc_cab' AND \"S1\" = 'foo')", queryPlan); |
| rs = conn.createStatement().executeQuery(query); |
| assertTrue(rs.next()); |
| assertEquals("abc_cab", rs.getString(1)); |
| assertFalse(rs.next()); |
| } |
| finally { |
| conn.close(); |
| } |
| } |
| |
| @Test |
| public void testExpressionThrowsException() throws Exception { |
| Connection conn = DriverManager.getConnection(getUrl()); |
| String dataTableName = generateUniqueName(); |
| String indexName = generateUniqueName(); |
| try { |
| String ddl = "CREATE TABLE " + dataTableName + " (k1 INTEGER PRIMARY KEY, k2 INTEGER)"; |
| conn.createStatement().execute(ddl); |
| ddl = "CREATE INDEX " + indexName + " on " + dataTableName + "(k1/k2)"; |
| conn.createStatement().execute(ddl); |
| // upsert should succeed |
| conn.createStatement().execute("UPSERT INTO " + dataTableName + " VALUES(1,1)"); |
| conn.commit(); |
| // divide by zero should fail |
| conn.createStatement().execute("UPSERT INTO " + dataTableName + " VALUES(1,0)"); |
| conn.commit(); |
| fail(); |
| } catch (CommitException e) { |
| } finally { |
| conn.close(); |
| } |
| } |
| |
| @Test |
| public void testImmutableCaseSensitiveFunctionIndex() throws Exception { |
| helpTestCaseSensitiveFunctionIndex(false, false); |
| } |
| |
| @Test |
| public void testImmutableLocalCaseSensitiveFunctionIndex() throws Exception { |
| helpTestCaseSensitiveFunctionIndex(false, true); |
| } |
| |
| @Test |
| public void testMutableCaseSensitiveFunctionIndex() throws Exception { |
| helpTestCaseSensitiveFunctionIndex(true, false); |
| } |
| |
| @Test |
| public void testMutableLocalCaseSensitiveFunctionIndex() throws Exception { |
| helpTestCaseSensitiveFunctionIndex(true, true); |
| } |
| |
| protected void helpTestCaseSensitiveFunctionIndex(boolean mutable, |
| boolean localIndex) throws Exception { |
| Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); |
| Connection conn = DriverManager.getConnection(getUrl(), props); |
| String dataTableName = generateUniqueName(); |
| String indexName = generateUniqueName(); |
| try { |
| conn.createStatement().execute( |
| "CREATE TABLE " + dataTableName + " (k VARCHAR NOT NULL PRIMARY KEY, v VARCHAR) " |
| + (!mutable ? "IMMUTABLE_ROWS=true" : "")); |
| String query = "SELECT * FROM " + dataTableName; |
| ResultSet rs = conn.createStatement().executeQuery(query); |
| assertFalse(rs.next()); |
| String ddl = "CREATE " + (localIndex ? "LOCAL" : "") |
| + " INDEX " + indexName + " ON " + dataTableName + " (REGEXP_SUBSTR(v,'id:\\\\w+'))"; |
| conn.createStatement().execute(ddl); |
| query = "SELECT * FROM " + indexName; |
| rs = conn.createStatement().executeQuery(query); |
| assertFalse(rs.next()); |
| |
| PreparedStatement stmt = conn.prepareStatement("UPSERT INTO " + dataTableName + " VALUES(?,?)"); |
| stmt.setString(1, "k1"); |
| stmt.setString(2, "{id:id1}"); |
| stmt.execute(); |
| stmt.setString(1, "k2"); |
| stmt.setString(2, "{id:id2}"); |
| stmt.execute(); |
| conn.commit(); |
| |
| query = "SELECT k FROM " + dataTableName + " WHERE REGEXP_SUBSTR(v,'id:\\\\w+') = 'id:id1'"; |
| rs = conn.createStatement().executeQuery("EXPLAIN " + query); |
| if (localIndex) { |
| assertEquals( |
| "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + dataTableName + " [1,'id:id1']\n" |
| + " SERVER FILTER BY FIRST KEY ONLY\nCLIENT MERGE SORT", |
| QueryUtil.getExplainPlan(rs)); |
| } else { |
| assertEquals( |
| "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + indexName + " ['id:id1']\n" |
| + " SERVER FILTER BY FIRST KEY ONLY", |
| QueryUtil.getExplainPlan(rs)); |
| } |
| |
| rs = conn.createStatement().executeQuery(query); |
| assertTrue(rs.next()); |
| assertEquals("k1", rs.getString(1)); |
| assertFalse(rs.next()); |
| } finally { |
| conn.close(); |
| } |
| } |
| |
| @Test |
| public void testImmutableTableOnlyHasPrimaryKeyIndex() throws Exception { |
| helpTestTableOnlyHasPrimaryKeyIndex(false, false); |
| } |
| |
| @Test |
| public void testImmutableLocalTableOnlyHasPrimaryKeyIndex() throws Exception { |
| helpTestTableOnlyHasPrimaryKeyIndex(false, true); |
| } |
| |
| @Test |
| public void testMutableTableOnlyHasPrimaryKeyIndex() throws Exception { |
| helpTestTableOnlyHasPrimaryKeyIndex(true, false); |
| } |
| |
| @Test |
| public void testMutableLocalTableOnlyHasPrimaryKeyIndex() throws Exception { |
| helpTestTableOnlyHasPrimaryKeyIndex(true, true); |
| } |
| |
| private void helpTestTableOnlyHasPrimaryKeyIndex(boolean mutable, |
| boolean localIndex) throws Exception { |
| Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); |
| Connection conn = DriverManager.getConnection(getUrl(), props); |
| String dataTableName = generateUniqueName(); |
| String indexName = generateUniqueName(); |
| try { |
| conn.createStatement().execute( |
| "CREATE TABLE " + dataTableName + " (" |
| + "pk1 VARCHAR not null, " |
| + "pk2 VARCHAR not null, " |
| + "CONSTRAINT PK PRIMARY KEY (pk1, pk2))" |
| + (!mutable ? "IMMUTABLE_ROWS=true" : "")); |
| String query = "SELECT * FROM " + dataTableName; |
| ResultSet rs = conn.createStatement().executeQuery(query); |
| assertFalse(rs.next()); |
| conn.createStatement().execute( |
| "CREATE " + (localIndex ? "LOCAL" : "") |
| + " INDEX " + indexName + " ON " + dataTableName + " (pk2, pk1)"); |
| query = "SELECT * FROM " + indexName; |
| rs = conn.createStatement().executeQuery(query); |
| assertFalse(rs.next()); |
| |
| PreparedStatement stmt = conn.prepareStatement("UPSERT INTO " + dataTableName + " VALUES(?,?)"); |
| stmt.setString(1, "k11"); |
| stmt.setString(2, "k21"); |
| stmt.execute(); |
| conn.commit(); |
| |
| query = "SELECT * FROM " + indexName; |
| rs = conn.createStatement().executeQuery(query); |
| assertTrue(rs.next()); |
| assertEquals("k21", rs.getString(1)); |
| assertEquals("k11", rs.getString(2)); |
| assertFalse(rs.next()); |
| |
| query = "SELECT * FROM " + dataTableName + " WHERE pk2='k21'"; |
| rs = conn.createStatement().executeQuery(query); |
| assertTrue(rs.next()); |
| assertEquals("k11", rs.getString(1)); |
| assertEquals("k21", rs.getString(2)); |
| assertFalse(rs.next()); |
| } finally { |
| conn.close(); |
| } |
| } |
| |
| @Test |
| public void testImmutableTableGlobalIndexExpressionWithJoin() throws Exception { |
| helpTestIndexExpressionWithJoin(false, false); |
| } |
| |
| @Test |
| public void testImmutableTableLocalIndexExpressionWithJoin() throws Exception { |
| helpTestIndexExpressionWithJoin(false, true); |
| } |
| |
| @Test |
| public void testMutableTableGlobalIndexExpressionWithJoin() throws Exception { |
| helpTestIndexExpressionWithJoin(true, false); |
| } |
| |
| @Test |
| public void testMutableTableLocalIndexExpressionWithJoin() throws Exception { |
| helpTestIndexExpressionWithJoin(true, true); |
| } |
| |
| public void helpTestIndexExpressionWithJoin(boolean mutable, |
| boolean localIndex) throws Exception { |
| Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); |
| Connection conn = DriverManager.getConnection(getUrl(), props); |
| String nameSuffix = "T" + (mutable ? "MUTABLE" : "_IMMUTABLE") + (localIndex ? "_LOCAL" : "_GLOBAL"); |
| String tableName = "T" + nameSuffix; |
| String indexName = "IDX" + nameSuffix; |
| try { |
| conn.createStatement().execute( |
| "CREATE TABLE " |
| + tableName |
| + "( c_customer_sk varchar primary key, c_first_name varchar, c_last_name varchar )" |
| + (!mutable ? "IMMUTABLE_ROWS=true" : "")); |
| String query = "SELECT * FROM " + tableName; |
| ResultSet rs = conn.createStatement().executeQuery(query); |
| assertFalse(rs.next()); |
| |
| conn.createStatement().execute( |
| "CREATE " + (localIndex ? "LOCAL" : "") |
| + " INDEX " + indexName + " ON " + tableName + " (c_customer_sk || c_first_name asc)"); |
| query = "SELECT * FROM " + indexName; |
| rs = conn.createStatement().executeQuery(query); |
| assertFalse(rs.next()); |
| |
| PreparedStatement stmt = conn.prepareStatement("UPSERT INTO " + tableName + " VALUES(?,?,?)"); |
| stmt.setString(1, "1"); |
| stmt.setString(2, "David"); |
| stmt.setString(3, "Smith"); |
| stmt.execute(); |
| conn.commit(); |
| |
| query = "select c.c_customer_sk from " + tableName + " c " |
| + "left outer join " + tableName + " c2 on c.c_customer_sk = c2.c_customer_sk " |
| + "where c.c_customer_sk || c.c_first_name = '1David'"; |
| rs = conn.createStatement().executeQuery("EXPLAIN "+query); |
| String explainPlan = QueryUtil.getExplainPlan(rs); |
| if (localIndex) { |
| assertEquals("CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + tableName + " [1,'1David']\n" + |
| " SERVER FILTER BY FIRST KEY ONLY\n" + |
| "CLIENT MERGE SORT\n" + |
| " PARALLEL LEFT-JOIN TABLE 0 (SKIP MERGE)\n" + |
| " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + tableName + " [1]\n" + |
| " SERVER FILTER BY FIRST KEY ONLY\n" + |
| " CLIENT MERGE SORT", explainPlan); |
| } |
| else { |
| assertEquals("CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + indexName + " ['1David']\n" + |
| " SERVER FILTER BY FIRST KEY ONLY\n" + |
| " PARALLEL LEFT-JOIN TABLE 0 (SKIP MERGE)\n" + |
| " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + indexName + "\n" + |
| " SERVER FILTER BY FIRST KEY ONLY", explainPlan); |
| } |
| |
| rs = conn.createStatement().executeQuery(query); |
| assertTrue(rs.next()); |
| assertEquals("1", rs.getString(1)); |
| assertFalse(rs.next()); |
| } finally { |
| conn.close(); |
| } |
| } |
| |
| } |