com.tesora.dve.sql.parser.TranslatorUtils.java Source code

Java tutorial

Introduction

Here is the source code for com.tesora.dve.sql.parser.TranslatorUtils.java

Source

package com.tesora.dve.sql.parser;

/*
 * #%L
 * Tesora Inc.
 * Database Virtualization Engine
 * %%
 * Copyright (C) 2011 - 2014 Tesora Inc.
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License, version 3,
 * as published by the Free Software Foundation.
 * 
 * This program 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 Affero General Public License for more details.
 * 
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 * #L%
 */

import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

import com.tesora.dve.charset.*;
import com.tesora.dve.db.DBNative;
import com.tesora.dve.variables.VariableService;
import com.tesora.dve.sql.infoschema.InformationSchemaService;
import org.antlr.runtime.Lexer;
import org.antlr.runtime.Token;
import org.antlr.runtime.tree.CommonTree;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;

import com.tesora.dve.common.PEStringUtils;
import com.tesora.dve.common.catalog.CatalogEntity;
import com.tesora.dve.common.catalog.ConstraintType;
import com.tesora.dve.common.catalog.Container;
import com.tesora.dve.common.catalog.DynamicPolicy;
import com.tesora.dve.common.catalog.ExternalService;
import com.tesora.dve.common.catalog.FKMode;
import com.tesora.dve.common.catalog.IndexType;
import com.tesora.dve.common.catalog.Key;
import com.tesora.dve.common.catalog.KeyColumn;
import com.tesora.dve.common.catalog.MultitenantMode;
import com.tesora.dve.common.catalog.PersistentGroup;
import com.tesora.dve.common.catalog.PersistentTemplate;
import com.tesora.dve.common.catalog.Provider;
import com.tesora.dve.common.catalog.TableState;
import com.tesora.dve.common.catalog.TemplateMode;
import com.tesora.dve.common.catalog.User;
import com.tesora.dve.common.catalog.UserDatabase;
import com.tesora.dve.common.catalog.UserTable;
import com.tesora.dve.db.DBResultConsumer;
import com.tesora.dve.db.ValueConverter;
import com.tesora.dve.distribution.DistributionRange;
import com.tesora.dve.errmap.AvailableErrors;
import com.tesora.dve.errmap.ErrorInfo;
import com.tesora.dve.exceptions.PEException;
import com.tesora.dve.lockmanager.LockManager;
import com.tesora.dve.queryplan.ExecutionState;
import com.tesora.dve.queryplan.QueryStepGeneralOperation.AdhocOperation;
import com.tesora.dve.resultset.ColumnInfo;
import com.tesora.dve.resultset.ProjectionInfo;
import com.tesora.dve.server.connectionmanager.UserXid;
import com.tesora.dve.singleton.Singletons;
import com.tesora.dve.siteprovider.SiteProviderPlugin;
import com.tesora.dve.siteprovider.SiteProviderPlugin.SiteProviderFactory;
import com.tesora.dve.sql.ParserException;
import com.tesora.dve.sql.ParserException.Pass;
import com.tesora.dve.sql.SchemaException;
import com.tesora.dve.sql.expression.Scope;
import com.tesora.dve.sql.expression.ScopeParsePhase;
import com.tesora.dve.sql.expression.ScopeStack;
import com.tesora.dve.sql.expression.SetQuantifier;
import com.tesora.dve.sql.expression.TableKey;
import com.tesora.dve.sql.infoschema.InformationSchemaTable;
import com.tesora.dve.sql.infoschema.ShowOptions;
import com.tesora.dve.sql.infoschema.ShowSchemaBehavior;
import com.tesora.dve.sql.infoschema.direct.DirectShowStatusInformation;
import com.tesora.dve.sql.infoschema.direct.DirectShowVariablesTable;
import com.tesora.dve.sql.node.Edge;
import com.tesora.dve.sql.node.EdgeName;
import com.tesora.dve.sql.node.MigrationException;
import com.tesora.dve.sql.node.expression.ActualLiteralExpression;
import com.tesora.dve.sql.node.expression.Alias;
import com.tesora.dve.sql.node.expression.CaseExpression;
import com.tesora.dve.sql.node.expression.CastFunctionCall;
import com.tesora.dve.sql.node.expression.CharFunctionCall;
import com.tesora.dve.sql.node.expression.ColumnInstance;
import com.tesora.dve.sql.node.expression.ConvertFunctionCall;
import com.tesora.dve.sql.node.expression.Default;
import com.tesora.dve.sql.node.expression.DelegatingLiteralExpression;
import com.tesora.dve.sql.node.expression.ExpressionNode;
import com.tesora.dve.sql.node.expression.ExpressionSet;
import com.tesora.dve.sql.node.expression.FunctionCall;
import com.tesora.dve.sql.node.expression.GroupConcatCall;
import com.tesora.dve.sql.node.expression.IdentifierLiteralExpression;
import com.tesora.dve.sql.node.expression.IndexHint;
import com.tesora.dve.sql.node.expression.IndexHint.HintTarget;
import com.tesora.dve.sql.node.expression.IndexHint.HintType;
import com.tesora.dve.sql.node.expression.IntervalExpression;
import com.tesora.dve.sql.node.expression.LiteralExpression;
import com.tesora.dve.sql.node.expression.NameAlias;
import com.tesora.dve.sql.node.expression.NameInstance;
import com.tesora.dve.sql.node.expression.Parameter;
import com.tesora.dve.sql.node.expression.RandFunctionCall;
import com.tesora.dve.sql.node.expression.StringLiteralAlias;
import com.tesora.dve.sql.node.expression.Subquery;
import com.tesora.dve.sql.node.expression.TableInstance;
import com.tesora.dve.sql.node.expression.TableJoin;
import com.tesora.dve.sql.node.expression.TriggerTableInstance;
import com.tesora.dve.sql.node.expression.TriggerTableInstance.EarlyTriggerTableCollector;
import com.tesora.dve.sql.node.expression.ValueSource;
import com.tesora.dve.sql.node.expression.VariableInstance;
import com.tesora.dve.sql.node.expression.WhenClause;
import com.tesora.dve.sql.node.expression.Wildcard;
import com.tesora.dve.sql.node.structural.FromTableReference;
import com.tesora.dve.sql.node.structural.JoinClauseType;
import com.tesora.dve.sql.node.structural.JoinClauseType.ClauseType;
import com.tesora.dve.sql.node.structural.JoinSpecification;
import com.tesora.dve.sql.node.structural.JoinedTable;
import com.tesora.dve.sql.node.structural.LimitSpecification;
import com.tesora.dve.sql.node.structural.SortingSpecification;
import com.tesora.dve.sql.node.test.EngineConstant;
import com.tesora.dve.sql.parser.ParserOptions.Option;
import com.tesora.dve.sql.schema.Capability;
import com.tesora.dve.sql.schema.Comment;
import com.tesora.dve.sql.schema.ComplexPETable;
import com.tesora.dve.sql.schema.ContainerDistributionVector;
import com.tesora.dve.sql.schema.ContainerPolicyContext;
import com.tesora.dve.sql.schema.Database;
import com.tesora.dve.sql.schema.DistributionVector;
import com.tesora.dve.sql.schema.ExplainOptions;
import com.tesora.dve.sql.schema.ExplainOptions.ExplainOption;
import com.tesora.dve.sql.schema.FloatSizeTypeAttribute;
import com.tesora.dve.sql.schema.ForeignKeyAction;
import com.tesora.dve.sql.schema.FunctionName;
import com.tesora.dve.sql.schema.GrantScope;
import com.tesora.dve.sql.schema.LoadDataInfileColOption;
import com.tesora.dve.sql.schema.LoadDataInfileLineOption;
import com.tesora.dve.sql.schema.LoadDataInfileModifier;
import com.tesora.dve.sql.schema.LockInfo;
import com.tesora.dve.sql.schema.Name;
import com.tesora.dve.sql.schema.PEAbstractTable;
import com.tesora.dve.sql.schema.PEAbstractTable.TableCacheKey;
import com.tesora.dve.sql.schema.PEColumn;
import com.tesora.dve.sql.schema.PEContainer;
import com.tesora.dve.sql.schema.PEDatabase;
import com.tesora.dve.sql.schema.PEExternalService;
import com.tesora.dve.sql.schema.PEForeignKey;
import com.tesora.dve.sql.schema.PEForeignKeyColumn;
import com.tesora.dve.sql.schema.PEForwardForeignKeyColumn;
import com.tesora.dve.sql.schema.PEForwardKeyColumn;
import com.tesora.dve.sql.schema.PEKey;
import com.tesora.dve.sql.schema.PEKeyColumn;
import com.tesora.dve.sql.schema.PEKeyColumnBase;
import com.tesora.dve.sql.schema.PEPersistentGroup;
import com.tesora.dve.sql.schema.PEPolicy;
import com.tesora.dve.sql.schema.PEPolicyClassConfig;
import com.tesora.dve.sql.schema.PEProvider;
import com.tesora.dve.sql.schema.PERawPlan;
import com.tesora.dve.sql.schema.PESiteInstance;
import com.tesora.dve.sql.schema.PEStorageSite;
import com.tesora.dve.sql.schema.PETable;
import com.tesora.dve.sql.schema.PETemplate;
import com.tesora.dve.sql.schema.PETrigger;
import com.tesora.dve.sql.schema.PEUser;
import com.tesora.dve.sql.schema.PEView;
import com.tesora.dve.sql.schema.Persistable;
import com.tesora.dve.sql.schema.PolicyClass;
import com.tesora.dve.sql.schema.QualifiedName;
import com.tesora.dve.sql.schema.RangeDistribution;
import com.tesora.dve.sql.schema.SQLMode;
import com.tesora.dve.sql.schema.SchemaContext;
import com.tesora.dve.sql.schema.SizeTypeAttribute;
import com.tesora.dve.sql.schema.SubqueryTable;
import com.tesora.dve.sql.schema.Table;
import com.tesora.dve.sql.schema.TableComponent;
import com.tesora.dve.sql.schema.TableResolver;
import com.tesora.dve.sql.schema.TriggerEvent;
import com.tesora.dve.sql.schema.TriggerTime;
import com.tesora.dve.sql.schema.UnqualifiedName;
import com.tesora.dve.sql.schema.UnresolvedDistributionVector;
import com.tesora.dve.sql.schema.UserScope;
import com.tesora.dve.sql.schema.ValueManager;
import com.tesora.dve.sql.schema.VariableScope;
import com.tesora.dve.sql.schema.VariableScopeKind;
import com.tesora.dve.sql.schema.cache.IAutoIncrementLiteralExpression;
import com.tesora.dve.sql.schema.cache.IDelegatingLiteralExpression;
import com.tesora.dve.sql.schema.cache.IParameter;
import com.tesora.dve.sql.schema.modifiers.AutoincTableModifier;
import com.tesora.dve.sql.schema.modifiers.CharsetCollationModifierBuilder;
import com.tesora.dve.sql.schema.modifiers.ChecksumModifier;
import com.tesora.dve.sql.schema.modifiers.ColumnKeyModifier;
import com.tesora.dve.sql.schema.modifiers.ColumnModifier;
import com.tesora.dve.sql.schema.modifiers.ColumnModifierKind;
import com.tesora.dve.sql.schema.modifiers.CommentTableModifier;
import com.tesora.dve.sql.schema.modifiers.DefaultValueModifier;
import com.tesora.dve.sql.schema.modifiers.EngineTableModifier;
import com.tesora.dve.sql.schema.modifiers.InsertModifier;
import com.tesora.dve.sql.schema.modifiers.MaxRowsModifier;
import com.tesora.dve.sql.schema.modifiers.RowFormatTableModifier;
import com.tesora.dve.sql.schema.modifiers.StringTypeModifier;
import com.tesora.dve.sql.schema.modifiers.TableModifier;
import com.tesora.dve.sql.schema.modifiers.TypeModifier;
import com.tesora.dve.sql.schema.modifiers.TypeModifierKind;
import com.tesora.dve.sql.schema.modifiers.UnknownTableModifier;
import com.tesora.dve.sql.schema.mt.PETenant;
import com.tesora.dve.sql.schema.mt.TenantColumn;
import com.tesora.dve.sql.schema.types.BasicType;
import com.tesora.dve.sql.schema.types.DBEnumType;
import com.tesora.dve.sql.schema.types.TempColumnType;
import com.tesora.dve.sql.schema.types.Type;
import com.tesora.dve.sql.statement.EmptyStatement;
import com.tesora.dve.sql.statement.Statement;
import com.tesora.dve.sql.statement.StatementTraits;
import com.tesora.dve.sql.statement.StatementType;
import com.tesora.dve.sql.statement.ddl.AddGlobalVariableStatement;
import com.tesora.dve.sql.statement.ddl.AddStorageSiteStatement;
import com.tesora.dve.sql.statement.ddl.AlterDatabaseStatement;
import com.tesora.dve.sql.statement.ddl.AlterDatabaseTemplateStatement;
import com.tesora.dve.sql.statement.ddl.GrantStatement;
import com.tesora.dve.sql.statement.ddl.PEAlterExternalServiceStatement;
import com.tesora.dve.sql.statement.ddl.PEAlterGroupProviderStatement;
import com.tesora.dve.sql.statement.ddl.PEAlterPersistentSite;
import com.tesora.dve.sql.statement.ddl.PEAlterPolicyStatement;
import com.tesora.dve.sql.statement.ddl.PEAlterRawPlanStatement;
import com.tesora.dve.sql.statement.ddl.PEAlterSiteInstanceStatement;
import com.tesora.dve.sql.statement.ddl.PEAlterStatement;
import com.tesora.dve.sql.statement.ddl.PEAlterTableStatement;
import com.tesora.dve.sql.statement.ddl.PEAlterTemplateStatement;
import com.tesora.dve.sql.statement.ddl.PECreateDatabaseStatement;
import com.tesora.dve.sql.statement.ddl.PECreateExternalServiceStatement;
import com.tesora.dve.sql.statement.ddl.PECreateGroupProviderStatement;
import com.tesora.dve.sql.statement.ddl.PECreateRawPlanStatement;
import com.tesora.dve.sql.statement.ddl.PECreateSiteInstanceStatement;
import com.tesora.dve.sql.statement.ddl.PECreateStatement;
import com.tesora.dve.sql.statement.ddl.PECreateStorageSiteStatement;
import com.tesora.dve.sql.statement.ddl.PECreateTableAsSelectStatement;
import com.tesora.dve.sql.statement.ddl.PECreateTableStatement;
import com.tesora.dve.sql.statement.ddl.PECreateTriggerStatement;
import com.tesora.dve.sql.statement.ddl.PECreateUserStatement;
import com.tesora.dve.sql.statement.ddl.PECreateViewStatement;
import com.tesora.dve.sql.statement.ddl.PEDropContainerStatement;
import com.tesora.dve.sql.statement.ddl.PEDropExternalServiceStatement;
import com.tesora.dve.sql.statement.ddl.PEDropGroupProviderStatement;
import com.tesora.dve.sql.statement.ddl.PEDropRangeStatement;
import com.tesora.dve.sql.statement.ddl.PEDropRawPlanStatement;
import com.tesora.dve.sql.statement.ddl.PEDropStatement;
import com.tesora.dve.sql.statement.ddl.PEDropStorageGroupStatement;
import com.tesora.dve.sql.statement.ddl.PEDropStorageSiteStatement;
import com.tesora.dve.sql.statement.ddl.PEDropTableStatement;
import com.tesora.dve.sql.statement.ddl.PEDropTriggerStatement;
import com.tesora.dve.sql.statement.ddl.PEDropUserStatement;
import com.tesora.dve.sql.statement.ddl.PEDropViewStatement;
import com.tesora.dve.sql.statement.ddl.PEGroupProviderDDLStatement;
import com.tesora.dve.sql.statement.ddl.RenameTableStatement;
import com.tesora.dve.sql.statement.ddl.SchemaQueryStatement;
import com.tesora.dve.sql.statement.ddl.SetPasswordStatement;
import com.tesora.dve.sql.statement.ddl.ShowPlanCacheStatement;
import com.tesora.dve.sql.statement.ddl.alter.AddColumnAction;
import com.tesora.dve.sql.statement.ddl.alter.AddIndexAction;
import com.tesora.dve.sql.statement.ddl.alter.AlterColumnAction;
import com.tesora.dve.sql.statement.ddl.alter.AlterTableAction;
import com.tesora.dve.sql.statement.ddl.alter.ChangeColumnAction;
import com.tesora.dve.sql.statement.ddl.alter.ChangeKeysStatusAction;
import com.tesora.dve.sql.statement.ddl.alter.ChangeTableDistributionAction;
import com.tesora.dve.sql.statement.ddl.alter.ChangeTableModifierAction;
import com.tesora.dve.sql.statement.ddl.alter.ConvertToAction;
import com.tesora.dve.sql.statement.ddl.alter.DropColumnAction;
import com.tesora.dve.sql.statement.ddl.alter.DropIndexAction;
import com.tesora.dve.sql.statement.ddl.alter.RenameTableAction;
import com.tesora.dve.sql.statement.dml.AliasInformation;
import com.tesora.dve.sql.statement.dml.DMLStatement;
import com.tesora.dve.sql.statement.dml.DeleteStatement;
import com.tesora.dve.sql.statement.dml.InsertIntoSelectStatement;
import com.tesora.dve.sql.statement.dml.InsertIntoValuesStatement;
import com.tesora.dve.sql.statement.dml.MysqlSelectOption;
import com.tesora.dve.sql.statement.dml.ProjectingStatement;
import com.tesora.dve.sql.statement.dml.ReplaceIntoSelectStatement;
import com.tesora.dve.sql.statement.dml.ReplaceIntoValuesStatement;
import com.tesora.dve.sql.statement.dml.SelectStatement;
import com.tesora.dve.sql.statement.dml.TruncateStatement;
import com.tesora.dve.sql.statement.dml.UnionStatement;
import com.tesora.dve.sql.statement.dml.UpdateStatement;
import com.tesora.dve.sql.statement.dml.compound.CaseStatement;
import com.tesora.dve.sql.statement.dml.compound.CompoundStatementList;
import com.tesora.dve.sql.statement.dml.compound.StatementWhenClause;
import com.tesora.dve.sql.statement.session.AnalyzeKeysStatement;
import com.tesora.dve.sql.statement.session.AnalyzeTablesStatement;
import com.tesora.dve.sql.statement.session.DeallocatePStmtStatement;
import com.tesora.dve.sql.statement.session.ExecutePStmtStatement;
import com.tesora.dve.sql.statement.session.ExternalServiceControlStatement;
import com.tesora.dve.sql.statement.session.FlushPrivilegesStatement;
import com.tesora.dve.sql.statement.session.KillStatement;
import com.tesora.dve.sql.statement.session.LoadDataInfileStatement;
import com.tesora.dve.sql.statement.session.LockStatement;
import com.tesora.dve.sql.statement.session.LockType;
import com.tesora.dve.sql.statement.session.PreparePStmtStatement;
import com.tesora.dve.sql.statement.session.RollbackTransactionStatement;
import com.tesora.dve.sql.statement.session.SavepointStatement;
import com.tesora.dve.sql.statement.session.SessionSetVariableStatement;
import com.tesora.dve.sql.statement.session.SessionStatement;
import com.tesora.dve.sql.statement.session.SetExpression;
import com.tesora.dve.sql.statement.session.SetTransactionIsolationExpression;
import com.tesora.dve.sql.statement.session.SetVariableExpression;
import com.tesora.dve.sql.statement.session.ShowErrorsWarningsStatement;
import com.tesora.dve.sql.statement.session.ShowPassthroughStatement;
import com.tesora.dve.sql.statement.session.ShowPassthroughStatement.PassThroughCommandType;
import com.tesora.dve.sql.statement.session.ShowProcesslistStatement;
import com.tesora.dve.sql.statement.session.ShowSitesStatusStatement;
import com.tesora.dve.sql.statement.session.StartTransactionStatement;
import com.tesora.dve.sql.statement.session.TableMaintenanceStatement;
import com.tesora.dve.sql.statement.session.TableMaintenanceStatement.MaintenanceCommandType;
import com.tesora.dve.sql.statement.session.TableMaintenanceStatement.MaintenanceOptionType;
import com.tesora.dve.sql.statement.session.TransactionStatement;
import com.tesora.dve.sql.statement.session.UseContainerStatement;
import com.tesora.dve.sql.statement.session.XABeginTransactionStatement;
import com.tesora.dve.sql.statement.session.XACommitTransactionStatement;
import com.tesora.dve.sql.statement.session.XAEndTransactionStatement;
import com.tesora.dve.sql.statement.session.XAPrepareTransactionStatement;
import com.tesora.dve.sql.statement.session.XARecoverTransactionStatement;
import com.tesora.dve.sql.statement.session.XARollbackTransactionStatement;
import com.tesora.dve.sql.template.TemplateManager;
import com.tesora.dve.sql.transform.CopyVisitor;
import com.tesora.dve.sql.transform.behaviors.BehaviorConfiguration;
import com.tesora.dve.sql.transform.execution.ExecutionSequence;
import com.tesora.dve.sql.transform.execution.PassThroughCommand.Command;
import com.tesora.dve.sql.transform.execution.TransientSessionExecutionStep;
import com.tesora.dve.sql.transform.strategy.NaturalJoinRewriter;
import com.tesora.dve.sql.util.Functional;
import com.tesora.dve.sql.util.ListOfPairs;
import com.tesora.dve.sql.util.ListSet;
import com.tesora.dve.sql.util.Pair;
import com.tesora.dve.sql.util.UnaryFunction;
import com.tesora.dve.sql.util.UnaryProcedure;
import com.tesora.dve.variable.VariableConstants;
import com.tesora.dve.variables.KnownVariables;
import com.tesora.dve.variables.VariableHandler;
import com.tesora.dve.worker.SiteManagerCommand;
import com.tesora.dve.worker.WorkerGroup;

// holds the bridge methods from antlr tree nodes to our nodes
public class TranslatorUtils extends Utils implements ValueSource {

    static Logger logger = Logger.getLogger(TranslatorUtils.class);

    static public final String PERSISTENT_GROUP_TAG = "PERSISTENT GROUP";
    static public final String PERSISTENT_SITE_TAG = "PERSISTENT SITE";
    static public final String PERSISTENT_INSTANCE = "PERSISTENT INSTANCE";
    static public final String DYNAMIC_SITE_PROVIDER = "DYNAMIC SITE PROVIDER";
    static public final String DYNAMIC_SITE_PROVIDER_SITES = DYNAMIC_SITE_PROVIDER + " SITES";
    static public final String DYNAMIC_SITE_POLICY_TAG = "DYNAMIC SITE POLICY";

    private final String MISSING_UNQUALIFIED_IDENTIFIER_ERROR_MSG = "Command was not parsed correctly "
            + "(can occur if certain keywords are used as identifiers)";

    private static final String UPDATABLE_VIEWS = "No support for updatable views";

    SchemaContext pc;
    private ParserOptions opts;
    private boolean resolveColumnsAsIdentifiers;
    private ScopeStack scope;
    private ListOfPairs<DelegatingLiteralExpression, Object> literals;
    private List<Parameter> parameters;
    // as soon as we know the lock type, we register it
    private LockInfo lockInfo;
    // if there is a regular insert, this is the first part of it - everything except the values
    private InsertIntoValuesStatement insertSkeleton;
    // if this is a continuation, the initial offset
    private int initialOffset;
    // this is set if enough is bumped
    private int finalOffset;
    // for big inserts
    private final long continuationThreshold;

    private NativeCharSetCatalog supportedCharSets = null;
    private NativeCollationCatalog supportedCollations = null;

    private static final TableResolver basicResolver = new TableResolver().withMTChecks()
            .withQualifiedMissingDBFormat("No such database '%s'.");

