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

io.trino.plugin.hive.projection.PartitionProjectionProperties 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.plugin.hive.projection;

import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import io.trino.metastore.Column;
import io.trino.metastore.Table;
import io.trino.spi.connector.ColumnMetadata;
import io.trino.spi.connector.ConnectorTableMetadata;
import io.trino.spi.type.Type;
import io.trino.spi.type.TypeManager;

import java.time.temporal.ChronoUnit;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static io.trino.plugin.hive.HiveTableProperties.getPartitionedBy;
import static io.trino.plugin.hive.HiveTimestampPrecision.DEFAULT_PRECISION;
import static io.trino.plugin.hive.util.HiveTypeUtil.getType;
import static java.lang.Boolean.parseBoolean;
import static java.lang.String.format;
import static java.util.Locale.ROOT;

public final class PartitionProjectionProperties
{
    private static final String COLUMN_PROJECTION_TYPE_SUFFIX = "type";
    private static final String COLUMN_PROJECTION_VALUES_SUFFIX = "values";
    private static final String COLUMN_PROJECTION_RANGE_SUFFIX = "range";
    private static final String COLUMN_PROJECTION_INTERVAL_SUFFIX = "interval";
    private static final String COLUMN_PROJECTION_DIGITS_SUFFIX = "digits";
    private static final String COLUMN_PROJECTION_FORMAT_SUFFIX = "format";
    private static final String METASTORE_PROPERTY_PROJECTION_INTERVAL_UNIT_SUFFIX = "interval.unit";
    private static final String METASTORE_PROPERTY_PROJECTION_ENABLED = "projection.enabled";
    private static final String METASTORE_PROPERTY_PROJECTION_LOCATION_TEMPLATE = "storage.location.template";
    private static final String METASTORE_PROPERTY_PROJECTION_IGNORE = "trino.partition_projection.ignore";
    private static final String PROPERTY_KEY_PREFIX = "partition_projection_";

    public static final String COLUMN_PROJECTION_FORMAT = PROPERTY_KEY_PREFIX + COLUMN_PROJECTION_FORMAT_SUFFIX;
    public static final String COLUMN_PROJECTION_DIGITS = PROPERTY_KEY_PREFIX + COLUMN_PROJECTION_DIGITS_SUFFIX;
    public static final String COLUMN_PROJECTION_INTERVAL_UNIT = PROPERTY_KEY_PREFIX + "interval_unit";
    public static final String COLUMN_PROJECTION_INTERVAL = PROPERTY_KEY_PREFIX + COLUMN_PROJECTION_INTERVAL_SUFFIX;
    public static final String COLUMN_PROJECTION_RANGE = PROPERTY_KEY_PREFIX + COLUMN_PROJECTION_RANGE_SUFFIX;
    public static final String COLUMN_PROJECTION_VALUES = PROPERTY_KEY_PREFIX + COLUMN_PROJECTION_VALUES_SUFFIX;
    public static final String COLUMN_PROJECTION_TYPE = PROPERTY_KEY_PREFIX + COLUMN_PROJECTION_TYPE_SUFFIX;
    public static final String PARTITION_PROJECTION_IGNORE = PROPERTY_KEY_PREFIX + "ignore";
    public static final String PARTITION_PROJECTION_LOCATION_TEMPLATE = PROPERTY_KEY_PREFIX + "location_template";
    public static final String PARTITION_PROJECTION_ENABLED = PROPERTY_KEY_PREFIX + "enabled";

    private PartitionProjectionProperties() {}

    public static Map getPartitionProjectionTrinoTableProperties(Table table)
    {
        Map metastoreTableProperties = table.getParameters();
        ImmutableMap.Builder trinoTablePropertiesBuilder = ImmutableMap.builder();

        String ignore = metastoreTableProperties.get(METASTORE_PROPERTY_PROJECTION_IGNORE);
        if (ignore != null) {
            trinoTablePropertiesBuilder.put(PARTITION_PROJECTION_IGNORE, Boolean.valueOf(ignore));
        }

        String enabled = metastoreTableProperties.get(METASTORE_PROPERTY_PROJECTION_ENABLED);
        if (enabled != null) {
            trinoTablePropertiesBuilder.put(PARTITION_PROJECTION_ENABLED, Boolean.valueOf(enabled));
        }

        String locationTemplate = metastoreTableProperties.get(METASTORE_PROPERTY_PROJECTION_LOCATION_TEMPLATE);
        if (locationTemplate != null) {
            trinoTablePropertiesBuilder.put(PARTITION_PROJECTION_LOCATION_TEMPLATE, locationTemplate);
        }

        return trinoTablePropertiesBuilder.buildOrThrow();
    }

