This is an automated email from the ASF dual-hosted git repository. pboado pushed a commit to branch 5.x-cdh6 in repository https://gitbox.apache.org/repos/asf/phoenix.git
commit 4a7cc097d82ed7d6f12d22e3c36b127c5cbc3e39 Author: Abhishek Singh Chouhan <abhishekchouhan...@gmail.com> AuthorDate: Fri Mar 15 22:50:37 2019 +0000 PHOENIX-5180 Add API to PhoenixRunTime to get ptable of a tenant using a global connection --- .../end2end/GlobalConnectionTenantTableIT.java | 188 +++++++++++++++++++++ .../org/apache/phoenix/schema/MetaDataClient.java | 2 +- .../org/apache/phoenix/util/PhoenixRuntime.java | 68 +++++++- 3 files changed, 252 insertions(+), 6 deletions(-) diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/GlobalConnectionTenantTableIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/GlobalConnectionTenantTableIT.java new file mode 100644 index 0000000..d0c890c --- /dev/null +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/GlobalConnectionTenantTableIT.java @@ -0,0 +1,188 @@ +/* + * 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; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.util.Map; +import java.util.Properties; + +import org.apache.phoenix.exception.SQLExceptionCode; +import org.apache.phoenix.query.BaseTest; +import org.apache.phoenix.schema.PTable; +import org.apache.phoenix.util.EnvironmentEdgeManager; +import org.apache.phoenix.util.PhoenixRuntime; +import org.apache.phoenix.util.ReadOnlyProps; +import org.apache.phoenix.util.SchemaUtil; +import org.junit.BeforeClass; +import org.junit.Test; + +import com.google.common.collect.Maps; + +public class GlobalConnectionTenantTableIT extends BaseTest { + + private static final String SCHEMA_NAME = "SCHEMA1"; + private static final String TABLE_NAME = generateUniqueName(); + private static final String TENANT_NAME = "TENANT_A"; + private static final String VIEW_NAME = "VIEW1"; + private static final String INDEX_NAME = "INDEX1"; + private static final String VIEW_INDEX_COL = "v2"; + private static final String FULL_VIEW_NAME = SchemaUtil.getTableName(SCHEMA_NAME, VIEW_NAME); + private static final String FULL_INDEX_NAME = SchemaUtil.getTableName(SCHEMA_NAME, INDEX_NAME); + + @BeforeClass + public static void doSetup() throws Exception { + Map<String, String> props = Maps.newHashMapWithExpectedSize(1); + setUpTestDriver(new ReadOnlyProps(props.entrySet().iterator())); + createBaseTable(SCHEMA_NAME, TABLE_NAME, true, null, null); + try (Connection conn = getTenantConnection(TENANT_NAME)) { + createView(conn, SCHEMA_NAME, VIEW_NAME, TABLE_NAME); + createViewIndex(conn, SCHEMA_NAME, INDEX_NAME, VIEW_NAME, VIEW_INDEX_COL); + } + } + + @Test + public void testGetLatestTenantTable() throws SQLException { + try (Connection conn = getConnection()) { + PTable table = PhoenixRuntime.getTable(conn, TENANT_NAME, FULL_VIEW_NAME, null); + assertNotNull(table); + table = null; + table = PhoenixRuntime.getTable(conn, TENANT_NAME, FULL_INDEX_NAME, null); + assertNotNull(table); + } + } + + @Test + public void testGetTenantViewAtTimestamp() throws SQLException { + long startTime = EnvironmentEdgeManager.currentTimeMillis(); + try (Connection conn = getConnection()) { + PTable table = PhoenixRuntime.getTable(conn, TENANT_NAME, FULL_VIEW_NAME, null); + long tableTimestamp = table.getTimeStamp(); + // Alter table + try (Connection tenantConn = getTenantConnection(TENANT_NAME)) { + String alterView = "ALTER VIEW " + FULL_VIEW_NAME + " ADD new_col INTEGER"; + tenantConn.createStatement().execute(alterView); + } + // Get the altered table and verify + PTable newTable = PhoenixRuntime.getTable(conn, TENANT_NAME, FULL_VIEW_NAME); + assertNotNull(newTable); + assertTrue(newTable.getTimeStamp() > tableTimestamp); + assertEquals(newTable.getColumns().size(), (table.getColumns().size() + 1)); + // Now get the old table and verify + PTable oldTable = PhoenixRuntime.getTable(conn, TENANT_NAME, FULL_VIEW_NAME, startTime); + assertNotNull(oldTable); + assertEquals(oldTable.getTimeStamp(), tableTimestamp); + } + } + + @Test + public void testGetTableWithoutTenantId() throws SQLException { + try (Connection conn = getConnection()) { + PTable table = + PhoenixRuntime.getTable(conn, null, + SchemaUtil.getTableName(SCHEMA_NAME, TABLE_NAME)); + assertNotNull(table); + + try { + table = PhoenixRuntime.getTable(conn, null, FULL_VIEW_NAME); + fail( + "Expected TableNotFoundException for trying to get tenant specific view without tenantid"); + } catch (SQLException e) { + assertEquals(e.getErrorCode(), SQLExceptionCode.TABLE_UNDEFINED.getErrorCode()); + } + } + } + + @Test + public void testTableNotFound() throws SQLException { + try (Connection conn = getConnection()) { + try { + PTable table = PhoenixRuntime.getTable(conn, TENANT_NAME, FULL_VIEW_NAME, 1L); + fail("Expected TableNotFoundException"); + } catch (SQLException e) { + assertEquals(e.getErrorCode(), SQLExceptionCode.TABLE_UNDEFINED.getErrorCode()); + } + } + + } + + @Test + public void testGetTableFromCache() throws SQLException { + try (Connection conn = getConnection()) { + PTable table = PhoenixRuntime.getTable(conn, TENANT_NAME, FULL_VIEW_NAME, null); + PTable newTable = PhoenixRuntime.getTable(conn, TENANT_NAME, FULL_VIEW_NAME, null); + assertNotNull(newTable); + assertTrue(newTable == table); + } + } + + private static void createBaseTable(String schemaName, String tableName, boolean multiTenant, + Integer saltBuckets, String splits) throws SQLException { + Connection conn = getConnection(); + String ddl = + "CREATE TABLE " + SchemaUtil.getTableName(schemaName, tableName) + + " (t_id VARCHAR NOT NULL,\n" + "k1 VARCHAR NOT NULL,\n" + + "k2 INTEGER NOT NULL,\n" + "v1 VARCHAR,\n" + VIEW_INDEX_COL + + " INTEGER,\n" + "CONSTRAINT pk PRIMARY KEY (t_id, k1, k2))\n"; + String ddlOptions = multiTenant ? "MULTI_TENANT=true" : ""; + if (saltBuckets != null) { + ddlOptions = + ddlOptions + (ddlOptions.isEmpty() ? "" : ",") + "salt_buckets=" + saltBuckets; + } + if (splits != null) { + ddlOptions = ddlOptions + (ddlOptions.isEmpty() ? "" : ",") + "splits=" + splits; + } + conn.createStatement().execute(ddl + ddlOptions); + conn.close(); + } + + private static void createView(Connection conn, String schemaName, String viewName, + String baseTableName) throws SQLException { + String fullViewName = SchemaUtil.getTableName(schemaName, viewName); + String fullTableName = SchemaUtil.getTableName(schemaName, baseTableName); + conn.createStatement() + .execute("CREATE VIEW " + fullViewName + " AS SELECT * FROM " + fullTableName); + conn.commit(); + } + + private static void createViewIndex(Connection conn, String schemaName, String indexName, + String viewName, String indexColumn) throws SQLException { + String fullViewName = SchemaUtil.getTableName(schemaName, viewName); + conn.createStatement().execute( + "CREATE INDEX " + indexName + " ON " + fullViewName + "(" + indexColumn + ")"); + conn.commit(); + } + + private static Connection getTenantConnection(String tenant) throws SQLException { + Properties props = new Properties(); + props.setProperty(PhoenixRuntime.TENANT_ID_ATTRIB, tenant); + return DriverManager.getConnection(getUrl(), props); + } + + private static Connection getConnection() throws SQLException { + Properties props = new Properties(); + return DriverManager.getConnection(getUrl(), props); + } + +} 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 965f637..3fad1a1 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 @@ -586,7 +586,7 @@ public class MetaDataClient { return currentScn; } - private MetaDataMutationResult updateCache(PName origTenantId, String schemaName, String tableName, + public MetaDataMutationResult updateCache(PName origTenantId, String schemaName, String tableName, boolean alwaysHitServer, Long resolvedTimestamp) throws SQLException { // TODO: pass byte[] herez boolean systemTable = SYSTEM_CATALOG_SCHEMA.equals(schemaName); // System tables must always have a null tenantId diff --git a/phoenix-core/src/main/java/org/apache/phoenix/util/PhoenixRuntime.java b/phoenix-core/src/main/java/org/apache/phoenix/util/PhoenixRuntime.java index d4f1177..ce267d4 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/util/PhoenixRuntime.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/util/PhoenixRuntime.java @@ -18,6 +18,7 @@ package org.apache.phoenix.util; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkArgument; import static org.apache.phoenix.schema.types.PDataType.ARRAY_TYPE_SUFFIX; import java.io.File; @@ -79,9 +80,12 @@ import org.apache.phoenix.schema.KeyValueSchema.KeyValueSchemaBuilder; import org.apache.phoenix.schema.MetaDataClient; import org.apache.phoenix.schema.PColumn; import org.apache.phoenix.schema.PColumnFamily; +import org.apache.phoenix.schema.PName; +import org.apache.phoenix.schema.PNameFactory; import org.apache.phoenix.schema.PTable; import org.apache.phoenix.schema.PTable.IndexType; import org.apache.phoenix.schema.PTableKey; +import org.apache.phoenix.schema.PTableRef; import org.apache.phoenix.schema.PTableType; import org.apache.phoenix.schema.RowKeyValueAccessor; import org.apache.phoenix.schema.TableNotFoundException; @@ -460,13 +464,67 @@ public class PhoenixRuntime { } /** - * Get list of ColumnInfos that contain Column Name and its associated - * PDataType for an import. The supplied list of columns can be null -- if it is non-null, - * it represents a user-supplied list of columns to be imported. - * + * Similar to {@link #getTable(Connection, String, String, Long)} but returns the most recent + * PTable + */ + public static PTable getTable(Connection conn, String tenantId, String fullTableName) + throws SQLException { + return getTable(conn, tenantId, fullTableName, HConstants.LATEST_TIMESTAMP); + } + + /** + * Returns the PTable as of the timestamp provided. This method can be used to fetch tenant + * specific PTable through a global connection. A null timestamp would result in the client side + * metadata cache being used (ie. in case table metadata is already present it'll be returned). + * To get the latest metadata use {@link #getTable(Connection, String, String)} + * @param conn + * @param tenantId + * @param fullTableName + * @param timestamp + * @return PTable + * @throws SQLException + * @throws NullPointerException if conn or fullTableName is null + * @throws IllegalArgumentException if timestamp is negative + */ + public static PTable getTable(Connection conn, @Nullable String tenantId, String fullTableName, + @Nullable Long timestamp) throws SQLException { + checkNotNull(conn); + checkNotNull(fullTableName); + if (timestamp != null) { + checkArgument(timestamp >= 0); + } + PTable table = null; + PhoenixConnection pconn = conn.unwrap(PhoenixConnection.class); + PName pTenantId = (tenantId == null) ? null : PNameFactory.newName(tenantId); + try { + PTableRef tableref = pconn.getTableRef(new PTableKey(pTenantId, fullTableName)); + if (timestamp == null + || (tableref != null && tableref.getResolvedTimeStamp() == timestamp)) { + table = tableref.getTable(); + } else { + throw new TableNotFoundException(fullTableName); + } + } catch (TableNotFoundException e) { + String schemaName = SchemaUtil.getSchemaNameFromFullName(fullTableName); + String tableName = SchemaUtil.getTableNameFromFullName(fullTableName); + MetaDataMutationResult result = + new MetaDataClient(pconn).updateCache(pTenantId, schemaName, tableName, false, + timestamp); + if (result.getMutationCode() != MutationCode.TABLE_ALREADY_EXISTS) { + throw e; + } + table = result.getTable(); + } + return table; + } + + /** + * Get list of ColumnInfos that contain Column Name and its associated PDataType for an import. + * The supplied list of columns can be null -- if it is non-null, it represents a user-supplied + * list of columns to be imported. * @param conn Phoenix connection from which metadata will be read * @param tableName Phoenix table name whose columns are to be checked. Can include a schema - * name + * name * @param columns user-supplied list of import columns, can be null */ public static List<ColumnInfo> generateColumnInfo(Connection conn,