/*
 * 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$
 *  
 */
// https://issues.jboss.org/browse/TEIID-2392
// https://issues.jboss.org/browse/TEIID-4084
// https://issues.jboss.org/browse/TEIID-4877
// https://issues.jboss.org/browse/TEIID-2859
// https://issues.jboss.org/browse/TEIID-3167
// https://issues.jboss.org/browse/TEIID-3163
// https://issues.jboss.org/browse/TEIID-3346
// https://issues.jboss.org/browse/TEIID-3542
// https://issues.jboss.org/browse/TEIID-4006
// https://issues.jboss.org/browse/TEIID-4367
// https://issues.jboss.org/browse/TEIID-2085
// https://issues.jboss.org/browse/TEIID-5005
// https://issues.jboss.org/browse/TEIID-2159
// https://issues.jboss.org/browse/TEIID-3295
// https://issues.jboss.org/browse/TEIID-2381
// https://issues.jboss.org/browse/TEIID-2559
// https://issues.jboss.org/browse/TEIID-2555
// https://issues.jboss.org/browse/TEIID-3515
// https://issues.jboss.org/browse/TEIID-2152
// https://issues.jboss.org/browse/TEIID-3630
// https://issues.redhat.com/browse/TEIID-5366
// https://issues.redhat.com/browse/TEIID-3439
// https://issues.redhat.com/browse/TEIID-2273
// https://issues.redhat.com/browse/TEIID-2341
// https://issues.redhat.com/browse/TEIID-4202
// https://issues.redhat.com/browse/TEIID-3869
// https://issues.redhat.com/browse/TEIID-2163
// https://issues.redhat.com/browse/TEIID-2227
// https://issues.redhat.com/browse/TEIID-4892
package org.teiid.query.sql.visitor;

import static org.teiid.language.SQLConstants.Reserved.*;

import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.teiid.core.types.ArrayImpl;
import org.teiid.core.types.DataTypeManager;
import org.teiid.core.util.PropertiesUtils;
import org.teiid.core.util.StringUtil;
import org.teiid.language.SQLConstants;
import org.teiid.language.SQLConstants.NonReserved;
import org.teiid.language.SQLConstants.Tokens;
import org.teiid.metadata.BaseColumn.NullType;
import org.teiid.metadata.Column;
import org.teiid.query.metadata.TempMetadataID;
import org.teiid.query.sql.LanguageObject;
import org.teiid.query.sql.LanguageVisitor;
import org.teiid.query.sql.function.CreateFunctionCommand;
import org.teiid.query.sql.lang.*;
import org.teiid.query.sql.lang.ExistsCriteria.SubqueryHint;
import org.teiid.query.sql.lang.ObjectTable.ObjectColumn;
import org.teiid.query.sql.lang.Option.MakeDep;
import org.teiid.query.sql.lang.SourceHint.SpecificHint;
import org.teiid.query.sql.lang.TableFunctionReference.ProjectedColumn;
import org.teiid.query.sql.lang.TextTable.TextColumn;
import org.teiid.query.sql.lang.XMLTable.XMLColumn;
import org.teiid.query.sql.proc.*;
import org.teiid.query.sql.proc.Statement.Labeled;
import org.teiid.query.sql.symbol.*;
import org.teiid.query.sql.symbol.AggregateSymbol.Type;
import org.teiid.query.sql.symbol.XMLNamespaces.NamespaceItem;
import org.teiid.query.sql.symbol.WindowSpecification.Frame;
import org.teiid.query.sql.symbol.WindowSpecification.FrameType;
import org.teiid.translator.SourceSystemFunctions;

import com.datavirtuality.dv.core.interfaces.DVCaller;
import com.datavirtuality.dv.core.interfaces.DVCommand;
import com.datavirtuality.dv.core.interfaces.query.sql.lang.SelectInto;

/**
 * <p>
 * The SQLStringVisitor will visit a set of language objects and return the corresponding SQL string representation.
 * </p>
 */
public class SQLStringVisitor extends LanguageVisitor {

    public static final String UNDEFINED = "<undefined>"; //$NON-NLS-1$
    private static final String SPACE = " "; //$NON-NLS-1$
    private static final String BEGIN_HINT = "/*+"; //$NON-NLS-1$
    private static final String END_HINT = "*/"; //$NON-NLS-1$
    private static final char ID_ESCAPE_CHAR = '\"';
    private static final Set<String> INFIX_FUNCTIONS = new HashSet<String>(Arrays.asList(SQLConstants.Tokens.PLUS,
            SQLConstants.Tokens.MINUS, SQLConstants.Tokens.ALL_COLS, SQLConstants.Tokens.SLASH,
            SQLConstants.Tokens.CONCAT, SQLConstants.Tokens.DOUBLE_AMP));
	protected StringBuilder parts = new StringBuilder();
	private boolean shortNameOnly = false;

    /**
     * Helper to quickly get the parser string for an object using the visitor.
     * 
     * @param obj Language object
     * @return String SQL String for obj
     */
    public static final String getSQLString( LanguageObject obj ) {
        if (obj == null) {
            return UNDEFINED;
        }
        SQLStringVisitor visitor = new SQLStringVisitor();
        obj.acceptVisitor(visitor);
        return visitor.getSQLString();
    }

    /**
     * Retrieve completed string from the visitor.
     * 
     * @return Complete SQL string for the visited nodes
     */
    public String getSQLString() {
        return this.parts.toString();
    }

    protected void visitNode( LanguageObject obj ) {
        if (obj == null) {
            append(UNDEFINED);
            return;
        }
		// EXTENSION
		// ISSUE: DVCORE-2366
		// FIXED BY: Roman Pedchenko
		// DATE: 2014.10.07
		Boolean processed = (Boolean) DVCaller.call(105, obj, this, parts);
		if (processed != null && processed)
			return;
		// END EXTENSION
        obj.acceptVisitor(this);
    }

    protected SQLStringVisitor append( Object value ) {
        this.parts.append(value);
        return this;
    }

    protected void beginClause( @SuppressWarnings("unused") int level ) {
        append(SPACE);
    }

    // ############ Visitor methods for language objects ####################

    public void visit( BetweenCriteria obj ) {
        visitNode(obj.getExpression());
        append(SPACE);

        if (obj.isNegated()) {
            append(NOT);
            append(SPACE);
        }
        append(BETWEEN);
        append(SPACE);
        visitNode(obj.getLowerExpression());

        append(SPACE);
        append(AND);
        append(SPACE);
        visitNode(obj.getUpperExpression());
    }

    public void visit( CaseExpression obj ) {
        append(CASE);
        append(SPACE);
        visitNode(obj.getExpression());
        append(SPACE);

        for (int i = 0; i < obj.getWhenCount(); i++) {
            append(WHEN);
            append(SPACE);
            visitNode(obj.getWhenExpression(i));
            append(SPACE);
            append(THEN);
            append(SPACE);
            visitNode(obj.getThenExpression(i));
            append(SPACE);
        }

        if (obj.getElseExpression() != null) {
            append(ELSE);
            append(SPACE);
            visitNode(obj.getElseExpression());
            append(SPACE);
        }
        append(END);
    }

    public void visit( CompareCriteria obj ) {
        Expression leftExpression = obj.getLeftExpression();
	    if (leftExpression instanceof Criteria) {
		    append(Tokens.LPAREN);
            visitNode(leftExpression);
		    append(Tokens.RPAREN);
	    } else {
		    visitNode(leftExpression);
		}
        append(SPACE);
        append(obj.getOperatorAsString());
        append(SPACE);
        Expression rightExpression = obj.getRightExpression();
	    if (rightExpression instanceof Criteria) {
		    append(Tokens.LPAREN);
            visitNode(rightExpression);
		    append(Tokens.RPAREN);
		} else {
		    visitNode(rightExpression);
		}
    }

    public void visit( CompoundCriteria obj ) {
        // Get operator string
        int operator = obj.getOperator();
        String operatorStr = ""; //$NON-NLS-1$
        if (operator == CompoundCriteria.AND) {
            operatorStr = AND;
        } else if (operator == CompoundCriteria.OR) {
            operatorStr = OR;
        }

        // Get criteria
        List<Criteria> subCriteria = obj.getCriteria();

        // Build parts
        if (subCriteria.size() == 1) {
            // Special case - should really never happen, but we are tolerant
            Criteria firstChild = subCriteria.get(0);
            visitNode(firstChild);
        } else {
            // Add first criteria
            Iterator<Criteria> iter = subCriteria.iterator();

            while (iter.hasNext()) {
                // Add criteria
                Criteria crit = iter.next();
                append(Tokens.LPAREN);
                visitNode(crit);
                append(Tokens.RPAREN);

                if (iter.hasNext()) {
                    // Add connector
                    append(SPACE);
                    append(operatorStr);
                    append(SPACE);
                }
            }
        }
    }

    public void visit( Delete obj ) {
        // add delete clause
        append(DELETE);
        addSourceHint(obj.getSourceHint());
        append(SPACE);
        // add from clause
        append(FROM);
        append(SPACE);
        visitNode(obj.getGroup());

        // add where clause
        if (obj.getCriteria() != null) {
            beginClause(0);
            visitCriteria(WHERE, obj.getCriteria());
        }

        // Option clause
        if (obj.getOption() != null) {
            beginClause(0);
            visitNode(obj.getOption());
        }
    }

    public void visit( DependentSetCriteria obj ) {
        visitNode(obj.getExpression());

        // operator and beginning of list
        append(SPACE);
        if (obj.isNegated()) {
            append(NOT);
            append(SPACE);
        }
        append(IN);
        append(" (<dependent values>)"); //$NON-NLS-1$
    }

    public void visit( From obj ) {
        append(FROM);
        beginClause(1);
        registerNodes(obj.getClauses(), 0);
    }

    public void visit( GroupBy obj ) {
        append(GROUP);
        append(SPACE);
        append(BY);
        append(SPACE);
        registerNodes(obj.getSymbols(), 0);
    }

    public void visit( Insert obj ) {
        if (obj.isUpsert()) {
            append(SQLConstants.Reserved.UPSERT);
        } else {
        append(INSERT);
        }
    	addSourceHint(obj.getSourceHint());
        append(SPACE);
        append(INTO);
        append(SPACE);
        visitNode(obj.getGroup());

        if (!obj.getVariables().isEmpty()) {
            beginClause(2);

            // Columns clause
            List<ElementSymbol> vars = obj.getVariables();
            if (vars != null) {
                append("("); //$NON-NLS-1$
                registerNodes(vars, 0);
                append(")"); //$NON-NLS-1$
            }
        }
        beginClause(1);
        if (obj.getQueryExpression() != null) {
            visitNode(obj.getQueryExpression());
        } else if (obj.getTupleSource() != null) {
            append(VALUES);
            append(" (...)"); //$NON-NLS-1$
        } else if (obj.getValues() != null) {
            append(VALUES);
            beginClause(2);
            append("("); //$NON-NLS-1$
            registerNodes(obj.getValues(), 0);
            append(")"); //$NON-NLS-1$
        }

        // Option clause
        if (obj.getOption() != null) {
            beginClause(1);
            visitNode(obj.getOption());
        }
    }

    // EXTENSION
 	// ISSUE: DVCORE-2813
 	// FIXED BY: Salvatore Raunich
 	// DATE: 2015.06.05
    public void visit( SelectInto obj ) {
    	DVCaller.call(75, obj, this, parts);
    }
    // END EXTENSION

    public void visit( Create obj ) {
        append(CREATE);
        append(SPACE);
        Boolean isTemporaryTable = (Boolean) DVCaller.call(205, obj);
        if (isTemporaryTable == null || isTemporaryTable) {
            append(LOCAL);
            append(SPACE);
            append(TEMPORARY);
            append(SPACE);
        }
        append(TABLE);
        append(SPACE);
        visitNode(obj.getTable());
        append(SPACE);

        // Columns clause
        List<Column> columns = obj.getColumns();
        append("("); //$NON-NLS-1$
        Iterator<Column> iter = columns.iterator();
        while (iter.hasNext()) {
            Column element = iter.next();
            outputDisplayName(element.getName());
            append(SPACE);
            if (element.isAutoIncremented()) {
            	append(NonReserved.SERIAL);
            } else {
	            append(element.getRuntimeType());
	            if (element.getNullType() == NullType.No_Nulls) {
	            	append(NOT);
	            	append(SPACE);
	            	append(NULL);
	            }
            }
            if (iter.hasNext()) {
                append(", "); //$NON-NLS-1$
            }
        }
        if (!obj.getPrimaryKey().isEmpty()) {
            append(", "); //$NON-NLS-1$
            append(PRIMARY);
            append(" "); //$NON-NLS-1$
            append(NonReserved.KEY);
            append(Tokens.LPAREN);
            Iterator<ElementSymbol> pkiter = obj.getPrimaryKey().iterator();
            while (pkiter.hasNext()) {
                outputShortName(pkiter.next());
                if (pkiter.hasNext()) {
                    append(", "); //$NON-NLS-1$
                }
            }
            append(Tokens.RPAREN);
        }
        append(")"); //$NON-NLS-1$
    }

	public void visit(Drop obj) {
		append(DROP);
		append(SPACE);
		append(TABLE);
		append(SPACE);
		if (obj.isIfExists()) {
			append(IF);
			append(SPACE);
			append(EXISTS);
			append(SPACE);
		}
		visitNode(obj.getTable());
	}

    public void visit( IsNullCriteria obj ) {
        Expression expr = obj.getExpression();
        visitNode(expr);
        append(SPACE);
        append(IS);
        append(SPACE);
        if (obj.isNegated()) {
            append(NOT);
            append(SPACE);
        }
        append(NULL);
    }

    public void visit( JoinPredicate obj ) {
        addHintComment(obj);

        if (obj.hasHint()) {
            append("(");//$NON-NLS-1$
        }

        // left clause
        FromClause leftClause = obj.getLeftClause();
        if (leftClause instanceof JoinPredicate && !((JoinPredicate)leftClause).hasHint()) {
            append("("); //$NON-NLS-1$
            visitNode(leftClause);
            append(")"); //$NON-NLS-1$
        } else {
            visitNode(leftClause);
        }

        // join type
        append(SPACE);
        visitNode(obj.getJoinType());
        append(SPACE);

        // right clause
        FromClause rightClause = obj.getRightClause();
        if (rightClause instanceof JoinPredicate && !((JoinPredicate)rightClause).hasHint()) {
            append("("); //$NON-NLS-1$
            visitNode(rightClause);
            append(")"); //$NON-NLS-1$
        } else {
            visitNode(rightClause);
        }

        // join criteria
        List joinCriteria = obj.getJoinCriteria();
        if (joinCriteria != null && joinCriteria.size() > 0) {
            append(SPACE);
            append(ON);
            append(SPACE);
            Iterator critIter = joinCriteria.iterator();
            while (critIter.hasNext()) {
                Criteria crit = (Criteria)critIter.next();
                if (crit instanceof PredicateCriteria || crit instanceof AtomicCriteria) {
                    visitNode(crit);
                } else {
                    append("("); //$NON-NLS-1$
                    visitNode(crit);
                    append(")"); //$NON-NLS-1$
                }

                if (critIter.hasNext()) {
                    append(SPACE);
                    append(AND);
                    append(SPACE);
                }
            }
        }

        if (obj.hasHint()) {
            append(")"); //$NON-NLS-1$
        }
    }

    private void addHintComment( FromClause obj ) {
    	if (obj.hasHint()) {
    		append(BEGIN_HINT);
            append(SPACE);
            if (obj.isOptional()) {
                append(Option.OPTIONAL);
                append(SPACE);
            }
            if (obj.isMakeDep()) {
                append(Option.MAKEDEP);
                MakeDep makedep = obj.getMakeDep();
                appendMakeDepOptions(makedep);
                append(SPACE);
            }
            if (obj.isMakeNotDep()) {
                append(Option.MAKENOTDEP);
                append(SPACE);
            }
            if (obj.getMakeInd() != null) {
                append(MAKEIND);
                appendMakeDepOptions(obj.getMakeInd());
                append(SPACE);
            }
            if (obj.isNoUnnest()) {
                append(SubqueryHint.NOUNNEST);
                append(SPACE);
            }
            if (obj.isPreserve()) {
                append(FromClause.PRESERVE);
                append(SPACE);
            }
            append(END_HINT);
            append(SPACE);
    	}
    }

    public void visit( JoinType obj ) {
        String[] output = null;
        if (obj.equals(JoinType.JOIN_INNER)) {
            output = new String[] {INNER, SPACE, JOIN};
        } else if (obj.equals(JoinType.JOIN_CROSS)) {
            output = new String[] {CROSS, SPACE, JOIN};
        } else if (obj.equals(JoinType.JOIN_LEFT_OUTER)) {
            output = new String[] {LEFT, SPACE, OUTER, SPACE, JOIN};
        } else if (obj.equals(JoinType.JOIN_RIGHT_OUTER)) {
            output = new String[] {RIGHT, SPACE, OUTER, SPACE, JOIN};
        } else if (obj.equals(JoinType.JOIN_FULL_OUTER)) {
            output = new String[] {FULL, SPACE, OUTER, SPACE, JOIN};
        } else if (obj.equals(JoinType.JOIN_UNION)) {
            output = new String[] {UNION, SPACE, JOIN};
        } else if (obj.equals(JoinType.JOIN_SEMI)) {
            output = new String[] {"SEMI", SPACE, JOIN}; //$NON-NLS-1$
        } else if (obj.equals(JoinType.JOIN_ANTI_SEMI)) {
            output = new String[] {"ANTI SEMI", SPACE, JOIN}; //$NON-NLS-1$
        } else {
            throw new AssertionError();
        }
        for (String part : output) {
            append(part);
        }
    }

    public void visit( MatchCriteria obj ) {
        visitNode(obj.getLeftExpression());

        append(SPACE);
        if (obj.isNegated()) {
            append(NOT);
            append(SPACE);
        }
        switch (obj.getMode()) {
        case SIMILAR:
        	append(SIMILAR);
        	append(SPACE);
        	append(TO);
        	break;
        case LIKE:
        	append(LIKE);
        	break;
        case REGEX:
        	append(LIKE_REGEX);
        	break;
        }
        append(SPACE);

        visitNode(obj.getRightExpression());

        if (obj.getEscapeChar() != MatchCriteria.NULL_ESCAPE_CHAR) {
            append(SPACE);
            append(ESCAPE);
            append(" '"); //$NON-NLS-1$
            append(String.valueOf(obj.getEscapeChar()));
            append("'"); //$NON-NLS-1$
        }
    }

    public void visit( NotCriteria obj ) {
        append(NOT);
        append(" ("); //$NON-NLS-1$
        visitNode(obj.getCriteria());
        append(")"); //$NON-NLS-1$
    }

    public void visit( Option obj ) {
        append(OPTION);

        Collection<String> groups = obj.getDependentGroups();
        if (groups != null && groups.size() > 0) {
            append(" "); //$NON-NLS-1$
            append(MAKEDEP);
            append(" "); //$NON-NLS-1$

            Iterator<String> iter = groups.iterator();
            Iterator<MakeDep> iter1 = obj.getMakeDepOptions().iterator();

            while (iter.hasNext()) {
                outputDisplayName(iter.next());

                appendMakeDepOptions(iter1.next());

                if (iter.hasNext()) {
                    append(", ");//$NON-NLS-1$
                }
            }
        }

        groups = obj.getNotDependentGroups();
        if (groups != null && groups.size() > 0) {
            append(" "); //$NON-NLS-1$
            append(MAKENOTDEP);
            append(" "); //$NON-NLS-1$

            Iterator<String> iter = groups.iterator();

            while (iter.hasNext()) {
                outputDisplayName(iter.next());

                if (iter.hasNext()) {
                    append(", ");//$NON-NLS-1$
                }
            }
        }

        groups = obj.getNoCacheGroups();
        if (groups != null && groups.size() > 0) {
            append(" "); //$NON-NLS-1$
            append(NOCACHE);
            append(" "); //$NON-NLS-1$

            Iterator<String> iter = groups.iterator();

            while (iter.hasNext()) {
                outputDisplayName(iter.next());

                if (iter.hasNext()) {
                    append(", ");//$NON-NLS-1$
                }
            }
        } else if (obj.isNoCache()) {
            append(" "); //$NON-NLS-1$
            append(NOCACHE);
        } else if (obj.isReplace()) {
            append(" $REPLACE"); //$NON-NLS-1$
        }

    	// EXTENSION
    	// ISSUE: DVCORE-527, DVCORE-913
    	// FIXED BY: Roman Pedchenko, Dmitry Pogorelov
    	// DATE: 2013.07.08, 2014.10.01
	    if (obj.isOptionSet()) {
		    for (Map.Entry<String, String> entry : obj.getOptions().entrySet()) {
			    String optionName = entry.getKey().toUpperCase();
			    String optionValue = entry.getValue().toUpperCase();
			    append(" $" + optionName + " ");
			    if (optionValue != null)
			        append(optionValue);
		    }
	    }
    	// END EXTENSION
    }

    public SQLStringVisitor appendMakeDepOptions(MakeDep makedep) {
        boolean parens = false;
        if (makedep.getMax() != null || makedep.getJoin() != null) {
            append(Tokens.LPAREN);
            parens = true;
        }
        boolean space = false;
        if (makedep.getMax() != null) {
            if (space) {
                append(SPACE);
            } else {
                space = true;
            }
            append(NonReserved.MAX);
            append(Tokens.COLON);
            append(makedep.getMax());
        }
        if (makedep.getJoin() != null) {
            if (space) {
                append(SPACE);
            } else {
                space = true;
            }
            if (!makedep.getJoin()) {
                append(NO);
                append(SPACE);
            }
            append(JOIN);
        }
        if (parens) {
            append(Tokens.RPAREN);
        }
        return this;
    }

    public void visit( OrderBy obj ) {
        append(ORDER);
        append(SPACE);
        append(BY);
        append(SPACE);
        registerNodes(obj.getOrderByItems(), 0);
    }

    // EXTENSION
	// ISSUE: DV-84
	// FIXED BY: Roman Pedchenko
	// DATE: 2013.08.10
	@Override
	public void visit(Frame frame) {
        append(frame.isRange() ? RANGE : ROWS);
        append(Tokens.SPACE);
        if (frame.getEndType() != FrameType.NONE) {
        	append(BETWEEN);
        	append(Tokens.SPACE);
        	renderFramePart(false, frame.getStartType(), frame.getStartExpr(), frame.isStartFollowing());
        	append(Tokens.SPACE);
        	append(AND);
        	append(Tokens.SPACE);
        	renderFramePart(true, frame.getEndType(), frame.getEndExpr(), frame.isEndFollowing());
        } else
			renderFramePart(false, frame.getStartType(), frame.getStartExpr(), false);
	}
	protected void renderFramePart(boolean partFollowing, FrameType type, Expression expression, boolean following) {
		switch (type) {
			case UNBND:
				append("UNBOUNDED");
				append(Tokens.SPACE);
				append(partFollowing ? "FOLLOWING" : "PRECEDING");
				break;
			case ROW:
				append("CURRENT ROW");
				break;
			case EXPR:
				visitNode(expression);
				append(Tokens.SPACE);
				append(following ? "FOLLOWING" : "PRECEDING");
				break;
			default:
				break;
		}
	}
	// END EXTENSION

