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

org.apache.calcite.sql.SqlUtil Maven / Gradle / Ivy

There is a newer version: 1.17.0-flink-r3
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 org.apache.calcite.sql;

import org.apache.calcite.linq4j.Ord;
import org.apache.calcite.linq4j.function.Functions;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeFactory;
import org.apache.calcite.rel.type.RelDataTypePrecedenceList;
import org.apache.calcite.runtime.CalciteContextException;
import org.apache.calcite.runtime.CalciteException;
import org.apache.calcite.runtime.Resources;
import org.apache.calcite.sql.fun.SqlStdOperatorTable;
import org.apache.calcite.sql.parser.SqlParserPos;
import org.apache.calcite.sql.type.SqlTypeFamily;
import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.calcite.sql.type.SqlTypeUtil;
import org.apache.calcite.sql.util.SqlBasicVisitor;
import org.apache.calcite.util.BarfingInvocationHandler;
import org.apache.calcite.util.ConversionUtil;
import org.apache.calcite.util.Glossary;
import org.apache.calcite.util.NlsString;
import org.apache.calcite.util.Pair;
import org.apache.calcite.util.Util;

import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterators;
import com.google.common.collect.Lists;

import java.nio.charset.Charset;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import static org.apache.calcite.util.Static.RESOURCE;

/**
 * Contains utility functions related to SQL parsing, all static.
 */
public abstract class SqlUtil {
  //~ Methods ----------------------------------------------------------------

  static SqlNode andExpressions(
      SqlNode node1,
      SqlNode node2) {
    if (node1 == null) {
      return node2;
    }
    ArrayList list = new ArrayList<>();
    if (node1.getKind() == SqlKind.AND) {
      list.addAll(((SqlCall) node1).getOperandList());
    } else {
      list.add(node1);
    }
    if (node2.getKind() == SqlKind.AND) {
      list.addAll(((SqlCall) node2).getOperandList());
    } else {
      list.add(node2);
    }
    return SqlStdOperatorTable.AND.createCall(
        SqlParserPos.ZERO,
        list);
  }

  static ArrayList flatten(SqlNode node) {
    ArrayList list = new ArrayList<>();
    flatten(node, list);
    return list;
  }

  /**
   * Returns the nth (0-based) input to a join expression.
   */
  public static SqlNode getFromNode(
      SqlSelect query,
      int ordinal) {
    ArrayList list = flatten(query.getFrom());
    return list.get(ordinal);
  }

  private static void flatten(
      SqlNode node,
      ArrayList list) {
    switch (node.getKind()) {
    case JOIN:
      SqlJoin join = (SqlJoin) node;
      flatten(
          join.getLeft(),
          list);
      flatten(
          join.getRight(),
          list);
      return;
    case AS:
      SqlCall call = (SqlCall) node;
      flatten(call.operand(0), list);
      return;
    default:
      list.add(node);
      return;
    }
  }

  /**
   * Converts an SqlNode array to a SqlNodeList
   */
  public static SqlNodeList toNodeList(SqlNode[] operands) {
    SqlNodeList ret = new SqlNodeList(SqlParserPos.ZERO);
    for (SqlNode node : operands) {
      ret.add(node);
    }
    return ret;
  }