    public static Map getPartitionProjectionTrinoColumnProperties(Table table, String columnName)
    {
        Map metastoreTableProperties = table.getParameters();
        return rewriteColumnProjectionProperties(metastoreTableProperties, columnName);
    }

    public static Map getPartitionProjectionHiveTableProperties(ConnectorTableMetadata tableMetadata)
    {
        ImmutableMap.Builder metastoreTablePropertiesBuilder = ImmutableMap.builder();
        // Handle Table Properties
        Map trinoTableProperties = tableMetadata.getProperties();

        Object ignore = trinoTableProperties.get(PARTITION_PROJECTION_IGNORE);
        if (ignore != null) {
            metastoreTablePropertiesBuilder.put(METASTORE_PROPERTY_PROJECTION_IGNORE, ignore.toString().toLowerCase(ROOT));
        }

        Object enabled = trinoTableProperties.get(PARTITION_PROJECTION_ENABLED);
        if (enabled != null) {
            metastoreTablePropertiesBuilder.put(METASTORE_PROPERTY_PROJECTION_ENABLED, enabled.toString().toLowerCase(ROOT));
        }

        Object locationTemplate = trinoTableProperties.get(PARTITION_PROJECTION_LOCATION_TEMPLATE);
        if (locationTemplate != null) {
            metastoreTablePropertiesBuilder.put(METASTORE_PROPERTY_PROJECTION_LOCATION_TEMPLATE, locationTemplate.toString());
        }

        // Handle Column Properties
        tableMetadata.getColumns().stream()
                .filter(columnMetadata -> !columnMetadata.getProperties().isEmpty())
                .forEach(columnMetadata -> {
                    Map columnProperties = columnMetadata.getProperties();
                    String columnName = columnMetadata.getName();

                    if (columnProperties.get(COLUMN_PROJECTION_TYPE) instanceof ProjectionType projectionType) {
                        metastoreTablePropertiesBuilder.put(getMetastoreProjectionPropertyKey(columnName, COLUMN_PROJECTION_TYPE_SUFFIX), projectionType.name().toLowerCase(ROOT));
                    }

                    if (columnProperties.get(COLUMN_PROJECTION_VALUES) instanceof List values) {
                        metastoreTablePropertiesBuilder.put(getMetastoreProjectionPropertyKey(columnName, COLUMN_PROJECTION_VALUES_SUFFIX), Joiner.on(",").join(values));
                    }

                    if (columnProperties.get(COLUMN_PROJECTION_RANGE) instanceof List range) {
                        metastoreTablePropertiesBuilder.put(getMetastoreProjectionPropertyKey(columnName, COLUMN_PROJECTION_RANGE_SUFFIX), Joiner.on(",").join(range));
                    }

                    if (columnProperties.get(COLUMN_PROJECTION_INTERVAL) instanceof Integer interval) {
                        metastoreTablePropertiesBuilder.put(getMetastoreProjectionPropertyKey(columnName, COLUMN_PROJECTION_INTERVAL_SUFFIX), interval.toString());
                    }

                    if (columnProperties.get(COLUMN_PROJECTION_INTERVAL_UNIT) instanceof ChronoUnit intervalUnit) {
                        metastoreTablePropertiesBuilder.put(getMetastoreProjectionPropertyKey(columnName, METASTORE_PROPERTY_PROJECTION_INTERVAL_UNIT_SUFFIX), intervalUnit.name().toLowerCase(ROOT));
                    }

                    if (columnProperties.get(COLUMN_PROJECTION_DIGITS) instanceof Integer digits) {
                        metastoreTablePropertiesBuilder.put(getMetastoreProjectionPropertyKey(columnName, COLUMN_PROJECTION_DIGITS_SUFFIX), digits.toString());
                    }

                    if (columnProperties.get(COLUMN_PROJECTION_FORMAT) instanceof String format) {
                        metastoreTablePropertiesBuilder.put(getMetastoreProjectionPropertyKey(columnName, COLUMN_PROJECTION_FORMAT_SUFFIX), format);
                    }
                });

        // We initialize partition projection to validate properties.
        Map metastoreTableProperties = metastoreTablePropertiesBuilder.buildOrThrow();
        Set partitionColumnNames = ImmutableSet.copyOf(getPartitionedBy(tableMetadata.getProperties()));
        createPartitionProjection(
                tableMetadata.getColumns()
                        .stream()
                        .map(ColumnMetadata::getName)
                        .filter(name -> !partitionColumnNames.contains(name))
                        .collect(toImmutableList()),
                tableMetadata.getColumns().stream()
                        .filter(columnMetadata -> partitionColumnNames.contains(columnMetadata.getName()))
                        .collect(toImmutableMap(ColumnMetadata::getName, ColumnMetadata::getType)),
                metastoreTableProperties);

        return metastoreTableProperties;
    }