	@Override
    public void visit( OrderByItem obj ) {
        Expression ses = obj.getSymbol();
        if (ses instanceof AliasSymbol) {
            AliasSymbol as = (AliasSymbol)ses;
            outputDisplayName(as.getOutputName());
        } else {
            visitNode(ses);
        }
        if (!obj.isAscending()) {
            append(SPACE);
            append(DESC);
        } // Don't print default "ASC"
        if (obj.getNullOrdering() != null) {
            append(SPACE);
            append(NonReserved.NULLS);
            append(SPACE);
            append(obj.getNullOrdering().name());
        }
    }

    public void visit( DynamicCommand obj ) {
        append(EXECUTE);
        append(SPACE);
        append(IMMEDIATE);
        append(SPACE);
        visitNode(obj.getSql());

        if (obj.isAsClauseSet()) {
            beginClause(1);
            append(AS);
            append(SPACE);
            for (int i = 0; i < obj.getAsColumns().size(); i++) {
                ElementSymbol symbol = (ElementSymbol)obj.getAsColumns().get(i);
                outputShortName(symbol);
                append(SPACE);
                append(DataTypeManager.getDataTypeName(symbol.getType()));
                if (i < obj.getAsColumns().size() - 1) {
                    append(", "); //$NON-NLS-1$
                }
            }
        }

        if (obj.getIntoGroup() != null) {
            beginClause(1);
            append(INTO);
            append(SPACE);
            visitNode(obj.getIntoGroup());
        }

        if (obj.getUsing() != null && !obj.getUsing().isEmpty()) {
            beginClause(1);
            append(USING);
            append(SPACE);
            visitNode(obj.getUsing());
        }

        if (obj.getUpdatingModelCount() > 0) {
            beginClause(1);
            append(UPDATE);
            append(SPACE);
            if (obj.getUpdatingModelCount() > 1) {
                append("*"); //$NON-NLS-1$
            } else {
                append("1"); //$NON-NLS-1$
            }
        }
    }

    public void visit( SetClauseList obj ) {
        for (Iterator<SetClause> iterator = obj.getClauses().iterator(); iterator.hasNext();) {
            SetClause clause = iterator.next();
            visitNode(clause);
            if (iterator.hasNext()) {
                append(", "); //$NON-NLS-1$
            }
        }
    }

    public void visit( SetClause obj ) {
        ElementSymbol symbol = obj.getSymbol();
        outputShortName(symbol);
        append(" = "); //$NON-NLS-1$
        visitNode(obj.getValue());
    }
    
    @Override
    public void visit(WithQueryCommand obj) {
    	visitNode(obj.getGroupSymbol());
    	append(SPACE);
    	if (obj.getColumns() != null && !obj.getColumns().isEmpty()) {
    		append(Tokens.LPAREN);
    		shortNameOnly = true;
    		registerNodes(obj.getColumns(), 0);
    		shortNameOnly = false;
    		append(Tokens.RPAREN);
    		append(SPACE);
    	}
    	append(AS);
    	if (obj.isNoInline()) {
            append(SPACE);
            append(BEGIN_HINT);
	        append(SPACE);
	        append(WithQueryCommand.NO_INLINE);
	        append(SPACE);
	        append(END_HINT);
    	}
    	append(SPACE);
    	append(Tokens.LPAREN);
        if (obj.getCommand() == null) {
            append("<dependent values>"); //$NON-NLS-1$
        } else {
            visitNode(obj.getCommand());
        }
    	append(Tokens.RPAREN);
    }

    public void visit( Query obj ) {
    	addCacheHint(obj.getCacheHint());
    	addWithClause(obj);
        append(SELECT);

    	SourceHint sh = obj.getSourceHint();
    	addSourceHint(sh);
    	if (obj.getSelect() != null) {
    		visitNode(obj.getSelect());
    	}

        if (obj.getInto() != null) {
            beginClause(1);
            visitNode(obj.getInto());
        }

        if (obj.getFrom() != null) {
            beginClause(1);
            visitNode(obj.getFrom());
        }

        // Where clause
        if (obj.getCriteria() != null) {
            beginClause(1);
            visitCriteria(WHERE, obj.getCriteria());
        }

        // Group by clause
        if (obj.getGroupBy() != null) {
            beginClause(1);
            visitNode(obj.getGroupBy());
        }

        // Having clause
        if (obj.getHaving() != null) {
            beginClause(1);
            visitCriteria(HAVING, obj.getHaving());
        }

        // Order by clause
        if (obj.getOrderBy() != null) {
            beginClause(1);
            visitNode(obj.getOrderBy());
        }

        if (obj.getLimit() != null) {
            beginClause(1);
            visitNode(obj.getLimit());
        }

        // Option clause
        if (obj.getOption() != null) {
            beginClause(1);
            visitNode(obj.getOption());
        }
    }

	private void addSourceHint(SourceHint sh) {
		if (sh != null) {
        	append(SPACE);
        	append(BEGIN_HINT);
        	append("sh"); //$NON-NLS-1$
			if (sh.isUseAliases()) {
				append(SPACE);
				append("KEEP ALIASES"); //$NON-NLS-1$
			}
        	if (sh.getGeneralHint() != null) {
        		appendSourceHintValue(sh.getGeneralHint());
            } else {
                append(SPACE);
        	}
			if (sh.getSpecificHints() != null) {
				for (Map.Entry<String, SpecificHint> entry : sh.getSpecificHints().entrySet()) {
        			append(entry.getKey());
					if (entry.getValue().isUseAliases()) {
						append(SPACE);
						append("KEEP ALIASES"); //$NON-NLS-1$
					}
					appendSourceHintValue(entry.getValue().getHint());
        		}
        	}
        	append(END_HINT);
    	}
	}

	private void addWithClause(QueryCommand obj) {
		if (obj.getWith() != null) {
    		append(WITH);
    		append(SPACE);
            registerNodes(obj.getWith(), 0);
    		beginClause(0);
    	}
	}

    protected void visitCriteria( String keyWord,
                                  Criteria crit ) {
        append(keyWord);
        append(SPACE);
        visitNode(crit);
    }

    public void visit( SearchedCaseExpression obj ) {
        append(CASE);
        for (int i = 0; i < obj.getWhenCount(); i++) {
            append(SPACE);
            append(WHEN);
            append(SPACE);
            visitNode(obj.getWhenCriteria(i));
            append(SPACE);
            append(THEN);
            append(SPACE);
            visitNode(obj.getThenExpression(i));
        }
        append(SPACE);
        if (obj.getElseExpression() != null) {
            append(ELSE);
            append(SPACE);
            visitNode(obj.getElseExpression());
            append(SPACE);
        }
        append(END);
    }

    public void visit( Select obj ) {
		if (obj.isDistinct()) {
            append(SPACE);
            append(DISTINCT);
        }
        beginClause(2);

        Iterator<Expression> iter = obj.getSymbols().iterator();
        while (iter.hasNext()) {
            Expression symbol = iter.next();
            visitNode(symbol);
            if (iter.hasNext()) {
                append(", "); //$NON-NLS-1$
            }
        }
	}

	private void appendSourceHintValue(String sh) {
		append(Tokens.COLON);
		append('\'');
		append(escapeStringValue(sh, "'")); //$NON-NLS-1$
		append('\'');
		append(SPACE);
	}

    public void visit( SetCriteria obj ) {
        // variable
        visitNode(obj.getExpression());

        // operator and beginning of list
        append(SPACE);
        if (obj.isNegated()) {
            append(NOT);
            append(SPACE);
        }
        append(IN);
        append(" ("); //$NON-NLS-1$

        // value list
        Collection vals = obj.getValues();
        int size = vals.size();
        if (size == 1) {
            Iterator iter = vals.iterator();
            Expression expr = (Expression)iter.next();
            visitNode(expr);
        } else if (size > 1) {
            Iterator iter = vals.iterator();
            Expression expr = (Expression)iter.next();
            visitNode(expr);
            while (iter.hasNext()) {
                expr = (Expression)iter.next();
                append(", "); //$NON-NLS-1$
                visitNode(expr);
            }
        }
        append(")"); //$NON-NLS-1$
    }

    public void visit( SetQuery obj ) {
        addCacheHint(obj.getCacheHint());
        addWithClause(obj);
        QueryCommand query = obj.getLeftQuery();
        appendSetQuery(obj, query, false);

        beginClause(0);
        append(obj.getOperation());

        if (obj.isAll()) {
            append(SPACE);
            append(ALL);
        }
        beginClause(0);
        query = obj.getRightQuery();
        appendSetQuery(obj, query, true);

        if (obj.getOrderBy() != null) {
            beginClause(0);
            visitNode(obj.getOrderBy());
        }

        if (obj.getLimit() != null) {
            beginClause(0);
            visitNode(obj.getLimit());
        }

        if (obj.getOption() != null) {
            beginClause(0);
            visitNode(obj.getOption());
        }
    }

    protected void appendSetQuery( SetQuery parent,
                                   QueryCommand obj,
                                   boolean right ) {
        if (obj.getLimit() != null || obj.getOrderBy() != null || (right && ((obj instanceof SetQuery
            && ((parent.isAll() && !((SetQuery)obj).isAll()) || parent.getOperation() != ((SetQuery)obj).getOperation()))))) {
            append(Tokens.LPAREN);
            visitNode(obj);
            append(Tokens.RPAREN);
        } else {
            visitNode(obj);
        }
    }

    public void visit( StoredExecutable obj ) {
        addCacheHint(obj.getCacheHint());
        if (obj.isCalledWithReturn()) {
        	for (SEParameter param : obj.getParameters()) {
    			if (param.getParameterType() == SEParameter.RETURN_VALUE) {
    				if (param.getExpression() == null) {
    					append("?"); //$NON-NLS-1$
    				} else {
    					visitNode(param.getExpression());
    				}
    			}
    		}
        	append(SPACE);
        	append(Tokens.EQ);
        	append(SPACE);
        }
        // exec clause
        append(EXEC);
        append(SPACE);
		String name = (String) DVCaller.call(49, obj, this);
		if (name != null)
			append(name);
		else
	        append(obj.getProcedureName());
        append("("); //$NON-NLS-1$
        boolean first = true;
        for (SEParameter param : obj.getParameters()) {
        	if (param.isUsingDefault() || param.getParameterType() == SEParameter.RETURN_VALUE 
        			|| param.getParameterType() == SEParameter.RESULT_SET || param.getExpression() == null) {
        		continue;
        	}
        	if (first) {
        		first = false;
        	} else {
        		append(", "); //$NON-NLS-1$
        	}
            if (obj.displayNamedParameters()) {
                append(escapeSinglePart(Symbol.getShortName(param.getParameterSymbol().getOutputName())));
                append(" => "); //$NON-NLS-1$
            }

            boolean addParens = !obj.displayNamedParameters() && param.getExpression() instanceof CompareCriteria;
            if (addParens) {
                append(Tokens.LPAREN);
            }
            visitNode(param.getExpression());
            if (addParens) {
                append(Tokens.RPAREN);
            }
        }
        append(")"); //$NON-NLS-1$

        // Option clause
        if (obj.getOption() != null) {
            beginClause(1);
            visitNode(obj.getOption());
        }
    }

