/* * Licensed 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 com.facebook.presto.raptor.systemtables; import com.facebook.presto.raptor.metadata.ColumnMetadataRow; import com.facebook.presto.raptor.metadata.ForMetadata; import com.facebook.presto.raptor.metadata.MetadataDao; import com.facebook.presto.raptor.metadata.TableMetadataRow; import com.facebook.presto.spi.ColumnMetadata; import com.facebook.presto.spi.ConnectorPageSource; import com.facebook.presto.spi.ConnectorSession; import com.facebook.presto.spi.ConnectorTableMetadata; import com.facebook.presto.spi.FixedPageSource; import com.facebook.presto.spi.Page; import com.facebook.presto.spi.PrestoException; import com.facebook.presto.spi.SchemaTableName; import com.facebook.presto.spi.SystemTable; import com.facebook.presto.spi.block.BlockBuilder; import com.facebook.presto.spi.connector.ConnectorTransactionHandle; import com.facebook.presto.spi.predicate.NullableValue; import com.facebook.presto.spi.predicate.TupleDomain; import com.facebook.presto.spi.type.Type; import com.facebook.presto.spi.type.TypeManager; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.PeekingIterator; import io.airlift.slice.Slice; import org.skife.jdbi.v2.IDBI; import javax.inject.Inject; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.OptionalInt; import java.util.OptionalLong; import java.util.SortedMap; import java.util.TreeMap; import static com.facebook.presto.raptor.RaptorErrorCode.RAPTOR_CORRUPT_METADATA; import static com.facebook.presto.raptor.util.DatabaseUtil.onDemandDao; import static com.facebook.presto.spi.SystemTable.Distribution.SINGLE_COORDINATOR; import static com.facebook.presto.spi.predicate.TupleDomain.extractFixedValues; import static com.facebook.presto.spi.type.BigintType.BIGINT; import static com.facebook.presto.spi.type.BooleanType.BOOLEAN; import static com.facebook.presto.spi.type.TypeSignature.parseTypeSignature; import static com.facebook.presto.spi.type.VarcharType.VARCHAR; import static com.google.common.collect.Iterators.peekingIterator; import static io.airlift.slice.Slices.utf8Slice; import static java.lang.String.format; import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.toList; public class TableMetadataSystemTable implements SystemTable { private static final String TABLE_NAME = "table_name"; private static final String SCHEMA_NAME = "table_schema"; private final MetadataDao dao; private final ConnectorTableMetadata tableMetadata; @Inject public TableMetadataSystemTable(@ForMetadata IDBI dbi, TypeManager typeManager) { this.dao = onDemandDao(dbi, MetadataDao.class); requireNonNull(typeManager, "typeManager is null"); Type arrayOfVarchar = typeManager.getType(parseTypeSignature("array<varchar>")); this.tableMetadata = new ConnectorTableMetadata( new SchemaTableName("system", "tables"), ImmutableList.of( new ColumnMetadata(SCHEMA_NAME, VARCHAR), new ColumnMetadata(TABLE_NAME, VARCHAR), new ColumnMetadata("temporal_column", VARCHAR), new ColumnMetadata("ordering_columns", arrayOfVarchar), new ColumnMetadata("distribution_name", VARCHAR), new ColumnMetadata("bucket_count", BIGINT), new ColumnMetadata("bucketing_columns", arrayOfVarchar), new ColumnMetadata("organized", BOOLEAN))); } @Override public Distribution getDistribution() { return SINGLE_COORDINATOR; } @Override public ConnectorTableMetadata getTableMetadata() { return tableMetadata; } @Override public ConnectorPageSource pageSource(ConnectorTransactionHandle transactionHandle, ConnectorSession session, TupleDomain<Integer> constraint) { return new FixedPageSource(buildPages(dao, tableMetadata, constraint)); } private static List<Page> buildPages(MetadataDao dao, ConnectorTableMetadata tableMetadata, TupleDomain<Integer> tupleDomain) { Map<Integer, NullableValue> domainValues = extractFixedValues(tupleDomain).orElse(ImmutableMap.of()); String schemaName = getStringValue(domainValues.get(getColumnIndex(tableMetadata, SCHEMA_NAME))); String tableName = getStringValue(domainValues.get(getColumnIndex(tableMetadata, TABLE_NAME))); PageListBuilder pageBuilder = new PageListBuilder(tableMetadata.getColumns().stream() .map(ColumnMetadata::getType) .collect(toList())); List<TableMetadataRow> tableRows = dao.getTableMetadataRows(schemaName, tableName); PeekingIterator<ColumnMetadataRow> columnRowIterator = peekingIterator(dao.getColumnMetadataRows(schemaName, tableName).iterator()); for (TableMetadataRow tableRow : tableRows) { while (columnRowIterator.hasNext() && columnRowIterator.peek().getTableId() < tableRow.getTableId()) { columnRowIterator.next(); } String temporalColumnName = null; SortedMap<Integer, String> sortColumnNames = new TreeMap<>(); SortedMap<Integer, String> bucketColumnNames = new TreeMap<>(); OptionalLong temporalColumnId = tableRow.getTemporalColumnId(); while (columnRowIterator.hasNext() && columnRowIterator.peek().getTableId() == tableRow.getTableId()) { ColumnMetadataRow columnRow = columnRowIterator.next(); if (temporalColumnId.isPresent() && columnRow.getColumnId() == temporalColumnId.getAsLong()) { temporalColumnName = columnRow.getColumnName(); } OptionalInt sortOrdinalPosition = columnRow.getSortOrdinalPosition(); if (sortOrdinalPosition.isPresent()) { sortColumnNames.put(sortOrdinalPosition.getAsInt(), columnRow.getColumnName()); } OptionalInt bucketOrdinalPosition = columnRow.getBucketOrdinalPosition(); if (bucketOrdinalPosition.isPresent()) { bucketColumnNames.put(bucketOrdinalPosition.getAsInt(), columnRow.getColumnName()); } } pageBuilder.beginRow(); // schema_name, table_name VARCHAR.writeSlice(pageBuilder.nextBlockBuilder(), utf8Slice(tableRow.getSchemaName())); VARCHAR.writeSlice(pageBuilder.nextBlockBuilder(), utf8Slice(tableRow.getTableName())); // temporal_column if (temporalColumnId.isPresent()) { if (temporalColumnName == null) { throw new PrestoException(RAPTOR_CORRUPT_METADATA, format("Table ID %s has corrupt metadata (invalid temporal column ID)", tableRow.getTableId())); } VARCHAR.writeSlice(pageBuilder.nextBlockBuilder(), utf8Slice(temporalColumnName)); } else { pageBuilder.nextBlockBuilder().appendNull(); } // ordering_columns writeArray(pageBuilder.nextBlockBuilder(), sortColumnNames.values()); // distribution_name Optional<String> distributionName = tableRow.getDistributionName(); if (distributionName.isPresent()) { VARCHAR.writeSlice(pageBuilder.nextBlockBuilder(), utf8Slice(distributionName.get())); } else { pageBuilder.nextBlockBuilder().appendNull(); } // bucket_count OptionalInt bucketCount = tableRow.getBucketCount(); if (bucketCount.isPresent()) { BIGINT.writeLong(pageBuilder.nextBlockBuilder(), bucketCount.getAsInt()); } else { pageBuilder.nextBlockBuilder().appendNull(); } // bucketing_columns writeArray(pageBuilder.nextBlockBuilder(), bucketColumnNames.values()); // organized BOOLEAN.writeBoolean(pageBuilder.nextBlockBuilder(), tableRow.isOrganized()); } return pageBuilder.build(); } private static void writeArray(BlockBuilder blockBuilder, Collection<String> values) { if (values.isEmpty()) { blockBuilder.appendNull(); } else { BlockBuilder array = blockBuilder.beginBlockEntry(); for (String value : values) { VARCHAR.writeSlice(array, utf8Slice(value)); } blockBuilder.closeEntry(); } } static int getColumnIndex(ConnectorTableMetadata tableMetadata, String columnName) { List<ColumnMetadata> columns = tableMetadata.getColumns(); for (int i = 0; i < columns.size(); i++) { if (columns.get(i).getName().equals(columnName)) { return i; } } throw new IllegalArgumentException(format("Column %s not found", columnName)); } static String getStringValue(NullableValue value) { if ((value == null) || value.isNull()) { return null; } return ((Slice) value.getValue()).toStringUtf8(); } }