    public static boolean arePartitionProjectionPropertiesSet(ConnectorTableMetadata tableMetadata)
    {
        if (tableMetadata.getProperties().keySet().stream()
                .anyMatch(propertyKey -> propertyKey.startsWith(PROPERTY_KEY_PREFIX))) {
            return true;
        }
        return tableMetadata.getColumns().stream()
                .map(columnMetadata -> columnMetadata.getProperties().keySet())
                .flatMap(Set::stream)
                .anyMatch(propertyKey -> propertyKey.startsWith(PROPERTY_KEY_PREFIX));
    }

    public static Optional getPartitionProjectionFromTable(Table table, TypeManager typeManager)
    {
        Map tableProperties = table.getParameters();
        if (parseBoolean(tableProperties.get(METASTORE_PROPERTY_PROJECTION_IGNORE)) ||
                !parseBoolean(tableProperties.get(METASTORE_PROPERTY_PROJECTION_ENABLED))) {
            return Optional.empty();
        }

        Set partitionColumnNames = table.getPartitionColumns().stream().map(Column::getName).collect(Collectors.toSet());
        return createPartitionProjection(
                table.getDataColumns().stream()
                        .map(Column::getName)
                        .filter(partitionColumnNames::contains)
                        .collect(toImmutableList()),
                table.getPartitionColumns().stream()
                        .collect(toImmutableMap(Column::getName, column -> getType(column.getType(), typeManager, DEFAULT_PRECISION))),
                tableProperties);
    }

    private static Optional createPartitionProjection(List dataColumns, Map partitionColumns, Map tableProperties)
    {
        // This method is used during table creation to validate the properties. The validation is performed even if the projection is disabled.
        boolean enabled = parseBoolean(tableProperties.get(METASTORE_PROPERTY_PROJECTION_ENABLED));

        if (!tableProperties.containsKey(METASTORE_PROPERTY_PROJECTION_ENABLED) &&
                partitionColumns.keySet().stream().anyMatch(columnName -> !rewriteColumnProjectionProperties(tableProperties, columnName).isEmpty())) {
            throw new InvalidProjectionException("Columns partition projection properties cannot be set when '%s' is not set".formatted(PARTITION_PROJECTION_ENABLED));
        }

        if (enabled && partitionColumns.isEmpty()) {
            throw new InvalidProjectionException("Partition projection cannot be enabled on a table that is not partitioned");
        }

        for (String columnName : dataColumns) {
            if (!rewriteColumnProjectionProperties(tableProperties, columnName).isEmpty()) {
                throw new InvalidProjectionException("Partition projection cannot be defined for non-partition column: '" + columnName + "'");
            }
        }

        Map columnProjections = new HashMap<>();
        partitionColumns.forEach((columnName, type) -> {
            Map columnProperties = rewriteColumnProjectionProperties(tableProperties, columnName);
            if (enabled) {
                columnProjections.put(columnName, parseColumnProjection(columnName, type, columnProperties));
            }
        });

        Optional storageLocationTemplate = Optional.ofNullable(tableProperties.get(METASTORE_PROPERTY_PROJECTION_LOCATION_TEMPLATE));
        storageLocationTemplate.ifPresent(locationTemplate -> {
            for (String columnName : partitionColumns.keySet()) {
                if (!locationTemplate.contains("${" + columnName + "}")) {
                    throw new InvalidProjectionException(format("Partition projection location template: %s is missing partition column: '%s' placeholder", locationTemplate, columnName));
                }
            }
        });

        if (!enabled) {
            return Optional.empty();
        }
        return Optional.of(new PartitionProjection(storageLocationTemplate, columnProjections));
    }