    public void addCacheHint( CacheHint obj ) {
        if (obj == null) {
            return;
        }
        append(BEGIN_HINT);
        append(SPACE);
        append(CacheHint.CACHE);
        boolean addParens = false;
        if (obj.isPrefersMemory()) {
            append(Tokens.LPAREN);
            addParens = true;
            append(CacheHint.PREF_MEM);
        }
        if (obj.getTtl() != null) {
            if (!addParens) {
                append(Tokens.LPAREN);
                addParens = true;
            } else {
                append(SPACE);
            }
            append(CacheHint.TTL);
            append(obj.getTtl());
        }
        if (obj.getUpdatable() != null) {
            if (!addParens) {
                append(Tokens.LPAREN);
                addParens = true;
            } else {
                append(SPACE);
            }
            append(CacheHint.UPDATABLE);
        }
        if (obj.getScope() != null) {
            if (!addParens) {
                append(Tokens.LPAREN);
                addParens = true;
            } else {
                append(SPACE);
            }     
            append(CacheHint.SCOPE);
            append(obj.getScope());            
        }
        if (addParens) {
            append(Tokens.RPAREN);
        }
        append(SPACE);
        append(END_HINT);
        beginClause(0);
    }

    public void visit( SubqueryFromClause obj ) {
        addHintComment(obj);
        if (obj.isLateral()) {
            append(LATERAL);
        }
        append("(");//$NON-NLS-1$
        visitNode(obj.getCommand());
        append(")");//$NON-NLS-1$
        append(" AS ");//$NON-NLS-1$
        append(escapeSinglePart(obj.getOutputName()));
    }

    public void visit( SubquerySetCriteria obj ) {
        // variable
        visitNode(obj.getExpression());

        // operator and beginning of list
        append(SPACE);
        if (obj.isNegated()) {
            append(NOT);
            append(SPACE);
        }
        append(IN);
        addSubqueryHint(obj.getSubqueryHint());
        append(" ("); //$NON-NLS-1$
        visitNode(obj.getCommand());
        append(")"); //$NON-NLS-1$
    }

    public void visit( UnaryFromClause obj ) {
        addHintComment(obj);
        if (obj.getExpandedCommand() != null) {
            append("(");//$NON-NLS-1$
            visitNode(obj.getExpandedCommand());
            append(")");//$NON-NLS-1$
            append(" AS ");//$NON-NLS-1$
            append(escapeSinglePart(obj.getGroup().getName()));
        } else {
        	visitNode(obj.getGroup());
        }
    }

    public void visit( Update obj ) {
        // Update clause
        append(UPDATE);
        addSourceHint(obj.getSourceHint());
        append(SPACE);
        visitNode(obj.getGroup());
        beginClause(1);
        // Set clause
        append(SET);
        beginClause(2);
        visitNode(obj.getChangeList());

        // Where clause
        if (obj.getCriteria() != null) {
            beginClause(1);
            visitCriteria(WHERE, obj.getCriteria());
        }

        // Option clause
        if (obj.getOption() != null) {
            beginClause(1);
            visitNode(obj.getOption());
        }
    }

    public void visit( Into obj ) {
        append(INTO);
        append(SPACE);
        visitNode(obj.getGroup());
    }

    // ############ Visitor methods for symbol objects ####################

    public void visit( AggregateSymbol obj ) {
        append(obj.getName());
        append("("); //$NON-NLS-1$

        if (obj.isDistinct()) {
            append(DISTINCT);
            append(" "); //$NON-NLS-1$
        } else if (obj.getAggregateFunction() == Type.USER_DEFINED) {
        	append(ALL);
        	append(" "); //$NON-NLS-1$
        }

        if (obj.getArgs().length == 0) {
        	if (obj.getAggregateFunction() == Type.COUNT) {
        		append(Tokens.ALL_COLS);
        	}
        } else {
            registerNodes(obj.getArgs(), 0);
        }

        if (obj.getOrderBy() != null) {
            append(SPACE);
            visitNode(obj.getOrderBy());
        }
        append(")"); //$NON-NLS-1$
        
        if (obj.getCondition() != null) {
        	append(SPACE);
        	append(FILTER);
        	append(Tokens.LPAREN);
        	append(WHERE);
        	append(SPACE);
        	append(Tokens.LPAREN);
        	append(obj.getCondition());
        	append(Tokens.RPAREN);
        	append(Tokens.RPAREN);
        }
    }

    public void visit( AliasSymbol obj ) {
        visitNode(obj.getSymbol());
        append(SPACE);
        append(AS);
        append(SPACE);
        append(escapeSinglePart(obj.getOutputName()));
    }

    @Override
    public void visit( MultipleElementSymbol obj ) {
    	if (obj.getGroup() == null) {
    		append(Tokens.ALL_COLS);
    	} else {
            if (isSinglePart(obj.getGroup())) {
                append(escapeSinglePart(obj.getGroup().getOutputName()));
            } else {
    		// EXTENSION
			// ISSUE: DVCORE-716,DVCORE-992
			// FIXED BY: Roman Pedchenko
			// DATE: 2013.12.17,2014.08.08
			// the fix prevents generation of strange artifacts like 'views.view1 AS v.*'
//    		visitNode(obj.getGroup());
                //boolean shortNameOnly1 = shortNameOnly;
                //shortNameOnly = true;
                visitNode(obj.getGroup());
                //shortNameOnly = shortNameOnly1;
                // END EXTENSION
            }
            append(Tokens.DOT);
            append(Tokens.ALL_COLS);
    	}
    }

    public void visit( Constant obj ) {
        Class<?> type = obj.getType();
	// EXTENSION
	// ISSUE: DVCORE-2441
	// FIXED BY: Roman Pedchenko
	// DATE: 2014.10.20
	// ADOPTED FROM: https://issues.jboss.org/browse/TEIID-2111
	//     String[] constantParts = null;
	//     if (obj.isMultiValued()) {
        boolean multiValued = obj.isMultiValued();
	    Object value = obj.getValue();
	    outputLiteral(type, multiValued, value);
    }

	private void outputLiteral(Class<?> type,
			boolean multiValued, Object value) throws AssertionError {
		String[] constantParts = null;
	    if (multiValued) {
	// END EXTENSION
            constantParts = new String[] {"?"}; //$NON-NLS-1$
		// EXTENSION
		// ISSUE: DVCORE-2441
		// FIXED BY: Roman Pedchenko
		// DATE: 2014.10.20
		// ADOPTED FROM: https://issues.jboss.org/browse/TEIID-2111
		// } else if (obj.isNull()) {
		//     if (type.equals(DataTypeManager.DefaultDataClasses.BOOLEAN)) {
		//         constantParts = new String[] {UNKNOWN};
		//     } else {
		//         constantParts = new String[] {"null"}; //$NON-NLS-1$
		//     }
		// } else {
		//     if (Number.class.isAssignableFrom(type)) {
		//         constantParts = new String[] {obj.getValue().toString()};
        } else if (value == null) {
		    if (type.equals(DataTypeManager.DefaultDataClasses.BOOLEAN)) {
		        constantParts = new String[] {UNKNOWN};
		    } else {
		        constantParts = new String[] {"null"}; //$NON-NLS-1$
		    }
		} else {
			// EXTENSION
			// ISSUE: DVCORE-2441
			// FIXED BY: Roman Pedchenko
			// DATE: 2014.10.23
			// ADOPTED FROM: https://issues.jboss.org/browse/TEIID-247, 2166, 2151, 2234, 2581, 2571, 2369, 1386, 2226, 2233, 2224, 2225, 2257
			// if (value.getClass() == ArrayValue.class) {
			// 	ArrayValue av = (ArrayValue)value;
			if (value.getClass() == ArrayImpl.class) {
				ArrayImpl av = (ArrayImpl)value;
			// END EXTENSION
				append(Tokens.LPAREN);
				for (int i = 0; i < av.getValues().length; i++) {
					if (i > 0) {
						append(Tokens.COMMA);
						append(SPACE);
					}
					Object value2 = av.getValues()[i];
					outputLiteral(value2!=null?value2.getClass():av.getValues().getClass().getComponentType(), multiValued, value2);
				}
				append(Tokens.RPAREN);
				return;
			}
			if (Number.class.isAssignableFrom(type)) {
		        constantParts = new String[] {value.toString()};
		// END EXTENSION
			// EXTENSION
			// ISSUE: DVCORE-987
			// FIXED BY: Roman Pedchenko
			// DATE: 2014.02.12
			// EXTENSION
			// ISSUE: DVCORE-2441
			// FIXED BY: Roman Pedchenko
			// DATE: 2014.10.20
			// ADOPTED FROM: https://issues.jboss.org/browse/TEIID-2111
			// } else if (obj.getValue().getClass().isArray()) {
			// 	Object[] array = (Object[]) obj.getValue();
			} else if (value.getClass().isArray()) {
				Object[] array = (Object[]) value;
			// END EXTENSION
				append("array(");
				for (int i = 0; i < array.length; i++) {
					if (i > 0)
						append(", ");
					// EXTENSION
					// ISSUE: DVCORE-2441
					// FIXED BY: Roman Pedchenko
					// DATE: 2014.10.20
					// ADOPTED FROM: https://issues.jboss.org/browse/TEIID-2111
					// final Object value = array[i];
					final Object value1 = array[i];
					// END EXTENSION
					Constant constant;
					// EXTENSION
					// ISSUE: DVCORE-2441
					// FIXED BY: Roman Pedchenko
					// DATE: 2014.10.20
					// ADOPTED FROM: https://issues.jboss.org/browse/TEIID-2111
					// if (value == null)
					if (value1 == null)
					// END EXTENSION
						constant = Constant.NULL_CONSTANT;
					else if (type.equals(DataTypeManager.DefaultDataClasses.BOOLEAN))
						// EXTENSION
						// ISSUE: DVCORE-2441
						// FIXED BY: Roman Pedchenko
						// DATE: 2014.10.20
						// ADOPTED FROM: https://issues.jboss.org/browse/TEIID-2111
						// constant = new Constant(value, DataTypeManager.DefaultDataClasses.BOOLEAN);
						constant = new Constant(value1, DataTypeManager.DefaultDataClasses.BOOLEAN);
						// END EXTENSION
					else if (type.equals(DataTypeManager.DefaultDataClasses.STRING))
						// EXTENSION
						// ISSUE: DVCORE-2441
						// FIXED BY: Roman Pedchenko
						// DATE: 2014.10.20
						// ADOPTED FROM: https://issues.jboss.org/browse/TEIID-2111
						// constant = new Constant(value, DataTypeManager.DefaultDataClasses.STRING);
						constant = new Constant(value1, DataTypeManager.DefaultDataClasses.STRING);
						// END EXTENSION
					else if (type.equals(DataTypeManager.DefaultDataClasses.VARBINARY))
						// EXTENSION
						// ISSUE: DVCORE-2441
						// FIXED BY: Roman Pedchenko
						// DATE: 2014.10.20
						// ADOPTED FROM: https://issues.jboss.org/browse/TEIID-2111
						// constant = new Constant(value, DataTypeManager.DefaultDataClasses.VARBINARY);
						constant = new Constant(value1, DataTypeManager.DefaultDataClasses.VARBINARY);
						// END EXTENSION
					else if (type.equals(DataTypeManager.DefaultDataClasses.TIMESTAMP))
						// EXTENSION
						// ISSUE: DVCORE-2441
						// FIXED BY: Roman Pedchenko
						// DATE: 2014.10.20
						// ADOPTED FROM: https://issues.jboss.org/browse/TEIID-2111
						// constant = new Constant(value, DataTypeManager.DefaultDataClasses.TIMESTAMP);
						constant = new Constant(value1, DataTypeManager.DefaultDataClasses.TIMESTAMP);
						// END EXTENSION
					else if (type.equals(DataTypeManager.DefaultDataClasses.TIME))
						// EXTENSION
						// ISSUE: DVCORE-2441
						// FIXED BY: Roman Pedchenko
						// DATE: 2014.10.20
						// ADOPTED FROM: https://issues.jboss.org/browse/TEIID-2111
						// constant = new Constant(value, DataTypeManager.DefaultDataClasses.TIME);
						constant = new Constant(value1, DataTypeManager.DefaultDataClasses.TIME);
						// END EXTENSION
					else if (type.equals(DataTypeManager.DefaultDataClasses.DATE))
						// EXTENSION
						// ISSUE: DVCORE-2441
						// FIXED BY: Roman Pedchenko
						// DATE: 2014.10.20
						// ADOPTED FROM: https://issues.jboss.org/browse/TEIID-2111
						// constant = new Constant(value, DataTypeManager.DefaultDataClasses.DATE);
						constant = new Constant(value1, DataTypeManager.DefaultDataClasses.DATE);
						// END EXTENSION
					else
						// EXTENSION
						// ISSUE: DVCORE-2441
						// FIXED BY: Roman Pedchenko
						// DATE: 2014.10.20
						// ADOPTED FROM: https://issues.jboss.org/browse/TEIID-2111
						// constant = new Constant(value, DataTypeManager.DefaultDataClasses.OBJECT);
						constant = new Constant(value1, DataTypeManager.DefaultDataClasses.OBJECT);
						// END EXTENSION
					visit(constant);
				}
				append(")");
				return;
			// END EXTENSION
		    // EXTENSION
		    // ISSUE: DVCORE-2441
		    // FIXED BY: Roman Pedchenko
		    // DATE: 2014.10.20
		    // ADOPTED FROM: https://issues.jboss.org/browse/TEIID-2111
		    // } else if (type.equals(DataTypeManager.DefaultDataClasses.BOOLEAN)) {
		    //     constantParts = new String[] {obj.getValue().equals(Boolean.TRUE) ? TRUE : FALSE};
		    // } else if (type.equals(DataTypeManager.DefaultDataClasses.TIMESTAMP)) {
		    //     constantParts = new String[] {"{ts'", obj.getValue().toString(), "'}"}; //$NON-NLS-1$ //$NON-NLS-2$
		    // } else if (type.equals(DataTypeManager.DefaultDataClasses.TIME)) {
		    //     constantParts = new String[] {"{t'", obj.getValue().toString(), "'}"}; //$NON-NLS-1$ //$NON-NLS-2$
		    // } else if (type.equals(DataTypeManager.DefaultDataClasses.DATE)) {
		    //     constantParts = new String[] {"{d'", obj.getValue().toString(), "'}"}; //$NON-NLS-1$ //$NON-NLS-2$
		    // } else if (type.equals(DataTypeManager.DefaultDataClasses.VARBINARY)) {
		    // 	constantParts = new String[] {"X'", obj.getValue().toString(), "'"}; //$NON-NLS-1$ //$NON-NLS-2$
		    // }
		    // if (constantParts == null) {
		    // 	if (DataTypeManager.isLOB(type)) {
		    // 		constantParts = new String[] {"?"}; //$NON-NLS-1$
		    // 	} else {
		    //         String strValue = obj.getValue().toString();
		    //         strValue = escapeStringValue(strValue, "'"); //$NON-NLS-1$
		    //         constantParts = new String[] {"'", strValue, "'"}; //$NON-NLS-1$ //$NON-NLS-2$
		    // 	}
		    // }
		    } else if (type.equals(DataTypeManager.DefaultDataClasses.BOOLEAN)) {
		        constantParts = new String[] {value.equals(Boolean.TRUE) ? TRUE : FALSE};
		    } else if (type.equals(DataTypeManager.DefaultDataClasses.TIMESTAMP)) {
		        constantParts = new String[] {"{ts'", value.toString(), "'}"}; //$NON-NLS-1$ //$NON-NLS-2$
		    } else if (type.equals(DataTypeManager.DefaultDataClasses.TIME)) {
		        constantParts = new String[] {"{t'", value.toString(), "'}"}; //$NON-NLS-1$ //$NON-NLS-2$
		    } else if (type.equals(DataTypeManager.DefaultDataClasses.DATE)) {
		        constantParts = new String[] {"{d'", value.toString(), "'}"}; //$NON-NLS-1$ //$NON-NLS-2$
		    } else if (type.equals(DataTypeManager.DefaultDataClasses.VARBINARY)) {
		    	constantParts = new String[] {"X'", value.toString(), "'"}; //$NON-NLS-1$ //$NON-NLS-2$
		    }
		    if (constantParts == null) {
		    	if (DataTypeManager.isLOB(type)) {
		    		constantParts = new String[] {"?"}; //$NON-NLS-1$
		    	} else {
				    append('\'');
		            String strValue = value.toString();
				    for (int i = 0; i < strValue.length(); i++) {
					    char c = strValue.charAt(i);
					    if (c == '\'') {
						    parts.append('\'');
						} else if (Character.isISOControl(c)) {
						    parts.append("\\u" + PropertiesUtils.toHex((c >> 12) & 0xF) + PropertiesUtils.toHex((c >>  8) & 0xF) //$NON-NLS-1$
								    + PropertiesUtils.toHex((c >>  4) & 0xF) + PropertiesUtils.toHex(c & 0xF));
						    continue;
						}
					    parts.append(c);
					}
				    parts.append('\'');
				    return;
		    	}
		    }
		    // END EXTENSION
        }

        for (String string : constantParts) {
            append(string);
        }
    }