    public static PEAbstractTable<?> getTable(final SchemaContext sc, final Name fullName,
            final LockInfo lockInfo) {
        TableInstance ti = basicResolver.lookupTable(sc, fullName, lockInfo);
        if (ti == null)
            return null;
        return ti.getAbstractTable();
    }

    public static UnqualifiedName getDatabaseNameForObject(final SchemaContext sc, final Name objectName) {
        return getDatabaseForObject(sc, objectName).getName().getUnqualified();
    }

    public static Database<?> getDatabaseForObject(final SchemaContext sc, final Name objectName)
            throws SchemaException {
        if (objectName.isQualified()) {
            final UnqualifiedName parentSchemaName = ((QualifiedName) objectName).getNamespace();
            final Database<?> parentSchema = sc.findDatabase(parentSchemaName);
            if (parentSchema != null) {
                return parentSchema;
            }

            throw new SchemaException(new ErrorInfo(AvailableErrors.UNKNOWN_DATABASE, parentSchemaName.get()));
        }

        final Database<?> currentSchema = sc.getCurrentDatabase();
        if (currentSchema != null) {
            return currentSchema;
        }

        throw new SchemaException(new ErrorInfo(AvailableErrors.NO_DATABASE_SELECTED));
    }

    public TranslatorUtils(ParserOptions opts, SchemaContext pc, InputState state) {
        super(opts);
        this.pc = pc;
        if (this.pc == null)
            throw new SchemaException(Pass.FIRST, "TranslatorUtils no longer accepts null SchemaContext");
        this.opts = opts;
        scope = new ScopeStack();
        this.lockInfo = null;
        resolveColumnsAsIdentifiers = false;
        literals = new ListOfPairs<DelegatingLiteralExpression, Object>();
        parameters = new ArrayList<Parameter>();
        this.initialOffset = state.getCurrentPosition();
        this.insertSkeleton = state.getInsertSkeleton();
        finalOffset = -1;
        continuationThreshold = state.getThreshold();

        if ((this.insertSkeleton != null) && (state instanceof ContinuationInputState)) {
            scope.pushScope();
            scope.insertTable(this.insertSkeleton.getTableInstance());
        }
    }

    public void setContext(SchemaContext sc) {
        pc = sc;
    }

    public String getInputSQL() {
        return pc.getOrigStmt();
    }

    public void pushScope() {
        scope.pushScope();
    }

    public void popScope() {
        scope.popScope();
    }

    public void pushUnresolvingScope() {
        scope.pushUnresolvingScope();
    }

    public void resolveProjection() {
        scope.resolveProjection(pc);
    }

    public void storeProjection(List<ExpressionNode> l) {
        scope.storeProjection(l);
    }

    public void setGroupByNamespace() {
        scope.setPhase(ScopeParsePhase.GROUPBY);
    }

    public void setHavingNamespace() {
        scope.setPhase(ScopeParsePhase.HAVING);
    }

    public void setTrailingNamespace() {
        scope.setPhase(ScopeParsePhase.TRAILING);
    }

    public int getLastPoppedScope() {
        return scope.getLastPoppedScopeID();
    }

    public void repushScope(int id) {
        scope.pushScopeID(id);
    }

    public void pushUnresolving() {
        opts = opts.unsetResolve();
    }

    public void popUnresolving() {
        opts = opts.setResolve();
    }

    // call this during parsing to indicate that we are handling ddl
    public void ddl() {
        if (pc.getCapability() != Capability.PARSING_ONLY)
            pc.forceMutableSource();
        if (opts == null || opts.getLockOverride() == null)
            lockInfo = new LockInfo(com.tesora.dve.lockmanager.LockType.EXCLUSIVE, "ddl");
        else if (opts != null && opts.getLockOverride() != null)
            lockInfo = opts.getLockOverride();
    }

    // call this during parsing to indicate that we are not handling ddl - used during temp table operations
    public void notddl() {
        if (pc.getCapability() != Capability.PARSING_ONLY)
            pc.forceImmutableSource();
        if (opts == null || opts.getLockOverride() == null)
            lockInfo = new LockInfo(com.tesora.dve.lockmanager.LockType.EXCLUSIVE, "ddl");
        else if (opts != null && opts.getLockOverride() != null)
            lockInfo = opts.getLockOverride();
    }

    protected void forceUncacheable(ValueManager.CacheStatus status) {
        if (pc.getCapability() != Capability.PARSING_ONLY)
            pc.getValueManager().markUncacheable(status);
    }

    public void assignPositions() {
        if (pc.getCapability() == Capability.PARSING_ONLY)
            return;
        if (!parameters.isEmpty()) {
            TreeMap<SourceLocation, Parameter> map = new TreeMap<SourceLocation, Parameter>();
            for (Parameter p : parameters)
                map.put(p.getSourceLocation(), p);
            if (map.size() != parameters.size())
                throw new SchemaException(Pass.SECOND, "Lost parameters while doing position assignment");
            int i = 0;
            for (Parameter p : map.values()) {
                p.setPosition(i);
                pc.getValueManager().registerParameter(pc, p);
                i++;
            }
        }
        if (literals.size() > KnownVariables.CACHED_PLAN_LITERALS_MAX
                .getValue(pc.getConnection().getVariableSource()).intValue()) {
            forceUncacheable(ValueManager.CacheStatus.NOCACHE_TOO_MANY_LITERALS);
        } else {
            TreeMap<SourceLocation, DelegatingLiteralExpression> map = new TreeMap<SourceLocation, DelegatingLiteralExpression>();
            for (Pair<DelegatingLiteralExpression, Object> p : literals) {
                map.put(p.getFirst().getSourceLocation(), p.getFirst());
            }
            if (map.size() != literals.size())
                throw new SchemaException(Pass.SECOND, "Lost literals while doing position assignment");
            int i = 0;
            for (DelegatingLiteralExpression dle : map.values()) {
                pc.getValueManager().addLiteralValue(pc, i, literals.get(dle.getPosition()).getSecond(), dle);
                dle.setPosition(i, true);
                i++;
            }
        }
    }

    @SuppressWarnings("unchecked")
    public ProjectingStatement buildSelectStatement(Map<String, Object> components, List<ExpressionNode> projection,
            List<Object> selectOptions, Object tree) {
        return buildSelectStatement((List<FromTableReference>) components.get(EdgeName.TABLES), projection,
                (ExpressionNode) components.get(EdgeName.WHERECLAUSE),
                (List<SortingSpecification>) components.get(EdgeName.ORDERBY),
                (LimitSpecification) components.get(EdgeName.LIMIT), selectOptions,
                (List<SortingSpecification>) components.get(EdgeName.GROUPBY),
                (ExpressionNode) components.get(EdgeName.HAVING),
                (Boolean) components.get(Statement.SELECT_LOCK_ATTRIBUTE), tree);
    }

    public ProjectingStatement buildSelectStatement(List<FromTableReference> tableRefs,
            List<ExpressionNode> projection, ExpressionNode whereClause, List<SortingSpecification> orderbys,
            LimitSpecification limit, List<Object> selectOptions, List<SortingSpecification> groupbys,
            ExpressionNode havingExpr, Boolean locking, Object tree) {
        // sort the set quantifier and select options
        SetQuantifier sq = null;
        List<MysqlSelectOption> options = new ArrayList<MysqlSelectOption>();
        for (Object opt : selectOptions) {
            if (opt instanceof SetQuantifier)
                sq = (SetQuantifier) opt;
            else
                options.add((MysqlSelectOption) opt);
        }
        SelectStatement ss = new SelectStatement(tableRefs, projection, whereClause, orderbys, limit, sq, options,
                groupbys, havingExpr, locking, new AliasInformation(scope), SourceLocation.make(tree));

        /*
         * The NATURAL [LEFT] JOIN are rewritten to an INNER JOIN or a LEFT JOIN
         * with a USING clause.
         * The rewrite must take place after ColumnInstance resolution (to
         * prevent premature failure on ambiguous column names), but before
         * USING-to-ON clause conversion and wildcard expansion which affect the
         * projection coalescing and ordering.
         */
        if (tableRefs != null) {
            for (final FromTableReference ftr : tableRefs) {
                final TableInstance base = ftr.getBaseTable();
                final ListSet<JoinedTable> naturalJoins = NaturalJoinRewriter
                        .collectNaturalJoins(ftr.getTableJoins());
                for (final JoinedTable join : naturalJoins) {
                    NaturalJoinRewriter.rewriteToInnerJoin(this.pc, base, join);
                }

                convertUsingColSpecToOnSpec(base, naturalJoins);
            }
        }

        ss.getDerivedInfo().takeScope(scope);
        Scope ps = scope.getParentScope();
        if (ps != null)
            ps.getNestedQueries().add(ss);

        shouldSetTimestampVariable(ss, null, null);

        return ss;
    }

    public Statement buildUpdateStatement(List<FromTableReference> tableRefs, List<ExpressionNode> updateExprs,
            ExpressionNode whereClause, List<SortingSpecification> orderbys, LimitSpecification limit,
            boolean ignore, Object tree) {

        PEAbstractTable<?> tab = tableRefs.get(0).getBaseTable().getAbstractTable();
        if (tab != null && tab.isView())
            throw new SchemaException(Pass.SECOND, UPDATABLE_VIEWS);

        UpdateStatement us = new UpdateStatement(tableRefs, updateExprs, whereClause, orderbys, limit,
                new AliasInformation(scope), SourceLocation.make(tree));
        us.setIgnore(ignore);
        us.getDerivedInfo().takeScope(scope);

        if (tab != null)
            shouldSetTimestampVariable(us, tab.asTable(), updateExprs);

        return us;
    }

    public Statement buildDeleteStatement(List<FromTableReference> tableRefs, List<Name> explicitRefs,
            ExpressionNode whereClause, List<SortingSpecification> orderbys, LimitSpecification limit,
            Object sloc) {
        List<TableInstance> explicitDeletes = null;
        if (explicitRefs == null || explicitRefs.isEmpty()) {
            // ignore
        } else {
            explicitDeletes = new ArrayList<TableInstance>();
            for (Name r : explicitRefs) {
                if (pc.getCapability() == Capability.PARSING_ONLY) {
                    explicitDeletes.add(new TableInstance(null, r, null, false));
                } else {
                    Name actual = r;
                    // if it has a trailing * strip that off
                    List<UnqualifiedName> parts = r.getParts();
                    if (parts.size() > 1 && parts.get(parts.size() - 1).isAsterisk()) {
                        ArrayList<UnqualifiedName> nparts = new ArrayList<UnqualifiedName>(parts);
                        nparts.remove(nparts.size() - 1);
                        if (nparts.size() == 1)
                            actual = nparts.get(0);
                        else
                            actual = new QualifiedName(nparts);
                    }
                    explicitDeletes.add(scope.lookupTableInstance(pc, actual, true));
                }
            }
        }
        DeleteStatement ds = new DeleteStatement(explicitDeletes, tableRefs, whereClause, orderbys, limit, false,
                new AliasInformation(scope), SourceLocation.make(sloc));
        ds.getDerivedInfo().takeScope(scope);

        shouldSetTimestampVariable(ds, null, null);

        return ds;
    }

    @SuppressWarnings("unchecked")
    public void pushSkeletonInsert(ExpressionNode tab, boolean replace, Object tree) {
        TableInstance intoTable = (TableInstance) tab;
        InsertIntoValuesStatement is = null;
        SourceLocation sloc = SourceLocation.make(tree);
        if (replace)
            is = new ReplaceIntoValuesStatement(intoTable, Collections.EMPTY_LIST, Collections.EMPTY_LIST,
                    new AliasInformation(scope), sloc);
        else
            is = new InsertIntoValuesStatement(intoTable, Collections.EMPTY_LIST, Collections.EMPTY_LIST, null,
                    new AliasInformation(scope), sloc);
        is.getDerivedInfo().addLocalTable(intoTable.getTableKey());
        is.getDerivedInfo().takeScope(scope);
        insertSkeleton = is;
    }

    public ExpressionNode pushInsertSkeletonField(ExpressionNode field) {
        insertSkeleton.getColumnSpecificationEdge().add(field);
        return field;
    }

    public InsertIntoValuesStatement buildInsertStatement(List<List<ExpressionNode>> values, boolean copy,
            TransactionStatement.Kind txnal, InsertModifier im, boolean ignore) {
        return buildInsertStatement(null, values, copy, txnal, im, ignore);
    }

    private InsertIntoValuesStatement buildInsertStatement(List<ExpressionNode> columnSpec,
            List<List<ExpressionNode>> values, boolean copy, TransactionStatement.Kind txnal, InsertModifier im,
            boolean ignore) {
        InsertIntoValuesStatement is = null;
        if (copy)
            is = CopyVisitor.copy(insertSkeleton);
        else
            is = insertSkeleton;
        if (columnSpec != null)
            is.setColumnSpecification(columnSpec);
        is.setValues(values);
        is.setTxnFlag(txnal);
        is.setIgnore(ignore);
        if (im != null) {
            if (opts.isResolve() && im.equals(InsertModifier.DELAYED)) {
                // replication slave always strips the DELAYED keyword
                if (!pc.getConnection().originatedFromReplicationSlave()) {
                    EngineTableModifier tem = is.getTableInstance().getAbstractTable().asTable().getEngine();
                    if (tem.isMyISAM())
                        is.setModifier(im);
                    else
                        throw new SchemaException(Pass.SECOND, "Insert modifier '" + im.getSQL()
                                + "' option not supported on table of type " + tem.getEngine().getSQL());
                }
            } else {
                is.setModifier(im);
            }
        }

        shouldSetTimestampVariable(is, is.getTableInstance().getAbstractTable(), is.getColumnSpecification(),
                values);

        return is;
    }

    public Statement buildInsertStatement(List<List<ExpressionNode>> values, List<ExpressionNode> onDupKey,
            InsertModifier im, boolean ignore) {
        InsertIntoValuesStatement is = buildInsertStatement(values, false,
                (initialOffset > 0 ? TransactionStatement.Kind.COMMIT : null), im, ignore);
        is.setOnDuplicateKey(onDupKey);
        // clear the skeleton
        insertSkeleton = null;

        return is;
    }

    /**
     * @param select
     * @param ignore
     * @param onDupKey
     * @return
     */
    @SuppressWarnings("unchecked")
    public Statement buildInsertIntoSelectStatement(ExpressionNode select, boolean ignore,
            List<ExpressionNode> onDupKey) {
        Subquery sq = (Subquery) select;
        ProjectingStatement selectStatement = sq.getStatement();
        // copy the table, etc. out of the skeleton, then clear it
        InsertIntoSelectStatement iiss = null;
        if (insertSkeleton.isReplace()) {
            iiss = new ReplaceIntoSelectStatement(insertSkeleton.getTableInstance(),
                    insertSkeleton.getColumnSpecification(), selectStatement, sq.isGrouped(),
                    new AliasInformation(scope), insertSkeleton.getSourceLocation());
        } else {
            iiss = new InsertIntoSelectStatement(insertSkeleton.getTableInstance(),
                    insertSkeleton.getColumnSpecification(), selectStatement, sq.isGrouped(), onDupKey,
                    new AliasInformation(scope), insertSkeleton.getSourceLocation());
            iiss.setIgnore(ignore);
        }
        // the select is nested
        iiss.getDerivedInfo().addNestedStatements(Collections.singleton(selectStatement));
        iiss.getDerivedInfo().addLocalTable(insertSkeleton.getTableInstance().getTableKey());
        iiss.getDerivedInfo().takeScope(scope);

        shouldSetTimestampVariable(iiss, insertSkeleton.getTableInstance().getAbstractTable(),
                insertSkeleton.getColumnSpecification(), Collections.EMPTY_LIST);

        insertSkeleton = null;
        return iiss;
    }

    public void enough(PE_MySQL parser, List<List<ExpressionNode>> insertValues) {
        Lexer lexer = (Lexer) parser.getTokenStream().getTokenSource();
        if (lexer.getCharIndex() - initialOffset > continuationThreshold) {
            finalOffset = lexer.getCharIndex();
            throw new EnoughException(buildInsertStatement(insertValues, true,
                    (initialOffset == 0 ? TransactionStatement.Kind.START : null), null, false));
        }
    }

    public void reportContinuationOnDupKey() {
        throw new ParserException(Pass.FIRST, "Statement is too large. Consider increasing the '"
                + VariableConstants.LARGE_INSERT_THRESHOLD_NAME + "' value.", null);
    }

    public InputState getInputState(InputState in) {
        if (finalOffset == -1)
            return null;
        if (finalOffset > -1) {
            in.setCurrentPosition(finalOffset);
        }
        if (insertSkeleton != null && in.getInsertSkeleton() == null)
            in.setInsertSkeleton(insertSkeleton);
        return in;
    }

    /**
     * @param updateExprs
     * @param ignore
     * @param onDupKey
     * @param im
     * @return
     */
    public Statement buildInsertIntoSetStatement(List<ExpressionNode> updateExprs, boolean ignore,
            List<ExpressionNode> onDupKey, InsertModifier im) {
        // use insert skeleton as is, since we cannot match the extended insert in the parser
        // but we have to unpack the update exprs to build the column spec and values
        List<ExpressionNode> columnSpec = new ArrayList<ExpressionNode>();
        List<ExpressionNode> values = new ArrayList<ExpressionNode>();
        for (ExpressionNode en : updateExprs) {
            FunctionCall fc = (FunctionCall) en;
            columnSpec.add(fc.getParametersEdge().get(0));
            values.add(fc.getParametersEdge().get(1));
        }
        return buildInsertStatement(columnSpec, Collections.singletonList(values), false, null, im, ignore);
    }

    private boolean shouldSetTimestampVariable(DMLStatement dmls, PETable tab, List<ExpressionNode> updateExprs) {

        // separate the update set column=value expression into columns and
        // values lists
        List<ExpressionNode> fields = new ArrayList<ExpressionNode>();
        List<List<ExpressionNode>> allValues = new ArrayList<List<ExpressionNode>>();
        List<ExpressionNode> rowValues = new ArrayList<ExpressionNode>();
        if (updateExprs != null) {
            for (ExpressionNode node : updateExprs) {
                // update set column= are function calls
                FunctionCall f = (FunctionCall) node;
                Pair<ColumnInstance, ExpressionNode> params = decomposeUpdateAssignment(f);
                fields.add(params.getFirst());
                rowValues.add(params.getSecond());
            }
        }
        allValues.add(rowValues);

        return shouldSetTimestampVariable(dmls, tab, fields, allValues);
    }

    private boolean shouldSetTimestampVariable(DMLStatement dmls, PEAbstractTable<?> tab,
            List<ExpressionNode> fields, List<List<ExpressionNode>> values) {

        // check if the now or current_timestamp function is used
        boolean ret = TimestampVariableUtils.isNowFunctionCallSpecified(dmls.getDerivedInfo().getFunctions());

        // can't do anything if petable is null
        if (tab == null) {
            // could have been set by above call so save in our statement
            // save the set timestamp variable flag in our statement
            dmls.getDerivedInfo().setSetTimestampVariable(ret);

            return ret;
        }

        if (!ret) {

            // determine which columns in the table are specified and which are
            // not
            List<PEColumn> specifiedColumns = new ArrayList<PEColumn>();
            List<PEColumn> unspecifiedColumns = new ArrayList<PEColumn>(tab.getColumns(pc));
            if (fields == null || fields.isEmpty()) {
                specifiedColumns.addAll(unspecifiedColumns);
                unspecifiedColumns.clear();
            } else {
                List<PEColumn> temp = new ArrayList<PEColumn>();
                for (ExpressionNode field : fields) {
                    temp.add(((ColumnInstance) field).getPEColumn());
                }
                unspecifiedColumns.removeAll(temp);
                for (ExpressionNode col : fields) {
                    PEColumn c = ((ColumnInstance) col).getPEColumn();
                    specifiedColumns.add(c);
                }
            }

            for (PEColumn c : unspecifiedColumns) {
                ret = TimestampVariableUtils.setTimestampVariableForUnspecifiedColumn(pc, dmls, c);
                if (ret) {
                    // set the variable so break loop
                    break;
                }
            }

            // we need values to check against
            if (!ret && (values != null)) {
                // haven't set the timestamp variable yet
                // so check if the specified columns need to set it
                for (int i = 0; i < specifiedColumns.size(); ++i) {
                    PEColumn c = specifiedColumns.get(i);
                    for (List<ExpressionNode> v : values) {
                        // make sure the ___mtid is skipped
                        // by checking the specified column count doesn't
                        // exceed the values
                        if (i >= v.size()) {
                            continue;
                        }
                        ExpressionNode e = v.get(i);
                        ExpressionNode r = e;
                        if (e instanceof Default) {
                            ExpressionNode defaultValue = c.getDefaultValue();
                            if (defaultValue == null) {
                                if (c.isNullable()) {
                                    r = LiteralExpression.makeNullLiteral();
                                }
                            } else {
                                r = (ExpressionNode) ((LiteralExpression) defaultValue).copy(null);
                            }
                        }
                        ret = TimestampVariableUtils.setTimestampVariableForSpecifiedValue(c, r);
                        if (ret) {
                            break;
                        }
                    }
                    if (ret) {
                        break;
                    }
                }
            }
        }

        // save the set timestamp variable flag in our statement
        dmls.getDerivedInfo().setSetTimestampVariable(ret);

        return ret;
    }

    private Pair<ColumnInstance, ExpressionNode> decomposeUpdateAssignment(FunctionCall fc) {
        ColumnInstance ci = null;
        ExpressionNode le = null;
        // for update assignment, we know exactly what we have - so just access
        // by index
        if (fc.getFunctionName().isEquals()) {
            List<ExpressionNode> params = fc.getParameters();
            if (params.get(0) instanceof ColumnInstance)
                ci = (ColumnInstance) params.get(0);
            else
                return null;
            le = params.get(1);
            return new Pair<ColumnInstance, ExpressionNode>(ci, le);
        }
        return null;
    }

    public Statement buildCreateTable(Name tableName, Name oldTableName, Boolean ine) {
        if ((tableName == null) || (oldTableName == null)) {
            throw new SchemaException(Pass.FIRST, MISSING_UNQUALIFIED_IDENTIFIER_ERROR_MSG);
        }

        PECreateTableStatement pecs = null;
        UnqualifiedName tabName = tableName.getUnqualified();
        TableInstance ti = pc.getCurrentPEDatabase().getSchema().buildInstance(pc, tabName, lockInfo);
        if (Boolean.TRUE.equals(ine) && opts.isResolve()) {
            // see if the table already exists
            // UnqualifiedName dbName = null;
            if (ti != null) {
                if (ti.getAbstractTable().isView())
                    throw new SchemaException(Pass.FIRST, tabName + " is a view, cannot create like");
                pecs = new PECreateTableStatement(ti.getAbstractTable().asTable(), ine, true);
                return pecs;
            }
        }

        PEAbstractTable<?> tab = null;
        if (ti != null)
            tab = ti.getAbstractTable();

        // see if the table already exists
        if (tab == null) {
            Name dbName = null;
            // now need to determine if source or old table exists too
            if (oldTableName.isQualified()) {
                dbName = ((QualifiedName) oldTableName).getNamespace();
            }
            if (dbName == null) {
                dbName = (pc.getCurrentDatabase() == null) ? null : pc.getCurrentDatabase().getName();
            }
            PEDatabase db = pc.findPEDatabase(dbName);
            if (db == null)
                throw new SchemaException(Pass.FIRST, "No such database: '" + dbName + "'");

            TableInstance otab = db.getSchema().buildInstance(pc, oldTableName.getUnqualified(), lockInfo);
            if (otab == null) {
                throw new SchemaException(Pass.FIRST, "No source table: '" + oldTableName + "'");
            }
            if (otab.getAbstractTable().isView())
                throw new SchemaException(Pass.FIRST, "Source table is a view");
            PETable oldTab = otab.getAbstractTable().asTable();

            String cts = oldTab.getDeclaration();
            if (cts == null)
                throw new SchemaException(Pass.FIRST, "Unable to obtain source create table statement");
            tab = oldTab.recreate(pc, cts, lockInfo);
            tab.setName(tabName);
            tab.asTable().removeForeignKeys(pc);
            tab.asTable().setDeclaration(pc, tab.asTable());
        }
        pecs = new PECreateTableStatement(tab.asTable(), ine, false);

        return pecs;
    }

