/*
 * 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.
 * Modified by
 * @author  Data Virtuality GmbH
 * @date    $Date$
 *
 */
/*
 * EXTENSION
 * ISSUE: DVCORE-2521
 * FIXED BY: Roman Pedchenko
 * DATE: 2014-12-05
 * ADOPTED FROM: TEIID-2153, 2171, 2359, 2149, 2358, 2410, 2271, 2467, 2429, 2363, 2160, 2461, 2797, 2326, 1557
 * END EXTENSION
 */
// https://issues.jboss.org/browse/TEIID-247, 2166, 2151, 2234, 2581, 2571, 2369, 1386, 2226, 2233, 2224, 2225, 2257
// https://issues.jboss.org/browse/TEIID-2235, 2661, 2178, 2297
// https://issues.jboss.org/browse/TEIID-4084
// https://issues.jboss.org/browse/TEIID-3346
// https://issues.jboss.org/browse/TEIID-2085
// https://issues.jboss.org/browse/TEIID-5739
// https://issues.jboss.org/browse/TEIID-2381
// https://issues.jboss.org/browse/TEIID-3515
// https://issues.redhat.com/browse/TEIID-2195
// https://issues.redhat.com/browse/TEIID-6047
// https://issues.redhat.com/browse/TEIID-3439
// https://issues.redhat.com/browse/TEIID-2341
// https://issues.redhat.com/browse/TEIID-4446
package org.teiid.query.parser;

import com.datavirtuality.dv.core.interfaces.DVCaller;
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.language.SQLConstants;
import org.teiid.metadata.*;
import org.teiid.metadata.Column.SearchType;
import org.teiid.metadata.FunctionMethod.PushDown;
import org.teiid.metadata.FunctionParameter;
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.SystemMetadata;
import org.teiid.query.sql.StreamPositionAware;
import org.teiid.query.sql.StreamPositionHolder;
import org.teiid.query.sql.StreamPositionImpl;
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.ElementSymbol;
import org.teiid.query.sql.symbol.Expression;
import org.teiid.query.sql.symbol.GroupSymbol;
import org.teiid.translator.TranslatorException;

import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static org.teiid.query.sql.lang.Option.NOFAIL;

public class SQLParserUtil {