    /**
     * Take a string literal and escape it as necessary. By default, this converts ' to ''.
     * 
     * @param str String literal value (unquoted), never null
     * @return Escaped string literal value
     */
    public static String escapeStringValue( String str,
                                     String tick ) {
        return StringUtil.replaceAll(str, tick, tick + tick);
    }

    @Override
    public void visit( ElementSymbol obj ) {
    	if (obj.getDisplayMode().equals(ElementSymbol.DisplayMode.SHORT_OUTPUT_NAME) || shortNameOnly) {
            outputShortName(obj);
            return;
        }
        String name = obj.getOutputName();
        //always use full qualification to avoid stripping quotes when an alias with a . is used
        //we can detect this easily with getDefinition != null, but to avoid overlap with existing
        //logic, such as procedure relational naming, we need to only affect non-scalar temp metadata
        GroupSymbol groupSymbol = obj.getGroupSymbol();
        if (name.contains(ElementSymbol.SEPARATOR)
                && groupSymbol != null
                && isSinglePart(groupSymbol)
                && groupSymbol.getOutputName().contains(ElementSymbol.SEPARATOR)) {
            append(escapeSinglePart(groupSymbol.getOutputName()));
            append(ElementSymbol.SEPARATOR);
            outputShortName(obj);
            return;
        }
        if (obj.getDisplayMode().equals(ElementSymbol.DisplayMode.FULLY_QUALIFIED)) {
            name = obj.getName();
        }
        outputDisplayName(name);
    }

    private boolean isSinglePart(GroupSymbol groupSymbol) {
        return (groupSymbol.getDefinition() != null || (!groupSymbol.isExecutable()
                && groupSymbol.getMetadataID() instanceof TempMetadataID
                && ((TempMetadataID) groupSymbol.getMetadataID())
                .getMetadataType() != TempMetadataID.Type.SCALAR));
    }

    private void outputShortName( ElementSymbol obj ) {
        outputDisplayName(Symbol.getShortName(obj.getOutputName()));
    }

    private void outputDisplayName( String name ) {
        String[] pathParts = name.split("\\."); //$NON-NLS-1$
        for (int i = 0; i < pathParts.length; i++) {
            if (i > 0) {
                append(Symbol.SEPARATOR);
            }
            append(escapeSinglePart(pathParts[i]));
        }
    }

    public void visit( ExpressionSymbol obj ) {
        visitNode(obj.getExpression());
    }

    public void visit( Function obj ) {
        String name = obj.getName();
        Expression[] args = obj.getArgs();
        if (obj.isImplicit()) {
            // Hide this function, which is implicit
            visitNode(args[0]);

        } else if (name.equalsIgnoreCase(CONVERT) || name.equalsIgnoreCase(CAST)) {
            append(name);
            append("("); //$NON-NLS-1$

            if (args != null && args.length > 0) {
                visitNode(args[0]);

                if (name.equalsIgnoreCase(CONVERT)) {
                    append(", "); //$NON-NLS-1$
                } else {
                    append(" "); //$NON-NLS-1$
                    append(AS);
                    append(" "); //$NON-NLS-1$
                }

                if (args.length < 2 || args[1] == null || !(args[1] instanceof Constant)) {
                    append(UNDEFINED);
                } else {
                    append(((Constant)args[1]).getValue());
                }
            }
            append(")"); //$NON-NLS-1$

        } else if (INFIX_FUNCTIONS.contains(name)) {
            append("("); //$NON-NLS-1$

            if (args != null) {
                for (int i = 0; i < args.length; i++) {
                    visitNode(args[i]);
                    if (i < (args.length - 1)) {
                        append(SPACE);
                        append(name);
                        append(SPACE);
                    }
                }
            }
            append(")"); //$NON-NLS-1$

        } else if (name.equalsIgnoreCase(NonReserved.TIMESTAMPADD) || name.equalsIgnoreCase(NonReserved.TIMESTAMPDIFF)) {
            append(name);
            append("("); //$NON-NLS-1$

            if (args != null && args.length > 0) {
                append(((Constant)args[0]).getValue());
                registerNodes(args, 1);
            }
            append(")"); //$NON-NLS-1$

        } else if (name.equalsIgnoreCase(SourceSystemFunctions.XMLPI)) {
            append(name);
            append("(NAME "); //$NON-NLS-1$
            outputDisplayName((String)((Constant)args[0]).getValue());
            registerNodes(args, 1);
            append(")"); //$NON-NLS-1$
        } else if (name.equalsIgnoreCase(SourceSystemFunctions.TRIM)) {
        	append(name);
        	append(SQLConstants.Tokens.LPAREN);
        	String value = (String)((Constant)args[0]).getValue();
        	if (!value.equalsIgnoreCase(BOTH)) {
	        	append(((Constant)args[0]).getValue());
	            append(" "); //$NON-NLS-1$
        	}
            append(args[1]);
            append(" "); //$NON-NLS-1$
            append(FROM);
            append(" "); //$NON-NLS-1$
            append(args[2]);
            append(")"); //$NON-NLS-1$
        } else {
            append(name);
            append("("); //$NON-NLS-1$
            registerNodes(args, 0);
            append(")"); //$NON-NLS-1$
        }
    }

    private void registerNodes( LanguageObject[] objects,
                                int begin ) {
        registerNodes(Arrays.asList(objects), begin);
    }

    private void registerNodes( List<? extends LanguageObject> objects,
                                int begin ) {
        for (int i = begin; i < objects.size(); i++) {
            if (i > 0) {
                append(", "); //$NON-NLS-1$
            }
            visitNode(objects.get(i));
        }
    }

    public void visit( GroupSymbol obj ) {
        String alias = null;
        String fullGroup = obj.getOutputName();
		// EXTENSION
		// ISSUE: DVCORE-992
		// FIXED BY: Roman Pedchenko
		// DATE: 2014.08.08
//        if (obj.getOutputDefinition() != null) {
        if (!shortNameOnly && obj.getOutputDefinition() != null) {
   		// END EXTENSION
            alias = obj.getOutputName();
            fullGroup = obj.getOutputDefinition();
        }

        outputDisplayName(fullGroup);

        if (alias != null) {
            append(SPACE);
            append(AS);
            append(SPACE);
            append(escapeSinglePart(alias));
        }
    }

    public void visit( Reference obj ) {
        if (!obj.isPositional() && obj.getExpression() != null) {
            visitNode(obj.getExpression());
        } else {
            append("?"); //$NON-NLS-1$
        }
    }

    // ############ Visitor methods for storedprocedure language objects ####################