    public Statement buildCreateTable(Name tableName, List<TableComponent<?>> fieldsAndKeys,
            UnresolvedDistributionVector indv, Name groupName, List<TableModifier> modifiers, Boolean ine,
            Pair<UnqualifiedName, List<UnqualifiedName>> discriminator, ProjectingStatement ctas,
            boolean temporary) {
        if (tableName == null) {
            throw new SchemaException(Pass.FIRST, MISSING_UNQUALIFIED_IDENTIFIER_ERROR_MSG);
        }

        PECreateTableStatement pecs = null;
        if (Boolean.TRUE.equals(ine) && opts.isResolve()) {
            // see if the table already exists
            // UnqualifiedName dbName = null;
            TableInstance ti = new TableResolver().withMTChecks().lookupTable(pc, tableName, lockInfo);
            if (ti != null) {
                if (ti.getAbstractTable().isView())
                    throw new SchemaException(Pass.FIRST, tableName + " is a view (cannot recreate)");
                pecs = new PECreateTableStatement(ti.getAbstractTable().asTable(), ine, true);
            }
        }
        PEPersistentGroup pesg = null;
        if (groupName != null) {
            pesg = pc.findStorageGroup(groupName);
            if (pesg == null)
                throw new SchemaException(Pass.SECOND, "No such persistent group: " + groupName.getSQL());
        }

        List<TableComponent<?>> actualFieldsAndKeys = null;

        ListOfPairs<PEColumn, Integer> ctaProjectionOffsets = null;

        if (ctas != null) {
            ctaProjectionOffsets = new ListOfPairs<PEColumn, Integer>();
            // the columns are ordered like so:
            // first all columns that are only explicitly declared
            // then all columns that are only implicitly or both declared
            // we will determine projection column types at runtime, and use a placeholder in the meantime

            ListSet<PEColumn> inproj = new ListSet<PEColumn>();

            ProjectionInfo pmd = ctas.getProjectionMetadata(pc);
            for (int i = 1; i <= pmd.getWidth(); i++) {
                int offset = i - 1;
                ColumnInfo ci = pmd.getColumnInfo(i);
                UnqualifiedName cname = new UnqualifiedName(ci.getAlias());
                PEColumn matching = lookupInProcessColumn(cname, true);
                if (matching != null) {
                    ctaProjectionOffsets.add(matching, offset);
                    inproj.add(matching);
                    continue;
                }
                // declare the column with a placeholder type
                PEColumn viaCTA = PECreateTableAsSelectStatement.createColumnFromExpression(pc, ci,
                        ctas.getProjections().get(0).get(offset));
                scope.registerColumn(viaCTA);
                ctaProjectionOffsets.add(viaCTA, offset);
                inproj.add(viaCTA);
            }

            actualFieldsAndKeys = new ArrayList<TableComponent<?>>();
            // first do the decl only bits
            for (Iterator<TableComponent<?>> iter = fieldsAndKeys.iterator(); iter.hasNext();) {
                TableComponent<?> tc = iter.next();
                if (tc instanceof PEKey)
                    continue;
                PEColumn pec = (PEColumn) tc;
                if (!inproj.contains(pec))
                    actualFieldsAndKeys.add(pec);
                iter.remove();
            }
            // now all the projection fields
            actualFieldsAndKeys.addAll(inproj);
            // now we have all columns declared, resolve anything that was forward
            for (Iterator<TableComponent<?>> iter = fieldsAndKeys.iterator(); iter.hasNext();) {
                TableComponent<?> tc = iter.next();
                if (tc instanceof PEKey) {
                    PEKey pek = (PEKey) tc;
                    actualFieldsAndKeys.add(pek.resolve(pc, scope));
                } else {
                    actualFieldsAndKeys.add(tc);
                }
            }
        } else {
            actualFieldsAndKeys = fieldsAndKeys;
        }

        // resolve the distribution vector
        DistributionVector dv = null;
        if (indv != null)
            dv = indv.resolve(pc, this);

        if (pecs == null) {
            // unpack the dbstuff
            PETable newTab = null;
            // we want to inject when both the dist vect and the discriminator are null; if either is non-null the dist info
            // was specified
            if (dv == null && discriminator == null) {
                newTab = buildTable(tableName, actualFieldsAndKeys, null, pesg, modifiers,
                        ctaProjectionOffsets != null, temporary);
                if ((pc.getCapability() == Capability.PARSING_ONLY)
                        || (opts != null && opts.isOmitMetadataInjection())) {
                    dv = new DistributionVector(pc, null, DistributionVector.Model.RANDOM);
                    newTab.setDistributionVector(pc, dv);
                } else
                    try {
                        if (!TemplateManager.inject(pc, newTab.getPEDatabase(pc), newTab)) {
                            if (newTab.getPEDatabase(pc).hasStrictTemplateMode()) {
                                throw new SchemaException(Pass.SECOND,
                                        "No matching template found for table " + newTab.getName().getSQL());
                            }
                            dv = new DistributionVector(pc, null, DistributionVector.Model.RANDOM);
                            newTab.setDistributionVector(pc, dv);
                        }
                    } catch (Exception e) {
                        throw new SchemaException(Pass.SECOND, e);
                    }
            } else if (dv != null) {
                newTab = buildTable(tableName, actualFieldsAndKeys, dv, pesg, modifiers,
                        ctaProjectionOffsets != null, temporary);
            } else if (discriminator != null) {
                // newTab will be the base table on the container - so change the dist vect on it to be the container
                PEContainer container = pc.findContainer(discriminator.getFirst());
                if (container == null)
                    throw new SchemaException(Pass.SECOND,
                            "No such container: " + discriminator.getFirst().getSQL());

                if (container.hasBaseTable()) {
                    throw new SchemaException(Pass.SECOND,
                            "Cannot set table '" + tableName + "' as a base table because container '"
                                    + discriminator.getFirst().getSQL() + "' already has a base table.");
                }

                dv = new ContainerDistributionVector(pc, container, false);
                newTab = buildTable(tableName, fieldsAndKeys, dv, pesg, modifiers, ctaProjectionOffsets != null,
                        temporary);
                // newTab is actually the container base table - so go resolve the columns now and so mark them
                List<UnqualifiedName> colNames = discriminator.getSecond();
                for (int i = 0; i < colNames.size(); i++) {
                    UnqualifiedName un = colNames.get(i);
                    PEColumn pec = newTab.lookup(pc, un);
                    if (pec == null)
                        throw new SchemaException(Pass.SECOND,
                                "No such column: " + un.getSQL() + " - cannot build discriminator");
                    pec.setContainerDistributionValuePosition(i + 1);
                }
                container.setBaseTable(pc, newTab);
            } else {
                throw new SchemaException(Pass.SECOND,
                        "Unable to determine declared distribution for table " + tableName.getSQL());
            }
            if (ctas == null)
                pecs = new PECreateTableStatement(newTab, ine, false);
            else
                pecs = new PECreateTableAsSelectStatement(newTab, ine, false, ctas, ctaProjectionOffsets);
        }
        Statement out = pecs;
        if (pc.getCapability() != Capability.PARSING_ONLY && !pc.getOptions().isTSchema()) {
            // containers don't generally have a separate policy at creation time - check for it on
            // the dist vect
            if (pecs.getCreated().get().getDistributionVector(pc).isContainer()) {
                out = ContainerPolicyContext.modifyCreateTable(pc, pecs);
            } else {
                out = pc.getPolicyContext().modifyCreateTable(pecs);
            }
        }
        return out;
    }

    public Statement buildDropTableStatement(List<Name> givenNames, Boolean ifExists, boolean tempTabs) {
        if (givenNames == null) {
            throw new SchemaException(Pass.FIRST, MISSING_UNQUALIFIED_IDENTIFIER_ERROR_MSG);
        }

        TableResolver resolver = new TableResolver().withMTChecks()
                .withDatabaseFunction(new UnaryProcedure<Database<?>>() {

                    @Override
                    public void execute(Database<?> object) {
                        if (!(object instanceof PEDatabase))
                            throw new SchemaException(Pass.SECOND,
                                    "Invalid database for drop table: '" + object.getName() + "'");
                    }

                });

        List<TableKey> tblKeys = new ArrayList<TableKey>();
        List<Name> unknownTables = new ArrayList<Name>();
        for (Name givenName : givenNames) {
            TableInstance ti = resolver.lookupTable(pc, givenName, lockInfo);
            if (ti == null) {
                unknownTables.add(givenName);
            } else {
                if (tempTabs && !ti.getTableKey().isUserlandTemporaryTable())
                    throw new SchemaException(
                            new ErrorInfo(AvailableErrors.UNKNOWN_TABLE, givenName.getUnquotedName().get()));
                tblKeys.add(ti.getTableKey());
            }
        }

        // we can throw if there is no valid existing table
        if ((tblKeys.size() == 0) && unknownTables.size() > 0) {
            if (!Boolean.TRUE.equals(ifExists))
                throw new SchemaException(Pass.SECOND,
                        "No such table(s) '" + StringUtils.join(unknownTables, ",") + "'");
        }

        PEDropTableStatement stmt = new PEDropTableStatement(pc, tblKeys, unknownTables, ifExists, tempTabs);
        return pc.getPolicyContext().modifyDropTable(stmt);
    }

    private Database<?> findDatabase(Name givenName) {
        Database<?> ondb = pc.getCurrentDatabase(false);
        if (ondb == null || givenName.isQualified()) {
            if (!givenName.isQualified())
                pc.getCurrentDatabase(true);
            QualifiedName qname = (QualifiedName) givenName;
            UnqualifiedName dbName = qname.getNamespace();
            if (ondb == null || !ondb.getName().equals(dbName)) {
                ondb = pc.findDatabase(dbName);
                if (ondb == null)
                    return ondb;
            }
        }
        return ondb;
    }

    public Statement buildDropDatabaseStatement(Name dbName, Boolean ifExists, boolean dropmt, String tag) {
        if (dbName == null) {
            throw new SchemaException(Pass.FIRST, MISSING_UNQUALIFIED_IDENTIFIER_ERROR_MSG);
        }

        return pc.getPolicyContext().buildDropDatabaseStatement(dbName, ifExists, dropmt, tag);
    }

    public Statement buildShowCreateDatabaseQuery(String onInfoSchemaTable, Name objectName, Boolean ifNotExists) {
        ShowSchemaBehavior ist = Singletons.require(InformationSchemaService.class)
                .lookupShowTable(new UnqualifiedName(onInfoSchemaTable));
        if (ist == null)
            throw new MigrationException("Need to add info schema table for " + onInfoSchemaTable);
        ShowOptions opts = new ShowOptions();
        if (Boolean.TRUE.equals(ifNotExists))
            opts = opts.withIfNotExists();
        return ist.buildUniqueStatement(pc, objectName, opts);
    }

    public Statement buildUseDatabaseStatement(Name firstName) {
        if (firstName == null)
            throw new SchemaException(Pass.FIRST, MISSING_UNQUALIFIED_IDENTIFIER_ERROR_MSG);
        if (pc.getCapability() == Capability.PARSING_ONLY)
            return new SessionStatement("use " + firstName.getSQL()) {
                @Override
                public boolean isPassthrough() {
                    return false;
                }
            };
        return pc.getPolicyContext().buildUseDatabaseStatement(firstName);
    }

    /**
     * @param projName
     * @return
     */
    public Statement buildUseProjectStatement(Name projName) {
        // no longer supported (just for the persistent version)
        throw new ParserException(Pass.FIRST, "No support for use project for persistent schema");
    }

    /**
     * @param jdbcURL
     * @return
     */
    public Statement buildCreateCatalog(Token jdbcURL) {
        throw new SchemaException(Pass.SECOND, "No support for create catalog.");
    }

    @SuppressWarnings("unchecked")
    public Statement buildCreateDatabase(Name dbName, Boolean ifNotExists, String tag, MultitenantMode mm,
            List<Pair<?, ?>> consolidatedDefs) {
        if (dbName == null) {
            throw new SchemaException(Pass.FIRST, MISSING_UNQUALIFIED_IDENTIFIER_ERROR_MSG);
        }

        Name charSetValue = null;
        Name collationValue = null;
        Name pgName = null;
        FKMode fkMode = null;
        Pair<Name, TemplateMode> templateDecl = null;
        for (Pair<?, ?> p : consolidatedDefs) {
            if (p.getFirst() instanceof String) {
                String key = (String) p.getFirst();
                if ("fkmode".equals(key)) {
                    fkMode = (FKMode) p.getSecond();
                    continue;
                }
                Name value = (Name) p.getSecond();
                if ("charset".equals(key)) {
                    charSetValue = value;
                } else if ("collate".equals(key)) {
                    collationValue = value;
                } else if ("pers_group".equals(key)) {
                    pgName = value;
                } else {
                    throw new SchemaException(Pass.FIRST, "Unknown create db attribute: " + key);
                }
            } else if (p.getSecond() instanceof TemplateMode) {
                templateDecl = (Pair<Name, TemplateMode>) p;
            }
        }

        if (templateDecl == null) {
            templateDecl = new Pair<Name, TemplateMode>(null, TemplateMode.getCurrentDefault(pc.getConnection()));
        }

        final Pair<String, String> charSetCollationPair = getCharsetCollationPair(charSetValue, collationValue);

        if (pc.getCapability() == Capability.PARSING_ONLY) {
            PEDatabase pdb = new PEDatabase(null, dbName.getUnquotedName(), null, templateDecl, mm, fkMode,
                    charSetCollationPair.getFirst(), charSetCollationPair.getSecond());
            PECreateStatement<PEDatabase, UserDatabase> cdb = new PECreateDatabaseStatement(pdb, false, ifNotExists,
                    tag, false);
            return cdb;
        }

        return pc.getPolicyContext().buildCreateDatabase(dbName, pgName, templateDecl, ifNotExists, tag, mm, fkMode,
                charSetCollationPair.getFirst(), charSetCollationPair.getSecond());
    }

    public Statement buildAlterDatabaseStatement(final Name dbName, final Name charSetName,
            final Name collationName) {
        if ((charSetName == null) && (collationName == null)) {
            throw new SchemaException(Pass.SECOND, "Can't alter database '" + dbName.getSQL() + "'; syntax error");
        }

        final Pair<String, String> charSetCollationPair = getCharsetCollationPair(charSetName, collationName);
        final PEDatabase db = getAlterDatabase(dbName);
        return new AlterDatabaseStatement(db, charSetCollationPair.getFirst(), charSetCollationPair.getSecond());
    }

    public Statement buildAlterDatabaseStatement(final Name dbName,
            final Pair<Name, TemplateMode> templateDeclaration) {
        final PEDatabase db = getAlterDatabase(dbName);
        final Pair<Name, TemplateMode> checkedTemplateDecl = TemplateManager.findTemplateForDatabase(pc, dbName,
                templateDeclaration.getFirst(), templateDeclaration.getSecond());

        return new AlterDatabaseTemplateStatement(db, checkedTemplateDecl);
    }

    public Pair<Name, TemplateMode> buildTemplateDeclaration(final Name templateName, TemplateMode mode) {
        final String templateNameIdentifier = templateName.get();
        if (TemplateMode.hasModeForName(templateNameIdentifier)) {
            if (templateNameIdentifier.equals(TemplateMode.OPTIONAL.toString())) {
                if (mode == null) {
                    return new Pair<Name, TemplateMode>(null, TemplateMode.OPTIONAL);
                }
            }

            throw new SchemaException(Pass.SECOND,
                    "Redundant mode specification '" + mode.toString() + "'; syntax error");
        }

        if ((mode != null) && !mode.requiresTemplate()) {
            throw new SchemaException(Pass.SECOND, "Redundant template specification '" + templateName.getSQL()
                    + "' for " + VariableConstants.TEMPLATE_MODE_NAME + " '" + mode.toString() + "'; syntax error");
        }

        if (mode == null) {
            mode = TemplateMode.getCurrentDefault(pc.getConnection());
        }

        return new Pair<Name, TemplateMode>(templateName, mode);
    }

    private PEDatabase getAlterDatabase(final Name dbName) {
        final Database<?> db = (dbName != null) ? pc.findDatabase(dbName) : pc.getCurrentDatabase();
        if (db == null) {
            throw new SchemaException(Pass.SECOND,
                    "Can't alter database '" + dbName.getSQL() + "'; database doesn't exist");
        } else if (!(db instanceof PEDatabase)) {
            throw new SchemaException(Pass.SECOND,
                    "Can't alter database '" + dbName.getSQL() + "'; target is not alterable");
        }

        return (PEDatabase) db;
    }

    private Pair<String, String> getCharsetCollationPair(final Name charSetName, final Name collationName) {
        return CharsetCollationModifierBuilder.buildCharsetCollationNamePair(charSetName, collationName,
                getNativeCharSetCatalog(), getNativeCollationCatalog());
    }

    public Statement buildCreatePersistentInstance(Name persistentInstanceName,
            List<Pair<Name, LiteralExpression>> options) {
        pc.getPolicyContext().checkRootPermission("create a persistent instance");

        if (persistentInstanceName == null)
            throw new SchemaException(Pass.SECOND, "Persistent instance name must be specified.");

        PESiteInstance pesi = pc.findSiteInstance(persistentInstanceName);
        if (pesi != null)
            throw new SchemaException(Pass.SECOND,
                    "Persistent instance " + persistentInstanceName + " already exists.");
        return PECreateSiteInstanceStatement.build(pc, persistentInstanceName, options);
    }

    public Statement buildAlterPersistentInstanceStatement(Name persistentInstanceName,
            List<Pair<Name, LiteralExpression>> options) {
        pc.getPolicyContext().checkRootPermission("change a persistent instance");
        PESiteInstance pesi = pc.findSiteInstance(persistentInstanceName);
        if (pesi == null)
            throw new SchemaException(Pass.SECOND,
                    "No such persistent instance: " + persistentInstanceName.getUnqualified().get());
        return new PEAlterSiteInstanceStatement(pesi, options);
    }

    @SuppressWarnings({ "unchecked", "rawtypes" })
    public Statement buildDropPersistentInstanceStatement(Name persistentInstanceName) {
        pc.getPolicyContext().checkRootPermission("drop a persistent instance");
        PESiteInstance pesi = pc.findSiteInstance(persistentInstanceName);
        if (pesi == null)
            throw new SchemaException(Pass.SECOND,
                    "No such persistent instance: " + persistentInstanceName.getUnqualified().get());
        return new PEDropStatement(PESiteInstance.class, null, true, pesi, PERSISTENT_INSTANCE);
    }

    public Statement buildCreatePersistentSite(Name persistentSiteName, List<Pair<Name, LiteralExpression>> opts) {
        pc.getPolicyContext().checkRootPermission("create a persistent site");
        PEStorageSite pess = pc.findStorageSite(persistentSiteName);
        if (pess != null)
            throw new SchemaException(Pass.SECOND,
                    "Persistent site " + persistentSiteName.getSQL() + " already exists.");
        return PECreateStorageSiteStatement.build(pc, persistentSiteName, opts);
    }

    public Statement buildCreatePersistentSite(Name persistentSiteName, String url, String user, String password) {
        pc.getPolicyContext().checkRootPermission("create a persistent site");
        PEStorageSite pess = pc.findStorageSite(persistentSiteName);
        if (pess != null)
            throw new SchemaException(Pass.SECOND,
                    "Persistent site " + persistentSiteName.getSQL() + " already exists.");
        // the jdbcURL probably still has the quotes on - have to strip those
        String stripped = Singletons.require(DBNative.class).getValueConverter().convertStringLiteral(url);
        // convert it into the other format
        List<Pair<Name, LiteralExpression>> opts = new ArrayList<Pair<Name, LiteralExpression>>();
        opts.add(buildConfigOption(new UnqualifiedName("URL"), LiteralExpression.makeStringLiteral(stripped)));
        opts.add(buildConfigOption(new UnqualifiedName("USER"), LiteralExpression.makeStringLiteral(user)));
        opts.add(buildConfigOption(new UnqualifiedName("PASSWORD"), LiteralExpression.makeStringLiteral(password)));
        return PECreateStorageSiteStatement.build(pc, persistentSiteName, opts);
    }

    @SuppressWarnings("unchecked")
    public Statement buildCreatePersistentSite(Name persistentSiteName, String haType, Name masterName,
            List<Name> siteInstances) {
        pc.getPolicyContext().checkRootPermission("create a persistent site");
        PEStorageSite pess = pc.findStorageSite(persistentSiteName);
        if (pess != null)
            throw new SchemaException(Pass.SECOND,
                    "Persistent site " + persistentSiteName.getSQL() + " already exists.");

        PESiteInstance masterPersistentInstance = pc.findSiteInstance(masterName);
        if (masterPersistentInstance == null) {
            throw new SchemaException(Pass.SECOND, "No such persistent instance: " + masterName.getQuoted());
        }

        List<PESiteInstance> peSiteInstances = null;
        if (siteInstances == null)
            peSiteInstances = Collections.EMPTY_LIST;
        else
            peSiteInstances = Functional.apply(siteInstances, new UnaryFunction<PESiteInstance, Name>() {

                @Override
                public PESiteInstance evaluate(Name object) {
                    PESiteInstance pesi = pc.findSiteInstance(object);
                    if (pesi == null)
                        throw new SchemaException(Pass.SECOND,
                                "No such persistent instance: " + object.getQuoted());
                    return pesi;
                }

            });

        return new PECreateStorageSiteStatement(
                new PEStorageSite(pc, persistentSiteName, haType, masterPersistentInstance, peSiteInstances));
    }

    public Statement buildAlterPersistentSite(Name persistentSiteName, String haType) {
        pc.getPolicyContext().checkRootPermission("alter a persistent site");
        PEStorageSite pess = pc.findStorageSite(persistentSiteName);
        if (pess == null)
            throw new SchemaException(Pass.SECOND, "No such persistent site: " + persistentSiteName.getSQL());
        return new PEAlterPersistentSite(pess, haType);
    }

    public Statement buildAlterPersistentSite(Name persistentSiteName, Name masterName) {
        pc.getPolicyContext().checkRootPermission("alter a persistent site");
        PEStorageSite pess = pc.findStorageSite(persistentSiteName);
        if (pess == null)
            throw new SchemaException(Pass.SECOND, "No such persistent site: " + persistentSiteName.getSQL());
        PESiteInstance masterSiteInstance = pc.findSiteInstance(masterName);
        if (masterSiteInstance == null)
            throw new SchemaException(Pass.SECOND,
                    "No such persistent instance: " + masterName.getUnqualified().get());
        return new PEAlterPersistentSite(pess, masterSiteInstance);
    }

    public Statement buildAlterPersistentSite(Name persistentSiteName, Boolean addOperation,
            List<Name> siteInstanceNames) {
        pc.getPolicyContext().checkRootPermission("alter a persistent site");
        PEStorageSite pess = pc.findStorageSite(persistentSiteName);
        if (pess == null)
            throw new SchemaException(Pass.SECOND, "No such persistent site: " + persistentSiteName.getSQL());
        ArrayList<PESiteInstance> siteInstances = new ArrayList<PESiteInstance>();
        for (Name n : siteInstanceNames) {
            PESiteInstance pesi = pc.findSiteInstance(n);
            if (pesi == null)
                throw new SchemaException(Pass.SECOND, "No such persistent instance: " + n.getUnqualified().get());
            siteInstances.add(pesi);
        }
        return new PEAlterPersistentSite(pess, addOperation, siteInstances);
    }

    public Statement buildDropPersistentSiteStatement(Name persistentSiteName) {
        pc.getPolicyContext().checkRootPermission("drop a persistent site");
        PEStorageSite pess = pc.findStorageSite(persistentSiteName);
        if (pess == null)
            throw new SchemaException(Pass.SECOND, "No such persistent site: " + persistentSiteName.getSQL());
        PEDropStorageSiteStatement out = new PEDropStorageSiteStatement(pess);
        out.ensureUnreferenced(pc);
        return out;
    }

    @SuppressWarnings("unchecked")
    public Statement buildCreatePersistentGroup(Name groupName, List<Name> sites) {
        pc.getPolicyContext().checkRootPermission("create a persistent group");
        PEPersistentGroup pesg = pc.findStorageGroup(groupName);
        if (pesg != null)
            throw new SchemaException(Pass.SECOND, "Persistent group " + groupName.getSQL() + " already exists");
        List<PEStorageSite> pesites = null;
        if (sites == null)
            pesites = Collections.EMPTY_LIST;
        else
            pesites = Functional.apply(sites, new UnaryFunction<PEStorageSite, Name>() {

                @Override
                public PEStorageSite evaluate(Name object) {
                    PEStorageSite pess = pc.findStorageSite(object);
                    if (pess == null)
                        throw new SchemaException(Pass.SECOND, "No such persistent site: " + object.getQuoted());
                    return pess;
                }

            });
        return new PECreateStatement<PEPersistentGroup, PersistentGroup>(
                new PEPersistentGroup(pc, groupName, pesites), true, PERSISTENT_GROUP_TAG, false);
    }

    public PEColumn lookupInProcessColumn(Name n, boolean missingOk) {
        PEColumn c = scope.lookupInProcessColumn(n);
        if (c == null)
            c = scope.lookupInProcessColumn(n.getCapitalized());
        if (c == null && !missingOk)
            throw new SchemaException(Pass.SECOND, "No such column: " + n.getSQL());
        return c;
    }

    public UnresolvedDistributionVector buildDistributionVector(DistributionVector.Model model,
            List<Name> columnNames, Name rangeOrContainer) {
        return new UnresolvedDistributionVector(model, columnNames, rangeOrContainer);
    }

    public ColumnModifier buildColumnModifier(ColumnModifierKind tag) {
        return new ColumnModifier(tag);
    }

    public ColumnModifier buildDefaultValue(ExpressionNode v) {
        return new DefaultValueModifier(v);
    }

    public ColumnModifier buildEnumDefaultValue(Type typeDef, ExpressionNode value) {
        final DBEnumType enumValues = (DBEnumType) typeDef;
        final ActualLiteralExpression defaultValue = (ActualLiteralExpression) value;

        /*
         * Integral default values are treated as 1-based indices into the ENUM.
         */
        if (defaultValue.isIntegerLiteral()) {
            final int valuePositionIndex = ((Long) defaultValue.getValue()).intValue();
            try {
                final LiteralExpression valueAtIndex = enumValues.getValueAt(valuePositionIndex);
                return new DefaultValueModifier(valueAtIndex);
            } catch (final IndexOutOfBoundsException e) {
                throw new SchemaException(Pass.SECOND, "No value at position " + valuePositionIndex + " in the "
                        + enumValues.getEnumerationTypeName().toUpperCase(), e);
            }
        }
        return new DefaultValueModifier(value);
    }

    public TypeModifier buildCollationSpec(Name collated) {
        return new StringTypeModifier(TypeModifierKind.COLLATE, collated.getUnquotedName().get());
    }

    public TypeModifier buildCharsetSpec(Name spec) {
        return new StringTypeModifier(TypeModifierKind.CHARSET, spec.getUnquotedName().get());
    }

    public Set<TableModifier> buildCharsetCollationModifiers(final CharsetCollationModifierBuilder builder) {
        if (builder.hasValues()) {
            return builder.buildModifiers(getNativeCharSetCatalog(), getNativeCollationCatalog());
        }
        return Collections.EMPTY_SET;
    }

    public ColumnModifier buildOnUpdate() {
        return new ColumnModifier(ColumnModifierKind.ONUPDATE);
        //            buildIdentifierLiteral("CURRENT_TIMESTAMP"));
    }

    public Name buildKeywordName(String in) {
        return new UnqualifiedName(in);
    }

    public Name buildIdentifier(String in, Object tree) {
        Name out = new UnqualifiedName(in, SourceLocation.make(tree));
        return out;
    }

    public Name buildIdentifier(Token tok) {
        Name out = new UnqualifiedName(tok.getText(), SourceLocation.make(tok));
        return out;
    }

    public Object buildLiteralValue(String in) {
        return in;
    }

    public Integer buildIntegerLiteral(String in) {
        try {
            return Integer.parseInt(in);
        } catch (NumberFormatException nfe) {
            throw new ParserException(Pass.SECOND, "Bad integer value: '" + in + "': " + nfe.getMessage(), nfe);
        }
    }

    private LiteralExpression asLiteral(ExpressionNode e) {
        if (e instanceof LiteralExpression) {
            return (LiteralExpression) e;
        } else if (e == null) {
            return null;
        } else {
            throw new ParserException(Pass.SECOND, "Expecting literal, got: " + e);
        }
    }

    private Integer asIntegralLiteral(ExpressionNode e) {
        LiteralExpression integLit = asLiteral(e);
        Object lv = null;
        if (integLit instanceof DelegatingLiteralExpression) {
            DelegatingLiteralExpression dle = (DelegatingLiteralExpression) integLit;
            lv = literals.get(dle.getPosition()).getSecond();
        } else {
            lv = integLit.getValue(pc.getValues());
        }
        if (lv instanceof Long) {
            return ((Long) lv).intValue();
        }
        throw new ParserException(Pass.SECOND, "Expecting integral literal, got: " + lv);
    }

    public SizeTypeAttribute buildSizeTypeAttribute(ExpressionNode a, ExpressionNode b) {
        // i.e. (x, y)
        if (b == null)
            return new SizeTypeAttribute(asIntegralLiteral(a));
        Integer precision = asIntegralLiteral(a);
        Integer scale = asIntegralLiteral(b);
        return new FloatSizeTypeAttribute(precision, precision, scale);
    }

    @SuppressWarnings("unchecked")
    public BasicType buildType(List<Name> typeNames, SizeTypeAttribute sizing, List<TypeModifier> modifiers) {
        return BasicType.buildType(typeNames, Collections.singletonList(sizing),
                (modifiers == null ? Collections.EMPTY_LIST : modifiers), pc.getTypes());
    }

    public BasicType buildEnum(boolean isSet, List<LiteralExpression> values, List<TypeModifier> modifiers) {
        return DBEnumType.make(isSet, values, modifiers, pc.getTypes());
    }

    public TypeModifier buildTypeModifier(TypeModifierKind tmk) {
        return new TypeModifier(tmk);
    }

    public TypeModifier buildComparisonModifier(String className) {
        String stripped = Singletons.require(DBNative.class).getValueConverter().convertStringLiteral(className);
        return new StringTypeModifier(TypeModifierKind.COMPARISON, stripped);
    }

    public List<TableComponent<?>> buildFieldDefinition(Name fieldName, Type type, List<ColumnModifier> attrs,
            String commentText) throws SchemaException {
        Comment comment = null;
        if (commentText != null) {
            comment = buildTableFieldComment(fieldName, commentText);
        }
        List<ColumnKeyModifier> inlineKeys = new ArrayList<ColumnKeyModifier>();
        for (Iterator<ColumnModifier> iter = attrs.iterator(); iter.hasNext();) {
            ColumnModifier cm = iter.next();
            if (cm.getTag() == ColumnModifierKind.INLINE_KEY) {
                inlineKeys.add((ColumnKeyModifier) cm);
                iter.remove();
            }
        }
        List<TableComponent<?>> out = new ArrayList<TableComponent<?>>();
        PEColumn nc = null;
        if (pc.getCapability() == Capability.PARSING_ONLY || !(pc.getPolicyContext().allowTenantColumnDeclaration()
                && TenantColumn.TENANT_COLUMN.equals(fieldName.get())))
            nc = scope.registerColumn(PEColumn.buildColumn(pc, fieldName, type, attrs, comment, inlineKeys));
        else
            nc = scope.registerColumn(new TenantColumn(pc));
        out.add(nc);
        // collapse the case where we see UNIQUE, KEY
        if (inlineKeys.size() > 1) {
            int uniqued = -1;
            int keyed = -1;
            for (int i = 0; i < inlineKeys.size(); i++) {
                if (inlineKeys.get(i).getConstraint() == ConstraintType.UNIQUE && uniqued == -1)
                    uniqued = i;
                else if (inlineKeys.get(i).getConstraint() == null && keyed == -1)
                    keyed = i;
            }
            if (uniqued > -1 && keyed > uniqued)
                inlineKeys.remove(keyed);
        }
        for (ColumnKeyModifier ckm : inlineKeys) {
            // first build the key
            @SuppressWarnings("unchecked")
            PEKey pek = buildKey(null, null,
                    Collections.singletonList((PEKeyColumnBase) new PEKeyColumn(nc, null, -1L)),
                    Collections.EMPTY_LIST);
            if (ckm.getConstraint() != null)
                pek = withConstraint(ckm.getConstraint(), null, pek);
            out.add(pek);
        }
        return out;
    }

    public PEKey buildKey(IndexType type, Name name, List<PEKeyColumnBase> cols, List<Object> options)
            throws SchemaException {
        // unpack the options in case we have anything lurking
        IndexType postSpecifiedType = null;
        Comment anyComment = null;
        for (Object o : options) {
            if (o instanceof String) {
                anyComment = buildTableFieldComment(name, (String) o);
            } else if (o instanceof IndexType) {
                postSpecifiedType = (IndexType) o;
            } else {
                throw new SchemaException(Pass.SECOND, "Unknown key option: " + o);
            }
        }
        IndexType actualType = type;
        if (actualType == null)
            actualType = postSpecifiedType;
        if (actualType == null)
            actualType = IndexType.BTREE;
        return new PEKey(name, actualType, cols, anyComment);
    }

    public PEKey withConstraint(ConstraintType ct, Name symbolName, PEKey pek) {
        pek.setConstraint(ct);
        if (symbolName != null)
            pek.setSymbol(symbolName.getUnqualified());
        return pek;
    }

    public ColumnModifier buildInlineKeyModifier(ConstraintType ct) {
        return new ColumnKeyModifier(ct);
    }

    public PEKeyColumnBase buildPEKeyColumn(Name identifier, ExpressionNode length, ExpressionNode cardinality) {
        PEColumn c = lookupInProcessColumn(identifier, true);
        Integer keyLength = (length == null ? null : asIntegralLiteral(length));
        long keyCardinality = (cardinality == null ? -1L : asIntegralLiteral(cardinality));

        if (c == null)
            return new PEForwardKeyColumn(null, identifier.getUnqualified(), keyLength, keyCardinality);
        else
            return new PEKeyColumn(c, keyLength, keyCardinality);
    }

    @SuppressWarnings("cast")
    public PEKey buildForeignKey(Name name, List<PEKeyColumnBase> mycols, Name targetTableName,
            List<UnqualifiedName> targetColumns, ForeignKeyAction deleteAction, ForeignKeyAction updateAction) {
        // are unknown tables ok?
        boolean required = (pc.getCapability() != Capability.PARSING_ONLY && KnownVariables.FOREIGN_KEY_CHECKS
                .getSessionValue(pc.getConnection().getVariableSource()).booleanValue());

        // figure out whether the target table is known or not
        PETable targetTab = null;
        Database<?> db = null;
        UnqualifiedName candidateName = null;
        if (targetTableName.isQualified()) {
            QualifiedName qn = (QualifiedName) targetTableName;
            UnqualifiedName ofdb = qn.getNamespace();
            candidateName = qn.getUnqualified();
            db = pc.findPEDatabase(ofdb);
            if (db == null && required)
                throw new SchemaException(Pass.FIRST, "No such database: " + ofdb);
        } else {
            if (pc.getCapability() != Capability.PARSING_ONLY)
                db = pc.getCurrentDatabase();
            if (db == null && required)
                throw new SchemaException(Pass.FIRST, "No current database");
            candidateName = (UnqualifiedName) targetTableName;
        }
        if (db != null) {
            boolean mtchecks = (pc.getCapability() != Capability.PARSING_ONLY
                    && !pc.getOptions().isDisableMTLookupChecks());
            TableInstance ti = db.getSchema().buildInstance(pc, candidateName, lockInfo, mtchecks);
            if (ti == null && required && mtchecks)
                throw new SchemaException(Pass.FIRST, "No such table: " + targetTableName);
            if (ti != null)
                targetTab = ti.getAbstractTable().asTable();
        }
        Name fullyQualifiedTargetName = null;
        if (targetTab == null) {
            if (targetTableName.isQualified())
                fullyQualifiedTargetName = targetTableName;
            else if (db != null)
                fullyQualifiedTargetName = targetTableName.postfix(db.getName());
            else if (pc.getCapability() == Capability.PARSING_ONLY)
                fullyQualifiedTargetName = targetTableName;
            else
                throw new SchemaException(Pass.FIRST, "No current database");
        }
        // regardless of whether the target table is known, we need to convert the PEKeyColumns to PEForeignKeyColumns
        if (mycols.size() != targetColumns.size())
            throw new SchemaException(Pass.FIRST, "Invalid foreign key declaration: source table names "
                    + mycols.size() + " columns but references " + targetColumns.size());
        List<PEKeyColumnBase> fkcols = new ArrayList<PEKeyColumnBase>();
        for (int i = 0; i < mycols.size(); i++) {
            PEKeyColumnBase mine = mycols.get(i);
            UnqualifiedName targName = targetColumns.get(i);
            PEKeyColumnBase pefk = null;
            if (targetTab == null) {
                if (mine.isUnresolved())
                    pefk = new PEForwardForeignKeyColumn(null, mine.getName(), targName);
                else
                    pefk = new PEForeignKeyColumn(mine.getColumn(), targName);
            } else {
                PEColumn targCol = targetTab.lookup(pc, targName);
                if (targCol == null)
                    throw new SchemaException(Pass.SECOND, "No such column " + targName + " in " + targetTableName);
                if (mine.isUnresolved())
                    pefk = new PEForwardForeignKeyColumn(null, mine.getName(), targCol);
                else
                    pefk = new PEForeignKeyColumn(mine.getColumn(), targCol);
            }
            fkcols.add(pefk);
        }
        return new PEForeignKey(pc, name, targetTab, fullyQualifiedTargetName, fkcols,
                updateAction == null ? ForeignKeyAction.RESTRICT : updateAction,
                deleteAction == null ? ForeignKeyAction.RESTRICT : deleteAction);
    }

    public PETable buildTable(Name tableName, List<TableComponent<?>> fieldsAndKeys, DistributionVector dv,
            PEPersistentGroup sg, List<TableModifier> modifiers, boolean nascent, boolean temporary) {
        if (tableName == null) {
            throw new SchemaException(Pass.FIRST, MISSING_UNQUALIFIED_IDENTIFIER_ERROR_MSG);
        }

        Database<?> cdb = null;
        Name unqualifiedTableName = tableName;
        if (pc.getCapability() != Capability.PARSING_ONLY) {
            cdb = pc.getCurrentDatabase(false);
            if (cdb == null || tableName.isQualified()) {
                if (cdb == null && !tableName.isQualified())
                    throw new SchemaException(Pass.SECOND, "Current database not set");
                QualifiedName qname = (QualifiedName) tableName;
                UnqualifiedName leading = qname.getNamespace();
                if (cdb == null || !cdb.getName().equals(leading)) {
                    cdb = pc.findDatabase(leading);
                    if (cdb == null)
                        throw new SchemaException(Pass.SECOND, "No such database: '" + leading + "'");
                }
                unqualifiedTableName = tableName.getUnqualified();
            }
            if (!(cdb instanceof PEDatabase))
                throw new SchemaException(Pass.SECOND,
                        "Invalid database for table creation: '" + cdb.getName() + "'");
        }
        // the dv should have the container here, if applicable
        PETable newtab = null;
        if (nascent || temporary) {
            ComplexPETable ctab = new ComplexPETable(pc, unqualifiedTableName, fieldsAndKeys, dv, modifiers, sg,
                    (PEDatabase) cdb, TableState.SHARED);
            if (nascent)
                ctab.withCTA();
            if (temporary)
                ctab.withTemporaryTable(pc);
            newtab = ctab;
        } else
            newtab = new PETable(pc, unqualifiedTableName, fieldsAndKeys, dv, modifiers, sg, (PEDatabase) cdb,
                    TableState.SHARED);
        if (pc.getCapability() != Capability.PARSING_ONLY) {
            pc.getPolicyContext().modifyTablePart(newtab);
        }
        newtab.setDeclaration(pc, newtab);
        return newtab;
    }

    public ExpressionNode buildBooleanLiteral(Boolean v, Token orig) {
        if (opts.isActualLiterals()) {
            return new ActualLiteralExpression(v, orig.getType(), SourceLocation.make(orig), null);
        } else {
            DelegatingLiteralExpression litex = new DelegatingLiteralExpression(TokenTypes.BOOLEAN,
                    SourceLocation.make(orig), this, literals.size(), null);
            literals.add(litex, v);
            return litex;
        }
    }

    public ExpressionNode buildColumnReference(Name parsedName) {
        if (resolveColumnsAsIdentifiers)
            return buildIdentifierLiteral(parsedName);
        if (opts.isResolve())
            return scope.buildColumnInstance(pc, parsedName);
        return new ColumnInstance(parsedName, null, null);
    }

    protected ExpressionNode buildLiteral(Token o) {
        String t = o.getText();
        int tok = o.getType();
        if ((tok == TokenTypes.NULL) || (tok == TokenTypes.GLOBAL))
            return new ActualLiteralExpression(t, tok, SourceLocation.make(o), null);
        // if it's a string literal, strip off any charset hint for later
        UnqualifiedName charsetHint = null;
        if (tok == TokenTypes.Character_String_Literal && t.length() > 0) {
            if (t.charAt(0) == '_') {
                int firstQuote = t.indexOf("'");
                if (firstQuote == -1)
                    firstQuote = t.indexOf("\"");
                String hint = t.substring(0, firstQuote);
                charsetHint = new UnqualifiedName(hint, false);
                t = t.substring(firstQuote);
            }

            if ((t.length() > 2) && (t.startsWith("\"") && (t.endsWith("\"")))) {
                t = PEStringUtils.escapeSingleQuoteIfNecessary(t);
            }
        }
        if (PEStringUtils.isHexNumber(t)) {
            tok = TokenTypes.Hex_String_Literal;
        }
        ExpressionNode ex = null;
        if (opts.isActualLiterals() || (pc.getCapability() != Capability.PARSING_ONLY && pc.isMutableSource()))
            ex = new ActualLiteralExpression(ValueConverter.INSTANCE.convertLiteral(t, tok), tok,
                    SourceLocation.make(o), charsetHint);
        else {
            DelegatingLiteralExpression litex = new DelegatingLiteralExpression(tok, SourceLocation.make(o), this,
                    literals.size(), charsetHint);
            literals.add(litex, ValueConverter.INSTANCE.convertLiteral(t, tok));
            ex = litex;
        }
        return ex;
    }

    public ExpressionNode buildIdentifierLiteral(String ident) {
        return buildIdentifierLiteral(new UnqualifiedName(ident));
    }

    public ExpressionNode buildIdentifierLiteral(Name ident) {
        return new IdentifierLiteralExpression(ident);
    }

    public ExpressionNode buildNullLiteral() {
        return LiteralExpression.makeNullLiteral();
    }

    public ExpressionNode buildFunctionCall(FunctionName givenName, List<ExpressionNode> params, SetQuantifier sq,
            Object tree) {
        FunctionName unqualifiedName = givenName;
        if (pc.getCapability() != Capability.PARSING_ONLY) {
            if (unqualifiedName.isPipes()) {
                forceUncacheable(ValueManager.CacheStatus.NOCACHE_DYNAMIC_FUNCTION);
                SQLMode mode = KnownVariables.SQL_MODE.getSessionValue(pc.getConnection().getVariableSource());
                if (mode.isPipesAsConcat()) {
                    // rewrite to use the concat function call
                    unqualifiedName = new FunctionName("CONCAT", -1, false);
                } else {
                    // rewrite using or
                    unqualifiedName = FunctionName.makeOr();
                }
            } else if (unqualifiedName.isDoubleAmpersand()) {
                unqualifiedName = FunctionName.makeAnd();
            }
        }
        @SuppressWarnings("unchecked")
        FunctionCall fc = new FunctionCall(unqualifiedName, (params == null ? Collections.EMPTY_LIST : params), sq,
                SourceLocation.make(tree));
        if (EngineConstant.FUNCTION.has(fc, EngineConstant.DATABASE)
                || EngineConstant.FUNCTION.has(fc, EngineConstant.SCHEMA)) {
            forceUncacheable(ValueManager.CacheStatus.NOCACHE_DYNAMIC_FUNCTION);
            Database<?> peds = pc.getCurrentDatabase(false);
            if (peds == null)
                return LiteralExpression.makeNullLiteral();
            return LiteralExpression.makeStringLiteral(peds.getName().get());
        } else if (EngineConstant.FUNCTION.has(fc, EngineConstant.CURRENT_USER)) {
            forceUncacheable(ValueManager.CacheStatus.NOCACHE_DYNAMIC_FUNCTION);
            PEUser peu = pc.getCurrentUser().get(pc);
            if (peu == null)
                return LiteralExpression.makeNullLiteral();
            // not entirely correct
            return LiteralExpression.makeStringLiteral(peu.getUserScope().getSQL());
        } else if (EngineConstant.FUNCTION.has(fc, EngineConstant.LAST_INSERT_ID)) {
            forceUncacheable(ValueManager.CacheStatus.NOCACHE_DYNAMIC_FUNCTION);
            return LiteralExpression.makeLongLiteral(pc.getConnection().getLastInsertedId());
        } else if (EngineConstant.FUNCTION.has(fc, EngineConstant.RAND)) {
            return buildRandCall(fc.getParameters());
        }

        if (unqualifiedName.isAggregate() || isFunctionThatRequiresSetTimestampVariable(
                fc.getFunctionName().getUnqualified().get(), params)) {
            scope.registerFunction(fc);
        }

        return fc;
    }

    private boolean isFunctionThatRequiresSetTimestampVariable(String name, List<ExpressionNode> params) {
        boolean ret = false;

        if (TimestampVariableUtils.isTimestampFunction(name)) {
            // unix_timestamp also takes a parameter
            // so we only need to set the timestamp variable if there are no
            // parameter(s)
            if (TimestampVariableUtils.isFunctionUnixTimestamp(name)) {
                if (params != null && params.size() > 0) {
                    ret = false;
                } else {
                    ret = true;
                }
            } else {
                ret = true;
            }
        }

        return ret;
    }

    private UnqualifiedName castAlias(Name alias) {
        if (alias == null)
            return null;
        if (alias instanceof UnqualifiedName)
            return (UnqualifiedName) alias;
        throw new ParserException(Pass.SECOND, "Use of qualified identifier for alias: '" + alias.getSQL() + "'");
    }

    public ExpressionNode buildTableInstance(Name name, Name alias) {
        return buildTableInstance(name, alias, null);
    }

