/* * 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()); } } }