org.pentaho.di.trans.dataservice.optimization.pushdown.ParameterPushdown.java Source code

Java tutorial

Introduction

Here is the source code for org.pentaho.di.trans.dataservice.optimization.pushdown.ParameterPushdown.java

Source

/*! ******************************************************************************
 *
 * Pentaho Data Integration
 *
 * Copyright (C) 2002-2016 by Pentaho : http://www.pentaho.com
 *
 *******************************************************************************
 *
 * 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 org.pentaho.di.trans.dataservice.optimization.pushdown;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Objects;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.base.Strings;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import org.pentaho.di.core.Condition;
import org.pentaho.di.core.parameters.DuplicateParamException;
import org.pentaho.di.core.sql.SQL;
import org.pentaho.di.core.sql.SQLCondition;
import org.pentaho.di.trans.TransMeta;
import org.pentaho.di.trans.dataservice.DataServiceExecutor;
import org.pentaho.di.trans.dataservice.DataServiceMeta;
import org.pentaho.di.trans.dataservice.optimization.OptimizationImpactInfo;
import org.pentaho.di.trans.dataservice.optimization.PushDownOptimizationMeta;
import org.pentaho.di.trans.dataservice.optimization.PushDownType;
import org.pentaho.metastore.persist.MetaStoreAttribute;

import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * @author nhudak
 */
public class ParameterPushdown implements PushDownType {
    static final String NAME = "Parameter Capture";

    public static final String DEFINITIONS_LIST_ATTRIBUTE = "definitions_list";
    public static final String PARAMETER_ATTRIBUTE = "parameter_name";
    public static final String FIELD_NAME_ATTRIBUTE = "column_name";
    public static final String FORMAT_ATTRIBUTE = "parameter_format";
    public static final String DEFAULT_FORMAT = "%s";
    public static final String PARAMETER_PREFIX = "_QUERY";

    private static final Joiner.MapJoiner mapJoiner = Joiner.on('\n').withKeyValueSeparator(" = ").useForNull("");

    @MetaStoreAttribute(key = DEFINITIONS_LIST_ATTRIBUTE)
    private final List<Definition> definitions = Lists.newArrayList();

    public List<Definition> getDefinitions() {
        return definitions;
    }

    public Definition createDefinition() {
        Definition definition = new Definition();
        definitions.add(definition);
        return definition;
    }

    @Override
    public void init(TransMeta transMeta, DataServiceMeta dataService, PushDownOptimizationMeta optMeta) {
        optMeta.setStepName(dataService.getStepname());

        for (Iterator<Definition> iterator = definitions.iterator(); iterator.hasNext();) {
            Definition definition = iterator.next();
            if (definition.isValid()) {
                try {
                    transMeta.addParameterDefinition(definition.getParameter(), "", "Data Service Field Parameter");
                } catch (DuplicateParamException e) {
                    // Ignore duplicates
                }
            } else {
                iterator.remove();
            }
        }
        transMeta.activateParameters();
    }

    @Override
    public ListenableFuture<Boolean> activate(DataServiceExecutor executor, PushDownOptimizationMeta meta) {
        Map<String, String> parameterValues = captureParameterValues(executor.getSql());

        executor.getParameters().putAll(parameterValues);
        return Futures.immediateFuture(!parameterValues.isEmpty());
    }

    private static final ImmutableSet<Integer> ALLOWED_FUNCTIONS = ImmutableSet.of(Condition.FUNC_EQUAL);

    private static final ImmutableSet<Integer> ALLOWED_OPERATORS = ImmutableSet.of(Condition.OPERATOR_NONE,
            Condition.OPERATOR_AND);

    @VisibleForTesting
    protected Map<String, String> captureParameterValues(SQL sql) {
        Optional<SQLCondition> whereCondition = Optional.fromNullable(sql.getWhereCondition());
        Multimap<String, Condition> conditionMap = FluentIterable.from(whereCondition.asSet())
                .transformAndConcat(new Function<SQLCondition, Iterable<Condition>>() {
                    @Override
                    public Iterable<Condition> apply(SQLCondition sqlCondition) {
                        Condition condition = sqlCondition.getCondition();
                        condition.simplify();

                        // Flatten first level of conditions
                        if (!condition.isComposite()) {
                            return Collections.singleton(condition);
                        }

                        // All child conditions must have allowable operators
                        for (Condition child : condition.getChildren()) {
                            if (!ALLOWED_OPERATORS.contains(child.getOperator())) {
                                return Collections.emptySet();
                            }
                        }

                        return condition.getChildren();
                    }
                }).filter(new Predicate<Condition>() {
                    @Override
                    public boolean apply(Condition condition) {
                        // Only simple 'equals' conditions should be allowed
                        // in the form of: WHERE A = 1
                        return !condition.isComposite() && !condition.isNegated()
                                && condition.getRightExact() != null
                                && ALLOWED_FUNCTIONS.contains(condition.getFunction());
                    }
                }).index(new Function<Condition, String>() {
                    @Override
                    public String apply(Condition condition) {
                        // Group by field for easy look up
                        return condition.getLeftValuename();
                    }
                });

        Map<String, String> builder = Maps.newLinkedHashMap();
        for (Definition definition : definitions) {
            // There should be either 0 or 1 conditions for each field.
            for (Condition condition : conditionMap.get(definition.getFieldName())) {
                builder.put(definition.getParameter(), definition.format(condition));
            }
        }
        return ImmutableMap.copyOf(builder);
    }

    @Override
    public OptimizationImpactInfo preview(DataServiceExecutor executor, PushDownOptimizationMeta meta) {
        OptimizationImpactInfo impactInfo = new OptimizationImpactInfo(meta.getStepName());

        try {
            TransMeta serviceTrans = executor.getServiceTransMeta();
            Map<String, String> defaults = Maps.newLinkedHashMap();
            for (Definition definition : definitions) {
                String defaultValue = serviceTrans.getParameterDefault(definition.getParameter());
                defaults.put(definition.getParameter(), Objects.firstNonNull(defaultValue, ""));
            }

            Map<String, String> parameterValues = Maps.newLinkedHashMap(defaults);
            parameterValues.putAll(captureParameterValues(executor.getSql()));

            impactInfo.setQueryBeforeOptimization(mapJoiner.join(defaults));
            impactInfo.setQueryAfterOptimization(mapJoiner.join(parameterValues));
            impactInfo.setModified(!parameterValues.equals(defaults));
        } catch (Exception e) {
            impactInfo.setErrorMsg(e);
        }

        return impactInfo;
    }

    public static class Definition {

        @MetaStoreAttribute(key = PARAMETER_ATTRIBUTE)
        private String parameter;

        @MetaStoreAttribute(key = FORMAT_ATTRIBUTE)
        private String format = DEFAULT_FORMAT;

        @MetaStoreAttribute(key = FIELD_NAME_ATTRIBUTE)
        private String fieldName;

        public String getFieldName() {
            return fieldName;
        }

        public Definition setFieldName(String column) {
            this.fieldName = column;
            return this;
        }

        public String getParameter() {
            return parameter;
        }

        public Definition setParameter(String parameter) {
            this.parameter = parameter;
            return this;
        }

        public String getFormat() {
            if (Strings.isNullOrEmpty(format)) {
                format = DEFAULT_FORMAT;
            }
            return format;
        }

        public void setFormat(String format) {
            this.format = format;
        }

        private String format(Condition condition) {
            return String.format(getFormat(), condition.getRightExact().getValueData().toString().trim());
        }

        @Override
        public String toString() {
            return Objects.toStringHelper(this).add("fieldName", getFieldName()).add("parameter", getParameter())
                    .add("format", getFormat()).toString();
        }

        public boolean isValid() {
            return !Strings.isNullOrEmpty(getParameter()) && !Strings.isNullOrEmpty(getFieldName());
        }
    }
}