com.facebook.presto.metadata.MetadataManager.java Source code

Java tutorial

Introduction

Here is the source code for com.facebook.presto.metadata.MetadataManager.java

Source

/*
 * 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.metadata;

import com.facebook.presto.Session;
import com.facebook.presto.block.BlockEncodingManager;
import com.facebook.presto.connector.informationSchema.InformationSchemaMetadata;
import com.facebook.presto.spi.ColumnHandle;
import com.facebook.presto.spi.ColumnMetadata;
import com.facebook.presto.spi.ConnectorInsertTableHandle;
import com.facebook.presto.spi.ConnectorMetadata;
import com.facebook.presto.spi.ConnectorOutputTableHandle;
import com.facebook.presto.spi.ConnectorPartition;
import com.facebook.presto.spi.ConnectorPartitionResult;
import com.facebook.presto.spi.ConnectorSession;
import com.facebook.presto.spi.ConnectorSplitManager;
import com.facebook.presto.spi.ConnectorTableHandle;
import com.facebook.presto.spi.ConnectorTableLayout;
import com.facebook.presto.spi.ConnectorTableLayoutResult;
import com.facebook.presto.spi.ConnectorTableMetadata;
import com.facebook.presto.spi.ConnectorViewDefinition;
import com.facebook.presto.spi.Constraint;
import com.facebook.presto.spi.PrestoException;
import com.facebook.presto.spi.SchemaTableName;
import com.facebook.presto.spi.SchemaTablePrefix;
import com.facebook.presto.spi.block.BlockEncodingSerde;
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.facebook.presto.spi.type.TypeSignature;
import com.facebook.presto.split.SplitManager;
import com.facebook.presto.sql.analyzer.FeaturesConfig;
import com.facebook.presto.sql.tree.QualifiedName;
import com.facebook.presto.type.TypeDeserializer;
import com.facebook.presto.type.TypeRegistry;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.google.common.base.Joiner;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
import io.airlift.json.JsonCodec;
import io.airlift.json.JsonCodecFactory;
import io.airlift.json.ObjectMapperProvider;
import io.airlift.slice.Slice;

import javax.inject.Inject;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.OptionalLong;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Predicate;

import static com.facebook.presto.metadata.MetadataUtil.checkCatalogName;
import static com.facebook.presto.metadata.OperatorType.BETWEEN;
import static com.facebook.presto.metadata.OperatorType.EQUAL;
import static com.facebook.presto.metadata.OperatorType.GREATER_THAN;
import static com.facebook.presto.metadata.OperatorType.GREATER_THAN_OR_EQUAL;
import static com.facebook.presto.metadata.OperatorType.HASH_CODE;
import static com.facebook.presto.metadata.OperatorType.LESS_THAN;
import static com.facebook.presto.metadata.OperatorType.LESS_THAN_OR_EQUAL;
import static com.facebook.presto.metadata.OperatorType.NOT_EQUAL;
import static com.facebook.presto.metadata.QualifiedTableName.convertFromSchemaTableName;
import static com.facebook.presto.metadata.TableLayout.fromConnectorLayout;
import static com.facebook.presto.metadata.ViewDefinition.ViewColumn;
import static com.facebook.presto.spi.StandardErrorCode.INVALID_VIEW;
import static com.facebook.presto.spi.StandardErrorCode.NOT_FOUND;
import static com.facebook.presto.spi.StandardErrorCode.SYNTAX_ERROR;
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.util.ImmutableCollectors.toImmutableList;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.Iterables.transform;
import static java.lang.String.format;
import static java.util.Locale.ENGLISH;
import static java.util.Objects.requireNonNull;

public class MetadataManager implements Metadata {
    private static final String INFORMATION_SCHEMA_NAME = "information_schema";

    private final ConcurrentMap<String, ConnectorMetadataEntry> informationSchemasByCatalog = new ConcurrentHashMap<>();
    private final ConcurrentMap<String, ConnectorMetadataEntry> systemTablesByCatalog = new ConcurrentHashMap<>();
    private final ConcurrentMap<String, ConnectorMetadataEntry> connectorsByCatalog = new ConcurrentHashMap<>();
    private final ConcurrentMap<String, ConnectorMetadataEntry> connectorsById = new ConcurrentHashMap<>();
    private final FunctionRegistry functions;
    private final TypeManager typeManager;
    private final JsonCodec<ViewDefinition> viewCodec;
    private final SplitManager splitManager;
    private final BlockEncodingSerde blockEncodingSerde;
    private final SessionPropertyManager sessionPropertyManager;
    private final TablePropertyManager tablePropertyManager;

    public MetadataManager(FeaturesConfig featuresConfig, TypeManager typeManager, SplitManager splitManager,
            BlockEncodingSerde blockEncodingSerde, SessionPropertyManager sessionPropertyManager,
            TablePropertyManager tablePropertyManager) {
        this(featuresConfig, typeManager, createTestingViewCodec(), splitManager, blockEncodingSerde,
                sessionPropertyManager, tablePropertyManager);
    }

    @Inject
    public MetadataManager(FeaturesConfig featuresConfig, TypeManager typeManager,
            JsonCodec<ViewDefinition> viewCodec, SplitManager splitManager, BlockEncodingSerde blockEncodingSerde,
            SessionPropertyManager sessionPropertyManager, TablePropertyManager tablePropertyManager) {
        functions = new FunctionRegistry(typeManager, blockEncodingSerde,
                featuresConfig.isExperimentalSyntaxEnabled());
        this.typeManager = requireNonNull(typeManager, "types is null");
        this.viewCodec = requireNonNull(viewCodec, "viewCodec is null");
        this.splitManager = requireNonNull(splitManager, "splitManager is null");
        this.blockEncodingSerde = requireNonNull(blockEncodingSerde, "blockEncodingSerde is null");
        this.sessionPropertyManager = requireNonNull(sessionPropertyManager, "sessionPropertyManager is null");
        this.tablePropertyManager = requireNonNull(tablePropertyManager, "tablePropertyManager is null");

        verifyComparableOrderableContract();
    }

    public static MetadataManager createTestMetadataManager() {
        FeaturesConfig featuresConfig = new FeaturesConfig();
        TypeManager typeManager = new TypeRegistry();
        SessionPropertyManager sessionPropertyManager = new SessionPropertyManager();
        SplitManager splitManager = new SplitManager();
        BlockEncodingSerde blockEncodingSerde = new BlockEncodingManager(typeManager);
        return new MetadataManager(featuresConfig, typeManager, splitManager, blockEncodingSerde,
                sessionPropertyManager, new TablePropertyManager());
    }

    public synchronized void addConnectorMetadata(String connectorId, String catalogName,
            ConnectorMetadata connectorMetadata) {
        checkMetadataArguments(connectorId, catalogName, connectorMetadata);
        checkArgument(!connectorsByCatalog.containsKey(catalogName), "Catalog '%s' is already registered",
                catalogName);

        ConnectorMetadataEntry connectorMetadataEntry = new ConnectorMetadataEntry(connectorId, catalogName,
                connectorMetadata);
        connectorsById.put(connectorId, connectorMetadataEntry);
        connectorsByCatalog.put(catalogName, connectorMetadataEntry);
    }

    public synchronized void addInformationSchemaMetadata(String connectorId, String catalogName,
            InformationSchemaMetadata metadata) {
        checkMetadataArguments(connectorId, catalogName, metadata);
        checkArgument(!informationSchemasByCatalog.containsKey(catalogName),
                "Information schema for catalog '%s' is already registered", catalogName);

        ConnectorMetadataEntry connectorMetadataEntry = new ConnectorMetadataEntry(connectorId, catalogName,
                metadata);
        connectorsById.put(connectorId, connectorMetadataEntry);
        informationSchemasByCatalog.put(catalogName, connectorMetadataEntry);
    }

    public synchronized void addSystemTablesMetadata(String connectorId, String catalogName,
            ConnectorMetadata metadata) {
        checkMetadataArguments(connectorId, catalogName, metadata);
        checkArgument(!systemTablesByCatalog.containsKey(catalogName),
                "System tables for catalog '%s' are already registered", catalogName);

        ConnectorMetadataEntry connectorMetadataEntry = new ConnectorMetadataEntry(connectorId, catalogName,
                metadata);
        connectorsById.put(connectorId, connectorMetadataEntry);
        systemTablesByCatalog.put(catalogName, connectorMetadataEntry);
    }

    private void checkMetadataArguments(String connectorId, String catalogName, ConnectorMetadata metadata) {
        requireNonNull(connectorId, "connectorId is null");
        requireNonNull(catalogName, "catalogName is null");
        requireNonNull(metadata, "metadata is null");
        checkArgument(!connectorsById.containsKey(connectorId), "Connector '%s' is already registered",
                connectorId);
    }

    @Override
    public final void verifyComparableOrderableContract() {
        Multimap<Type, OperatorType> missingOperators = HashMultimap.create();
        for (Type type : typeManager.getTypes()) {
            if (type.isComparable()) {
                if (!functions.canResolveOperator(HASH_CODE, BIGINT, ImmutableList.of(type))) {
                    missingOperators.put(type, HASH_CODE);
                }
                if (!functions.canResolveOperator(EQUAL, BOOLEAN, ImmutableList.of(type, type))) {
                    missingOperators.put(type, EQUAL);
                }
                if (!functions.canResolveOperator(NOT_EQUAL, BOOLEAN, ImmutableList.of(type, type))) {
                    missingOperators.put(type, NOT_EQUAL);
                }
            }
            if (type.isOrderable()) {
                for (OperatorType operator : ImmutableList.of(LESS_THAN, LESS_THAN_OR_EQUAL, GREATER_THAN,
                        GREATER_THAN_OR_EQUAL)) {
                    if (!functions.canResolveOperator(operator, BOOLEAN, ImmutableList.of(type, type))) {
                        missingOperators.put(type, operator);
                    }
                }
                if (!functions.canResolveOperator(BETWEEN, BOOLEAN, ImmutableList.of(type, type, type))) {
                    missingOperators.put(type, BETWEEN);
                }
            }
        }
        // TODO: verify the parametric types too
        if (!missingOperators.isEmpty()) {
            List<String> messages = new ArrayList<>();
            for (Type type : missingOperators.keySet()) {
                messages.add(format("%s missing for %s", missingOperators.get(type), type));
            }
            throw new IllegalStateException(Joiner.on(", ").join(messages));
        }
    }

    @Override
    public Type getType(TypeSignature signature) {
        return typeManager.getType(signature);
    }

    @Override
    public boolean isAggregationFunction(QualifiedName name) {
        return functions.isAggregationFunction(name);
    }

    @Override
    public List<SqlFunction> listFunctions() {
        return functions.list();
    }

    @Override
    public void addFunctions(List<? extends SqlFunction> functionInfos) {
        functions.addFunctions(functionInfos);
    }

    @Override
    public List<String> listSchemaNames(Session session, String catalogName) {
        checkCatalogName(catalogName);
        ImmutableSet.Builder<String> schemaNames = ImmutableSet.builder();
        for (ConnectorMetadataEntry entry : allConnectorsFor(catalogName)) {
            schemaNames.addAll(entry.getMetadata().listSchemaNames(session.toConnectorSession(entry.getCatalog())));
        }
        return ImmutableList.copyOf(schemaNames.build());
    }

    @Override
    public Optional<TableHandle> getTableHandle(Session session, QualifiedTableName table) {
        requireNonNull(table, "table is null");

        ConnectorMetadataEntry entry = getConnectorFor(table);
        if (entry != null) {
            ConnectorMetadata metadata = entry.getMetadata();

            ConnectorTableHandle tableHandle = metadata
                    .getTableHandle(session.toConnectorSession(entry.getCatalog()), table.asSchemaTableName());

            if (tableHandle != null) {
                return Optional.of(new TableHandle(entry.getConnectorId(), tableHandle));
            }
        }
        return Optional.empty();
    }

    @Override
    public List<TableLayoutResult> getLayouts(Session session, TableHandle table,
            Constraint<ColumnHandle> constraint, Optional<Set<ColumnHandle>> desiredColumns) {
        if (constraint.getSummary().isNone()) {
            return ImmutableList.of();
        }

        TupleDomain<ColumnHandle> summary = constraint.getSummary();
        String connectorId = table.getConnectorId();
        ConnectorTableHandle connectorTable = table.getConnectorHandle();
        Predicate<Map<ColumnHandle, NullableValue>> predicate = constraint.predicate();

        List<ConnectorTableLayoutResult> layouts;
        ConnectorMetadataEntry entry = getConnectorMetadata(connectorId);
        ConnectorSession connectorSession = session.toConnectorSession(entry.getCatalog());
        try {
            layouts = entry.getMetadata().getTableLayouts(connectorSession, connectorTable,
                    new Constraint<>(summary, predicate::test), desiredColumns);
        } catch (UnsupportedOperationException e) {
            ConnectorSplitManager connectorSplitManager = splitManager.getConnectorSplitManager(connectorId);
            ConnectorPartitionResult result = connectorSplitManager.getPartitions(connectorSession, connectorTable,
                    summary);

            List<ConnectorPartition> partitions = result.getPartitions().stream()
                    .filter(partition -> !partition.getTupleDomain().isNone())
                    .filter(partition -> predicate.test(extractFixedValues(partition.getTupleDomain()).get()))
                    .collect(toImmutableList());

            List<TupleDomain<ColumnHandle>> partitionDomains = partitions.stream()
                    .map(ConnectorPartition::getTupleDomain).collect(toImmutableList());

            TupleDomain<ColumnHandle> effectivePredicate = TupleDomain.none();
            if (!partitionDomains.isEmpty()) {
                effectivePredicate = TupleDomain.columnWiseUnion(partitionDomains);
            }

            ConnectorTableLayout layout = new ConnectorTableLayout(
                    new LegacyTableLayoutHandle(connectorTable, partitions), Optional.empty(), effectivePredicate,
                    Optional.empty(), Optional.of(partitionDomains), ImmutableList.of());
            layouts = ImmutableList.of(new ConnectorTableLayoutResult(layout, result.getUndeterminedTupleDomain()));
        }

        return layouts.stream()
                .map(layout -> new TableLayoutResult(fromConnectorLayout(connectorId, layout.getTableLayout()),
                        layout.getUnenforcedConstraint()))
                .collect(toImmutableList());
    }

    @Override
    public TableLayout getLayout(Session session, TableLayoutHandle handle) {
        if (handle.getConnectorHandle() instanceof LegacyTableLayoutHandle) {
            LegacyTableLayoutHandle legacyHandle = (LegacyTableLayoutHandle) handle.getConnectorHandle();
            List<TupleDomain<ColumnHandle>> partitionDomains = legacyHandle.getPartitions().stream()
                    .map(ConnectorPartition::getTupleDomain).collect(toImmutableList());

            TupleDomain<ColumnHandle> predicate = TupleDomain.none();
            if (!partitionDomains.isEmpty()) {
                predicate = TupleDomain.columnWiseUnion(partitionDomains);
            }
            return new TableLayout(handle, new ConnectorTableLayout(legacyHandle, Optional.empty(), predicate,
                    Optional.empty(), Optional.of(partitionDomains), ImmutableList.of()));
        }

        String connectorId = handle.getConnectorId();
        ConnectorMetadataEntry entry = getConnectorMetadata(connectorId);
        return fromConnectorLayout(connectorId, entry.getMetadata()
                .getTableLayout(session.toConnectorSession(entry.getCatalog()), handle.getConnectorHandle()));
    }

    @Override
    public TableMetadata getTableMetadata(Session session, TableHandle tableHandle) {
        ConnectorMetadataEntry entry = lookupConnectorFor(tableHandle);
        ConnectorTableMetadata tableMetadata = entry.getMetadata()
                .getTableMetadata(session.toConnectorSession(entry.getCatalog()), tableHandle.getConnectorHandle());

        return new TableMetadata(tableHandle.getConnectorId(), tableMetadata);
    }

    @Override
    public Map<String, ColumnHandle> getColumnHandles(Session session, TableHandle tableHandle) {
        ConnectorMetadataEntry entry = lookupConnectorFor(tableHandle);
        return entry.getMetadata().getColumnHandles(session.toConnectorSession(entry.getCatalog()),
                tableHandle.getConnectorHandle());
    }

    @Override
    public ColumnMetadata getColumnMetadata(Session session, TableHandle tableHandle, ColumnHandle columnHandle) {
        requireNonNull(tableHandle, "tableHandle is null");
        requireNonNull(columnHandle, "columnHandle is null");

        ConnectorMetadataEntry entry = lookupConnectorFor(tableHandle);
        return entry.getMetadata().getColumnMetadata(session.toConnectorSession(entry.getCatalog()),
                tableHandle.getConnectorHandle(), columnHandle);
    }

    @Override
    public List<QualifiedTableName> listTables(Session session, QualifiedTablePrefix prefix) {
        requireNonNull(prefix, "prefix is null");

        String schemaNameOrNull = prefix.getSchemaName().orElse(null);
        Set<QualifiedTableName> tables = new LinkedHashSet<>();
        for (ConnectorMetadataEntry entry : allConnectorsFor(prefix.getCatalogName())) {
            ConnectorSession connectorSession = session.toConnectorSession(entry.getCatalog());
            for (QualifiedTableName tableName : transform(
                    entry.getMetadata().listTables(connectorSession, schemaNameOrNull),
                    convertFromSchemaTableName(prefix.getCatalogName()))) {
                tables.add(tableName);
            }
        }
        return ImmutableList.copyOf(tables);
    }

    @Override
    public Optional<ColumnHandle> getSampleWeightColumnHandle(Session session, TableHandle tableHandle) {
        requireNonNull(tableHandle, "tableHandle is null");
        ConnectorMetadataEntry entry = lookupConnectorFor(tableHandle);
        ColumnHandle handle = entry.getMetadata().getSampleWeightColumnHandle(
                session.toConnectorSession(entry.getCatalog()), tableHandle.getConnectorHandle());

        return Optional.ofNullable(handle);
    }

    @Override
    public boolean canCreateSampledTables(Session session, String catalogName) {
        ConnectorMetadataEntry connectorMetadata = connectorsByCatalog.get(catalogName);
        checkArgument(connectorMetadata != null, "Catalog %s does not exist", catalogName);
        return connectorMetadata.getMetadata()
                .canCreateSampledTables(session.toConnectorSession(connectorMetadata.getCatalog()));
    }

    @Override
    public Map<QualifiedTableName, List<ColumnMetadata>> listTableColumns(Session session,
            QualifiedTablePrefix prefix) {
        requireNonNull(prefix, "prefix is null");
        SchemaTablePrefix tablePrefix = prefix.asSchemaTablePrefix();

        Map<QualifiedTableName, List<ColumnMetadata>> tableColumns = new HashMap<>();
        for (ConnectorMetadataEntry connectorMetadata : allConnectorsFor(prefix.getCatalogName())) {
            ConnectorMetadata metadata = connectorMetadata.getMetadata();

            ConnectorSession connectorSession = session.toConnectorSession(connectorMetadata.getCatalog());
            for (Entry<SchemaTableName, List<ColumnMetadata>> entry : metadata
                    .listTableColumns(connectorSession, tablePrefix).entrySet()) {
                QualifiedTableName tableName = new QualifiedTableName(prefix.getCatalogName(),
                        entry.getKey().getSchemaName(), entry.getKey().getTableName());
                tableColumns.put(tableName, entry.getValue());
            }

            // if table and view names overlap, the view wins
            for (Entry<SchemaTableName, ConnectorViewDefinition> entry : metadata
                    .getViews(connectorSession, tablePrefix).entrySet()) {
                QualifiedTableName tableName = new QualifiedTableName(prefix.getCatalogName(),
                        entry.getKey().getSchemaName(), entry.getKey().getTableName());

                ImmutableList.Builder<ColumnMetadata> columns = ImmutableList.builder();
                for (ViewColumn column : deserializeView(entry.getValue().getViewData()).getColumns()) {
                    columns.add(new ColumnMetadata(column.getName(), column.getType(), false));
                }

                tableColumns.put(tableName, columns.build());
            }
        }
        return ImmutableMap.copyOf(tableColumns);
    }

    @Override
    public void createTable(Session session, String catalogName, TableMetadata tableMetadata) {
        ConnectorMetadataEntry connectorMetadata = connectorsByCatalog.get(catalogName);
        checkArgument(connectorMetadata != null, "Catalog %s does not exist", catalogName);

        connectorMetadata.getMetadata().createTable(session.toConnectorSession(connectorMetadata.getCatalog()),
                tableMetadata.getMetadata());
    }

    @Override
    public void renameTable(Session session, TableHandle tableHandle, QualifiedTableName newTableName) {
        String catalogName = newTableName.getCatalogName();
        ConnectorMetadataEntry target = connectorsByCatalog.get(catalogName);
        if (target == null) {
            throw new PrestoException(NOT_FOUND, format("Target catalog '%s' does not exist", catalogName));
        }
        if (!tableHandle.getConnectorId().equals(target.getConnectorId())) {
            throw new PrestoException(SYNTAX_ERROR, "Cannot rename tables across catalogs");
        }

        ConnectorMetadataEntry entry = lookupConnectorFor(tableHandle);
        entry.getMetadata().renameTable(session.toConnectorSession(entry.getCatalog()),
                tableHandle.getConnectorHandle(), newTableName.asSchemaTableName());
    }

    @Override
    public void renameColumn(Session session, TableHandle tableHandle, ColumnHandle source, String target) {
        ConnectorMetadataEntry entry = lookupConnectorFor(tableHandle);
        entry.getMetadata().renameColumn(session.toConnectorSession(entry.getCatalog()),
                tableHandle.getConnectorHandle(), source, target.toLowerCase(ENGLISH));
    }

    @Override
    public void addColumn(Session session, TableHandle tableHandle, ColumnMetadata column) {
        ConnectorMetadataEntry entry = lookupConnectorFor(tableHandle);
        entry.getMetadata().addColumn(session.toConnectorSession(entry.getCatalog()),
                tableHandle.getConnectorHandle(), column);
    }

    @Override
    public void dropTable(Session session, TableHandle tableHandle) {
        ConnectorMetadataEntry entry = lookupConnectorFor(tableHandle);
        entry.getMetadata().dropTable(session.toConnectorSession(entry.getCatalog()),
                tableHandle.getConnectorHandle());
    }

    @Override
    public OutputTableHandle beginCreateTable(Session session, String catalogName, TableMetadata tableMetadata) {
        ConnectorMetadataEntry connectorMetadata = connectorsByCatalog.get(catalogName);
        checkArgument(connectorMetadata != null, "Catalog %s does not exist", catalogName);
        ConnectorSession connectorSession = session.toConnectorSession(connectorMetadata.getCatalog());
        ConnectorOutputTableHandle handle = connectorMetadata.getMetadata().beginCreateTable(connectorSession,
                tableMetadata.getMetadata());
        return new OutputTableHandle(connectorMetadata.getConnectorId(), handle);
    }

    @Override
    public void commitCreateTable(Session session, OutputTableHandle tableHandle, Collection<Slice> fragments) {
        ConnectorMetadataEntry entry = lookupConnectorFor(tableHandle);
        entry.getMetadata().commitCreateTable(session.toConnectorSession(entry.getCatalog()),
                tableHandle.getConnectorHandle(), fragments);
    }

    @Override
    public void rollbackCreateTable(Session session, OutputTableHandle tableHandle) {
        ConnectorMetadataEntry entry = lookupConnectorFor(tableHandle);
        entry.getMetadata().rollbackCreateTable(session.toConnectorSession(entry.getCatalog()),
                tableHandle.getConnectorHandle());
    }

    @Override
    public InsertTableHandle beginInsert(Session session, TableHandle tableHandle) {
        ConnectorMetadataEntry entry = lookupConnectorFor(tableHandle);
        ConnectorInsertTableHandle handle = entry.getMetadata()
                .beginInsert(session.toConnectorSession(entry.getCatalog()), tableHandle.getConnectorHandle());
        return new InsertTableHandle(tableHandle.getConnectorId(), handle);
    }

    @Override
    public void commitInsert(Session session, InsertTableHandle tableHandle, Collection<Slice> fragments) {
        ConnectorMetadataEntry entry = lookupConnectorFor(tableHandle);
        entry.getMetadata().commitInsert(session.toConnectorSession(entry.getCatalog()),
                tableHandle.getConnectorHandle(), fragments);
    }

    @Override
    public void rollbackInsert(Session session, InsertTableHandle tableHandle) {
        ConnectorMetadataEntry entry = lookupConnectorFor(tableHandle);
        entry.getMetadata().rollbackInsert(session.toConnectorSession(entry.getCatalog()),
                tableHandle.getConnectorHandle());
    }

    @Override
    public ColumnHandle getUpdateRowIdColumnHandle(Session session, TableHandle tableHandle) {
        ConnectorMetadataEntry entry = lookupConnectorFor(tableHandle);
        return entry.getMetadata().getUpdateRowIdColumnHandle(session.toConnectorSession(entry.getCatalog()),
                tableHandle.getConnectorHandle());
    }

    @Override
    public boolean supportsMetadataDelete(Session session, TableHandle tableHandle,
            TableLayoutHandle tableLayoutHandle) {
        ConnectorMetadataEntry entry = lookupConnectorFor(tableHandle);
        return entry.getMetadata().supportsMetadataDelete(session.toConnectorSession(entry.getCatalog()),
                tableHandle.getConnectorHandle(), tableLayoutHandle.getConnectorHandle());
    }

    @Override
    public OptionalLong metadataDelete(Session session, TableHandle tableHandle,
            TableLayoutHandle tableLayoutHandle) {
        ConnectorMetadataEntry entry = lookupConnectorFor(tableHandle);
        return entry.getMetadata().metadataDelete(session.toConnectorSession(entry.getCatalog()),
                tableHandle.getConnectorHandle(), tableLayoutHandle.getConnectorHandle());
    }

    @Override
    public TableHandle beginDelete(Session session, TableHandle tableHandle) {
        ConnectorMetadataEntry entry = lookupConnectorFor(tableHandle);
        ConnectorTableHandle newHandle = entry.getMetadata()
                .beginDelete(session.toConnectorSession(entry.getCatalog()), tableHandle.getConnectorHandle());
        return new TableHandle(tableHandle.getConnectorId(), newHandle);
    }

    @Override
    public void commitDelete(Session session, TableHandle tableHandle, Collection<Slice> fragments) {
        ConnectorMetadataEntry entry = lookupConnectorFor(tableHandle);
        entry.getMetadata().commitDelete(session.toConnectorSession(entry.getCatalog()),
                tableHandle.getConnectorHandle(), fragments);
    }

    @Override
    public void rollbackDelete(Session session, TableHandle tableHandle) {
        ConnectorMetadataEntry entry = lookupConnectorFor(tableHandle);
        entry.getMetadata().rollbackDelete(session.toConnectorSession(entry.getCatalog()),
                tableHandle.getConnectorHandle());
    }

    @Override
    public Map<String, String> getCatalogNames() {
        ImmutableMap.Builder<String, String> catalogsMap = ImmutableMap.builder();
        for (Map.Entry<String, ConnectorMetadataEntry> entry : connectorsByCatalog.entrySet()) {
            catalogsMap.put(entry.getKey(), entry.getValue().getConnectorId());
        }
        return catalogsMap.build();
    }

    @Override
    public List<QualifiedTableName> listViews(Session session, QualifiedTablePrefix prefix) {
        requireNonNull(prefix, "prefix is null");

        String schemaNameOrNull = prefix.getSchemaName().orElse(null);
        Set<QualifiedTableName> views = new LinkedHashSet<>();
        for (ConnectorMetadataEntry entry : allConnectorsFor(prefix.getCatalogName())) {
            ConnectorSession connectorSession = session.toConnectorSession(entry.getCatalog());
            for (QualifiedTableName tableName : transform(
                    entry.getMetadata().listViews(connectorSession, schemaNameOrNull),
                    convertFromSchemaTableName(prefix.getCatalogName()))) {
                views.add(tableName);
            }
        }
        return ImmutableList.copyOf(views);
    }

    @Override
    public Map<QualifiedTableName, ViewDefinition> getViews(Session session, QualifiedTablePrefix prefix) {
        requireNonNull(prefix, "prefix is null");
        SchemaTablePrefix tablePrefix = prefix.asSchemaTablePrefix();

        Map<QualifiedTableName, ViewDefinition> views = new LinkedHashMap<>();
        for (ConnectorMetadataEntry metadata : allConnectorsFor(prefix.getCatalogName())) {
            ConnectorSession connectorSession = session.toConnectorSession(metadata.getCatalog());
            for (Entry<SchemaTableName, ConnectorViewDefinition> entry : metadata.getMetadata()
                    .getViews(connectorSession, tablePrefix).entrySet()) {
                QualifiedTableName viewName = new QualifiedTableName(prefix.getCatalogName(),
                        entry.getKey().getSchemaName(), entry.getKey().getTableName());
                views.put(viewName, deserializeView(entry.getValue().getViewData()));
            }
        }
        return ImmutableMap.copyOf(views);
    }

    @Override
    public Optional<ViewDefinition> getView(Session session, QualifiedTableName viewName) {
        ConnectorMetadataEntry entry = getConnectorFor(viewName);
        if (entry != null) {
            ConnectorMetadata metadata = entry.getMetadata();
            Map<SchemaTableName, ConnectorViewDefinition> views = metadata.getViews(
                    session.toConnectorSession(entry.getCatalog()),
                    viewName.asSchemaTableName().toSchemaTablePrefix());
            ConnectorViewDefinition view = views.get(viewName.asSchemaTableName());
            if (view != null) {
                return Optional.of(deserializeView(view.getViewData()));
            }
        }
        return Optional.empty();
    }

    @Override
    public void createView(Session session, QualifiedTableName viewName, String viewData, boolean replace) {
        ConnectorMetadataEntry connectorMetadata = connectorsByCatalog.get(viewName.getCatalogName());
        checkArgument(connectorMetadata != null, "Catalog %s does not exist", viewName.getCatalogName());
        connectorMetadata.getMetadata().createView(session.toConnectorSession(connectorMetadata.getCatalog()),
                viewName.asSchemaTableName(), viewData, replace);
    }

    @Override
    public void dropView(Session session, QualifiedTableName viewName) {
        ConnectorMetadataEntry connectorMetadata = connectorsByCatalog.get(viewName.getCatalogName());
        checkArgument(connectorMetadata != null, "Catalog %s does not exist", viewName.getCatalogName());
        connectorMetadata.getMetadata().dropView(session.toConnectorSession(connectorMetadata.getCatalog()),
                viewName.asSchemaTableName());
    }

    @Override
    public FunctionRegistry getFunctionRegistry() {
        return functions;
    }

    @Override
    public TypeManager getTypeManager() {
        return typeManager;
    }

    @Override
    public BlockEncodingSerde getBlockEncodingSerde() {
        return blockEncodingSerde;
    }

    @Override
    public SessionPropertyManager getSessionPropertyManager() {
        return sessionPropertyManager;
    }

    @Override
    public TablePropertyManager getTablePropertyManager() {
        return tablePropertyManager;
    }

    private ViewDefinition deserializeView(String data) {
        try {
            return viewCodec.fromJson(data);
        } catch (IllegalArgumentException e) {
            throw new PrestoException(INVALID_VIEW, "Invalid view JSON: " + data, e);
        }
    }

    private List<ConnectorMetadataEntry> allConnectorsFor(String catalogName) {
        ImmutableList.Builder<ConnectorMetadataEntry> builder = ImmutableList.builder();

        ConnectorMetadataEntry entry = informationSchemasByCatalog.get(catalogName);
        if (entry != null) {
            builder.add(entry);
        }

        ConnectorMetadataEntry systemTables = systemTablesByCatalog.get(catalogName);
        if (systemTables != null) {
            builder.add(systemTables);
        }

        ConnectorMetadataEntry connector = connectorsByCatalog.get(catalogName);
        if (connector != null) {
            builder.add(connector);
        }

        return builder.build();
    }

    private ConnectorMetadataEntry getConnectorFor(QualifiedTableName name) {
        String catalog = name.getCatalogName();
        String schema = name.getSchemaName();

        if (schema.equals(INFORMATION_SCHEMA_NAME)) {
            return informationSchemasByCatalog.get(catalog);
        }

        ConnectorMetadataEntry entry = systemTablesByCatalog.get(catalog);
        if ((entry != null) && (entry.getMetadata().getTableHandle(null, name.asSchemaTableName()) != null)) {
            return entry;
        }

        return connectorsByCatalog.get(catalog);
    }

    private ConnectorMetadataEntry lookupConnectorFor(TableHandle tableHandle) {
        return getConnectorMetadata(tableHandle.getConnectorId());
    }

    private ConnectorMetadataEntry lookupConnectorFor(OutputTableHandle tableHandle) {
        return getConnectorMetadata(tableHandle.getConnectorId());
    }

    private ConnectorMetadataEntry lookupConnectorFor(InsertTableHandle tableHandle) {
        return getConnectorMetadata(tableHandle.getConnectorId());
    }

    private ConnectorMetadataEntry getConnectorMetadata(String connectorId) {
        ConnectorMetadataEntry result = connectorsById.get(connectorId);
        checkArgument(result != null, "No connector for connector ID: %s", connectorId);
        return result;
    }

    private static class ConnectorMetadataEntry {
        private final String connectorId;
        private final String catalog;
        private final ConnectorMetadata metadata;

        private ConnectorMetadataEntry(String connectorId, String catalog, ConnectorMetadata metadata) {
            this.connectorId = requireNonNull(connectorId, "connectorId is null");
            this.catalog = requireNonNull(catalog, "catalog is null");
            this.metadata = requireNonNull(metadata, "metadata is null");
        }

        private String getConnectorId() {
            return connectorId;
        }

        private String getCatalog() {
            return catalog;
        }

        private ConnectorMetadata getMetadata() {
            return metadata;
        }
    }

    private static JsonCodec<ViewDefinition> createTestingViewCodec() {
        ObjectMapperProvider provider = new ObjectMapperProvider();
        provider.setJsonDeserializers(ImmutableMap.<Class<?>, JsonDeserializer<?>>of(Type.class,
                new TypeDeserializer(new TypeRegistry())));
        return new JsonCodecFactory(provider).jsonCodec(ViewDefinition.class);
    }
}