    public void visit( Block obj ) {
    	addLabel(obj);
        List<Statement> statements = obj.getStatements();
        // Add first clause
        append(BEGIN);
        if (obj.isAtomic()) {
        	append(SPACE);
        	append(ATOMIC);
        }
        append("\n"); //$NON-NLS-1$
	// EXTENSION
	// ISSUE: DVCORE-2441
	// FIXED BY: Roman Pedchenko
	// DATE: 2014.10.23
	// ADOPTED FROM: https://issues.jboss.org/browse/TEIID-247, 2166, 2151, 2234, 2581, 2571, 2369, 1386, 2226, 2233, 2224, 2225, 2257
	//     Iterator<Statement> stmtIter = statements.iterator();
        addStatements(statements);
        if (obj.getExceptionGroup() != null) {
        	append(NonReserved.EXCEPTION);
        	append(SPACE);
        	outputDisplayName(obj.getExceptionGroup());
        	append("\n"); //$NON-NLS-1$
        	if (obj.getExceptionStatements() != null) {
        		addStatements(obj.getExceptionStatements());
        	}
        }
        append(END);
    }

	private void addStatements(List<Statement> statements) {
		Iterator<Statement> stmtIter = statements.iterator();
	// END EXTENSION
        while (stmtIter.hasNext()) {
            // Add each statement
            addTabs(1);
            visitNode(stmtIter.next());
            append("\n"); //$NON-NLS-1$
        }
        addTabs(0);
	// EXTENSION
	// ISSUE: DVCORE-2441
	// FIXED BY: Roman Pedchenko
	// DATE: 2014.10.23
	// ADOPTED FROM: https://issues.jboss.org/browse/TEIID-247, 2166, 2151, 2234, 2581, 2571, 2369, 1386, 2226, 2233, 2224, 2225, 2257
	//     append(END);
	// }
	}
	// END EXTENSION

	private void addLabel(Labeled obj) {
		if (obj.getLabel() != null) {
    		outputDisplayName(obj.getLabel());
    		append(SPACE);
    		append(Tokens.COLON);
    		append(SPACE);
    	}
	}

    /**
	 * @param level  
	 */
    protected void addTabs( int level ) {
    }

    public void visit( CommandStatement obj ) {
        visitNode(obj.getCommand());
        // EXTENSION
        // ISSUE: DVCORE-2441
        // FIXED BY: Roman Pedchenko
        // DATE: 2014.10.23
        // ADOPTED FROM: https://issues.jboss.org/browse/TEIID-247, 2166, 2151, 2234, 2581, 2571, 2369, 1386, 2226, 2233, 2224, 2225, 2257
        if (!obj.isReturnable()) {
        	append(SPACE);
        	append(WITHOUT);
        	append(SPACE);
        	append(RETURN);
        }
        // END EXTENSION
        append(";"); //$NON-NLS-1$
    }

    public void visit( CreateProcedureCommand obj ) {
    	addCacheHint(obj.getCacheHint());
        append(CREATE);
        append(SPACE);
        append(VIRTUAL);
        append(SPACE);
        append(PROCEDURE);
        append("\n"); //$NON-NLS-1$
        addTabs(0);
        visitNode(obj.getBlock());
    }

    public void visit( CreateFunctionCommand obj ) {
        addCacheHint(obj.getCacheHint());
        append(CREATE);
        append(SPACE);
        append(VIRTUAL);
        append(SPACE);
        append(FUNCTION);
    }

    public void visit( DeclareStatement obj ) {
        append(DECLARE);
        append(SPACE);
        append(obj.getVariableType());
        append(SPACE);
        createAssignment(obj);
    }

    /**
     * @param obj
     */
    private void createAssignment( AssignmentStatement obj ) {
        visitNode(obj.getVariable());
        if (obj.getExpression() != null) {
            if (obj.getArrayIndex() != null) {
                append(Tokens.LSBRACE);
                append(obj.getArrayIndex());
                append(Tokens.RSBRACE);
            }
            append(" = "); //$NON-NLS-1$
            visitNode(obj.getExpression());
        }
        append(";"); //$NON-NLS-1$
    }

    public void visit( IfStatement obj ) {
        append(IF);
        append("("); //$NON-NLS-1$
        visitNode(obj.getCondition());
        append(")\n"); //$NON-NLS-1$
        addTabs(0);
        visitNode(obj.getIfBlock());
        if (obj.hasElseBlock()) {
            append("\n"); //$NON-NLS-1$
            addTabs(0);
            append(ELSE);
            append("\n"); //$NON-NLS-1$
            addTabs(0);
            visitNode(obj.getElseBlock());
        }
    }

    public void visit( AssignmentStatement obj ) {
        createAssignment(obj);
    }

    // EXTENSION
    // ISSUE: DVCORE-2441
    // FIXED BY: Roman Pedchenko
    // DATE: 2014.10.23
    // ADOPTED FROM: https://issues.jboss.org/browse/TEIID-247, 2166, 2151, 2234, 2581, 2571, 2369, 1386, 2226, 2233, 2224, 2225, 2257
    // public void visit( RaiseErrorStatement obj ) {
    //     append(ERROR);
    public void visit( RaiseStatement obj ) {
        append(NonReserved.RAISE);
    // END EXTENSION
        append(SPACE);
        // EXTENSION
        // ISSUE: DVCORE-2441
        // FIXED BY: Roman Pedchenko
        // DATE: 2014.10.23
        // ADOPTED FROM: https://issues.jboss.org/browse/TEIID-247, 2166, 2151, 2234, 2581, 2571, 2369, 1386, 2226, 2233, 2224, 2225, 2257
        if (obj.isWarning()) {
        	append(SQLWARNING);
            append(SPACE);
        }
        // END EXTENSION
        visitNode(obj.getExpression());
        append(";"); //$NON-NLS-1$
    }
    // EXTENSION
    // ISSUE: DVCORE-2441
    // FIXED BY: Roman Pedchenko
    // DATE: 2014.10.23
    // ADOPTED FROM: https://issues.jboss.org/browse/TEIID-247, 2166, 2151, 2234, 2581, 2571, 2369, 1386, 2226, 2233, 2224, 2225, 2257
    
    @Override
    public void visit(ExceptionExpression exceptionExpression) {
		append(SQLEXCEPTION);
    	append(SPACE);
    	visitNode(exceptionExpression.getMessage());
    	if (exceptionExpression.getSqlState() != null) {
    		append(SPACE);
    		append(SQLSTATE);
    		append(SPACE);
    		append(exceptionExpression.getSqlState());
    		if (exceptionExpression.getErrorCode() != null) {
    			append(Tokens.COMMA);
    			append(SPACE);
    			append(exceptionExpression.getErrorCode());
    		}
    	}
    	if (exceptionExpression.getParent() != null) {
        	append(SPACE);
        	append(NonReserved.CHAIN);
        	append(SPACE);
        	append(exceptionExpression.getParent());
    	}
    }
    // END EXTENSION

    @Override
    public void visit(ReturnStatement obj) {
        append(RETURN);
        if (obj.getExpression() != null) {
            append(SPACE);
            visitNode(obj.getExpression());
        }
        append(Tokens.SEMICOLON);
    }

    public void visit( BranchingStatement obj ) {
    	switch (obj.getMode()) {
    	case CONTINUE:
    		append(CONTINUE);
    		break;
    	case BREAK:
    		append(BREAK);
    		break;
    	case LEAVE:
    		append(LEAVE);
    		break;
    	}
    	if (obj.getLabel() != null) {
    		append(SPACE);
    		outputDisplayName(obj.getLabel());
    	}
        append(";"); //$NON-NLS-1$
    }

    public void visit( LoopStatement obj ) {
    	addLabel(obj);
        append(LOOP);
        append(" "); //$NON-NLS-1$
        append(ON);
        append(" ("); //$NON-NLS-1$
        visitNode(obj.getCommand());
        append(") "); //$NON-NLS-1$
        append(AS);
        append(" "); //$NON-NLS-1$
        // EXTENSION
        // ISSUE: DVCORE-2441
        // FIXED BY: Roman Pedchenko
        // DATE: 2014.10.23
        // ADOPTED FROM: https://issues.jboss.org/browse/TEIID-247, 2166, 2151, 2234, 2581, 2571, 2369, 1386, 2226, 2233, 2224, 2225, 2257
        // append(obj.getCursorName());
        outputDisplayName(obj.getCursorName());
        // END EXTENSION
        append("\n"); //$NON-NLS-1$
        addTabs(0);
        visitNode(obj.getBlock());
    }

    public void visit( WhileStatement obj ) {
    	addLabel(obj);
        append(WHILE);
        append("("); //$NON-NLS-1$
        visitNode(obj.getCondition());
        append(")\n"); //$NON-NLS-1$
        addTabs(0);
        visitNode(obj.getBlock());
    }

    public void visit( ExistsCriteria obj ) {
    	if (obj.isNegated()) {
            append(NOT);
            append(SPACE);
        }
        append(EXISTS);
        addSubqueryHint(obj.getSubqueryHint());
        append(" ("); //$NON-NLS-1$
        visitNode(obj.getCommand());
        append(")"); //$NON-NLS-1$
    }
    
    public void addSubqueryHint(SubqueryHint hint) {
    	if (hint.isNoUnnest()) {
        	append(SPACE);
        	append(BEGIN_HINT);
            append(SPACE);
            append(SubqueryHint.NOUNNEST);
            append(SPACE);
            append(END_HINT);
    	} else if (hint.isDepJoin()) {
    		append(SPACE);
        	append(BEGIN_HINT);
            append(SPACE);
            append(SubqueryHint.DJ);
            append(SPACE);
            append(END_HINT);
        } else if (hint.isMergeJoin()) {
            append(SPACE);
        	append(BEGIN_HINT);
            append(SPACE);
            append(SubqueryHint.MJ);
            append(SPACE);
            append(END_HINT);
        }
    }

    public void visit( SubqueryCompareCriteria obj ) {
        Expression leftExpression = obj.getLeftExpression();
        visitNode(leftExpression);

        String operator = obj.getOperatorAsString();
        String quantifier = obj.getPredicateQuantifierAsString();

        // operator and beginning of list
        append(SPACE);
        append(operator);
        append(SPACE);
        append(quantifier);
        addSubqueryHint(obj.getSubqueryHint());
        append(" ("); //$NON-NLS-1$
        visitNode(obj.getCommand());
        append(")"); //$NON-NLS-1$
    }

    public void visit( ScalarSubquery obj ) {
        if (obj.getSubqueryHint().isDepJoin() || obj.getSubqueryHint().isMergeJoin() || obj.getSubqueryHint().isNoUnnest()) {
            if (this.parts.length() > 0 && this.parts.charAt(this.parts.length()-1) == ' ') {
                this.parts.setLength(this.parts.length() -1);
            }
            addSubqueryHint(obj.getSubqueryHint());
            append(SPACE);
        }
        append("("); //$NON-NLS-1$
        visitNode(obj.getCommand());
        append(")"); //$NON-NLS-1$
    }

    @Override
    public void visit( XMLAttributes obj ) {
        append(XMLATTRIBUTES);
        append("("); //$NON-NLS-1$
        registerNodes(obj.getArgs(), 0);
        append(")"); //$NON-NLS-1$
    }

    @Override
    public void visit( XMLElement obj ) {
        append(XMLELEMENT);
    	append('('); //$NON-NLS-1$
        if (!obj.isEvalName()) {
        	append("NAME "); //$NON-NLS-1$
        	outputDisplayName(obj.getName());
        } else {
        	append("EVALNAME "); //$NON-NLS-1$
        	visitNode(obj.getNameExpr().getExpression());
        }
        if (obj.getNamespaces() != null) {
            append(", "); //$NON-NLS-1$
            visitNode(obj.getNamespaces());
        }
        if (obj.getAttributes() != null) {
            append(", "); //$NON-NLS-1$
            visitNode(obj.getAttributes());
        }
        if (!obj.getContent().isEmpty()) {
            append(", "); //$NON-NLS-1$
        }
        registerNodes(obj.getContent(), 0);
        append(")"); //$NON-NLS-1$
    }

