Example #1
0
  /**
   * 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
   * @param columns user-supplied list of import columns, can be null
   * @param strict if true, an exception will be thrown if unknown columns are supplied
   */
  public static List<ColumnInfo> generateColumnInfo(
      Connection conn, String tableName, List<String> columns, boolean strict) throws SQLException {
    Map<String, Integer> columnNameToTypeMap = Maps.newLinkedHashMap();
    Set<String> ambiguousColumnNames = new HashSet<String>();
    Map<String, Integer> fullColumnNameToTypeMap = Maps.newLinkedHashMap();
    DatabaseMetaData dbmd = conn.getMetaData();
    int unfoundColumnCount = 0;
    // TODO: escape wildcard characters here because we don't want that
    // behavior here
    String escapedTableName = StringUtil.escapeLike(tableName);
    String[] schemaAndTable = escapedTableName.split("\\.");
    ResultSet rs = null;
    try {
      rs =
          dbmd.getColumns(
              null,
              (schemaAndTable.length == 1 ? "" : schemaAndTable[0]),
              (schemaAndTable.length == 1 ? escapedTableName : schemaAndTable[1]),
              null);
      while (rs.next()) {
        String colName = rs.getString(QueryUtil.COLUMN_NAME_POSITION);
        String colFam = rs.getString(QueryUtil.COLUMN_FAMILY_POSITION);

        // use family qualifier, if available, otherwise, use column name
        String fullColumn = (colFam == null ? colName : String.format("%s.%s", colFam, colName));
        String sqlTypeName = rs.getString(QueryUtil.DATA_TYPE_NAME_POSITION);

        // allow for both bare and family qualified names.
        if (columnNameToTypeMap.keySet().contains(colName)) {
          ambiguousColumnNames.add(colName);
        }
        columnNameToTypeMap.put(colName, PDataType.fromSqlTypeName(sqlTypeName).getSqlType());
        fullColumnNameToTypeMap.put(
            fullColumn, PDataType.fromSqlTypeName(sqlTypeName).getSqlType());
      }
      if (columnNameToTypeMap.isEmpty()) {
        throw new IllegalArgumentException("Table " + tableName + " not found");
      }
    } finally {
      if (rs != null) {
        rs.close();
      }
    }
    List<ColumnInfo> columnInfoList = Lists.newArrayList();
    Set<String> unresolvedColumnNames = new TreeSet<String>();
    if (columns == null) {
      // use family qualified names by default, if no columns are specified.
      for (Map.Entry<String, Integer> entry : fullColumnNameToTypeMap.entrySet()) {
        columnInfoList.add(new ColumnInfo(entry.getKey(), entry.getValue()));
      }
    } else {
      // Leave "null" as indication to skip b/c it doesn't exist
      for (int i = 0; i < columns.size(); i++) {
        String columnName = columns.get(i).trim();
        Integer sqlType = null;
        if (fullColumnNameToTypeMap.containsKey(columnName)) {
          sqlType = fullColumnNameToTypeMap.get(columnName);
        } else if (columnNameToTypeMap.containsKey(columnName)) {
          if (ambiguousColumnNames.contains(columnName)) {
            unresolvedColumnNames.add(columnName);
          }
          // fall back to bare column name.
          sqlType = columnNameToTypeMap.get(columnName);
        }
        if (unresolvedColumnNames.size() > 0) {
          StringBuilder exceptionMessage = new StringBuilder();
          boolean first = true;
          exceptionMessage.append(
              "Unable to resolve these column names to a single column family:\n");
          for (String col : unresolvedColumnNames) {
            if (first) first = false;
            else exceptionMessage.append(",");
            exceptionMessage.append(col);
          }
          exceptionMessage.append("\nAvailable columns with column families:\n");
          first = true;
          for (String col : fullColumnNameToTypeMap.keySet()) {
            if (first) first = false;
            else exceptionMessage.append(",");
            exceptionMessage.append(col);
          }
          throw new SQLException(exceptionMessage.toString());
        }

        if (sqlType == null) {
          if (strict) {
            throw new SQLExceptionInfo.Builder(SQLExceptionCode.COLUMN_NOT_FOUND)
                .setColumnName(columnName)
                .setTableName(tableName)
                .build()
                .buildException();
          }
          unfoundColumnCount++;
        } else {
          columnInfoList.add(new ColumnInfo(columnName, sqlType));
        }
      }
      if (unfoundColumnCount == columns.size()) {
        throw new SQLExceptionInfo.Builder(SQLExceptionCode.COLUMN_NOT_FOUND)
            .setColumnName(Arrays.toString(columns.toArray(new String[0])))
            .setTableName(tableName)
            .build()
            .buildException();
      }
    }
    return columnInfoList;
  }
  @Test
  public void testAllDropParentTableWithCascadeWithMultipleTenantTablesAndIndexes()
      throws Exception {
    // Create a second tenant table
    createTestTable(PHOENIX_JDBC_TENANT_SPECIFIC_URL2, TENANT_TABLE_DDL, null, nextTimestamp());
    // TODO Create some tenant specific table indexes

    long ts = nextTimestamp();
    Properties props = new Properties();
    props.setProperty(PhoenixRuntime.CURRENT_SCN_ATTRIB, Long.toString(ts));
    Connection conn = null;
    Connection connTenant1 = null;
    Connection connTenant2 = null;

    try {
      conn = DriverManager.getConnection(getUrl(), props);
      DatabaseMetaData meta = conn.getMetaData();
      ResultSet rs =
          meta.getSuperTables(null, null, StringUtil.escapeLike(TENANT_TABLE_NAME) + "%");
      assertTrue(rs.next());
      assertEquals(TENANT_ID2, rs.getString(PhoenixDatabaseMetaData.TABLE_CAT));
      assertEquals(TENANT_TABLE_NAME, rs.getString(PhoenixDatabaseMetaData.TABLE_NAME));
      assertEquals(PARENT_TABLE_NAME, rs.getString(PhoenixDatabaseMetaData.SUPERTABLE_NAME));
      assertTrue(rs.next());
      assertEquals(TENANT_ID, rs.getString(PhoenixDatabaseMetaData.TABLE_CAT));
      assertEquals(TENANT_TABLE_NAME, rs.getString(PhoenixDatabaseMetaData.TABLE_NAME));
      assertEquals(PARENT_TABLE_NAME, rs.getString(PhoenixDatabaseMetaData.SUPERTABLE_NAME));
      assertTrue(rs.next());
      assertEquals(TENANT_ID, rs.getString(PhoenixDatabaseMetaData.TABLE_CAT));
      assertEquals(
          TENANT_TABLE_NAME_NO_TENANT_TYPE_ID, rs.getString(PhoenixDatabaseMetaData.TABLE_NAME));
      assertEquals(
          PARENT_TABLE_NAME_NO_TENANT_TYPE_ID,
          rs.getString(PhoenixDatabaseMetaData.SUPERTABLE_NAME));
      assertFalse(rs.next());
      rs.close();
      conn.close();

      // Drop Parent Table
      conn.createStatement().executeUpdate("DROP TABLE " + PARENT_TABLE_NAME + " CASCADE");

      // Validate Tenant Views are dropped
      props.setProperty(PhoenixRuntime.CURRENT_SCN_ATTRIB, Long.toString(ts + 10));
      connTenant1 = DriverManager.getConnection(PHOENIX_JDBC_TENANT_SPECIFIC_URL, props);
      validateTenantViewIsDropped(connTenant1);
      connTenant2 = DriverManager.getConnection(PHOENIX_JDBC_TENANT_SPECIFIC_URL2, props);
      validateTenantViewIsDropped(connTenant2);

      // Validate Tenant Metadata is gone for the Tenant Table TENANT_TABLE_NAME
      conn = DriverManager.getConnection(getUrl(), props);
      meta = conn.getMetaData();
      rs = meta.getSuperTables(null, null, StringUtil.escapeLike(TENANT_TABLE_NAME) + "%");
      assertTrue(rs.next());
      assertEquals(TENANT_ID, rs.getString(PhoenixDatabaseMetaData.TABLE_CAT));
      assertEquals(
          TENANT_TABLE_NAME_NO_TENANT_TYPE_ID, rs.getString(PhoenixDatabaseMetaData.TABLE_NAME));
      assertEquals(
          PARENT_TABLE_NAME_NO_TENANT_TYPE_ID,
          rs.getString(PhoenixDatabaseMetaData.SUPERTABLE_NAME));
      assertFalse(rs.next());
      rs.close();

    } finally {
      if (conn != null) {
        conn.close();
      }
      if (connTenant1 != null) {
        connTenant1.close();
      }
      if (connTenant2 != null) {
        connTenant2.close();
      }
    }
  }
  @Test
  public void testTableMetadataScan() throws Exception {
    // create a tenant table with same name for a different tenant to make sure we are not picking
    // it up in metadata scans for TENANT_ID
    String tenantId2 = "tenant2";
    String secondTenatConnectionURL =
        PHOENIX_JDBC_TENANT_SPECIFIC_URL.replace(TENANT_ID, tenantId2);
    String tenantTable2 = TENANT_TABLE_NAME + "2";
    createTestTable(
        secondTenatConnectionURL,
        TENANT_TABLE_DDL.replace(TENANT_TABLE_NAME, tenantTable2),
        null,
        nextTimestamp(),
        false);

    Properties props = new Properties();
    props.setProperty(PhoenixRuntime.CURRENT_SCN_ATTRIB, Long.toString(nextTimestamp()));
    Connection conn = DriverManager.getConnection(getUrl(), props);
    try {
      // empty string means global tenant id
      // make sure connections w/o tenant id only see non-tenant-specific tables, both SYSTEM and
      // USER
      DatabaseMetaData meta = conn.getMetaData();
      ResultSet rs = meta.getTables("", null, null, null);
      assertTrue(rs.next());
      assertTableMetaData(rs, SYSTEM_CATALOG_SCHEMA, SYSTEM_CATALOG_TABLE, SYSTEM);
      assertTrue(rs.next());
      assertTableMetaData(rs, SYSTEM_CATALOG_SCHEMA, TYPE_SEQUENCE, SYSTEM);
      assertTrue(rs.next());
      assertTableMetaData(rs, null, PARENT_TABLE_NAME, TABLE);
      assertTrue(rs.next());
      assertTableMetaData(rs, null, PARENT_TABLE_NAME_NO_TENANT_TYPE_ID, TABLE);
      assertFalse(rs.next());

      // make sure connections w/o tenant id only see non-tenant-specific columns
      rs = meta.getColumns("", null, null, null);
      while (rs.next()) {
        assertNotEquals(TENANT_TABLE_NAME, rs.getString("TABLE_NAME"));
        assertNotEquals(tenantTable2, rs.getString("TABLE_NAME"));
      }

      // null catalog means across all tenant_ids
      rs = meta.getSuperTables(null, null, StringUtil.escapeLike(TENANT_TABLE_NAME) + "%");
      assertTrue(rs.next());
      assertEquals(TENANT_ID, rs.getString(PhoenixDatabaseMetaData.TABLE_CAT));
      assertEquals(TENANT_TABLE_NAME, rs.getString(PhoenixDatabaseMetaData.TABLE_NAME));
      assertEquals(PARENT_TABLE_NAME, rs.getString(PhoenixDatabaseMetaData.SUPERTABLE_NAME));
      assertTrue(rs.next());
      assertEquals(TENANT_ID, rs.getString(PhoenixDatabaseMetaData.TABLE_CAT));
      assertEquals(
          TENANT_TABLE_NAME_NO_TENANT_TYPE_ID, rs.getString(PhoenixDatabaseMetaData.TABLE_NAME));
      assertEquals(
          PARENT_TABLE_NAME_NO_TENANT_TYPE_ID,
          rs.getString(PhoenixDatabaseMetaData.SUPERTABLE_NAME));
      assertTrue(rs.next());
      assertEquals(tenantId2, rs.getString(PhoenixDatabaseMetaData.TABLE_CAT));
      assertEquals(tenantTable2, rs.getString(PhoenixDatabaseMetaData.TABLE_NAME));
      assertEquals(PARENT_TABLE_NAME, rs.getString(PhoenixDatabaseMetaData.SUPERTABLE_NAME));
      assertFalse(rs.next());
      conn.close();

      // Global connection sees all tenant tables
      conn = DriverManager.getConnection(getUrl());
      rs = conn.getMetaData().getSuperTables(TENANT_ID, null, null);
      assertTrue(rs.next());
      assertEquals(TENANT_ID, rs.getString(PhoenixDatabaseMetaData.TABLE_CAT));
      assertEquals(TENANT_TABLE_NAME, rs.getString(PhoenixDatabaseMetaData.TABLE_NAME));
      assertEquals(PARENT_TABLE_NAME, rs.getString(PhoenixDatabaseMetaData.SUPERTABLE_NAME));
      assertTrue(rs.next());
      assertEquals(TENANT_ID, rs.getString(PhoenixDatabaseMetaData.TABLE_CAT));
      assertEquals(
          TENANT_TABLE_NAME_NO_TENANT_TYPE_ID, rs.getString(PhoenixDatabaseMetaData.TABLE_NAME));
      assertEquals(
          PARENT_TABLE_NAME_NO_TENANT_TYPE_ID,
          rs.getString(PhoenixDatabaseMetaData.SUPERTABLE_NAME));
      assertFalse(rs.next());

      rs = conn.getMetaData().getCatalogs();
      assertTrue(rs.next());
      assertEquals(TENANT_ID, rs.getString(PhoenixDatabaseMetaData.TABLE_CAT));
      assertTrue(rs.next());
      assertEquals(tenantId2, rs.getString(PhoenixDatabaseMetaData.TABLE_CAT));
      assertFalse(rs.next());
    } finally {
      props.clear();
      conn.close();
    }

    props.setProperty(PhoenixRuntime.CURRENT_SCN_ATTRIB, Long.toString(nextTimestamp()));
    conn = DriverManager.getConnection(PHOENIX_JDBC_TENANT_SPECIFIC_URL, props);
    try {
      // make sure tenant-specific connections only see their own tables and the global tables
      DatabaseMetaData meta = conn.getMetaData();
      ResultSet rs = meta.getTables(null, null, null, null);
      assertTrue(rs.next());
      assertTableMetaData(
          rs,
          PhoenixDatabaseMetaData.SYSTEM_CATALOG_SCHEMA,
          PhoenixDatabaseMetaData.SYSTEM_CATALOG_TABLE,
          PTableType.SYSTEM);
      assertTrue(rs.next());
      assertTableMetaData(
          rs,
          PhoenixDatabaseMetaData.SYSTEM_CATALOG_SCHEMA,
          PhoenixDatabaseMetaData.TYPE_SEQUENCE,
          PTableType.SYSTEM);
      assertTrue(rs.next());
      assertTableMetaData(rs, null, PARENT_TABLE_NAME, PTableType.TABLE);
      assertTrue(rs.next());
      assertTableMetaData(rs, null, PARENT_TABLE_NAME_NO_TENANT_TYPE_ID, PTableType.TABLE);
      assertTrue(rs.next());
      assertTableMetaData(rs, null, TENANT_TABLE_NAME, PTableType.VIEW);
      assertTrue(rs.next());
      assertTableMetaData(rs, null, TENANT_TABLE_NAME_NO_TENANT_TYPE_ID, PTableType.VIEW);
      assertFalse(rs.next());

      // make sure tenants see parent table's columns and their own
      rs = meta.getColumns(null, null, StringUtil.escapeLike(TENANT_TABLE_NAME) + "%", null);
      assertTrue(rs.next());
      assertColumnMetaData(rs, null, TENANT_TABLE_NAME, "user");
      assertTrue(rs.next());
      assertColumnMetaData(rs, null, TENANT_TABLE_NAME, "tenant_id");
      assertTrue(rs.next());
      assertColumnMetaData(rs, null, TENANT_TABLE_NAME, "tenant_type_id");
      assertTrue(rs.next());
      assertColumnMetaData(rs, null, TENANT_TABLE_NAME, "id");
      assertTrue(rs.next());
      assertColumnMetaData(rs, null, TENANT_TABLE_NAME, "tenant_col");
      assertTrue(rs.next());
      assertColumnMetaData(rs, null, TENANT_TABLE_NAME_NO_TENANT_TYPE_ID, "user");
      assertTrue(rs.next());
      assertColumnMetaData(rs, null, TENANT_TABLE_NAME_NO_TENANT_TYPE_ID, "tenant_id");
      assertTrue(rs.next());
      assertColumnMetaData(rs, null, TENANT_TABLE_NAME_NO_TENANT_TYPE_ID, "id");
      assertTrue(rs.next());
      assertColumnMetaData(rs, null, TENANT_TABLE_NAME_NO_TENANT_TYPE_ID, "tenant_col");
      assertFalse(rs.next());
    } finally {
      conn.close();
    }
  }
 /**
  * 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
  * @param columns user-supplied list of import columns, can be null
  * @param strict if true, an exception will be thrown if unknown columns are supplied
  */
 public static List<ColumnInfo> generateColumnInfo(
     Connection conn, String tableName, List<String> columns, boolean strict) throws SQLException {
   Map<String, Integer> columnNameToTypeMap = Maps.newLinkedHashMap();
   DatabaseMetaData dbmd = conn.getMetaData();
   int unfoundColumnCount = 0;
   // TODO: escape wildcard characters here because we don't want that
   // behavior here
   String escapedTableName = StringUtil.escapeLike(tableName);
   String[] schemaAndTable = escapedTableName.split("\\.");
   ResultSet rs = null;
   try {
     rs =
         dbmd.getColumns(
             null,
             (schemaAndTable.length == 1 ? "" : schemaAndTable[0]),
             (schemaAndTable.length == 1 ? escapedTableName : schemaAndTable[1]),
             null);
     while (rs.next()) {
       String sqlTypeName = rs.getString(QueryUtil.DATA_TYPE_NAME_POSITION);
       columnNameToTypeMap.put(
           rs.getString(QueryUtil.COLUMN_NAME_POSITION),
           PDataType.fromSqlTypeName(sqlTypeName).getSqlType());
     }
     if (columnNameToTypeMap.isEmpty()) {
       throw new IllegalArgumentException("Table " + tableName + " not found");
     }
   } finally {
     if (rs != null) {
       rs.close();
     }
   }
   List<ColumnInfo> columnInfoList = Lists.newArrayList();
   if (columns == null) {
     for (Map.Entry<String, Integer> entry : columnNameToTypeMap.entrySet()) {
       columnInfoList.add(new ColumnInfo(entry.getKey(), entry.getValue()));
     }
   } else {
     // Leave "null" as indication to skip b/c it doesn't exist
     for (int i = 0; i < columns.size(); i++) {
       String columnName = columns.get(i).trim();
       Integer sqlType = columnNameToTypeMap.get(columnName);
       if (sqlType == null) {
         if (strict) {
           throw new SQLExceptionInfo.Builder(SQLExceptionCode.COLUMN_NOT_FOUND)
               .setColumnName(columnName)
               .setTableName(tableName)
               .build()
               .buildException();
         }
         unfoundColumnCount++;
       } else {
         columnInfoList.add(new ColumnInfo(columnName, sqlType));
       }
     }
     if (unfoundColumnCount == columns.size()) {
       throw new SQLExceptionInfo.Builder(SQLExceptionCode.COLUMN_NOT_FOUND)
           .setColumnName(Arrays.toString(columns.toArray(new String[0])))
           .setTableName(tableName)
           .build()
           .buildException();
     }
   }
   return columnInfoList;
 }