org.apache.calcite.sql.SqlUtil Maven / Gradle / Ivy
Show all versions of calcite-core Show documentation
/*
* 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 n
th (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 i
th 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