    public ExpressionNode buildTableInstance(Name name, Name alias, List<IndexHint> hints) {
        if (name == null) {
            throw new SchemaException(Pass.FIRST, MISSING_UNQUALIFIED_IDENTIFIER_ERROR_MSG);
        }

        TableInstance ti = null;
        if (opts.isResolve()) {
            ti = scope.buildTableInstance(name, castAlias(alias), pc, lockInfo);
        } else {
            ti = new TableInstance(null, name, castAlias(alias), 0, opts.isResolve());
        }
        if (hints != null)
            ti.setHints(hints);
        return ti;
    }

    public IndexHint buildIndexHint(HintType type, boolean isKey, HintTarget targ,
            List<UnqualifiedName> indexNames) {
        return new IndexHint(type, isKey, targ, indexNames);
    }

    public ExpressionNode buildWildcard(Token orig) {
        return new Wildcard(SourceLocation.make(orig));
    }

    public ExpressionNode buildQuestionMark(CommonTree orig) {
        return new Parameter(SourceLocation.make(orig));
    }

    public LimitSpecification buildLimitSpecification(ExpressionNode rowcount, ExpressionNode offset) {
        return new LimitSpecification(rowcount, offset);
    }

    /**
     * @param a
     * @param asc
     * @param desc
     * @return
     */
    public SortingSpecification buildSortingSpecification(ExpressionNode a, Object asc, Object desc) {
        // default is asc, so as long as desc is non-null it is descending
        boolean direction = (desc == null);
        return new SortingSpecification(a, direction);
    }

    public FunctionName buildOperatorName(CommonTree ct) {
        return new FunctionName(ct.getText(), ct.getType(), true);
    }

    public FunctionName buildOperatorName(Token t) {
        return new FunctionName(t.getText(), t.getType(), true);
    }

    public FunctionName buildFunctionName(String v, int tokenKind) {
        return new FunctionName(v, tokenKind, false);
    }

    public FunctionName buildFunctionName(Token tok, boolean op) {
        return new FunctionName(tok.getText(), tok.getType(), op, SourceLocation.make(tok));
    }

    public JoinSpecification buildJoinType(String primary) {
        return buildJoinType(primary, null, null);
    }

    public JoinSpecification buildJoinType(String primary, String natural, String outer) {
        StringBuilder buf = new StringBuilder();
        if (natural != null)
            buf.append(natural).append(" ");
        if (primary != null)
            buf.append(primary).append(" ");
        if (outer != null)
            buf.append(outer);
        return JoinSpecification.makeJoinSpecification(buf.toString().toUpperCase());
    }

    public JoinClauseType buildJoinClauseType(ExpressionNode node, List<Name> namedCols) {
        return new JoinClauseType(node, namedCols, ((namedCols != null) ? ClauseType.USING : ClauseType.ON));
    }

    public JoinedTable buildJoinedTable(ExpressionNode target, JoinClauseType joinOn,
            JoinSpecification injoinType) {
        // JOIN is INNER JOIN
        JoinSpecification joinType = (injoinType == null ? buildJoinType("INNER") : injoinType);
        return new JoinedTable(target, (joinOn == null ? null : joinOn.getNode()), joinType,
                (joinOn == null ? null : joinOn.getColumnIdentifiers()));
    }

    public TableJoin buildTableJoin(ExpressionNode factor, List<JoinedTable> joins) {
        return new TableJoin(factor, joins);
    }

    public FromTableReference buildFromTableReference(ExpressionNode factor, List<JoinedTable> joins) {

        if (joins.isEmpty()) {
            return new FromTableReference(factor);
        }

        convertUsingColSpecToOnSpec(factor, joins);

        return new FromTableReference(new TableJoin(factor, joins));
    }

    private void convertUsingColSpecToOnSpec(ExpressionNode factor, List<JoinedTable> joins) {
        if (!opts.isResolve())
            return;
        List<UnqualifiedName> visibleAliases = new LinkedList<UnqualifiedName>();
        if (factor instanceof TableInstance)
            visibleAliases.add(0, ((TableInstance) factor).getReferenceName(pc).getUnqualified());
        for (JoinedTable jt : joins) {
            List<Name> usingColSpec = jt.getUsingColSpec();
            if (usingColSpec != null && usingColSpec.size() > 0) {
                ExpressionNode jtTable = jt.getJoinedTo();

                visibleAliases.add(0, ((TableInstance) jtTable).getReferenceName(pc).getUnqualified());

                // turn USING column spec to equivalent ON t1.fieldn = t2.fieldn 
                List<Name> unqparts = new ArrayList<Name>();
                List<ExpressionNode> params = new ArrayList<ExpressionNode>();
                List<ExpressionNode> equals = new ArrayList<ExpressionNode>();

                // build list of t1.fieldn = t2.fieldn function(s) first
                List<ExpressionNode> functions = new ArrayList<ExpressionNode>();
                for (Name name : usingColSpec) {
                    params.clear();

                    // try to find the right table to hang the thing on
                    int foff = -1;
                    SchemaException any = null;
                    int offset = -1;
                    for (UnqualifiedName un : visibleAliases) {
                        offset++;
                        if (params.size() == 2)
                            break;
                        unqparts.clear();
                        unqparts.add(un);
                        unqparts.add(name.getUnqualified());
                        try {
                            ExpressionNode en = buildColumnReference(buildQualifiedName(unqparts));
                            if (params.isEmpty()) {
                                params.add(en);
                                foff = offset;
                            } else if (foff > offset)
                                params.add(en);
                            else
                                params.add(0, en);
                        } catch (SchemaException se) {
                            any = se;
                        }
                    }
                    if (params.size() != 2) {
                        if (any != null)
                            throw any;
                        throw new SchemaException(Pass.SECOND, "Failed to resolve using clause");
                    }

                    equals.add(buildFunctionCall(FunctionName.makeEquals(), params, null, null));
                }

                if (equals.size() > 0) {
                    // build function(s) of AND'ed t1.fieldn = t2.fieldn
                    functions.clear();
                    ExpressionNode current = null;
                    ExpressionNode last = null;
                    for (int i = 0; i < equals.size(); ++i) {
                        current = equals.get(i);
                        if (i > 0) {
                            params.clear();
                            params.add(last);
                            params.add(current);
                            current = buildFunctionCall(FunctionName.makeAnd(), params, null, null);
                        }
                        last = current;
                    }
                    // just set using the last function
                    Edge<?, ExpressionNode> edge = jt.getJoinOnEdge();
                    edge.add(last);

                    // clear the using column spec
                    // usingColSpec.clear();
                }
            }
        }
    }

    public Name buildQualifiedName(List<Name> parts) {
        if (parts.size() == 1)
            return parts.get(0);
        ArrayList<UnqualifiedName> unqparts = new ArrayList<UnqualifiedName>();
        for (Name n : parts) {
            if (n == null)
                continue;
            unqparts.addAll(n.getParts());
        }
        Name out = new QualifiedName(unqparts);
        // verify that none of the parts (other than the last), is an asterisk
        for (Iterator<UnqualifiedName> iter = unqparts.iterator(); iter.hasNext();) {
            UnqualifiedName un = iter.next();
            if (un.isAsterisk()) {
                if (iter.hasNext())
                    throw new SchemaException(Pass.FIRST, "Invalid qualified name: " + out.getSQL());
            }
        }
        return out;
    }

    public Name buildNameFromStringLiteral(Object in) {
        CommonTree tree = (CommonTree) in;
        return new UnqualifiedName(PEStringUtils.dequote(tree.getText()));
    }

    public Name buildName(Token tok) {
        return new UnqualifiedName(tok.getText(), SourceLocation.make(tok));
    }

    public Name buildName(Object in) {
        CommonTree tree = (CommonTree) in;
        return new UnqualifiedName(tree.getText(), SourceLocation.make(tree));
    }

    public Name buildName(String in) {
        return new UnqualifiedName(in, null);
    }

    public ExpressionNode addGrouping(ExpressionNode in) {
        in.setGrouped();
        return in;
    }

    public ProjectingStatement addGrouping(ProjectingStatement in) {
        in.setGrouped(true);
        return in;
    }

    // only 1 of the first four args will be non null.
    /**
     * @param plusSign
     * @param minusSign
     * @param binary
     * @param tilde
     * @param in
     * @param collate
     * @param collation
     * @return
     */
    public ExpressionNode applyExprFlags(Token plusSign, Token minusSign, Token binary, Token tilde,
            ExpressionNode in, Token collate, Name collation) {
        ExpressionNode out = in;
        if (collation != null) {
            // collation is an operator - it has higher precedence than the other stuff; hack it in
            FunctionName fn = new FunctionName(collate.getText(), collate.getType(), true);
            FunctionCall fc = new FunctionCall(fn, out, new IdentifierLiteralExpression(collation));
            out = fc;
        }
        if (minusSign != null) {
            out.setNegated();
        } else if (tilde != null) {
            out.setBitNegated();
        } else if (binary != null) {
            // this is a function call
            FunctionName fn = new FunctionName(binary.getText(), binary.getType(), true);
            FunctionCall fc = new FunctionCall(fn, out);
            out = fc;
        }
        return out;
    }

    public ExpressionNode buildDefaultKeywordExpr(Token orig) {
        return new Default(SourceLocation.make(orig));
    }

    public ExpressionNode buildUpdateExpression(FunctionName eqop, Name columnRef, ExpressionNode expr, Object in) {
        CommonTree tree = (CommonTree) in;
        // this is just a function call, but we have to wrap the column ref
        ArrayList<ExpressionNode> params = new ArrayList<ExpressionNode>();
        params.add(buildColumnReference(columnRef));
        params.add(expr);
        return buildFunctionCall(eqop, params, null, tree);
    }

    public VariableInstance buildLHSVariableInstance(VariableScope vs, Name n, Object tree) {
        return captureVariable(new VariableInstance(n.getUnqualified(), vs, SourceLocation.make(tree), false));
    }

    public VariableInstance buildRHSVariableInstance(Object fat, Object sat, VariableScopeKind vsk, Name n,
            Object tree) {
        VariableScopeKind kind = vsk;
        if (kind == null) {
            if (fat != null) {
                if (sat != null) {
                    /*
                     * MySQL returns the session value if it exists and the
                     * global value otherwise.
                     */
                    final String varName = n.getUnqualified().getUnquotedName().get();
                    final VariableHandler<?> exists = Singletons.require(VariableService.class).getVariableManager()
                            .lookup(varName);
                    if (exists != null) {
                        if (exists.getScopes().contains(VariableScopeKind.SESSION)) {
                            kind = VariableScopeKind.SESSION;
                        } else {
                            kind = VariableScopeKind.GLOBAL;
                        }
                    }
                } else {
                    kind = VariableScopeKind.USER;
                }
            } else {
                kind = VariableScopeKind.SESSION;
            }
        }
        return captureVariable(
                new VariableInstance(n.getUnqualified(), new VariableScope(kind), SourceLocation.make(tree), true));
    }

    private VariableInstance captureVariable(VariableInstance vi) {
        if (!scope.isEmpty())
            scope.getVariables().add(vi);
        return vi;
    }

    public VariableScope buildVariableScope(VariableScopeKind vsk) {
        return new VariableScope(vsk);
    }

    public VariableScope buildVariableScope(Name scoped) {
        if (scoped == null)
            return new VariableScope(VariableScopeKind.SCOPED);
        return new VariableScope(scoped.getUnquotedName().get());
    }

    public SetExpression buildSetVariableExpression(VariableInstance v, ExpressionNode en) {
        return buildSetVariableExpression(v, Collections.singletonList(en));
    }

    public SetExpression buildSetVariableExpression(VariableInstance v, List<ExpressionNode> l) {

        if (StringUtils.endsWithIgnoreCase(v.getVariableName().get(), "NAMES")) {
            // validate NAME variable is one of our supported ones
            // should only be one item in l
            if (l.get(0) instanceof LiteralExpression) {
                LiteralExpression le = (LiteralExpression) l.get(0);
                if (le.isNullLiteral()) {
                    throw new SchemaException(Pass.FIRST, "Must specify a character set");
                }
                String value = (String) le.getValue(pc.getValues());
                if (Singletons.require(CharSetNative.class).getCharSetCatalog().findCharSetByName(value) == null) {
                    // character set not supported
                    throw new SchemaException(Pass.FIRST, "Cannot set an unsupported character set: " + value);
                }
            }
        }

        if (l.size() == 1)
            return new SetVariableExpression(v, l.get(0));
        return new SetVariableExpression(v, l);
    }

    public SetExpression buildSetTransactionIsolation(SetTransactionIsolationExpression.IsolationLevel il,
            VariableScopeKind scopeKind) {
        return new SetTransactionIsolationExpression(il, scopeKind);
    }

    public SessionSetVariableStatement buildSessionSetVarStatement(List<SetExpression> l) {
        return new SessionSetVariableStatement(l);
    }

    public SessionSetVariableStatement buildAlterPersistentVariable(VariableInstance vi, ExpressionNode expr) {
        ArrayList<SetExpression> sets = new ArrayList<SetExpression>();
        sets.add(new SetVariableExpression(vi, expr));
        return new SessionSetVariableStatement(sets);
    }

    @SuppressWarnings("rawtypes")
    public Statement buildAddVariable(Name newName, List<Pair<Name, LiteralExpression>> options) {
        pc.getPolicyContext().checkRootPermission("create a new system variable");
        String varName = newName.getUnqualified().getUnquotedName().get();
        VariableHandler exists = Singletons.require(VariableService.class).getVariableManager().lookup(varName);
        if (exists != null)
            throw new SchemaException(Pass.SECOND, "Variable " + newName + " already exists");
        return AddGlobalVariableStatement.decode(pc, varName, options);
    }

    private Comment buildComment(String c) {
        return new Comment(PEStringUtils.dequote(c));
    }

    public TableModifier buildTableModifier(List<ExpressionNode> l) {
        return new UnknownTableModifier(l);
    }

    public TableModifier buildTableModifier(ExpressionNode lhs, boolean hasEquals, ExpressionNode rhs) {
        ArrayList<ExpressionNode> out = new ArrayList<ExpressionNode>();
        if (hasEquals) {
            out.add(buildFunctionCall(FunctionName.makeEquals(), Arrays.asList(new ExpressionNode[] { lhs, rhs }),
                    null, null));
        } else {
            out.add(lhs);
            out.add(rhs);
        }
        return new UnknownTableModifier(out);
    }

    public TableModifier buildAutoincTableModifier(ExpressionNode litval) {
        LiteralExpression lit = (LiteralExpression) litval;
        Object value = lit.getValue(pc.getValues());
        if (value instanceof Number) {
            Number n = (Number) value;
            return new AutoincTableModifier(n.longValue());
        }
        throw new SchemaException(Pass.SECOND, "Unknown autoinc value kind: " + value.getClass().getSimpleName());
    }

    public TableModifier buildEngineTableModifier(Name name) {
        EngineTableModifier.EngineTag actualTag = EngineTableModifier.EngineTag
                .findEngine(name.getUnqualified().getUnquotedName().get());
        if (actualTag == null)
            actualTag = EngineTableModifier.EngineTag.INNODB;
        return new EngineTableModifier(actualTag);
    }

    public TableModifier buildCommentTableModifier(final Name tableName, final String s) throws SchemaException {
        final long maxAllowedLength = Singletons.require(DBNative.class).getMaxTableCommentLength();
        if (s.length() > maxAllowedLength) {
            throw new SchemaException(new ErrorInfo(AvailableErrors.TOO_LONG_TABLE_COMMENT,
                    tableName.getUnqualified().getUnquotedName().get(), maxAllowedLength));
        }

        return new CommentTableModifier(buildComment(s));
    }

    public Comment buildTableFieldComment(final Name fieldName, final String s) throws SchemaException {
        final long maxAllowedLength = Singletons.require(DBNative.class).getMaxTableFieldCommentLength();
        if (s.length() > maxAllowedLength) {
            throw new SchemaException(new ErrorInfo(AvailableErrors.TOO_LONG_TABLE_FIELD_COMMENT,
                    fieldName.getUnquotedName().get(), maxAllowedLength));
        }

        return buildComment(s);
    }

    public TableModifier buildRowFormatTableModifier(Name n) {
        return new RowFormatTableModifier(n.getUnqualified());
    }

    public TableModifier buildMaxRowsModifier(ExpressionNode en) {
        LiteralExpression litex = asLiteral(en);
        Object v = litex.getValue(pc.getValues());
        if (v instanceof Number) {
            Number n = (Number) v;
            return new MaxRowsModifier(n.longValue());
        }
        throw new SchemaException(Pass.FIRST, "Invalid max rows value - not a number");
    }

    public TableModifier buildChecksumModifier(ExpressionNode en) {
        Integer v = asIntegralLiteral(en);
        return new ChecksumModifier(v.intValue());
    }

    public TableModifier buildDelayKeyWriteTableModifier(ExpressionNode en) {
        throw new SchemaException(Pass.FIRST, "No support for DELAY_KEY_WRITE table option");
    }

    public Statement buildCommitTransactionStatement() {
        return new TransactionStatement(TransactionStatement.Kind.COMMIT);
    }

    public Statement buildStartTransactionStatement(boolean snapshot) {
        return new StartTransactionStatement(snapshot);
    }

    public Statement buildSavepointTransactionStatement(Name n) {
        return new SavepointStatement(n, false);
    }

    public Statement buildReleaseSavepointTransactionStatement(Name n) {
        return new SavepointStatement(n, true);
    }

    public Statement buildRollbackTransactionStatement(Name n) {
        return new RollbackTransactionStatement(n);
    }

    public Statement buildTruncateStatement(Name name, Object tree) {
        final TableInstance ti = (TableInstance) buildTableInstance(name, null);
        if (ti.getAbstractTable().isTable()) {
            final TruncateStatement ts = new TruncateStatement(ti, SourceLocation.make(tree));
            ts.getDerivedInfo().takeScope(scope);
            return ts;
        }
        UnqualifiedName encName = null;
        UnqualifiedName tname = null;
        if (name.isQualified()) {
            QualifiedName qn = (QualifiedName) name;
            encName = qn.getNamespace();
            tname = qn.getUnqualified();
        } else {
            tname = (UnqualifiedName) name;
            encName = pc.getCurrentDatabase().getName().getUnqualified();
        }

        throw new SchemaException(new ErrorInfo(AvailableErrors.TABLE_DNE, encName.getUnquotedName().get(),
                tname.getUnquotedName().get()));
    }

    public ExpressionNode buildSubquery(Statement in, Name alias, Object orig, boolean makeVirtualTable) {
        ProjectingStatement dmls = (ProjectingStatement) in;
        Subquery sq = new Subquery(dmls, alias, SourceLocation.make(orig));
        if (makeVirtualTable) {
            SubqueryTable sqt = SubqueryTable.make(pc, dmls);
            sq.setTable(sqt);
            scope.pushVirtualTable(sqt, alias.getUnqualified(), pc);
            scope.getNestedQueries().add(dmls);
        }

        // if an alias was specified, then enter a fake table into the scope.
        return sq;

    }

    public ExpressionNode buildExists(Token exists, Statement ss, Object orig) {
        ExpressionNode param = buildSubquery(ss, null, orig, false);
        return buildFunctionCall(buildFunctionName(exists, false), Collections.singletonList(param), null, null);
    }

    public Statement buildCreateRangeStatement(Name rangeName, Name persistentGroup, List<BasicType> types,
            Boolean ifNotExists) {
        pc.getPolicyContext().checkRootPermission("create a range");
        RangeDistribution rd = pc.findRange(rangeName, persistentGroup);
        if (rd != null && !getOptions().isAllowDuplicates()) {
            if (Boolean.TRUE.equals(ifNotExists)) {
                return buildEmptyStatement("Create an existing range.");
            }
            throw new SchemaException(Pass.SECOND, "Range " + rangeName.getSQL() + " already exists.");
        }
        PEPersistentGroup onGroup = pc.findStorageGroup(persistentGroup);
        if (onGroup == null)
            throw new SchemaException(Pass.SECOND, "No such persistent group: " + persistentGroup.getSQL());
        RangeDistribution nrd = new RangeDistribution(pc, rangeName, types, onGroup);
        if (rd != null)
            try {
                // it is an error if the redeclaration doesn't match the original
                rd.verifySame(pc, nrd);
            } catch (PEException pe) {
                throw new SchemaException(Pass.SECOND, pe);
            }
        return new PECreateStatement<RangeDistribution, DistributionRange>((rd == null ? nrd : rd), true, "RANGE",
                rd != null);
    }

    public Statement buildDropRangeStatement(Name rangeName, Boolean ifExists, Name storageGroup) {
        pc.getPolicyContext().checkRootPermission("drop a range");
        RangeDistribution rd = null;
        if (storageGroup == null) {
            List<DistributionRange> allsuch = pc.getCatalog()
                    .findDistributionRange(rangeName.getUnquotedName().get());
            if (allsuch.size() > 1)
                throw new SchemaException(Pass.SECOND, "More than one range named " + rangeName
                        + " please specify group (add persistent group <name>)");
            else if (allsuch.isEmpty()) {
                if (Boolean.TRUE.equals(ifExists)) {
                    return buildEmptyStatement("Drop a non existing range.");
                }
                throw new SchemaException(Pass.SECOND, "No such range: " + rangeName.getSQL());
            }
            rd = pc.findRange(rangeName, new UnqualifiedName(allsuch.get(0).getStorageGroup().getName()));
        } else {
            rd = pc.findRange(rangeName, storageGroup);
            if (rd == null) {
                if (Boolean.TRUE.equals(ifExists)) {
                    return buildEmptyStatement("Drop a non existing range.");
                }
                throw new SchemaException(Pass.SECOND, "No such range: " + rangeName.getSQL());
            }
        }
        PEDropRangeStatement out = new PEDropRangeStatement(rd);
        out.ensureUnreferenced(pc);
        return out;
    }

    public Statement buildDropPersistentGroupStatement(Name groupName) {
        pc.getPolicyContext().checkRootPermission("drop a persistent group");
        PEPersistentGroup peg = pc.findStorageGroup(groupName);
        if (peg == null)
            throw new SchemaException(Pass.SECOND, "No such persistent group: " + groupName.getSQL());
        PEDropStorageGroupStatement out = new PEDropStorageGroupStatement(peg);
        out.ensureUnreferenced(pc);
        return out;
    }

    public Statement buildAddPersistentSiteStatement(Name sgName, List<Name> siteNames, boolean rebalance) {
        pc.getPolicyContext().checkRootPermission("add a persistent site");
        PEPersistentGroup pesg = pc.findStorageGroup(sgName);
        if (pesg == null)
            throw new SchemaException(Pass.SECOND, "No such persistent group: " + sgName.getSQL());
        ArrayList<PEStorageSite> sites = new ArrayList<PEStorageSite>();
        for (Name n : siteNames) {
            PEStorageSite pess = pc.findStorageSite(n);
            if (pess == null)
                throw new SchemaException(Pass.SECOND, "No such persistent site: " + n.getSQL());
            sites.add(pess);
        }
        return new AddStorageSiteStatement(pesg, sites, rebalance);
    }

    public Statement buildShowSingularQuery(String onInfoSchemaTable, Name objectName) {
        if (pc.getCapability() == Capability.PARSING_ONLY && !pc.getCatalog().isPersistent())
            return new EmptyStatement("no catalog queries with transient execution engine");

        // TODO:
        // handle !resolve
        ShowSchemaBehavior ist = Singletons.require(InformationSchemaService.class)
                .lookupShowTable(new UnqualifiedName(onInfoSchemaTable));
        if (ist == null)
            throw new MigrationException("Need to add info schema table for " + onInfoSchemaTable);
        return ist.buildUniqueStatement(pc, objectName, new ShowOptions());
    }

