You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@phoenix.apache.org by td...@apache.org on 2019/03/26 06:15:30 UTC
[phoenix] branch 4.x-HBase-1.2 updated: PHOENIX-5180 Add API to
PhoenixRunTime to get ptable of a tenant using a global connection
This is an automated email from the ASF dual-hosted git repository.
tdsilva pushed a commit to branch 4.x-HBase-1.2
in repository https://gitbox.apache.org/repos/asf/phoenix.git
The following commit(s) were added to refs/heads/4.x-HBase-1.2 by this push:
new 9f2b1fd PHOENIX-5180 Add API to PhoenixRunTime to get ptable of a tenant using a global connection
9f2b1fd is described below
commit 9f2b1fd9c76e6a1050ef5da1fb9e190bc2acfcc4
Author: Abhishek Singh Chouhan <ab...@gmail.com>
AuthorDate: Fri Mar 15 15:50:37 2019 -0700
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 be6d286..71b1dc1 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
@@ -585,7 +585,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 aee66aa..07a3de6 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;
@@ -80,9 +81,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;
@@ -461,13 +465,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,