	static final Pattern udtPattern = Pattern.compile("(\\w+)\\s*\\(\\s*(\\d+),\\s*(\\d+),\\s*(\\d+)\\)"); //$NON-NLS-1$

	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;
	}

	public void convertToParameters(List<Expression> values, StoredExecutable storedExecutable, int paramIndex) {
		for (Expression value : values) {
			SEParameter parameter = new SEParameter(paramIndex++, value);
			parameter.setParameterType(SEParameter.IN);
			storedExecutable.setParameter(parameter);
		}
	}

	String matchesAny(String arg, String ... expected) {
		for (String string : expected) {
			if (string.equalsIgnoreCase(arg)) {
				return arg;
			}
		}
		return null;
	}

	public static 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) {
		// EXTENSION
		// ISSUE: INFRA-137
		// FIXED BY: Dmitry Pogorelov
		// DATE: 2014.12.05,2014.12.08
		// if (s.indexOf('"') == -1) {
		// return s;
		List<String> nameParts = normalizeIdToList(s);
		return joinNameParts(nameParts);
	}

	public static ElementSymbol normalizeElementSymbol(String s) {
		List<String> nameParts = normalizeIdToList(s);
		final int partsCount = nameParts.size();
		if (hasNoQuotes(s) || partsCount <= 1) {
			final ElementSymbol el = new ElementSymbol(joinNameParts(nameParts));
			el.setOriginalName(s);
			return el;
		}
		GroupSymbol gs = new GroupSymbol(joinNameParts(nameParts.subList(0, partsCount - 1)));
		final String name = nameParts.get(partsCount - 1);
		final ElementSymbol el = new ElementSymbol(name, gs);
		el.setOriginalName(s);
		return el;
	}

	private static boolean hasNoQuotes(String s) {
		return s.indexOf('"') == -1;
	}

	private static String joinNameParts(List<String> nameParts) {
		StringBuilder sb = new StringBuilder();
		for (Iterator<String> i = nameParts.iterator(); i.hasNext();) {
			sb.append(i.next());
			if (i.hasNext()) {
				sb.append('.');
			}
			// END EXTENSION
		}
		// EXTENSION
		// ISSUE: INFRA-137
		// FIXED BY: Dmitry Pogorelov
		// DATE: 2014.12.05
		return sb.toString();
	}

	void validateQuotedName(String aliasID, String rawAlias) throws ParseException {
		String adjustedAlias = aliasID;
		if (rawAlias.charAt(0) == '"') {
			adjustedAlias = '"' + StringUtil.replaceAll(aliasID, "\"", "\"\"") + '"'; //$NON-NLS-1$ //$NON-NLS-2$
		}
		if (!rawAlias.equals(adjustedAlias)) {
			throw new ParseException(QueryPlugin.Util.getString("SQLParser.ddl_id_unqualified", rawAlias)); //$NON-NLS-1$
		}
	}

	private static List<String> normalizeIdToList(String s) {
		if (hasNoQuotes(s)) {
			return Arrays.asList(s);
		}
		// END EXTENSION
		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);
			}
		}
		// EXTENSION
		// ISSUE: INFRA-137
		// FIXED BY: Dmitry Pogorelov
		// DATE: 2014.12.05
		// StringBuilder sb = new StringBuilder();
		// for (Iterator<String> i = nameParts.iterator(); i.hasNext();) {
		// sb.append(i.next());
		// if (i.hasNext()) {
		// sb.append('.');
		// }
		// }
		// return sb.toString();
		return nameParts;
		// END EXTENSION
	}

    /**
     * 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;
	    boolean makedep = false;
	    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);
		    }
        }
    }

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

    boolean isNoInline(Token paren){
        String comment = getComment(paren);
        if (comment == null || comment.isEmpty()) {
                return false;
        }
        String[] parts = comment.split("\\s"); //$NON-NLS-1$
        for (String part : parts) {
                if (WithQueryCommand.NO_INLINE.equalsIgnoreCase(part)) {
                        return true;
                }
        }
        return false;
    }

    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) {
		String comment = getFullComment(t, false);
		if (comment.length() == 0) {
			return comment;
		}
		if (comment.startsWith("--")) { //$NON-NLS-1$
			return comment.substring(2);
		}
		String hint = comment.substring(2, comment.length() - 2);
		if (hint.startsWith("+")) { //$NON-NLS-1$
			hint = hint.substring(1);
		}
		return hint;
	}

	/**
	 * Get the full comment including the nesting characters -- or \* *\/
	 * Or return the empty string if there is no comment.
	 * @param includeEnding if true include a space or newline after the comment
	 */
	String getFullComment(Token t, boolean includeEnding) {
		if (t == null) {
			return ""; //$NON-NLS-1$
		}
		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;
        }
		if (includeEnding && image.startsWith("--")) { //$NON-NLS-1$
			return image + "\n"; //$NON-NLS-1$
		}
		if (includeEnding) {
			return image + " "; //$NON-NLS-1$
		}
		return image;
	}

	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;
		}
		int start = matcher.start();
		if (start > 0 && !Character.isWhitespace(comment.charAt(start -1))) {
			return null;
		}
		int end = matcher.end();
		if (end < comment.length() - 1 && !Character.isWhitespace(comment.charAt(end-1))) {
			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));
		}

		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 CACHE_HINT = Pattern.compile("/\\*\\+?\\s*cache(\\(\\s*(pref_mem)?\\s*(ttl:\\d{1,19})?\\s*(updatable)?\\s*(scope:(session|vdb|user))?[^\\)]*\\))?[^\\*]*\\*\\/.*", Pattern.CASE_INSENSITIVE | Pattern.DOTALL); //$NON-NLS-1$

	static CacheHint getQueryCacheOption(String query) {
    	Matcher match = CACHE_HINT.matcher(query);
    	if (match.matches()) {
    		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);
    		}
    		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);
    }

    void setColumnOptions(ParseInfo info, BaseColumn c)  throws MetadataException {
    	Map<String, String> props = c.getProperties();
		setCommonProperties(info, c, props);

    	String v = props.remove(DDLConstants.RADIX);
    	if (v != null) {
    		c.setRadix(Integer.parseInt(v));
    	}

	    v = props.remove(DDLConstants.NATIVE_TYPE);
	    if (v != null) {
		    c.setNativeType(v);
	    }

    	if (c instanceof Column) {
    		setColumnOptions((Column)c, props);
    	}
    }

	private void setColumnOptions(Column c, Map<String, String> props) throws MetadataException {
		String v = props.remove(DDLConstants.CASE_SENSITIVE);
        if (v != null) {
        	c.setCaseSensitive(isTrue(v));
        }

    	v = props.remove(DDLConstants.SELECTABLE);
    	if (v != null) {
    		c.setSelectable(isTrue(v));
    	}

    	v = props.remove(DDLConstants.UPDATABLE);
    	if (v != null) {
    		c.setUpdatable(isTrue(v));
    	}

    	v = props.remove(DDLConstants.SIGNED);
    	if (v != null) {
    		c.setSigned(isTrue(v));
    	}

    	v = props.remove(DDLConstants.CURRENCY);
    	if (v != null) {
    		c.setSigned(isTrue(v));
    	}

    	v = props.remove(DDLConstants.FIXED_LENGTH);
    	if (v != null) {
    		c.setFixedLength(isTrue(v));
    	}

    	v = props.remove(DDLConstants.SEARCHABLE);
    	if (v != null) {
    		c.setSearchType(StringUtil.caseInsensitiveValueOf(SearchType.class, v));
    	}

    	v = props.remove(DDLConstants.MIN_VALUE);
    	if (v != null) {
    		c.setMinimumValue(v);
    	}

    	v = props.remove(DDLConstants.MAX_VALUE);
    	if (v != null) {
    		c.setMaximumValue(v);
    	}

    	v = props.remove(DDLConstants.CHAR_OCTET_LENGTH);
    	if (v != null) {
    		c.setCharOctetLength(Integer.parseInt(v));
    	}

    	v = props.remove(DDLConstants.NULL_VALUE_COUNT);
    	if (v != null) {
    		c.setNullValues(Integer.parseInt(v));
    	}

    	v = props.remove(DDLConstants.DISTINCT_VALUES);
    	if (v != null) {
    		c.setDistinctValues(Integer.parseInt(v));
    	}

    	v = props.remove(DDLConstants.UDT);
    	if (v != null) {
    		Matcher matcher = udtPattern.matcher(v);
    		Map<String, Datatype> datatypes = SystemMetadata.getInstance().getSystemStore().getDatatypes();
    		if (matcher.matches() && datatypes.get(matcher.group(1)) != null) {
    			c.setDatatype(datatypes.get(matcher.group(1)));
			    c.setLength(Integer.parseInt(matcher.group(2)));
			    ParsedDataType pdt = new ParsedDataType(matcher.group(1), Integer.parseInt(matcher.group(3)), Integer.parseInt(matcher.group(4)), true);
			    setTypeInfo(pdt, c);
    		}
    		else {
    			throw new MetadataException(QueryPlugin.Util.getString("udt_format_wrong", c.getName())); //$NON-NLS-1$
    		}
    	}
    }

	static void setCommonProperties(ParseInfo info, AbstractMetadataRecord c, Map<String, String> props) {
		String v = props.remove(DDLConstants.UUID);
		if (v != null) {
			c.setUUID(v);
		}

    	v = props.remove(DDLConstants.ANNOTATION);
    	if (v != null) {
			if (info.isDynamicDdl())
				c.setAnnotation(v);
			else
				c.setRemark(v);
    	}

		v = props.remove(DDLConstants.NAMEINSOURCE);
		if (v != null) {
			c.setNameInSource(v);
		}
	}

    public static void setTableOptions(ParseInfo info, Table table) {
    	Map<String, String> props = table.getProperties();
    	setCommonProperties(info, table, props);

    	String value = props.remove(DDLConstants.MATERIALIZED);
    	if (value != null) {
    		table.setMaterialized(isTrue(value));
    	}

		value = props.remove(DDLConstants.MATERIALIZED_TABLE);
		if (value != null) {
    		Table mattable = new Table();
    		mattable.setName(value);
    		table.setMaterializedTable(mattable);
    	}

		value = props.remove(DDLConstants.UPDATABLE);
		if (value != null) {
			table.setSupportsUpdate(isTrue(value));
		}

    	value = props.remove(DDLConstants.CARDINALITY);
    	if (value != null) {
    		table.setCardinality(Integer.parseInt(value));
    	}

		value = props.remove(DDLConstants.FULLPROCESSING);
		if (value != null) {
			table.setFullProcessingTable(value);
		}

		value = props.remove(DDLConstants.FULLPROCESSINGMAP);
		if (value != null) {
			table.setFullProcessingMap(value);
		}

		value = props.remove(DDLConstants.FULLPROCESSINGVDB);
		if (value != null) {
			table.setFullProcessingVdb(isTrue(value));
		}

		value = props.remove(DDLConstants.FULLPROCESSINGSCRIPT);
		if (value != null) {
			table.setFullProcessingScript(value);
		}

	    value = props.remove(DDLConstants.CACHEABLE);
	    if (value != null) {
		    table.setCacheable(isTrue(value));
	    }

		value = props.remove(DDLConstants.CLUSTERMODE);
		if (value != null) {
			table.setClusterMode(isTrue(value));
		}
	}
	
	private static FunctionParameter copyFunctionParameter(FunctionParameter parameter) {
		FunctionParameter fp = new FunctionParameter(parameter.getName(), parameter.getRuntimeType());
		fp.setDatatype(parameter.getDatatype(), true, parameter.getArrayDimensions());
		fp.setLength(parameter.getLength());
		fp.setNameInSource(parameter.getNameInSource());
		fp.setNativeType(parameter.getNativeType());
		fp.setNullType(parameter.getNullType());
		fp.setProperties(parameter.getProperties());
		fp.setRadix(parameter.getRadix());
		fp.setScale(parameter.getScale());
		fp.setUUID(parameter.getUUID());
		fp.setRemark(parameter.getRemark());
		fp.setAnnotation(parameter.getAnnotation());
		fp.setOptPos(parameter.getOptPos());
		fp.getPositions().putAll(parameter.getPositions());
		
		return fp;
	}
	
	public static FunctionMethod augmentFunctionMethod(ParseInfo info, FunctionMethod func, FunctionMethod result) {
		result.setName(func.getName());
		result.setPushdown(func.isVirtual()? FunctionMethod.PushDown.CAN_PUSHDOWN: FunctionMethod.PushDown.MUST_PUSHDOWN);

		ArrayList<FunctionParameter> ins = new ArrayList<>();
		for (FunctionParameter parameter: func.getInputParameters()) {
			//copy the metadata
			FunctionParameter fp = copyFunctionParameter(parameter);
			fp.setVarArg(parameter.isVarArg());
			ins.add(fp);
			fp.setPosition(ins.size());
		}
		result.setInputParameters(ins);

		FunctionParameter outputParameter = copyFunctionParameter(func.getReturnParameter());
		result.setReturnParameter(outputParameter);
		outputParameter.setPosition(0);

		if (info.isDynamicDdl())
			result.setAnnotation(func.getAnnotation());
		else
			result.setRemark(func.getRemark());
		result.setNameInSource(func.getNameInSource());
		result.setUUID(func.getUUID());

		Map<String, String> props = func.getProperties();

		String value = props.get(DDLConstants.CATEGORY);
		result.setCategory(value);

		value = props.get(DDLConstants.DETERMINISM);
		if (value != null) {
			result.setDeterminism(FunctionMethod.Determinism.valueOf(value.toUpperCase()));
		}

		value = props.get(DDLConstants.JAVA_CLASS);
		result.setInvocationClass(value);

		value = props.get(DDLConstants.JAVA_METHOD);
		result.setInvocationMethod(value);

		for (String key:props.keySet()) {
			value = props.get(key);
			result.setProperty(key, value);
		}

		FunctionMethod.convertExtensionMetadata(func, result);
		if (result.getInvocationMethod() != null) {
			result.setPushdown(PushDown.CAN_PUSHDOWN);
		}

		return result;
	}

    void setProcedureOptions(ParseInfo info, Procedure proc) {
    	Map<String, String> props = proc.getProperties();
    	setCommonProperties(info, proc, props);

    	String value = props.remove("UPDATECOUNT"); //$NON-NLS-1$
    	if (value != null) {
    		proc.setUpdateCount(Integer.parseInt(value));
    	}
		// we should handle procedure parameters for odata as according to them odata handles procedures
		// either as actions or as functions
		DVCaller.call(236, proc);
    }

	void setFunctionOptions(ParseInfo info, FunctionMethod function, MetadataFactory factory) {
		Map<String, String> props = function.getProperties();
		String oldUUID = function.getUUID();
		setCommonProperties(info, function, props);
		String newUUID = function.getUUID();
		if (!oldUUID.equals(newUUID)) {
			function.setUUID(oldUUID);
			factory.getSchema().removeFunctions(oldUUID);
			function.setUUID(newUUID);
			factory.getSchema().addFunction(function);
		}
	}

    public static boolean isTrue(final String text) {
        return Boolean.valueOf(text);
    }

	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$
	}

	void createDDLTrigger(MetadataFactory schema, AlterTrigger trigger) {
		GroupSymbol group = trigger.getTarget();

		Table table = schema.getSchema().getTable(group.getName());
		if (trigger.getEvent().equals(Table.TriggerEvent.INSERT)) {
			table.setInsertPlan(trigger.getDefinition().toString());
		}
		else if (trigger.getEvent().equals(Table.TriggerEvent.UPDATE)) {
			table.setUpdatePlan(trigger.getDefinition().toString());
		}
		else if (trigger.getEvent().equals(Table.TriggerEvent.DELETE)) {
			table.setDeletePlan(trigger.getDefinition().toString());
		}
	}

	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;
	}

	BaseColumn addFunctionColumn(MetadataFactory factory, FunctionMethod function, String name, ParsedDataType type) throws MetadataException {
		BaseColumn column = null;
		if (function.getReturnParameter() != null) {
			if (function.getReturnParameter().getDatatype() != factory.getDataTypes().get(type.type)) {
				throw new MetadataException(QueryPlugin.Util.getString("SQLParser.func_type_conflict", function.getName(), function.getReturnParameter().getDatatype(), type.type)); //$NON-NLS-1$
			}
		} else {
			column = factory.addFunctionOutputParameter(name, type.type, function);
		}
		setTypeInfo(type, column);
		return column;
	}

	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);
			}
		}
	}

	static String resolvePropertyKey(MetadataFactory factory, String key) {
	 	int index = key.indexOf(':');
	 	if (index > 0 && index < key.length() - 1) {
	 		String prefix = key.substring(0, index);
	 		String uri = MetadataFactory.BUILTIN_NAMESPACES.get(prefix);
	 		if (uri == null) {
	 			uri = factory.getNamespaces().get(prefix);
	 		}
	 		if (uri != null) {
	 			key = '{' +uri + '}' + key.substring(index + 1, key.length());
	 		}
	 		//TODO warnings or errors if not resolvable
	 	}
	 	return key;
	}

	public static KeyRecord addFBI(MetadataFactory factory, List<Expression> expressions, Table table, String name) throws ParseException {
		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;
			}
		}
		try{
			table.addAttchment(MetadataFactory.class, factory);
			return factory.addFunctionBasedIndex(name != null?name:(SQLConstants.NonReserved.INDEX+(fbi?table.getFunctionBasedIndexes().size():table.getIndexes().size())), columnNames, nonColumnExpressions, table);
		}catch(TranslatorException e){
			throw new ParseException(e.getMessage());
		}
	}

	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, length, precision);
			this.scale = scale;
			if (scale == 0 && (DataTypeManager.DefaultDataTypes.BIG_DECIMAL.equalsIgnoreCase(type) ||
					"decimal".equalsIgnoreCase(type))) {
				this.type = DataTypeManager.DefaultDataTypes.BIG_INTEGER;
			}
		}
	}

	public StreamPositionAware getTokenPosition(Token token) {
		return new StreamPositionHolder(token.absoluteBeginColumn, token.absoluteBeginColumn + token.image.length());
	}

	public StreamPositionAware getTokenPositionWithCheck(Token token) {
		StreamPositionAware result = getTokenPosition(token);
		return result != null ? result : new StreamPositionHolder(0, 0);
	}

	public int startPosition(ParseInfo info, Token token) {
		final StreamPositionAware tokenPosition = getTokenPositionWithCheck(token);
		int posCorrection = info.posCorrection;
		info.posCorrection = tokenPosition.getTextStart();
		return posCorrection;
	}

	public StreamPositionAware getPosition(ParseInfo info, Token token) {
		final StreamPositionAware tokenPosition = getTokenPositionWithCheck(token);
		return new StreamPositionImpl(tokenPosition.getTextStart() - info.posCorrection, tokenPosition.getTextEnd() - info.posCorrection);
	}

	public StreamPositionAware getZeroPosition(ParseInfo info, Token token) {
		final StreamPositionAware tokenPosition = getTokenPositionWithCheck(token);
		return new StreamPositionImpl(tokenPosition.getTextStart() - info.posCorrection, tokenPosition.getTextStart() - info.posCorrection);
	}

	public StreamPositionAware getNextPosition(ParseInfo info, Token token) {
		return getNextPosition(info, token, 0);
	}

	public StreamPositionAware getNextPosition(ParseInfo info, Token token, int correction) {
		final int pos = getTokenPositionWithCheck(token).getTextEnd() + correction;
		return new StreamPositionImpl(pos - info.posCorrection, pos - info.posCorrection);
	}

	public StreamPositionAware getDiffPos(StreamPositionAware start, StreamPositionAware end) {
		return new StreamPositionImpl(start.getTextStart(), end.getTextEnd());
	}

	public StreamPositionAware getPositionTill(ParseInfo info, StreamPositionAware start, Token token) {
		return getDiffPos(start, getPosition(info, token));
	}

	public static <T> List<T> findDuplicates(Collection<T> collection) {
		List<T> duplicates = new ArrayList<>();
		Set<T> uniques = new HashSet<>();

		for(T t : collection) {
			if(!uniques.add(t)) {
				duplicates.add(t);
			}
		}

		return duplicates;
	}

	public static String validateConstraintDefinition(List<TableFunctionReference.ProjectedColumn> viewColumns, List<String> constraintColumns) {
		for (String constraintCol : constraintColumns) {
			boolean colIsDefined = false;
			if (viewColumns != null) {
				for (TableFunctionReference.ProjectedColumn viewCol : viewColumns) {
					if (viewCol.getName().equalsIgnoreCase(constraintCol)) {
						colIsDefined = true;
						break;
					}
				}
			}
			if (!colIsDefined)
				return constraintCol;
		}
		return null;
	}

	private int nextAlias;
	public void initAliasGen() {
		nextAlias = 0;
	}

	public String nextAlias() {
		return "@datavirtuality_temp_alias" + (nextAlias++);
	}

	public static class ParsedForeignKey {
		String name;
		List<String> columnNames;
		String referenceTable;
		List<String> referencedColumnNames;

		public ParsedForeignKey(String name, List<String> columnNames, String referenceTable, List<String> referencedColumnNames) {
			this.name = name;
			this.columnNames = columnNames;
			this.referenceTable = referenceTable;
			this.referencedColumnNames = referencedColumnNames;
		}

		public String getName() {
			return name;
		}

		public List<String> getColumnNames() {
			return columnNames;
		}

		public String getReferenceTable() {
			return referenceTable;
		}

		public List<String> getReferencedColumnNames() {
			return referencedColumnNames;
		}
	}

    public void pushUpOptions(@SuppressWarnings("rawtypes") Alter command) {
        if (command == null || command.getDefinition() == null || command.getDefinition().getOption() == null ||
                command.getDefinition().getOption().getOptions() == null)
            return;
		final String nofail = command.getDefinition().getOption().getOptions().get(NOFAIL);
		final StreamPositionAware posNofail = command.getDefinition().getOption().getPosNofail();
		if (nofail != null) {
			command.getDefinition().getOption().getOptions().remove(NOFAIL);
			command.getDefinition().getOption().setPosNofail(null);
			if (command.getDefinition().getOption().getOptions().isEmpty())
				command.getDefinition().setOption(null);
			if (command.getOption() == null)
				command.setOption(new Option());
			command.getOption().addOption(NOFAIL, nofail);
			command.getOption().setPosNofail(posNofail);
		}
    }
}
