All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.trino.connector.ConnectorServices Maven / Gradle / Ivy

There is a newer version: 465
Show newest version
/*
 * 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 io.trino.connector;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import io.airlift.log.Logger;
import io.opentelemetry.api.trace.Tracer;
import io.trino.metadata.CatalogMetadata.SecurityManagement;
import io.trino.metadata.CatalogProcedures;
import io.trino.metadata.CatalogTableFunctions;
import io.trino.metadata.CatalogTableProcedures;
import io.trino.spi.classloader.ThreadContextClassLoader;
import io.trino.spi.connector.CatalogHandle;
import io.trino.spi.connector.Connector;
import io.trino.spi.connector.ConnectorAccessControl;
import io.trino.spi.connector.ConnectorCapabilities;
import io.trino.spi.connector.ConnectorIndexProvider;
import io.trino.spi.connector.ConnectorNodePartitioningProvider;
import io.trino.spi.connector.ConnectorPageSinkProvider;
import io.trino.spi.connector.ConnectorPageSourceProvider;
import io.trino.spi.connector.ConnectorRecordSetProvider;
import io.trino.spi.connector.ConnectorSecurityContext;
import io.trino.spi.connector.ConnectorSplitManager;
import io.trino.spi.connector.SchemaRoutineName;
import io.trino.spi.connector.SystemTable;
import io.trino.spi.connector.TableProcedureMetadata;
import io.trino.spi.eventlistener.EventListener;
import io.trino.spi.function.FunctionKind;
import io.trino.spi.function.FunctionProvider;
import io.trino.spi.function.table.ArgumentSpecification;
import io.trino.spi.function.table.ConnectorTableFunction;
import io.trino.spi.function.table.ReturnTypeSpecification.DescribedTable;
import io.trino.spi.function.table.TableArgumentSpecification;
import io.trino.spi.procedure.Procedure;
import io.trino.spi.security.TrinoPrincipal;
import io.trino.spi.session.PropertyMetadata;
import io.trino.split.RecordPageSourceProvider;

import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Verify.verify;
import static io.trino.metadata.CatalogMetadata.SecurityManagement.CONNECTOR;
import static io.trino.metadata.CatalogMetadata.SecurityManagement.SYSTEM;
import static java.lang.String.format;
import static java.util.Objects.requireNonNull;

public class ConnectorServices
{
    private static final Logger log = Logger.get(ConnectorServices.class);

    private final Tracer tracer;
    private final CatalogHandle catalogHandle;
    private final Connector connector;
    private final Set systemTables;
    private final CatalogProcedures procedures;
    private final CatalogTableProcedures tableProcedures;
    private final Optional functionProvider;
    private final CatalogTableFunctions tableFunctions;
    private final Optional splitManager;
    private final Optional pageSourceProvider;
    private final Optional pageSinkProvider;
    private final Optional indexProvider;
    private final Optional partitioningProvider;
    private final Optional accessControl;
    private final List eventListeners;
    private final Map> sessionProperties;
    private final Map> tableProperties;
    private final Map> viewProperties;
    private final Map> materializedViewProperties;
    private final Map> schemaProperties;
    private final Map> columnProperties;
    private final Map> analyzeProperties;
    private final Set capabilities;

    private final AtomicBoolean shutdown = new AtomicBoolean();

    public ConnectorServices(Tracer tracer, CatalogHandle catalogHandle, Connector connector)
    {
        this.tracer = requireNonNull(tracer, "tracer is null");
        this.catalogHandle = requireNonNull(catalogHandle, "catalogHandle is null");
        this.connector = requireNonNull(connector, "connector is null");

        Set systemTables = connector.getSystemTables();
        requireNonNull(systemTables, format("Connector '%s' returned a null system tables set", catalogHandle));
        this.systemTables = ImmutableSet.copyOf(systemTables);

        Set procedures = connector.getProcedures();
        requireNonNull(procedures, format("Connector '%s' returned a null procedures set", catalogHandle));
        this.procedures = new CatalogProcedures(procedures);

        Set tableProcedures = connector.getTableProcedures();
        requireNonNull(procedures, format("Connector '%s' returned a null table procedures set", catalogHandle));
        this.tableProcedures = new CatalogTableProcedures(tableProcedures);

        this.functionProvider = requireNonNull(connector.getFunctionProvider(), format("Connector '%s' returned a null function provider", catalogHandle));

        Set tableFunctions = connector.getTableFunctions();
        requireNonNull(tableFunctions, format("Connector '%s' returned a null table functions set", catalogHandle));
        this.tableFunctions = new CatalogTableFunctions(tableFunctions);
        // TODO ConnectorTableFunction should be converted to a metadata class (and a separate analysis interface) which performs this validation in the constructor
        tableFunctions.forEach(ConnectorServices::validateTableFunction);

        ConnectorSplitManager splitManager = null;
        try {
            splitManager = connector.getSplitManager();
        }
        catch (UnsupportedOperationException ignored) {
        }
        this.splitManager = Optional.ofNullable(splitManager);

        ConnectorPageSourceProvider connectorPageSourceProvider = null;
        try {
            connectorPageSourceProvider = connector.getPageSourceProvider();
            requireNonNull(connectorPageSourceProvider, format("Connector '%s' returned a null page source provider", catalogHandle));
        }
        catch (UnsupportedOperationException ignored) {
        }

        try {
            ConnectorRecordSetProvider connectorRecordSetProvider = connector.getRecordSetProvider();
            requireNonNull(connectorRecordSetProvider, format("Connector '%s' returned a null record set provider", catalogHandle));
            verify(connectorPageSourceProvider == null, "Connector '%s' returned both page source and record set providers", catalogHandle);
            connectorPageSourceProvider = new RecordPageSourceProvider(connectorRecordSetProvider);
        }
        catch (UnsupportedOperationException ignored) {
        }
        this.pageSourceProvider = Optional.ofNullable(connectorPageSourceProvider);

        ConnectorPageSinkProvider connectorPageSinkProvider = null;
        try {
            connectorPageSinkProvider = connector.getPageSinkProvider();
            requireNonNull(connectorPageSinkProvider, format("Connector '%s' returned a null page sink provider", catalogHandle));
        }
        catch (UnsupportedOperationException ignored) {
        }
        this.pageSinkProvider = Optional.ofNullable(connectorPageSinkProvider);

        ConnectorIndexProvider indexProvider = null;
        try {
            indexProvider = connector.getIndexProvider();
            requireNonNull(indexProvider, format("Connector '%s' returned a null index provider", catalogHandle));
        }
        catch (UnsupportedOperationException ignored) {
        }
        this.indexProvider = Optional.ofNullable(indexProvider);

        ConnectorNodePartitioningProvider partitioningProvider = null;
        try {
            partitioningProvider = connector.getNodePartitioningProvider();
            requireNonNull(partitioningProvider, format("Connector '%s' returned a null partitioning provider", catalogHandle));
        }
        catch (UnsupportedOperationException ignored) {
        }
        this.partitioningProvider = Optional.ofNullable(partitioningProvider);

        ConnectorAccessControl accessControl = null;
        try {
            accessControl = connector.getAccessControl();
        }
        catch (UnsupportedOperationException ignored) {
        }
        verifyAccessControl(accessControl);
        this.accessControl = Optional.ofNullable(accessControl);

        Iterable eventListeners = connector.getEventListeners();
        requireNonNull(eventListeners, format("Connector '%s' returned a null event listeners iterable", eventListeners));
        this.eventListeners = ImmutableList.copyOf(eventListeners);

        List> sessionProperties = connector.getSessionProperties();
        requireNonNull(sessionProperties, format("Connector '%s' returned a null system properties set", catalogHandle));
        this.sessionProperties = Maps.uniqueIndex(sessionProperties, PropertyMetadata::getName);

        List> tableProperties = connector.getTableProperties();
        requireNonNull(tableProperties, format("Connector '%s' returned a null table properties set", catalogHandle));
        this.tableProperties = Maps.uniqueIndex(tableProperties, PropertyMetadata::getName);

        List> viewProperties = connector.getViewProperties();
        requireNonNull(viewProperties, format("Connector '%s' returned a null view properties set", catalogHandle));
        this.viewProperties = Maps.uniqueIndex(viewProperties, PropertyMetadata::getName);

        List> materializedViewProperties = connector.getMaterializedViewProperties();
        requireNonNull(materializedViewProperties, format("Connector '%s' returned a null materialized view properties set", catalogHandle));
        this.materializedViewProperties = Maps.uniqueIndex(materializedViewProperties, PropertyMetadata::getName);

        List> schemaProperties = connector.getSchemaProperties();
        requireNonNull(schemaProperties, format("Connector '%s' returned a null schema properties set", catalogHandle));
        this.schemaProperties = Maps.uniqueIndex(schemaProperties, PropertyMetadata::getName);

        List> columnProperties = connector.getColumnProperties();
        requireNonNull(columnProperties, format("Connector '%s' returned a null column properties set", catalogHandle));
        this.columnProperties = Maps.uniqueIndex(columnProperties, PropertyMetadata::getName);

        List> analyzeProperties = connector.getAnalyzeProperties();
        requireNonNull(analyzeProperties, format("Connector '%s' returned a null analyze properties set", catalogHandle));
        this.analyzeProperties = Maps.uniqueIndex(analyzeProperties, PropertyMetadata::getName);

        Set capabilities = connector.getCapabilities();
        requireNonNull(capabilities, format("Connector '%s' returned a null capabilities set", catalogHandle));
        this.capabilities = capabilities;
    }

    public Tracer getTracer()
    {
        return tracer;
    }

    public CatalogHandle getCatalogHandle()
    {
        return catalogHandle;
    }

    public Connector getConnector()
    {
        return connector;
    }

    public Set getSystemTables()
    {
        return systemTables;
    }

    public CatalogProcedures getProcedures()
    {
        return procedures;
    }

    public CatalogTableProcedures getTableProcedures()
    {
        return tableProcedures;
    }

    public FunctionProvider getFunctionProvider()
    {
        checkArgument(functionProvider.isPresent(), "Connector '%s' does not have functions", catalogHandle);
        return functionProvider.get();
    }

    public CatalogTableFunctions getTableFunctions()
    {
        return tableFunctions;
    }

    public Optional getSplitManager()
    {
        return splitManager;
    }

    public Optional getPageSourceProvider()
    {
        return pageSourceProvider;
    }

    public Optional getPageSinkProvider()
    {
        return pageSinkProvider;
    }

    public Optional getIndexProvider()
    {
        return indexProvider;
    }

    public Optional getPartitioningProvider()
    {
        return partitioningProvider;
    }

    public SecurityManagement getSecurityManagement()
    {
        return accessControl.isPresent() ? CONNECTOR : SYSTEM;
    }

    public Optional getAccessControl()
    {
        return accessControl;
    }

    public List getEventListeners()
    {
        return eventListeners;
    }

    public Map> getSessionProperties()
    {
        return sessionProperties;
    }

    public Map> getTableProperties()
    {
        return tableProperties;
    }

    public Map> getViewProperties()
    {
        return viewProperties;
    }

    public Map> getMaterializedViewProperties()
    {
        return materializedViewProperties;
    }

    public Map> getColumnProperties()
    {
        return columnProperties;
    }

    public Map> getSchemaProperties()
    {
        return schemaProperties;
    }

    public Map> getAnalyzeProperties()
    {
        return analyzeProperties;
    }

    public Set getCapabilities()
    {
        return capabilities;
    }

    public void shutdown()
    {
        if (!shutdown.compareAndSet(false, true)) {
            return;
        }

        try (ThreadContextClassLoader ignored = new ThreadContextClassLoader(connector.getClass().getClassLoader())) {
            connector.shutdown();
        }
        catch (Throwable t) {
            log.error(t, "Error shutting down catalog: %s", catalogHandle);
        }
    }

    private static void validateTableFunction(ConnectorTableFunction tableFunction)
    {
        requireNonNull(tableFunction, "tableFunction is null");
        requireNonNull(tableFunction.getName(), "table function name is null");
        requireNonNull(tableFunction.getSchema(), "table function schema name is null");
        requireNonNull(tableFunction.getArguments(), "table function arguments is null");
        requireNonNull(tableFunction.getReturnTypeSpecification(), "table function returnTypeSpecification is null");

        checkArgument(!tableFunction.getName().isEmpty(), "table function name is empty");
        checkArgument(!tableFunction.getSchema().isEmpty(), "table function schema name is empty");

        Set argumentNames = new HashSet<>();
        for (ArgumentSpecification specification : tableFunction.getArguments()) {
            if (!argumentNames.add(specification.getName())) {
                throw new IllegalArgumentException("duplicate argument name: " + specification.getName());
            }
        }
        long tableArgumentsWithRowSemantics = tableFunction.getArguments().stream()
                .filter(TableArgumentSpecification.class::isInstance)
                .map(TableArgumentSpecification.class::cast)
                .filter(TableArgumentSpecification::isRowSemantics)
                .count();
        checkArgument(tableArgumentsWithRowSemantics <= 1, "more than one table argument with row semantics");
        // The 'keep when empty' or 'prune when empty' property must not be explicitly specified for a table argument with row semantics.
        // Such a table argument is implicitly 'prune when empty'. The TableArgumentSpecification.Builder enforces the 'prune when empty' property
        // for a table argument with row semantics.

        if (tableFunction.getReturnTypeSpecification() instanceof DescribedTable describedTable) {
            checkArgument(describedTable.getDescriptor().isTyped(), "field types missing in returned type specification");
        }
    }

    private static void verifyAccessControl(ConnectorAccessControl accessControl)
    {
        if (accessControl != null) {
            mustNotDeclareMethod(accessControl.getClass(), "checkCanExecuteFunction", ConnectorSecurityContext.class, FunctionKind.class, SchemaRoutineName.class);
            mustNotDeclareMethod(accessControl.getClass(), "checkCanGrantExecuteFunctionPrivilege", ConnectorSecurityContext.class, FunctionKind.class, SchemaRoutineName.class, TrinoPrincipal.class, boolean.class);
        }
    }

    private static void mustNotDeclareMethod(Class clazz, String name, Class... parameterTypes)
    {
        try {
            clazz.getMethod(name, parameterTypes);
            throw new IllegalArgumentException(format("Access control %s must not implement removed method %s(%s)",
                    clazz.getName(),
                    name, Arrays.stream(parameterTypes).map(Class::getName).collect(Collectors.joining(", "))));
        }
        catch (ReflectiveOperationException ignored) {
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy