diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/FunctionCatalog.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/FunctionCatalog.java index 94e63c96dc..0bdb25b5c5 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/FunctionCatalog.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/FunctionCatalog.java @@ -21,7 +21,6 @@ package com.apple.foundationdb.record.query.plan.cascades.values; import com.apple.foundationdb.record.query.plan.cascades.BuiltInFunction; -import com.apple.foundationdb.record.query.plan.cascades.typing.Type; import com.apple.foundationdb.record.query.plan.cascades.typing.Typed; import com.apple.foundationdb.record.util.ServiceLoaderProvider; import com.google.common.base.Suppliers; @@ -30,7 +29,6 @@ import org.slf4j.LoggerFactory; import javax.annotation.Nonnull; -import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; @@ -72,11 +70,7 @@ private static Map> loadFunctions( return catalogBuilder.build(); } - public static Optional> resolveAndValidate(@Nonnull final String functionName, List argumentTypes) { - return resolve(functionName, argumentTypes.size()) - .flatMap(builtInFunction -> builtInFunction.validateCall(argumentTypes)); - } - + @Nonnull @SuppressWarnings("java:S1066") public static Optional> resolve(@Nonnull final String functionName, int numberOfArguments) { BuiltInFunction builtInFunction = getFunctionCatalog().get(new FunctionKey(functionName, numberOfArguments, false)); diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/UdfFunction.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/UdfFunction.java index 0baebe73ab..6f922f613e 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/UdfFunction.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/UdfFunction.java @@ -54,7 +54,6 @@ public final String getFunctionName() { @Nonnull protected abstract UdfValue newCallsite(@Nonnull List arguments); - @SuppressWarnings("unchecked") @Nonnull @Override public final Typed encapsulate(@Nonnull final List arguments) { @@ -62,7 +61,7 @@ public final Typed encapsulate(@Nonnull final List arguments) { final List parameterTypes = getParameterTypes(); if (arguments.size() != parameterTypes.size()) { final String udfName = getFunctionName(); - throw new RecordCoreException("attempt to call '%s' with incorrect number of parameters", udfName); + throw new RecordCoreException("attempt to call " + udfName + " with incorrect number of parameters"); } final ImmutableList.Builder promotedArgumentsList = ImmutableList.builder(); diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/Expressions.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/Expressions.java index a742291095..0f6ad5648b 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/Expressions.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/Expressions.java @@ -3,7 +3,7 @@ * * This source file is part of the FoundationDB open source project * - * Copyright 2021-2024 Apple Inc. and the FoundationDB project authors + * Copyright 2021-2025 Apple Inc. and the FoundationDB project authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,6 +25,7 @@ import com.apple.foundationdb.record.query.plan.cascades.Column; import com.apple.foundationdb.record.query.plan.cascades.CorrelationIdentifier; import com.apple.foundationdb.record.query.plan.cascades.Quantifier; +import com.apple.foundationdb.record.query.plan.cascades.typing.Type; import com.apple.foundationdb.record.query.plan.cascades.values.FieldValue; import com.apple.foundationdb.record.query.plan.cascades.values.Value; import com.apple.foundationdb.relational.util.Assert; @@ -196,6 +197,11 @@ public Iterable underlying() { return Streams.stream(this).map(Expression::getUnderlying).collect(ImmutableList.toImmutableList()); } + @Nonnull + public List underlyingTypes() { + return Streams.stream(underlying()).map(Value::getResultType).collect(ImmutableList.toImmutableList()); + } + @Nonnull public Stream stream() { return underlying.stream(); @@ -229,6 +235,12 @@ public static Expressions of(@Nonnull Iterable expressions) { return new Expressions(expressions); } + @Nonnull + public static Expressions of(@Nonnull final Expression[] expressions) { + List expressionsList = ImmutableList.copyOf(expressions); + return Expressions.of(expressionsList); + } + @Nonnull public static Expressions ofSingle(@Nonnull Expression expression) { return new Expressions(ImmutableList.of(expression)); diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/SemanticAnalyzer.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/SemanticAnalyzer.java index 4cb9d5d724..54dd65036f 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/SemanticAnalyzer.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/SemanticAnalyzer.java @@ -3,7 +3,7 @@ * * This source file is part of the FoundationDB open source project * - * Copyright 2021-2024 Apple Inc. and the FoundationDB project authors + * Copyright 2021-2025 Apple Inc. and the FoundationDB project authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,7 +36,6 @@ import com.apple.foundationdb.record.query.plan.cascades.values.FieldValue; import com.apple.foundationdb.record.query.plan.cascades.values.InOpValue; import com.apple.foundationdb.record.query.plan.cascades.values.IndexableAggregateValue; -import com.apple.foundationdb.record.query.plan.cascades.values.JavaCallFunction; import com.apple.foundationdb.record.query.plan.cascades.values.LiteralValue; import com.apple.foundationdb.record.query.plan.cascades.values.NotValue; import com.apple.foundationdb.record.query.plan.cascades.values.RecordConstructorValue; @@ -52,8 +51,8 @@ import com.apple.foundationdb.relational.api.metadata.Table; import com.apple.foundationdb.relational.generated.RelationalParser; import com.apple.foundationdb.relational.recordlayer.metadata.DataTypeUtils; -import com.apple.foundationdb.relational.recordlayer.query.functions.FunctionCatalog; import com.apple.foundationdb.relational.recordlayer.query.functions.SqlFunctionCatalog; +import com.apple.foundationdb.relational.recordlayer.query.functions.SqlFunctionCatalogImpl; import com.apple.foundationdb.relational.recordlayer.query.visitors.QueryVisitor; import com.apple.foundationdb.relational.util.Assert; import com.google.common.base.Equivalence; @@ -95,10 +94,10 @@ public class SemanticAnalyzer { private final SchemaTemplate metadataCatalog; @Nonnull - private final FunctionCatalog functionCatalog; + private final SqlFunctionCatalog functionCatalog; public SemanticAnalyzer(@Nonnull SchemaTemplate metadataCatalog, - @Nonnull FunctionCatalog functionCatalog) { + @Nonnull SqlFunctionCatalog functionCatalog) { this.metadataCatalog = metadataCatalog; this.functionCatalog = functionCatalog; } @@ -723,13 +722,13 @@ public static void validateContinuation(@Nonnull Expression continuation) { } /** - * Resolves a function given its name and a list of arguments by looking it up in the {@link FunctionCatalog}. + * Resolves a function given its name and a list of arguments by looking it up in the {@link SqlFunctionCatalog}. *
* Ideally, this overload should not exist, in other words, the caller should not be responsible for determining * whether the single-item records should be flattened or not. * Currently almost all supported SQL functions do not expect {@code Record} objects, * so this is probably ok, however, this does not necessarily hold for the future. - * See {@link SqlFunctionCatalog#flattenRecordWithOneField(Typed)} for more information. + * See {@link SqlFunctionCatalogImpl#flattenRecordWithOneField(Typed)} for more information. * @param functionName The function name. * @param flattenSingleItemRecords {@code true} if single-item records should be (recursively) replaced with their * content, otherwise {@code false}. @@ -737,25 +736,25 @@ public static void validateContinuation(@Nonnull Expression continuation) { * @return A resolved SQL function {@code Expression}. */ @Nonnull - public Expression resolveFunction(@Nonnull String functionName, boolean flattenSingleItemRecords, - @Nonnull Expression... arguments) { + public Expression resolveFunction(@Nonnull final String functionName, boolean flattenSingleItemRecords, + @Nonnull final Expression... arguments) { Assert.thatUnchecked(functionCatalog.containsFunction(functionName), ErrorCode.UNSUPPORTED_QUERY, () -> String.format("Unsupported operator %s", functionName)); - final var builtInFunction = functionCatalog.lookUpFunction(functionName); + final var builtInFunction = functionCatalog.lookUpFunction(functionName, arguments); List argumentList = new ArrayList<>(); argumentList.addAll(List.of(arguments)); if (BITMAP_SCALAR_FUNCTIONS.contains(functionName.toLowerCase(Locale.ROOT))) { argumentList.add(Expression.ofUnnamed(new LiteralValue<>(BITMAP_DEFAULT_ENTRY_SIZE))); } final List valueArgs = argumentList.stream().map(Expression::getUnderlying) - .map(v -> flattenSingleItemRecords ? SqlFunctionCatalog.flattenRecordWithOneField(v) : v) + .map(v -> flattenSingleItemRecords ? SqlFunctionCatalogImpl.flattenRecordWithOneField(v) : v) .collect(ImmutableList.toImmutableList()); final var resultingValue = Assert.castUnchecked(builtInFunction.encapsulate(valueArgs), Value.class); return Expression.ofUnnamed(DataTypeUtils.toRelationalType(resultingValue.getResultType()), resultingValue); } - public boolean isUdfFunction(@Nonnull String functionName) { - return functionCatalog.lookUpFunction(functionName).getClass().equals(JavaCallFunction.class); + public boolean isUdfFunction(@Nonnull final String functionName) { + return functionCatalog.isUdfFunction(functionName); } public boolean containsReferencesTo(@Nonnull final ParseTree parseTree, diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/functions/AutoServiceFunctionCatalog.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/functions/AutoServiceFunctionCatalog.java deleted file mode 100644 index b80c1a86cd..0000000000 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/functions/AutoServiceFunctionCatalog.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * AutoServiceFunctionCatalog.java - * - * This source file is part of the FoundationDB open source project - * - * Copyright 2021-2024 Apple Inc. and the FoundationDB project authors - * - * 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 com.apple.foundationdb.relational.recordlayer.query.functions; - -import com.apple.foundationdb.annotation.API; - -import com.apple.foundationdb.record.query.plan.cascades.BuiltInFunction; -import com.apple.foundationdb.record.query.plan.cascades.typing.Typed; -import com.apple.foundationdb.record.util.ServiceLoaderProvider; -import com.apple.foundationdb.relational.api.exceptions.ErrorCode; -import com.apple.foundationdb.relational.util.Assert; - -import com.google.common.collect.BiMap; -import com.google.common.collect.ImmutableBiMap; - -import javax.annotation.Nonnull; -import java.util.Locale; - -@API(API.Status.EXPERIMENTAL) -public final class AutoServiceFunctionCatalog implements FunctionCatalog { - - @Nonnull - private static final AutoServiceFunctionCatalog INSTANCE = new AutoServiceFunctionCatalog(); - - @Nonnull - private final BiMap> registeredFunctions; - - private AutoServiceFunctionCatalog() { - this.registeredFunctions = lookUpAllLoadedFunctions(); - } - - @Nonnull - @Override - public BuiltInFunction lookUpFunction(@Nonnull String name) { - return Assert.notNullUnchecked(registeredFunctions.get(name), ErrorCode.INTERNAL_ERROR, - () -> String.format("could not find function %s", name)); - } - - @SuppressWarnings("unchecked") - @Nonnull - BiMap> lookUpAllLoadedFunctions() { - final Iterable> builtInFunctions = (Iterable>) (Object) - ServiceLoaderProvider.load(BuiltInFunction.class); - final ImmutableBiMap.Builder> resultBuilder = ImmutableBiMap.builder(); - builtInFunctions.forEach(builtInFunction -> resultBuilder.put(builtInFunction.getFunctionName().toLowerCase(Locale.ROOT), builtInFunction)); - return resultBuilder.build(); - } - - @Override - public boolean containsFunction(@Nonnull String name) { - return registeredFunctions.containsKey(name); - } - - @Nonnull - public static AutoServiceFunctionCatalog instance() { - return INSTANCE; - } -} diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/functions/FunctionCatalog.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/functions/FunctionCatalog.java deleted file mode 100644 index b6f13ea996..0000000000 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/functions/FunctionCatalog.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * FunctionCatalog.java - * - * This source file is part of the FoundationDB open source project - * - * Copyright 2021-2024 Apple Inc. and the FoundationDB project authors - * - * 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 com.apple.foundationdb.relational.recordlayer.query.functions; - -import com.apple.foundationdb.record.query.plan.cascades.BuiltInFunction; -import com.apple.foundationdb.record.query.plan.cascades.typing.Typed; - -import javax.annotation.Nonnull; - -public interface FunctionCatalog { - @Nonnull - BuiltInFunction lookUpFunction(@Nonnull String name); - - boolean containsFunction(@Nonnull String name); -} diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/functions/SqlFunctionCatalog.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/functions/SqlFunctionCatalog.java index 1abfabda9c..47d09261dc 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/functions/SqlFunctionCatalog.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/functions/SqlFunctionCatalog.java @@ -1,9 +1,9 @@ /* - * SqlFunctionCatalog.java + * FunctionCatalog.java * * This source file is part of the FoundationDB open source project * - * Copyright 2021-2024 Apple Inc. and the FoundationDB project authors + * Copyright 2021-2025 Apple Inc. and the FoundationDB project authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,127 +20,17 @@ package com.apple.foundationdb.relational.recordlayer.query.functions; -import com.apple.foundationdb.annotation.API; - import com.apple.foundationdb.record.query.plan.cascades.BuiltInFunction; import com.apple.foundationdb.record.query.plan.cascades.typing.Typed; -import com.apple.foundationdb.record.query.plan.cascades.values.RecordConstructorValue; -import com.apple.foundationdb.record.query.plan.cascades.values.Value; -import com.apple.foundationdb.relational.util.Assert; - -import com.google.common.collect.ImmutableMap; +import com.apple.foundationdb.relational.recordlayer.query.Expression; import javax.annotation.Nonnull; -import java.util.Locale; -import java.util.stream.StreamSupport; - -import static java.util.stream.Collectors.toList; - -/** - * A catalog of built-in SQL functions. - */ -@API(API.Status.EXPERIMENTAL) -public final class SqlFunctionCatalog implements FunctionCatalog { - - @Nonnull - private static final AutoServiceFunctionCatalog underlying = AutoServiceFunctionCatalog.instance(); - - @Nonnull - private static final SqlFunctionCatalog INSTANCE = new SqlFunctionCatalog(); +public interface SqlFunctionCatalog { @Nonnull - private final ImmutableMap> synonyms; + BuiltInFunction lookUpFunction(@Nonnull String name, @Nonnull Expression... expressions); - private SqlFunctionCatalog() { - this.synonyms = createSynonyms(); - } + boolean containsFunction(@Nonnull String name); - @Nonnull - @Override - public BuiltInFunction lookUpFunction(@Nonnull String name) { - return Assert.notNullUnchecked(synonyms.get(name.toLowerCase(Locale.ROOT))); - } - - @Override - public boolean containsFunction(@Nonnull String name) { - return synonyms.containsKey(name.toLowerCase(Locale.ROOT)); - } - - @Nonnull - private static ImmutableMap> createSynonyms() { - return ImmutableMap.>builder() - .put("+", underlying.lookUpFunction("add")) - .put("-", underlying.lookUpFunction("sub")) - .put("*", underlying.lookUpFunction("mul")) - .put("/", underlying.lookUpFunction("div")) - .put("%", underlying.lookUpFunction("mod")) - .put(">", underlying.lookUpFunction("gt")) - .put(">=", underlying.lookUpFunction("gte")) - .put("<", underlying.lookUpFunction("lt")) - .put("<=", underlying.lookUpFunction("lte")) - .put("=", underlying.lookUpFunction("equals")) - .put("<>", underlying.lookUpFunction("notequals")) - .put("!=", underlying.lookUpFunction("notequals")) - .put("&", underlying.lookUpFunction("bitand")) - .put("|", underlying.lookUpFunction("bitor")) - .put("^", underlying.lookUpFunction("bitxor")) - .put("bitmap_bit_position", underlying.lookUpFunction("bitmap_bit_position")) - .put("bitmap_bucket_offset", underlying.lookUpFunction("bitmap_bucket_offset")) - .put("bitmap_construct_agg", underlying.lookUpFunction("bitmap_construct_agg")) - .put("not", underlying.lookUpFunction("not")) - .put("and", underlying.lookUpFunction("and")) - .put("or", underlying.lookUpFunction("or")) - .put("count", underlying.lookUpFunction("count")) - .put("max", underlying.lookUpFunction("max")) - .put("min", underlying.lookUpFunction("min")) - .put("avg", underlying.lookUpFunction("avg")) - .put("sum", underlying.lookUpFunction("sum")) - .put("max_ever", underlying.lookUpFunction("max_ever")) - .put("min_ever", underlying.lookUpFunction("min_ever")) - .put("java_call", underlying.lookUpFunction("java_call")) - .put("greatest", underlying.lookUpFunction("greatest")) - .put("least", underlying.lookUpFunction("least")) - .put("like", underlying.lookUpFunction("like")) - .put("in", underlying.lookUpFunction("in")) - .put("coalesce", underlying.lookUpFunction("coalesce")) - .put("is null", underlying.lookUpFunction("isnull")) - .put("is not null", underlying.lookUpFunction("notnull")) - .put("__pattern_for_like", underlying.lookUpFunction("patternforlike")) - .put("__internal_array", underlying.lookUpFunction("array")) - .build(); - } - - @Nonnull - public static SqlFunctionCatalog instance() { - return INSTANCE; - } - - /** - * A utility method that transforms a single-item {@link RecordConstructorValue} value into its inner {@link Value}. - * This is mainly used for deterministically distinguishing between: - *
    - *
  • Single item record constructor
  • - *
  • Order of operations
  • - *
- * Currently, all of our SQL functions are assumed to be working with primitives, therefore, when there is a scenario - * where the arguments can be interpreted as either single-item records or to indicate order of operations, - * the precedence is always given to the latter. - * For example, the argument {@code (3+4)} in this expression {@code (3+4)*5} is considered to correspond to a - * single integer which is the result of {@code 3+4} as opposed to a single-item record whose element is {@code 3+4}. - * - * @param value The value to potentially simplify - * @return if the {@code value} is a single-item record, then the content of the {@code value} is recursively checked - * and returned, otherwise, the {@code value} itself is returned without modification. - */ - @Nonnull - public static Typed flattenRecordWithOneField(@Nonnull final Typed value) { - if (value instanceof RecordConstructorValue && ((RecordConstructorValue) value).getColumns().size() == 1) { - return flattenRecordWithOneField(((Value) value).getChildren().iterator().next()); - } - if (value instanceof Value) { - return ((Value) value).withChildren(StreamSupport.stream(((Value) value).getChildren().spliterator(), false) - .map(SqlFunctionCatalog::flattenRecordWithOneField).map(v -> (Value) v).collect(toList())); - } - return value; - } + boolean isUdfFunction(@Nonnull String name); } diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/functions/SqlFunctionCatalogImpl.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/functions/SqlFunctionCatalogImpl.java new file mode 100644 index 0000000000..31ee33c108 --- /dev/null +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/functions/SqlFunctionCatalogImpl.java @@ -0,0 +1,152 @@ +/* + * SqlFunctionCatalog.java + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2021-2025 Apple Inc. and the FoundationDB project authors + * + * 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 com.apple.foundationdb.relational.recordlayer.query.functions; + +import com.apple.foundationdb.annotation.API; + +import com.apple.foundationdb.record.query.plan.cascades.BuiltInFunction; +import com.apple.foundationdb.record.query.plan.cascades.typing.Typed; +import com.apple.foundationdb.record.query.plan.cascades.values.FunctionCatalog; +import com.apple.foundationdb.record.query.plan.cascades.values.RecordConstructorValue; +import com.apple.foundationdb.record.query.plan.cascades.values.Value; +import com.apple.foundationdb.relational.recordlayer.query.Expression; +import com.apple.foundationdb.relational.util.Assert; + +import com.google.common.collect.ImmutableMap; + +import javax.annotation.Nonnull; +import java.util.Locale; +import java.util.Objects; +import java.util.function.Function; +import java.util.stream.StreamSupport; + +import static java.util.stream.Collectors.toList; + +/** + * A catalog of built-in SQL functions. + */ +@API(API.Status.EXPERIMENTAL) +public final class SqlFunctionCatalogImpl implements SqlFunctionCatalog { + + @Nonnull + private static final SqlFunctionCatalogImpl INSTANCE = new SqlFunctionCatalogImpl(); + + @Nonnull + private final ImmutableMap>> synonyms; + + private SqlFunctionCatalogImpl() { + this.synonyms = createSynonyms(); + } + + @Nonnull + @Override + public BuiltInFunction lookUpFunction(@Nonnull final String name, @Nonnull final Expression... expressions) { + return Assert.notNullUnchecked(Objects.requireNonNull(synonyms.get(name.toLowerCase(Locale.ROOT))).apply(expressions.length)); + } + + @Override + public boolean containsFunction(@Nonnull String name) { + return synonyms.containsKey(name.toLowerCase(Locale.ROOT)); + } + + @Override + public boolean isUdfFunction(@Nonnull final String name) { + return "java_call".equals(name.trim().toLowerCase(Locale.ROOT)); + } + + @Nonnull + private static ImmutableMap>> createSynonyms() { + return ImmutableMap.>>builder() + .put("+", argumentsCount -> FunctionCatalog.resolve("add", argumentsCount).orElseThrow()) + .put("-", argumentsCount -> FunctionCatalog.resolve("sub", argumentsCount).orElseThrow()) + .put("*", argumentsCount -> FunctionCatalog.resolve("mul", argumentsCount).orElseThrow()) + .put("/", argumentsCount -> FunctionCatalog.resolve("div", argumentsCount).orElseThrow()) + .put("%", argumentsCount -> FunctionCatalog.resolve("mod", argumentsCount).orElseThrow()) + .put(">", argumentsCount -> FunctionCatalog.resolve("gt", argumentsCount).orElseThrow()) + .put(">=", argumentsCount -> FunctionCatalog.resolve("gte", argumentsCount).orElseThrow()) + .put("<", argumentsCount -> FunctionCatalog.resolve("lt", argumentsCount).orElseThrow()) + .put("<=", argumentsCount -> FunctionCatalog.resolve("lte", argumentsCount).orElseThrow()) + .put("=", argumentsCount -> FunctionCatalog.resolve("equals", argumentsCount).orElseThrow()) + .put("<>", argumentsCount -> FunctionCatalog.resolve("notEquals", argumentsCount).orElseThrow()) + .put("!=", argumentsCount -> FunctionCatalog.resolve("notEquals", argumentsCount).orElseThrow()) + .put("&", argumentsCount -> FunctionCatalog.resolve("bitand", argumentsCount).orElseThrow()) + .put("|", argumentsCount -> FunctionCatalog.resolve("bitor", argumentsCount).orElseThrow()) + .put("^", argumentsCount -> FunctionCatalog.resolve("bitxor", argumentsCount).orElseThrow()) + .put("bitmap_bit_position", argumentsCount -> FunctionCatalog.resolve("bitmap_bit_position", 1 + argumentsCount).orElseThrow()) + .put("bitmap_bucket_offset", argumentsCount -> FunctionCatalog.resolve("bitmap_bucket_offset", 1 + argumentsCount).orElseThrow()) + .put("bitmap_construct_agg", argumentsCount -> FunctionCatalog.resolve("BITMAP_CONSTRUCT_AGG", argumentsCount).orElseThrow()) + .put("not", argumentsCount -> FunctionCatalog.resolve("not", argumentsCount).orElseThrow()) + .put("and", argumentsCount -> FunctionCatalog.resolve("and", argumentsCount).orElseThrow()) + .put("or", argumentsCount -> FunctionCatalog.resolve("or", argumentsCount).orElseThrow()) + .put("count", argumentsCount -> FunctionCatalog.resolve("COUNT", argumentsCount).orElseThrow()) + .put("max", argumentsCount -> FunctionCatalog.resolve("MAX", argumentsCount).orElseThrow()) + .put("min", argumentsCount -> FunctionCatalog.resolve("MIN", argumentsCount).orElseThrow()) + .put("avg", argumentsCount -> FunctionCatalog.resolve("AVG", argumentsCount).orElseThrow()) + .put("sum", argumentsCount -> FunctionCatalog.resolve("SUM", argumentsCount).orElseThrow()) + .put("max_ever", argumentsCount -> FunctionCatalog.resolve("MAX_EVER", argumentsCount).orElseThrow()) + .put("min_ever", argumentsCount -> FunctionCatalog.resolve("MIN_EVER", argumentsCount).orElseThrow()) + .put("java_call", argumentsCount -> FunctionCatalog.resolve("java_call", argumentsCount).orElseThrow()) + .put("greatest", argumentsCount -> FunctionCatalog.resolve("greatest", argumentsCount).orElseThrow()) + .put("least", argumentsCount -> FunctionCatalog.resolve("least", argumentsCount).orElseThrow()) + .put("like", argumentsCount -> FunctionCatalog.resolve("like", argumentsCount).orElseThrow()) + .put("in", argumentsCount -> FunctionCatalog.resolve("in", argumentsCount).orElseThrow()) + .put("coalesce", argumentsCount -> FunctionCatalog.resolve("coalesce", argumentsCount).orElseThrow()) + .put("is null", argumentsCount -> FunctionCatalog.resolve("isNull", argumentsCount).orElseThrow()) + .put("is not null", argumentsCount -> FunctionCatalog.resolve("notNull", argumentsCount).orElseThrow()) + .put("__pattern_for_like", argumentsCount -> FunctionCatalog.resolve("patternForLike", argumentsCount).orElseThrow()) + .put("__internal_array", argumentsCount -> FunctionCatalog.resolve("array", argumentsCount).orElseThrow()) + .build(); + } + + @Nonnull + public static SqlFunctionCatalogImpl instance() { + return INSTANCE; + } + + /** + * A utility method that transforms a single-item {@link RecordConstructorValue} value into its inner {@link Value}. + * This is mainly used for deterministically distinguishing between: + *
    + *
  • Single item record constructor
  • + *
  • Order of operations
  • + *
+ * Currently, all of our SQL functions are assumed to be working with primitives, therefore, when there is a scenario + * where the arguments can be interpreted as either single-item records or to indicate order of operations, + * the precedence is always given to the latter. + * For example, the argument {@code (3+4)} in this expression {@code (3+4)*5} is considered to correspond to a + * single integer which is the result of {@code 3+4} as opposed to a single-item record whose element is {@code 3+4}. + * + * @param value The value to potentially simplify + * @return if the {@code value} is a single-item record, then the content of the {@code value} is recursively checked + * and returned, otherwise, the {@code value} itself is returned without modification. + */ + @Nonnull + public static Typed flattenRecordWithOneField(@Nonnull final Typed value) { + if (value instanceof RecordConstructorValue && ((RecordConstructorValue) value).getColumns().size() == 1) { + return flattenRecordWithOneField(((Value) value).getChildren().iterator().next()); + } + if (value instanceof Value) { + return ((Value) value).withChildren(StreamSupport.stream(((Value) value).getChildren().spliterator(), false) + .map(SqlFunctionCatalogImpl::flattenRecordWithOneField).map(v -> (Value) v).collect(toList())); + } + return value; + } +} diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/BaseVisitor.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/BaseVisitor.java index 6b34d02e7a..cfbb11fb9e 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/BaseVisitor.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/BaseVisitor.java @@ -45,8 +45,8 @@ import com.apple.foundationdb.relational.recordlayer.query.QueryPlan; import com.apple.foundationdb.relational.recordlayer.query.SemanticAnalyzer; import com.apple.foundationdb.relational.recordlayer.query.StringTrieNode; -import com.apple.foundationdb.relational.recordlayer.query.functions.FunctionCatalog; import com.apple.foundationdb.relational.recordlayer.query.functions.SqlFunctionCatalog; +import com.apple.foundationdb.relational.recordlayer.query.functions.SqlFunctionCatalogImpl; import com.apple.foundationdb.relational.util.Assert; import org.antlr.v4.runtime.tree.AbstractParseTreeVisitor; import org.antlr.v4.runtime.tree.ParseTree; @@ -160,8 +160,8 @@ public LogicalOperatorCatalog getLogicalOperatorCatalog() { } @Nonnull - public FunctionCatalog getFunctionCatalog() { - return SqlFunctionCatalog.instance(); + public SqlFunctionCatalog getFunctionCatalog() { + return SqlFunctionCatalogImpl.instance(); } @Nonnull