    public void push_info_schema_scope(Name n) {
        // parsing only - just add the darn thing
        if (pc.getCapability() == Capability.PARSING_ONLY) {
            pushScope();
            scope.buildTableInstance(n, new UnqualifiedName("a"), null, pc, null);
        } else {
            ShowSchemaBehavior sst = Singletons.require(InformationSchemaService.class)
                    .lookupShowTable((UnqualifiedName) n);
            if (sst == null)
                throw new SchemaException(Pass.SECOND, "No such table: " + n.getSQL());
            InformationSchemaTable ist = (InformationSchemaTable) sst;
            pushScope();
            // we put in an alias anyways
            scope.buildTableInstance(ist.getName(), new UnqualifiedName("a"),
                    Singletons.require(InformationSchemaService.class).getShowSchema(), pc, null);
        }
    }

    public void push_info_schema_scope(String s) {
        push_info_schema_scope(buildName(s));
    }

    public Statement buildShowPluralQuery(String onInfoSchemaTable, List<Name> scoping,
            Pair<ExpressionNode, ExpressionNode> likeOrWhere) {
        return buildShowPluralQuery(onInfoSchemaTable, scoping, likeOrWhere, null);
    }

    public Statement buildShowPluralQuery(String onInfoSchemaTable, List<Name> scoping,
            Pair<ExpressionNode, ExpressionNode> likeOrWhere, Token full) {
        if (pc.getCapability() == Capability.PARSING_ONLY && !pc.getCatalog().isPersistent())
            return new EmptyStatement("no catalog queries with transient execution engine");

        ShowSchemaBehavior ist = Singletons.require(InformationSchemaService.class)
                .lookupShowTable(new UnqualifiedName(onInfoSchemaTable));
        if (ist == null)
            throw new MigrationException("Need to add info schema table for " + onInfoSchemaTable);
        ExpressionNode likeExpr = (likeOrWhere == null ? null : likeOrWhere.getFirst());
        ExpressionNode whereExpr = (likeOrWhere == null ? null : likeOrWhere.getSecond());

        ShowOptions so = new ShowOptions();
        if (full != null)
            so.withFull();

        // break up the scoping value into parts if qualified
        List<Name> scopingParts = new ArrayList<Name>();
        if (scoping != null) {
            for (Name name : scoping) {
                if (name instanceof QualifiedName) {
                    QualifiedName qn = (QualifiedName) name;
                    scopingParts.add(qn.getParts().get(1)); // table name
                    scopingParts.add(qn.getParts().get(0)); // database
                } else {
                    // assumed UnqualifiedName
                    scopingParts.add(name);
                }
            }
        }
        return ist.buildShowPlural(pc, scopingParts, likeExpr, whereExpr, so);
    }

    public Statement buildReloadLogging() {
        return new SessionStatement("RELOAD LOGGING") {
            @Override
            public boolean isPassthrough() {
                return false;
            }

            @Override
            public void plan(SchemaContext sc, ExecutionSequence es, BehaviorConfiguration config)
                    throws PEException {
                es.append(new TransientSessionExecutionStep("RELOAD LOGGING", new AdhocOperation() {

                    @Override
                    public void execute(ExecutionState estate, WorkerGroup wg, DBResultConsumer resultConsumer)
                            throws Throwable {
                        try {
                            // Servers should have been started with a log4j system property
                            // e.g. -Dlog4j.configuration=<something>log4j.properties 

                            String name = System.getProperty("log4j.configuration");
                            if (StringUtils.isBlank(name))
                                throw new PEException("log4j.configuration property not set");

                            URL resource = TranslatorUtils.class.getClassLoader().getResource(name);
                            if (resource == null)
                                throw new PEException("Couldn't locate file '" + name + "' on the classpath");

                            PropertyConfigurator.configure(resource);

                            logger.info("log4j configuration reloaded from '" + name + "'");
                        } catch (Exception e) {
                            logger.error("Unable to reload logging configuation: " + e.getMessage(), e);

                            throw new PEException("Unable to reload logging configuration: " + e.getMessage(), e);
                        }
                    }

                }));
            }
        };
    }

    public PEPersistentGroup lookupPersistentGroup(Name n) {
        PEPersistentGroup pesg = pc.findStorageGroup(n);
        if (pesg == null)
            throw new SchemaException(Pass.SECOND, "No such persistent group " + n.getSQL());
        return pesg;
    }

    public Statement setExplainFlag(Statement in, List<Pair<Name, LiteralExpression>> options) {
        ExplainOptions opts = ExplainOptions.NONE;
        if (options != null) {
            for (Pair<Name, LiteralExpression> p : options) {
                ExplainOption opt = ExplainOption.find(p.getFirst().getUnqualified());
                if (opt == null)
                    throw new SchemaException(Pass.FIRST, "No such explain option: " + p.getFirst().getSQL());
                opts = opts.addSetting(opt, p.getSecond().getValue(pc.getValues()));
            }
        }
        in.setExplain(opts);
        return in;
    }

    public SetQuantifier lookupSetQuantifier(String in) {
        SetQuantifier sq = SetQuantifier.fromSQL(in);
        if (sq == null)
            throw new SchemaException(Pass.SECOND, "Unknown set quantifier: " + in);
        return sq;
    }

    public Statement buildKillStatement(ExpressionNode expr, Boolean isKillConnection) {
        return new KillStatement(asIntegralLiteral(expr), isKillConnection.booleanValue());
    }

    public Statement buildEmptyStatement(String text) {
        return new EmptyStatement(text);
    }

    public LockType lookupLockType(String l, String r) {
        LockType lt = LockType.fromSQL(l, r);
        if (lt == null)
            throw new SchemaException(Pass.SECOND,
                    "No such lock type " + (l == null ? "" : l) + " " + (r == null ? "" : r));
        return lt;
    }

    public Statement buildUnlockTablesStatement() {
        return new LockStatement();
    }

    public Statement buildLockTablesStatement(List<Pair<ExpressionNode, LockType>> in) {
        ListOfPairs<TableInstance, LockType> tabs = new ListOfPairs<TableInstance, LockType>();
        for (Pair<ExpressionNode, LockType> p : in) {
            TableInstance ti = (TableInstance) p.getFirst();
            tabs.add(ti, p.getSecond());
        }
        return new LockStatement(tabs);
    }

    public void startSessionVarsScope() {
        resolveColumnsAsIdentifiers = true;
    }

    public void endSessionVarsScope() {
        resolveColumnsAsIdentifiers = false;
    }

    public UserScope findCurrentUser() {
        if (pc.getCapability() == Capability.PARSING_ONLY)
            return null;
        return pc.getCurrentUser().get(pc).getUserScope();
    }

    public UserScope buildUserScope(String name, String scopeStr) {
        return new UserScope(PEStringUtils.dequote(name),
                PEStringUtils.dequote((scopeStr == null) ? "'%'" : scopeStr));
    }

    public PEUser buildUserSpec(UserScope us, String pword) {
        return new PEUser(us, pword == null ? null : PEStringUtils.dequote(pword), false);
    }

    public Statement buildCreateUserStatement(List<PEUser> specs) {
        pc.getPolicyContext().checkRootPermission("create a user");
        // should check to see whether the users already exist
        if (pc.getCapability() != Capability.PARSING_ONLY && pc.isPersistent()) {
            for (PEUser peu : specs) {
                List<User> users = pc.getCatalog().findUsers(peu.getUserScope().getUserName(),
                        peu.getUserScope().getScope());
                if (!users.isEmpty())
                    throw new SchemaException(Pass.SECOND,
                            "User " + peu.getUserScope().getSQL() + " already exists");

            }
        }
        return new PECreateUserStatement(specs, false);
    }

    public Statement buildSetPasswordStatement(UserScope us, String text) {
        pc.getPolicyContext().checkRootPermission("set a password");
        PEUser exists = pc.findUser(us.getUserName(), us.getScope());
        if (exists == null)
            throw new SchemaException(Pass.SECOND, "User " + us.getUserName() + " does not exist");
        return new SetPasswordStatement(exists, PEStringUtils.dequote(text));
    }

    public Statement buildDropUserStatement(UserScope us, Boolean ifExists) {
        pc.getPolicyContext().checkRootPermission("drop a user");
        PEUser peu = pc.findUser(us.getUserName(), us.getScope());
        if (peu == null) {
            if (Boolean.TRUE.equals(ifExists)) {
                // just do an empty statement
                return new EmptyStatement("drop nonexistent user");
            }
            throw new SchemaException(new ErrorInfo(AvailableErrors.UNKNOWN_USER, us.getUserName(), us.getScope()));
        }
        return new PEDropUserStatement(peu);
    }

    public Statement buildAlterTableStatement(TableKey tab, AlterTableAction single) {
        return buildAlterTableStatement(tab, Collections.singletonList(single));
    }

    public Statement buildAlterTableStatement(TableKey tab, List<AlterTableAction> actions) {
        if (pc.getCapability() == Capability.PARSING_ONLY)
            return new PEAlterTableStatement(pc, tab, actions);
        PEAlterStatement<PETable> single = null;
        AlterTableAction singleAction = null;
        for (AlterTableAction aa : actions) {
            single = aa.requiresSingleStatement(pc, tab);
            if (single != null) {
                singleAction = aa;
                break;
            }
        }
        if (single != null) {
            if (actions.size() > 1)
                throw new SchemaException(Pass.SECOND, "alter action of type "
                        + singleAction.getClass().getSimpleName() + " must be the sole alter action");
            return single;
        }
        PEAlterTableStatement orig = new PEAlterTableStatement(pc, tab, actions);
        return pc.getPolicyContext().modifyAlterTableStatement(orig);
    }

    public Statement buildRenameTableStatement(final List<Pair<Name, Name>> sourceTargetNamePairs) {
        return RenameTableStatement.buildRenameTableStatement(pc, sourceTargetNamePairs);
    }

    public Statement buildRenameDatabaseStatement(final Pair<Name, Name> sourceTargetNames) {
        throw new SchemaException(Pass.FIRST, "This syntax is not supported as it has been deprecated.");
    }

    private List<AlterTableAction> wrapAlterAction(AlterTableAction ata) {
        return Collections.singletonList(ata);
    }

    public List<AlterTableAction> buildRenameTableAction(Name newName) {
        return wrapAlterAction(new RenameTableAction(newName));
    }

    /**
     * If you specify CONVERT TO CHARACTER SET without a collation, the default
     * collation for the character set is used.
     */
    public List<AlterTableAction> buildTableConvertToAction(final Name charSet, final Name collation) {
        final Pair<String, String> charSetCollationPair = getCharsetCollationPair(charSet, collation);
        return wrapAlterAction(
                new ConvertToAction(charSetCollationPair.getFirst(), charSetCollationPair.getSecond()));
    }

    public List<AlterTableAction> buildChangeColumnAction(Name columnName, TableComponent<PEColumn> newDef,
            Pair<String, Name> firstOrAfterSpec) {
        return wrapAlterAction(
                new ChangeColumnAction(lookupAlteredColumn(columnName), (PEColumn) newDef, firstOrAfterSpec));
    }

    public List<AlterTableAction> buildChangeColumnAction(Name columnName, List<TableComponent<?>> defs,
            Pair<String, Name> firstOrAfterSpec) {
        ArrayList<AlterTableAction> out = new ArrayList<AlterTableAction>();
        for (TableComponent<?> tc : defs) {
            if (tc instanceof PEColumn) {
                PEColumn pec = (PEColumn) defs.get(0);
                out.add(new ChangeColumnAction(lookupAlteredColumn(columnName), pec, firstOrAfterSpec));
            } else if (tc instanceof PEKey) {
                out.add(new AddIndexAction((PEKey) tc));
            } else {
                throw new SchemaException(Pass.FIRST, "Unknown alter table change column target: " + tc);
            }
        }
        return out;
    }

    public List<AlterTableAction> buildModifyColumnAction(List<TableComponent<?>> defs,
            Pair<String, Name> firstOrAfterSpec) {
        if (defs.size() > 1)
            throw new SchemaException(Pass.SECOND, "No support for modifying column def with inline key def");
        PEColumn newDef = (PEColumn) defs.get(0);
        PEColumn existing = lookupAlteredColumn(newDef.getName());
        return wrapAlterAction(new ChangeColumnAction(existing, newDef, firstOrAfterSpec));
    }

    public List<AlterTableAction> buildAlterColumnAction(Name columnName, ExpressionNode litex) {
        return wrapAlterAction(new AlterColumnAction(lookupAlteredColumn(columnName), (LiteralExpression) litex));
    }

    public List<AlterTableAction> buildAddIndexAction(PEKey newIndex) {
        if (pc.getCapability() != Capability.PARSING_ONLY && newIndex.isUnresolved())
            throw new SchemaException(Pass.FIRST, "Invalid forward key during alter");
        return wrapAlterAction(new AddIndexAction((PEKey) newIndex));
    }

    public List<AlterTableAction> buildDropIndexAction(ConstraintType kt, Name indexName) {
        PEKey targ = lookupAlteredKey(kt, indexName);
        return wrapAlterAction(new DropIndexAction(targ));
    }

    public Statement buildExternalDropIndexStatement(Name indexName, Name tableName) {
        TableKey tk = lookupAlteredTable(tableName);
        AlterTableAction aa = buildDropIndexAction(null, indexName).get(0);
        return buildAlterTableStatement(tk, aa);
    }

    public List<AlterTableAction> buildDropColumnAction(Name columnName) {
        List<AlterTableAction> actions = new ArrayList<AlterTableAction>();
        PEColumn alteredCol = lookupAlteredColumn(columnName);

        // cannot drop column if it is the target in an FK
        if (pc.getCapability() == Capability.FULL) {
            for (UserTable ut : pc.getCatalog()
                    .findTablesWithFKSReferencing(alteredCol.getTable().getPersistentID())) {
                for (Key k : ut.getKeys()) {
                    if (k.isForeignKey() && StringUtils.equals(k.getReferencedTable().getName(),
                            alteredCol.getTable().getName().get())) {
                        for (KeyColumn c : k.getColumns()) {
                            if (StringUtils.equals(c.getTargetColumnName(), alteredCol.getName().get())) {
                                throw new SchemaException(Pass.SECOND, "Cannot drop column '" + columnName.get()
                                        + "' because it is part of foreign key '" + k.getName() + "'");
                            }
                        }
                    }
                }
            }

            for (PEKey key : alteredCol.getTable().getKeys(pc)) {
                if (key.containsColumn(alteredCol)) {
                    if (key.isForeign()) {
                        throw new SchemaException(Pass.SECOND, "Cannot drop column '" + columnName.get()
                                + "' because it is part of foreign key '" + key.getName().get() + "'");
                    }

                    actions.add(buildDropIndexAction(null, key.getName()).get(0));

                    if (key.getColumns(pc).size() > 1) {
                        // rebuild index if multicol
                        PEKey newPEKey = key.copy(pc, alteredCol.getTable().asTable());
                        newPEKey.removeColumn(alteredCol);

                        actions.add(buildAddIndexAction(newPEKey).get(0));
                    }
                }
            }
        }

        actions.add(new DropColumnAction(alteredCol));
        return actions;
    }

    public List<AlterTableAction> buildAlterTableOptionActions(List<TableModifier> tm) {
        final List<AlterTableAction> actions = new ArrayList<AlterTableAction>(tm.size());
        for (final TableModifier modifier : tm) {
            actions.add(new ChangeTableModifierAction(modifier));
        }

        return actions;
    }

    public List<AlterTableAction> buildModifyDistributionAction(UnresolvedDistributionVector ndv) {
        return wrapAlterAction(new ChangeTableDistributionAction(ndv.resolve(pc, this)));
    }

    @SuppressWarnings("rawtypes")
    public List<AlterTableAction> buildAddColumnAction(List<TableComponent> newColumns) {
        return buildAddColumnAction(newColumns, null);
    }

    @SuppressWarnings("rawtypes")
    public List<AlterTableAction> buildAddColumnAction(List<TableComponent> newColumns,
            Pair<String, Name> firstOrAfterSpec) {
        if (firstOrAfterSpec != null) {
            // only valid when adding one column.  note that if we added a column & a key at the same time we should
            // ignore the key part (because keys doen't really have a position).
            int ncols = 0;
            for (TableComponent tc : newColumns)
                if (tc instanceof PEColumn)
                    ncols++;
            if (ncols > 1) {
                // only valid when adding one column
                throw new SchemaException(Pass.FIRST, "Cannot specify FIRST or AFTER option with multicolumn ADD");
            }
        }

        ArrayList<PEColumn> casted = new ArrayList<PEColumn>();
        ArrayList<PEKey> keys = new ArrayList<PEKey>();
        for (TableComponent nc : newColumns) {
            if (nc instanceof PEColumn)
                casted.add((PEColumn) nc);
            else if (nc instanceof PEKey)
                keys.add((PEKey) nc);
        }
        List<AlterTableAction> out = new ArrayList<AlterTableAction>();
        if (!casted.isEmpty()) {
            out.add(new AddColumnAction(casted, firstOrAfterSpec));
        }
        for (PEKey pek : keys) {
            out.add(new AddIndexAction(pek));
        }
        return out;
    }

    @SuppressWarnings("unchecked")
    private PEColumn lookupAlteredColumn(Name columnName) {
        PEColumn c = null;
        final Table<?> parentTable = scope.getAlteredTable();
        c = (PEColumn) parentTable.lookup(pc, columnName);
        if (c == null) {
            if (pc.getCapability() == Capability.PARSING_ONLY) {
                c = PEColumn.buildColumn(pc, columnName, TempColumnType.TEMP_TYPE, Collections.EMPTY_LIST, null,
                        null);
            } else {
                throw new SchemaException(Pass.SECOND,
                        "Unknown column '" + columnName + "' in '" + parentTable.getName().get() + "'");
            }

        }
        return c;
    }

    /**
     * @param kt
     * @param keyName
     * @return
     */
    @SuppressWarnings("unchecked")
    private PEKey lookupAlteredKey(ConstraintType kt, Name keyName) {
        PEKey k = null;
        if (pc.getCapability() == Capability.PARSING_ONLY) {
            k = new PEKey(keyName, IndexType.BTREE, Collections.EMPTY_LIST, null);
        } else {
            k = ((PETable) scope.getAlteredTable()).lookupKey(pc, keyName);
            if (k == null)
                throw new SchemaException(Pass.SECOND, "No such key: " + keyName);
        }
        return k;
    }

    @SuppressWarnings("unchecked")
    public TableKey lookupAlteredTable(Name tabName) {
        TableKey tab = null;
        if (pc.getCapability() == Capability.PARSING_ONLY) {
            tab = TableKey.make(pc, new PETable(pc, tabName, Collections.EMPTY_LIST, null, null, null), 0);
        } else {
            TableResolver resolver = new TableResolver().withMTChecks()
                    .withDatabaseFunction(new UnaryProcedure<Database<?>>() {

                        @Override
                        public void execute(Database<?> object) {
                            if (!(object instanceof PEDatabase))
                                throw new SchemaException(Pass.SECOND,
                                        "Invalid database for table alter: '" + object.getName() + "'");
                        }

                    });
            TableInstance ti = resolver.lookupTable(pc, tabName, lockInfo);
            if (ti == null)
                throw new SchemaException(Pass.SECOND, "No such table: " + tabName.getSQL());
            tab = ti.getTableKey();
        }
        // now we have to push all the columns in case they are used
        pushScope();
        scope.registerAlterColumns(pc, tab.getAbstractTable().asTable());
        return tab;
    }

    public WhenClause buildWhenClause(ExpressionNode te, ExpressionNode re, Object orig) {
        return new WhenClause(te, re, SourceLocation.make(orig));
    }

    public ExpressionNode buildCaseExpression(ExpressionNode te, ExpressionNode ee, List<WhenClause> whens,
            Object orig) {
        return new CaseExpression(te, ee, whens, SourceLocation.make(orig));
    }

    public ExpressionNode buildCastCall(String castText, ExpressionNode toCast, String asText, Name tn) {
        return new CastFunctionCall(toCast, castText, tn, asText);
    }

    public Name buildCastToIntegralType(String signed, String integer) {
        if (integer == null)
            return new UnqualifiedName(signed);
        return new UnqualifiedName(signed + " " + integer);
    }

    public Name buildCastToSizedType(String theType, String theFirstSize, String theSecondSize) {
        if (theFirstSize == null && theSecondSize == null)
            return new UnqualifiedName(theType);
        StringBuilder buf = new StringBuilder();
        buf.append(theType).append("(").append(theFirstSize);
        if (theSecondSize != null)
            buf.append(",").append(theSecondSize);
        buf.append(")");
        return new UnqualifiedName(buf.toString());
    }

    public Name buildCastToBinaryOrChar(String theType, String maybeFirstSize, Name maybeCharset) {
        StringBuilder buf = new StringBuilder();
        buf.append(theType);
        if (maybeFirstSize != null) {
            buf.append("(").append(maybeFirstSize).append(")");
        } else if (maybeCharset != null) {
            buf.append(" charset ").append(maybeCharset.getSQL());
        }
        return new UnqualifiedName(buf.toString());
    }

    public ExpressionNode buildCharCall(List<ExpressionNode> charCodes, Name outputEncoding) {
        return new CharFunctionCall(charCodes, outputEncoding);
    }

    public ExpressionNode buildRandCall(List<ExpressionNode> args) {
        if (args.isEmpty()) {
            return new RandFunctionCall(null);
        } else if (args.size() == 1) {
            return new RandFunctionCall(args.get(0));
        } else {
            throw new SchemaException(new ErrorInfo(AvailableErrors.INCORRECT_PARAM_COUNT_FUNCTION_CALL, "RAND"));
        }
    }

    public ExpressionNode buildConvertCall(ExpressionNode toConvert, Name transcodingName, Type castToType) {
        Name rhs = transcodingName;
        if (castToType != null) {
            StringBuilder buf = new StringBuilder();
            Singletons.require(DBNative.class).getEmitter().emitConvertTypeDeclaration(castToType, buf);
            rhs = new UnqualifiedName(buf.toString());
        }
        return new ConvertFunctionCall(toConvert, rhs, (transcodingName != null));
    }

    public ExpressionNode buildTimestampDiffCall(String unit, ExpressionNode param1, ExpressionNode param2) {
        FunctionName fn = new FunctionName("TIMESTAMPDIFF", TokenTypes.TIMESTAMPDIFF, false);
        ArrayList<ExpressionNode> actuals = new ArrayList<ExpressionNode>();
        actuals.add(buildIdentifierLiteral(unit));
        actuals.add(param1);
        actuals.add(param2);
        return buildFunctionCall(fn, actuals, null, null);
    }

    public ExpressionNode buildInterval(ExpressionNode intervalExpression, String unit) {
        return new IntervalExpression(intervalExpression, unit, null);
    }

    public ExpressionNode buildMultivalueExpression(final List<ExpressionNode> values) {
        if (values.size() == 1) {
            return addGrouping(values.get(0));
        }
        return buildExpressionSet(values);
    }

    public ExpressionNode buildExpressionSet(List<ExpressionNode> values) {
        return new ExpressionSet(values, null);
    }

    public ExpressionNode buildGroupConcat(FunctionName fn, List<ExpressionNode> params, boolean distinct,
            String separator) {
        SetQuantifier sq = (distinct ? SetQuantifier.DISTINCT : null);
        return new GroupConcatCall(fn, params, sq, separator);
    }