    private static Map rewriteColumnProjectionProperties(Map metastoreTableProperties, String columnName)
    {
        ImmutableMap.Builder trinoTablePropertiesBuilder = ImmutableMap.builder();

        String type = metastoreTableProperties.get(getMetastoreProjectionPropertyKey(columnName, COLUMN_PROJECTION_TYPE_SUFFIX));
        if (type != null) {
            trinoTablePropertiesBuilder.put(COLUMN_PROJECTION_TYPE, ProjectionType.valueOf(type.toUpperCase(ROOT)));
        }

        String values = metastoreTableProperties.get(getMetastoreProjectionPropertyKey(columnName, COLUMN_PROJECTION_VALUES_SUFFIX));
        if (values != null) {
            trinoTablePropertiesBuilder.put(COLUMN_PROJECTION_VALUES, splitCommaSeparatedString(values));
        }

        String range = metastoreTableProperties.get(getMetastoreProjectionPropertyKey(columnName, COLUMN_PROJECTION_RANGE_SUFFIX));
        if (range != null) {
            trinoTablePropertiesBuilder.put(COLUMN_PROJECTION_RANGE, splitCommaSeparatedString(range));
        }

        String interval = metastoreTableProperties.get(getMetastoreProjectionPropertyKey(columnName, COLUMN_PROJECTION_INTERVAL_SUFFIX));
        if (interval != null) {
            trinoTablePropertiesBuilder.put(COLUMN_PROJECTION_INTERVAL, Integer.valueOf(interval));
        }

        String intervalUnit = metastoreTableProperties.get(getMetastoreProjectionPropertyKey(columnName, METASTORE_PROPERTY_PROJECTION_INTERVAL_UNIT_SUFFIX));
        if (intervalUnit != null) {
            trinoTablePropertiesBuilder.put(COLUMN_PROJECTION_INTERVAL_UNIT, ChronoUnit.valueOf(intervalUnit.toUpperCase(ROOT)));
        }

        String digits = metastoreTableProperties.get(getMetastoreProjectionPropertyKey(columnName, COLUMN_PROJECTION_DIGITS_SUFFIX));
        if (digits != null) {
            trinoTablePropertiesBuilder.put(COLUMN_PROJECTION_DIGITS, Integer.valueOf(digits));
        }

        String format = metastoreTableProperties.get(getMetastoreProjectionPropertyKey(columnName, COLUMN_PROJECTION_FORMAT_SUFFIX));
        if (format != null) {
            trinoTablePropertiesBuilder.put(COLUMN_PROJECTION_FORMAT, format);
        }

        return trinoTablePropertiesBuilder.buildOrThrow();
    }

    private static Projection parseColumnProjection(String columnName, Type columnType, Map columnProperties)
    {
        ProjectionType projectionType = (ProjectionType) columnProperties.get(COLUMN_PROJECTION_TYPE);
        if (projectionType == null) {
            throw new InvalidProjectionException(columnName, "Projection type property missing");
        }
        return switch (projectionType) {
            case ENUM -> new EnumProjection(columnName, columnType, columnProperties);
            case INTEGER -> new IntegerProjection(columnName, columnType, columnProperties);
            case DATE -> new DateProjection(columnName, columnType, columnProperties);
            case INJECTED -> new InjectedProjection(columnName, columnType);
        };
    }

    private static List splitCommaSeparatedString(String value)
    {
        return Splitter.on(',')
                .trimResults()
                .omitEmptyStrings()
                .splitToList(value);
    }

    private static String getMetastoreProjectionPropertyKey(String columnName, String propertyKeySuffix)
    {
        return "projection" + "." + columnName + "." + propertyKeySuffix;
    }

    static  T getProjectionPropertyRequiredValue(
            String columnName,
            Map columnProjectionProperties,
            String propertyKey,
            Function decoder)
    {
        return getProjectionPropertyValue(columnProjectionProperties, propertyKey, decoder)
                .orElseThrow(() -> new InvalidProjectionException(columnName, format("Missing required property: '%s'", propertyKey)));
    }

    static  Optional getProjectionPropertyValue(
            Map columnProjectionProperties,
            String propertyKey,
            Function decoder)
    {
        return Optional.ofNullable(
                        columnProjectionProperties.get(propertyKey))
                .map(decoder);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy