Package net.hydromatic.optiq.impl.splunk

Source Code of net.hydromatic.optiq.impl.splunk.SplunkPushDownRule

/*
// Licensed to Julian Hyde under one or more contributor license
// agreements. See the NOTICE file distributed with this work for
// additional information regarding copyright ownership.
//
// Julian Hyde licenses this file to you 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 net.hydromatic.optiq.impl.splunk;

import net.hydromatic.optiq.impl.splunk.util.StringUtils;

import org.eigenbase.rel.*;
import org.eigenbase.relopt.*;
import org.eigenbase.reltype.*;
import org.eigenbase.rex.*;
import org.eigenbase.sql.*;
import org.eigenbase.sql.fun.SqlStdOperatorTable;
import org.eigenbase.sql.type.SqlTypeName;
import org.eigenbase.util.NlsString;
import org.eigenbase.util.Pair;

import com.google.common.collect.ImmutableSet;

import java.util.*;
import java.util.logging.Logger;

/**
* Planner rule to push filters and projections to Splunk.
*/
public class SplunkPushDownRule
    extends RelOptRule {
  private static final Logger LOGGER =
      StringUtils.getClassTracer(SplunkPushDownRule.class);

  private static final Set<SqlKind> SUPPORTED_OPS =
      ImmutableSet.of(
          SqlKind.CAST,
          SqlKind.EQUALS,
          SqlKind.LESS_THAN,
          SqlKind.LESS_THAN_OR_EQUAL,
          SqlKind.GREATER_THAN,
          SqlKind.GREATER_THAN_OR_EQUAL,
          SqlKind.NOT_EQUALS,
          SqlKind.LIKE,
          SqlKind.AND,
          SqlKind.OR,
          SqlKind.NOT);

  public static final SplunkPushDownRule PROJECT_ON_FILTER =
      new SplunkPushDownRule(
          operand(
              ProjectRel.class,
              operand(
                  FilterRel.class,
                  operand(
                      ProjectRel.class,
                      operand(SplunkTableAccessRel.class, none())))),
          "proj on filter on proj");

  public static final SplunkPushDownRule FILTER_ON_PROJECT =
      new SplunkPushDownRule(
          operand(
              FilterRel.class,
              operand(
                  ProjectRel.class,
                  operand(SplunkTableAccessRel.class, none()))),
          "filter on proj");

  public static final SplunkPushDownRule FILTER =
      new SplunkPushDownRule(
          operand(
              FilterRel.class, operand(SplunkTableAccessRel.class, none())),
          "filter");

  public static final SplunkPushDownRule PROJECT =
      new SplunkPushDownRule(
          operand(
              ProjectRel.class,
              operand(SplunkTableAccessRel.class, none())),
          "proj");

  /** Creates a SplunkPushDownRule. */
  protected SplunkPushDownRule(RelOptRuleOperand rule, String id) {
    super(rule, "SplunkPushDownRule: " + id);
  }

  // ~ Methods --------------------------------------------------------------

  // implement RelOptRule
  public void onMatch(RelOptRuleCall call) {
    LOGGER.fine(description);

    int relLength = call.rels.length;
    SplunkTableAccessRel splunkRel =
        (SplunkTableAccessRel) call.rels[relLength - 1];

    FilterRel  filter;
    ProjectRel topProj    = null;
    ProjectRel bottomProj = null;


    RelDataType topRow = splunkRel.getRowType();

    int filterIdx = 2;
    if (call.rels[relLength - 2] instanceof ProjectRel) {
      bottomProj = (ProjectRel) call.rels[relLength - 2];
      filterIdx  = 3;

      // bottom projection will change the field count/order
      topRow =  bottomProj.getRowType();
    }

    String filterString;

    if (filterIdx <= relLength
        && call.rels[relLength - filterIdx] instanceof FilterRel) {
      filter = (FilterRel) call.rels[relLength - filterIdx];

      int topProjIdx = filterIdx + 1;
      if (topProjIdx <= relLength
          && call.rels[relLength - topProjIdx] instanceof ProjectRel) {
        topProj = (ProjectRel) call.rels[relLength - topProjIdx];
      }

      RexCall filterCall = (RexCall) filter.getCondition();
      SqlOperator op = filterCall.getOperator();
      List<RexNode> operands = filterCall.getOperands();

      LOGGER.fine("fieldNames: " + getFieldsString(topRow));

      final StringBuilder buf = new StringBuilder();
      if (getFilter(op, operands, buf, topRow.getFieldNames())) {
        filterString = buf.toString();
      } else {
        return; // can't handle
      }
    } else {
      filterString = "";
    }

    // top projection will change the field count/order
    if (topProj != null) {
      topRow =  topProj.getRowType();
    }
    LOGGER.fine("pre transformTo fieldNames: " + getFieldsString(topRow));

    call.transformTo(
        appendSearchString(
            filterString, splunkRel, topProj, bottomProj,
            topRow, null));
  }

  /**
   * Appends a search string.
   *
   * @param toAppend Search string to append
   * @param splunkRel Relational expression
   * @param topProj Top projection
   * @param bottomProj Bottom projection
   */
  protected RelNode appendSearchString(
      String toAppend,
      SplunkTableAccessRel splunkRel,
      ProjectRel topProj,
      ProjectRel bottomProj,
      RelDataType topRow,
      RelDataType bottomRow) {
    StringBuilder updateSearchStr = new StringBuilder(splunkRel.search);

    if (!toAppend.isEmpty()) {
      updateSearchStr.append(" ").append(toAppend);
    }
    List<RelDataTypeField> bottomFields =
        bottomRow == null ? null : bottomRow.getFieldList();
    List<RelDataTypeField> topFields    =
        topRow    == null ? null : topRow.getFieldList();

    if (bottomFields == null) {
      bottomFields = splunkRel.getRowType().getFieldList();
    }

    // handle bottom projection (ie choose a subset of the table fields)
    if (bottomProj != null) {
      List<RelDataTypeField> tmp  =
          new ArrayList<RelDataTypeField>();
      List<RelDataTypeField> dRow = bottomProj.getRowType().getFieldList();
      for (RexNode rn : bottomProj.getProjects()) {
        RelDataTypeField rdtf;
        if (rn instanceof RexSlot) {
          RexSlot rs = (RexSlot) rn;
          rdtf = bottomFields.get(rs.getIndex());
        } else {
          rdtf = dRow.get(tmp.size());
        }
        tmp.add(rdtf);
      }
      bottomFields = tmp;
    }

    // field renaming: to -> from
    List<Pair<String, String>> renames =
        new LinkedList<Pair<String, String>>();

    // handle top projection (ie reordering and renaming)
    List<RelDataTypeField> newFields = bottomFields;
    if (topProj != null) {
      LOGGER.fine("topProj: " + String.valueOf(topProj.getPermutation()));
      newFields = new ArrayList<RelDataTypeField>();
      int i = 0;
      for (RexNode rn : topProj.getProjects()) {
        RexInputRef rif = (RexInputRef) rn;
        RelDataTypeField field = bottomFields.get(rif.getIndex());
        if (!bottomFields.get(rif.getIndex()).getName()
            .equals(topFields.get(i).getName())) {
          renames.add(
              new Pair<String, String>(
                  bottomFields.get(rif.getIndex()).getName(),
                  topFields.get(i).getName()));
          field = topFields.get(i);
        }
        newFields.add(field);
      }
    }

    if (!renames.isEmpty()) {
      updateSearchStr.append("| rename ");
      for (Pair<String, String> p : renames) {
        updateSearchStr.append(p.left).append(" AS ")
            .append(p.right).append(" ");
      }
    }

    RelDataType resultType = new RelRecordType(newFields);
    String searchWithFilter = updateSearchStr.toString();

    RelNode rel =
        new SplunkTableAccessRel(
            splunkRel.getCluster(),
            splunkRel.getTable(),
            splunkRel.splunkTable,
            searchWithFilter,
            splunkRel.earliest,
            splunkRel.latest,
            resultType.getFieldNames());

    LOGGER.fine(
        "end of appendSearchString fieldNames: "
        + rel.getRowType().getFieldNames());
    return rel;
  }

  // ~ Private Methods ------------------------------------------------------

  private static RelNode addProjectionRule(ProjectRel proj, RelNode rel) {
    if (proj == null) {
      return rel;
    }
    return new ProjectRel(
        proj.getCluster(),
        proj.getCluster().traitSetOf(
            proj.getCollationList().isEmpty()
                ? RelCollationImpl.EMPTY
                : proj.getCollationList().get(0)),
        rel, proj.getProjects(), proj.getRowType(), proj.getFlags());
  }

  // TODO: use StringBuilder instead of String
  // TODO: refactor this to use more tree like parsing, need to also
  //      make sure we use parens properly - currently precedence
  //      rules are simply left to right
  private boolean getFilter(SqlOperator op, List<RexNode> operands,
      StringBuilder s, List<String> fieldNames) {
    if (!valid(op.getKind())) {
      return false;
    }

    boolean like = false;
    switch (op.getKind()) {
    case NOT:
      // NOT op pre-pended
      s = s.append(" NOT ");
      break;
    case CAST:
      return asd(false, operands, s, fieldNames, 0);
    case LIKE:
      like = true;
      break;
    }

    for (int i = 0; i < operands.size(); i++) {
      if (!asd(like, operands, s, fieldNames, i)) {
        return false;
      }
      if (op instanceof SqlBinaryOperator && i == 0) {
        s.append(" ").append(op).append(" ");
      }
    }
    return true;
  }

  private boolean asd(boolean like, List<RexNode> operands, StringBuilder s,
      List<String> fieldNames, int i) {
    RexNode operand = operands.get(i);
    if (operand instanceof RexCall) {
      s.append("(");
      final RexCall call = (RexCall) operand;
      boolean b =
          getFilter(
              call.getOperator(),
              call.getOperands(),
              s,
              fieldNames);
      if (!b) {
        return false;
      }
      s.append(")");
    } else {
      if (operand instanceof RexInputRef) {
        if (i != 0) {
          return false;
        }
        int fieldIndex = ((RexInputRef) operand).getIndex();
        String name = fieldNames.get(fieldIndex);
        s.append(name);
      } else { // RexLiteral
        String tmp = toString(like, (RexLiteral) operand);
        if (tmp == null) {
          return false;
        }
        s.append(tmp);
      }
    }
    return true;
  }

  private boolean valid(SqlKind kind) {
    return SUPPORTED_OPS.contains(kind);
  }

  private String toString(SqlOperator op) {
    if (op.equals(SqlStdOperatorTable.LIKE)) {
      return SqlStdOperatorTable.EQUALS.toString();
    } else if (op.equals(SqlStdOperatorTable.NOT_EQUALS)) {
      return "!=";
    }
    return op.toString();
  }

  public static String searchEscape(String str) {
    if (str.isEmpty()) {
      return "\"\"";
    }
    StringBuilder sb = new StringBuilder(str.length());
    boolean quote = false;

    for (int i = 0; i < str.length(); i++) {
      char c = str.charAt(i);
      if (c == '"' || c == '\\') {
        sb.append('\\');
      }
      sb.append(c);

      quote |= !(Character.isLetterOrDigit(c) || c == '_');
    }

    if (quote || sb.length() != str.length()) {
      sb.insert(0, '"');
      sb.append('"');
      return sb.toString();
    }
    return str;
  }

  private String toString(boolean like, RexLiteral literal) {
    String value = null;
    SqlTypeName litSqlType = literal.getTypeName();
    if (SqlTypeName.NUMERIC_TYPES.contains(litSqlType)) {
      value = literal.getValue().toString();
    } else if (litSqlType.equals(SqlTypeName.CHAR)) {
      value = ((NlsString) literal.getValue()).getValue();
      if (like) {
        value = value.replaceAll("%", "*");
      }
      value = searchEscape(value);
    }
    return value;
  }

  // transform the call from SplunkUdxRel to FarragoJavaUdxRel
  // usually used to stop the optimizer from calling us
  protected void transformToFarragoUdxRel(
      RelOptRuleCall call,
      SplunkTableAccessRel splunkRel,
      FilterRel filter,
      ProjectRel topProj,
      ProjectRel bottomProj) {
    assert false;
/*
        RelNode rel =
            new JavaRules.EnumerableTableAccessRel(
                udxRel.getCluster(),
                udxRel.getTable(),
                udxRel.getRowType(),
                udxRel.getServerMofId());

        rel = RelOptUtil.createCastRel(rel, udxRel.getRowType(), true);

        rel = addProjectionRule(bottomProj, rel);

        if (filter != null) {
            rel =
                new FilterRel(filter.getCluster(), rel, filter.getCondition());
        }

        rel = addProjectionRule(topProj, rel);

        call.transformTo(rel);
        */
  }

  public static String getFieldsString(RelDataType row) {
    return row.getFieldNames().toString();
  }
}

// End SplunkPushDownRule.java
TOP

Related Classes of net.hydromatic.optiq.impl.splunk.SplunkPushDownRule

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.