    public Statement buildCreateTenantStatement(Name tenantName, Name onDB, String description) {
        PEDatabase peds = null;
        if (onDB == null) {
            peds = pc.findSingleMTDatabase();
            if (peds == null)
                throw new SchemaException(Pass.SECOND, "No multitenant database found");
        }
        if (peds == null)
            peds = pc.findPEDatabase(onDB);
        if (peds == null)
            throw new SchemaException(Pass.SECOND,
                    "No such database: " + (onDB == null ? "(blank)" : onDB.getSQL()));
        if (!peds.getMTMode().isMT())
            throw new SchemaException(Pass.SECOND, "Cannot create tenant on " + peds.getMTMode().describe()
                    + " database " + (onDB == null ? "(blank)" : onDB.getSQL()));
        return pc.getPolicyContext().buildCreateTenantStatement(peds, tenantName,
                (description == null ? null : PEStringUtils.dequote(description)));
    }

    public Statement buildSuspendTenantStatement(Name tenantName) {
        return pc.getPolicyContext().buildSuspendTenantStatement(tenantName);
    }

    public Statement buildResumeTenantStatement(Name tenantName) {
        return pc.getPolicyContext().buildResumeTenantStatement(tenantName);
    }

    public Statement buildDropTenantStatement(Name tenantName) {
        return pc.getPolicyContext().buildDropTenantStatement(tenantName, false);
    }

    public Name buildUnrestrictedName(List<Name> names) {
        ArrayList<UnqualifiedName> parts = new ArrayList<UnqualifiedName>();
        for (Name n : names) {
            // should not happen
            if (n.isQualified())
                throw new SchemaException(Pass.FIRST, "Qualified name found where unqualified name expected");
            parts.add(n.getUnqualified());
        }
        return new QualifiedName(parts);
    }

    public Pair<Name, LiteralExpression> buildConfigOption(Name key, ExpressionNode value) {
        return new Pair<Name, LiteralExpression>(key, asLiteral(value));
    }

    private static final Name pluginKey = new UnqualifiedName("PLUGIN");
    private static final Name activeKey = new UnqualifiedName("ACTIVE");

    public Statement buildCreateDynamicSiteProvider(Name name, List<Pair<Name, LiteralExpression>> options) {
        pc.getPolicyContext().checkRootPermission("create a dynamic site provider");
        // see if we have one already
        Provider extant = pc.getCatalog().findProvider(name.get());
        if (extant != null)
            throw new SchemaException(Pass.SECOND, "Dynamic site provider " + name.getSQL() + " already exists");
        String plugin = null;
        Boolean isActive = null;
        for (Iterator<Pair<Name, LiteralExpression>> iter = options.iterator(); iter.hasNext();) {
            Pair<Name, LiteralExpression> p = iter.next();
            if (p.getFirst().getCapitalized().equals(pluginKey)) {
                plugin = p.getSecond().asString(pc.getValues());
                iter.remove();
            } else if (p.getFirst().getCapitalized().equals(activeKey)) {
                isActive = (Boolean) p.getSecond().getValue(pc.getValues());
                iter.remove();
            }
        }
        if (isActive == null)
            isActive = Boolean.TRUE;
        if (plugin == null)
            throw new SchemaException(Pass.SECOND, "Must specify plugin for CREATE DYNAMIC SITE PROVIDER");
        PEProvider provider = new PEProvider(pc, name, plugin, isActive);
        return new PECreateGroupProviderStatement(provider, options);
    }

    public Statement buildAlterDynamicSiteProviderStatement(Name name,
            List<Pair<Name, LiteralExpression>> options) {
        pc.getPolicyContext().checkRootPermission("alter a dynamic site provider");
        // see if we have one already
        Provider extant = pc.getCatalog().findProvider(name.get());
        if (extant == null)
            throw new SchemaException(Pass.SECOND, "No such dynamic site provider: " + name.getSQL());
        PEProvider pep = PEProvider.load(extant, pc);
        return new PEAlterGroupProviderStatement(pep, options);
    }

    public Statement buildShowDynamicSiteProvider(Name name, List<Pair<Name, LiteralExpression>> options) {
        pc.getPolicyContext().checkRootPermission("show a dynamic site provider");
        // see if we have one already
        Provider extant = pc.getCatalog().findProvider(name.get());
        if (extant == null)
            throw new SchemaException(Pass.SECOND, "No such dynamic site provider: " + name.getSQL());
        List<CatalogEntity> ents = null;
        // build the command block, use null for the action
        SiteManagerCommand smc = new SiteManagerCommand(Command.SHOW, extant,
                PEGroupProviderDDLStatement.convertOptions(pc, options));
        try {
            SiteProviderPlugin sm = SiteProviderFactory.getInstance(smc.getTarget().getName());
            ents = sm.show(smc);
        } catch (PEException pe) {
            throw new SchemaException(Pass.SECOND, "Unable to query provider " + extant.getName(), pe);
        }
        return new SchemaQueryStatement(true, DYNAMIC_SITE_PROVIDER, ents, false, null);
    }

    public Statement buildShowDynamicSiteProvidersSites() {
        pc.getPolicyContext().checkRootPermission("show dynamic site providers sites");
        // in this case, we just find all the providers, then route the
        // appropriate command to each provider, aggregate results,
        // etc.
        ListOfPairs<String, Object> options = new ListOfPairs<String, Object>();
        // or whatever you like
        options.add("cmd", "sites");
        ArrayList<CatalogEntity> results = new ArrayList<CatalogEntity>();
        List<Provider> providers = pc.getCatalog().findAllProviders();
        for (Provider p : providers) {
            try {
                SiteManagerCommand smc = new SiteManagerCommand(Command.SHOW, p, options);
                SiteProviderPlugin sm = SiteProviderFactory.getInstance(smc.getTarget().getName());
                results.addAll(sm.show(smc));
            } catch (PEException pe) {
                throw new SchemaException(Pass.SECOND, "Unable to query provider " + p.getName(), pe);
            }
        }
        return new SchemaQueryStatement(true, DYNAMIC_SITE_PROVIDER_SITES, results, false, null);
    }

    @SuppressWarnings("unchecked")
    public Statement buildDropDynamicSiteProviderStatement(Name name, List<Pair<Name, LiteralExpression>> options) {
        pc.getPolicyContext().checkRootPermission("drop a dynamic site provider");
        // see if we have one already
        Provider extant = pc.getCatalog().findProvider(name.get());
        if (extant == null)
            throw new SchemaException(Pass.SECOND, "No such dynamic site provider: " + name.getSQL());
        PEProvider pep = PEProvider.load(extant, pc);
        return new PEDropGroupProviderStatement(pep, (options == null ? Collections.EMPTY_LIST : options));
    }

    public PEPolicyClassConfig buildClassConfig(Name theClassName, ExpressionNode countLiteral,
            ExpressionNode providerLiteral, ExpressionNode poolLiteral) {
        PolicyClass theClass = PolicyClass.findSQL(theClassName.getUnqualified().getUnquotedName().get());
        if (theClass == null)
            throw new SchemaException(Pass.SECOND, "No such policy type: " + theClassName);
        Integer theCount = null;
        String theProvider = null;
        String thePool = null;
        if (poolLiteral instanceof LiteralExpression)
            thePool = (String) ((LiteralExpression) poolLiteral).getValue(pc.getValues());
        if (providerLiteral instanceof LiteralExpression)
            theProvider = (String) ((LiteralExpression) providerLiteral).getValue(pc.getValues());
        if (countLiteral instanceof LiteralExpression) {
            LiteralExpression litex = (LiteralExpression) countLiteral;
            Number n = (Number) litex.getValue(pc.getValues());
            theCount = n.intValue();
        }
        return new PEPolicyClassConfig(theClass, theProvider, thePool, (theCount == null ? 0 : theCount));
    }

    public Statement buildCreateDynamicSitePolicyStatement(Name name, boolean strict,
            List<PEPolicyClassConfig> classes) {
        if (name == null) {
            throw new SchemaException(Pass.FIRST, MISSING_UNQUALIFIED_IDENTIFIER_ERROR_MSG);
        }

        pc.getPolicyContext().checkRootPermission("create a dynamic site policy");
        DynamicPolicy dp = pc.getCatalog().findPolicy(name.get());
        if (dp != null)
            throw new SchemaException(Pass.SECOND, "Dynamic site policy " + name.getSQL() + " already exists");
        PEPolicy pep = new PEPolicy(pc, name, strict, classes);
        return new PECreateStatement<PEPolicy, DynamicPolicy>(pep, true, DYNAMIC_SITE_POLICY_TAG, false);
    }

    public Statement buildAlterDynamicSitePolicyStatement(Name name, Name newName, Boolean newStrict,
            List<PEPolicyClassConfig> configs) {
        pc.getPolicyContext().checkRootPermission("alter a dynamic site policy");
        DynamicPolicy dp = pc.getCatalog().findPolicy(name.get());
        if (dp == null)
            throw new SchemaException(Pass.SECOND, "No such dynamic site policy: " + name.getSQL());
        PEPolicy pep = PEPolicy.load(dp, pc);
        return new PEAlterPolicyStatement(pep, newName, newStrict, configs);
    }

    @SuppressWarnings({ "unchecked", "rawtypes" })
    public Statement buildDropDynamicSitePolicyStatement(Name name) {
        pc.getPolicyContext().checkRootPermission("drop a dynamic site policy");
        DynamicPolicy dp = pc.getCatalog().findPolicy(name.get());
        if (dp == null)
            throw new SchemaException(Pass.SECOND, "Dynamic site policy " + name.getSQL() + " does not exist");
        PEPolicy pep = PEPolicy.load(dp, pc);
        return new PEDropStatement(PEPolicy.class, null, true, pep, "DYNAMIC SITE POLICY");
    }

    public Statement buildCreateExternalServiceStatement(Name name, List<Pair<Name, LiteralExpression>> options) {
        if (name == null) {
            throw new SchemaException(Pass.FIRST, MISSING_UNQUALIFIED_IDENTIFIER_ERROR_MSG);
        }

        // check permissions
        pc.getPolicyContext().checkRootPermission("create an external service");

        ExternalService es = pc.getCatalog().findExternalService(name.getUnqualified().get());
        if (es != null)
            throw new SchemaException(Pass.SECOND, "External Service " + name.getSQL() + " already exists.");
        return new PECreateExternalServiceStatement(new PEExternalService(pc, name, options), true,
                "EXTERNAL SERVICE", false);
    }

    public Statement buildDropExternalServiceStatement(Name name) {
        pc.getPolicyContext().checkRootPermission("drop an external service");
        ExternalService es = pc.getCatalog().findExternalService(name.getUnqualified().get());
        if (es == null)
            throw new SchemaException(Pass.SECOND, "No such external service: " + name.getSQL());
        return new PEDropExternalServiceStatement(PEExternalService.class, null, true,
                new PEExternalService(pc, es), "EXTERNAL SERVICE");
    }

    public Statement buildAlterExternalServiceStatement(Name name, List<Pair<Name, LiteralExpression>> options) {
        pc.getPolicyContext().checkRootPermission("alter an external service");
        ExternalService es = pc.getCatalog().findExternalService(name.getUnqualified().get());
        if (es == null)
            throw new SchemaException(Pass.SECOND, "No such external service: " + name.getSQL());
        return new PEAlterExternalServiceStatement(new PEExternalService(pc, es, options));
    }

    public Statement buildStartExternalServiceStatement(Name name) {
        pc.getPolicyContext().checkRootPermission("start an external service");
        ExternalService es = pc.getCatalog().findExternalService(name.getUnqualified().get());
        if (es == null)
            throw new SchemaException(Pass.SECOND, "No such external service: " + name.getSQL());
        return new ExternalServiceControlStatement(ExternalServiceControlStatement.Action.START, es);
    }

    public Statement buildStopExternalServiceStatement(Name name) {
        pc.getPolicyContext().checkRootPermission("stop an external service");
        ExternalService es = pc.getCatalog().findExternalService(name.getUnqualified().get());
        if (es == null)
            throw new SchemaException(Pass.SECOND, "No such external service: " + name.getSQL());
        return new ExternalServiceControlStatement(ExternalServiceControlStatement.Action.STOP, es);
    }

    public Statement buildCreateContainerStatement(Name containerName, Name groupName,
            Pair<DistributionVector.Model, UnqualifiedName> backingModel, Boolean ifNotExists) {
        //      // check permissions
        //      pc.getPolicyContext().checkRootPermission("create a container");

        Container container = pc.getCatalog().findContainer(containerName.getUnqualified().get());
        if (container != null) {
            if (Boolean.TRUE.equals(ifNotExists)) {
                // just do an empty statement
                return new EmptyStatement("Create existing container");
            }
            throw new SchemaException(Pass.SECOND, "Container " + containerName.getSQL() + " already exists.");
        }

        PEPersistentGroup pesg = null;
        if (groupName != null) {
            pesg = pc.findStorageGroup(groupName);
            if (pesg == null)
                throw new SchemaException(Pass.SECOND, "No such persistent group: " + groupName.getSQL());
        }

        if (backingModel == null) {
            throw new SchemaException(Pass.SECOND,
                    "Missing distribution declaration on container " + containerName.getSQL());
        }

        DistributionVector.Model model = backingModel.getFirst();
        RangeDistribution rd = null;
        if (model == DistributionVector.Model.RANGE) {
            UnqualifiedName rangeName = backingModel.getSecond();
            rd = pc.findRange(rangeName, groupName);
            if (rd == null)
                throw new SchemaException(Pass.SECOND,
                        "No such range on group " + groupName + ": " + rangeName.getSQL());
        }

        return new PECreateStatement<PEContainer, Container>(new PEContainer(pc, containerName, pesg, model, rd),
                true, "CONTAINER", false);
    }

    public Statement buildDropContainerStatement(Name name, Boolean ifExists) {
        pc.getPolicyContext().checkRootPermission("drop a container");
        PEContainer cont = pc.findContainer(name);
        if (cont == null) {
            if (Boolean.TRUE.equals(ifExists)) {
                return buildEmptyStatement("Drop a non existing container.");
            }
            throw new SchemaException(Pass.SECOND, "No such container: " + name.getSQL());
        }
        return new PEDropContainerStatement(cont);
    }

    // move this somewhere else
    public Statement buildUseContainerStatement(Name containerName, List<Pair<LiteralExpression, Name>> values) {
        for (Pair<LiteralExpression, Name> val : values) {
            LiteralExpression expr = val.getFirst();
            if (expr.isNullLiteral()) {
                if (values.size() == 1) {
                    return new UseContainerStatement(pc, true);
                }
                throw new SchemaException(Pass.SECOND,
                        "NULL context cannot be specified with container discriminant.");
            } else if (expr.isGlobalLiteral()) {
                if (values.size() == 1) {
                    return new UseContainerStatement(pc, false);
                }
                throw new SchemaException(Pass.SECOND,
                        "GLOBAL context cannot be specified with container discriminant.");
            }
        }

        PEContainer cont = pc.findContainer(containerName);
        if (cont == null)
            throw new SchemaException(Pass.SECOND, "No such container: " + containerName.getSQL());
        PETable bt = cont.getBaseTable(pc);
        if (bt == null)
            throw new SchemaException(Pass.SECOND,
                    "Unable to use container " + containerName.getSQL() + ": no discriminator set");
        List<PEColumn> discCols = cont.getDiscriminantColumns(pc);
        if (discCols.size() != values.size())
            throw new SchemaException(Pass.SECOND, "Invalid discriminant value.  Found " + values.size()
                    + " columns but require " + discCols.size() + " columns");
        return new UseContainerStatement(pc, cont, bt, discCols, values, false);
    }

    public Parameter buildParameter(Token tree) {
        Parameter param = new Parameter(SourceLocation.make(tree));
        parameters.add(param);
        return param;
    }

    public Statement buildShowProcesslistStatement(Boolean full) {
        return new ShowProcesslistStatement(full);
    }

    public Statement buildShowPassthroughStatement(String command) {
        if (StringUtils.equals(command, "PLUGINS")) {
            return new ShowPassthroughStatement(PassThroughCommandType.PLUGINS, false, null);
        } else if (StringUtils.equals(command, "MASTER LOGS")) {
            return new ShowPassthroughStatement(PassThroughCommandType.MASTERLOGS, false, null);
        } else if (StringUtils.equals(command, "MASTER STATUS")) {
            return new ShowPassthroughStatement(PassThroughCommandType.MASTERSTATUS, false, null);
        } else if (StringUtils.equals(command, "SLAVE STATUS")) {
            return new ShowPassthroughStatement(PassThroughCommandType.SLAVESTATUS, false, null);
        } else if (StringUtils.equals(command, "GRANTS")) {
            return new ShowPassthroughStatement(PassThroughCommandType.GRANTS, false, null);
        }
        throw new SchemaException(Pass.SECOND, "No SHOW support for command '" + command + "'");
    }

    public GrantScope buildGrantScope(Name n) {
        if (n == null)
            return new GrantScope();
        // otherwise it's a db.* or db.table
        // the grant scope might be a tenant
        if (pc.getCapability() == Capability.PARSING_ONLY)
            return new GrantScope(n);
        PEDatabase peds = pc.findPEDatabase(n);
        PETenant ten = null;
        if (peds == null)
            ten = pc.findTenant(n);
        if (peds == null && ten == null)
            throw new SchemaException(Pass.SECOND, "No such database: " + n.getSQL());
        if (peds != null)
            return new GrantScope(peds);
        return new GrantScope(ten);
    }

    /**
     * @param gs
     * @param user
     * @param whatToGrant
     * @return
     */
    public Statement buildGrant(GrantScope gs, PEUser user, String whatToGrant) {
        if (pc.getCapability() == Capability.PARSING_ONLY) {
            return new GrantStatement(gs.buildPriviledge(pc, user));
        }
        pc.getPolicyContext().checkRootPermission("grant privileges");
        // we're going to use the PEUser bit to look up the actual user
        PEUser actualUser = pc.findUser(user.getUserScope().getUserName(), user.getUserScope().getScope());
        if (actualUser == null)
            actualUser = user;
        // so, a grant statement always creates a priv, but optionally creates the user
        return new GrantStatement(gs.buildPriviledge(pc, actualUser));
    }

    public Statement buildFlushPrivileges() {
        return new FlushPrivilegesStatement();
    }

    protected DelegatingLiteralExpression replaceLiteral(DelegatingLiteralExpression oldLiteral, int valueType,
            SourceLocation sloc, Object value) {
        literals.remove(oldLiteral.getPosition());
        DelegatingLiteralExpression dle = new DelegatingLiteralExpression(valueType, sloc, this,
                oldLiteral.getPosition(), oldLiteral.getCharsetHint());
        literals.add(oldLiteral.getPosition(), new Pair<DelegatingLiteralExpression, Object>(dle, value));
        return dle;
    }

    public ExpressionNode maybeNegate(Token tok, ExpressionNode rhs) {
        if (tok == null)
            return rhs;
        FunctionName fn = buildFunctionName(tok, true);
        ArrayList<ExpressionNode> parts = new ArrayList<ExpressionNode>(1);
        parts.add(rhs);
        return buildFunctionCall(fn, parts, null, null);
    }

    public ExpressionNode buildFunctionOrIdentifier(Name name, SetQuantifier sq, List<ExpressionNode> params,
            Token asterisk, Object loc, Token leftParen, Token rightParen) {
        SourceLocation sloc = SourceLocation.make(loc);
        if (sq == null && params == null && asterisk == null && leftParen == null && rightParen == null) {
            if (opts.isResolve())
                return buildColumnReference(name);
            return new NameInstance(name, sloc);
        }
        UnqualifiedName unq = name.getUnqualified();
        FunctionName fn = new FunctionName(unq, false);
        if (sq != null && !fn.isAggregate())
            throw new SchemaException(Pass.FIRST, "Illegal use of set quantifier");
        else if (asterisk != null && !fn.isCount())
            throw new SchemaException(Pass.FIRST, "Illegal use of '*' in a function");
        else if (asterisk != null && (params != null) && !params.isEmpty())
            throw new SchemaException(Pass.FIRST, "Mixed use of parameters and '*'");
        ArrayList<ExpressionNode> actuals = new ArrayList<ExpressionNode>();
        if (asterisk != null)
            actuals.add(new Wildcard(SourceLocation.make(asterisk)));
        else {
            if (params != null) {
                actuals.addAll(params);
            }
        }
        return buildFunctionCall(fn, actuals, sq, sloc);
    }

    public ExpressionNode maybeBuildExprAlias(ExpressionNode targ, Name alias, String stringAlias, Object tree) {
        if (alias == null && stringAlias == null)
            return targ;

        if (targ instanceof NameInstance && alias != null) {
            // Don't allow an alias to be the same name as the column
            // This will cause a stack overflow because the column will refer to an alias 
            // which refers back to the column.
            NameInstance colName = (NameInstance) targ;
            if (colName.getName().get().equals(alias.get())) {
                return targ;
            }
        }

        Alias a = null;
        if (alias != null)
            a = new NameAlias(castAlias(alias));
        else if (stringAlias != null)
            a = new StringLiteralAlias(PEStringUtils.dequote(stringAlias));

        return scope.buildExpressionAlias(targ, a, SourceLocation.make(tree));
    }

    public ProjectingStatement maybeBuildUnionStatement(List<ProjectingStatement> stmts, List<Boolean> unionOps) {
        ProjectingStatement singleReturn = null;
        if (stmts.size() == 1)
            singleReturn = stmts.get(0);
        else {
            // working from the back of the stmts list, build union clauses until
            // there is only one stmt left
            for (int op = unionOps.size() - 1; op > -1; op--) {
                ProjectingStatement rhs = stmts.get(op + 1);
                ProjectingStatement lhs = stmts.get(op);
                Boolean variety = unionOps.get(op);
                UnionStatement nus = new UnionStatement(lhs, rhs, variety.booleanValue(), lhs.getSourceLocation());
                stmts.remove(op + 1);
                stmts.remove(op);
                stmts.add(op, nus);
                nus.getDerivedInfo().addNestedStatements(Arrays.asList(new ProjectingStatement[] { lhs, rhs }));
            }
            singleReturn = stmts.get(0);
        }
        return singleReturn;
    }

    public Statement buildShowErrorsOrWarnings(String typeTag, LimitSpecification limit) {
        return new ShowErrorsWarningsStatement(typeTag, limit);
    }

    public Statement buildShowSitesStatus() {
        return new ShowSitesStatusStatement();
    }

    @SuppressWarnings("unchecked")
    public Statement buildShowStatus(Pair<ExpressionNode, ExpressionNode> likeOrWhere) {
        DirectShowStatusInformation ist = (DirectShowStatusInformation) Singletons
                .require(InformationSchemaService.class).lookupShowTable(new UnqualifiedName("status"));

        ExpressionNode likeExpr = (likeOrWhere == null ? null : likeOrWhere.getFirst());
        ExpressionNode whereExpr = (likeOrWhere == null ? null : likeOrWhere.getSecond());
        if (pc.getCapability() == Capability.PARSING_ONLY)
            return new SchemaQueryStatement(false, "status", Collections.EMPTY_LIST, true, null);
        return ist.buildShowPlural(pc, null, likeExpr, whereExpr, null);
    }

    @SuppressWarnings("unchecked")
    public Statement buildShowVariables(VariableScope ivs, Pair<ExpressionNode, ExpressionNode> likeOrWhere) {
        DirectShowVariablesTable ist = (DirectShowVariablesTable) Singletons.require(InformationSchemaService.class)
                .lookupShowTable(new UnqualifiedName("variables"));
        VariableScope vs = ivs;
        if (vs == null)
            vs = new VariableScope(VariableScopeKind.SESSION);
        ExpressionNode likeExpr = (likeOrWhere == null ? null : likeOrWhere.getFirst());
        ExpressionNode whereExpr = (likeOrWhere == null ? null : likeOrWhere.getSecond());
        if (pc.getCapability() == Capability.PARSING_ONLY)
            return new SchemaQueryStatement(false, "variables", Collections.EMPTY_LIST, true, null);
        return ist.buildShow(pc, vs, likeExpr, whereExpr);
    }

