PHOENIX-1367 VIEW derived from another VIEW doesn't use parent VIEW indexes
Project: http://git-wip-us.apache.org/repos/asf/phoenix/repo Commit: http://git-wip-us.apache.org/repos/asf/phoenix/commit/5a6496e1 Tree: http://git-wip-us.apache.org/repos/asf/phoenix/tree/5a6496e1 Diff: http://git-wip-us.apache.org/repos/asf/phoenix/diff/5a6496e1 Branch: refs/heads/calcite Commit: 5a6496e1dc51576f4882c3718495eb2e1d6bce0a Parents: 8e082fb Author: Cody Marcel <cmar...@salesforce.com> Authored: Mon Jul 18 17:58:32 2016 -0600 Committer: Thomas D'Silva <tdsi...@salesforce.com> Committed: Sun Sep 11 11:33:53 2016 -0700 ---------------------------------------------------------------------- .../index/ChildViewsUseParentViewIndexIT.java | 254 +++++++++++++++++++ .../coprocessor/MetaDataEndpointImpl.java | 14 +- .../apache/phoenix/index/IndexMaintainer.java | 16 +- .../apache/phoenix/query/QueryConstants.java | 1 + .../apache/phoenix/schema/MetaDataClient.java | 67 +++-- .../apache/phoenix/schema/PMetaDataImpl.java | 2 +- .../java/org/apache/phoenix/schema/PTable.java | 20 +- .../org/apache/phoenix/schema/PTableImpl.java | 41 ++- .../org/apache/phoenix/util/ExpressionUtil.java | 15 +- 9 files changed, 360 insertions(+), 70 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/phoenix/blob/5a6496e1/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/ChildViewsUseParentViewIndexIT.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/ChildViewsUseParentViewIndexIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/ChildViewsUseParentViewIndexIT.java new file mode 100644 index 0000000..60a428c --- /dev/null +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/ChildViewsUseParentViewIndexIT.java @@ -0,0 +1,254 @@ +package org.apache.phoenix.end2end.index; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +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.phoenix.end2end.BaseHBaseManagedTimeTableReuseIT; +import org.apache.phoenix.util.QueryUtil; +import org.junit.Test; + +public class ChildViewsUseParentViewIndexIT extends BaseHBaseManagedTimeTableReuseIT { + + @Test + public void testIndexOnParentViewWithTenantSpecificConnection() throws Exception { + final String baseTableName = generateRandomString(); + final String globalViewName = generateRandomString(); + final String globalViewIdxName = generateRandomString(); + final String tenantViewName1 = generateRandomString(); + final String tenantViewName2 = generateRandomString(); + + // Set up props with TenantId + Properties props = new Properties(); + props.setProperty("TenantId", "00Dxxxxxxxxxxx1"); + + try (Connection conn = DriverManager.getConnection(getUrl()); + Connection tenantConn = DriverManager.getConnection(getUrl(), props)) { + // Create Base table + createBaseTable(baseTableName, conn); + + // Create the Global View on the Base Table for a value of KP + createGlobalView(globalViewName, baseTableName, conn); + + // Create Global Index on View + createGlobalIndexOnView(globalViewName, globalViewIdxName, conn); + + // Create tenant specific view which is a child of global view + createTenantSpecificView(globalViewName, tenantViewName1, tenantConn); + + // Create tenant specific view which is a child of previous tenant view + createTenantSpecificView(tenantViewName1, tenantViewName2, tenantConn); + + int rowCount = 0; + insertRowIntoView(globalViewName, tenantConn, ++rowCount); + insertRowIntoView(globalViewName, tenantConn, ++rowCount); + insertRowIntoView(globalViewName, tenantConn, ++rowCount); + assertQueryIndex(globalViewName, baseTableName, tenantConn, 3); + + insertRowIntoView(tenantViewName1, tenantConn, ++rowCount); + insertRowIntoView(tenantViewName2, tenantConn, ++rowCount); + + // assert that we get 5 rows while querying via tenant specific views and that we use the index + assertQueryIndex(tenantViewName1, baseTableName, tenantConn, 5); + assertQueryIndex(tenantViewName2, baseTableName, tenantConn, 5); + + // assert that we get 5 rows while querying via global specific view and that we use the index + assertQueryIndex(globalViewName, baseTableName, tenantConn, 5); + } + } + + + @Test + public void testParentViewIndexWithSpecializedChildViews() throws Exception { + final String baseTableName = generateRandomString(); + final String parentViewName = generateRandomString(); + final String parentViewIdxName = generateRandomString(); + final String childViewName1 = generateRandomString(); + final String childViewName2 = generateRandomString(); + + try (Connection conn = DriverManager.getConnection(getUrl())) { + // create base table + String baseTableDdl = "CREATE TABLE " + baseTableName + " (" + + "A0 CHAR(1) NOT NULL PRIMARY KEY," + + "A1 CHAR(1)," + + "A2 CHAR(1)," + + "A3 CHAR(1)," + + "A4 CHAR(1))"; + conn.createStatement().execute(baseTableDdl); + + // create the parent view on the base table for a value of A + String globalViewDdl = "CREATE VIEW " + parentViewName + " AS SELECT * FROM " + baseTableName + " WHERE A1 = 'X'"; + conn.createStatement().execute(globalViewDdl); + + // create index on parent view + conn.createStatement().execute("CREATE INDEX " + parentViewIdxName + " ON " + parentViewName + "(A4, A2)"); + + // create child of parent view that should be able to use the parent's index + String childViewDdl = "CREATE VIEW " + childViewName1 + " AS SELECT * FROM " + parentViewName + " WHERE A2 = 'Y'"; + conn.createStatement().execute(childViewDdl); + + // create child of parent view that should *not* be able to use the parent's index + String grandChildViewDdl1 = "CREATE VIEW " + childViewName2 + " AS SELECT * FROM " + childViewName1 + " WHERE A3 = 'Z'"; + conn.createStatement().execute(grandChildViewDdl1); + + // upsert row using parent view + PreparedStatement stmt = conn.prepareStatement("UPSERT INTO " + parentViewName + " (A0, A2, A3, A4) VALUES(?,?,?,?)"); + stmt.setString(1, "1"); + stmt.setString(2, "Y"); + stmt.setString(3, "Z"); + stmt.setString(4, "1"); + stmt.execute(); + conn.commit(); + + // upsert row using first child view + stmt = conn.prepareStatement("UPSERT INTO " + childViewName1 + " (A0, A3, A4) VALUES(?,?,?)"); + stmt.setString(1, "2"); + stmt.setString(2, "Z"); + stmt.setString(3, "2"); + stmt.execute(); + conn.commit(); + + // upsert row using second child view + stmt = conn.prepareStatement("UPSERT INTO " + childViewName2 + " (A0, A4) VALUES(?,?)"); + stmt.setString(1, "3"); + stmt.setString(2, "3"); + stmt.execute(); + conn.commit(); + + // assert that we get 2 rows while querying via parent views and that we use the index + assertQueryUsesIndex(baseTableName, parentViewName, conn, false); + + // assert that we get 2 rows while querying via the first child view and that we use the index + assertQueryUsesIndex(baseTableName, childViewName1, conn, true); + + // assert that we get 3 rows while querying via the second child view and that we use the base table + assertQueryUsesBaseTable(baseTableName, childViewName2, conn); + } + } + + private void assertQueryUsesIndex(final String baseTableName, final String viewName, Connection conn, boolean isChildView) throws SQLException { + String sql = "SELECT A0, A1, A2, A4 FROM " + viewName +" WHERE A4 IN ('1', '2', '3') ORDER BY A4, A2"; + ResultSet rs = conn.prepareStatement("EXPLAIN " + sql).executeQuery(); + String childViewScanKey = isChildView ? ",'Y'" : ""; + assertEquals( + "CLIENT PARALLEL 1-WAY SKIP SCAN ON 3 KEYS OVER _IDX_" + baseTableName + " [-32768,'1'" + childViewScanKey + "] - [-32768,'3'" + childViewScanKey + "]\n" + + " SERVER FILTER BY FIRST KEY ONLY", + QueryUtil.getExplainPlan(rs)); + + rs = conn.createStatement().executeQuery(sql); + assertTrue(rs.next()); + assertEquals("1", rs.getString(1)); + assertEquals("X", rs.getString(2)); + assertEquals("Y", rs.getString(3)); + assertEquals("1", rs.getString(4)); + assertTrue(rs.next()); + assertEquals("2", rs.getString(1)); + assertEquals("X", rs.getString(2)); + assertEquals("Y", rs.getString(3)); + assertEquals("2", rs.getString(4)); + assertFalse(rs.next()); + } + + private void assertQueryUsesBaseTable(final String baseTableName, final String viewName, Connection conn) throws SQLException { + String sql = "SELECT A0, A1, A2, A4 FROM " + viewName +" WHERE A4 IN ('1', '2', '3') "; + ResultSet rs = conn.prepareStatement("EXPLAIN " + sql).executeQuery(); + assertEquals( + "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + baseTableName + "\n" + + " SERVER FILTER BY (A4 IN ('1','2','3') AND ((A1 = 'X' AND A2 = 'Y') AND A3 = 'Z'))", + QueryUtil.getExplainPlan(rs)); + + rs = conn.createStatement().executeQuery(sql); + assertTrue(rs.next()); + assertEquals("1", rs.getString(1)); + assertEquals("X", rs.getString(2)); + assertEquals("Y", rs.getString(3)); + assertEquals("1", rs.getString(4)); + assertTrue(rs.next()); + assertEquals("2", rs.getString(1)); + assertEquals("X", rs.getString(2)); + assertEquals("Y", rs.getString(3)); + assertEquals("2", rs.getString(4)); + assertTrue(rs.next()); + assertEquals("3", rs.getString(1)); + assertEquals("X", rs.getString(2)); + assertEquals("Y", rs.getString(3)); + assertEquals("3", rs.getString(4)); + assertFalse(rs.next()); + } + + private void createBaseTable(String baseTableName, Connection conn) throws SQLException { + String baseTableDdl = "CREATE TABLE " + baseTableName + " (" + + "OID CHAR(15) NOT NULL,\n" + + "KP CHAR(3) NOT NULL\n," + + "CREATED_DATE DATE\n," + + "CREATED_BY CHAR(3)\n," + + "CONSTRAINT PK PRIMARY KEY (oid, kp))\n"; + String ddlOptions = "MULTI_TENANT=true, IMMUTABLE_ROWS=TRUE, VERSIONS=1"; + conn.createStatement().execute(baseTableDdl + ddlOptions); + } + + private void createGlobalView(String globalViewName, String baseTableName, Connection conn) throws SQLException { + String globalViewDdl = "CREATE VIEW IF NOT EXISTS " + globalViewName + " (" + + "A_DATE DATE NOT NULL," + + "WO_ID CHAR(15) NOT NULL," + + "WA_ID CHAR(15) NOT NULL," + + "C_TYPE VARCHAR NOT NULL," + + "CA_TYPE VARCHAR NOT NULL," + + "V_ID CHAR(15)," + + "C_CTX VARCHAR," + + "CONSTRAINT PKVIEW PRIMARY KEY (A_DATE, WO_ID, WA_ID, C_TYPE, CA_TYPE))" + + "AS SELECT * FROM " + baseTableName + " WHERE KP = 'xyz'"; + conn.createStatement().execute(globalViewDdl); + } + + private void createGlobalIndexOnView(String globalViewName, String globalViewIdxName, Connection conn) throws SQLException { + String globalViewIdxDdl = "CREATE INDEX IF NOT EXISTS " + globalViewIdxName + + " ON " + globalViewName + "(WO_ID, A_DATE DESC)"; + conn.createStatement().execute(globalViewIdxDdl); + } + + private void createTenantSpecificView(String parentViewName, String tenantViewName, Connection conn) throws SQLException { + String tenantSpecificViewDdl = "CREATE VIEW IF NOT EXISTS " + + tenantViewName + " AS SELECT * FROM " + parentViewName; + conn.createStatement(). + execute(tenantSpecificViewDdl); + } + + private void insertRowIntoView(String viewName, Connection conn, int rowCount) throws SQLException { + PreparedStatement stmt = conn.prepareStatement( + "UPSERT INTO " + viewName + " (A_DATE, WO_ID, WA_ID, C_TYPE, CA_TYPE, V_ID) VALUES(?,?,?,?,?,?)"); + stmt.setDate(1, new Date(System.currentTimeMillis())); + stmt.setString(2, "003xxxxxxxxxxx" + rowCount); + stmt.setString(3, "701xxxxxxxxxxx" + rowCount); + stmt.setString(4, "ctype1"); + stmt.setString(5, "catype1"); + stmt.setString(6, "xyzxxxxxxxxxxx" + rowCount); + stmt.execute(); + conn.commit(); + } + + private void assertQueryIndex(String viewName, String baseTableName, Connection conn, int expectedRows) throws SQLException { + String sql = "SELECT WO_ID FROM " + viewName + + " WHERE WO_ID IN ('003xxxxxxxxxxx1', '003xxxxxxxxxxx2', '003xxxxxxxxxxx3', '003xxxxxxxxxxx4', '003xxxxxxxxxxx5') " + + " AND (A_DATE > TO_DATE('2016-01-01 06:00:00.0')) " + + " ORDER BY WO_ID, A_DATE DESC"; + ResultSet rs = conn.prepareStatement("EXPLAIN " + sql).executeQuery(); + assertEquals( + "CLIENT PARALLEL 1-WAY SKIP SCAN ON 5 RANGES OVER _IDX_" + baseTableName + " [-32768,'00Dxxxxxxxxxxx1','003xxxxxxxxxxx1',*] - [-32768,'00Dxxxxxxxxxxx1','003xxxxxxxxxxx5',~'2016-01-01 06:00:00.000']\n" + + " SERVER FILTER BY FIRST KEY ONLY", + QueryUtil.getExplainPlan(rs)); + + rs = conn.createStatement().executeQuery(sql); + for (int i=0; i<expectedRows; ++i) + assertTrue(rs.next()); + assertFalse(rs.next()); + } +} http://git-wip-us.apache.org/repos/asf/phoenix/blob/5a6496e1/phoenix-core/src/main/java/org/apache/phoenix/coprocessor/MetaDataEndpointImpl.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/main/java/org/apache/phoenix/coprocessor/MetaDataEndpointImpl.java b/phoenix-core/src/main/java/org/apache/phoenix/coprocessor/MetaDataEndpointImpl.java index 7d3468d..a5d77bc 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/coprocessor/MetaDataEndpointImpl.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/coprocessor/MetaDataEndpointImpl.java @@ -924,8 +924,10 @@ public class MetaDataEndpointImpl extends MetaDataProtocol implements Coprocesso List<PColumn> columns = Lists.newArrayListWithExpectedSize(columnCount); - List<PTable> indexes = new ArrayList<PTable>(); - List<PName> physicalTables = new ArrayList<PName>(); + List<PTable> indexes = Lists.newArrayList(); + List<PName> physicalTables = Lists.newArrayList(); + PName parentTableName = tableType == INDEX ? dataTableName : null; + PName parentSchemaName = tableType == INDEX ? schemaName : null; while (true) { results.clear(); scanner.next(results); @@ -943,6 +945,9 @@ public class MetaDataEndpointImpl extends MetaDataProtocol implements Coprocesso addIndexToTable(tenantId, schemaName, famName, tableName, clientTimeStamp, indexes); } else if (linkType == LinkType.PHYSICAL_TABLE) { physicalTables.add(famName); + } else if (linkType == LinkType.PARENT_TABLE) { + parentTableName = PNameFactory.newName(SchemaUtil.getTableNameFromFullName(famName.getBytes())); + parentSchemaName = PNameFactory.newName(SchemaUtil.getSchemaNameFromFullName(famName.getBytes())); } } else { addColumnToTable(results, colName, famName, colKeyValues, columns, saltBucketNum != null); @@ -951,8 +956,7 @@ public class MetaDataEndpointImpl extends MetaDataProtocol implements Coprocesso // Avoid querying the stats table because we're holding the rowLock here. Issuing an RPC to a remote // server while holding this lock is a bad idea and likely to cause contention. return PTableImpl.makePTable(tenantId, schemaName, tableName, tableType, indexState, timeStamp, tableSeqNum, - pkName, saltBucketNum, columns, tableType == INDEX ? schemaName : null, - tableType == INDEX ? dataTableName : null, indexes, isImmutableRows, physicalTables, defaultFamilyName, + pkName, saltBucketNum, columns, parentSchemaName, parentTableName, indexes, isImmutableRows, physicalTables, defaultFamilyName, viewStatement, disableWAL, multiTenant, storeNulls, viewType, viewIndexId, indexType, rowKeyOrderOptimizable, transactional, updateCacheFrequency, baseColumnCount, indexDisableTimestamp, isNamespaceMapped, autoPartitionSeq, isAppendOnlySchema); @@ -1718,7 +1722,7 @@ public class MetaDataEndpointImpl extends MetaDataProtocol implements Coprocesso return new MetaDataMutationResult(MutationCode.TABLE_NOT_FOUND, EnvironmentEdgeManager.currentTimeMillis(), null); } // Make sure we're not deleting the "wrong" child - if (!Arrays.equals(parentTableName, table.getParentTableName() == null ? null : table.getParentTableName().getBytes())) { + if (parentTableName!=null && table.getParentTableName() != null && !Arrays.equals(parentTableName, table.getParentTableName().getBytes())) { return new MetaDataMutationResult(MutationCode.TABLE_NOT_FOUND, EnvironmentEdgeManager.currentTimeMillis(), null); } // Since we don't allow back in time DDL, we know if we have a table it's the one http://git-wip-us.apache.org/repos/asf/phoenix/blob/5a6496e1/phoenix-core/src/main/java/org/apache/phoenix/index/IndexMaintainer.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/main/java/org/apache/phoenix/index/IndexMaintainer.java b/phoenix-core/src/main/java/org/apache/phoenix/index/IndexMaintainer.java index ee67ca2..6595562 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/index/IndexMaintainer.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/index/IndexMaintainer.java @@ -50,6 +50,7 @@ import org.apache.phoenix.compile.ColumnResolver; import org.apache.phoenix.compile.FromCompiler; import org.apache.phoenix.compile.IndexExpressionCompiler; import org.apache.phoenix.compile.StatementContext; +import org.apache.phoenix.expression.CoerceExpression; import org.apache.phoenix.expression.Expression; import org.apache.phoenix.expression.ExpressionType; import org.apache.phoenix.expression.KeyValueColumnExpression; @@ -85,10 +86,12 @@ import org.apache.phoenix.schema.tuple.ValueGetterTuple; import org.apache.phoenix.schema.types.PDataType; import org.apache.phoenix.util.BitSet; import org.apache.phoenix.util.ByteUtil; +import org.apache.phoenix.util.ExpressionUtil; import org.apache.phoenix.util.IndexUtil; import org.apache.phoenix.util.MetaDataUtil; import org.apache.phoenix.util.SchemaUtil; import org.apache.phoenix.util.TrustedByteArrayOutputStream; +import org.apache.tephra.TxConstants; import com.google.common.base.Predicate; import com.google.common.collect.Iterators; @@ -96,8 +99,6 @@ import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; -import org.apache.tephra.TxConstants; - /** * * Class that builds index row key from data row key and current state of @@ -416,7 +417,16 @@ public class IndexMaintainer implements Writable, Iterable<ColumnReference> { this.rowKeyMetaData.setIndexPkPosition(dataPkPos, indexPos); } else { indexColByteSize += column.getDataType().isFixedWidth() ? SchemaUtil.getFixedByteSize(column) : ValueSchema.ESTIMATED_VARIABLE_LENGTH_SIZE; - this.indexedExpressions.add(expression); + try { + // Surround constant with cast so that we can still know the original type. Otherwise, if we lose the type, + // (for example when VARCHAR becomes CHAR), it can lead to problems in the type translation we do between data tables and indexes. + if (column.isNullable() && ExpressionUtil.isConstant(expression)) { + expression = CoerceExpression.create(expression, indexColumn.getDataType()); + } + this.indexedExpressions.add(expression); + } catch (SQLException e) { + throw new RuntimeException(e); // Impossible + } } } else { http://git-wip-us.apache.org/repos/asf/phoenix/blob/5a6496e1/phoenix-core/src/main/java/org/apache/phoenix/query/QueryConstants.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/main/java/org/apache/phoenix/query/QueryConstants.java b/phoenix-core/src/main/java/org/apache/phoenix/query/QueryConstants.java index 7e5f7a2..155d1ba 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/query/QueryConstants.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/query/QueryConstants.java @@ -131,6 +131,7 @@ import org.apache.phoenix.util.ByteUtil; public interface QueryConstants { public static final String NAME_SEPARATOR = "."; public static final String NAMESPACE_SEPARATOR = ":"; + public static final String CHILD_VIEW_INDEX_NAME_SEPARATOR = "#"; public static final byte[] NAMESPACE_SEPARATOR_BYTES = Bytes.toBytes(NAMESPACE_SEPARATOR); public static final byte NAMESPACE_SEPARATOR_BYTE = NAMESPACE_SEPARATOR_BYTES[0]; public static final String NAME_SEPARATOR_REGEX = "\\" + NAME_SEPARATOR; http://git-wip-us.apache.org/repos/asf/phoenix/blob/5a6496e1/phoenix-core/src/main/java/org/apache/phoenix/schema/MetaDataClient.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/main/java/org/apache/phoenix/schema/MetaDataClient.java b/phoenix-core/src/main/java/org/apache/phoenix/schema/MetaDataClient.java index fd6b48c..7da7010 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/schema/MetaDataClient.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/schema/MetaDataClient.java @@ -541,7 +541,7 @@ public class MetaDataClient { // In this case, we update the parent table which may in turn pull // in indexes to add to this table. long resolvedTime = TransactionUtil.getResolvedTime(connection, result); - if (addIndexesFromPhysicalTable(result, resolvedTimestamp)) { + if (addIndexesFromParentTable(result, resolvedTimestamp)) { connection.addTable(result.getTable(), resolvedTime); } else { @@ -656,7 +656,7 @@ public class MetaDataClient { } /** - * Fault in the physical table to the cache and add any indexes it has to the indexes + * Fault in the parent table to the cache and add any indexes it has to the indexes * of the table for which we just updated. * TODO: combine this round trip with the one that updates the cache for the child table. * @param result the result from updating the cache for the current table. @@ -664,29 +664,30 @@ public class MetaDataClient { * @return true if the PTable contained by result was modified and false otherwise * @throws SQLException if the physical table cannot be found */ - private boolean addIndexesFromPhysicalTable(MetaDataMutationResult result, Long resolvedTimestamp) throws SQLException { + private boolean addIndexesFromParentTable(MetaDataMutationResult result, Long resolvedTimestamp) throws SQLException { PTable view = result.getTable(); // If not a view or if a view directly over an HBase table, there's nothing to do if (view.getType() != PTableType.VIEW || view.getViewType() == ViewType.MAPPED) { return false; } - String physicalName = view.getPhysicalName().getString(); - String schemaName = SchemaUtil.getSchemaNameFromFullName(physicalName); - String tableName = SchemaUtil.getTableNameFromFullName(physicalName); - MetaDataMutationResult parentResult = updateCache(null, schemaName, tableName, false, resolvedTimestamp); - PTable physicalTable = parentResult.getTable(); - if (physicalTable == null) { + // a view on a table will not have a parent name but will have a physical table name (which is the parent) + String parentName = view.getParentName().getString(); + String schemaName = SchemaUtil.getSchemaNameFromFullName(parentName); + String tableName = SchemaUtil.getTableNameFromFullName(parentName); + MetaDataMutationResult parentResult = updateCache(connection.getTenantId(), schemaName, tableName, false, resolvedTimestamp); + PTable parentTable = parentResult.getTable(); + if (parentTable == null) { throw new TableNotFoundException(schemaName, tableName); } if (!result.wasUpdated() && !parentResult.wasUpdated()) { return false; } - List<PTable> physicalTableIndexes = physicalTable.getIndexes(); - if (physicalTableIndexes.isEmpty()) { + List<PTable> parentTableIndexes = parentTable.getIndexes(); + if (parentTableIndexes.isEmpty()) { return false; } // Filter out indexes if column doesn't exist in view - List<PTable> indexesToAdd = Lists.newArrayListWithExpectedSize(physicalTableIndexes.size() + view.getIndexes().size()); + List<PTable> indexesToAdd = Lists.newArrayListWithExpectedSize(parentTableIndexes.size() + view.getIndexes().size()); if (result.wasUpdated()) { // Table from server never contains inherited indexes indexesToAdd.addAll(view.getIndexes()); } else { // Only add original ones, as inherited ones may have changed @@ -697,11 +698,11 @@ public class MetaDataClient { } } } - for (PTable index : physicalTableIndexes) { + for (PTable index : parentTableIndexes) { boolean containsAllReqdCols = true; // Ensure that all columns required to create index exist in the view too, // since view columns may be removed. - IndexMaintainer indexMaintainer = index.getIndexMaintainer(physicalTable, connection); + IndexMaintainer indexMaintainer = index.getIndexMaintainer(parentTable, connection); // Check that the columns required for the index pk are present in the view Set<ColumnReference> indexColRefs = indexMaintainer.getIndexedColumns(); for (ColumnReference colRef : indexColRefs) { @@ -720,7 +721,7 @@ public class MetaDataClient { } } // Ensure that constant columns (i.e. columns matched in the view WHERE clause) - // all exist in the index on the physical table. + // all exist in the index on the parent table. for (PColumn col : view.getColumns()) { if (col.getViewConstant() != null) { try { @@ -729,17 +730,36 @@ public class MetaDataClient { // would fail to compile. String indexColumnName = IndexUtil.getIndexColumnName(col); index.getColumn(indexColumnName); - } catch (ColumnNotFoundException e) { // Ignore this index and continue with others - containsAllReqdCols = false; - break; + } catch (ColumnNotFoundException e1) { + PColumn indexCol = null; + try { + String cf = col.getFamilyName()!=null ? col.getFamilyName().getString() : null; + String cq = col.getName().getString(); + if (cf!=null) { + indexCol = parentTable.getColumnFamily(cf).getColumn(cq); + } + else { + indexCol = parentTable.getColumn(cq); + } + } catch (ColumnNotFoundException e2) { // Ignore this index and continue with others + containsAllReqdCols = false; + break; + } + if (indexCol.getViewConstant()==null || Bytes.compareTo(indexCol.getViewConstant(), col.getViewConstant())!=0) { + containsAllReqdCols = false; + break; + } } } } if (containsAllReqdCols) { // Tack on view statement to index to get proper filtering for view - String viewStatement = IndexUtil.rewriteViewStatement(connection, index, physicalTable, view.getViewStatement()); - index = PTableImpl.makePTable(index, viewStatement); - indexesToAdd.add(index); + String viewStatement = IndexUtil.rewriteViewStatement(connection, index, parentTable, view.getViewStatement()); + PName modifiedIndexName = PNameFactory.newName(index.getSchemaName().getString() + QueryConstants.CHILD_VIEW_INDEX_NAME_SEPARATOR + + index.getName().getString() + QueryConstants.CHILD_VIEW_INDEX_NAME_SEPARATOR + view.getName().getString()); + // add the index table with a new name so that it does not conflict with the existing index table + // also set update cache frequency to never since the renamed index is not present on the server + indexesToAdd.add(PTableImpl.makePTable(index, modifiedIndexName, viewStatement, Long.MAX_VALUE, view.getTenantId())); } } PTable allIndexesTable = PTableImpl.makePTable(view, view.getTimeStamp(), indexesToAdd); @@ -2190,7 +2210,6 @@ public class MetaDataClient { // add the columns in reverse order since we reverse the list later Collections.reverse(columnMetadata); tableMetaData.addAll(columnMetadata); - String dataTableName = parent == null || tableType == PTableType.VIEW ? null : parent.getTableName().getString(); PIndexState indexState = parent == null || tableType == PTableType.VIEW ? null : PIndexState.BUILDING; PreparedStatement tableUpsert = connection.prepareStatement(CREATE_TABLE); @@ -2341,7 +2360,7 @@ public class MetaDataClient { PTable table = PTableImpl.makePTable( tenantId, newSchemaName, PNameFactory.newName(tableName), tableType, indexState, timestamp!=null ? timestamp : result.getMutationTime(), PTable.INITIAL_SEQ_NUM, pkName == null ? null : PNameFactory.newName(pkName), saltBucketNum, columns.values(), - dataTableName == null ? null : newSchemaName, dataTableName == null ? null : PNameFactory.newName(dataTableName), Collections.<PTable>emptyList(), isImmutableRows, + parent == null ? null : parent.getSchemaName(), parent == null ? null : parent.getTableName(), Collections.<PTable>emptyList(), isImmutableRows, physicalNames, defaultFamilyName == null ? null : PNameFactory.newName(defaultFamilyName), viewStatement, Boolean.TRUE.equals(disableWAL), multiTenant, storeNulls, viewType, indexId, indexType, rowKeyOrderOptimizable, transactional, updateCacheFrequency, 0L, isNamespaceMapped, autoPartitionSeq, isAppendOnlySchema); result = new MetaDataMutationResult(code, result.getMutationTime(), table, true); @@ -3513,7 +3532,7 @@ public class MetaDataClient { } private PTable addTableToCache(MetaDataMutationResult result) throws SQLException { - addIndexesFromPhysicalTable(result, null); + addIndexesFromParentTable(result, null); PTable table = result.getTable(); connection.addTable(table, TransactionUtil.getResolvedTime(connection, result)); return table; http://git-wip-us.apache.org/repos/asf/phoenix/blob/5a6496e1/phoenix-core/src/main/java/org/apache/phoenix/schema/PMetaDataImpl.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/main/java/org/apache/phoenix/schema/PMetaDataImpl.java b/phoenix-core/src/main/java/org/apache/phoenix/schema/PMetaDataImpl.java index 7a78006..8d7161e 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/schema/PMetaDataImpl.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/schema/PMetaDataImpl.java @@ -104,7 +104,7 @@ public class PMetaDataImpl implements PMetaData { PTable newParentTable = null; PTableRef newParentTableRef = null; long parentResolvedTimestamp = resolvedTime; - if (table.getParentName() != null) { // Upsert new index table into parent data table list + if (table.getType() == PTableType.INDEX) { // Upsert new index table into parent data table list String parentName = table.getParentName().getString(); PTableRef oldParentRef = metaData.get(new PTableKey(table.getTenantId(), parentName)); // If parentTable isn't cached, that's ok we can skip this http://git-wip-us.apache.org/repos/asf/phoenix/blob/5a6496e1/phoenix-core/src/main/java/org/apache/phoenix/schema/PTable.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/main/java/org/apache/phoenix/schema/PTable.java b/phoenix-core/src/main/java/org/apache/phoenix/schema/PTable.java index 344dc2c..b585323 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/schema/PTable.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/schema/PTable.java @@ -24,8 +24,6 @@ import org.apache.hadoop.hbase.util.Bytes; import org.apache.phoenix.hbase.index.util.KeyValueBuilder; import org.apache.phoenix.index.IndexMaintainer; import org.apache.phoenix.jdbc.PhoenixConnection; -import org.apache.phoenix.query.ConnectionQueryServices; -import org.apache.phoenix.schema.stats.PTableStats; /** @@ -281,21 +279,21 @@ public interface PTable extends PMetaDataEntity { PIndexState getIndexState(); /** - * Gets the full name of the data table for an index table. - * @return the name of the data table that this index is on - * or null if not an index. + * @return the full name of the parent view for a view or data table for an index table + * or null if this is not a view or index table. Also returns null for a view of a data table + * (use @getPhysicalName for this case) */ PName getParentName(); /** - * Gets the table name of the data table for an index table. - * @return the table name of the data table that this index is - * on or null if not an index. + * @return the table name of the parent view for a view or data table for an index table + * or null if this is not a view or index table. Also returns null for a view of a data table + * (use @getPhysicalTableName for this case) */ PName getParentTableName(); /** - * Gets the schema name of the data table for an index table. - * @return the schema name of the data table that this index is - * on or null if not an index. + * @return the schema name of the parent view for a view or data table for an index table + * or null if this is not a view or index table. Also returns null for view of a data table + * (use @getPhysicalSchemaName for this case) */ PName getParentSchemaName(); http://git-wip-us.apache.org/repos/asf/phoenix/blob/5a6496e1/phoenix-core/src/main/java/org/apache/phoenix/schema/PTableImpl.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/main/java/org/apache/phoenix/schema/PTableImpl.java b/phoenix-core/src/main/java/org/apache/phoenix/schema/PTableImpl.java index b9cec02..293d422 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/schema/PTableImpl.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/schema/PTableImpl.java @@ -206,12 +206,22 @@ public class PTableImpl implements PTable { } public static PTableImpl makePTable(PTable table, long timeStamp, List<PTable> indexes) throws SQLException { - return makePTable(table, timeStamp, indexes, table.getSchemaName(), table.getViewStatement()); + return makePTable(table, timeStamp, indexes, table.getParentSchemaName(), table.getViewStatement()); } - public static PTable makePTable(PTable table, String viewStatement) throws SQLException { - return Objects.equal(viewStatement, table.getViewStatement()) ? table : makePTable(table, table.getTimeStamp(), table.getIndexes(), table.getSchemaName(), viewStatement); + public static PTable makePTable(PTable index, PName indexName, String viewStatement, long updateCacheFrequency, PName tenantId) throws SQLException { + return Objects.equal(viewStatement, index.getViewStatement()) ? index : makePTable(index, indexName, index.getTimeStamp(), Lists.newArrayList(index.getPhysicalName()), index.getIndexes(), viewStatement, updateCacheFrequency, tenantId); } + + public static PTableImpl makePTable(PTable table, PName tableName, long timeStamp, List<PName> physicalNames, List<PTable> indexes, String viewStatement, long updateCacheFrequency, PName tenantId) throws SQLException { + return new PTableImpl( + tenantId, table.getSchemaName(), tableName, table.getType(), table.getIndexState(), timeStamp, + table.getSequenceNumber(), table.getPKName(), table.getBucketNum(), getColumnsToClone(table), table.getParentSchemaName(), table.getParentTableName(), + indexes, table.isImmutableRows(), physicalNames, table.getDefaultFamilyName(), viewStatement, + table.isWALDisabled(), table.isMultiTenant(), table.getStoreNulls(), table.getViewType(), table.getViewIndexId(), table.getIndexType(), + table.getBaseColumnCount(), table.rowKeyOrderOptimizable(), table.isTransactional(), updateCacheFrequency, + table.getIndexDisableTimestamp(), table.isNamespaceMapped(), table.getAutoPartitionSeqName(), table.isAppendOnlySchema()); + } public static PTableImpl makePTable(PTable table, long timeStamp, List<PTable> indexes, PName parentSchemaName, String viewStatement) throws SQLException { return new PTableImpl( @@ -335,7 +345,7 @@ public class PTableImpl implements PTable { int baseColumnCount, boolean rowKeyOrderOptimizable, boolean isTransactional, long updateCacheFrequency, long indexDisableTimestamp, boolean isNamespaceMapped, String autoPartitionSeqName, boolean isAppendOnlySchema) throws SQLException { init(tenantId, schemaName, tableName, type, state, timeStamp, sequenceNumber, pkName, bucketNum, columns, - schemaName, parentTableName, indexes, isImmutableRows, physicalNames, defaultFamilyName, + parentSchemaName, parentTableName, indexes, isImmutableRows, physicalNames, defaultFamilyName, viewExpression, disableWAL, multiTenant, storeNulls, viewType, viewIndexId, indexType, baseColumnCount, rowKeyOrderOptimizable, isTransactional, updateCacheFrequency, indexDisableTimestamp, isNamespaceMapped, autoPartitionSeqName, isAppendOnlySchema); } @@ -519,7 +529,7 @@ public class PTableImpl implements PTable { this.parentSchemaName = parentSchemaName; this.parentTableName = parentTableName; this.parentName = parentTableName == null ? null : PNameFactory.newName(SchemaUtil.getTableName( - parentSchemaName.getString(), parentTableName.getString())); + parentSchemaName!=null ? parentSchemaName.getString() : null, parentTableName.getString())); estimatedSize += PNameFactory.getEstimatedSize(this.parentName); this.physicalNames = physicalNames == null ? ImmutableList.<PName>of() : ImmutableList.copyOf(physicalNames); @@ -945,12 +955,15 @@ public class PTableImpl implements PTable { @Override public PName getParentTableName() { - return parentTableName; + // a view on a table will not have a parent name but will have a physical table name (which is the parent) + return (type!=PTableType.VIEW || parentName!=null) ? parentTableName : + PNameFactory.newName(SchemaUtil.getTableNameFromFullName(getPhysicalName().getBytes())); } @Override public PName getParentName() { - return parentName; + // a view on a table will not have a parent name but will have a physical table name (which is the parent) + return (type!=PTableType.VIEW || parentName!=null) ? parentName : getPhysicalName(); } @Override @@ -1058,9 +1071,11 @@ public class PTableImpl implements PTable { } boolean isImmutableRows = table.getIsImmutableRows(); - PName dataTableName = null; + PName parentSchemaName = null; + PName parentTableName = null; if (table.hasDataTableNameBytes()) { - dataTableName = PNameFactory.newName(table.getDataTableNameBytes().toByteArray()); + parentSchemaName = PNameFactory.newName(SchemaUtil.getSchemaNameFromFullName((table.getDataTableNameBytes().toByteArray()))); + parentTableName = PNameFactory.newName(SchemaUtil.getTableNameFromFullName(table.getDataTableNameBytes().toByteArray())); } PName defaultFamilyName = null; if (table.hasDefaultFamilyName()) { @@ -1115,7 +1130,7 @@ public class PTableImpl implements PTable { try { PTableImpl result = new PTableImpl(); result.init(tenantId, schemaName, tableName, tableType, indexState, timeStamp, sequenceNumber, pkName, - (bucketNum == NO_SALTING) ? null : bucketNum, columns, schemaName,dataTableName, indexes, + (bucketNum == NO_SALTING) ? null : bucketNum, columns, parentSchemaName, parentTableName, indexes, isImmutableRows, physicalNames, defaultFamilyName, viewStatement, disableWAL, multiTenant, storeNulls, viewType, viewIndexId, indexType, baseColumnCount, rowKeyOrderOptimizable, isTransactional, updateCacheFrequency, indexDisableTimestamp, isNamespaceMapped, autoParititonSeqName, isAppendOnlySchema); @@ -1172,7 +1187,7 @@ public class PTableImpl implements PTable { builder.setIsImmutableRows(table.isImmutableRows()); if (table.getParentName() != null) { - builder.setDataTableNameBytes(ByteStringer.wrap(table.getParentTableName().getBytes())); + builder.setDataTableNameBytes(ByteStringer.wrap(table.getParentName().getBytes())); } if (table.getDefaultFamilyName()!= null) { builder.setDefaultFamilyName(ByteStringer.wrap(table.getDefaultFamilyName().getBytes())); @@ -1211,7 +1226,9 @@ public class PTableImpl implements PTable { @Override public PName getParentSchemaName() { - return parentSchemaName; + // a view on a table will not have a parent name but will have a physical table name (which is the parent) + return (type!=PTableType.VIEW || parentName!=null) ? parentSchemaName : + PNameFactory.newName(SchemaUtil.getSchemaNameFromFullName(getPhysicalName().getBytes())); } @Override http://git-wip-us.apache.org/repos/asf/phoenix/blob/5a6496e1/phoenix-core/src/main/java/org/apache/phoenix/util/ExpressionUtil.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/main/java/org/apache/phoenix/util/ExpressionUtil.java b/phoenix-core/src/main/java/org/apache/phoenix/util/ExpressionUtil.java index 6032aec..6f8b19f 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/util/ExpressionUtil.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/util/ExpressionUtil.java @@ -10,34 +10,21 @@ package org.apache.phoenix.util; import java.sql.SQLException; -import java.util.List; import org.apache.hadoop.hbase.io.ImmutableBytesWritable; import org.apache.phoenix.expression.Determinism; import org.apache.phoenix.expression.Expression; import org.apache.phoenix.expression.LiteralExpression; -import org.apache.phoenix.expression.function.CurrentDateFunction; -import org.apache.phoenix.expression.function.CurrentTimeFunction; -import org.apache.phoenix.expression.function.FunctionExpression; import org.apache.phoenix.schema.types.PDataType; -import com.google.common.collect.Lists; - public class ExpressionUtil { - @SuppressWarnings("unchecked") - private static final List<Class<? extends FunctionExpression>> OVERRIDE_LITERAL_FUNCTIONS = Lists - .<Class<? extends FunctionExpression>> newArrayList( - CurrentDateFunction.class, CurrentTimeFunction.class); - private ExpressionUtil() { } public static boolean isConstant(Expression expression) { return (expression.isStateless() && (expression.getDeterminism() == Determinism.ALWAYS - || expression.getDeterminism() == Determinism.PER_STATEMENT - // TODO remove this in 3.4/4.4 (need to support clients on 3.1/4.1) - || OVERRIDE_LITERAL_FUNCTIONS.contains(expression.getClass()))); + || expression.getDeterminism() == Determinism.PER_STATEMENT)); } public static LiteralExpression getConstantExpression(Expression expression, ImmutableBytesWritable ptr)