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

io.trino.execution.CreateMaterializedViewTask Maven / Gradle / Ivy

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

import com.google.common.annotations.VisibleForTesting;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.inject.Inject;
import io.trino.Session;
import io.trino.connector.system.GlobalSystemConnector;
import io.trino.execution.querystats.PlanOptimizersStatsCollector;
import io.trino.execution.warnings.WarningCollector;
import io.trino.metadata.MaterializedViewDefinition;
import io.trino.metadata.MaterializedViewPropertyManager;
import io.trino.metadata.QualifiedObjectName;
import io.trino.metadata.ViewColumn;
import io.trino.security.AccessControl;
import io.trino.spi.TrinoException;
import io.trino.spi.connector.CatalogHandle;
import io.trino.spi.type.Type;
import io.trino.sql.PlannerContext;
import io.trino.sql.analyzer.Analysis;
import io.trino.sql.analyzer.AnalyzerFactory;
import io.trino.sql.parser.SqlParser;
import io.trino.sql.tree.CreateMaterializedView;
import io.trino.sql.tree.Expression;
import io.trino.sql.tree.NodeRef;
import io.trino.sql.tree.Parameter;

import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;

import static com.google.common.base.Verify.verify;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.common.util.concurrent.Futures.immediateVoidFuture;
import static io.trino.execution.ParameterExtractor.bindParameters;
import static io.trino.metadata.MetadataUtil.createQualifiedObjectName;
import static io.trino.metadata.MetadataUtil.getRequiredCatalogHandle;
import static io.trino.spi.StandardErrorCode.NOT_SUPPORTED;
import static io.trino.spi.StandardErrorCode.TYPE_MISMATCH;
import static io.trino.spi.connector.ConnectorCapabilities.MATERIALIZED_VIEW_GRACE_PERIOD;
import static io.trino.sql.SqlFormatterUtil.getFormattedSql;
import static io.trino.sql.analyzer.SemanticExceptions.semanticException;
import static io.trino.sql.planner.ExpressionInterpreter.evaluateConstantExpression;
import static io.trino.type.IntervalDayTimeType.INTERVAL_DAY_TIME;
import static java.util.Locale.ENGLISH;
import static java.util.Objects.requireNonNull;

public class CreateMaterializedViewTask
        implements DataDefinitionTask
{
    private final PlannerContext plannerContext;
    private final AccessControl accessControl;
    private final SqlParser sqlParser;
    private final AnalyzerFactory analyzerFactory;
    private final MaterializedViewPropertyManager materializedViewPropertyManager;

    @Inject
    public CreateMaterializedViewTask(
            PlannerContext plannerContext,
            AccessControl accessControl,
            SqlParser sqlParser,
            AnalyzerFactory analyzerFactory,
            MaterializedViewPropertyManager materializedViewPropertyManager)
    {
        this.plannerContext = requireNonNull(plannerContext, "plannerContext is null");
        this.accessControl = requireNonNull(accessControl, "accessControl is null");
        this.sqlParser = requireNonNull(sqlParser, "sqlParser is null");
        this.analyzerFactory = requireNonNull(analyzerFactory, "analyzerFactory is null");
        this.materializedViewPropertyManager = requireNonNull(materializedViewPropertyManager, "materializedViewPropertyManager is null");
    }

    @Override
    public String getName()
    {
        return "CREATE MATERIALIZED VIEW";
    }

    @Override
    public ListenableFuture execute(CreateMaterializedView statement, QueryStateMachine stateMachine, List parameters, WarningCollector warningCollector)
    {
        Analysis analysis = executeInternal(statement, stateMachine.getSession(), parameters, warningCollector, stateMachine.getPlanOptimizersStatsCollector());
        stateMachine.setOutput(analysis.getTarget());
        stateMachine.setReferencedTables(analysis.getReferencedTables());
        return immediateVoidFuture();
    }

    @VisibleForTesting
    Analysis executeInternal(
            CreateMaterializedView statement,
            Session session,
            List parameters,
            WarningCollector warningCollector,
            PlanOptimizersStatsCollector planOptimizersStatsCollector)
    {
        QualifiedObjectName name = createQualifiedObjectName(session, statement, statement.getName());
        Map, Expression> parameterLookup = bindParameters(statement, parameters);

        String sql = getFormattedSql(statement.getQuery(), sqlParser);

        Analysis analysis = analyzerFactory.createAnalyzer(session, parameters, parameterLookup, warningCollector, planOptimizersStatsCollector)
                .analyze(statement);

        List columns = analysis.getOutputDescriptor(statement.getQuery())
                .getVisibleFields().stream()
                .map(field -> new ViewColumn(field.getName().get(), field.getType().getTypeId(), Optional.empty()))
                .collect(toImmutableList());

        String catalogName = name.getCatalogName();
        CatalogHandle catalogHandle = getRequiredCatalogHandle(plannerContext.getMetadata(), session, statement, catalogName);

        Map properties = materializedViewPropertyManager.getProperties(
                catalogName,
                catalogHandle,
                statement.getProperties(),
                session,
                plannerContext,
                accessControl,
                parameterLookup,
                true);

        Optional gracePeriod = statement.getGracePeriod()
                .map(expression -> {
                    if (!plannerContext.getMetadata().getConnectorCapabilities(session, catalogHandle).contains(MATERIALIZED_VIEW_GRACE_PERIOD)) {
                        throw semanticException(NOT_SUPPORTED, statement, "Catalog '%s' does not support GRACE PERIOD", catalogName);
                    }

                    Type type = analysis.getType(expression);
                    if (type != INTERVAL_DAY_TIME) {
                        throw new TrinoException(TYPE_MISMATCH, "Unsupported grace period type %s, expected %s".formatted(type.getDisplayName(), INTERVAL_DAY_TIME.getDisplayName()));
                    }
                    Long milliseconds = (Long) evaluateConstantExpression(
                            expression,
                            analysis.getCoercions(),
                            analysis.getTypeOnlyCoercions(),
                            plannerContext,
                            session,
                            accessControl,
                            analysis.getColumnReferences(),
                            parameterLookup);
                    // Sanity check. Impossible per grammar.
                    verify(milliseconds != null, "Grace period cannot be null");
                    return Duration.ofMillis(milliseconds);
                });

        MaterializedViewDefinition definition = new MaterializedViewDefinition(
                sql,
                session.getCatalog(),
                session.getSchema(),
                columns,
                gracePeriod,
                statement.getComment(),
                session.getIdentity(),
                session.getPath().getPath().stream()
                        // system path elements are not stored
                        .filter(element -> !element.getCatalogName().equals(GlobalSystemConnector.NAME))
                        .collect(toImmutableList()),
                Optional.empty());

        Set specifiedPropertyKeys = statement.getProperties().stream()
                // property names are case-insensitive and normalized to lower case
                .map(property -> property.getName().getValue().toLowerCase(ENGLISH))
                .collect(toImmutableSet());
        Map explicitlySetProperties = properties.keySet().stream()
                .peek(key -> verify(key.equals(key.toLowerCase(ENGLISH)), "Property name '%s' not in lower-case", key))
                .filter(specifiedPropertyKeys::contains)
                .collect(toImmutableMap(Function.identity(), properties::get));
        accessControl.checkCanCreateMaterializedView(session.toSecurityContext(), name, explicitlySetProperties);
        plannerContext.getMetadata().createMaterializedView(session, name, definition, properties, statement.isReplace(), statement.isNotExists());
        return analysis;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy