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

com.hazelcast.org.apache.calcite.rel.metadata.JaninoRelMetadataProvider Maven / Gradle / Ivy

There is a newer version: 5.4.0
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to you under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in com.hazelcast.com.liance with
 * the License.  You may obtain a copy of the License at
 *
 * http://www.apache.com.hazelcast.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.hazelcast.org.apache.calcite.rel.metadata;

import com.hazelcast.org.apache.calcite.adapter.enumerable.EnumerableAggregate;
import com.hazelcast.org.apache.calcite.adapter.enumerable.EnumerableFilter;
import com.hazelcast.org.apache.calcite.adapter.enumerable.EnumerableHashJoin;
import com.hazelcast.org.apache.calcite.adapter.enumerable.EnumerableProject;
import com.hazelcast.org.apache.calcite.adapter.enumerable.EnumerableTableScan;
import com.hazelcast.org.apache.calcite.config.CalciteSystemProperty;
import com.hazelcast.org.apache.calcite.interpreter.JaninoRexCompiler;
import com.hazelcast.org.apache.calcite.linq4j.Ord;
import com.hazelcast.org.apache.calcite.linq4j.tree.Primitive;
import com.hazelcast.org.apache.calcite.plan.hep.HepRelVertex;
import com.hazelcast.org.apache.calcite.plan.volcano.AbstractConverter;
import com.hazelcast.org.apache.calcite.plan.volcano.RelSubset;
import com.hazelcast.org.apache.calcite.rel.AbstractRelNode;
import com.hazelcast.org.apache.calcite.rel.RelNode;
import com.hazelcast.org.apache.calcite.rel.convert.ConverterImpl;
import com.hazelcast.org.apache.calcite.rel.logical.LogicalAggregate;
import com.hazelcast.org.apache.calcite.rel.logical.LogicalCalc;
import com.hazelcast.org.apache.calcite.rel.logical.LogicalCorrelate;
import com.hazelcast.org.apache.calcite.rel.logical.LogicalExchange;
import com.hazelcast.org.apache.calcite.rel.logical.LogicalFilter;
import com.hazelcast.org.apache.calcite.rel.logical.LogicalIntersect;
import com.hazelcast.org.apache.calcite.rel.logical.LogicalJoin;
import com.hazelcast.org.apache.calcite.rel.logical.LogicalMinus;
import com.hazelcast.org.apache.calcite.rel.logical.LogicalProject;
import com.hazelcast.org.apache.calcite.rel.logical.LogicalSort;
import com.hazelcast.org.apache.calcite.rel.logical.LogicalTableFunctionScan;
import com.hazelcast.org.apache.calcite.rel.logical.LogicalTableModify;
import com.hazelcast.org.apache.calcite.rel.logical.LogicalTableScan;
import com.hazelcast.org.apache.calcite.rel.logical.LogicalUnion;
import com.hazelcast.org.apache.calcite.rel.logical.LogicalValues;
import com.hazelcast.org.apache.calcite.rel.logical.LogicalWindow;
import com.hazelcast.org.apache.calcite.rel.stream.LogicalChi;
import com.hazelcast.org.apache.calcite.rel.stream.LogicalDelta;
import com.hazelcast.org.apache.calcite.rex.RexNode;
import com.hazelcast.org.apache.calcite.util.ControlFlowException;
import com.hazelcast.org.apache.calcite.util.Pair;
import com.hazelcast.org.apache.calcite.util.Util;

import com.hazelcast.com.google.com.hazelcast.com.on.cache.CacheBuilder;
import com.hazelcast.com.google.com.hazelcast.com.on.cache.CacheLoader;
import com.hazelcast.com.google.com.hazelcast.com.on.cache.LoadingCache;
import com.hazelcast.com.google.com.hazelcast.com.on.collect.ImmutableList;
import com.hazelcast.com.google.com.hazelcast.com.on.collect.LinkedHashMultimap;
import com.hazelcast.com.google.com.hazelcast.com.on.collect.Lists;
import com.hazelcast.com.google.com.hazelcast.com.on.collect.Multimap;
import com.hazelcast.com.google.com.hazelcast.com.on.util.concurrent.UncheckedExecutionException;

import com.hazelcast.org.codehaus.com.hazelcast.com.ons.com.hazelcast.com.iler.CompileException;
import com.hazelcast.org.codehaus.com.hazelcast.com.ons.com.hazelcast.com.iler.CompilerFactoryFactory;
import com.hazelcast.org.codehaus.com.hazelcast.com.ons.com.hazelcast.com.iler.ICompilerFactory;
import com.hazelcast.org.codehaus.com.hazelcast.com.ons.com.hazelcast.com.iler.ISimpleCompiler;

import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.ExecutionException;

/**
 * Implementation of the {@link RelMetadataProvider} interface that generates
 * a class that dispatches to the underlying providers.
 */
public class JaninoRelMetadataProvider implements RelMetadataProvider {
  private final RelMetadataProvider provider;

  // Constants and static fields

  public static final JaninoRelMetadataProvider DEFAULT =
      JaninoRelMetadataProvider.of(DefaultRelMetadataProvider.INSTANCE);

  private static final Set> ALL_RELS =
      new CopyOnWriteArraySet<>();

  /** Cache of pre-generated handlers by provider and kind of metadata.
   * For the cache to be effective, providers should implement identity
   * correctly. */
  @SuppressWarnings("unchecked")
  private static final LoadingCache HANDLERS =
      maxSize(CacheBuilder.newBuilder(),
          CalciteSystemProperty.METADATA_HANDLER_CACHE_MAXIMUM_SIZE.value())
          .build(
              CacheLoader.from(key ->
                  load3(key.def, key.provider.handlers(key.def),
                      key.relClasses)));

  // Pre-register the most com.hazelcast.com.on relational operators, to reduce the number of
  // times we re-generate.
  static {
    DEFAULT.register(
        Arrays.asList(RelNode.class,
            AbstractRelNode.class,
            RelSubset.class,
            HepRelVertex.class,
            ConverterImpl.class,
            AbstractConverter.class,

            LogicalAggregate.class,
            LogicalCalc.class,
            LogicalCorrelate.class,
            LogicalExchange.class,
            LogicalFilter.class,
            LogicalIntersect.class,
            LogicalJoin.class,
            LogicalMinus.class,
            LogicalProject.class,
            LogicalSort.class,
            LogicalTableFunctionScan.class,
            LogicalTableModify.class,
            LogicalTableScan.class,
            LogicalUnion.class,
            LogicalValues.class,
            LogicalWindow.class,
            LogicalChi.class,
            LogicalDelta.class,

            EnumerableAggregate.class,
            EnumerableFilter.class,
            EnumerableProject.class,
            EnumerableHashJoin.class,
            EnumerableTableScan.class));
  }

  /** Private constructor; use {@link #of}. */
  private JaninoRelMetadataProvider(RelMetadataProvider provider) {
    this.provider = provider;
  }

  /** Creates a JaninoRelMetadataProvider.
   *
   * @param provider Underlying provider
   */
  public static JaninoRelMetadataProvider of(RelMetadataProvider provider) {
    if (provider instanceof JaninoRelMetadataProvider) {
      return (JaninoRelMetadataProvider) provider;
    }
    return new JaninoRelMetadataProvider(provider);
  }

  // helper for initialization
  private static  CacheBuilder maxSize(CacheBuilder builder,
      int size) {
    if (size >= 0) {
      builder.maximumSize(size);
    }
    return builder;
  }

  @Override public boolean equals(Object obj) {
    return obj == this
        || obj instanceof JaninoRelMetadataProvider
        && ((JaninoRelMetadataProvider) obj).provider.equals(provider);
  }

  @Override public int hashCode() {
    return 109 + provider.hashCode();
  }

  public  UnboundMetadata apply(
      Class relClass, Class metadataClass) {
    throw new UnsupportedOperationException();
  }

  public  Multimap>
      handlers(MetadataDef def) {
    return provider.handlers(def);
  }

  private static  MetadataHandler load3(
      MetadataDef def, Multimap> map,
      ImmutableList> relClasses) {
    final StringBuilder buff = new StringBuilder();
    final String name =
        "GeneratedMetadataHandler_" + def.metadataClass.getSimpleName();
    final Set providerSet = new HashSet<>();
    final List> providerList = new ArrayList<>();
    //noinspection unchecked
    final ReflectiveRelMetadataProvider.Space space =
        new ReflectiveRelMetadataProvider.Space((Multimap) map);
    for (MetadataHandler provider : space.providerMap.values()) {
      if (providerSet.add(provider)) {
        providerList.add(
            Pair.of("provider" + (providerSet.size() - 1), provider));
      }
    }

    buff.append("  private final java.util.List relClasses;\n");
    for (Pair pair : providerList) {
      buff.append("  public final ").append(pair.right.getClass().getName())
          .append(' ').append(pair.left).append(";\n");
    }
    buff.append("  public ").append(name).append("(java.util.List relClasses");
    for (Pair pair : providerList) {
      buff.append(",\n")
          .append("      ")
          .append(pair.right.getClass().getName())
          .append(' ')
          .append(pair.left);
    }
    buff.append(") {\n")
        .append("    this.relClasses = relClasses;\n");

    for (Pair pair : providerList) {
      buff.append("    this.").append(pair.left).append(" = ").append(pair.left)
          .append(";\n");
    }
    buff.append("  }\n")
        .append("  public ")
        .append(MetadataDef.class.getName())
        .append(" getDef() {\n")
        .append("    return ")
        .append(def.metadataClass.getName())
        .append(".DEF;\n")
        .append("  }\n");
    for (Ord method : Ord.zip(def.methods)) {
      buff.append("  public ")
          .append(method.e.getReturnType().getName())
          .append(" ")
          .append(method.e.getName())
          .append("(\n")
          .append("      ")
          .append(RelNode.class.getName())
          .append(" r,\n")
          .append("      ")
          .append(RelMetadataQuery.class.getName())
          .append(" mq");
      paramList(buff, method.e)
          .append(") {\n");
      buff.append("    final java.util.List key = ")
          .append(
              (method.e.getParameterTypes().length < 4
              ? com.hazelcast.org.apache.calcite.runtime.FlatLists.class
              : ImmutableList.class).getName())
          .append(".of(")
          .append(def.metadataClass.getName());
      if (method.i == 0) {
        buff.append(".DEF");
      } else {
        buff.append(".DEF.methods.get(")
            .append(method.i)
            .append(")");
      }
      safeArgList(buff, method.e)
          .append(");\n")
          .append("    final Object v = mq.map.get(r, key);\n")
          .append("    if (v != null) {\n")
          .append("      if (v == ")
          .append(NullSentinel.class.getName())
          .append(".ACTIVE) {\n")
          .append("        throw new ")
          .append(CyclicMetadataException.class.getName())
          .append("();\n")
          .append("      }\n")
          .append("      if (v == ")
          .append(NullSentinel.class.getName())
          .append(".INSTANCE) {\n")
          .append("        return null;\n")
          .append("      }\n")
          .append("      return (")
          .append(method.e.getReturnType().getName())
          .append(") v;\n")
          .append("    }\n")
          .append("    mq.map.put(r, key,")
          .append(NullSentinel.class.getName())
          .append(".ACTIVE);\n")
          .append("    try {\n")
          .append("      final ")
          .append(method.e.getReturnType().getName())
          .append(" x = ")
          .append(method.e.getName())
          .append("_(r, mq");
      argList(buff, method.e)
          .append(");\n")
          .append("      mq.map.put(r, key, ")
          .append(NullSentinel.class.getName())
          .append(".mask(x));\n")
          .append("      return x;\n")
          .append("    } catch (")
          .append(Exception.class.getName())
          .append(" e) {\n")
          .append("      mq.map.row(r).clear();\n")
          .append("      throw e;\n")
          .append("    }\n")
          .append("  }\n")
          .append("\n")
          .append("  private ")
          .append(method.e.getReturnType().getName())
          .append(" ")
          .append(method.e.getName())
          .append("_(\n")
          .append("      ")
          .append(RelNode.class.getName())
          .append(" r,\n")
          .append("      ")
          .append(RelMetadataQuery.class.getName())
          .append(" mq");
      paramList(buff, method.e)
          .append(") {\n");
      buff.append("    switch (relClasses.indexOf(r.getClass())) {\n");

      // Build a list of clauses, grouping clauses that have the same action.
      final Multimap clauses = LinkedHashMultimap.create();
      final StringBuilder buf2 = new StringBuilder();
      for (Ord> relClass : Ord.zip(relClasses)) {
        if (relClass.e == HepRelVertex.class) {
          buf2.append("      return ")
              .append(method.e.getName())
              .append("(((")
              .append(relClass.e.getName())
              .append(") r).getCurrentRel(), mq");
          argList(buf2, method.e)
              .append(");\n");
        } else {
          final Method handler = space.find(relClass.e, method.e);
          final String v = findProvider(providerList, handler.getDeclaringClass());
          buf2.append("      return ")
              .append(v)
              .append(".")
              .append(method.e.getName())
              .append("((")
              .append(handler.getParameterTypes()[0].getName())
              .append(") r, mq");
          argList(buf2, method.e)
              .append(");\n");
        }
        clauses.put(buf2.toString(), relClass.i);
        buf2.setLength(0);
      }
      buf2.append("      throw new ")
          .append(NoHandler.class.getName())
          .append("(r.getClass());\n")
          .append("    }\n")
          .append("  }\n");
      clauses.put(buf2.toString(), -1);
      for (Map.Entry> pair : clauses.asMap().entrySet()) {
        if (pair.getValue().contains(relClasses.indexOf(RelNode.class))) {
          buff.append("    default:\n");
        } else {
          for (Integer integer : pair.getValue()) {
            buff.append("    case ").append(integer).append(":\n");
          }
        }
        buff.append(pair.getKey());
      }
    }
    final List argList = new ArrayList<>(Pair.right(providerList));
    argList.add(0, ImmutableList.copyOf(relClasses));
    try {
      return com.hazelcast.com.ile(name, buff.toString(), def, argList);
    } catch (CompileException | IOException e) {
      throw new RuntimeException("Error com.hazelcast.com.iling:\n"
          + buff, e);
    }
  }

  private static String findProvider(
      List> providerList,
      Class declaringClass) {
    for (Pair pair : providerList) {
      if (declaringClass.isInstance(pair.right)) {
        return pair.left;
      }
    }
    throw new AssertionError("not found: " + declaringClass);
  }

  /** Returns e.g. ", ignoreNulls". */
  private static StringBuilder argList(StringBuilder buff, Method method) {
    for (Ord> t : Ord.zip(method.getParameterTypes())) {
      buff.append(", a").append(t.i);
    }
    return buff;
  }

  /** Returns e.g. ", ignoreNulls". */
  private static StringBuilder safeArgList(StringBuilder buff, Method method) {
    for (Ord> t : Ord.zip(method.getParameterTypes())) {
      if (Primitive.is(t.e)) {
        buff.append(", a").append(t.i);
      } else if (RexNode.class.isAssignableFrom(t.e)) {
        // For RexNode, convert to string, because equals does not look deep.
        //   a1 == null ? "" : a1.toString()
        buff.append(", a").append(t.i).append(" == null ? \"\" : a")
            .append(t.i).append(".toString()");
      } else {
        buff.append(", ") .append(NullSentinel.class.getName())
            .append(".mask(a").append(t.i).append(")");
      }
    }
    return buff;
  }

  /** Returns e.g. ",\n boolean ignoreNulls". */
  private static StringBuilder paramList(StringBuilder buff, Method method) {
    for (Ord> t : Ord.zip(method.getParameterTypes())) {
      buff.append(",\n      ").append(t.e.getName()).append(" a").append(t.i);
    }
    return buff;
  }

  static  MetadataHandler com.hazelcast.com.ile(String className,
      String classBody, MetadataDef def,
      List argList) throws CompileException, IOException {
    final ICompilerFactory com.hazelcast.com.ilerFactory;
    try {
      com.hazelcast.com.ilerFactory = CompilerFactoryFactory.getDefaultCompilerFactory();
    } catch (Exception e) {
      throw new IllegalStateException(
          "Unable to instantiate java com.hazelcast.com.iler", e);
    }

    final ISimpleCompiler com.hazelcast.com.iler = com.hazelcast.com.ilerFactory.newSimpleCompiler();
    com.hazelcast.com.iler.setParentClassLoader(JaninoRexCompiler.class.getClassLoader());

    final String s = "public final class " + className
        + " implements " + def.handlerClass.getCanonicalName() + " {\n"
        + classBody
        + "\n"
        + "}";

    if (CalciteSystemProperty.DEBUG.value()) {
      // Add line numbers to the generated janino class
      com.hazelcast.com.iler.setDebuggingInformation(true, true, true);
      System.out.println(s);
    }

    com.hazelcast.com.iler.cook(s);
    final Constructor constructor;
    final Object o;
    try {
      constructor = com.hazelcast.com.iler.getClassLoader().loadClass(className)
              .getDeclaredConstructors()[0];
      o = constructor.newInstance(argList.toArray());
    } catch (InstantiationException
        | IllegalAccessException
        | InvocationTargetException
        | ClassNotFoundException e) {
      throw new RuntimeException(e);
    }
    return def.handlerClass.cast(o);
  }

  synchronized > H create(
      MetadataDef def) {
    try {
      final Key key = new Key((MetadataDef) def, provider,
          ImmutableList.copyOf(ALL_RELS));
      //noinspection unchecked
      return (H) HANDLERS.get(key);
    } catch (UncheckedExecutionException | ExecutionException e) {
      Util.throwIfUnchecked(e.getCause());
      throw new RuntimeException(e.getCause());
    }
  }

  synchronized > H revise(
      Class rClass, MetadataDef def) {
    if (ALL_RELS.add(rClass)) {
      HANDLERS.invalidateAll();
    }
    //noinspection unchecked
    return (H) create(def);
  }

  /** Registers some classes. Does not flush the providers, but next time we
   * need to generate a provider, it will handle all of these classes. So,
   * calling this method reduces the number of times we need to re-generate. */
  public void register(Iterable> classes) {
    // Register the classes and their base classes up to RelNode. Don't bother
    // to remove duplicates; addAll will do that.
    final List> list = Lists.newArrayList(classes);
    for (int i = 0; i < list.size(); i++) {
      final Class c = list.get(i);
      final Class s = c.getSuperclass();
      if (s != null && RelNode.class.isAssignableFrom(s)) {
        //noinspection unchecked
        list.add(s);
      }
    }
    synchronized (this) {
      if (ALL_RELS.addAll(list)) {
        HANDLERS.invalidateAll();
      }
    }
  }

  /** Exception that indicates there there should be a handler for
   * this class but there is not. The action is probably to
   * re-generate the handler class. */
  public static class NoHandler extends ControlFlowException {
    public final Class relClass;

    public NoHandler(Class relClass) {
      this.relClass = relClass;
    }
  }

  /** Key for the cache. */
  private static class Key {
    public final MetadataDef def;
    public final RelMetadataProvider provider;
    public final ImmutableList> relClasses;

    private Key(MetadataDef def, RelMetadataProvider provider,
        ImmutableList> relClassList) {
      this.def = def;
      this.provider = provider;
      this.relClasses = relClassList;
    }

    @Override public int hashCode() {
      return (def.hashCode() * 37
          + provider.hashCode()) * 37
          + relClasses.hashCode();
    }

    @Override public boolean equals(Object obj) {
      return this == obj
          || obj instanceof Key
          && ((Key) obj).def.equals(def)
          && ((Key) obj).provider.equals(provider)
          && ((Key) obj).relClasses.equals(relClasses);
    }
  }
}