/*
* 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.eigenbase.sql;
import java.nio.charset.*;
import java.sql.*;
import java.text.*;
import java.util.*;
import org.eigenbase.reltype.*;
import org.eigenbase.resource.Resources;
import org.eigenbase.sql.fun.*;
import org.eigenbase.sql.parser.*;
import org.eigenbase.sql.type.*;
import org.eigenbase.util.*;
import org.eigenbase.util14.*;
import com.google.common.collect.Lists;
import static org.eigenbase.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<SqlNode> list = new ArrayList<SqlNode>();
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<SqlNode> flatten(SqlNode node) {
ArrayList<SqlNode> list = new ArrayList<SqlNode>();
flatten(node, list);
return list;
}
/**
* Returns the <code>n</code>th (0-based) input to a join expression.
*/
public static SqlNode getFromNode(
SqlSelect query,
int ordinal) {
ArrayList<SqlNode> list = flatten(query.getFrom());
return list.get(ordinal);
}
private static void flatten(
SqlNode node,
ArrayList<SqlNode> 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.
*
* <p>Examples:
*
* <ul>
* <li>For {@link SqlLiteral} Unknown, returns false.
* <li>For <code>CAST(NULL AS <i>type</i>)</code>, returns true if <code>
* allowCast</code> is true, false otherwise.
* <li>For <code>CAST(CAST(NULL AS <i>type</i>) AS <i>type</i>))</code>,
* returns false.
* </ul>
*/
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
* <code>CAST(NULL AS type)</code> calls. For example:
* <code>isNull(CAST(CAST(NULL as INTEGER) AS VARCHAR(1)))</code>
* 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.
*
* <p>Many constructs which require literals also accept <code>CAST(NULL AS
* <i>type</i>)</code>. 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) {
assert node != null;
return node instanceof SqlLiteral;
}
/**
* 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;
switch (function.getFunctionType()) {
case USER_DEFINED_SPECIFIC_FUNCTION:
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) {
SqlBinaryOperator binop = (SqlBinaryOperator) operator;
assert call.operandCount() == 2;
final SqlWriter.Frame frame =
writer.startList(
(binop instanceof SqlSetOperator)
? SqlWriter.FrameTypeEnum.SETOP
: SqlWriter.FrameTypeEnum.SIMPLE);
call.operand(0).unparse(writer, leftPrec, binop.getLeftPrec());
final boolean needsSpace = binop.needsSpace();
writer.setNeedWhitespace(needsSpace);
writer.sep(binop.getName());
writer.setNeedWhitespace(needsSpace);
call.operand(1).unparse(writer, binop.getRightPrec(), rightPrec);
writer.endList(frame);
}
/**
* Concatenates string literals.
*
* <p>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<SqlLiteral> 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 category whether a function or a procedure. (If a procedure is
* being invoked, the overload rules are simpler.)
* @return matching routine, or null if none found
* @sql.99 Part 2 Section 10.4
*/
public static SqlFunction lookupRoutine(
SqlOperatorTable opTab,
SqlIdentifier funcName,
List<RelDataType> argTypes,
SqlFunctionCategory category) {
List<SqlFunction> list =
lookupSubjectRoutines(
opTab,
funcName,
argTypes,
category);
if (list.isEmpty()) {
return null;
} else {
// return first on schema path
return list.get(0);
}
}
/**
* 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 category category of routine to look up
* @return list of matching routines
* @sql.99 Part 2 Section 10.4
*/
public static List<SqlFunction> lookupSubjectRoutines(
SqlOperatorTable opTab,
SqlIdentifier funcName,
List<RelDataType> argTypes,
SqlFunctionCategory category) {
// start with all routines matching by name
List<SqlFunction> routines =
lookupSubjectRoutinesByName(opTab, funcName, category);
// first pass: eliminate routines which don't accept the given
// number of arguments
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
filterRoutinesByParameterType(routines, argTypes);
// see if we can stop now; this is necessary for the case
// of builtin functions where we don't have param type info
if (routines.size() < 2) {
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
filterRoutinesByTypePrecedence(routines, argTypes);
return routines;
}
/**
* Determine if 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<RelDataType> argTypes,
SqlFunctionCategory category) {
// start with all routines matching by name
List<SqlFunction> routines =
lookupSubjectRoutinesByName(opTab, funcName, category);
// first pass: eliminate routines which don't accept the given
// number of arguments
filterRoutinesByParameterCount(routines, argTypes);
return routines.size() > 0;
}
private static List<SqlFunction> lookupSubjectRoutinesByName(
SqlOperatorTable opTab,
SqlIdentifier funcName,
SqlFunctionCategory category) {
final List<SqlOperator> operators = Lists.newArrayList();
opTab.lookupOperatorOverloads(funcName, category, SqlSyntax.FUNCTION,
operators);
List<SqlFunction> routines = new ArrayList<SqlFunction>();
for (SqlOperator operator : operators) {
if (operator instanceof SqlFunction) {
routines.add((SqlFunction) operator);
}
}
return routines;
}
private static void filterRoutinesByParameterCount(
List<SqlFunction> routines,
List<RelDataType> argTypes) {
Iterator<SqlFunction> iter = routines.iterator();
while (iter.hasNext()) {
SqlFunction function = iter.next();
SqlOperandCountRange od = function.getOperandCountRange();
if (!od.isValidCount(argTypes.size())) {
iter.remove();
}
}
}
/**
* @sql.99 Part 2 Section 10.4 Syntax Rule 6.b.iii.2.B
*/
private static void filterRoutinesByParameterType(
List<SqlFunction> routines,
List<RelDataType> argTypes) {
Iterator<SqlFunction> iter = routines.iterator();
while (iter.hasNext()) {
SqlFunction function = iter.next();
List<RelDataType> paramTypes = function.getParamTypes();
if (paramTypes == null) {
// no parameter information for builtins; keep for now
continue;
}
assert paramTypes.size() == argTypes.size();
boolean keep = true;
for (Pair<RelDataType, RelDataType> p : Pair.zip(paramTypes, argTypes)) {
final RelDataType argType = p.right;
final RelDataType paramType = p.left;
if (!SqlTypeUtil.canAssignFrom(paramType, argType)) {
keep = false;
break;
}
}
if (!keep) {
iter.remove();
}
}
}
/**
* @sql.99 Part 2 Section 9.4
*/
private static void filterRoutinesByTypePrecedence(
List<SqlFunction> routines,
List<RelDataType> argTypes) {
for (int i = 0; i < argTypes.size(); ++i) {
RelDataTypePrecedenceList precList =
argTypes.get(i).getPrecedenceList();
RelDataType bestMatch = null;
for (SqlFunction function : routines) {
List<RelDataType> 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;
}
}
}
if (bestMatch != null) {
Iterator<SqlFunction> iter = routines.iterator();
while (iter.hasNext()) {
SqlFunction function = iter.next();
List<RelDataType> paramTypes = function.getParamTypes();
int c;
if (paramTypes == null) {
c = -1;
} else {
c = precList.compareTypePrecedence(
paramTypes.get(i),
bestMatch);
}
if (c < 0) {
iter.remove();
}
}
}
}
}
/**
* Returns the <code>i</code>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<SqlOperator> list = Lists.newArrayList();
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(", ");
}
ret.append("<").append(
typeList.get(i).toString().toUpperCase()).append(">");
}
ret.append(")'");
} else {
Object[] values = new Object[typeList.size() + 1];
values[0] = opName;
ret.append("'");
for (int i = 0; i < typeList.size(); i++) {
values[i + 1] = "<" + typeList.get(i).toString().toUpperCase() + ">";
}
ret.append(MessageFormat.format(template, values));
ret.append("'");
assert (typeList.size() + 1) == values.length;
}
return ret.toString();
}
/**
* Wraps an exception with context.
*/
public static EigenbaseException newContextException(
final SqlParserPos pos,
Resources.ExInst<?> e,
String inputText) {
EigenbaseContextException ex = newContextException(pos, e);
ex.setOriginalStatement(inputText);
return ex;
}
/**
* Wraps an exception with context.
*/
public static EigenbaseContextException 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 EigenbaseContextException newContextException(
int line,
int col,
int endLine,
int endCol,
Resources.ExInst<?> e) {
EigenbaseContextException 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 NlsString}.
*
* <p>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) {
switch (node.getKind()) {
case AS:
return ((SqlCall) node).operand(0);
default:
return node;
}
}
//~ Inner Classes ----------------------------------------------------------
/**
* Handles particular {@link DatabaseMetaData} methods; invocations of other
* methods will fall through to the base class, {@link
* 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;
}
}
}
// End SqlUtil.java