blob: 52e2e2812801a5477a030d77dd9d08df89f97513 [file] [log] [blame]
/*
* 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 org.apache.calcite.sql.fun;
import org.apache.calcite.sql.SqlOperator;
import org.apache.calcite.sql.SqlOperatorTable;
import org.apache.calcite.sql.util.SqlOperatorTables;
import org.apache.calcite.util.Util;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import static java.util.Objects.requireNonNull;
/**
* Factory that creates operator tables that consist of functions and operators
* for particular named libraries. For example, the following code will return
* an operator table that contains operators for both Oracle and MySQL:
*
* <blockquote>
* <pre>SqlOperatorTable opTab =
* SqlLibraryOperatorTableFactory.INSTANCE.getOperatorTable(
* EnumSet.of(SqlLibrary.ORACLE, SqlLibrary.MYSQL))</pre>
* </blockquote>
*
* <p>To define a new library, add a value to enum {@link SqlLibrary}.
*
* <p>To add functions to a library, add the {@link LibraryOperator} annotation
* to fields that of type {@link SqlOperator}, and the library name to its
* {@link LibraryOperator#libraries()} field.
*/
public class SqlLibraryOperatorTableFactory {
/** List of classes to scan for operators. */
private final ImmutableList<Class> classes;
/** The singleton instance. */
public static final SqlLibraryOperatorTableFactory INSTANCE =
new SqlLibraryOperatorTableFactory(SqlLibraryOperators.class);
private SqlLibraryOperatorTableFactory(Class... classes) {
this.classes = ImmutableList.copyOf(classes);
}
//~ Instance fields --------------------------------------------------------
/** A cache that returns an operator table for a given library (or set of
* libraries). */
@SuppressWarnings("methodref.receiver.bound.invalid")
private final LoadingCache<ImmutableSet<SqlLibrary>, SqlOperatorTable> cache =
CacheBuilder.newBuilder().build(CacheLoader.from(this::create));
//~ Methods ----------------------------------------------------------------
/** Creates an operator table that contains operators in the given set of
* libraries. */
private SqlOperatorTable create(ImmutableSet<SqlLibrary> librarySet) {
final List<SqlOperator> list = new ArrayList<>();
boolean custom = false;
boolean standard = false;
for (SqlLibrary library : librarySet) {
switch (library) {
case STANDARD:
standard = true;
break;
case SPATIAL:
list.addAll(SqlOperatorTables.spatialInstance().getOperatorList());
break;
default:
custom = true;
}
}
// Use reflection to register the expressions stored in public fields.
// Skip if the only libraries asked for are "standard" or "spatial".
if (custom) {
for (Class aClass : classes) {
for (Field field : aClass.getFields()) {
try {
if (SqlOperator.class.isAssignableFrom(field.getType())) {
final SqlOperator op =
(SqlOperator) requireNonNull(field.get(this),
() -> "null value of " + field + " for " + this);
if (operatorIsInLibrary(op.getName(), field, librarySet)) {
list.add(op);
}
}
} catch (IllegalArgumentException | IllegalAccessException e) {
throw Util.throwAsRuntime(Util.causeOrSelf(e));
}
}
}
}
SqlOperatorTable operatorTable = SqlOperatorTables.of(list);
if (standard) {
operatorTable =
SqlOperatorTables.chain(SqlStdOperatorTable.instance(),
operatorTable);
}
return operatorTable;
}
/** Returns whether an operator is in one or more of the given libraries. */
private static boolean operatorIsInLibrary(String operatorName, Field field,
Set<SqlLibrary> seekLibrarySet) {
LibraryOperator libraryOperator =
field.getAnnotation(LibraryOperator.class);
if (libraryOperator == null) {
throw new AssertionError("Operator must have annotation: "
+ operatorName);
}
SqlLibrary[] librarySet = libraryOperator.libraries();
if (librarySet.length <= 0) {
throw new AssertionError("Operator must belong to at least one library: "
+ operatorName);
}
for (SqlLibrary library : librarySet) {
if (seekLibrarySet.contains(library)) {
return true;
}
}
return false;
}
/** Returns a SQL operator table that contains operators in the given library
* or libraries. */
public SqlOperatorTable getOperatorTable(SqlLibrary... libraries) {
return getOperatorTable(ImmutableSet.copyOf(libraries));
}
/** Returns a SQL operator table that contains operators in the given set of
* libraries. */
public SqlOperatorTable getOperatorTable(Iterable<SqlLibrary> librarySet) {
return getOperatorTable(librarySet, true);
}
/** Returns a SQL operator table that contains operators in the given set of
* libraries, optionally inheriting in operators in {@link SqlLibrary#ALL}. */
public SqlOperatorTable getOperatorTable(Iterable<SqlLibrary> librarySet,
boolean includeAll) {
try {
// Expand so that 'hive' becomes 'all,hive';
// ensures that operators with library=all get loaded.
final List<SqlLibrary> expandedLibrarySet =
includeAll
? SqlLibrary.expandUp(librarySet)
: ImmutableList.copyOf(librarySet);
return cache.get(ImmutableSet.copyOf(expandedLibrarySet));
} catch (ExecutionException e) {
throw Util.throwAsRuntime("populating SqlOperatorTable for library "
+ librarySet, Util.causeOrSelf(e));
}
}
}