/*
* JBoss, Home of Professional Open Source.
* See the COPYRIGHT.txt file distributed with this work for information
* regarding copyright ownership. Some portions may be licensed
* to Red Hat, Inc. under one or more contributor license agreements.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*/
package org.teiid.query.parser;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.teiid.core.types.DataTypeManager;
import org.teiid.core.util.Assertion;
import org.teiid.core.util.PropertiesUtils;
import org.teiid.core.util.StringUtil;
import org.teiid.dqp.internal.process.DQPWorkContext;
import org.teiid.language.SQLConstants;
import org.teiid.metadata.*;
import org.teiid.metadata.FunctionMethod.PushDown;
import org.teiid.metadata.ProcedureParameter.Type;
import org.teiid.query.QueryPlugin;
import org.teiid.query.function.FunctionMethods;
import org.teiid.query.metadata.DDLConstants;
import org.teiid.query.metadata.DatabaseStore;
import org.teiid.query.sql.lang.*;
import org.teiid.query.sql.lang.ExistsCriteria.SubqueryHint;
import org.teiid.query.sql.proc.Block;
import org.teiid.query.sql.proc.Statement;
import org.teiid.query.sql.symbol.Constant;
import org.teiid.query.sql.symbol.ElementSymbol;
import org.teiid.query.sql.symbol.Expression;
import org.teiid.query.sql.symbol.GroupSymbol;
public class SQLParserUtil {
static final Pattern hintPattern = Pattern.compile("\\s*(\\w+(?:\\(\\s*(max:\\d+)?\\s*((?:no)?\\s*join)\\s*\\))?)\\s*", Pattern.DOTALL | Pattern.CASE_INSENSITIVE); //$NON-NLS-1$
public static final boolean DECIMAL_AS_DOUBLE = PropertiesUtils.getBooleanProperty(System.getProperties(), "org.teiid.decimalAsDouble", false); //$NON-NLS-1$
String prependSign(String sign, String literal) {
if (sign != null && sign.charAt(0) == '-') {
return sign + literal;
}
return literal;
}
void convertToParameters(List<Expression> values, StoredProcedure storedProcedure, int paramIndex) {
for (Expression value : values) {
SPParameter parameter = new SPParameter(paramIndex++, value);
parameter.setParameterType(SPParameter.IN);
storedProcedure.setParameter(parameter);
}
}
String matchesAny(String arg, String ... expected) {
for (String string : expected) {
if (string.equalsIgnoreCase(arg)) {
return arg;
}
}
return null;
}
String normalizeStringLiteral(String s) {
int start = 1;
boolean unescape = false;
if (s.charAt(0) == 'N') {
start++;
} else if (s.charAt(0) == 'E') {
start++;
unescape = true;
}
char tickChar = s.charAt(start - 1);
s = s.substring(start, s.length() - 1);
String result = removeEscapeChars(s, String.valueOf(tickChar));
if (unescape) {
result = FunctionMethods.unescape(result);
}
return result;
}
public static String normalizeId(String s) {
if (s.indexOf('"') == -1) {
return s;
}
List<String> nameParts = new LinkedList<String>();
while (s.length() > 0) {
if (s.charAt(0) == '"') {
boolean escape = false;
for (int i = 1; i < s.length(); i++) {
if (s.charAt(i) != '"') {
continue;
}
escape = !escape;
boolean end = i == s.length() - 1;
if (end || (escape && s.charAt(i + 1) == '.')) {
String part = s.substring(1, i);
s = s.substring(i + (end?1:2));
nameParts.add(removeEscapeChars(part, "\"")); //$NON-NLS-1$
break;
}
}
} else {
int index = s.indexOf('.');
if (index == -1) {
nameParts.add(s);
break;
}
nameParts.add(s.substring(0, index));
s = s.substring(index + 1);
}
}
StringBuilder sb = new StringBuilder();
for (Iterator<String> i = nameParts.iterator(); i.hasNext();) {
sb.append(i.next());
if (i.hasNext()) {
sb.append('.');
}
}
return sb.toString();
}
/**
* Check if this is a valid string literal
* @param id Possible string literal
*/
boolean isStringLiteral(String str, ParseInfo info) {
if (info.useAnsiQuotedIdentifiers() || str.charAt(0) != '"' || str.charAt(str.length() - 1) != '"') {
return false;
}
int index = 1;
while (index < str.length() - 1) {
index = str.indexOf('"', index);
if (index == -1 || index + 1 == str.length()) {
return true;
}
if (str.charAt(index + 1) != '"') {
return false;
}
index += 2;
}
return true;
}
String validateName(String id, boolean nonAlias) throws ParseException {
if(id.indexOf('.') != -1) {
String key = "SQLParser.Invalid_alias"; //$NON-NLS-1$
if (nonAlias) {
key = "SQLParser.Invalid_short_name"; //$NON-NLS-1$
}
throw new ParseException(QueryPlugin.Util.getString(key, id));
}
return id;
}
static String removeEscapeChars(String str, String tickChar) {
return StringUtil.replaceAll(str, tickChar + tickChar, tickChar);
}
void setFromClauseOptions(Token groupID, FromClause fromClause){
String comment = getComment(groupID);
if (comment == null || comment.isEmpty()) {
return;
}
Matcher m = hintPattern.matcher(comment);
int start = 0;
while (m.find(start)) {
String hint = m.group(1);
start = m.end();
if (StringUtil.startsWithIgnoreCase(hint, "make")) { //$NON-NLS-1$
if (hint.equalsIgnoreCase(Option.MAKENOTDEP)) {
fromClause.setMakeNotDep(true);
} else if (StringUtil.startsWithIgnoreCase(hint, Option.MAKEDEP)) {
Option.MakeDep option = new Option.MakeDep();
fromClause.setMakeDep(option);
parseOptions(m, option);
} else if (StringUtil.startsWithIgnoreCase(hint, SQLConstants.Reserved.MAKEIND)) {
Option.MakeDep option = new Option.MakeDep();
fromClause.setMakeInd(option);
parseOptions(m, option);
}
} else if (hint.equalsIgnoreCase(SubqueryHint.NOUNNEST)) {
fromClause.setNoUnnest(true);
} else if (hint.equalsIgnoreCase(FromClause.PRESERVE)) {
fromClause.setPreserve(true);
} else if (hint.equalsIgnoreCase(Option.OPTIONAL)) {
fromClause.setOptional(true);
}
}
}
void parseWithHints(Token paren, WithQueryCommand with){
String comment = getComment(paren);
if (comment == null || comment.isEmpty()) {
return;
}
String[] parts = comment.split("\\s"); //$NON-NLS-1$
for (String part : parts) {
if (WithQueryCommand.NO_INLINE.equalsIgnoreCase(part)) {
with.setNoInline(true);
} else if (WithQueryCommand.MATERIALIZE.equalsIgnoreCase(part)) {
with.setMaterialize(true);
}
}
}
//([max:val] [[no] join])
private void parseOptions(Matcher m, Option.MakeDep option) {
if (m.group(3) != null) {
if (StringUtil.startsWithIgnoreCase(m.group(3), "no")) { //$NON-NLS-1$
option.setJoin(false);
} else {
option.setJoin(true);
}
}
if (m.group(2) != null) {
option.setMax(Integer.valueOf(m.group(2).trim().substring(4)));
}
}
SubqueryHint getSubqueryHint(Token t) {
SubqueryHint hint = new SubqueryHint();
String[] parts = getComment(t).split("\\s"); //$NON-NLS-1$
for (int i = 0; i < parts.length; i++) {
if (parts[i].equalsIgnoreCase(SubqueryHint.MJ)) {
hint.setMergeJoin(true);
} else if (parts[i].equalsIgnoreCase(SubqueryHint.NOUNNEST)) {
hint.setNoUnnest(true);
} else if (parts[i].equalsIgnoreCase(SubqueryHint.DJ)) {
hint.setDepJoin();
}
}
return hint;
}
String getComment(Token t) {
Token optToken = t.specialToken;
if (optToken == null) {
return ""; //$NON-NLS-1$
}
//handle nested comments
String image = optToken.image;
while (optToken.specialToken != null) {
optToken = optToken.specialToken;
image = optToken.image + image;
}
String hint = image.substring(2, image.length() - 2);
if (hint.startsWith("+")) { //$NON-NLS-1$
hint = hint.substring(1);
}
return hint;
}
private static Pattern SOURCE_HINT = Pattern.compile("\\s*sh(\\s+KEEP ALIASES)?\\s*(?::((?:'[^']*')+))?\\s*", Pattern.CASE_INSENSITIVE | Pattern.DOTALL); //$NON-NLS-1$
private static Pattern SOURCE_HINT_ARG = Pattern.compile("\\s*([^: ]+)(\\s+KEEP ALIASES)?\\s*:((?:'[^']*')+)", Pattern.CASE_INSENSITIVE | Pattern.DOTALL); //$NON-NLS-1$
SourceHint getSourceHint(SQLParser parser) {
int index = 1;
//scan for the first keyword
Token t = null;
do {
t = parser.getToken(index++);
} while (t != null && t.kind == SQLParserConstants.LPAREN);
t = parser.getToken(index);
if (t == null) {
return null;
}
String comment = getComment(t);
Matcher matcher = SOURCE_HINT.matcher(comment);
if (!matcher.find()) {
return null;
}
SourceHint sourceHint = new SourceHint();
if (matcher.group(1) != null) {
sourceHint.setUseAliases(true);
}
String generalHint = matcher.group(2);
if (generalHint != null) {
sourceHint.setGeneralHint(normalizeStringLiteral(generalHint));
}
int end = matcher.end();
matcher = SOURCE_HINT_ARG.matcher(comment);
while (matcher.find(end)) {
end = matcher.end();
sourceHint.setSourceHint(matcher.group(1), normalizeStringLiteral(matcher.group(3)), matcher.group(2) != null);
}
return sourceHint;
}
void setSourceHint(SourceHint sourceHint, Command command) {
if (sourceHint != null) {
if (command instanceof SetQuery) {
((SetQuery)command).getProjectedQuery().setSourceHint(sourceHint);
} else {
command.setSourceHint(sourceHint);
}
}
}
boolean isNonStrictHint(Token t) {
String[] parts = getComment(t).split("\\s"); //$NON-NLS-1$
for (int i = 0; i < parts.length; i++) {
if (parts[i].equalsIgnoreCase(Limit.NON_STRICT)) {
return true;
}
}
return false;
}
private static Pattern HINT = Pattern.compile("\\s*/\\*([^/]*)\\*/", Pattern.CASE_INSENSITIVE | Pattern.DOTALL); //$NON-NLS-1$
private static Pattern CACHE_HINT = Pattern.compile("\\+?\\s*cache(\\(\\s*(pref_mem)?\\s*(ttl:\\d{1,19})?\\s*(updatable)?\\s*(scope:(session|vdb|user))?\\s*(min:\\d{1,19})?[^\\)]*\\))?[^\\*]*", Pattern.CASE_INSENSITIVE | Pattern.DOTALL); //$NON-NLS-1$
static CacheHint getQueryCacheOption(String query) {
Matcher hintMatch = HINT.matcher(query);
int start = 0;
while (hintMatch.find()) {
if (start != hintMatch.start()) {
break;
}
start = hintMatch.end();
Matcher match = CACHE_HINT.matcher(hintMatch.group(1));
if (!match.matches()) {
continue;
}
CacheHint hint = new CacheHint();
if (match.group(2) !=null) {
hint.setPrefersMemory(true);
}
String ttl = match.group(3);
if (ttl != null) {
hint.setTtl(Long.valueOf(ttl.substring(4)));
}
if (match.group(4) != null) {
hint.setUpdatable(true);
}
String scope = match.group(5);
if (scope != null) {
scope = scope.substring(6);
hint.setScope(scope);
}
String min = match.group(7);
if (min != null) {
hint.setMinRows(Long.valueOf(min.substring(4)));
}
return hint;
}
return null;
}
int getOperator(String opString) {
if (opString.equals("=")) { //$NON-NLS-1$
return CompareCriteria.EQ;
} else if (opString.equals("<>") || opString.equals("!=")) { //$NON-NLS-1$ //$NON-NLS-2$
return CompareCriteria.NE;
} else if (opString.equals("<")) { //$NON-NLS-1$
return CompareCriteria.LT;
} else if (opString.equals(">")) { //$NON-NLS-1$
return CompareCriteria.GT;
} else if (opString.equals("<=")) { //$NON-NLS-1$
return CompareCriteria.LE;
} else if (opString.equals(">=")) { //$NON-NLS-1$
return CompareCriteria.GE;
}
Assertion.failed("unknown operator"); //$NON-NLS-1$
return 0;
}
SetQuery addQueryToSetOperation(QueryCommand query, QueryCommand rightQuery, SetQuery.Operation type, boolean all) {
SetQuery setQuery = new SetQuery(type, all, query, rightQuery);
return setQuery;
}
static Block asBlock(Statement stmt) {
if (stmt == null) {
return null;
}
if (stmt instanceof Block) {
return (Block)stmt;
}
return new Block(stmt);
}
static FunctionMethod replaceProcedureWithFunction(MetadataFactory factory,
Procedure proc) throws MetadataException {
if (proc.isFunction() && proc.getQueryPlan() != null) {
return null;
}
FunctionMethod method = createFunctionMethod(proc);
//remove the old proc
factory.getSchema().getResolvingOrder().remove(factory.getSchema().getResolvingOrder().size() - 1);
factory.getSchema().getProcedures().remove(proc.getName());
factory.getSchema().addFunction(method);
return method;
}
public static FunctionMethod createFunctionMethod(Procedure proc) {
FunctionMethod method = new FunctionMethod();
method.setName(proc.getName());
method.setPushdown(proc.isVirtual()?FunctionMethod.PushDown.CAN_PUSHDOWN:FunctionMethod.PushDown.MUST_PUSHDOWN);
ArrayList<FunctionParameter> ins = new ArrayList<FunctionParameter>();
for (ProcedureParameter pp:proc.getParameters()) {
if (pp.getType() == ProcedureParameter.Type.InOut || pp.getType() == ProcedureParameter.Type.Out) {
throw new MetadataException(QueryPlugin.Util.getString("SQLParser.function_in", proc.getName())); //$NON-NLS-1$
}
//copy the metadata
FunctionParameter fp = new FunctionParameter(pp.getName(), pp.getRuntimeType(), pp.getAnnotation());
fp.setDatatype(pp.getDatatype(), true, pp.getArrayDimensions());
fp.setLength(pp.getLength());
fp.setNameInSource(pp.getNameInSource());
fp.setNativeType(pp.getNativeType());
fp.setNullType(pp.getNullType());
fp.setProperties(pp.getProperties());
fp.setRadix(pp.getRadix());
fp.setScale(pp.getScale());
fp.setUUID(pp.getUUID());
if (pp.getType() == ProcedureParameter.Type.In) {
fp.setVarArg(pp.isVarArg());
ins.add(fp);
fp.setPosition(ins.size());
} else {
method.setOutputParameter(fp);
fp.setPosition(0);
}
}
method.setInputParameters(ins);
if (proc.getResultSet() != null || method.getOutputParameter() == null) {
throw new MetadataException(QueryPlugin.Util.getString("SQLParser.function_return", proc.getName())); //$NON-NLS-1$
}
method.setAnnotation(proc.getAnnotation());
method.setNameInSource(proc.getNameInSource());
method.setUUID(proc.getUUID());
Map<String, String> props = proc.getProperties();
String value = props.remove(DDLConstants.CATEGORY);
method.setCategory(value);
value = props.remove(DDLConstants.DETERMINISM);
if (value != null) {
method.setDeterminism(FunctionMethod.Determinism.valueOf(value.toUpperCase()));
}
value = props.remove(DDLConstants.JAVA_CLASS);
method.setInvocationClass(value);
value = props.remove(DDLConstants.JAVA_METHOD);
method.setInvocationMethod(value);
for (String key:props.keySet()) {
value = props.get(key);
method.setProperty(key, value);
}
FunctionMethod.convertExtensionMetadata(proc, method);
if (method.getInvocationMethod() != null) {
method.setPushdown(PushDown.CAN_PUSHDOWN);
}
return method;
}
public static boolean isTrue(final String text) {
return Boolean.valueOf(text);
}
AbstractMetadataRecord getChild(String name, AbstractMetadataRecord record, boolean parameter) {
if (record instanceof Table) {
if (parameter) {
throw new MetadataException(QueryPlugin.Util.getString("SQLParser.alter_table_param", name, record.getName())); //$NON-NLS-1$
}
return getColumn(name, (Table)record);
}
return getColumn(name, (Procedure)record, parameter);
//TODO: function is not supported yet because we store by uid, which should instead be a more friendly "unique name"
}
Column getColumn(String columnName, Table table) throws MetadataException {
Column c = table.getColumnByName(columnName);
if (c != null) {
return c;
}
throw new MetadataException(QueryPlugin.Util.getString("SQLParser.no_column", columnName, table.getName())); //$NON-NLS-1$
}
AbstractMetadataRecord getColumn(String paramName, Procedure proc, boolean parameter) throws MetadataException {
if (proc.getResultSet() != null) {
Column result = proc.getResultSet().getColumnByName(paramName);
if (result != null) {
return result;
}
}
if (parameter) {
List<ProcedureParameter> params = proc.getParameters();
for (ProcedureParameter param:params) {
if (param.getName().equalsIgnoreCase(paramName)) {
return param;
}
}
}
throw new MetadataException(QueryPlugin.Util.getString("SQLParser.alter_procedure_param_doesnot_exist", paramName, proc.getName())); //$NON-NLS-1$
}
FunctionParameter getParameter(String paramName, FunctionMethod func) throws MetadataException {
List<FunctionParameter> params = func.getInputParameters();
for (FunctionParameter param:params) {
if (param.getName().equalsIgnoreCase(paramName)) {
return param;
}
}
throw new MetadataException(QueryPlugin.Util.getString("SQLParser.alter_function_param_doesnot_exist", paramName, func.getName())); //$NON-NLS-1$
}
void createDDLTrigger(DatabaseStore events, AlterTrigger trigger) {
GroupSymbol group = trigger.getTarget();
events.setTableTriggerPlan(trigger.getName(), group.getName(), trigger.getEvent(), trigger.getDefinition().toString(), trigger.isAfter());
}
BaseColumn addProcColumn(MetadataFactory factory, Procedure proc, String name, ParsedDataType type, boolean rs) throws MetadataException {
BaseColumn column = null;
if (rs) {
column = factory.addProcedureResultSetColumn(name, type.type, proc);
} else {
boolean added = false;
for (ProcedureParameter pp : proc.getParameters()) {
if (pp.getType() == Type.ReturnValue) {
added = true;
if (pp.getDatatype() != factory.getDataTypes().get(type.type)) {
throw new MetadataException(QueryPlugin.Util.getString("SQLParser.proc_type_conflict", proc.getName(), pp.getDatatype(), type.type)); //$NON-NLS-1$
}
}
}
if (!added) {
column = factory.addProcedureParameter(name, type.type, ProcedureParameter.Type.ReturnValue, proc);
}
}
setTypeInfo(type, column);
return column;
}
static void setTypeInfo(ParsedDataType type, BaseColumn column) {
if (type.length != null){
column.setLength(type.length);
}
if (type.precision != null){
if (type.precision == 0) {
throw new MetadataException(QueryPlugin.Util.getString("SQLParser.zero_precision")); //$NON-NLS-1$
}
column.setPrecision(type.precision);
if (type.scale != null){
if (Math.abs(type.scale) > type.precision) {
throw new MetadataException(QueryPlugin.Util.getString("SQLParser.invalid_scale", type.scale, type.precision)); //$NON-NLS-1$
}
column.setScale(type.scale);
} else {
column.setScale(0);
}
}
}
KeyRecord addFBI(MetadataFactory factory, List<Expression> expressions, Table table, String name) throws MetadataException {
List<String> columnNames = new ArrayList<String>(expressions.size());
List<Boolean> nonColumnExpressions = new ArrayList<Boolean>(expressions.size());
boolean fbi = false;
for (int i = 0; i < expressions.size(); i++) {
Expression ex = expressions.get(i);
if (ex instanceof ElementSymbol) {
columnNames.add(((ElementSymbol)ex).getName());
nonColumnExpressions.add(Boolean.FALSE);
} else {
columnNames.add(ex.toString());
nonColumnExpressions.add(Boolean.TRUE);
fbi = true;
}
}
return factory.addFunctionBasedIndex(name != null?name:(SQLConstants.NonReserved.INDEX+(fbi?table.getFunctionBasedIndexes().size():table.getIndexes().size())), columnNames, nonColumnExpressions, table);
}
MetadataFactory getTempMetadataFactory() {
DQPWorkContext workContext = DQPWorkContext.getWorkContext();
return workContext.getTempMetadataFactory();
}
List<Expression> arrayExpressions(List<Expression> expressions, Expression expr) {
if (expressions == null) {
expressions = new ArrayList<Expression>();
}
if (expr != null) {
expressions.add(expr);
}
return expressions;
}
static class ParsedDataType{
String type;
Integer length;
Integer scale;
Integer precision;
public ParsedDataType(String type) {
this.type = type;
}
public ParsedDataType(String type, int length, boolean precision) {
this.type = type;
if (precision) {
this.precision = length;
}
else {
this.length = length;
}
}
public ParsedDataType(String type, int length, int scale, boolean precision) {
this.type = type;
this.scale = scale;
if (precision) {
this.precision = length;
}
else {
this.length = length;
}
}
}
public static void setDefault(BaseColumn column, Expression value) {
if ((value instanceof Constant) && value.getType() == DataTypeManager.DefaultDataClasses.STRING) {
column.setDefaultValue(((Constant)value).getValue().toString());
} else {
//it's an expression
column.setProperty(BaseColumn.DEFAULT_HANDLING, BaseColumn.EXPRESSION_DEFAULT);
column.setDefaultValue(value.toString());
}
}
}