  /**
   * Returns whether a node represents the NULL value.
   *
   * 

Examples: * *

    *
  • For {@link SqlLiteral} Unknown, returns false. *
  • For CAST(NULL AS type), returns true if * allowCast is true, false otherwise. *
  • For CAST(CAST(NULL AS type) AS type)), * returns false. *
*/ public static boolean isNullLiteral( SqlNode node, boolean allowCast) { if (node instanceof SqlLiteral) { SqlLiteral literal = (SqlLiteral) node; if (literal.getTypeName() == SqlTypeName.NULL) { assert null == literal.getValue(); return true; } else { // We don't regard UNKNOWN -- SqlLiteral(null,Boolean) -- as // NULL. return false; } } if (allowCast) { if (node.getKind() == SqlKind.CAST) { SqlCall call = (SqlCall) node; if (isNullLiteral(call.operand(0), false)) { // node is "CAST(NULL as type)" return true; } } } return false; } /** * Returns whether a node represents the NULL value or a series of nested * CAST(NULL AS type) calls. For example: * isNull(CAST(CAST(NULL as INTEGER) AS VARCHAR(1))) * returns {@code true}. */ public static boolean isNull(SqlNode node) { return isNullLiteral(node, false) || node.getKind() == SqlKind.CAST && isNull(((SqlCall) node).operand(0)); } /** * Returns whether a node is a literal. * *

Examples: * *

    *
  • For CAST(literal AS type), returns true if * allowCast is true, false otherwise. *
  • For CAST(CAST(literal AS type) AS type)), * returns false. *
* * @param node The node, never null. * @param allowCast whether to regard CAST(literal) as a literal * @return Whether the node is a literal */ public static boolean isLiteral(SqlNode node, boolean allowCast) { assert node != null; if (node instanceof SqlLiteral) { return true; } if (allowCast) { if (node.getKind() == SqlKind.CAST) { SqlCall call = (SqlCall) node; if (isLiteral(call.operand(0), false)) { // node is "CAST(literal as type)" return true; } } } return false; } /** * Returns whether a node is a literal. * *

Many constructs which require literals also accept CAST(NULL AS * type). This method does not accept casts, so you should * call {@link #isNullLiteral} first. * * @param node The node, never null. * @return Whether the node is a literal */ public static boolean isLiteral(SqlNode node) { return isLiteral(node, false); } /** * Returns whether a node is a literal chain which is used to represent a * continued string literal. * * @param node The node, never null. * @return Whether the node is a literal chain */ public static boolean isLiteralChain(SqlNode node) { assert node != null; if (node instanceof SqlCall) { SqlCall call = (SqlCall) node; return call.getKind() == SqlKind.LITERAL_CHAIN; } else { return false; } } /** * Unparses a call to an operator which has function syntax. * * @param operator The operator * @param writer Writer * @param call List of 0 or more operands */ public static void unparseFunctionSyntax( SqlOperator operator, SqlWriter writer, SqlCall call) { if (operator instanceof SqlFunction) { SqlFunction function = (SqlFunction) operator; if (function.getFunctionType().isSpecific()) { writer.keyword("SPECIFIC"); } SqlIdentifier id = function.getSqlIdentifier(); if (id == null) { writer.keyword(operator.getName()); } else { id.unparse(writer, 0, 0); } } else { writer.print(operator.getName()); } if (call.operandCount() == 0) { switch (call.getOperator().getSyntax()) { case FUNCTION_ID: // For example, the "LOCALTIME" function appears as "LOCALTIME" // when it has 0 args, not "LOCALTIME()". return; case FUNCTION_STAR: // E.g. "COUNT(*)" case FUNCTION: // E.g. "RANK()" // fall through - dealt with below } } final SqlWriter.Frame frame = writer.startList(SqlWriter.FrameTypeEnum.FUN_CALL, "(", ")"); final SqlLiteral quantifier = call.getFunctionQuantifier(); if (quantifier != null) { quantifier.unparse(writer, 0, 0); } if (call.operandCount() == 0) { switch (call.getOperator().getSyntax()) { case FUNCTION_STAR: writer.sep("*"); } } for (SqlNode operand : call.getOperandList()) { writer.sep(","); operand.unparse(writer, 0, 0); } writer.endList(frame); } public static void unparseBinarySyntax( SqlOperator operator, SqlCall call, SqlWriter writer, int leftPrec, int rightPrec) { assert call.operandCount() == 2; final SqlWriter.Frame frame = writer.startList( (operator instanceof SqlSetOperator) ? SqlWriter.FrameTypeEnum.SETOP : SqlWriter.FrameTypeEnum.SIMPLE); call.operand(0).unparse(writer, leftPrec, operator.getLeftPrec()); final boolean needsSpace = operator.needsSpace(); writer.setNeedWhitespace(needsSpace); writer.sep(operator.getName()); writer.setNeedWhitespace(needsSpace); call.operand(1).unparse(writer, operator.getRightPrec(), rightPrec); writer.endList(frame); } /** * Concatenates string literals. * *

This method takes an array of arguments, since pairwise concatenation * means too much string copying. * * @param lits an array of {@link SqlLiteral}, not empty, all of the same * class * @return a new {@link SqlLiteral}, of that same class, whose value is the * string concatenation of the values of the literals * @throws ClassCastException if the lits are not homogeneous. * @throws ArrayIndexOutOfBoundsException if lits is an empty array. */ public static SqlLiteral concatenateLiterals(List lits) { if (lits.size() == 1) { return lits.get(0); // nothing to do } return ((SqlAbstractStringLiteral) lits.get(0)).concat1(lits); } /** * Looks up a (possibly overloaded) routine based on name and argument * types. * * @param opTab operator table to search * @param funcName name of function being invoked * @param argTypes argument types * @param argNames argument names, or null if call by position * @param category whether a function or a procedure. (If a procedure is * being invoked, the overload rules are simpler.) * @param coerce if allows type coercion when filter routines by parameter types * @return matching routine, or null if none found * * @see Glossary#SQL99 SQL:1999 Part 2 Section 10.4 */ public static SqlOperator lookupRoutine(SqlOperatorTable opTab, SqlIdentifier funcName, List argTypes, List argNames, SqlFunctionCategory category, SqlSyntax syntax, SqlKind sqlKind, boolean coerce) { Iterator list = lookupSubjectRoutines( opTab, funcName, argTypes, argNames, syntax, sqlKind, category, coerce); if (list.hasNext()) { // return first on schema path return list.next(); } return null; } private static Iterator filterOperatorRoutinesByKind( Iterator routines, final SqlKind sqlKind) { return Iterators.filter(routines, operator -> Objects.requireNonNull(operator).getKind() == sqlKind); } /** * Looks up all subject routines matching the given name and argument types. * * @param opTab operator table to search * @param funcName name of function being invoked * @param argTypes argument types * @param argNames argument names, or null if call by position * @param sqlSyntax the SqlSyntax of the SqlOperator being looked up * @param sqlKind the SqlKind of the SqlOperator being looked up * @param category category of routine to look up * @param coerce if allows type coercion when filter routine by parameter types * @return list of matching routines * @see Glossary#SQL99 SQL:1999 Part 2 Section 10.4 */ public static Iterator lookupSubjectRoutines( SqlOperatorTable opTab, SqlIdentifier funcName, List argTypes, List argNames, SqlSyntax sqlSyntax, SqlKind sqlKind, SqlFunctionCategory category, boolean coerce) { // start with all routines matching by name Iterator routines = lookupSubjectRoutinesByName(opTab, funcName, sqlSyntax, category); // first pass: eliminate routines which don't accept the given // number of arguments routines = filterRoutinesByParameterCount(routines, argTypes); // NOTE: according to SQL99, procedures are NOT overloaded on type, // only on number of arguments. if (category == SqlFunctionCategory.USER_DEFINED_PROCEDURE) { return routines; } // second pass: eliminate routines which don't accept the given // argument types routines = filterRoutinesByParameterType(sqlSyntax, routines, argTypes, argNames, coerce); // see if we can stop now; this is necessary for the case // of builtin functions where we don't have param type info, // or UDF which needs type coercion. final List list = Lists.newArrayList(routines); routines = list.iterator(); if (list.size() < 2 || coerce) { return routines; } // third pass: for each parameter from left to right, eliminate // all routines except those with the best precedence match for // the given arguments routines = filterRoutinesByTypePrecedence(sqlSyntax, routines, argTypes); // fourth pass: eliminate routines which do not have the same // SqlKind as requested return filterOperatorRoutinesByKind(routines, sqlKind); } /** * Determines whether there is a routine matching the given name and number * of arguments. * * @param opTab operator table to search * @param funcName name of function being invoked * @param argTypes argument types * @param category category of routine to look up * @return true if match found */ public static boolean matchRoutinesByParameterCount( SqlOperatorTable opTab, SqlIdentifier funcName, List argTypes, SqlFunctionCategory category) { // start with all routines matching by name Iterator routines = lookupSubjectRoutinesByName(opTab, funcName, SqlSyntax.FUNCTION, category); // first pass: eliminate routines which don't accept the given // number of arguments routines = filterRoutinesByParameterCount(routines, argTypes); return routines.hasNext(); } private static Iterator lookupSubjectRoutinesByName( SqlOperatorTable opTab, SqlIdentifier funcName, final SqlSyntax syntax, SqlFunctionCategory category) { final List sqlOperators = new ArrayList<>(); opTab.lookupOperatorOverloads(funcName, category, syntax, sqlOperators); switch (syntax) { case FUNCTION: return Iterators.filter(sqlOperators.iterator(), Predicates.instanceOf(SqlFunction.class)); default: return Iterators.filter(sqlOperators.iterator(), operator -> Objects.requireNonNull(operator).getSyntax() == syntax); } } private static Iterator filterRoutinesByParameterCount( Iterator routines, final List argTypes) { return Iterators.filter(routines, operator -> Objects.requireNonNull(operator) .getOperandCountRange().isValidCount(argTypes.size())); } /** * @see Glossary#SQL99 SQL:1999 Part 2 Section 10.4 Syntax Rule 6.b.iii.2.B */ private static Iterator filterRoutinesByParameterType( SqlSyntax syntax, final Iterator routines, final List argTypes, final List argNames, final boolean coerce) { if (syntax != SqlSyntax.FUNCTION) { return routines; } //noinspection unchecked return (Iterator) Iterators.filter( Iterators.filter(routines, SqlFunction.class), function -> { List paramTypes = function.getParamTypes(); if (paramTypes == null) { // no parameter information for builtins; keep for now, // the type coerce will not work here. return true; } final List permutedArgTypes; if (argNames != null) { // Arguments passed by name. Make sure that the function has // parameters of all of these names. final Map map = new HashMap<>(); for (Ord argName : Ord.zip(argNames)) { final int i = function.getParamNames().indexOf(argName.e); if (i < 0) { return false; } map.put(i, argName.i); } permutedArgTypes = Functions.generate(paramTypes.size(), a0 -> { if (map.containsKey(a0)) { return argTypes.get(map.get(a0)); } else { return null; } }); } else { permutedArgTypes = Lists.newArrayList(argTypes); while (permutedArgTypes.size() < argTypes.size()) { paramTypes.add(null); } } for (Pair p : Pair.zip(paramTypes, permutedArgTypes)) { final RelDataType argType = p.right; final RelDataType paramType = p.left; if (argType != null && !SqlTypeUtil.canCastFrom(paramType, argType, coerce)) { return false; } } return true; }); } /** * @see Glossary#SQL99 SQL:1999 Part 2 Section 9.4 */ private static Iterator filterRoutinesByTypePrecedence( SqlSyntax sqlSyntax, Iterator routines, List argTypes) { if (sqlSyntax != SqlSyntax.FUNCTION) { return routines; } List sqlFunctions = Lists.newArrayList(Iterators.filter(routines, SqlFunction.class)); for (final Ord argType : Ord.zip(argTypes)) { final RelDataTypePrecedenceList precList = argType.e.getPrecedenceList(); final RelDataType bestMatch = bestMatch(sqlFunctions, argType.i, precList); if (bestMatch != null) { sqlFunctions = sqlFunctions.stream() .filter(function -> { final List paramTypes = function.getParamTypes(); if (paramTypes == null) { return false; } final RelDataType paramType = paramTypes.get(argType.i); return precList.compareTypePrecedence(paramType, bestMatch) >= 0; }) .collect(Collectors.toList()); } } //noinspection unchecked return (Iterator) sqlFunctions.iterator(); } private static RelDataType bestMatch(List sqlFunctions, int i, RelDataTypePrecedenceList precList) { RelDataType bestMatch = null; for (SqlFunction function : sqlFunctions) { List paramTypes = function.getParamTypes(); if (paramTypes == null) { continue; } final RelDataType paramType = paramTypes.get(i); if (bestMatch == null) { bestMatch = paramType; } else { int c = precList.compareTypePrecedence( bestMatch, paramType); if (c < 0) { bestMatch = paramType; } } } return bestMatch; } /** * Returns the ith select-list item of a query. */ public static SqlNode getSelectListItem(SqlNode query, int i) { switch (query.getKind()) { case SELECT: SqlSelect select = (SqlSelect) query; final SqlNode from = stripAs(select.getFrom()); if (from.getKind() == SqlKind.VALUES) { // They wrote "VALUES (x, y)", but the validator has // converted this into "SELECT * FROM VALUES (x, y)". return getSelectListItem(from, i); } final SqlNodeList fields = select.getSelectList(); // Range check the index to avoid index out of range. This // could be expanded to actually check to see if the select // list is a "*" if (i >= fields.size()) { i = 0; } return fields.get(i); case VALUES: SqlCall call = (SqlCall) query; assert call.operandCount() > 0 : "VALUES must have at least one operand"; final SqlCall row = call.operand(0); assert row.operandCount() > i : "VALUES has too few columns"; return row.operand(i); default: // Unexpected type of query. throw Util.needToImplement(query); } } /** * If an identifier is a legitimate call to a function which has no * arguments and requires no parentheses (for example "CURRENT_USER"), * returns a call to that function, otherwise returns null. */ public static SqlCall makeCall( SqlOperatorTable opTab, SqlIdentifier id) { if (id.names.size() == 1) { final List list = new ArrayList<>(); opTab.lookupOperatorOverloads(id, null, SqlSyntax.FUNCTION, list); for (SqlOperator operator : list) { if (operator.getSyntax() == SqlSyntax.FUNCTION_ID) { // Even though this looks like an identifier, it is a // actually a call to a function. Construct a fake // call to this function, so we can use the regular // operator validation. return new SqlBasicCall( operator, SqlNode.EMPTY_ARRAY, id.getParserPosition(), true, null); } } } return null; } public static String deriveAliasFromOrdinal(int ordinal) { // Use a '$' so that queries can't easily reference the // generated name. return "EXPR$" + ordinal; } /** * Constructs an operator signature from a type list. * * @param op operator * @param typeList list of types to use for operands. Types may be * represented as {@link String}, {@link SqlTypeFamily}, or * any object with a valid {@link Object#toString()} method. * @return constructed signature */ public static String getOperatorSignature(SqlOperator op, List typeList) { return getAliasedSignature(op, op.getName(), typeList); } /** * Constructs an operator signature from a type list, substituting an alias * for the operator name. * * @param op operator * @param opName name to use for operator * @param typeList list of {@link SqlTypeName} or {@link String} to use for * operands * @return constructed signature */ public static String getAliasedSignature( SqlOperator op, String opName, List typeList) { StringBuilder ret = new StringBuilder(); String template = op.getSignatureTemplate(typeList.size()); if (null == template) { ret.append("'"); ret.append(opName); ret.append("("); for (int i = 0; i < typeList.size(); i++) { if (i > 0) { ret.append(", "); } final String t = typeList.get(i).toString().toUpperCase(Locale.ROOT); ret.append("<").append(t).append(">"); } ret.append(")'"); } else { Object[] values = new Object[typeList.size() + 1]; values[0] = opName; ret.append("'"); for (int i = 0; i < typeList.size(); i++) { final String t = typeList.get(i).toString().toUpperCase(Locale.ROOT); values[i + 1] = "<" + t + ">"; } ret.append(new MessageFormat(template, Locale.ROOT).format(values)); ret.append("'"); assert (typeList.size() + 1) == values.length; } return ret.toString(); } /** * Wraps an exception with context. */ public static CalciteException newContextException( final SqlParserPos pos, Resources.ExInst e, String inputText) { CalciteContextException ex = newContextException(pos, e); ex.setOriginalStatement(inputText); return ex; } /** * Wraps an exception with context. */ public static CalciteContextException newContextException( final SqlParserPos pos, Resources.ExInst e) { int line = pos.getLineNum(); int col = pos.getColumnNum(); int endLine = pos.getEndLineNum(); int endCol = pos.getEndColumnNum(); return newContextException(line, col, endLine, endCol, e); } /** * Wraps an exception with context. */ public static CalciteContextException newContextException( int line, int col, int endLine, int endCol, Resources.ExInst e) { CalciteContextException contextExcn = (line == endLine && col == endCol ? RESOURCE.validatorContextPoint(line, col) : RESOURCE.validatorContext(line, col, endLine, endCol)).ex(e.ex()); contextExcn.setPosition(line, col, endLine, endCol); return contextExcn; } /** * Returns whether a {@link SqlNode node} is a {@link SqlCall call} to a * given {@link SqlOperator operator}. */ public static boolean isCallTo(SqlNode node, SqlOperator operator) { return (node instanceof SqlCall) && (((SqlCall) node).getOperator() == operator); } /** * Creates the type of an {@link org.apache.calcite.util.NlsString}. * *

The type inherits the The NlsString's {@link Charset} and * {@link SqlCollation}, if they are set, otherwise it gets the system * defaults. * * @param typeFactory Type factory * @param str String * @return Type, including collation and charset */ public static RelDataType createNlsStringType( RelDataTypeFactory typeFactory, NlsString str) { Charset charset = str.getCharset(); if (null == charset) { charset = typeFactory.getDefaultCharset(); } SqlCollation collation = str.getCollation(); if (null == collation) { collation = SqlCollation.COERCIBLE; } RelDataType type = typeFactory.createSqlType( SqlTypeName.CHAR, str.getValue().length()); type = typeFactory.createTypeWithCharsetAndCollation( type, charset, collation); return type; } /** * Translates a character set name from a SQL-level name into a Java-level * name. * * @param name SQL-level name * @return Java-level name, or null if SQL-level name is unknown */ public static String translateCharacterSetName(String name) { if (name.equals("LATIN1")) { return "ISO-8859-1"; } else if (name.equals("UTF16")) { return ConversionUtil.NATIVE_UTF16_CHARSET_NAME; } else if (name.equals(ConversionUtil.NATIVE_UTF16_CHARSET_NAME)) { // no translation needed return name; } else if (name.equals("ISO-8859-1")) { // no translation needed return name; } return null; } /** If a node is "AS", returns the underlying expression; otherwise returns * the node. */ public static SqlNode stripAs(SqlNode node) { if (node != null && node.getKind() == SqlKind.AS) { return ((SqlCall) node).operand(0); } return node; } /** Returns a list of ancestors of {@code predicate} within a given * {@code SqlNode} tree. * *

The first element of the list is {@code root}, and the last is * the node that matched {@code predicate}. Throws if no node matches. */ public static ImmutableList getAncestry(SqlNode root, Predicate predicate, Predicate postPredicate) { try { new Genealogist(predicate, postPredicate).visitChild(root); throw new AssertionError("not found: " + predicate + " in " + root); } catch (Util.FoundOne e) { //noinspection unchecked return (ImmutableList) e.getNode(); } } //~ Inner Classes ---------------------------------------------------------- /** * Handles particular {@link DatabaseMetaData} methods; invocations of other * methods will fall through to the base class, * {@link org.apache.calcite.util.BarfingInvocationHandler}, which will throw * an error. */ public static class DatabaseMetaDataInvocationHandler extends BarfingInvocationHandler { private final String databaseProductName; private final String identifierQuoteString; public DatabaseMetaDataInvocationHandler( String databaseProductName, String identifierQuoteString) { this.databaseProductName = databaseProductName; this.identifierQuoteString = identifierQuoteString; } public String getDatabaseProductName() throws SQLException { return databaseProductName; } public String getIdentifierQuoteString() throws SQLException { return identifierQuoteString; } } /** Walks over a {@link org.apache.calcite.sql.SqlNode} tree and returns the * ancestry stack when it finds a given node. */ private static class Genealogist extends SqlBasicVisitor { private final List ancestors = new ArrayList<>(); private final Predicate predicate; private final Predicate postPredicate; Genealogist(Predicate predicate, Predicate postPredicate) { this.predicate = predicate; this.postPredicate = postPredicate; } private Void check(SqlNode node) { preCheck(node); postCheck(node); return null; } private Void preCheck(SqlNode node) { if (predicate.test(node)) { throw new Util.FoundOne(ImmutableList.copyOf(ancestors)); } return null; } private Void postCheck(SqlNode node) { if (postPredicate.test(node)) { throw new Util.FoundOne(ImmutableList.copyOf(ancestors)); } return null; } private void visitChild(SqlNode node) { if (node == null) { return; } ancestors.add(node); node.accept(this); ancestors.remove(ancestors.size() - 1); } @Override public Void visit(SqlIdentifier id) { return check(id); } @Override public Void visit(SqlCall call) { preCheck(call); for (SqlNode node : call.getOperandList()) { visitChild(node); } return postCheck(call); } @Override public Void visit(SqlIntervalQualifier intervalQualifier) { return check(intervalQualifier); } @Override public Void visit(SqlLiteral literal) { return check(literal); } @Override public Void visit(SqlNodeList nodeList) { preCheck(nodeList); for (SqlNode node : nodeList) { visitChild(node); } return postCheck(nodeList); } @Override public Void visit(SqlDynamicParam param) { return check(param); } @Override public Void visit(SqlDataTypeSpec type) { return check(type); } } } // End SqlUtil.java





© 2015 - 2024 Weber Informatics LLC | Privacy Policy