    public Statement buildShowColumns(String onInfoSchemaTable, List<Name> scoping,
            Pair<ExpressionNode, ExpressionNode> likeOrWhere, Token full) {
        if (pc.getCapability() == Capability.PARSING_ONLY && !pc.getCatalog().isPersistent())
            return new EmptyStatement("no catalog queries with transient execution engine");
        ShowSchemaBehavior ist = Singletons.require(InformationSchemaService.class)
                .lookupShowTable(new UnqualifiedName(onInfoSchemaTable));
        if (ist == null)
            throw new MigrationException("Need to add info schema table for " + onInfoSchemaTable);
        ExpressionNode likeExpr = (likeOrWhere == null ? null : likeOrWhere.getFirst());
        ExpressionNode whereExpr = (likeOrWhere == null ? null : likeOrWhere.getSecond());
        ShowOptions so = new ShowOptions();
        if (full != null)
            so.withFull();
        // break up the scoping value into parts if qualified
        List<Name> scopingParts = new ArrayList<Name>();
        for (Name name : scoping) {
            if (name instanceof QualifiedName) {
                QualifiedName qn = (QualifiedName) name;
                scopingParts.add(qn.getParts().get(1)); // table name
                scopingParts.add(qn.getParts().get(0)); // database
            } else {
                // assumed UnqualifiedName
                scopingParts.add(name);
            }
        }
        return ist.buildShowPlural(pc, scopingParts, likeExpr, whereExpr, so);
    }

    public List<AlterTableAction> buildEnableKeysAction() {
        return wrapAlterAction(new ChangeKeysStatusAction(true));
    }

    public List<AlterTableAction> buildDisableKeysAction() {
        return wrapAlterAction(new ChangeKeysStatusAction(false));
    }

    public Statement buildShowMultitenantLocks() {
        return new SchemaQueryStatement(true, "multitenant locks",
                Singletons.lookup(LockManager.class).showState());
    }

    public Statement buildMaintenanceQuery(String operation, String option, List<Name> tables) {
        MaintenanceCommandType maintenanceCommand = MaintenanceCommandType.valueOf(operation.toUpperCase());
        if (maintenanceCommand == null) {
            throw new SchemaException(Pass.FIRST, "Invalid table maintenance command: " + operation);
        }

        MaintenanceOptionType maintenanceOption = MaintenanceOptionType.NONE;
        if (option != null) {
            maintenanceOption = MaintenanceOptionType.valueOf(option.toUpperCase());
            if (maintenanceOption == null) {
                throw new SchemaException(Pass.FIRST, "Invalid table maintenance option: " + option);
            }
        }

        List<TableInstance> tblInstances = lookupTables(tables);
        // restrict maintenance commands to a single Persistent Group
        Name persistentGroup = tblInstances.get(0).getAbstractTable().getPersistentStorage(pc).getName();
        for (TableInstance tableInstance : tblInstances) {
            if (!persistentGroup.equals(tableInstance.getAbstractTable().getPersistentStorage(pc).getName())) {
                throw new SchemaException(Pass.FIRST,
                        "Table '" + tableInstance.getAbstractTable().getName(pc, pc.getValues()).get()
                                + "' in maintenance command is not in Persistent Group '" + persistentGroup.get()
                                + "'");
            }
        }

        if (MaintenanceCommandType.ANALYZE == maintenanceCommand)
            return new AnalyzeTablesStatement(maintenanceOption, tblInstances);

        return new TableMaintenanceStatement(maintenanceCommand, maintenanceOption, tblInstances);
    }

    private List<TableInstance> lookupTables(List<Name> tableNames) {
        List<TableInstance> tblInstances = new ArrayList<TableInstance>();

        TableResolver resolver = new TableResolver().withMTChecks();

        // figure out whether the target table(s) are known or not
        for (Name targetTableName : tableNames) {
            /*
            Database<?> db = null;
            UnqualifiedName candidateName = null;
            if (targetTableName.isQualified()) {
               QualifiedName qn = (QualifiedName) targetTableName;
               UnqualifiedName ofdb = qn.getNamespace();
               candidateName = qn.getUnqualified();
               db = pc.findPEDatabase(ofdb);
               if (db == null)
                  throw new SchemaException(Pass.FIRST, "No such database: " + ofdb);
            } else {
               db = pc.getCurrentDatabase();
               if (db == null)
                  throw new SchemaException(Pass.FIRST, "No current database");
               candidateName = (UnqualifiedName) targetTableName;
            }
            TableInstance ti = db.getSchema().buildInstance(pc, candidateName, lockInfo, true);
            */
            TableInstance ti = resolver.lookupTable(pc, targetTableName, lockInfo);
            if (ti == null)
                throw new SchemaException(Pass.FIRST, "No such table: " + targetTableName);

            tblInstances.add(ti.adapt(targetTableName.getUnqualified(), null, ti.getNode(), false));
        }
        return tblInstances;
    }

    public Statement buildAnalyzeKeys(List<Name> names) {
        return new AnalyzeKeysStatement(lookupTables(names));
    }

    @Override
    public Object getValue(IParameter p) {
        throw new SchemaException(Pass.SECOND, "Attempt to access parameter value from non value source");
    }

    @Override
    public Object getLiteral(IDelegatingLiteralExpression dle) {
        return literals.get(dle.getPosition()).getSecond();
    }

    @Override
    public Object getTenantID() {
        throw new SchemaException(Pass.SECOND, "Attempt to access tenant id value from non tenant id source");
    }

    @Override
    public Object getAutoincValue(IAutoIncrementLiteralExpression exp) {
        throw new SchemaException(Pass.SECOND, "Attempt to access autoinc value before planning");
    }

    public LoadDataInfileColOption buildLoadDataInfileColOption(String terminated, String enclosed,
            String escaped) {
        LoadDataInfileColOption option = new LoadDataInfileColOption(terminated, enclosed, escaped);
        return option;
    }

    public LoadDataInfileLineOption buildLoadDataInfileLineOption(String starting, String terminated) {
        LoadDataInfileLineOption option = new LoadDataInfileLineOption(starting, terminated);
        return option;
    }

    public Statement buildLoadDataInfileStatement(LoadDataInfileModifier modifier, boolean local, String fileName,
            boolean replace, boolean ignore, Name tempTableName, Name characterSet,
            LoadDataInfileColOption colOption, LoadDataInfileLineOption lineOption, ExpressionNode ignoredLines,
            List<Name> colOrVarList, List<ExpressionNode> updateExprs) {

        // validate this table is a qualified table or has a current database set
        Name dbName = null;
        Name tableName = tempTableName;
        if (tableName.isQualified()) {
            dbName = ((QualifiedName) tempTableName).getNamespace();
            tableName = ((QualifiedName) tempTableName).getUnqualified();
        }
        if (dbName == null) {
            dbName = (pc.getCurrentDatabase() == null) ? null : pc.getCurrentDatabase().getName();
        }
        if (dbName == null) {
            throw new SchemaException(Pass.FIRST, "No database has been specified.");
        }

        PEDatabase db = pc.findPEDatabase(dbName);
        if (db == null) {
            throw new SchemaException(Pass.FIRST, "No such database: '" + dbName + "'");
        }

        if (colOption != null) {
            if ((colOption.getEnclosed() != null) && (colOption.getEnclosed().length() > 1)) {
                throw new SchemaException(Pass.FIRST, "Fields enclosed by must be a single character.  Value is '"
                        + colOption.getEnclosed() + "'");
            }
            if ((colOption.getEscaped() != null) && (colOption.getEscaped().length() > 1)) {
                throw new SchemaException(Pass.FIRST,
                        "Fields escaped by must be a single character.  Value is '" + colOption.getEscaped() + "'");
            }
        }

        if (replace) {
            throw new SchemaException(Pass.FIRST, "REPLACE option is not currently supported.");
        }

        if (ignoredLines != null) {
            throw new SchemaException(Pass.FIRST, "Ignoring lines is not currently supported.");
        }

        if (updateExprs != null) {
            throw new SchemaException(Pass.FIRST, "SET option is not currently supported.");
        }

        LoadDataInfileStatement stmt = new LoadDataInfileStatement(modifier, local, PEStringUtils.dequote(fileName),
                replace, ignore, db, tableName, characterSet, colOption, lineOption,
                (ignoredLines == null) ? null : asIntegralLiteral(ignoredLines), colOrVarList, updateExprs);
        return stmt;
    }

    private static String[] unpackXmlParams(List<Pair<String, String>> params, String[] tags) {
        String[] out = new String[tags.length];
        for (Pair<String, String> p : params) {
            String t = p.getFirst();
            for (int i = 0; i < tags.length; i++) {
                if (tags[i].equals(t)) {
                    if (out[i] != null)
                        throw new SchemaException(Pass.SECOND, "Duplicate " + t + " specified");
                    if (p.getSecond() != null)
                        out[i] = PEStringUtils.dequote(p.getSecond());
                }
            }
        }
        return out;
    }

    // 0=xml, 1=match, 2=comment
    private String[] unpackTemplateParams(List<Pair<String, String>> params) {
        return unpackXmlParams(params, new String[] { "xml", "match", "comment" });
    }

    public Statement buildCreateTemplateStatement(Name name, boolean ine, List<Pair<String, String>> params) {
        PETemplate pet = pc.findTemplate(name);
        if (pet != null) {
            if (ine)
                return buildEmptyStatement("create existing template");
            throw new SchemaException(Pass.SECOND, "Template " + name.getSQL() + " already exists");
        }
        String[] p = unpackTemplateParams(params);
        if (p[0] == null)
            throw new SchemaException(Pass.SECOND, "Must specify template body for add template");
        pet = new PETemplate(pc, name.getUnqualified(), p[0], p[1], p[2]);
        return new PECreateStatement<PETemplate, PersistentTemplate>(pet, true, "TEMPLATE", false);
    }

    public Statement buildAlterTemplateStatement(Name name, List<Pair<String, String>> params) {
        PETemplate pet = pc.findTemplate(name);
        if (pet == null)
            throw new SchemaException(Pass.SECOND, "Template " + name.getSQL() + " does not exist");
        String[] p = unpackTemplateParams(params);
        return new PEAlterTemplateStatement(pet, p[0], p[1], p[2]);
    }

    public Statement buildDropTemplateStatement(Name name, boolean ifExists) {
        PETemplate pet = pc.findTemplate(name);
        if (pet == null) {
            if (!ifExists)
                throw new SchemaException(Pass.SECOND, "Template " + name.getSQL() + " does not exist");
            return new PEDropStatement<PETemplate, PersistentTemplate>(PETemplate.class, true, true, name,
                    "TEMPLATE");
        }
        return new PEDropStatement<PETemplate, PersistentTemplate>(PETemplate.class, ifExists, true, pet,
                "TEMPLATE");
    }

    // 0=xml, 1=comment, 2=enabled
    private String[] unpackRawPlanParams(List<Pair<String, String>> params) {
        return unpackXmlParams(params, new String[] { "xml", "comment", "enabled" });
    }

    public Statement buildCreateRawPlanStatement(Name name, boolean ine, Name dbName,
            List<Pair<String, String>> params) {
        PERawPlan perp = pc.findRawPlan(name);
        if (perp != null) {
            if (ine)
                return buildEmptyStatement("create existing plan");
            throw new SchemaException(Pass.SECOND, "Raw plan " + name + " already exists");
        }
        PEDatabase pdb = pc.findPEDatabase(dbName);
        if (pdb == null)
            throw new SchemaException(Pass.SECOND, "No such database: " + dbName.getSQL());
        // unpack the params
        String[] fields = unpackRawPlanParams(params);
        perp = new PERawPlan(pc, name.getUnqualified(), pdb, fields[0],
                (fields[2] == null ? true : Boolean.valueOf(fields[2])), fields[1]);
        return new PECreateRawPlanStatement(perp, ine);
    }

    public Statement buildDropRawPlanStatement(Name name, boolean ie) {
        PERawPlan perp = pc.findRawPlan(name);
        if (perp == null) {
            if (ie)
                return buildEmptyStatement("drop nonexistent raw plan");
            throw new SchemaException(Pass.SECOND, "Raw plan " + name + " does not exist");
        }
        return new PEDropRawPlanStatement(perp, ie);
    }

    public Statement buildAlterRawPlanStatement(Name name, List<Pair<String, String>> params) {
        PERawPlan perp = pc.findRawPlan(name);
        if (perp == null)
            throw new SchemaException(Pass.SECOND, "No such raw plan: " + name);
        // 0=xml, 1=comment, 2=enabled
        String[] fields = unpackRawPlanParams(params);
        return new PEAlterRawPlanStatement(perp, fields[1], (fields[2] == null ? null : Boolean.valueOf(fields[2])),
                fields[0]);
    }

    public Statement buildCreateViewStatementKern(Name viewName, ProjectingStatement viewDef,
            List<UnqualifiedName> columnNames, String checkOption, List<TableComponent<?>> colDefs) {
        return PECreateViewStatement.build(pc, viewName, viewDef, columnNames, checkOption, colDefs);
    }

    public Statement buildDropViewStatement(Name viewName, boolean ifExists) {
        if (pc.getCapability() == Capability.PARSING_ONLY)
            return new PEDropViewStatement(viewName, ifExists);
        UnqualifiedName tableName = viewName.getUnqualified();
        Database<?> ondb = findDatabase(viewName);
        if (ondb == null)
            throw new SchemaException(Pass.SECOND, "No such database: '" + viewName + "'");
        if (!(ondb instanceof PEDatabase))
            throw new SchemaException(Pass.SECOND, "Invalid database for drop view: '" + ondb.getName() + "'");
        PEAbstractTable<?> exists = pc.findTable(PEAbstractTable.getTableKey((PEDatabase) ondb, tableName));
        if (exists == null) {
            if (!ifExists)
                throw new SchemaException(Pass.SECOND, "No such view: " + viewName);
            return new PEDropViewStatement(viewName, ifExists);
        }
        return new PEDropViewStatement(exists.asView(), ifExists);
    }

    public Statement buildDropTriggerStatement(Name triggerName, boolean ifExists) {
        try {
            final Database<?> parentSchema = getDatabaseForObject(pc, triggerName);
            final PETrigger trigger = PETrigger.lookup(pc, triggerName, parentSchema);
            if (trigger != null) {
                return new PEDropTriggerStatement(trigger, ifExists);
            } else if (Boolean.TRUE.equals(ifExists)) {
                return new EmptyStatement("drop nonexistent trigger", StatementType.DROP_TRIGGER);
            }
        } catch (final SchemaException e) {
            final ErrorInfo error = e.getErrorInfo();
            if (!AvailableErrors.UNKNOWN_DATABASE.equals(error.getCode())) {
                throw e;
            }
        }

        throw new SchemaException(new ErrorInfo(AvailableErrors.TRG_DOES_NOT_EXIST));
    }

    public Statement buildPreparePreparedStatement(Name pstmtName, String stmt) {
        return new PreparePStmtStatement(pstmtName.getUnqualified(), PEStringUtils.dequote(stmt));
    }

    public Statement buildExecutePreparedStatement(Name pstmtName, List<VariableInstance> vars) {
        return new ExecutePStmtStatement(pstmtName.getUnqualified(), vars);
    }

    public Statement buildDeallocatePreparedStatement(Name pstmtName) {
        return new DeallocatePStmtStatement(pstmtName.getUnqualified());
    }

    public Statement buildShowPlanCache(boolean statsToo) {
        pc.getPolicyContext().checkRootPermission("show plan cache");
        return new ShowPlanCacheStatement(statsToo);
    }

    public Statement buildXACommit(UserXid xid, Object onePhase) {
        return new XACommitTransactionStatement(xid, onePhase != null);
    }

    public Statement buildXAEnd(UserXid xid, Object suspend, Object forMigrate) {
        if (forMigrate != null)
            throw new SchemaException(Pass.SECOND, "No support for xa end suspend for migrate");
        if (suspend != null)
            throw new SchemaException(Pass.SECOND, "No support for xa end suspend");
        return new XAEndTransactionStatement(xid);
    }

    public Statement buildXAPrepare(UserXid xid) {
        return new XAPrepareTransactionStatement(xid);
    }

    public Statement buildXARecover() {
        return new XARecoverTransactionStatement();
    }

    public Statement buildXARollback(UserXid xid) {
        return new XARollbackTransactionStatement(xid);
    }

    public Statement buildXAStart(UserXid xid, Object join, Object resume) {
        if (join != null)
            throw new SchemaException(Pass.SECOND, "No support for xa start join");
        if (resume != null)
            throw new SchemaException(Pass.SECOND, "No support for xa start resume");
        return new XABeginTransactionStatement(xid);
    }

    public UserXid buildXAXid(String gtrid, String bqual, String formatID) {
        // not quite right yet - we should convert based on the type of the literal
        return new UserXid(gtrid, bqual, formatID);
    }

    public void pushDML(String reason) {
        if (lockInfo == null && (opts != null && !opts.isIgnoreLocking())) {
            com.tesora.dve.lockmanager.LockType lt = StatementTraits.getLockType(reason);
            if (lt == null)
                throw new SchemaException(Pass.FIRST, "Missing lock type for dml reason " + reason);
            lockInfo = new LockInfo(lt, reason);
        }
    }

    public NativeCharSetCatalog getNativeCharSetCatalog() {
        if (supportedCharSets == null) {
            supportedCharSets = Singletons.require(NativeCharSetCatalog.class);
        }
        return supportedCharSets;
    }

    public NativeCollationCatalog getNativeCollationCatalog() {
        if (supportedCollations == null) {
            supportedCollations = Singletons.require(NativeCollationCatalog.class);
        }
        return supportedCollations;
    }

    public Statement buildCompoundStatement(List<Statement> stmts) {
        return new CompoundStatementList(null, stmts);
    }

    public Statement buildCreateTrigger(Name triggerName, boolean isBefore, TriggerEvent triggerType,
            PETable targetTable, Statement body, Token triggerToken) {
        PETrigger already = pc.findTrigger(PETrigger.buildCacheKey(triggerName.getUnquotedName().get(),
                (TableCacheKey) targetTable.getCacheKey()));
        if (already != null) {
            throw new SchemaException(new ErrorInfo(AvailableErrors.TRG_ALREADY_EXISTS));
        } else if (((triggerType == TriggerEvent.UPDATE) || (triggerType == TriggerEvent.DELETE))
                && !targetTable.hasUniqueKey(pc)) {
            final String targetTableName = targetTable.getName().getUnquotedName().get();
            throw new SchemaException(new ErrorInfo(AvailableErrors.NO_UNIQUE_KEY_ON_TRG_TARGET, targetTableName));
        }

        final EarlyTriggerTableCollector collector = TriggerTableInstance.collectTriggerTableReferences(body);
        if ((triggerType == TriggerEvent.INSERT) && collector.hasBeforeColumns()) {
            throw new SchemaException(new ErrorInfo(AvailableErrors.NO_SUCH_ROW_IN_TRG,
                    TriggerTime.BEFORE.getAlias().get(), TriggerEvent.INSERT.toString()));
        } else if ((triggerType == TriggerEvent.DELETE) && collector.hasAfterColumns()) {
            throw new SchemaException(new ErrorInfo(AvailableErrors.NO_SUCH_ROW_IN_TRG,
                    TriggerTime.AFTER.getAlias().get(), TriggerEvent.DELETE.toString()));
        }

        String origStmt = getInputSQL();
        int l = triggerToken.getCharPositionInLine();
        String rawSQL = origStmt.substring(l);

        String charset = KnownVariables.CHARACTER_SET_CLIENT.getSessionValue(pc.getConnection().getVariableSource())
                .getName();
        String collation = KnownVariables.COLLATION_CONNECTION
                .getSessionValue(pc.getConnection().getVariableSource());
        String collationDB = KnownVariables.COLLATION_DATABASE
                .getSessionValue(pc.getConnection().getVariableSource());
        SQLMode sqlMode = KnownVariables.SQL_MODE.getSessionValue(pc.getConnection().getVariableSource());
        SQLMode globalMode = KnownVariables.SQL_MODE.getGlobalValue(pc.getConnection().getVariableSource());
        if (globalMode.equals(sqlMode))
            sqlMode = null;

        PETrigger trig = new PETrigger(pc, triggerName, targetTable, body, triggerType, null /* PEUser user */,
                collation, charset, collationDB, isBefore ? TriggerTime.BEFORE : TriggerTime.AFTER, sqlMode,
                rawSQL);
        popScope();
        opts = opts.setResolve();

        return new PECreateTriggerStatement(trig);
    }

    public Statement addViewTriggerFields(Statement in, boolean createOrReplace, String algo, UserScope definer,
            String security) {
        PECreateStatement pect = (PECreateStatement) in;
        Persistable pt = pect.getCreated();

        PEUser user = null;
        if (definer == null || pc.getOptions().isIgnoreMissingUser())
            user = pc.getCurrentUser().get(pc);
        else {
            user = pc.findUser(definer.getUserName(), definer.getScope());
            if (user == null)
                // apparently it is legal to specify a user that doesn't exist.  in that case, just use the current user
                user = pc.getCurrentUser().get(pc);
            // throw new SchemaException(Pass.SECOND, "No such user: " + definer.getSQL());
        }

        if (pt instanceof PEView) {
            PECreateViewStatement cvs = (PECreateViewStatement) pect;
            PEView pev = (PEView) pt;
            pev.setUser(pc, user, false);

            PEDatabase theDB = cvs.getDatabase(pc);

            PEAbstractTable<?> existing = pc.findTable(PEAbstractTable.getTableKey(theDB, pev.getName()));
            if (existing != null && !createOrReplace) {
                throw new SchemaException(Pass.SECOND, "View " + pev.getName() + " already exists");
            }

            if (createOrReplace) {
                ((PECreateViewStatement) pect).setCreateOrReplace();
            }

            String algorithm = (algo == null ? "UNDEFINED" : algo);
            String sec = (security == null ? "DEFINER" : security);

            pev.setAlgorithm(algorithm);
            pev.setSecurity(sec);

        } else {
            PETrigger trig = (PETrigger) pt;
            trig.setUser(pc, user);
            if (createOrReplace || algo != null || security != null) {
                // TODO:
                // come back and put in the right error message
                throw new SchemaException(Pass.FIRST, "Illegal syntax");
            }
        }
        return pect;
    }

    public PETable pushTriggerTable(Name n) {
        TableInstance targTab = basicResolver.lookupTable(pc, n, lockInfo);
        PETable theTable = targTab.getAbstractTable().asTable();
        long node = pc.getNextTable();
        TriggerTableInstance before = new TriggerTableInstance(theTable, node, TriggerTime.BEFORE);
        TriggerTableInstance after = new TriggerTableInstance(theTable, node, TriggerTime.AFTER);
        pushScope();
        scope.insertTable(before);
        scope.insertTable(after);
        opts = opts.clearSetting(Option.RESOLVE);
        return theTable;
    }

    public PETable pushTriggerTable(PETable tab) {
        // we always use the same node number for trigger tables so that
        // we can correctly plan when there are both before and after triggers
        long node = -1;
        TriggerTableInstance before = new TriggerTableInstance(tab, node, TriggerTime.BEFORE);
        TriggerTableInstance after = new TriggerTableInstance(tab, node, TriggerTime.AFTER);
        pushScope();
        scope.insertTable(before);
        scope.insertTable(after);
        return tab;
    }

    public Statement buildCaseStatement(ExpressionNode testExpr, List<StatementWhenClause> whenClauses,
            Statement elseStatement) {
        return new CaseStatement(null, testExpr, whenClauses, elseStatement);
    }

    public StatementWhenClause buildStatementWhenClause(ExpressionNode testExpr, Statement result) {
        return new StatementWhenClause(testExpr, result, null);

    }
}