    @Override
    public void visit( XMLForest obj ) {
        append(XMLFOREST);
        append("("); //$NON-NLS-1$
        if (obj.getNamespaces() != null) {
            visitNode(obj.getNamespaces());
            append(", "); //$NON-NLS-1$
        }
        registerNodes(obj.getArgs(), 0);
        append(")"); //$NON-NLS-1$
    }

    @Override
 	public void visit( JSONObject obj ) {
        append(NonReserved.JSONOBJECT);
        append("("); //$NON-NLS-1$
        registerNodes(obj.getArgs(), 0);
        append(")"); //$NON-NLS-1$
    }
    
    @Override
    public void visit( TextLine obj ) {
        append(FOR); 
        append(SPACE);
        registerNodes(obj.getExpressions(), 0);
        
        if (obj.getDelimiter() != null) {
            append(SPACE);
            append(NonReserved.DELIMITER);
            append(SPACE);
            visitNode(new Constant(obj.getDelimiter()));
        }
        if (obj.getQuote() != null) {
            append(SPACE);
            append(NonReserved.QUOTE);
            append(SPACE);
            visitNode(new Constant(obj.getQuote()));
        }
        if (obj.isIncludeHeader()) {
            append(SPACE);
            append(NonReserved.HEADER);
        }
        if (obj.getEncoding() != null) {
        	append(SPACE);
            append(NonReserved.ENCODING);
        	append(SPACE);
        	outputDisplayName(obj.getEncoding());
        }
    }    

    @Override
    public void visit( XMLNamespaces obj ) {
        append(XMLNAMESPACES);
        append("("); //$NON-NLS-1$
        for (Iterator<NamespaceItem> items = obj.getNamespaceItems().iterator(); items.hasNext();) {
            NamespaceItem item = items.next();
            if (item.getPrefix() == null) {
                if (item.getUri() == null) {
                    append("NO DEFAULT"); //$NON-NLS-1$
                } else {
                    append("DEFAULT "); //$NON-NLS-1$
                    visitNode(item.getUri());
                }
            } else {
                visitNode(item.getUri());
                append(" AS "); //$NON-NLS-1$
                outputDisplayName(item.getPrefix());
            }
            if (items.hasNext()) {
                append(", "); //$NON-NLS-1$
            }
        }
        append(")"); //$NON-NLS-1$
    }

    public void visit( Limit obj ) {
    	if (!obj.isStrict()) {
    		append(BEGIN_HINT);
    		append(SPACE);
    		append(Limit.NON_STRICT);
    		append(SPACE);
    		append(END_HINT);
    		append(SPACE);
    	}
    	if (obj.getRowLimit() == null) {
    		append(OFFSET);
    		append(SPACE);
            visitNode(obj.getOffset());
            append(SPACE);
            append(ROWS);
    		return;
    	}
        append(LIMIT);
        if (obj.getOffset() != null) {
            append(SPACE);
            visitNode(obj.getOffset());
            append(","); //$NON-NLS-1$
        }
        append(SPACE);
        visitNode(obj.getRowLimit());
    }

    @Override
    public void visit( TextTable obj ) {
        addHintComment(obj);
        append("TEXTTABLE("); //$NON-NLS-1$
        visitNode(obj.getFile());
        if (obj.getSelector() != null) {
        	append(SPACE);
        	append(NonReserved.SELECTOR);
        	append(SPACE);
	        outputLiteral(String.class, false, obj.getSelector());
        }
        append(SPACE);
        append(NonReserved.COLUMNS);
	    boolean noTrim = obj.isNoTrim();
        for (Iterator<TextColumn> cols = obj.getColumns().iterator(); cols.hasNext();) {
            TextColumn col = cols.next();
            append(SPACE);
            outputDisplayName(col.getName());
            append(SPACE);
	        if (col.isOrdinal()) {
		        append(FOR);
		        append(SPACE);
		        append(NonReserved.ORDINALITY);
		    } else {
		        if (col.getHeader() != null) {
			        append(NonReserved.HEADER);
			        append(SPACE);
			        outputLiteral(String.class, false, col.getHeader());
			        append(SPACE);
			    }
		        append(col.getType());
		        if (col.getWidth() != null) {
			        append(SPACE);
			        append(NonReserved.WIDTH);
			        append(SPACE);
			        append(col.getWidth());
		        }
		        if (!noTrim && col.isNoTrim()) {
			        append(SPACE);
			        append(NO);
			        append(SPACE);
			        append(NonReserved.TRIM);
		        }
		        if (col.getSelector() != null) {
			        append(SPACE);
			        append(NonReserved.SELECTOR);
			        append(SPACE);
			        outputLiteral(String.class, false, col.getSelector());
			        append(SPACE);
			        append(col.getPosition());
		        }
	        }
            if (cols.hasNext()) {
                append(","); //$NON-NLS-1$
            }
        }
        if (!obj.isUsingRowDelimiter()) {
        	append(SPACE);
            append(NO);
        	append(SPACE);
            append(ROW);
        	append(SPACE);
            append(NonReserved.DELIMITER);
        } else if (obj.getRowDelimiter() != null) {
	        append(SPACE);
	        append(ROW);
	        append(SPACE);
	        append(NonReserved.DELIMITER);
	        append(SPACE);
	        visitNode(new Constant(obj.getRowDelimiter()));
	    }
        if (obj.getDelimiter() != null) {
            append(SPACE);
            append(NonReserved.DELIMITER);
            append(SPACE);
            visitNode(new Constant(obj.getDelimiter()));
        }
        if (obj.getQuote() != null) {
            append(SPACE);
            if (obj.isEscape()) {
                append(ESCAPE);
            } else {
                append(NonReserved.QUOTE);
            }
            append(SPACE);
            visitNode(new Constant(obj.getQuote()));
        }
        if (obj.getHeader() != null) {
            append(SPACE);
            append(NonReserved.HEADER);
            if (1 != obj.getHeader()) {
                append(SPACE);
                append(obj.getHeader());
            }
        }
        if (obj.getSkip() != null) {
            append(SPACE);
            append(NonReserved.SKIP);
            append(SPACE);
            append(obj.getSkip());
        }
	    if (noTrim) {
		    append(SPACE);
		    append(NO);
		    append(SPACE);
		    append(NonReserved.TRIM);
		}
	    endTableFunction(obj);
    }

    private void endTableFunction(TableFunctionReference obj) {
        append(")");//$NON-NLS-1$
        append(SPACE);
        append(AS);
        append(SPACE);
        append(escapeSinglePart(obj.getName()));
    }

    @Override
    public void visit( XMLTable obj ) {
        addHintComment(obj);
        append("XMLTABLE("); //$NON-NLS-1$
        if (obj.getNamespaces() != null) {
            visitNode(obj.getNamespaces());
            append(","); //$NON-NLS-1$
            append(SPACE);
        }
        visitNode(obj.getXquery());
        if (!obj.getPassing().isEmpty()) {
            append(SPACE);
            append(NonReserved.PASSING);
            append(SPACE);
            registerNodes(obj.getPassing(), 0);
        }
        // EXTENSION
        // ISSUE: DVCORE-1078
        // FIXED BY: Roman Pedchenko
        // DATE: 2014.03.26
        // ADOPTED FROM: https://issues.jboss.org/browse/TEIID-2141
        // if (!obj.getColumns().isEmpty()) {
        if (!obj.getColumns().isEmpty() && !obj.isUsingDefaultColumn()) {
        // END EXTENSION
            append(SPACE);
            append(NonReserved.COLUMNS);
            for (Iterator<XMLColumn> cols = obj.getColumns().iterator(); cols.hasNext();) {
                XMLColumn col = cols.next();
                append(SPACE);
                outputDisplayName(col.getName());
                append(SPACE);
                if (col.isOrdinal()) {
                    append(FOR);
                    append(SPACE);
                    append(NonReserved.ORDINALITY);
                } else {
                    append(col.getType());
                    if (col.getDefaultExpression() != null) {
                        append(SPACE);
                        append(DEFAULT);
                        append(SPACE);
                        visitNode(col.getDefaultExpression());
                    }
                    if (col.getPath() != null) {
                        append(SPACE);
                        append(NonReserved.PATH);
                        append(SPACE);
                        visitNode(col.getPath());
                    }
                }
                if (cols.hasNext()) {
                    append(","); //$NON-NLS-1$
                }
            }
        }
        endTableFunction(obj);
    }

    @Override
    // EXTENSION
    // ISSUE: DVCORE-1078
    // FIXED BY: Roman Pedchenko
    // DATE: 2014.03.26
    // ADOPTED FROM: https://issues.jboss.org/browse/TEIID-2141
    public void visit( ObjectTable obj ) {
        addHintComment(obj);
        append("OBJECTTABLE("); //$NON-NLS-1$
        if (obj.getScriptingLanguage() != null) {
        	append(LANGUAGE);
            append(SPACE);
            visitNode(new Constant(obj.getScriptingLanguage()));
            append(SPACE);
        }
        visitNode(new Constant(obj.getRowScript()));
        if (!obj.getPassing().isEmpty()) {
            append(SPACE);
            append(NonReserved.PASSING);
            append(SPACE);
            registerNodes(obj.getPassing(), 0);
        }
        append(SPACE);
        append(NonReserved.COLUMNS);
        for (Iterator<ObjectColumn> cols = obj.getColumns().iterator(); cols.hasNext();) {
        	ObjectColumn col = cols.next();
            append(SPACE);
            outputDisplayName(col.getName());
            append(SPACE);
            append(col.getType());
            append(SPACE);
            visitNode(new Constant(col.getPath()));
            if (col.getDefaultExpression() != null) {
                append(SPACE);
                append(DEFAULT);
                append(SPACE);
                visitNode(col.getDefaultExpression());
            }
            if (cols.hasNext()) {
                append(","); //$NON-NLS-1$
            }
        }
        endTableFunction(obj);
    }

    @Override
    public void visit(JsonTable obj ) {
        addHintComment(obj);
        append(NonReserved.JSONTABLE).append(Tokens.LPAREN);
        visitNode(obj.getJson());
        append(Tokens.COMMA).append(SPACE);
        visitNode(new Constant(obj.getRowPath()));
        if (obj.getNullLeaf() != null) {
            append(Tokens.COMMA).append(SPACE);
            visitNode(new Constant(obj.getNullLeaf()));
        }
        append(SPACE);
        append(NonReserved.COLUMNS);
        for (Iterator<JsonTable.JsonColumn> cols = obj.getColumns().iterator(); cols.hasNext();) {
            JsonTable.JsonColumn col = cols.next();
            append(SPACE);
            outputDisplayName(col.getName());
            append(SPACE);
            if (col.isOrdinal()) {
                append(FOR);
                append(SPACE);
                append(NonReserved.ORDINALITY);
            } else {
                append(col.getType());
                if (col.getPath() != null) {
                    append(SPACE);
                    append(NonReserved.PATH);
                    append(SPACE);
                    visitNode(new Constant(col.getPath()));
                }
            }
            if (cols.hasNext()) {
                append(","); //$NON-NLS-1$
            }
        }
        endTableFunction(obj);
    }
    @Override
    // END EXTENSION
    public void visit( XMLQuery obj ) {
        append("XMLQUERY("); //$NON-NLS-1$
        if (obj.getNamespaces() != null) {
            visitNode(obj.getNamespaces());
            append(","); //$NON-NLS-1$
            append(SPACE);
        }
        visitNode(obj.getXquery().getExpression());
        if (!obj.getPassing().isEmpty()) {
            append(SPACE);
            append(NonReserved.PASSING);
            append(SPACE);
            registerNodes(obj.getPassing(), 0);
        }
        if (obj.getEmptyOnEmpty() != null) {
            append(SPACE);
            if (obj.getEmptyOnEmpty()) {
                append(NonReserved.EMPTY);
            } else {
                append(NULL);
            }
            append(SPACE);
            append(ON);
            append(SPACE);
            append(NonReserved.EMPTY);
        }
        append(")");//$NON-NLS-1$
    }

