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,