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

com.facebook.presto.druid.DruidQueryGeneratorContext 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 com.facebook.presto.druid;

import com.facebook.presto.spi.PrestoException;
import com.facebook.presto.spi.relation.VariableReferenceExpression;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

import static com.facebook.presto.druid.DruidErrorCode.DRUID_QUERY_GENERATOR_FAILURE;
import static com.google.common.base.MoreObjects.toStringHelper;
import static com.google.common.base.Preconditions.checkArgument;
import static java.util.Objects.requireNonNull;

public class DruidQueryGeneratorContext
{
    private final Map selections;
    private final Optional from;
    private final Optional filter;

    @Override
    public String toString()
    {
        return toStringHelper(this)
                .add("selections", selections)
                .add("from", from)
                .add("filter", filter)
                .toString();
    }

    DruidQueryGeneratorContext()
    {
        this(new LinkedHashMap<>(), null);
    }

    DruidQueryGeneratorContext(
            Map selections,
            String from)
    {
        this(
                selections,
                Optional.ofNullable(from),
                Optional.empty());
    }

    private DruidQueryGeneratorContext(
            Map selections,
            Optional from,
            Optional filter)
    {
        this.selections = new LinkedHashMap<>(requireNonNull(selections, "selections can't be null"));
        this.from = requireNonNull(from, "source can't be null");
        this.filter = requireNonNull(filter, "filter is null");
    }

    public DruidQueryGeneratorContext withFilter(String filter)
    {
        checkArgument(!hasFilter(), "Druid doesn't support filters at multiple levels");
        return new DruidQueryGeneratorContext(
                selections,
                from,
                Optional.of(filter));
    }

    public DruidQueryGeneratorContext withProject(Map newSelections)
    {
        return new DruidQueryGeneratorContext(
                newSelections,
                from,
                filter);
    }

    private boolean hasFilter()
    {
        return filter.isPresent();
    }

    public Map getSelections()
    {
        return selections;
    }

    public DruidQueryGenerator.GeneratedDql toQuery()
    {
        String expressions = selections.entrySet().stream()
                .map(s -> s.getValue().getDefinition())
                .collect(Collectors.joining(", "));
        if (expressions.isEmpty()) {
            throw new PrestoException(DRUID_QUERY_GENERATOR_FAILURE, "Empty Druid query");
        }

        String tableName = from.orElseThrow(() -> new PrestoException(DRUID_QUERY_GENERATOR_FAILURE, "Table name missing in Druid query"));
        String query = "SELECT " + expressions + " FROM " + tableName;
        boolean pushdown = false;
        if (filter.isPresent()) {
            // this is hack!!!. Ideally we want to clone the scan pipeline and create/update the filter in the scan pipeline to contain this filter and
            // at the same time add the time column to scan so that the query generator doesn't fail when it looks up the time column in scan output columns
            query += " WHERE " + filter.get();
            pushdown = true;
        }
        return new DruidQueryGenerator.GeneratedDql(tableName, query, pushdown);
    }

    public Map getAssignments()
    {
        Map result = new LinkedHashMap<>();
        selections.entrySet().forEach(entry -> {
            VariableReferenceExpression variable = entry.getKey();
            Selection selection = entry.getValue();
            DruidColumnHandle handle = selection.getOrigin() == Origin.TABLE_COLUMN ? new DruidColumnHandle(selection.getDefinition(), variable.getType(), DruidColumnHandle.DruidColumnType.REGULAR) : new DruidColumnHandle(variable, DruidColumnHandle.DruidColumnType.DERIVED);
            result.put(variable, handle);
        });
        return result;
    }

    public DruidQueryGeneratorContext withOutputColumns(List outputColumns)
    {
        Map newSelections = new LinkedHashMap<>();
        outputColumns.forEach(o -> newSelections.put(o, requireNonNull(selections.get(o), "Cannot find the selection " + o + " in the original context " + this)));

        return new DruidQueryGeneratorContext(newSelections, from, filter);
    }

    public enum Origin
    {
        TABLE_COLUMN, // refers to direct column in table
        DERIVED, // expression is derived from one or more input columns or a combination of input columns and literals
        LITERAL, // derived from literal
    }

    // Projected/selected column definition in query
    public static class Selection
    {
        private final String definition;
        private final Origin origin;

        public Selection(String definition, Origin origin)
        {
            this.definition = definition;
            this.origin = origin;
        }

        public String getDefinition()
        {
            return definition;
        }

        public Origin getOrigin()
        {
            return origin;
        }

        @Override
        public String toString()
        {
            return definition;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy