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

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

There is a newer version: 5.5.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 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.hazelcast.org.apache.calcite.rel.metadata;

import com.hazelcast.org.apache.calcite.plan.RelOptPlanner;
import com.hazelcast.org.apache.calcite.rel.RelNode;

import com.hazelcast.com.google.common.collect.ImmutableList;
import com.hazelcast.com.google.common.collect.Multimap;

import com.hazelcast.org.checkerframework.checker.nullness.qual.Nullable;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static com.hazelcast.org.apache.calcite.linq4j.Nullness.castNonNull;

import static java.util.Objects.requireNonNull;

/**
 * Implementation of the {@link RelMetadataProvider}
 * interface that caches results from an underlying provider.
 */
@Deprecated // to be removed before 2.0
public class CachingRelMetadataProvider implements RelMetadataProvider {
  //~ Instance fields --------------------------------------------------------

  private final Map cache = new HashMap<>();

  private final RelMetadataProvider underlyingProvider;

  private final RelOptPlanner planner;

  //~ Constructors -----------------------------------------------------------

  public CachingRelMetadataProvider(
      RelMetadataProvider underlyingProvider,
      RelOptPlanner planner) {
    this.underlyingProvider = underlyingProvider;
    this.planner = planner;
  }

  //~ Methods ----------------------------------------------------------------
  @Deprecated // to be removed before 2.0
  @Override public <@Nullable M extends @Nullable Metadata> @Nullable UnboundMetadata apply(
      Class relClass,
      final Class metadataClass) {
    final UnboundMetadata function =
        underlyingProvider.apply(relClass, metadataClass);
    if (function == null) {
      return null;
    }

    // TODO jvs 30-Mar-2006: Use meta-metadata to decide which metadata
    // query results can stay fresh until the next Ice Age.
    return (rel, mq) -> {
      final Metadata metadata = requireNonNull(function.bind(rel, mq),
          () -> "metadata must not be null, relClass=" + relClass
              + ", metadataClass=" + metadataClass);
      return metadataClass.cast(
          Proxy.newProxyInstance(metadataClass.getClassLoader(),
              new Class[]{metadataClass},
              new CachingInvocationHandler(metadata)));
    };
  }

  @Deprecated // to be removed before 2.0
  @Override public  Multimap> handlers(
      MetadataDef def) {
    return underlyingProvider.handlers(def);
  }

  @Override public List> handlers(
      Class> handlerClass) {
    return underlyingProvider.handlers(handlerClass);
  }

  //~ Inner Classes ----------------------------------------------------------

  /** An entry in the cache. Consists of the cached object and the timestamp
   * when the entry is valid. If read at a later timestamp, the entry will be
   * invalid and will be re-computed as if it did not exist. The net effect is a
   * lazy-flushing cache. */
  private static class CacheEntry {
    long timestamp;

    @Nullable Object result;
  }

  /** Implementation of {@link InvocationHandler} for calls to a
   * {@link CachingRelMetadataProvider}. Each request first looks in the cache;
   * if the cache entry is present and not expired, returns the cache entry,
   * otherwise computes the value and stores in the cache. */
  private class CachingInvocationHandler implements InvocationHandler {
    private final Metadata metadata;

    CachingInvocationHandler(Metadata metadata) {
      this.metadata = requireNonNull(metadata, "metadata");
    }

    @Override public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args)
        throws Throwable {
      // Compute hash key.
      final ImmutableList.Builder builder = ImmutableList.builder();
      builder.add(method);
      builder.add(metadata.rel());
      if (args != null) {
        for (Object arg : args) {
          // Replace null values because ImmutableList does not allow them.
          builder.add(NullSentinel.mask(arg));
        }
      }
      List key = builder.build();

      long timestamp = planner.getRelMetadataTimestamp(metadata.rel());

      // Perform cache lookup.
      CacheEntry entry = cache.get(key);
      if (entry != null) {
        if (timestamp == entry.timestamp) {
          return entry.result;
        }
      }

      // Cache miss or stale.
      try {
        Object result = method.invoke(metadata, args);
        if (result != null) {
          entry = new CacheEntry();
          entry.timestamp = timestamp;
          entry.result = result;
          cache.put(key, entry);
        }
        return result;
      } catch (InvocationTargetException e) {
        throw castNonNull(e.getCause());
      }
    }
  }
}