    @Override
    public void visit( DerivedColumn obj ) {
        visitNode(obj.getExpression());
        if (obj.getAlias() != null) {
            append(SPACE);
            append(AS);
            append(SPACE);
            outputDisplayName(obj.getAlias());
        }
    }

    @Override
    public void visit( XMLSerialize obj ) {
        append(XMLSERIALIZE);
        append(Tokens.LPAREN);
        if (obj.getDocument() != null) {
            if (obj.getDocument()) {
                append(NonReserved.DOCUMENT);
            } else {
                append(NonReserved.CONTENT);
            }
            append(SPACE);
        }
        visitNode(obj.getExpression());
        if (obj.getTypeString() != null) {
            append(SPACE);
            append(AS);
            append(SPACE);
            append(obj.getTypeString());
        }
        if (obj.getEncoding() != null) {
            append(SPACE);
            append(NonReserved.ENCODING);
            append(SPACE);
            append(escapeSinglePart(obj.getEncoding()));
        }
        if (obj.getVersion() != null) {
            append(SPACE);
            append(NonReserved.VERSION);
            append(SPACE);
            append(new Constant(obj.getVersion()));
        }
        if (obj.getDeclaration() != null) {
            append(SPACE);
            if (obj.getDeclaration()) {
                append(NonReserved.INCLUDING);
            } else {
                append(NonReserved.EXCLUDING);
            }
            append(SPACE);
            append(NonReserved.XMLDECLARATION);
        }
        append(Tokens.RPAREN);
    }

    @Override
    public void visit( QueryString obj ) {
        append(NonReserved.QUERYSTRING);
        append("("); //$NON-NLS-1$
        visitNode(obj.getPath());
        if (!obj.getArgs().isEmpty()) {
            append(","); //$NON-NLS-1$
            append(SPACE);
            registerNodes(obj.getArgs(), 0);
        }
        append(")"); //$NON-NLS-1$
    }

    @Override
    public void visit( XMLParse obj ) {
        append(XMLPARSE);
        append(Tokens.LPAREN);
        if (obj.isDocument()) {
            append(NonReserved.DOCUMENT);
        } else {
            append(NonReserved.CONTENT);
        }
        append(SPACE);
        visitNode(obj.getExpression());
        if (obj.isWellFormed()) {
            append(SPACE);
            append(NonReserved.WELLFORMED);
        }
        append(Tokens.RPAREN);
    }

    @Override
    public void visit( ExpressionCriteria obj ) {
        visitNode(obj.getExpression());
    }
    
    @Override
    public void visit(TriggerAction obj) {
    	append(FOR);
        append(SPACE);
        append(EACH);
        append(SPACE);
        append(ROW);
        append("\n"); //$NON-NLS-1$
        addTabs(0);
    	visitNode(obj.getBlock());
    }
    
    @Override
    public void visit(ArrayTable obj) {
        addHintComment(obj);
    	append("ARRAYTABLE("); //$NON-NLS-1$
        if (obj.getSingleRow() != null) {
            append(obj.getSingleRow()?ROW:ROWS);
            append(SPACE);
        }
        visitNode(obj.getArrayValue());
        append(SPACE);
        append(NonReserved.COLUMNS);

        for (Iterator<ProjectedColumn> cols = obj.getColumns().iterator(); cols.hasNext();) {
        	ProjectedColumn col = cols.next();
            append(SPACE);
            outputDisplayName(col.getName());
            append(SPACE);
            append(col.getType());
            if (cols.hasNext()) {
                append(","); //$NON-NLS-1$
            }
        }
        endTableFunction(obj);
    }

    /*private void addMakeDep(FromClause obj) {
        MakeDep makeDep = obj.getMakeDep();
        if (makeDep != null && !makeDep.isSimple()) {
            append(SPACE);
            append(MAKEDEP);
            appendMakeDepOptions(makeDep);
        }
        makeDep = obj.getMakeInd();
        if (makeDep != null && !makeDep.isSimple()) {
            append(SPACE);
            append(MAKEIND);
            appendMakeDepOptions(makeDep);
        }
    }*/

    @Override
    public void visit(AlterProcedure alterProcedure) {
    	append(ALTER);
    	append(SPACE);
    	append(PROCEDURE);
    	append(SPACE);
    	append(alterProcedure.getTarget());
    	beginClause(1);
    	append(AS);
    	addCacheHint(alterProcedure.getCacheHint());
    	append(alterProcedure.getDefinition().getBlock());
    }

    @Override
    public void visit(AlterFunction alterFunction) {
        append(ALTER);
        append(SPACE);
        append(FUNCTION);
        append(SPACE);
        append(alterFunction.getTarget());
    }
    
    @Override
    public void visit(AlterTrigger alterTrigger) {
    	if (alterTrigger.isCreate()) {
    		append(CREATE);
    	} else {
    		append(ALTER);
    	}
    	append(SPACE);
    	append(TRIGGER);
    	append(SPACE);
    	append(ON);
    	append(SPACE);
    	append(alterTrigger.getTarget());
    	beginClause(0);
    	append(NonReserved.INSTEAD);
    	append(SPACE);
    	append(OF);
    	append(SPACE);
    	append(alterTrigger.getEvent());
    	if (alterTrigger.getDefinition() != null) {
	    	beginClause(0);
	    	append(AS);
	    	append("\n"); //$NON-NLS-1$
	        addTabs(0);
	    	append(alterTrigger.getDefinition());
    	} else {
    		append(SPACE);
    		append(alterTrigger.getEnabled()?NonReserved.ENABLED:NonReserved.DISABLED);
    	}
    }
    
    @Override
    public void visit(AlterView alterView) {
    	append(ALTER);
    	append(SPACE);
    	append(NonReserved.VIEW);
    	append(SPACE);
    	append(alterView.getTarget());
    	beginClause(0);
    	append(AS);
    	append("\n"); //$NON-NLS-1$
        addTabs(0);
    	append(alterView.getDefinition());
    }
    
    @Override
    public void visit(WindowFunction windowFunction) {
    	append(windowFunction.getFunction());
    	append(SPACE);
    	append(OVER);
    	append(SPACE);
    	append(windowFunction.getWindowSpecification());
    }
    
    @Override
    public void visit(WindowSpecification windowSpecification) {
    	append(Tokens.LPAREN);
    	boolean needsSpace = false;
    	if (windowSpecification.getPartition() != null) {
    		append(PARTITION);
    		append(SPACE);
    		append(BY);
    		append(SPACE);
    		registerNodes(windowSpecification.getPartition(), 0);
    		needsSpace = true;
    	}
    	if (windowSpecification.getOrderBy() != null) {
    		if (needsSpace) {
    			append(SPACE);
    		}
    		append(windowSpecification.getOrderBy());
        	// EXTENSION
        	// ISSUE: DV-84
        	// FIXED BY: Roman Pedchenko
        	// DATE: 2013.06.26
    		needsSpace = true;
        	// END EXTENSION
    	}
    	// EXTENSION
    	// ISSUE: DV-84
    	// FIXED BY: Roman Pedchenko
    	// DATE: 2013.06.26
    	if (windowSpecification.getFrame() != null) {
    		if (needsSpace)
				append(SPACE);
    		append(windowSpecification.getFrame());
    	}
    	// END EXTENSION
    	append(Tokens.RPAREN);	
    }
    
    @Override
    public void visit(Array array) {
    	// EXTENSION
    	// ISSUE: DVCORE-2441
    	// FIXED BY: Roman Pedchenko
    	// DATE: 2014.10.20
    	// ADOPTED FROM: https://issues.jboss.org/browse/TEIID-2111
    	// append(Tokens.LPAREN);
    	if (!array.isImplicit()) {
    		append(Tokens.LPAREN);
    	}
    	// END EXTENSION
    	registerNodes(array.getExpressions(), 0);
    	// EXTENSION
    	// ISSUE: DVCORE-2441
    	// FIXED BY: Roman Pedchenko
    	// DATE: 2014.10.20
    	// ADOPTED FROM: https://issues.jboss.org/browse/TEIID-2111
    	// append(Tokens.RPAREN);
    	// EXTENSION
    	// ISSUE: DVCORE-2441
    	// FIXED BY: Roman Pedchenko
    	// DATE: 2014.10.23
    	// ADOPTED FROM: https://issues.jboss.org/browse/TEIID-247, 2166, 2151, 2234, 2581, 2571, 2369, 1386, 2226, 2233, 2224, 2225, 2257
    	// if (!array.isImplicit()) {
    	// 	append(Tokens.RPAREN);
    	if (!array.isImplicit()) {
    		if (array.getExpressions().size() == 1) {
    			append(Tokens.COMMA);
    		}
    		append(Tokens.RPAREN);
    	// END EXTENSION
    	}
    	// END EXTENSION
    }

    public static String escapeSinglePart( String part ) {
        if (isReservedWord(part)) {
            return ID_ESCAPE_CHAR + part + ID_ESCAPE_CHAR;
        }
        boolean escape = true;
        // EXTENSION
        // ISSUE: INFRA-137
        // FIXED BY: Dmitry Pogorelov
        // DATE: 2014.12.05
        if (!part.isEmpty()) {
        // END EXTENSION
        char start = part.charAt(0);
        if (start == '#' || start == '@' || StringUtil.isLetter(start)) {
            escape = false;
            for (int i = 1; !escape && i < part.length(); i++) {
                char c = part.charAt(i);
                escape = !StringUtil.isLetterOrDigit(c) && c != '_';
            }
        }
        // EXTENSION
        // ISSUE: INFRA-137
        // FIXED BY: Dmitry Pogorelov
        // DATE: 2014.12.05
        }
        // END EXTENSION
        if (escape) {
            return ID_ESCAPE_CHAR + escapeStringValue(part, "\"") + ID_ESCAPE_CHAR; //$NON-NLS-1$
        }
        return part;
    }

    /**
     * Check whether a string is considered a reserved word or not. Subclasses may override to change definition of reserved word.
     * 
     * @param string String to check
     * @return True if reserved word
     */
    static boolean isReservedWord( String string ) {
        if (string == null) {
            return false;
        }
        return SQLConstants.isReservedWord(string);
    }

    // EXTENSION
	// ISSUE: DVCORE-830
	// FIXED BY: Roman Pedchenko
	// DATE: 2013.12.12
	@Override
	public void visit(DVCommand command) {
		DVCaller.call(35, command, this, parts);
	}
	// END EXTENSION

    @Override
    public void visit(Unpivot obj) {
        registerNodes(obj.getFrom().getClauses(), 0);
        append(" UNPIVOT(");
        append(obj.getCol());
        append(" FOR ");
        append(obj.getForCol());
        append(" IN (");
        for (Iterator<ElementSymbol> it = obj.getValues().iterator(); it.hasNext(); ) {
            ElementSymbol el = it.next();
            append(ID_ESCAPE_CHAR);
            append(el.getShortName());
            append(ID_ESCAPE_CHAR);
            if (it.hasNext())
                append(", ");
        }
        append(")) AS ");
        append(obj.getGroupSymbol().getName());
    }
}
