| /* |
| * 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.sql2rel; |
| |
| import org.apache.calcite.rex.RexNode; |
| import org.apache.calcite.sql.SqlCall; |
| import org.apache.calcite.sql.SqlNode; |
| import org.apache.calcite.sql.SqlOperator; |
| import org.apache.calcite.sql.parser.SqlParserPos; |
| |
| import org.checkerframework.checker.initialization.qual.UnderInitialization; |
| import org.checkerframework.checker.nullness.qual.Nullable; |
| import org.checkerframework.checker.nullness.qual.RequiresNonNull; |
| |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.util.HashMap; |
| import java.util.Map; |
| |
| import static com.google.common.base.Preconditions.checkArgument; |
| |
| import static org.apache.calcite.util.ReflectUtil.isPublic; |
| |
| import static java.util.Objects.requireNonNull; |
| |
| /** |
| * Implementation of {@link SqlRexConvertletTable} which uses reflection to call |
| * any method of the form <code>public RexNode convertXxx(ConvertletContext, |
| * SqlNode)</code> or <code>public RexNode convertXxx(ConvertletContext, |
| * SqlOperator, SqlCall)</code>. |
| */ |
| public class ReflectiveConvertletTable implements SqlRexConvertletTable { |
| //~ Instance fields -------------------------------------------------------- |
| |
| private final Map<Object, Object> map = new HashMap<>(); |
| |
| //~ Constructors ----------------------------------------------------------- |
| |
| public ReflectiveConvertletTable() { |
| for (final Method method : getClass().getMethods()) { |
| registerNodeTypeMethod(method); |
| registerOpTypeMethod(method); |
| } |
| } |
| |
| //~ Methods ---------------------------------------------------------------- |
| |
| /** |
| * Registers method if it: a. is public, and b. is named "convertXxx", and |
| * c. has a return type of "RexNode" or a subtype d. has a 2 parameters with |
| * types ConvertletContext and SqlNode (or a subtype) respectively. |
| */ |
| @RequiresNonNull("map") |
| private void registerNodeTypeMethod( |
| @UnderInitialization ReflectiveConvertletTable this, |
| final Method method) { |
| if (!isPublic(method)) { |
| return; |
| } |
| if (!method.getName().startsWith("convert")) { |
| return; |
| } |
| if (!RexNode.class.isAssignableFrom(method.getReturnType())) { |
| return; |
| } |
| final Class[] parameterTypes = method.getParameterTypes(); |
| if (parameterTypes.length != 2) { |
| return; |
| } |
| if (parameterTypes[0] != SqlRexContext.class) { |
| return; |
| } |
| final Class parameterType = parameterTypes[1]; |
| if (!SqlNode.class.isAssignableFrom(parameterType)) { |
| return; |
| } |
| map.put(parameterType, (SqlRexConvertlet) (cx, call) -> { |
| try { |
| @SuppressWarnings("argument.type.incompatible") |
| RexNode result = |
| (RexNode) method.invoke(ReflectiveConvertletTable.this, cx, call); |
| return requireNonNull(result, |
| () -> "null result from " + method + " for call " + call); |
| } catch (IllegalAccessException | InvocationTargetException e) { |
| throw new RuntimeException("while converting " + call, e); |
| } |
| }); |
| } |
| |
| /** |
| * Registers method if it: a. is public, and b. is named "convertXxx", and |
| * c. has a return type of "RexNode" or a subtype d. has a 3 parameters with |
| * types: ConvertletContext; SqlOperator (or a subtype), SqlCall (or a |
| * subtype). |
| */ |
| @RequiresNonNull("map") |
| private void registerOpTypeMethod( |
| @UnderInitialization ReflectiveConvertletTable this, |
| final Method method) { |
| if (!isPublic(method)) { |
| return; |
| } |
| if (!method.getName().startsWith("convert")) { |
| return; |
| } |
| if (!RexNode.class.isAssignableFrom(method.getReturnType())) { |
| return; |
| } |
| final Class[] parameterTypes = method.getParameterTypes(); |
| if (parameterTypes.length != 3) { |
| return; |
| } |
| if (parameterTypes[0] != SqlRexContext.class) { |
| return; |
| } |
| final Class opClass = parameterTypes[1]; |
| if (!SqlOperator.class.isAssignableFrom(opClass)) { |
| return; |
| } |
| final Class parameterType = parameterTypes[2]; |
| if (!SqlCall.class.isAssignableFrom(parameterType)) { |
| return; |
| } |
| map.put(opClass, (SqlRexConvertlet) (cx, call) -> { |
| try { |
| @SuppressWarnings("argument.type.incompatible") |
| RexNode result = |
| (RexNode) method.invoke(ReflectiveConvertletTable.this, cx, |
| call.getOperator(), call); |
| return requireNonNull(result, |
| () -> "null result from " + method + " for call " + call); |
| } catch (IllegalAccessException | InvocationTargetException e) { |
| throw new RuntimeException("while converting " + call, e); |
| } |
| }); |
| } |
| |
| @Override public @Nullable SqlRexConvertlet get(SqlCall call) { |
| SqlRexConvertlet convertlet; |
| final SqlOperator op = call.getOperator(); |
| |
| // Is there a convertlet for this operator |
| // (e.g. SqlStdOperatorTable.PLUS)? |
| convertlet = (SqlRexConvertlet) map.get(op); |
| if (convertlet != null) { |
| return convertlet; |
| } |
| |
| // Is there a convertlet for this class of operator |
| // (e.g. SqlBinaryOperator)? |
| Class<?> clazz = op.getClass(); |
| while (clazz != null) { |
| convertlet = (SqlRexConvertlet) map.get(clazz); |
| if (convertlet != null) { |
| return convertlet; |
| } |
| clazz = clazz.getSuperclass(); |
| } |
| |
| // Is there a convertlet for this class of expression |
| // (e.g. SqlCall)? |
| clazz = call.getClass(); |
| while (clazz != null) { |
| convertlet = (SqlRexConvertlet) map.get(clazz); |
| if (convertlet != null) { |
| return convertlet; |
| } |
| clazz = clazz.getSuperclass(); |
| } |
| return null; |
| } |
| |
| /** |
| * Registers a convertlet for a given operator instance. |
| * |
| * @param op Operator instance, say |
| * {@link org.apache.calcite.sql.fun.SqlStdOperatorTable#MINUS} |
| * @param convertlet Convertlet |
| */ |
| protected void registerOp( |
| @UnderInitialization ReflectiveConvertletTable this, |
| SqlOperator op, SqlRexConvertlet convertlet) { |
| map.put(op, convertlet); |
| } |
| |
| /** |
| * Registers that one operator is an alias for another. |
| * |
| * @param alias Operator which is alias |
| * @param target Operator to translate calls to |
| */ |
| protected void addAlias( |
| @UnderInitialization ReflectiveConvertletTable this, |
| final SqlOperator alias, final SqlOperator target) { |
| map.put( |
| alias, (SqlRexConvertlet) (cx, call) -> { |
| checkArgument(call.getOperator() == alias, "call to wrong operator"); |
| final SqlCall newCall = |
| target.createCall(SqlParserPos.ZERO, call.getOperandList()); |
| cx.getValidator().setValidatedNodeType(newCall, |
| cx.getValidator().getValidatedNodeType(call)); |
| return cx.convertExpression(newCall); |
| }); |
| } |
| } |