options {
	STATIC = false;
	ERROR_REPORTING = true;
	JAVA_UNICODE_ESCAPE = true;
	UNICODE_INPUT = false;
	IGNORE_CASE = true;
	DEBUG_PARSER = false;
	DEBUG_LOOKAHEAD = false;
    DEBUG_TOKEN_MANAGER = false;
}

PARSER_BEGIN(SQLParser)

package org.teiid.query.parser;

import java.math.BigInteger;
import java.util.*;
import org.teiid.core.types.*;
import org.teiid.core.util.StringUtil;
import org.teiid.query.QueryPlugin;
import org.teiid.query.sql.LanguageObject;
import org.teiid.query.sql.lang.*;
import org.teiid.query.sql.proc.*;
import org.teiid.query.sql.symbol.*;
import org.teiid.metadata.*;
import org.teiid.metadata.BaseColumn.NullType;
import org.teiid.language.Like.MatchMode;
import org.teiid.translator.*;

/** 
 * <p>The SQLParser is a JavaCC-generated parser that reads a SQL string and produces a 
 * Query object.  The SQLParser.java file is generated by JavaCC from the SQLParser.jj
 * file.  <B>WARNING:</B> DO NOT MODIFY the SQLParser.java file as it will be 
 * regenerated from the .jj file and your changes will be lost!</p>
 */
@SuppressWarnings({"nls", "unused"})
public class SQLParser extends SQLParserUtil {

  private void jj_add_error_token ( int kind, int pos) {
	    if (pos >= 100) return;
	    if (pos == jj_endpos + 1) {
	      jj_lasttokens[jj_endpos++] = kind;
	    } else if (jj_endpos != 0) {
	      jj_expentry = new int[jj_endpos];
	      for (int i = 0; i < jj_endpos; i++) {
	        jj_expentry[i] = jj_lasttokens[i];
	      }
	      boolean exists = false;
	      for (java.util.Iterator e = jj_expentries.iterator(); e.hasNext();) {
	        int[] oldentry = (int[])(e.next());
	        if (oldentry.length == jj_expentry.length) {
	          exists = true;
	          for (int i = 0; i < jj_expentry.length; i++) {
	            if (oldentry[i] != jj_expentry[i]) {
	              exists = false;
	              break;
	            }
	          }
	          if (exists) break;
	        }
	      }
	      if (!exists) jj_expentries.add(jj_expentry);
	      if (pos != 0) jj_lasttokens[(jj_endpos = pos) - 1] = kind;
	    }
	  }

} // end class

PARSER_END(SQLParser)

TOKEN_MGR_DECLS : 
{
    int commentNestingDepth; 
} 

SKIP :
{
	" " | "\t" | "\n" | "\r"
}

MORE :
{
  "/*" { commentNestingDepth = 1 ; } : IN_MULTI_LINE_COMMENT
}

<IN_MULTI_LINE_COMMENT>
MORE : 
{  
  "/*" { commentNestingDepth += 1 ; } 
} 

<IN_MULTI_LINE_COMMENT>
SPECIAL_TOKEN : 
{  
  "*/" { 
    commentNestingDepth -= 1;
    SwitchTo( commentNestingDepth==0 ? DEFAULT : IN_MULTI_LINE_COMMENT ) ; 
  }
} 

<IN_MULTI_LINE_COMMENT>
MORE :
{
  < ~[] >
}

SPECIAL_TOKEN:
{
   <LINE_COMMENT: "--"(~["\r","\n"])*>
}

TOKEN: /* Data types */
{
	<STRING: "string">
|   <VARBINARY: "varbinary">
|   <VARCHAR: "varchar">
|	<BOOLEAN: "boolean">
|	<BYTE: "byte">
|   <TINYINT: "tinyint">
|	<SHORT: "short">
|   <SMALLINT: "smallint">
|	<CHAR: "char">
|	<INTEGER: "integer">
|	<LONG: "long">
|   <BIGINT: "bigint">
|	<BIGINTEGER: "biginteger">
|	<FLOAT: "float">
|   <REAL: "real"> 
|	<DOUBLE: "double">
|	<BIGDECIMAL: "bigdecimal">
|   <DECIMAL: "decimal">
|	<DATE: "date">
|	<TIME: "time">
|	<TIMESTAMP: "timestamp">
|	<OBJECT: "object">
|	<BLOB: "blob">
|	<CLOB: "clob">
|	<XML: "xml">
}


TOKEN: /* Functions with special syntax */
{
	<CAST: "cast">
|	<CONVERT: "convert">
}

TOKEN : /* Reserved words */
{
    <ADD: "add">
|   <ALL: "all">
|   <ALTER: "alter">
|   <AND: "and">
|   <ANY: "any">
|   <ARRAY: "array">
|   <ARRAY_AGG: "array_agg">
|   <AS: "as">
|   <ASC: "asc">
|   <ATOMIC: "atomic">
|   <AUTORIZATION: "authorization">
|   <BEGIN: "begin">
|   <BETWEEN: "between">
|   <BINARY: "binary">
|   <BOTH: "both">
|   <BREAK: "break">
|   <BY: "by">
|   <CALL: "call">
|   <CALLED: "called">
|   <CASCADED: "cascaded">
|   <CASE: "case">
|   <CHARACTER: "character">
|   <CHECK: "check">
|   <CLOSE: "close">
|   <COLLATE: "collate">
|   <COLUMN: "column">
|   <COMMIT: "commit">
|   <CONNECT: "connect">
|   <CONSTRAINT: "constraint">
|   <CONTINUE: "continue">
|   <CORRESPONDING: "corresponding">
|   <CURRENT_DATE: "current_date">
|   <CURRENT_TIME: "current_time">
|   <CURRENT_TIMESTAMP: "current_timestamp">
|   <CURRENT_USER: "current_user">
|   <CREATE: "create">
|   <CRITERIA: "criteria">
|   <CROSS: "cross">
|   <CURSOR: "cursor">
|   <DAY: "day">
|   <DEALLOCATE: "deallocate">
|   <DEFAULT_KEYWORD: "default">
|   <DECLARE: "declare">
|   <DELETE: "delete">
|   <DESC: "desc">
|   <DESCRIBE: "describe">
|   <DETERMINISTIC: "deterministic">
|   <DISCONNECT: "disconnect">
|   <DISTINCT: "distinct">
|   <DROP: "drop">
|	<EACH: "each">
|   <ELSE: "else">
|   <END: "end">
|   <ERROR: "error">
|   <ESCAPE: "escape">
|   <EXCEPT: "except">
|   <EXEC: "exec">
|   <EXECUTE: "execute">
|   <EXTERNAL: "external">
|   <EXISTS: "exists">
|   <FALSE: "false">
|   <FETCH: "fetch">
|   <FILTER: "filter">
|   <FOR: "for">
|   <FOREIGN: "foreign">
|   <FROM: "from">
|   <FULL: "full">
|   <FUNCTION: "function">
|   <GET: "get">
|   <GLOBAL: "global">
|   <GRANT: "grant">
|   <GROUP: "group">
|   <HAS: "has">
|   <HAVING: "having">
|   <HOLD: "hold">
|   <HOUR: "hour">
|   <IF: "if">
|   <IDENTITY: "identity">
|   <IMMEDIATE: "immediate">
|   <IN: "in">
|   <INDICATOR: "indicator">
|   <INNER: "inner">
|   <INPUT: "input">
|   <INOUT: "inout">
|   <INSENSITIVE: "insensitive">
|   <INSERT: "insert">
|   <INTERSECT: "intersect">
|   <INTERVAL: "interval">
|   <INTO: "into">
|   <IS: "is">
|   <ISOLATION: "isolation">
|   <JOIN: "join">
|   <LEFT: "left">
|   <LANGUAGE: "language">
|   <LARGE: "large">
|   <LEADING: "leading">
|   <LEAVE: "leave">
|   <LIKE: "like">
|   <LIKE_REGEX: "like_regex">
|   <LIMIT: "limit">
|   <LOCAL: "local">
|   <LOOP: "loop">
|   <MAKEDEP: "makedep">
|   <MAKENOTDEP: "makenotdep">
|   <MATCH: "match">
|   <MERGE: "merge">
|   <METHOD: "method">
|   <MINUTE: "minute">
|   <MODIFIES: "modifies">
|   <MODULE: "module">
|   <MONTH: "month">
|   <NATURAL: "natural">
|   <NEW: "new">
|	<NOCACHE: "nocache">
|   <NO: "no">
|   <NONE: "none">
|   <NOT: "not">
|   <NULL: "null">
|   <OF: "of">
|   <OFFSET: "offset">
|   <OLD: "old">
|   <ON: "on">
|   <ONLY: "only">
|   <OPEN: "open">
|   <OPTION: "option">
|   <OPTIONS: "options">
|   <OR: "or">
|   <ORDER: "order">
|   <OUT: "out">
|   <OUTER: "outer">
|   <OUTPUT: "output">
|   <OVER: "over">
|   <OVERLAPS: "OVERLAPS">
|   <PARAMETER: "parameter">
|   <PARTITION: "partition">
|   <PRECISION: "precision">
|   <PREPARE: "prepare">
|   <PRIMARY: "primary">
|   <PROCEDURE: "procedure">
|   <RANGE: "range">
|   <READS: "reads">
|   <RECURSIVE: "recursive">
|   <REFERENCES: "REFERENCES">
|   <REFERENCING: "REFERENCING">
|   <RETURN: "return">
|   <RETURNS: "returns">
|   <REVOKE: "REVOKE">
|   <RIGHT: "right">
|   <ROLLBACK: "ROLLBACK">
|   <ROLLUP: "ROLLUP">
|   <ROW: "row">
|   <ROWS: "rows">
|   <SAVEPOINT: "savepoint">
|   <SCROLL: "scroll">
|   <SEARCH: "search">
|   <SECOND: "second">
|   <SELECT: "select">
|   <SENSITIVE: "sensitive">
|   <SESSION_USER: "session_user">
|   <SET: "set">
|   <SIMILAR: "similar">
|   <SPECIFIC: "specific">
|   <SOME: "some">
|   <SQL: "sql">
|   <SQLEXCEPTION: "sqlexception">
|   <SQLSTATE: "sqlstate">
|   <SQLWARNING: "sqlwarning">
|   <START: "start">
|   <STATIC: "static">
|   <SYSTEM: "system">
|   <SYSTEM_USER: "system_user">
|   <TABLE: "table">
|   <TEMPORARY: "temporary">
|   <THEN: "then">
|   <TIMEZONE_HOUR: "timezone_hour">
|   <TIMEZONE_MINUTE: "timezone_minute">
|   <TO: "to">
|   <TRAILING: "trailing">
|   <TRANSLATE: "translate">
|   <TRIGGER: "trigger">
|   <TRUE: "true">
|   <UNION: "union">
|   <UNIQUE: "unique">
|   <UNKNOWN: "unknown">
|   <USER: "user">
|   <UPDATE: "update">
|   <USING: "using">
|   <VALUE: "value">
|   <VALUES: "values">
|   <VIRTUAL: "virtual">
|   <WHEN: "when">
|   <WHENEVER: "whenever">
|   <WHERE: "where">
|   <WITH: "with">
|   <WHILE: "while">
|   <WINDOW: "window">
|   <WITHIN: "within">
|   <WITHOUT: "without">
|   <YEAR: "year">

|   <ALLOCATE: "allocate">
|   <ARE: "are">
|   <ASENSITIVE: "asensitive">
|   <ASYMETRIC: "asymetric">
|   <CYCLE: "cycle">
|   <DEC: "dec">
|   <DEREF: "deref">
|   <DYNAMIC: "dynamic">
|   <ELEMENT: "element">
|   <FREE: "free">
|   <INT: "int">
|   <LATERAL: "lateral">
|   <LOCALTIME: "localtime">
|   <LOCALTIMESTAMP: "localtimestamp">
|   <MEMBER: "member">
|   <MULTISET: "multiset">
|   <NATIONAL: "national">
|   <NCHAR: "nchar">
|   <NCLOB: "nclob">
|   <NUMERIC: "numeric">
|   <RELEASE: "release">
|   <SPECIFICTYPE: "specifictype">
|   <SYMETRIC: "symetric">
|   <SUBMULTILIST: "submultilist">
|   <TRANSLATION: "translation">
|   <TREAT: "treat">
|   <VARYING: "varying">
}

TOKEN : /* SQL/XML Reserved words */
{
    <XMLAGG: "xmlagg">
|   <XMLATTRIBUTES: "xmlattributes">
|   <XMLBINARY: "xmlbinary">
|   <XMLCAST: "xmlcast">
|   <XMLCONCAT: "xmlconcat">
|   <XMLCOMMENT: "xmlcomment">
|   <XMLDOCUMENT: "xmldocument">
|   <XMLELEMENT: "xmlelement">
|   <XMLEXISTS: "xmlexists">
|   <XMLFOREST: "xmlforest">
|   <XMLITERATE: "xmliterate">
|   <XMLNAMESPACES: "xmlnamespaces">
|   <XMLPARSE: "xmlparse">
|   <XMLPI: "xmlpi">
|   <XMLQUERY: "xmlquery">
|   <XMLSERIALIZE: "xmlserialize">
|   <XMLTABLE: "xmltable">
|   <XMLTEXT: "xmltext">
|   <XMLVALIDATE: "xmlvalidate">
}

TOKEN : /* SQL/MED Reserved words */
{
    <DATALINK: "datalink">
|   <DLNEWCOPY: "dlnewcopy">
|   <DLPREVIOUSCOPY: "dlpreviouscopy">
|   <DLURLCOMPLETE: "dlurlcomplete">
|   <DLURLCOMPLETEWRITE: "dlurlcompletewrite">
|   <DLURLCOMPLETEONLY: "dlurlcompleteonly">
|   <DLURLPATH: "dlurlpath">
|   <DLURLPATHWRITE: "dlurlpathwrite">
|   <DLURLPATHONLY: "dlurlpathonly">
|   <DLURLSCHEME: "dlurlscheme">
|   <DLURLSERVER: "dlurlserver">
|   <DLVALUE: "dlvalue">
|   <IMPORT: "import">
}

TOKEN : /* NonReserved words */
{
    <INSTEAD: "instead">
|   <ENABLED: "enabled">
|   <DISABLED: "disabled">
|   <VIEW: "view">
|   <KEY: "key">
|   <SERIAL: "serial">
|   <TEXTAGG: "textagg">
|   <COUNT: "count">
|   <ROW_NUMBER: "row_number">
|   <RANK: "rank">
|   <DENSE_RANK: "dense_rank">
|   <SUM: "sum">
|   <AVG: "avg">
|   <MIN: "min">
|   <MAX: "max">
|   <EVERY: "every">
|   <STDDEV_POP: "stddev_pop">
|   <STDDEV_SAMP: "stddev_samp">
|   <VAR_SAMP: "var_samp">
|   <VAR_POP: "var_pop">
|   <DOCUMENT: "document">
|   <CONTENT: "content">
|   <TRIM: "trim">
|   <EMPTY: "empty">
|   <ORDINALITY: "ordinality">
|   <PATH: "path">
|   <FIRST: "first">
|   <LAST: "last">
|   <NEXT: "next">
|   <SUBSTRING: "substring">
|   <EXTRACT: "extract">
|   <TO_CHARS: "to_chars">
|   <TO_BYTES: "to_bytes">
|   <TIMESTAMPADD: "timestampadd">
|   <TIMESTAMPDIFF: "timestampdiff">
|   <QUERYSTRING: "querystring">
|   <NAMESPACE: "namespace">
|   <RESULT: "result">
|   <INDEX: "index">
|   <ACCESSPATTERN: "accesspattern">
|   <AUTO_INCREMENT: "auto_increment">
|   <WELLFORMED: "wellformed">
|   <SQL_TSI_FRAC_SECOND: "sql_tsi_frac_second">
|   <SQL_TSI_SECOND: "sql_tsi_second">
|   <SQL_TSI_MINUTE: "sql_tsi_minute">
|   <SQL_TSI_HOUR: "sql_tsi_hour">
|   <SQL_TSI_DAY: "sql_tsi_day">
|   <SQL_TSI_WEEK: "sql_tsi_week">
|   <SQL_TSI_MONTH: "sql_tsi_month">
|   <SQL_TSI_QUARTER: "sql_tsi_quarter">
|   <SQL_TSI_YEAR: "sql_tsi_year">
|   <TEXTTABLE: "texttable">
|   <ARRAYTABLE: "arraytable">
|   <SELECTOR: "selector">
|   <SKIP_KEYWORD: "skip">
|   <WIDTH: "width">
|   <PASSING: "passing">
|   <NAME: "name">
|   <ENCODING: "encoding">
|   <COLUMNS: "columns">
|   <DELIMITER: "delimiter">
|   <QUOTE: "quote">
|   <HEADER: "header">
|   <NULLS: "nulls">
|   <OBJECTTABLE: "objecttable">
|   <VERSION: "version">
|   <INCLUDING: "including">
|   <EXCLUDING: "excluding">
|   <XMLDECLARATION: "xmldeclaration">
|   <VARIADIC: "variadic">
|   <EXCEPTION: "exception">
|   <RAISE: "raise">
|   <CHAIN: "chain">
|   <JSONARRAY_AGG: "jsonarray_agg">
|   <JSONOBJECT: "jsonobject">
|   <PRESERVE: "preserve">
|   <GEOMETRY: "geometry">

}
/*
name=all in group identifier
*/
TOKEN : { < ALL_IN_GROUP: <ID> <PERIOD> <STAR> > }
/*
name=identifier
*/
TOKEN : { < ID: <QUOTED_ID> (<PERIOD> <QUOTED_ID>)* > }
TOKEN : { < #QUOTED_ID: <ID_PART> | ("\"" (("\"\"") | ~["\""] )+ "\"") >  }     
TOKEN : { < #ID_PART: (("@" | "#" | <LETTER>) (<LETTER> | "_" | <DIGIT>)*) > }
/*
name=escaped function
*/
TOKEN : { < ESCAPEDFUNCTION: "{" "fn" > }
/*
name=escaped join
*/
TOKEN : { < ESCAPEDJOIN: "{" "oj" > }
/*
name=escaped type
*/
TOKEN : { < ESCAPEDTYPE: "{" ("d" | "t" | "ts" | "b") > }
/*
name=decimal numeric literal
*/
TOKEN : { < DECIMALVAL: (<DIGIT>)* <PERIOD> <UNSIGNEDINTEGER> > }
/*
name=approximate numeric literal
*/
TOKEN : { < FLOATVAL: <DIGIT> <PERIOD> <UNSIGNEDINTEGER> 
				["e", "E"] (<PLUS>|<MINUS>)? <UNSIGNEDINTEGER> > }
/*
name=string literal
*/				
TOKEN : { < STRINGVAL: (("N"|"E")? "'" ( ("''") | ~["'"] )* "'") > }
TOKEN : { < #LETTER: (["a"-"z","A"-"Z"] | ["\u0153"-"\ufffd"]) > }
TOKEN : { < #DIGIT: ["0"-"9"] > }
/*
name=unsigned integer literal
*/
TOKEN : { < UNSIGNEDINTEGER: (<DIGIT>)+ > }
/*
name=binary string literal
*/
TOKEN : { < BINARYSTRINGVAL: ("X"|"x" "'" ( <HEXIT><HEXIT> )+ "'") > }
TOKEN : { < #HEXIT: (["a"-"f","A"-"F"] | <DIGIT> ) > }

TOKEN :
{
	<COMMA: ",">
|	<PERIOD: ".">
|	<LPAREN: "(">
|	<RPAREN: ")">
|   <LBRACE: "{">
|   <RBRACE: "}">
|   <LSBRACE: "[">
|   <RSBRACE: "]">
|	<EQ: "=">
|	<NE: "<>">
|	<NE2: "!=">
|	<LT: "<">
|	<LE: "<=">
|	<GT: ">">
|	<GE: ">=">
|	<STAR: "*">
|	<SLASH: "/">
|	<PLUS: "+">
|	<MINUS: "-">
|   <QMARK: "?">
|   <DOLLAR: "$">
|   <SEMICOLON: ";">
|   <COLON: ":">
|	<CONCAT_OP: "||">
}

/*
name=string
description=A string literal value.  Use '' to escape ' in the string.  
example={code:sql}'a string'{code}\n{code:sql}'it''s a string'{code}
*/
String stringVal() :
{
	Token t = null;	
}
{
  	t = <STRINGVAL>
  	{
  		return normalizeStringLiteral(t.image);
  	}
}

/*
name=non-reserved identifier
description=Allows non-reserved keywords to be parsed as identifiers  
example=SELECT *COUNT* FROM ...
*/
Token nonReserved() :
{
}
{
    (<INSTEAD>|<VIEW>|<ENABLED>|<DISABLED>|<KEY>|<SERIAL>|<TEXTAGG>|<COUNT>|<ROW_NUMBER>|<RANK>|<DENSE_RANK>|<SUM>|<AVG>|<MIN>|<MAX>|<EVERY>|<STDDEV_POP>
     |<STDDEV_SAMP>|<VAR_SAMP>|<VAR_POP>|<DOCUMENT>|<CONTENT>|<TRIM>|<EMPTY>|<ORDINALITY>|<PATH>|<FIRST>|<LAST>|<NEXT>|<SUBSTRING>|<EXTRACT>|<TO_CHARS>
     |<TO_BYTES>|<TIMESTAMPADD>|<TIMESTAMPDIFF>|<QUERYSTRING>|<NAMESPACE>|<RESULT>|<INDEX>|<ACCESSPATTERN>|<AUTO_INCREMENT>|<WELLFORMED>|<SQL_TSI_FRAC_SECOND>
     |<SQL_TSI_SECOND>|<SQL_TSI_MINUTE>|<SQL_TSI_HOUR>|<SQL_TSI_DAY>|<SQL_TSI_WEEK>|<SQL_TSI_MONTH>|<SQL_TSI_QUARTER>|<SQL_TSI_YEAR>|<TEXTTABLE>|<ARRAYTABLE>
     |<SELECTOR>|<SKIP_KEYWORD>|<WIDTH>|<PASSING>|<NAME>|<ENCODING>|<COLUMNS>|<DELIMITER>|<QUOTE>|<HEADER>|<NULLS>|<OBJECTTABLE>
     |<VERSION>|<INCLUDING>|<EXCLUDING>|<XMLDECLARATION>|<VARIADIC>|<RAISE>|<EXCEPTION>|<CHAIN>|<JSONARRAY_AGG>|<JSONOBJECT>|<PRESERVE>)
    {
    	return getToken(0);
    }	
}

/*
name=identifier
description=Partial or full name of a single entity.
example={code:sql}tbl.col{code}\n{code:sql}"tbl"."col"{code}
*/
String id(Boolean validate) :
{
}
{
  	(<ID>|nonReserved())
  	{
  		String id = normalizeId(getToken(0).image);
  		if (validate != null) {
  			return validateName(id, validate);
  		}
  		return id;
  	}
}

/* TODO: createProcedure should not be here, but is due to all of the legacy tests
unused=true
*/
Command command(ParseInfo info) :
{
	Command command = null;
}
{
	 (LOOKAHEAD(2) command = createProcedure(info) |
	  command = userCommand(info) |
	  command = callableStatement(info)
	)
	[<SEMICOLON>]
	<EOF>
	{
		return command;
	}
}

/*
unused=true
*/
Command designerCommand(ParseInfo info) :
{
	Command command = null;
}
{
	(LOOKAHEAD (2) command = createProcedure(info) |
	 command = forEachRowTriggerAction(info) |
	 command = userCommand(info)	  
	)
	[<SEMICOLON>]
	<EOF>
	{
		return command;
	}
}

/*
name=create trigger
description=Creates a trigger action on the given target.
example={code:sql}CREATE TRIGGER ON vw INSTEAD OF INSERT AS FOR EACH ROW BEGIN ATOMIC ... END{code}
*/
Command createTrigger(ParseInfo info) :
{
  	String target = null;
  	TriggerAction triggerAction = null;
  	Token event = null;
  	
}
{
	<CREATE> <TRIGGER> <ON> target = id(null) <INSTEAD> <OF> 
	(event = <INSERT> | event = <UPDATE> | event = <DELETE>) 
	<AS> triggerAction = forEachRowTriggerAction(info)
	{
		AlterTrigger alterTrigger = new AlterTrigger();
		alterTrigger.setTarget(new GroupSymbol(target));
		alterTrigger.setDefinition(triggerAction);
		alterTrigger.setEvent(Table.TriggerEvent.valueOf(event.image.toUpperCase()));
		alterTrigger.setCreate(true);
		return alterTrigger;				
	}
}

/*
name=alter
description=Alter the given target.
example={code:sql}ALTER VIEW vw AS SELECT col FROM tbl{code}
*/
Command alter(ParseInfo info) :
{
  	String target = null;
  	QueryCommand command = null;
  	Statement stmt = null;
  	TriggerAction triggerAction = null;
  	Token comment = null;
  	Token event = null;
  	Boolean enabled = null;
}
{
	<ALTER>
	(
	 	(<VIEW> target = id(null) <AS> { comment = getToken(1).specialToken; } command = queryExpression(info)) 
	 	{
	 	    if (comment != null) {
		 	    command.setCacheHint(getQueryCacheOption(comment.image));
	 	    }
	 		AlterView alterView = new AlterView();
	 		alterView.setTarget(new GroupSymbol(target));
	 		alterView.setDefinition(command);
	 		return alterView;	
	 	}
		| (<PROCEDURE> target = id(null) <AS> { comment = getToken(1).specialToken; } stmt = statement(info)) 
		{
			CreateProcedureCommand cup = new CreateProcedureCommand(asBlock(stmt));
			if (comment != null) {
		 	    cup.setCacheHint(getQueryCacheOption(comment.image));
	 	    }
			AlterProcedure alterProc = new AlterProcedure();
			alterProc.setTarget(new GroupSymbol(target));
			alterProc.setDefinition(cup);
			return alterProc;
		}
		| (<TRIGGER> <ON> target = id(null) <INSTEAD> <OF> 
		  (event = <INSERT> | event = <UPDATE> | event = <DELETE>) 
		  ((<AS> triggerAction = forEachRowTriggerAction(info))
		  |<ENABLED> {enabled=true;}| <DISABLED> {enabled=false;}))
		{
			AlterTrigger alterTrigger = new AlterTrigger();
			alterTrigger.setTarget(new GroupSymbol(target));
			alterTrigger.setDefinition(triggerAction);
			alterTrigger.setEvent(Table.TriggerEvent.valueOf(event.image.toUpperCase()));
			if (enabled != null) {
				alterTrigger.setEnabled(enabled);
			}
			return alterTrigger;				
		}
	)
}

/*
name=for each row trigger action
description=Defines an action to perform on each row.
example={code:sql}FOR EACH ROW BEGIN ATOMIC ... END{code}
*/
TriggerAction forEachRowTriggerAction(ParseInfo info) :
{
	Block b = new Block();
	b.setAtomic(true);
	Statement stmt = null;
}
{
	<FOR> <EACH> <ROW>
	(
	 LOOKAHEAD(1) (<BEGIN> [<ATOMIC>]
      (
	    stmt = statement(info)
	    {          
    	    b.addStatement(stmt);            	                	    
	    }
      )*
      <END> 
     )
     |
     stmt = statement(info)
     {
       b = asBlock(stmt);
       b.setAtomic(true);
     }
    ) 
	{
		return new TriggerAction(b);
	}
}

/*
name=directly executable statement
description=A statement that can be executed at runtime.
example={code:sql}SELECT * FROM tbl{code}
index=true
*/
Command userCommand(ParseInfo info) :
{
	Command command = null;
	SourceHint sourceHint = null;
	Block block = null;
}
{
	(command = queryExpression(info) |
	command = storedProcedure(info, 1) |
	{ sourceHint = getSourceHint(this); } command = insert(info) |
	{ sourceHint = getSourceHint(this); } command = update(info) |
	{ sourceHint = getSourceHint(this); } command = delete(info) |
	command = dropTable(info) |
	LOOKAHEAD(2) command = createTempTable(info) |
	LOOKAHEAD(2) command = createForeignTempTable(info) |
	command = alter(info) |
	command = createTrigger(info) |
	block = compoundStatement(info) {command = new CreateProcedureCommand(block);}
	)
	{
		setSourceHint(sourceHint, command);
		return command;
	}
}

/*
name=drop table
description=Creates a trigger action on the given target.
example={code:sql}CREATE TRIGGER ON vw INSTEAD OF INSERT AS FOR EACH ROW BEGIN ATOMIC ... END{code}
*/
Command dropTable(ParseInfo info) :
{
	Drop drop = new Drop();
	String table = null;
}
{
	<DROP> <TABLE> 
	table = id(null)
	{
		drop.setTable(new GroupSymbol(table));
		return drop;
	}
}
	
/*
name=create temporary table
description=Creates a temporary table.
example={code:sql}CREATE LOCAL TEMPORARY TABLE tmp (col integer){code}
*/
Command createTempTable(ParseInfo info) :
{
	Create create = new Create();
	String table = null;
	String pkId = null;
	Column col = null;
	List<ElementSymbol> pk = null;
}
{
	<CREATE> [<LOCAL>] <TEMPORARY> <TABLE> 
	table = id(null)
	<LPAREN>
	{
		create.setTable(new GroupSymbol(table));
	}
    col = tableElement(info)
    {
      create.getColumns().add(col);
    }
    (LOOKAHEAD(2) <COMMA>
      col = tableElement(info)
      {
        create.getColumns().add(col);
      }
    )*	
	[<COMMA> <PRIMARY> <KEY> 
	  pk = columnList(true, true)
	  {
	  	create.getPrimaryKey().addAll(pk);
	  }	  
	]
	<RPAREN>
	[<ON> <COMMIT> 
	     <PRESERVE> {create.setCommitAction(Create.CommitAction.PRESERVE_ROWS);} <ROWS>]
	{
		return create;
	}
}

/*
name=temporary table element
description=Defines a temporary table column.
example={code:sql}col string NOT NULL{code}
*/
Column tableElement(ParseInfo info) :
{
	String element = null;
	ParsedDataType type = null;
	boolean autoIncrement = false;
	//TODO: once we support udts, then this will need to inherit the nullability from the type
	boolean notNull = false;
}
{
	 element = id(Boolean.TRUE)
	 (
	  type = parseDataType()
	  |
	  <SERIAL>
	  {
	    type = new ParsedDataType("INTEGER");
	    autoIncrement = true;
	    notNull = true;
	  }
	 )
	 [<NOT> <NULL> { notNull = true; }]
	 {
	    Column c = new Column();
	    c.setName(element);
	    c.setRuntimeType(type.type);
	    c.setAutoIncremented(autoIncrement);
	    c.setNullType(notNull?NullType.No_Nulls:NullType.Nullable);
	    return c;
	 }
}

/*
name=raise error statement
description=Raises an error with the given message.
example={code:sql}ERROR 'something went wrong'{code}
*/
RaiseStatement errorStatement(ParseInfo info) :
{
    Expression errMsg = null;           
}
{ 
    <ERROR>
    errMsg = expression(info)           
        
    {
        ExceptionExpression ee = new ExceptionExpression();
        ee.setMessage(errMsg);
        return new RaiseStatement(ee);       
    }
}

/*
name=raise statement
description=Raises an error or warning with the given message.
example={code:sql}RAISE SQLEXCEPTION 'something went wrong'{code}
*/
RaiseStatement raiseStatement(ParseInfo info) :
{
    Expression err = null;
    boolean warning = false;
}
{ 
    <RAISE>
    [<SQLWARNING> {warning = true;}]
    err = exceptionReference(info)
    {
        return new RaiseStatement(err, warning);       
    }
}

/*
name=exception reference
description=a reference to an exception
example={code:sql}SQLEXCEPTION 'something went wrong' SQLSTATE '00X', 2{code}
*/
Expression exceptionReference(ParseInfo info) :
{
	String id = null;
	Expression ex;
}
{
	id = id(null) {return new ElementSymbol(id);}
	|
	ex = exception(info)
	{
		return ex;
	}
}

/*
name=sql exception
description=creates a sql exception or warning with the specified message, state, and code
example={code:sql}SQLEXCEPTION 'something went wrong' SQLSTATE '00X', 2{code}
*/
Expression exception(ParseInfo info) :
{
	Expression err = null;
	Expression sqlState = null;
	Expression errCode = null;
	Expression parent = null;
}
{
    <SQLEXCEPTION>
	err = commonValueExpression(info)
	[<SQLSTATE> sqlState = commonValueExpression(info)
     [<COMMA> errCode = commonValueExpression(info)]
    ]
    [<CHAIN> parent = exceptionReference(info)]
	{
		ExceptionExpression ee = new ExceptionExpression();
		ee.setMessage(err);
		ee.setSqlState(sqlState);
		ee.setErrorCode(errCode);
		ee.setParent(parent);
		return ee;
	}	
}

/*
name=statement
description=A procedure statement.
example={code:sql}IF (x = 5) BEGIN ... END{code}
*/
Statement statement(ParseInfo info) :
{
    Statement stmt = null;
    String label = null;
}
{
    (
	 LOOKAHEAD(2) ([label = id(null) <COLON>] 
	  (
	   stmt = loopStatement(info) |
	   stmt = whileStatement(info) |
	   stmt = compoundStatement(info)
	  )
	  {
	  	((Statement.Labeled)stmt).setLabel(label);
	  }
	 )
	 |
	 (
      stmt = ifStatement(info) |
      stmt = delimitedStatement(info)
	 )
	)
    
    {
        return stmt;
    }
}

/*
name=delimited statement
description=A procedure statement terminated by ;.
example={code:sql}SELECT * FROM tbl;{code}
*/
Statement delimitedStatement(ParseInfo info) :
{
    Statement stmt = null;
}           
{
     (LOOKAHEAD(2) stmt = assignStatement(info) |
	  stmt = sqlStatement(info) |
      stmt = errorStatement(info) |
      stmt = raiseStatement(info) |
	  stmt = declareStatement(info) |
	  stmt = branchingStatement(info) |
	  stmt = returnStatement(info)
	 )
	 <SEMICOLON>
	 {
	     return stmt;
	 }
}

/*
name=compound statement
description=A procedure statement block contained in BEGIN END.
example={code:sql}BEGIN NOT ATOMIC ... END{code}
*/
Block compoundStatement(ParseInfo info) :
{
    Statement stmt = null;  
    Block block = new Block();
    Boolean atomic = null;
    String eId = null;
}
{   
    <BEGIN> [[<NOT> {atomic = Boolean.FALSE;}] <ATOMIC> {if (atomic == null) {atomic = Boolean.TRUE;}}]
    (LOOKAHEAD(2)
      stmt = statement(info)
      {          
	    block.addStatement(stmt, false);            	                	    
	  }
 	)*
 	[<EXCEPTION> eId = id(Boolean.FALSE) {block.setExceptionGroup(eId);}
 	 (
       stmt = statement(info)
       {          
	     block.addStatement(stmt, true);            	                	    
	   }
 	 )*
 	]
    <END>
    {
        if (atomic != null) {
        	block.setAtomic(atomic);
        }
        return block;
    }
}        

/*
name=branching statement
description=A procedure branching control statement, which typically specifies a label to return control to.
example={code:sql}BREAK x{code}
*/
BranchingStatement branchingStatement(ParseInfo info) :
{
    BranchingStatement breakStmt = new BranchingStatement();
    Token mode = null;
    String label = null;
}
{
 	(
 	 (
 	  (mode = <BREAK> | mode = <CONTINUE>) [label = id(null)] 
 	 )
 	 | 
 	 (mode = <LEAVE> label = id(null))
 	)
 	{
 	    breakStmt.setMode(BranchingStatement.BranchingMode.valueOf(mode.image.toUpperCase()));
 	    breakStmt.setLabel(label);
 		return breakStmt;
 	}
} 	

/*
name=return statement
description=A return statement.
example={code:sql}RETURN 1{code}
*/
ReturnStatement returnStatement(ParseInfo info) :
{
    Expression ex = null;
}
{
 	<RETURN> [ex = expression(info)]
 	{
 	    return new ReturnStatement(ex);
 	}
} 	

/*
name=while statement
description=A procedure while statement that executes until its condition is false.
example={code:sql}WHILE (var) BEGIN ... END{code}
*/
WhileStatement whileStatement(ParseInfo info) :
{
    WhileStatement whileStmt = null;    
    Criteria criteria = null;
    Statement stmt = null;
}
{
    <WHILE>
    <LPAREN>  
    criteria = criteria(info)
    <RPAREN>
    stmt = statement(info)
    {
        whileStmt = new WhileStatement(criteria, asBlock(stmt));   
        return whileStmt;
    }    
}    

/*
name=loop statement
description=A procedure loop statement that executes over the given cursor.
example={code:sql}LOOP ON (SELECT * FROM tbl) AS x BEGIN ... END{code}
*/
LoopStatement loopStatement(ParseInfo info) :
{
    LoopStatement loopStmt = null;    
    String cursor = null;
    QueryCommand query = null;
    Statement stmt = null;
}
{
    <LOOP>
    <ON>
    <LPAREN>  
    query = queryExpression(info)
    <RPAREN>
    <AS>
    cursor = id(null)
    stmt = statement(info)
    {
        loopStmt = new LoopStatement(asBlock(stmt), query, cursor);   
        return loopStmt;
    }    
}   

/*
name=if statement
description=A procedure loop statement that executes over the given cursor.
example={code:sql}IF (boolVal) BEGIN variables.x = 1 END ELSE BEGIN variables.x = 2 END{code}
*/
IfStatement ifStatement(ParseInfo info) :
{
    IfStatement ifStmt = null;    
    Criteria criteria = null;
    Statement ifStatement = null;
    Statement elseStatement = null;
}
{
    <IF>
    <LPAREN>  
    criteria = criteria(info)
    <RPAREN>
    ifStatement = statement(info)
    //else blocks will be associated with the closest if block
    [LOOKAHEAD(1)<ELSE> elseStatement = statement(info)]
    {
        ifStmt = new IfStatement(criteria, asBlock(ifStatement));
        ifStmt.setElseBlock(asBlock(elseStatement));
        return ifStmt;
    }    
}    
            
/*
name=declare statement
description=A procedure declaration statement that creates a variable and optionally assigns a value.
example={code:sql}DECLARE STRING x = 'a'{code}
*/
DeclareStatement declareStatement(ParseInfo info) :
{
    DeclareStatement declStmt = null;
    String var = null;    
    ParsedDataType type = null;  
    ElementSymbol variableID = null;
    LanguageObject value = null;
}
{
    <DECLARE>
    (type = parseDataType() | <EXCEPTION> {type = new ParsedDataType(getToken(0).image);})
    var = id(null)
    {
        variableID = new ElementSymbol(var);
    }
    [<EQ>
     value = assignStatementOperand(info)
    ]
    
    {
        if (value instanceof Expression) {
    		return new DeclareStatement(variableID, type.type, (Expression)value);
    	}
    	if (value instanceof QueryCommand) {
    		return new DeclareStatement(variableID, type.type, new ScalarSubquery((QueryCommand)value));
    	}
    	return new DeclareStatement(variableID, type.type, (Command)value);
    }           
}

/*
name=assignment statement
description=Assigns a variable a value in a procedure.
example={code:sql}x = 'b'{code}
*/
Statement assignStatement(ParseInfo info) :
{
    LanguageObject value = null;
    String var = null;
    ElementSymbol elementID = null;
    boolean returnable=true;
}
{
    var = id(null)
    {
        elementID = new ElementSymbol(var);              
    } 
    <EQ>
    (value = assignStatementOperand(info) |
     (value = storedProcedure(info, 2) [(<WITH>|<WITHOUT> {returnable = false;}) <RETURN>]) {
		StoredProcedure storedProcedure = (StoredProcedure)value;
	    SPParameter parameter = new SPParameter(1, SPParameter.RETURN_VALUE, "return"); //$NON-NLS-1$
	    parameter.setExpression(elementID);
		storedProcedure.setParameter(parameter);  
		storedProcedure.setCalledWithReturn(true);
		CommandStatement cs = new CommandStatement(storedProcedure);
		cs.setReturnable(returnable);
		return cs;      	    
     }
    )
        
    {   
    	if (value instanceof Expression) {
    		return new AssignmentStatement(elementID, (Expression)value);
    	}
    	if (value instanceof QueryCommand) {
    		return new AssignmentStatement(elementID, (QueryCommand)value);
    	}
    	return new AssignmentStatement(elementID, (Command)value);
    }    
}

/*
name=assignment statement operand
description=A value or command that can be used in an assignment.  {note}All assigments except for expression are deprecated.{note}
*/
LanguageObject assignStatementOperand(ParseInfo info) :
{
    LanguageObject value = null;
}
{
    (
	  LOOKAHEAD(<INSERT><INTO>) (value = insert(info)) | //deprecated
      value = update(info) | //deprecated
      value = delete(info) | //deprecated
      LOOKAHEAD(1) value = expression(info) |      
      value = queryExpression(info) | //deprecated should be a scalar subquery
      value = exception(info)
    ) 
    {   
        return value;
    }    
}

/*
name=data statement
description=A procedure statement that executes a SQL statement.  An update statement can have its update count accessed via the ROWCOUNT variable. 
*/
CommandStatement sqlStatement(ParseInfo info) :
{
    Command cmd = null;
    String var = null;
    ElementSymbol elementID = null;
    StoredProcedure storedProcedure = null;
    boolean returnable = true;
}
{
    (LOOKAHEAD(2) cmd = userCommand(info) |
     cmd = dynamicCommand(info)
    ) [(<WITH>|<WITHOUT> {returnable = false;}) <RETURN>]
  
    {
    	CommandStatement cs = new CommandStatement(cmd);
    	cs.setReturnable(returnable);
    	return cs;
    }   
}
 
/*
unused=true
*/
CreateProcedureCommand  createProcedure(ParseInfo info) :
{
    CreateProcedureCommand procCmd = 
        new CreateProcedureCommand();
    Statement stmt = null;  
}
{ 
    <CREATE> [<VIRTUAL>] <PROCEDURE>
    stmt = statement(info)
    {        
        procCmd.setBlock(asBlock(stmt));
        return procCmd;        
    }    
}   

/*
name=procedure body definition
description=Defines a procedure body on a Procedure metadata object.
example={code:sql}BEGIN ... END{code}
index=true
*/
CreateProcedureCommand  procedureBodyCommand(ParseInfo info) :
{
    CreateProcedureCommand procCmd = 
        new CreateProcedureCommand();
    Statement stmt = null;  
}
{ 
    [LOOKAHEAD(2) <CREATE> [<VIRTUAL>] <PROCEDURE>]
    stmt = statement(info)
    {        
        procCmd.setBlock(asBlock(stmt));
        return procCmd;        
    }    
} 

/*
name=dynamic data statement
description=A procedure statement that can execute arbitrary sql.
example={code:sql}EXECUTE IMMEDIATE 'SELECT * FROM tbl' AS x STRING INTO #temp{code}
*/
DynamicCommand dynamicCommand(ParseInfo info) :
{
    Expression sql = null;           
    String groupID = null;
	GroupSymbol group = null;
	int updateCount = 0;
	List<TableFunctionReference.ProjectedColumn> elements = null;
	SetClauseList using = null;
	DynamicCommand dcStmt = new DynamicCommand();
	SetClauseList setClauseList = null;
}
{ 
    (<EXECUTE>|<EXEC>) [(<STRING>|<IMMEDIATE>)]
    sql = expression(info)
    {
        dcStmt.setSql(sql);
    }
    [<AS> 
	elements = createElementsWithTypes(info)

	 [<INTO>
      groupID = id(null)
      {
         group = new GroupSymbol(groupID);
      }
     ]
     {
         dcStmt.setIntoGroup(group);
         List<ElementSymbol> symbols = new ArrayList(elements.size());
         for (TableFunctionReference.ProjectedColumn col : elements) {
         	symbols.add(col.getSymbol());
         }
         dcStmt.setAsColumns(symbols);
         dcStmt.setAsClauseSet(true);
     }
	]
    [<USING>
     using = setClauseList(true, info)
	 {
	     dcStmt.setUsing(using);
	 }
    ]
    [<UPDATE>
     (updateCount = intVal()
     |
     <STAR> 
     {	
        updateCount = 2;
     })
    ]
    {
        dcStmt.setUpdatingModelCount(updateCount);
        return dcStmt; 
    }
}

/*
name=set clause list
description=A list of value assignments.
example={code:sql}col1 = 'x', col2 = 'y' ...{code}
*/
SetClauseList setClauseList(boolean shortName, ParseInfo info) :
{
	SetClauseList using = new SetClauseList();
	String element = null;
	Expression value = null;
}
{
	element = id(shortName?Boolean.TRUE:null)
    <EQ>
    value = expression(info)
    {
        ElementSymbol symbol = new ElementSymbol(element);
        using.addClause(symbol, value);
    }
	(<COMMA>
	 element = id(null)
     <EQ>
     value = expression(info)
     {
     	  symbol = new ElementSymbol(element);
          using.addClause(symbol, value);
     }
	)*
	{
		return using;
	}
}

/*
name=typed element list
description=A list of typed elements.
example={code:sql}col1 string, col2 integer ...{code}
*/
List<TableFunctionReference.ProjectedColumn> createElementsWithTypes(ParseInfo info) :
{
	String element = null;
	ParsedDataType type = null;
	List<TableFunctionReference.ProjectedColumn> elements = new ArrayList<TableFunctionReference.ProjectedColumn>();
}
{
	 element = id(Boolean.TRUE)
	 type = parseDataType()
	 {
	    TableFunctionReference.ProjectedColumn symbol = new TableFunctionReference.ProjectedColumn(element, type.type);
		elements.add(symbol);
	 }
	 (LOOKAHEAD(2) <COMMA>
		element = id(Boolean.TRUE)
		type = parseDataType()
		{
	      symbol = new TableFunctionReference.ProjectedColumn(element, type.type);
		  elements.add(symbol);
		}
	 )*
	 {
	 	return elements;
	 }
}

/*
name=callable statement
description=A callable statement defined using JDBC escape syntax.
example={code:sql}{? = CALL proc}{code}
index=true
*/
StoredProcedure callableStatement(ParseInfo info) :
{
	StoredProcedure storedProcedure = new StoredProcedure();
	storedProcedure.setCallableStatement(true);
	String procName = null;
	Option option = null;
	SPParameter parameter = null;
	int parameterIndex = 1;
	List<Expression> values = null;
}
{
	<LBRACE>
	[<QMARK> <EQ>
	 {
	   parameter = new SPParameter(parameterIndex++, SPParameter.RETURN_VALUE, "return"); //$NON-NLS-1$
	   storedProcedure.setParameter(parameter);
	   storedProcedure.setCalledWithReturn(true);
	 }
	]
	<CALL> procName = id(null)
	{
		storedProcedure.setProcedureName(procName);
	}
		
	//parameters
		
	[<LPAREN>
	  (LOOKAHEAD(2)
        executeNamedParams(info, storedProcedure, parameterIndex)
        |      
        [values = expressionList(info) { convertToParameters(values, storedProcedure, parameterIndex); }]
      )
	 <RPAREN>
	]
	<RBRACE>			
	[option = option(info)
	 {
	   storedProcedure.setOption(option);
	 }
	]

	{
		return storedProcedure;
	}
}

/*
name=call statement
description=Executes the procedure with the given parameters.
example={code:sql}CALL proc('a', 1){code}
*/
StoredProcedure storedProcedure(ParseInfo info, int startIndex) :
{
	String procName = null;
	Option option = null;
	List<Expression> values = null;
	StoredProcedure storedProcedure = new StoredProcedure();
	SourceHint sourceHint = null;
}
{
	{ sourceHint = getSourceHint(this); }
	(
		(<EXEC> | <EXECUTE> | <CALL>)
		procName = id(null)
		{
			storedProcedure.setProcedureName(procName);
		}
		
		//parameters
		<LPAREN>
		
		(
		 LOOKAHEAD(2)
		 executeNamedParams(info, storedProcedure, startIndex)
		 | 		
		 [values = expressionList(info) { convertToParameters(values, storedProcedure, startIndex); }]
		)
		
		<RPAREN>
	)
	[option = option(info)
	 {
	   storedProcedure.setOption(option);
	 }
	]
	{
		storedProcedure.setSourceHint(sourceHint);
		return storedProcedure;
	}
}

/*
name=named parameter list
description=A list of named parameters.
example={code:sql}param1 => 'x', param2 => 1{code}
*/
void executeNamedParams(ParseInfo info, StoredProcedure storedProcedure, int parameterIndex) :
{
	String name = null;
    Expression value = null;
	SPParameter parameter = null;
}
{
	{
		storedProcedure.setDisplayNamedParameters(true);
	}
	(name=id(null)
	 <EQ> [<GT>]
	 value = expression(info)
		{
			parameter = new SPParameter(parameterIndex++, value);
			parameter.setName(name);
			parameter.setParameterType(SPParameter.IN);
			storedProcedure.setParameter(parameter);
			parameter = null;
		}
		(	<COMMA>
		 	name=id(null)
	 		<EQ> [<GT>]
	 		value = expression(info)
			{
				parameter = new SPParameter(parameterIndex++, value);
				parameter.setName(name);
				parameter.setParameterType(SPParameter.IN);
				storedProcedure.setParameter(parameter);
				parameter = null;
			}
		)*
	)
}

/*
name=insert statement
description=Inserts values into the given target.
example={code:sql}INSERT INTO tbl (col1, col2) VALUES ('a', 1){code}
*/
Insert insert(ParseInfo info) :
{
	Insert insert = new Insert();
	boolean merge = false;
	String group = null;
	List values = null;
	List<ElementSymbol> columns = null;
	Option option = null;
	QueryCommand query = null;
}
{
	(<INSERT>|<MERGE>{merge=true;}) <INTO>
	group = id(null)

	[LOOKAHEAD(<LPAREN> id(null)) columns = columnList(false, true) {
	  insert.setVariables(columns);
	}]

    query = queryExpression(info)
	{
	  insert.setQueryExpression(query);
    }
	
	[ option = option(info)
	  {
	    insert.setOption(option);
	  }
	]
	
	{
		// Store group
		insert.setGroup(new GroupSymbol(group) );
		insert.setMerge(merge);
		return insert;
	}
}

/*
name=expression list
description=A list of expressions.
example={code:sql}col1, 'a', ...{code}
*/
ArrayList<Expression> expressionList(ParseInfo info) :
{
	ArrayList<Expression> rowVals = new ArrayList<Expression>(4);
	Expression value = null;
}
{
	value = expression(info)
	{
        rowVals.add(value);
	}
	(	<COMMA>
	    value = expression(info)
	    {
            rowVals.add(value);
	    }
	)*
	{
		return rowVals;
	}
}

/*
name=update statement
description=Update values in the given target.
example={code:sql}UPDATE tbl SET (col1 = 'a') WHERE col2 = 1{code}
*/
Update update(ParseInfo info) :
{
	Update update = new Update();
	String group = null;
	SetClauseList setClauseList = null;
	Criteria criteria = null;
	Option option = null;
}
{
	<UPDATE>
	group = id(null)
	<SET>
	setClauseList = setClauseList(false, info)
	{
		update.setChangeList(setClauseList);
	}
	[ criteria = where(info) ]
	[option = option(info)
	  {
	   update.setOption(option);
	  }
	]
	{	
		// Store group
		update.setGroup(new GroupSymbol( group) );
		
		// Store optional criteria
		if(criteria != null) {
			update.setCriteria(criteria);
		}
		
		return update;
	}
}

/*
name=delete statement
description=Delete rows from the given target.
example={code:sql}DELETE FROM tbl WHERE col2 = 1{code}
*/
Delete delete(ParseInfo info) :
{
	String group = null;
	Criteria criteria = null;
	Delete delete = new Delete();
	Option option = null;
}
{
	<DELETE> <FROM>
	group = id(null)
	[criteria = where(info)]
	[option = option(info)
	  {
	   delete.setOption(option);
	  }
	]
	{
		delete.setGroup(new GroupSymbol(group));
		delete.setCriteria(criteria);
		        
        return delete; 
	}
}

/*
name=query expression
description=A declarative query for data.
example={code:sql}SELECT * FROM tbl WHERE col2 = 1{code}
*/
QueryCommand queryExpression(ParseInfo info) :
{
	QueryCommand query = null;
	List<WithQueryCommand> withList = null;
	WithQueryCommand withQueryCommand = null;
	SourceHint sourceHint = null;
}
{
	[<WITH> withQueryCommand = withListElement(info)
	  {
	    withList = new LinkedList<WithQueryCommand>();
	    withList.add(withQueryCommand);
	  }
	  (	<COMMA>
	    withQueryCommand = withListElement(info)
	    {
            withList.add(withQueryCommand);
	    }
	  )*
	]
	{ sourceHint = getSourceHint(this); }
	query = queryExpressionBody(info)
	{
		query.setWith(withList);
		setSourceHint(sourceHint, query);
		return query;
	}
}

/*
name=with list element
description=A query expression for use in the enclosing query. 
example={code:sql}X (Y, Z) AS (SELECT 1, 2){code}
*/
WithQueryCommand withListElement(ParseInfo info) :
{
   String name = null;
   List<ElementSymbol> columns = null;
   QueryCommand queryExpression = null;
}
{
	name = id(Boolean.FALSE)
	[ columns = columnList(true, true)] 
	<AS> <LPAREN> queryExpression = queryExpression(info) <RPAREN>
	{
	    return new WithQueryCommand(new GroupSymbol(name), columns, queryExpression);
	}
}

/*
name=query expression body
description=The body of a query expression, which can optionally be ordered and limited.
example={code:sql}SELECT * FROM tbl ORDER BY col1 LIMIT 1{code}
*/
QueryCommand queryExpressionBody(ParseInfo info) :
{
    QueryCommand query = null;
    QueryCommand rightQuery = null;
    boolean all = false;
    Option option = null;
	OrderBy orderby = null;
	Limit limit = null;
	SetQuery.Operation type;
}
{
	query = queryTerm(info) 
	(	(LOOKAHEAD( <UNION>, { getToken(2).kind != JOIN } ) <UNION> { type = SetQuery.Operation.UNION; } | <EXCEPT> { type = SetQuery.Operation.EXCEPT; }) 
 	 	[<ALL> { all = true; } | <DISTINCT>]
 	 	rightQuery = queryTerm(info)
 	   	{
 	   		query = addQueryToSetOperation(query, rightQuery, type, all);
    		all = false;
	   	}
	) *
    [orderby  = orderby(info) {query.setOrderBy( orderby );}]
    [limit    = limit(info) {query.setLimit( limit );}]
    [LOOKAHEAD(<OPTION>) option = option(info) { query.setOption(option);  }	]
    {
		return query;
    }    
}

/*
name=query term
description=Used to establish INTERSECT precedence.
example={code:sql}SELECT * FROM tbl{code}\n{code:sql}SELECT * FROM tbl1 INTERSECT SELECT * FROM tbl2{code}
*/
QueryCommand queryTerm(ParseInfo info) : 
{
    QueryCommand query = null;
    QueryCommand rightQuery = null;
    boolean all = false;
}
{
	query=queryPrimary(info) 
	(	<INTERSECT> 
 		[<ALL> { all = true; } | <DISTINCT>] 
 		rightQuery=queryPrimary(info)
 		{
 	   		query = addQueryToSetOperation(query, rightQuery, SetQuery.Operation.INTERSECT, all);
    		all = false;
 		}
	) *
    {
        return query;
    }
}    

/*
name=query primary
description=A declarative source of rows.
example={code:sql}TABLE tbl{code}\n{code:sql}SELECT * FROM tbl1{code}
*/
QueryCommand queryPrimary(ParseInfo info) : 
{
    QueryCommand query = null;
    String name = null;
    List<Expression> expr = null;
}
{
    (
        query=query(info) |
        (<VALUES> <LPAREN> expr = expressionList(info) 
         {
           query = Query.rowConstructor(expr);
         } 
         <RPAREN> 
          (<COMMA> <LPAREN> expr = expressionList(info) 
           { 
             query = new SetQuery(SetQuery.Operation.UNION, true, query, Query.rowConstructor(expr));
           } 
          <RPAREN>)*) | 
        (<TABLE> name=id(null) {
           Query q = new Query();
           q.setSelect(new Select(Arrays.asList(new MultipleElementSymbol())));
           q.setFrom(new From(Arrays.asList(new UnaryFromClause(new GroupSymbol(name)))));
           query = q;
        }) |
        (<LPAREN> query=queryExpressionBody(info) <RPAREN>)
    )    
    {
        return query;
    }
}    

/*
name=query
description=A SELECT query.
example={code:sql}SELECT col1, max(col2) FROM tbl GROUP BY col1{code}
*/
Query query(ParseInfo info) :
{
	Select   select         = null;
	From     from           = null;
	Into 	 into			= null;
	Criteria criteria       = null;
	GroupBy  groupBy        = null;
	Criteria having         = null;	
}
{
    select	  = select(info)
    [into	  = into(info)]
   	[from 	  = from(info)
   		
    [criteria = where(info)]
    [groupBy  = groupBy(info)]
    [having   = having(info)]]

	{
		// Build query from parsed pieces
		Query query = new Query();
		query.setSelect( select );
		query.setFrom( from );
		query.setInto( into );
		query.setCriteria(criteria);
		query.setGroupBy(groupBy);
		query.setHaving(having);

		return query;
  	}
}

/*
name=into clause
description=Used to direct the query into a table.  {note}This is deprecated.  Use INSERT INTO with a query expression instead.{note}
example={code:sql}INTO tbl{code}
*/
Into into(ParseInfo info) :
{
	String groupID = null;
	Into into = null;
}
{	
	<INTO>
	(groupID=id(null))
	{
    	into = new Into(new GroupSymbol(groupID));
		return into;
    }	
}

/*
name=select clause
description=The columns returned by a query.  Can optionally be distinct.
example={code:sql}SELECT *{code}\n{code:sql}SELECT DISTINCT a, b, c{code}
 */
Select select(ParseInfo info) :
{
	boolean isDistinct = false;		// unless DISTINCT keyword in SELECT
	Expression symbol = null;
	Select select = new Select();
}
{
	<SELECT>
	[<ALL> | (<DISTINCT> {isDistinct=true;})]
	(<STAR>
		{
			select.addSymbol(new MultipleElementSymbol());
		}
	|
		(	symbol=selectSymbol(info)      { select.addSymbol(symbol); }
		    (	<COMMA>
				symbol=selectSymbol(info)  { select.addSymbol(symbol); }
			)*		
		)
	)		
	{
 		select.setDistinct(isDistinct);
		return select;
	}
}

/*
name=select sublist
description=An element in the select clause
example={code:sql}tbl.*{code}\n{code:sql}tbl.col AS x{code}
*/
Expression selectSymbol(ParseInfo info) :
{
	Expression symbol = null;	
}
{
	(
		symbol = selectExpression(info)
		|
		symbol = allInGroupSymbol()
	)
	{
		return symbol;
	}
}

/*
name=select derived column
description=A select clause item that selects a single column. {note}This is slightly different than a derived column in that the AS keyword is optional.{note}
example={code:sql}tbl.col AS x{code}
*/
Expression selectExpression(ParseInfo info) :
{
	Expression expression = null;
    String alias = null;
}
{
	(	
		// Expression
		expression=expression(info)	
		[[<AS>] alias=id(Boolean.FALSE) ]
	)
	{		
		if(alias != null) {
			return new AliasSymbol(alias, expression);
		}
		return expression;
	}
}

/*
name=derived column
description=An optionally named expression.
example={code:sql}tbl.col AS x{code}
*/
DerivedColumn derivedColumn(ParseInfo info) :
{
	Expression expression = null;
    String alias = null;
}
{
	(	
		// Expression
		expression=expression(info)	
		[<AS> alias=id(Boolean.FALSE) ]
	)
	{		
		return new DerivedColumn(alias, expression);
	}
}

/*
name=all in group 
description=A select sublist that can select all columns from the given group.
example={code:sql}tbl.*{code}
*/
MultipleElementSymbol allInGroupSymbol() :
{
	Token allInGroupToken = null;
	
}
{
   	allInGroupToken = <ALL_IN_GROUP>    
    {
		return new MultipleElementSymbol(normalizeId(allInGroupToken.image.substring(0, allInGroupToken.image.length() - 2)));
	}
}

/*
name=ordered aggreate function
description=An aggregate function that can optionally be ordered.
example={code:sql}XMLAGG(col1) ORDER BY col2{code}\n{code:sql}ARRAY_AGG(col1){code}
*/
AggregateSymbol orderedAgg(ParseInfo info) :
{
    Token t = null;
	Expression expression = null;
	OrderBy orderBy = null;
}
{
	(t=<XMLAGG>|t=<ARRAY_AGG>|t=<JSONARRAY_AGG>)
	<LPAREN>
	expression = expression(info)
	[ orderBy = orderby(info) ]
	<RPAREN>
	{
		String aggName = t.image.toUpperCase();
		AggregateSymbol	agg = new AggregateSymbol(aggName, false, expression);
		agg.setOrderBy(orderBy);
		return agg;
	}
}

/*
name=text aggreate function
description=An aggregate function for creating separated value clobs.
example={code:sql}TEXTAGG (col1 as t1, col2 as t2 DELIMITER ',' HEADER){code}
*/
AggregateSymbol textAgg(ParseInfo info) :
{
	DerivedColumn expression = null;
	Character delimiter = null;
	Character quote = null;
	boolean header = false;	
	List<DerivedColumn> expressions = new ArrayList<DerivedColumn>();
	OrderBy orderBy = null;
	String encoding = null;
}
{
	<TEXTAGG> <LPAREN>
	[<FOR>] expression = derivedColumn(info)
	{
		expressions.add(expression);
	}
	(<COMMA>
		expression = derivedColumn(info)
		{
			expressions.add(expression);
		}
	)* 		
	[
	  <DELIMITER>
	  delimiter = charVal(info, "DELMITER")
	]
	[
	  <QUOTE>
	  quote = charVal(info, "QUOTE")
	]
	[
	  <HEADER>
	  { header = true; }
	]	
	[
	  <ENCODING>
	  encoding = id(null) 
	]
	[
		orderBy = orderby(info)
	]	
	<RPAREN>
	{
		TextLine tf = new TextLine();
		tf.setDelimiter(delimiter);
		tf.setQuote(quote);
		tf.setIncludeHeader(header);
		tf.setExpressions(expressions);
		tf.setEncoding(encoding);
		AggregateSymbol agg = new AggregateSymbol("TEXTAGG", false, tf);
		agg.setOrderBy(orderBy);
		return agg;		
	}
}

/*
name=standard aggregate function
description=A standard aggregate function.
example={code:sql}COUNT(*){code}
*/
AggregateSymbol aggregateSymbol(ParseInfo info) :
{
	Token func = null;
	Token starToken = null;
	AggregateSymbol agg = null;
	boolean isDistinct = false;
	Expression expression = null;
}
{
	(
		// COUNT(*)
		LOOKAHEAD(3) (func = <COUNT>
			<LPAREN>
			starToken = <STAR>
			<RPAREN> )	
		| 
		// Remaining aggregates
		(	(<COUNT> | <SUM> | <AVG> | <MIN> | <MAX> | <EVERY> | <STDDEV_POP> | <STDDEV_SAMP> | <VAR_SAMP> | <VAR_POP> | <SOME> | <ANY>) {func = getToken(0);}
			<LPAREN>
			[ <DISTINCT> {isDistinct=true;} | <ALL>]
			expression = expression(info)
			<RPAREN>
		)
	)
	{
		String name = func.image.toUpperCase();
		if(starToken == null) { 
			// Aggregate
			agg = new AggregateSymbol(name, isDistinct, expression);
		} else {
			// COUNT(*)			
			agg = new AggregateSymbol(name, false, null);
		}
		return agg;
	}
}

/*
name=analytic aggregate function
description=An analytic aggregate function.
example={code:sql}ROW_NUMBER(){code}
*/
AggregateSymbol analyticAggregateSymbol(ParseInfo info) :
{
	Token func = null;
}
{
	(<ROW_NUMBER> | <RANK> | <DENSE_RANK>) {func = getToken(0);}
	<LPAREN> <RPAREN>
	{
		return new AggregateSymbol(func.image.toUpperCase(), false, null);	
	}
}

/*
name=filter clause
description=An aggregate filter clause applied prior to accumulating the value.
example={code:sql}FILTER (WHERE col1='a'){code}
*/
Expression filterClause(ParseInfo info) :
{
	Expression condition = null;
}
{
	<FILTER> <LPAREN> <WHERE> condition = booleanPrimary(info) <RPAREN>
	{
		return condition;	
	}
}

/*
name=from clause
description=A query from clause containing a list of table references.
example={code:sql}FROM a, b{code}\n{code:sql}FROM a right outer join b, c, d join e".</p>{code}
*/
From from(ParseInfo info) :
{
	FromClause clause = null;
	List clauseList = new LinkedList();
}
{
	<FROM>
	(	clause = tableReference(info)
		{
			clauseList.add(clause);
			clause = null;
		}
		
		(<COMMA> clause = tableReference(info)
			{
				clauseList.add(clause);
				clause = null;
			}
		)*
	) 

	{
		return new From(clauseList);	
	}
}					

/*
name=table reference
description=An optionally escaped joined table.
example={code:sql}a{code}\n{code:sql}a inner join b{code}
*/
FromClause tableReference(ParseInfo info) :
{
	FromClause result = null;
}
{	
	(
		(	
			<ESCAPEDJOIN>
			result=joinedTable(info)
			<RBRACE>
		) 
		|
		result=joinedTable(info)
	)
	{
		return result;
	}
}	

/*
name=joined table
description=A table or join.
example={code:sql}a{code}\n{code:sql}a inner join b{code}
*/
FromClause joinedTable(ParseInfo info) :
{
	FromClause result = null;
	JoinPredicate jp = null;
}
{
	result = tablePrimary(info) /* this is a fix for left recursion in the spec */
	( LOOKAHEAD(2)	
		(	jp = crossJoin(info)
			| jp = qualifiedJoin(info)
		)
		{
			jp.setLeftClause(result);
			result = jp;
		}
	) *
	{
		return result;
	}
}

/*
name=cross join
description=A cross join.
example={code:sql}a CROSS JOIN b{code}
*/
JoinPredicate crossJoin(ParseInfo info) :
{
	JoinPredicate result = new JoinPredicate();
	FromClause clause = null;
}
{
	(	(<CROSS> {result.setJoinType(JoinType.JOIN_CROSS);}|<UNION> {result.setJoinType(JoinType.JOIN_UNION);})
    	<JOIN> 
		clause = tablePrimary(info) {result.setRightClause(clause);}
	)
	{
		return result;
	}
}

/*
name=qualified table
description=An INNER or OUTER join.
example={code:sql}a inner join b{code}
*/
JoinPredicate qualifiedJoin(ParseInfo info) :
{
	JoinPredicate result = new JoinPredicate();
	FromClause clause = null;
	Criteria onCriteria = null;
}
{
	(	[	(<RIGHT> {result.setJoinType(JoinType.JOIN_RIGHT_OUTER);} [<OUTER>])
    		| (<LEFT> {result.setJoinType(JoinType.JOIN_LEFT_OUTER);} [<OUTER>])
    		|  (<FULL> {result.setJoinType(JoinType.JOIN_FULL_OUTER);} [<OUTER>])
    		| <INNER>
		]
		<JOIN>
		clause = tableReference(info) {result.setRightClause(clause);}
		<ON>
		onCriteria = criteria(info) {result.setJoinCriteria(Criteria.separateCriteriaByAnd(onCriteria));}
	)
	{
		return result;
	}
}

/*
name=table primary
description=A single source of rows.
example={code:sql}a{code}
*/
FromClause tablePrimary(ParseInfo info) :
{
	FromClause clause = null;
	Token lparen = null;
	Option.MakeDep makedep = null;
}
{
	(	LOOKAHEAD(2) clause = textTable(info)
	    |
	    LOOKAHEAD(2) clause = arrayTable(info)
	    |
	    clause = xmlTable(info)
	    |
	    LOOKAHEAD(2) clause = objectTable(info)
	    |
	    clause = unaryFromClause(info)
		|
		LOOKAHEAD(subqueryFromClause(info)) clause = subqueryFromClause(info) 
	    |			
		(lparen = <LPAREN> clause = joinedTable(info) 
		 {
			setFromClauseOptions(lparen, clause);
		 }
		 <RPAREN> 
		)
	)
	[	(<MAKEDEP> makedep = makedepOptions() { clause.setMakeDep(makedep); })
	 	| (<MAKENOTDEP> {  clause.setMakeNotDep(true); } )
	]
	{
		return clause;
	}
}

/*
name=make dep options
description=options for the make dep hint
example={code:sql}(min:10000){code}
*/
Option.MakeDep makedepOptions() :
{
	Integer max = null;
	boolean join = false;
}
{
 [<LPAREN>
	[<MAX><COLON> max = intVal()]
	[<JOIN> {join = true;}]
  <RPAREN>]
 {
 	Option.MakeDep result = new Option.MakeDep();
 	result.setMax(max);
 	result.setJoin(join);
 	return result;
 }
}

/*
name=xml serialize
description=Serializes an XML value.
example={code:sql}XMLSERIALIZE(col1 AS CLOB){code}
*/
XMLSerialize xmlSerialize(ParseInfo info) :
{
	Expression expr = null;
	Boolean doc = null;
	Token t = null;
	String enc = null;
	Boolean declr = null;
	String ver = null;
}
{
	<XMLSERIALIZE> <LPAREN>
	[LOOKAHEAD(1) (<DOCUMENT> {doc = true;}| <CONTENT> {doc = false;})]
	expr = expression(info)
	[
		<AS> (<STRING> | <VARCHAR> | <CLOB> | <VARBINARY> | <BLOB>) { t = getToken(0);}
	]
	[ <ENCODING> enc = id(null) ]
	[ <VERSION> ver = stringVal() ]
	[ ( <INCLUDING> {declr = true;}| <EXCLUDING> {declr = false;}) <XMLDECLARATION> ]
	<RPAREN>
	{
		XMLSerialize result = new XMLSerialize();
		result.setEncoding(enc);
		result.setVersion(ver);
		result.setDeclaration(declr);
		result.setExpression(expr);
		if (doc != null) {
			result.setDocument(doc);
		}
		if (t != null) {
			result.setTypeString(t.image);
		}
		return result;
	}
}

/*
name=array table
description=The ARRAYTABLE table function creates tabular results from arrays.  It can be used as a nested table reference.
example={code:sql}ARRAYTABLE (col1 COLUMNS x STRING) AS y{code}
*/
ArrayTable arrayTable(ParseInfo info) :
{
	Expression array = null;
	List<TableFunctionReference.ProjectedColumn> columns;
	String aliasID = null;
}
{
 	<ARRAYTABLE> <LPAREN> array = valueExpressionPrimary(info)
 	<COLUMNS>
 	columns = createElementsWithTypes(info) 
 	<RPAREN>
 	[<AS>] aliasID=id(Boolean.FALSE)
 	{
 		ArrayTable result = new ArrayTable();
 		result.setArrayValue(array);
 		result.setColumns(columns);
 		result.setName(aliasID);
 		return result;
 	}
}

/*
name=text table
description=The TEXTTABLE table function creates tabular results from text.  It can be used as a nested table reference.
example={code:sql}TEXTTABLE (file COLUMNS x STRING) AS y{code}
*/
TextTable textTable(ParseInfo info) :
{
	Expression file = null;
	TextTable.TextColumn column = null;
	List<TextTable.TextColumn> columns = new ArrayList<TextTable.TextColumn>();
	boolean useRowDelimiter = true;
	Character delimiter = null;
	Character rowDelimiter = null;
	boolean escape = false;
	Character quote = null;
	Integer header = null;
	Integer skip = null;
	String selector = null;
	String aliasID = null;
	boolean noTrim = false;
}
{
 	<TEXTTABLE> <LPAREN> file = commonValueExpression(info)
 	[
	  <SELECTOR>
	  selector = stringVal()
	]
 	<COLUMNS>
 	column = textColumn(info)
	{
		columns.add(column);
	} 
	(<COMMA>
		column = textColumn(info)
		{
			columns.add(column);
		}
	)* 
	[
	  LOOKAHEAD(2) ((<NO> <ROW> <DELIMITER>
	  {
	    useRowDelimiter = false;
	  })
	  |
	  (<ROW> <DELIMITER>
	    rowDelimiter = charVal(info, " ROW DELMITER")
	  ))
	]
	[
	  <DELIMITER>
	  delimiter = charVal(info, "DELMITER")
	]
	[
	  (<ESCAPE> quote = charVal(info, "ESCAPE") ) { escape = true; }
	  |
	  (<QUOTE> quote = charVal(info, "QUOTE") )
	]
	[
	  <HEADER>
	  [
	  	header = intVal()
	  ]
	  {
	  	if (header == null) {
	  		header = 1;
	  	}
	  }
	]
	[
	  <SKIP_KEYWORD>
	  skip = intVal()
	]
	[ LOOKAHEAD(2)
      <NO> <TRIM>
      {
    	noTrim = true;	  	
      }
  	]
 	<RPAREN>
 	[<AS>] aliasID=id(Boolean.FALSE)
 	{
 		TextTable result = new TextTable();
 		result.setFile(file);
 		result.setColumns(columns);
 		result.setDelimiter(delimiter);
 		result.setEscape(escape);
 		result.setHeader(header);
 		result.setSkip(skip);
 		result.setName(aliasID);
 		result.setQuote(quote);
 		result.setUsingRowDelimiter(useRowDelimiter);
 		result.setSelector(selector);
 		if (noTrim) {
 			result.setNoTrim();
 		}
 		result.setRowDelimiter(rowDelimiter);
 		return result;
 	}
}

/*
name=text table column
description=A text table column.
example={code:sql}x INTEGER WIDTH 6{code}
*/
TextTable.TextColumn textColumn(ParseInfo info):
{
	String name = null;
	ParsedDataType datatype = null;
	Integer width = null;
	boolean noTrim = false;
	String selector = null;
	Integer position = null;
}
{
	name = id(Boolean.TRUE)
	
	( (
      <FOR> <ORDINALITY> 
      {
        return new TextTable.TextColumn(name); 
      } 
    ) | (
    	datatype = parseDataType()
    	[ 
    	  	<WIDTH>
    	  	width = intVal() 
    	  	[ LOOKAHEAD(2)
    		  	<NO> <TRIM>
    		  	{
    				noTrim = true;	  	
    		  	}
    		]
    	]
    	[ 
	  		<SELECTOR>
		    selector = stringVal()
		  	position = intVal() 
		]
		{
			TextTable.TextColumn result = new TextTable.TextColumn(name, datatype.type, width, noTrim);
			result.setSelector(selector);
			result.setPosition(position);
			return result;
		}
    ) )
	
}

/*
name=xml query
description=Executes an XQuery to return an XML result.
example={code:sql}XMLQUERY('<a>...</a>' PASSING doc){code}
*/
Expression xmlExists(ParseInfo info) :
{
    String xquery = null;
    XMLNamespaces xmlNamespaces = null;
    DerivedColumn passing = null;
    ArrayList<DerivedColumn> passingValues = new ArrayList<DerivedColumn>();
    Boolean content = null;
    Boolean empty = null;
}
{
    <XMLEXISTS> <LPAREN> 
    [ xmlNamespaces = xmlNamespaces(info) <COMMA> ]
    xquery = stringVal()
    [ 
      <PASSING>
      passing = derivedColumn(info)
      {
        passingValues.add(passing);
        passing.setPropagateName(false);
      }
      (<COMMA>
        passing = derivedColumn(info)
        {
          passingValues.add(passing);
          passing.setPropagateName(false);
        }
      )*
    ]
    <RPAREN>
    {
        XMLQuery result = new XMLQuery();
        result.setXquery(xquery);
        result.setNamespaces(xmlNamespaces);
        result.setPassing(passingValues);
        result.setEmptyOnEmpty(true);
        return new XMLExists(result);
    }
}

/*
name=xml query
description=Executes an XQuery to return an XML result.
example={code:sql}XMLQUERY('<a>...</a>' PASSING doc){code}
*/
XMLQuery xmlQuery(ParseInfo info) :
{
	String xquery = null;
	XMLNamespaces xmlNamespaces = null;
	DerivedColumn passing = null;
	ArrayList<DerivedColumn> passingValues = new ArrayList<DerivedColumn>();
	Boolean content = null;
	Boolean empty = null;
}
{
 	<XMLQUERY> <LPAREN> 
 	[ xmlNamespaces = xmlNamespaces(info) <COMMA> ]
 	xquery = stringVal()
 	[ 
 	  <PASSING>
 	  passing = derivedColumn(info)
 	  {
 	  	passingValues.add(passing);
 	  	passing.setPropagateName(false);
 	  }
 	  (<COMMA>
 	    passing = derivedColumn(info)
 	    {
 	  	  passingValues.add(passing);
 	  	  passing.setPropagateName(false);
 	    }
 	  )*
 	]
/* 	[
 	  LOOKAHEAD(<ID>, { "returning".equalsIgnoreCase(getToken(1).image) }) <ID>
 	  (
 	  	LOOKAHEAD(<ID>, { "content".equalsIgnoreCase(getToken(1).image) }) <ID> { content = true; }
 	  	|
 	  	LOOKAHEAD(<ID>, { "sequence".equalsIgnoreCase(getToken(1).image) }) (<ID> { content = false; }
 	  )
 	]*/
 	[
 	  (<NULL> { empty = false; }
 	   |
 	   <EMPTY> { empty = true; }
 	  )
 	  <ON> <EMPTY>
 	]
 	<RPAREN>
 	{
 		XMLQuery result = new XMLQuery();
 		result.setXquery(xquery);
 		result.setNamespaces(xmlNamespaces);
 		result.setPassing(passingValues);
 		result.setEmptyOnEmpty(empty);
 		return result;
 	}
}

/*
name=object table
description=Returns table results by processing a script.
example={code:sql}OBJECTTABLE('z' PASSING val AS z COLUMNS col OBJECT 'teiid_row') AS X{code}
*/
ObjectTable objectTable(ParseInfo info) :
{
	String rowScript = null;
	String lang = null;
	DerivedColumn passing = null;
	ArrayList<DerivedColumn> passingValues = new ArrayList<DerivedColumn>();
	ObjectTable.ObjectColumn column = null;
	ArrayList<ObjectTable.ObjectColumn> columns = new ArrayList<ObjectTable.ObjectColumn>();
	String aliasID = null;
}
{
 	<OBJECTTABLE> <LPAREN> 
 	[<LANGUAGE> lang = stringVal()]
 	rowScript = stringVal()
 	[ 
 	  <PASSING>
 	  passing = derivedColumn(info)
 	  {
 	  	passingValues.add(passing);
 	  	passing.setPropagateName(false);
 	  }
 	  (<COMMA>
 	    passing = derivedColumn(info)
 	    {
 	  	  passingValues.add(passing);
 	  	  passing.setPropagateName(false);
 	    }
 	  )*
 	]
    <COLUMNS>
    column = objectColumn(info)
    {
      columns.add(column);
    }
    (<COMMA>
      column = objectColumn(info)
      {
        columns.add(column);
      }
    )*
 	<RPAREN>
 	[<AS>] aliasID=id(Boolean.FALSE)
 	{
 		ObjectTable result = new ObjectTable();
 		result.setScriptingLanguage(lang);
 		result.setRowScript(rowScript);
 		result.setPassing(passingValues);
 		result.setColumns(columns);
 		result.setName(aliasID);
 		return result;
 	}
}

/*
name=object table column
description=object table column.
example={code:sql}y integer 'teiid_row_number'{code}
*/
ObjectTable.ObjectColumn objectColumn(ParseInfo info):
{
	String name = null;
	ParsedDataType datatype = null;
	String path = null;
	Expression defaultExpr = null;
}
{
	name = id(null)
    datatype = parseDataType()
	path = stringVal()    
    [
   	  <DEFAULT_KEYWORD> defaultExpr = expression(info)
	]
	{
	  return new ObjectTable.ObjectColumn(name, datatype.type, path, defaultExpr);
	}
}


/*
name=xml table
description=Returns table results by processing an XQuery.
example={code:sql}XMLTABLE('/a/b' PASSING doc COLUMNS col XML PATH '.') AS X{code}
*/
XMLTable xmlTable(ParseInfo info) :
{
	String xquery = null;
	XMLNamespaces xmlNamespaces = null;
	DerivedColumn passing = null;
	ArrayList<DerivedColumn> passingValues = new ArrayList<DerivedColumn>();
	XMLTable.XMLColumn column = null;
	ArrayList<XMLTable.XMLColumn> columns = new ArrayList<XMLTable.XMLColumn>();
	String aliasID = null;
}
{
 	<XMLTABLE> <LPAREN> 
 	[ xmlNamespaces = xmlNamespaces(info) <COMMA> ]
 	xquery = stringVal()
 	[ 
 	  <PASSING>
 	  passing = derivedColumn(info)
 	  {
 	  	passingValues.add(passing);
 	  	passing.setPropagateName(false);
 	  }
 	  (<COMMA>
 	    passing = derivedColumn(info)
 	    {
 	  	  passingValues.add(passing);
 	  	  passing.setPropagateName(false);
 	    }
 	  )*
 	]
 	[
 	  <COLUMNS>
 	  column = xmlColumn(info)
 	  {
 	    columns.add(column);
 	  }
 	  (<COMMA>
 	    column = xmlColumn(info)
 	    {
 	      columns.add(column); 	    
 	    }
 	  )*
 	]
 	<RPAREN>
 	[<AS>] aliasID=id(Boolean.FALSE)
 	{
 		XMLTable result = new XMLTable();
 		result.setXquery(xquery);
 		result.setNamespaces(xmlNamespaces);
 		result.setPassing(passingValues);
 		result.setColumns(columns);
 		result.setName(aliasID);
 		return result;
 	}
}

/*
name=xml table column
description=XML table column.
example={code:sql}y FOR ORDINALITY{code}
*/
XMLTable.XMLColumn xmlColumn(ParseInfo info):
{
	String name = null;
	ParsedDataType datatype = null;
	String path = null;
	Expression defaultExpr = null;
}
{
	name = id(null)
	
	((
	  <FOR> <ORDINALITY> 
	  {
	    return new XMLTable.XMLColumn(name); 
	  }	
	) | (
      datatype = parseDataType()
      [
	    <DEFAULT_KEYWORD> defaultExpr = expression(info)
	  ]
      [ 
	    <PATH> path = stringVal()
	  ]
	  {
		return new XMLTable.XMLColumn(name, datatype.type, path, defaultExpr);
	  }
	))
}

/*
name=unsigned integer
description=An unsigned interger value.
example={code:sql}12345{code}
*/
int intVal() :
{
	Token t;
}
{
	t = <UNSIGNEDINTEGER>
	{
		try { 
			return Integer.valueOf(t.image); 				
		} catch(NumberFormatException e1) { 
			throw new ParseException(QueryPlugin.Util.getString("SQLParser.Integer_parse", t.image)); //$NON-NLS-1$
		}
	}
}

/*
name=table subquery
description=A table defined by a subquery.
example={code:sql}(SELECT * FROM tbl) AS x{code}
*/
SubqueryFromClause subqueryFromClause(ParseInfo info) :
{
    String aliasID = null;
	Command command = null;
	Token lparen = null;
	boolean table = false;
}
{	
	[
		(<TABLE>|<LATERAL>) { table = true; }
	]
	lparen = <LPAREN> 
	( command = queryExpression(info) |
      command = storedProcedure(info, 1) )
	<RPAREN>
	[<AS>]
	aliasID = id(Boolean.FALSE)
	
	{
		SubqueryFromClause clause = new SubqueryFromClause(aliasID, command);
        setFromClauseOptions(lparen, clause);
        clause.setTable(table);
        return clause;
    }	
}	
		
/*
name=table name
description=A table named in the FROM clause.
example={code:sql}tbl AS x{code}
*/
UnaryFromClause unaryFromClause(ParseInfo info) :
{
	GroupSymbol group = null;
	String groupID = null;
	Token groupToken = null;
    String aliasID = null;
	UnaryFromClause clause = null;
}
{	
	(groupID=id(null) {groupToken = getToken(0);} [[<AS>] aliasID=id(Boolean.FALSE)])
	{
    	if(aliasID != null) {		
            group = new GroupSymbol(aliasID, groupID);
        } else {
			group = new GroupSymbol(groupID);
        }
        clause = new UnaryFromClause(group);
        setFromClauseOptions(groupToken, clause);
		return clause;
    }	
}		

/*
name=where clause
description=Specifies a search condition
example={code:sql}WHERE x = 'a'{code}
*/
Criteria where(ParseInfo info) :
{
    Criteria criteria = null;
}
{
    <WHERE>
	criteria = criteria(info)
	
	{
		return criteria;
	}
}

/*
name=condition
description=A boolean expression.
*/
Criteria criteria(ParseInfo info) :
{
	Criteria criteria = null;
}
{	
	criteria = compoundCritOr(info)

	{
		return criteria;
	}
}

/*
name=boolean value expression
description=An optionally ORed boolean expression.
*/
Criteria compoundCritOr(ParseInfo info) :
{
    ArrayList logicList = null;
    Criteria logicPart = null;
    Criteria logicPart1 = null;
}
{
	logicPart=compoundCritAnd(info) 
	(<OR> logicPart1=compoundCritAnd(info) 			{ if (logicList == null) { logicList = new ArrayList(2); logicList.add(logicPart); } logicList.add(logicPart1); } )*

	{
		if(logicList == null) {
			return logicPart;
		} else {
         	return new CompoundCriteria(CompoundCriteria.OR,logicList);
		}
	}
}

/*
name=boolean term
description=An optional ANDed boolean factor.
*/
Criteria compoundCritAnd(ParseInfo info) :
{
    ArrayList logicList = null;
    Criteria logicPart = null;
    Criteria logicPart1 = null;
}
{
	logicPart=notCrit(info) 		
	(<AND> logicPart1=notCrit(info) 		{ if (logicList == null) { logicList = new ArrayList(2); logicList.add(logicPart); } logicList.add(logicPart1); } )*

	{
   		if(logicList == null) {
			return logicPart;
		} else {	
         	return new CompoundCriteria(CompoundCriteria.AND,logicList);
		}	
	}
}

/*
name=boolean factor
description=A boolean factor.
example={code:sql}NOT x = 'a'{code}
*/
Criteria notCrit(ParseInfo info) :
{
    Expression ex = null;
	boolean isNot = false;
}
{
	[<NOT> {isNot=true;}] 
	ex=booleanPrimary(info)

	{
	    Criteria crit = null;
	    if (ex instanceof Criteria) {
	      crit = (Criteria)ex;
	    } else {
	      crit = new ExpressionCriteria(ex);
	    }
  		if(isNot) { 
  			return new NotCriteria(crit);
		} else {
			return crit;
		}
	}
}

/*
name=boolean primary
description=A boolean predicate or simple expression.
example={code:sql}col LIKE 'a%'{code}
*/
Expression booleanPrimary(ParseInfo info) :
{
    Expression ex = null;
}
{
    (
	    (ex = commonValueExpression(info)
	     [(
			LOOKAHEAD(2) ex=betweenCrit(info, ex) |
			LOOKAHEAD(2) ex=matchCrit(info, ex) |
			LOOKAHEAD(2) ex=regexMatchCrit(info, ex) |
			ex=setCrit(info, ex) |
			ex=isNullCrit(info, ex) |
			LOOKAHEAD(operator() (<ANY>|<SOME>|<ALL>) subquery(info)) ex=subqueryCompareCriteria(info, ex) |
			ex=compareCrit(info, ex)
	     )]
	    )
	    | ex=existsCriteria(info)
	    | ex=xmlExists(info)
    )
	{
		return ex;
	}
}

/*
name=comparison operator
description=A comparison operator.
example={code:sql}={code}
*/
Token operator() :
{
	Token operator = null;
}
{
	(operator=<EQ> |
    operator=<NE> |
    operator=<NE2> |
    operator=<LT> |
    operator=<LE> |
    operator=<GT> |
    operator=<GE>
    )
    {
    	return operator;
    }
}

/*
name=comparison predicate
description=A value comparison.
example={code:sql}= 'a'{code}
*/
CompareCriteria compareCrit(ParseInfo info, Expression expression) :
{
    Expression value = null;
    Token operator = null;
}
{
	operator=operator()
	value=commonValueExpression(info)

	{
		CompareCriteria compareCriteria = new CompareCriteria();

		// Set left expression
		compareCriteria.setLeftExpression(expression);

		compareCriteria.setOperator(getOperator(operator.image));
		
		// Set value
		compareCriteria.setRightExpression(value);

	    return compareCriteria;
	}
}

/*
name=subquery
description=A subquery.
example={code:sql}(SELECT * FROM tbl){code}
*/
QueryCommand subquery(ParseInfo info) :
{
    QueryCommand subquery = null;
    StoredProcedure proc = null;
}
{
	<LPAREN>
	( subquery = queryExpression(info) | 
	 (
		proc = storedProcedure(info, 1) //deprecated	  
	 )
	 {
	    subquery = new Query(new Select(Arrays.asList(new MultipleElementSymbol())), new From(Arrays.asList(new SubqueryFromClause("x", proc))), null, null, null);
	 }
	)
	<RPAREN>
	{
		return subquery;
	}
}

/*
name=quantified comparison predicate
description=A subquery comparison.
example={code:sql}= ANY (SELECT col FROM tbl){code}
*/
SubqueryCompareCriteria subqueryCompareCriteria(ParseInfo info, Expression expression) :
{
    SubqueryCompareCriteria subqueryCrit = null;
    QueryCommand subquery = null;
    Token operator = null;
    Token quantifier = null;
    ExistsCriteria.SubqueryHint hint = null;
}
{
	operator=operator()
	( quantifier=<ANY> |
	  quantifier=<SOME> |
	  quantifier=<ALL>
	)
	{ hint = getSubqueryHint(getToken(1)); }
	subquery = subquery(info)

	{
		subqueryCrit = new SubqueryCompareCriteria();
	    subqueryCrit.setLeftExpression(expression);
	    subqueryCrit.setCommand(subquery);
    
		// Set operator
		subqueryCrit.setOperator(getOperator(operator.image));

		// Set predicate quantifier
		if(quantifier.image.equalsIgnoreCase("any")) { //$NON-NLS-1$
			subqueryCrit.setPredicateQuantifier(SubqueryCompareCriteria.ANY);
		} else if(quantifier.image.equalsIgnoreCase("some")) { //$NON-NLS-1$
			subqueryCrit.setPredicateQuantifier(SubqueryCompareCriteria.SOME);
		} else if(quantifier.image.equalsIgnoreCase("all")) { //$NON-NLS-1$
			subqueryCrit.setPredicateQuantifier(SubqueryCompareCriteria.ALL);
		} 
        subqueryCrit.setSubqueryHint(hint);
	    return subqueryCrit;
   	}
}

/*
name=match predicate
description=Matches based upon a pattern.
example={code:sql}LIKE 'a_'{code}
*/
MatchCriteria matchCrit(ParseInfo info, Expression expression) :
{
    Character esc = null;
	Expression value = null;
	boolean negated = false;
	boolean similar = false;
	Token t = null;
}
{
	[<NOT> {negated = true;}]
    (<LIKE>|(<SIMILAR> <TO> {similar = true;}))
    {t = getToken(1);}
	value=commonValueExpression(info)
    [   <ESCAPE> esc = charVal(info, "LIKE/SIMILAR TO ESCAPE") |
        (<LBRACE> <ESCAPE> esc = charVal(info, "LIKE/SIMILAR TO ESCAPE") <RBRACE>) 
    ]
	{
    	MatchCriteria matchcriteria = new MatchCriteria(expression, value);
		matchcriteria.setNegated(negated);
	    if(esc != null) {
			matchcriteria.setEscapeChar(esc.charValue());
		} else if (!similar && value instanceof Constant) {
			if (t.image != null && t.image.startsWith("E'")) {
				matchcriteria.setEscapeChar('\\');
			}
		}
		matchcriteria.setMode(similar?MatchMode.SIMILAR:MatchMode.LIKE);
		return matchcriteria;
   	}
}

/*
name=like regex predicate
description=A regular expression match.
example={code:sql}LIKE_REGEX 'a.*b'{code}
*/
MatchCriteria regexMatchCrit(ParseInfo info, Expression expression) :
{
	Expression value = null;
	boolean negated = false;
}
{
	[<NOT> {negated = true;}]
    <LIKE_REGEX>
	value=commonValueExpression(info)
	{
    	MatchCriteria matchcriteria = new MatchCriteria(expression, value);
		matchcriteria.setNegated(negated);
		matchcriteria.setMode(MatchMode.REGEX);
		return matchcriteria;
   	}
}

/*
name=character
description=A single character.
example={code:sql}'a'{code}
*/
Character charVal(ParseInfo info, String type) :
{
	String escStr = null;
}
{
	escStr=stringVal()
	{
		if (escStr.length() != 1) {
			throw new ParseException(QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30398, type, escStr)); //$NON-NLS-1$
		}
		return Character.valueOf(escStr.charAt(0));
	}
}

/*
name=between predicate
description=A comparison between two values.
example={code:sql}BETWEEN 1 AND 5{code}
*/
BetweenCriteria betweenCrit(ParseInfo info, Expression expression) :
{
	Expression lowerExpression = null, upperExpression = null;
	boolean negated = false;
}
{
	[<NOT> {negated=true;}]
	<BETWEEN>
	lowerExpression = commonValueExpression(info)
	<AND>
	upperExpression = commonValueExpression(info)
	
	{
		BetweenCriteria criteria = new BetweenCriteria(expression, lowerExpression, upperExpression);
		criteria.setNegated(negated);
		return criteria;
	}
}

/*
name=is null predicate
description=A null test.
example={code:sql}IS NOT NULL{code}
*/
IsNullCriteria isNullCrit(ParseInfo info, Expression expression) :
{
	boolean negated = false;
	IsNullCriteria criteria = null;
}
{
	<IS>
	[<NOT> {negated = true;}]
	<NULL>

	{
	criteria = new IsNullCriteria(expression);
	criteria.setNegated(negated);
	return criteria;
	}
}

/*
name=in predicate
description=A comparison with multiple values.
example={code:sql}IN (1, 5){code}
*/
AbstractSetCriteria setCrit(ParseInfo info, Expression expression) :
{
	Expression value = null;
	List setList = new ArrayList();
	QueryCommand command = null;
	ExistsCriteria.SubqueryHint hint = null;
	boolean negated = false;
	AbstractSetCriteria criteria = null;
}
{
	[<NOT> {negated = true;}]
	<IN>
	(
		LOOKAHEAD(subquery(info)) { hint = getSubqueryHint(getToken(1)); } (command = subquery(info)) |
		(
			<LPAREN>
			value = commonValueExpression(info)
			{
				setList.add(value);
			}
			( <COMMA>
				value = commonValueExpression(info)
				{
					setList.add(value);
				}
			)*
			<RPAREN>
		)
	)
	{
	    if (command != null) {
			SubquerySetCriteria ssc = new SubquerySetCriteria(expression, command);
			ssc.setSubqueryHint(hint);
			criteria = ssc;
		} else {
		    criteria = new SetCriteria(expression, setList);
		}
	    criteria.setNegated(negated);
	    return criteria;
   	}
}

/*
name=exists predicate
description=A test if rows exist.
example={code:sql}EXISTS (SELECT col FROM tbl){code}
*/
ExistsCriteria existsCriteria(ParseInfo info) :
{
    QueryCommand subquery = null;
    ExistsCriteria.SubqueryHint hint = null;
}
{
	<EXISTS> { hint = getSubqueryHint(getToken(1)); } 
	subquery = subquery(info)

	{
		ExistsCriteria existsCrit = new ExistsCriteria(subquery);
		existsCrit.setSubqueryHint(hint);
	    return existsCrit;
   	}
}

/*
name=group by clause
description=Defines the grouping columns
example={code:sql}GROUP BY col1, col2{code}
*/
GroupBy groupBy(ParseInfo info) :
{
	List<Expression> expressions = null;
	boolean rollup = false;
}
{
	<GROUP> <BY>
	(<ROLLUP><LPAREN>
     expressions = expressionList(info)
     <RPAREN>
     {
       rollup = true;
     }
     |
     expressions = expressionList(info)
    )	
	{
		GroupBy result = new GroupBy(expressions);
		result.setRollup(rollup);
		return result;
	}
}

/*
name=having clause
description=Search condition applied after grouping.
example={code:sql}HAVING max(col1) = 5{code}
*/
Criteria having(ParseInfo info) :
{
    Criteria criteria = null;
}
{
    <HAVING>
	criteria = criteria(info)
	{
		return criteria;
	}
}

/*
name=order by clause
description=Specifices row ordering.
example={code:sql}ORDER BY x, y DESC{code}
*/
OrderBy orderby(ParseInfo info) :
{
    OrderBy orderby = new OrderBy();
    OrderByItem item = null;
}
{
	<ORDER> <BY>
	item = sortSpecification(info)
	{
    	orderby.getOrderByItems().add(item);
	}
	(<COMMA>
		item = sortSpecification(info)
		{
    		orderby.getOrderByItems().add(item);
		}
	)*
	{
	    return orderby;
	}
}

/*
name=sort specification
description=Defines how to sort on a particular expression
example={code:sql}col1 NULLS FIRST{code}
*/
OrderByItem sortSpecification(ParseInfo info) :
{
    Expression ex = null;
    boolean ascending = true;
    org.teiid.language.SortSpecification.NullOrdering nullOrdering = null;    
}
{
	ex=sortKey(info) 
	[<ASC> | <DESC> {ascending=false;}]
	[<NULLS> (<FIRST> {nullOrdering=org.teiid.language.SortSpecification.NullOrdering.FIRST;} | <LAST> {nullOrdering=org.teiid.language.SortSpecification.NullOrdering.LAST;})] 
	{
    	OrderByItem item = new OrderByItem(ex, ascending);
    	if (nullOrdering != null) {
    		item.setNullOrdering(nullOrdering);
    	}
    	return item;
	}
}

/*
name=sort key
description=A sort expression.
example={code:sql}col1{code}
*/
Expression sortKey(ParseInfo info) :
{
    Expression ex = null;
}
{
    ex=expression(info)
    {
        //legacy support check for positional constants
        if (ex instanceof Constant) {
        	boolean valid = false;
            Constant c = (Constant)ex;
            if (c.getValue() instanceof Integer) {
                Integer val = (Integer)c.getValue();
                valid = val.intValue() > 0;
            }      
            if (!valid) {
            	throw new ParseException(QueryPlugin.Util.getString("SQLParser.non_position_constant", ex)); //$NON-NLS-1$
            }
        } 
        return ex;
    }
}

/*
name=integer parameter
description=A literal integer or parameter reference to an integer.
example={code:sql}?{code}
*/
Expression intParam(ParseInfo info) :
{
   Expression ex = null;
   Integer val = null;
}
{
   val = intVal() 
   {
     return new Constant(val, DataTypeManager.DefaultDataClasses.INTEGER);
   }
   |
   ex = unsignedValueExpressionPrimary(info)
   {
     return ex;
   }
}

/*
name=limit clause
description=Limits and/or offsets the resultant rows.
example={code:sql}LIMIT 2{code}
*/
Limit limit(ParseInfo info) :
{
	Expression limit = null;
	Expression offset = null;
	Token t = null;
}
{
    {t = getToken(1);}
    ((<LIMIT> offset = intParam(info)
	[<COMMA> limit = intParam(info)])
	{
	  if (limit == null) {
	    limit = offset;
	    offset = null;
	  }
	}
	|
	(<OFFSET> offset = intParam(info) (<ROW>|<ROWS>) 
    [limit = fetchLimit(info)])
    |
    (limit = fetchLimit(info)))
	{
		Limit result = new Limit(offset, limit);
		result.setStrict(!isNonStrictHint(t));
		return result;
	}
}

/*
name=fetch clause
description=ANSI limit.
example={code:sql}FETCH FIRST 1 ROWS ONLY{code}
*/
Expression fetchLimit(ParseInfo info) :
{
    Expression limit = null;
}
{
    <FETCH> (<FIRST> | <NEXT>) [limit = intParam(info)] (<ROW>|<ROWS>) <ONLY>
    {
      if (limit == null) {
        return new Constant(1, DataTypeManager.DefaultDataClasses.INTEGER); 
      }
      return limit;
    }
}

/*
name=option clause
description=Specifies query options.
example={code:sql}OPTION MAKEDEP tbl{code}
*/
Option option(ParseInfo info) :
{
	String id = null;
	Token nocache = null;
	Option option = new Option();
	Option.MakeDep makedep = null;
}
{
	<OPTION>
	(
		<MAKEDEP>		
		id=id(null) makedep = makedepOptions()
		{
            option.addDependentGroup(id, makedep);
		}
		(<COMMA>
			id=id(null) makedep = makedepOptions()
			{
    	        option.addDependentGroup(id, makedep);
	        }
		)* |
		<MAKENOTDEP>		
		id=id(null)
		{
            option.addNotDependentGroup(id);
		}
		(<COMMA>
			id=id(null)
			{
                option.addNotDependentGroup(id);
	        }
		)* |
		
		nocache	= <NOCACHE>		
		[id=id(null)
		{
            option.addNoCacheGroup(id);
		}
		(<COMMA>
			id=id(null)
			{
                option.addNoCacheGroup(id);
	        }
		)*]
	)*

	{
		option.setNoCache( (nocache != null) );
		return option;
	}
}

/*
name=expression
description=A value.
example={code:sql}col1{code}
*/
Expression expression(ParseInfo info) :
{
	Expression expression = null;
}
{
    expression = criteria(info)
	{
		if (expression instanceof ExpressionCriteria) {
			return ((ExpressionCriteria)expression).getExpression();
		}
		return expression;
	}
}

/*
name=common value expression
description=Establishes the precedence of concat.
example={code:sql}'a' || 'b'{code}
*/
Expression commonValueExpression(ParseInfo info) :
{
	Expression leftExpression = null;
	Expression rightExpression = null;
}
{
	(	leftExpression=plusExpression(info)
		(
			<CONCAT_OP>
			rightExpression=plusExpression(info)
			{
				leftExpression = new Function("||", new Expression[] {leftExpression, rightExpression}); //$NON-NLS-1$
				rightExpression = null;				
			}
		)*
	)

	{
		return leftExpression;
	}
}

/*
name=numeric value expression
example={code:sql}1 + 2{code}
*/
Expression plusExpression(ParseInfo info) :
{
	Expression leftExpression = null;
	Expression rightExpression = null;
	String operator = null;
}
{
	(	leftExpression=timesExpression(info)
		(
			operator=plusMinus()
			rightExpression=timesExpression(info)
			{
				leftExpression = new Function(operator, new Expression[] { leftExpression, rightExpression });
				rightExpression = null;
				operator = null;
			}
		)*
	)

	{
		return leftExpression;
	}
}

/*
name=plus or minus
description=The + or - operator.
example={code:sql}+{code}
*/
String plusMinus() :
{
}
{
	(<PLUS> | <MINUS>)
	{
		return getToken(0).image;
	}
}

/*
name=term
description=A numeric term
example={code:sql}1 * 2{code}
*/
Expression timesExpression(ParseInfo info) :
{
	Expression leftExpression = null;
	Expression rightExpression = null;
	String operator = null;
}
{
	(	leftExpression=valueExpressionPrimary(info)
		(
			operator=timesOperator()
			rightExpression=valueExpressionPrimary(info) 
			{
				leftExpression = new Function(operator, new Expression[] {leftExpression, rightExpression});
				rightExpression = null;
				operator = null;
			}
		)*
	)

	{
		return leftExpression;
	}
}

/*
name=star or slash
description=The * or / operator.
example={code:sql}/{code}
*/
String timesOperator() :
{
	Token operator = null;
}
{
	(	operator = <STAR> |
		operator = <SLASH>
	)

	{
		return operator.image;
	}
}

/*
name=value expression primary
description=A simple value expression.
example={code:sql}+col1{code}
*/
Expression valueExpressionPrimary(ParseInfo info) :
{
	String strVal = null;
	Expression ex = null;
	Expression indexExpr = null;
    List<Expression> indexExprs = null;
}
{
	// Literal
	(ex=nonNumericLiteral()
	| [strVal = plusMinus()] 
	  (ex = unsignedNumericLiteral(strVal)
	   |
	   (ex = unsignedValueExpressionPrimary(info)
	    //array element reference expressions
        (<LSBRACE> indexExpr = plusExpression(info) <RSBRACE> {
           indexExprs = arrayExpressions(indexExprs, indexExpr);
           }
        )*
        {
          if (indexExprs != null) {
            for (Expression expr : indexExprs) {
                ex = new Function("array_get", new Expression[] {ex, expr});
            }
          }
        }
	   ) {
         if (strVal != null && strVal.charAt(0) == '-') {
            ex = new Function("*", new Expression[] { new Constant(-1), ex });
         }
        }
	   )
	 )
	 {
		return ex;	 
	 }  
}

/*
name=parameter reference
description=A parameter reference to be bound later.
example={code:sql}?{code}
*/
Reference parameterReference(ParseInfo info) :
{
    Integer index = null;
}
{
    <QMARK> {return new Reference(info.referenceCount++);}
    |
    (<DOLLAR> index = intVal() {return new Reference(index -1); })
}

Expression unescapedFunction(ParseInfo info) :
{
    Expression expression = null;
    Expression condition = null;
    ParsedDataType type = null;
    
}
{
    // Aggregate function
    (LOOKAHEAD(2) ((expression=textAgg(info) | expression=aggregateSymbol(info) | expression=orderedAgg(info)) [condition = filterClause(info)] [expression = windowSpecification(expression, info)])
    |
    LOOKAHEAD(2) (expression=analyticAggregateSymbol(info) [condition = filterClause(info)] expression = windowSpecification(expression, info))
    |
    // Function
    LOOKAHEAD(2) (expression=function(info) [expression = windowSpecification(expression, info)]))
    {
        if (condition != null) {
            if (expression instanceof WindowFunction) {
                ((WindowFunction)expression).getFunction().setCondition(condition);
            } else {
                ((AggregateSymbol)expression).setCondition(condition);
            }
        }
        return expression;
    }
    |
	(			
		<XMLCAST>
		<LPAREN>
		expression = expression(info)
		<AS>
		type = parseDataType()
		<RPAREN>				
    ) { return new XMLCast(expression, DataTypeManager.getDataTypeClass(type.type)); }
}

/*
name=nested expression
description=An expression nested in parens
example={code:sql}(1){code}
*/
Expression nestedExpression(ParseInfo info) :
{
    Expression expression = null;
    Expression arrayExpression = null;
    List<Expression> arrayExpressions = null;
}
{
    ( <LPAREN> 
        [expression = expression(info)
        (LOOKAHEAD(2) <COMMA>arrayExpression = expression(info) {
          if (expression != null) {
             arrayExpressions = arrayExpressions(arrayExpressions, expression);
             expression = null;
          } 
          arrayExpressions = arrayExpressions(arrayExpressions, arrayExpression);})*]
        [<COMMA> {arrayExpressions = arrayExpressions(arrayExpressions, expression);} ]
      <RPAREN>
    )
    {
        if (arrayExpressions != null) {
            expression = new Array(arrayExpressions);
        } else if (expression == null) {
            expression = new Array(new ArrayList<Expression>(0));
        }
        return expression;
    }
}    

/*
name=unsigned value expression primary
description=An unsigned simple value expression.
example={code:sql}col1{code}
*/
Expression unsignedValueExpressionPrimary(ParseInfo info) :
{
	Expression expression = null;
	Token symbol = null;
	QueryCommand subquery = null;
	ExistsCriteria.SubqueryHint hint = null;
}
{
    (
		// Reference
		expression = parameterReference(info)
		|
		// Escaped function
		(	<ESCAPEDFUNCTION>
			expression=function(info)
			<RBRACE>
		)
		|
		LOOKAHEAD(2) expression = unescapedFunction(info)		
		|
		// ElementSymbol
		((symbol=<ID> {
				String symbolImage = symbol.image;
				if(isStringLiteral(symbolImage, info)) {
				    return new Constant(normalizeStringLiteral(symbolImage));
				}  
			} | symbol=nonReserved()) {expression = new ElementSymbol(normalizeId(symbol.image));} 
		)
		|
		LOOKAHEAD(subquery(info)) ({ hint = getSubqueryHint(getToken(1)); } subquery = subquery(info) {
		   ScalarSubquery ss = new ScalarSubquery(subquery);
		   ss.setSubqueryHint(hint);
		   expression = ss;
		})
		|
		expression = nestedExpression(info)
		|
		// Searched CASE expressions
		LOOKAHEAD(2) expression = searchedCaseExpression(info)
		|
		// Non-searched CASE expressions
		expression = caseExpression(info)
	)
	{
		return expression;
	}
}

/*
name=window specification
description=The window specification for an analytical or windowed aggregate function.
example={code:sql}OVER (PARTION BY col1){code}
*/
Expression windowSpecification(Expression agg, ParseInfo info) :
{
	List<Expression> partitionList = null;
	OrderBy orderBy = null;
}
{
	<OVER> { 
	  if (!(agg instanceof AggregateSymbol)) {
	    if (!(agg instanceof Function)) {
	      throw new ParseException(QueryPlugin.Util.getString("SQLParser.invalid_window", agg)); //$NON-NLS-1$
	    }
	    Function f = (Function)agg;
	    agg = new AggregateSymbol(f.getName(), false, f.getArgs(), null);
	  }
    }
    <LPAREN> 
    [<PARTITION> <BY> partitionList = expressionList(info)]
    [orderBy = orderby(info)]
    <RPAREN>
    {
    	WindowFunction result = new WindowFunction();
    	WindowSpecification ws = new WindowSpecification();
    	result.setFunction((AggregateSymbol)agg);
    	ws.setPartition(partitionList);
    	ws.setOrderBy(orderBy);
    	result.setWindowSpecification(ws);
    	return result;
    }
}

/*
name=case expression
description=If/then/else chain using a common search predicand.
example={code:sql}CASE col1 WHEN 'a' THEN 1 ELSE 2{code}
*/
CaseExpression caseExpression(ParseInfo info) :
{
	Expression expression = null, whenExpression = null, thenExpression = null, elseExpression = null;
	ArrayList whenExpressions = new ArrayList(2);
	ArrayList thenExpressions = new ArrayList(2);
}
{
	<CASE>
	expression = expression(info)
	(
		<WHEN>
		whenExpression = expression(info)
		<THEN>
		thenExpression = expression(info)
		{
			whenExpressions.add(whenExpression);
			thenExpressions.add(thenExpression);
		}
	)+
	[<ELSE> elseExpression = expression(info)]
	<END>
	{
		CaseExpression caseExpr = new CaseExpression(expression, whenExpressions, thenExpressions);
		caseExpr.setElseExpression(elseExpression);
		return caseExpr;
	}
}

/*
name=searched case expression
description=If/then/else chain using multiple search conditions.
example={code:sql}CASE WHEN x = 'a' THEN 1 WHEN y = 'b' THEN 2{code}
*/
SearchedCaseExpression searchedCaseExpression(ParseInfo info) :
{
	Expression thenExpression = null, elseExpression = null;
	Criteria whenCriteria = null;
	ArrayList whenCriteriaList = new ArrayList(2);
	ArrayList thenExpressions = new ArrayList(2);
}
{
	<CASE>
	(
		<WHEN>
		whenCriteria = criteria(info)
		<THEN>
		thenExpression = expression(info)
		{
			whenCriteriaList.add(whenCriteria);
			thenExpressions.add(thenExpression);
		}
	)+
	
	[<ELSE> elseExpression = expression(info)]
	<END>
	{
		SearchedCaseExpression caseExpr = new SearchedCaseExpression(whenCriteriaList, thenExpressions);
		caseExpr.setElseExpression(elseExpression);
		return caseExpr;
	}
}

/*
name=function
description=Calls a scalar function.
example={code:sql}func('1', col1){code}
*/
Expression function(ParseInfo info) :
{
	String funcName = null;
    
	Expression expression = null;
	ArrayList args = new ArrayList(2);
	ArrayList otherArgs = null;
	Token funcToken = null;
	Boolean distinct = null;
	OrderBy orderBy = null;
	Expression condition = null;
	String literal = null;
	ParsedDataType type = null;
}
{
	((	funcToken = <CONVERT>
		<LPAREN>
		expression = expression(info)
		{
			args.add(expression);
		}
		<COMMA>
		type = parseDataType()	
		{
			args.add(new Constant(type.type));
		}
		<RPAREN> 
	)
	|
	(			
		funcToken = <CAST>
		<LPAREN>
		expression = expression(info)
		{
			args.add(expression);
		} 
		<AS>
		type = parseDataType()
		{
			args.add(new Constant(type.type));
		}
		<RPAREN>				
	)
	|
	LOOKAHEAD(2) (
	   funcToken = <SUBSTRING>
	   <LPAREN>
	   expression = expression(info)
	   {
	      args.add(expression);
	   }
	   ((<FROM> expression = expression(info)
	   {
	      args.add(expression);
	   }
  	   [<FOR> expression = expression(info)
	    {
	      args.add(expression);
	    }
	   ])|(<COMMA> otherArgs = expressionList(info)) {args.addAll(otherArgs);})
	   <RPAREN>
	)
	|
	LOOKAHEAD(2) (
	   funcToken = <EXTRACT>
	   <LPAREN>
	   (funcToken = <YEAR> | 
	    funcToken = <MONTH> |
	    funcToken = <DAY> |
	    funcToken = <HOUR> |
	    funcToken = <MINUTE> |
	    funcToken = <SECOND>)
	   <FROM> expression = expression(info)
	   {
	   	  if (funcToken.image.equalsIgnoreCase("DAY")) {
	         funcName = "DAYOFMONTH";
	      }  	      
	      args.add(expression);
	   }
	   <RPAREN>
	)
	|
	LOOKAHEAD(2) (
	   funcToken = <TRIM>
	   <LPAREN>
	   { funcName = funcToken.image; funcToken = null; expression = null;}
	   [
	    LOOKAHEAD((<LEADING>|<TRAILING>|<BOTH>)|(expression(info)<FROM>)) (((funcToken = <LEADING>|
	      funcToken = <TRAILING>|
	      funcToken = <BOTH>)
	    [expression = expression(info)])
	    |
	    expression = expression(info))
		<FROM>
	   ]
  	   {
	      if (funcToken == null) {
	      	  args.add(new Constant("BOTH"));
	      } else {
	          args.add(new Constant(funcToken.image));
	      }
	      if (expression == null) {
	          args.add(new Constant(" "));
	      } else {
	          if (expression instanceof Constant) {
	              Object value = ((Constant)expression).getValue();
	              if (value instanceof String && ((String)value).length() != 1) {
				      throw new ParseException(QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30398, "TRIM CHAR", value)); //$NON-NLS-1$
	              }
	          }
	          args.add(expression);
	      }
	   }
	   expression = expression(info)
	   {
	      args.add(expression);
	   }
	   <RPAREN>
	)
	|
	LOOKAHEAD(2) (	
		(funcToken = <TO_CHARS> | funcToken = <TO_BYTES>)
		<LPAREN>
		expression = expression(info) 
		{		
			args.add(expression);
		}
		<COMMA>
		literal = stringVal()
		{
			args.add(new Constant(literal));
		} 
		[<COMMA> expression = expression(info) { args.add(expression); }]
		<RPAREN>				
	)
	|
	LOOKAHEAD(2) (	
		(funcToken = <TIMESTAMPADD> | funcToken = <TIMESTAMPDIFF>)
		<LPAREN>
		expression = intervalType() 
		{		
			args.add(expression);
		}
		<COMMA>
		expression = expression(info)
		{
			args.add(expression);
		} 
		<COMMA>
		expression = expression(info)
		{
			args.add(expression);
		} 
		<RPAREN>				
	)
	|
	LOOKAHEAD(2) expression = queryString(info)
	  {
		return expression;
	  }		
	|
	(
		(funcToken = <LEFT> | funcToken = <RIGHT> | funcToken = <CHAR> | funcToken = <USER> 
		                    | funcToken = <YEAR> | funcToken = <MONTH> | funcToken = <HOUR>
		                    | funcToken = <MINUTE> | funcToken = <SECOND> | funcToken = <XMLCONCAT>
		                    | funcToken = <XMLCOMMENT> | funcToken = <XMLTEXT>)
        <LPAREN>
		[args = expressionList(info)]
		<RPAREN>
	)		
	|
	LOOKAHEAD(<TRANSLATE>|<INSERT> <LPAREN>) (
		(funcToken = <TRANSLATE> | funcToken = <INSERT>)
		<LPAREN>
		[args = expressionList(info)]
		<RPAREN>
	)  
	| expression = xmlParse(info)
	  {
	  	return expression;
	  }
	| expression = xmlElement(info)
	  {
	  	return expression;
	  }
	| ( funcToken = <XMLPI>
		<LPAREN>
		(
		 [LOOKAHEAD(1) <NAME>] literal = id(null)
		)
		{
			args.add(new Constant(literal));	
		}
		[
			<COMMA> expression = expression(info)
			{
				args.add(expression);	
			}	
		]
		<RPAREN>
	)
	|	expression = xmlForest(info)
		{
			return expression;
		}
	|	LOOKAHEAD(<JSONOBJECT>) expression = jsonObject(info)
		{
			return expression;
		}
	|	expression = xmlSerialize(info)
		{
			return expression;
		}
	|	expression = xmlQuery(info)
		{
			return expression;
		}
	|   
	(	funcName = id(null)
		<LPAREN>
		[<ALL> { distinct = false; } | <DISTINCT> {distinct = true;} ]
		[ args = expressionList(info) ]
		[ orderBy = orderby(info) ]
		<RPAREN>
		[condition = filterClause(info)]
	))		
	{
		if(funcName == null) { 
			funcName = funcToken.image;
		}
		if (distinct != null || orderBy != null || condition != null || "string_agg".equalsIgnoreCase(funcName)) {
			AggregateSymbol as = new AggregateSymbol(funcName, distinct!=null?distinct:false, (Expression[])args.toArray(new Expression[args.size()]), orderBy);
			as.setCondition(condition);
			return as;
		}	
		return new Function(funcName, (Expression[])args.toArray(new Expression[args.size()]));
	}
}

/*
name=xml parse
description=Parses the given value as XML.
example={code:sql}XMLPARSE(DOCUMENT doc WELLFORMED){code}
*/
XMLParse xmlParse(ParseInfo info) :
{
	Expression expr = null;
	boolean doc = false;
	boolean wellformed = false;
}
{
	<XMLPARSE><LPAREN>
	(<DOCUMENT> {doc = true;}| <CONTENT> {doc = false;})
	expr = expression(info)
	[<WELLFORMED>
	 {
	   wellformed = true;
	 }
	]
	<RPAREN>
	{
		XMLParse result = new XMLParse();
		result.setDocument(doc);
		result.setExpression(expr);
		result.setWellFormed(wellformed);
		return result;
	}
}

/*
name=querystring function
description=Produces a URL query string from the given arguments.
example={code:sql}QUERYSTRING('path', col1 AS opt, col2 AS val){code}
*/
QueryString queryString(ParseInfo info) :
{
	Expression path = null;
	DerivedColumn arg = null;
	ArrayList<DerivedColumn> args = new ArrayList<DerivedColumn>(2);
}
{
 	<QUERYSTRING> <LPAREN>
 	path = expression(info)
	(<COMMA> arg=derivedColumn(info)
	 {
		args.add(arg);
		arg = null;
	 }
	)*
	<RPAREN>  		
	{
		return new QueryString(path, args);
	}
}

/*
name=xml element
description=Creates an XML element.
example={code:sql}XMLELEMENT(NAME "root", child){code}
*/
XMLElement xmlElement(ParseInfo info) :
{
	String name = null;
	ArrayList content = new ArrayList(2);
	XMLNamespaces xmlNamespaces = null;
	XMLAttributes xmlAttributes = null;
	Expression expression = null;
}
{
	<XMLELEMENT> <LPAREN>
	(
	 [LOOKAHEAD(1) <NAME>] name = id(null)
	)
	[
		LOOKAHEAD(2) <COMMA> xmlNamespaces = xmlNamespaces(info)
	]
	[
		LOOKAHEAD(2) <COMMA> xmlAttributes = xmlAttributes(info)
	]
	(<COMMA> expression=expression(info)
	{
		content.add(expression);
	}
	)*
	<RPAREN>
	{
		XMLElement result = new XMLElement(name, content);
		result.setNamespaces(xmlNamespaces);
		result.setAttributes(xmlAttributes);
		return result;
	}
}	

/*
name=xml attributes
description=Creates attributes for the containing element.
example={code:sql}XMLATTRIBUTES(col1 AS attr1, col2 AS attr2){code}
*/
XMLAttributes xmlAttributes(ParseInfo info) :
{
	DerivedColumn expression = null;
	ArrayList<DerivedColumn> args = new ArrayList<DerivedColumn>(2);
}
{
 	<XMLATTRIBUTES> <LPAREN>
	expression = derivedColumn(info)
	{
		args.add(expression);
	}
	(<COMMA> expression=derivedColumn(info)
	 {
		args.add(expression);
	 }
	)*
	<RPAREN>  		
	{
		return new XMLAttributes(args);
	}
}

/*
name=json object
description=Produces a JSON object containing name value pairs.
example={code:sql}JSONOBJECT(col1 AS val1, col2 AS val2){code}
*/
JSONObject jsonObject(ParseInfo info) :
{
	ArrayList<DerivedColumn> args = null;
}
{
 	<JSONOBJECT> <LPAREN>
 	args = derivedColumnList(info)
 	<RPAREN>  		
	{
		JSONObject result = new JSONObject(args);
		return result;
	}
}

/*
name=derived column list
description=a list of name value pairs
example={code:sql}col1 AS val1, col2 AS val2{code}
*/
ArrayList<DerivedColumn> derivedColumnList(ParseInfo info) :
{
	DerivedColumn expression = null;
	ArrayList<DerivedColumn> args = new ArrayList<DerivedColumn>(2);
}
{
	expression = derivedColumn(info)
	{
		args.add(expression);
	}
	(<COMMA> expression=derivedColumn(info)
	 {
		args.add(expression);
	 }
	)*
	{ 
		return args;
	}
}

/*
name=xml forest
description=Produces an element for each derived column.
example={code:sql}XMLFOREST(col1 AS ELEM1, col2 AS ELEM2){code}
*/
XMLForest xmlForest(ParseInfo info) :
{
	DerivedColumn expression = null;
	XMLNamespaces xmlNamespaces = null;
	ArrayList<DerivedColumn> args = null;
}
{
 	<XMLFOREST> <LPAREN>
	[
		xmlNamespaces = xmlNamespaces(info) <COMMA>
	]
	args = derivedColumnList(info)
	<RPAREN>  		
	{
		XMLForest result = new XMLForest(args);
		result.setNamespaces(xmlNamespaces);
		return result;
	}
}		
		
/*
name=xml namespaces
description=Defines XML namespace URI/prefix combinations
example={code:sql}XMLNAMESPACES('http://foo' AS foo){code}
*/
XMLNamespaces xmlNamespaces(ParseInfo info) :
{
	ArrayList namespaces = new ArrayList(2);
	XMLNamespaces.NamespaceItem item = null;
}
{
 	<XMLNAMESPACES> <LPAREN>
	item = namespaceItem()
	{
		namespaces.add(item);
	}
	(<COMMA> item = namespaceItem()
	 {
		 namespaces.add(item);
	 }
	)*
	<RPAREN>  		
	{
		return new XMLNamespaces(namespaces);
	}
}

/*
name=xml namespace element
description=An xml namespace
example={code:sql}NO DEFAULT{code}
*/
XMLNamespaces.NamespaceItem namespaceItem() :
{
	String uri = null;
	String prefix = null;
}
{
	(
		uri = stringVal() <AS> prefix = id(null)
		{
			return new XMLNamespaces.NamespaceItem(uri, prefix);
		}
	) | (
		<NO> <DEFAULT_KEYWORD>
		{
			return new XMLNamespaces.NamespaceItem();
		}
	) | (
		<DEFAULT_KEYWORD> uri = stringVal()
		{
			return new XMLNamespaces.NamespaceItem(uri);
		}
	)
}

/*
name=simple data type
description=A non-collection data type.
example={code:sql}STRING{code}
*/
ParsedDataType parseDataTypePrimary() :
{
	Token typeToken = null;
	Integer length = null;
	Integer scale = null; 
	boolean precision = true;
}
{
	(
		(typeToken = <STRING> ([<LPAREN>length=intVal()<RPAREN>]) {precision = false;} ) |
		(typeToken = <VARCHAR> ([<LPAREN>length=intVal()<RPAREN>]) {precision = false;} ) | 
		typeToken = <BOOLEAN> | 
		typeToken = <BYTE> | 
		typeToken = <TINYINT> |
		typeToken = <SHORT> | 
		typeToken = <SMALLINT> |
		(typeToken = <CHAR> ([<LPAREN>length=intVal()<RPAREN>]) {precision = false;} ) | 
		typeToken = <INTEGER> | 
		typeToken = <LONG> | 
		typeToken = <BIGINT> |
		(typeToken = <BIGINTEGER> [<LPAREN>length=intVal()<RPAREN>]) | 
		typeToken = <FLOAT> |
		typeToken = <REAL> | 
		typeToken = <DOUBLE> | 
		(typeToken = <BIGDECIMAL> [<LPAREN>length=intVal()[<COMMA>scale=intVal()]<RPAREN>])|
		(typeToken = <DECIMAL> [<LPAREN>length=intVal()[<COMMA>scale=intVal()]<RPAREN>])| 
		typeToken = <DATE> | 
		typeToken = <TIME> |
		typeToken = <TIMESTAMP> |
		(typeToken = <OBJECT> ([<LPAREN>length=intVal()<RPAREN>]) {precision = false;} )|
		(typeToken = <BLOB> ([<LPAREN>length=intVal()<RPAREN>]) {precision = false;} )|
		(typeToken = <CLOB> ([<LPAREN>length=intVal()<RPAREN>]) {precision = false;} )|
		(typeToken = <VARBINARY> ([<LPAREN>length=intVal()<RPAREN>]) {precision = false;} ) |
		typeToken = <GEOMETRY> |
		typeToken = <XML>
	)
	{
	    String typeName = typeToken.image;
		if (scale != null){
			return new ParsedDataType(typeName, length, scale, precision);
		}
		else if (length != null){
			return new ParsedDataType(typeName, length, precision);
		}
		return new ParsedDataType(typeName);
	}
}

/*
name=data type
description=A data type.
example={code:sql}STRING[]{code}
*/
ParsedDataType parseDataType() :
{
    int arraySuffixes = 0;
    ParsedDataType type = null;
}
{
    type = parseDataTypePrimary()
    (<LSBRACE><RSBRACE> {arraySuffixes++;})*
    {
        if (arraySuffixes > 0) {
           type.type += StringUtil.join(Collections.nCopies(arraySuffixes, "[]"), "");
        }
        return type;
    }
}

/*
name=time interval
description=A time interval keyword.
example={code:sql}SQL_TSI_HOUR{code}
*/
Constant intervalType() : 
{
	Token type = null;
}
{
	(
		type = <SQL_TSI_FRAC_SECOND>
	   |type = <SQL_TSI_SECOND>
	   |type = <SQL_TSI_MINUTE>
	   |type = <SQL_TSI_HOUR>
	   |type = <SQL_TSI_DAY>
	   |type = <SQL_TSI_WEEK>
	   |type = <SQL_TSI_MONTH>
	   |type = <SQL_TSI_QUARTER>
	   |type = <SQL_TSI_YEAR>
	)
	{
		return new Constant(type.image);
	}
}

/*
name=non numeric literal
description=An escaped or simple non numeric literal.
example={code:sql}'a'{code}
*/
Constant nonNumericLiteral() :
{
    Token t = null;
    String strVal = "";
    Class escapeType = null;
    Constant constant = null;
}
{
	(	
		strVal=stringVal()	{ 
			constant = new Constant(strVal, DataTypeManager.DefaultDataClasses.STRING);
		} |
		t=<BINARYSTRINGVAL> { constant = new Constant(new BinaryType(javax.xml.bind.DatatypeConverter.parseHexBinary(t.image.substring(2, t.image.length() - 1))), DataTypeManager.DefaultDataClasses.VARBINARY); } |
		t=<FALSE>    { constant = new Constant(Boolean.FALSE, DataTypeManager.DefaultDataClasses.BOOLEAN); } |
		t=<TRUE>     { constant = new Constant(Boolean.TRUE, DataTypeManager.DefaultDataClasses.BOOLEAN); }  |
        t=<UNKNOWN>  { constant = new Constant(null, DataTypeManager.DefaultDataClasses.BOOLEAN); }       |
        t=<NULL>     { constant = new Constant(null); }          |
        
        ( <ESCAPEDTYPE> { 
            String image = getToken(0).image; 
        	if (StringUtil.endsWithIgnoreCase(image, "b")) {
        		escapeType=DataTypeManager.DefaultDataClasses.BOOLEAN;
        	} else if (StringUtil.endsWithIgnoreCase(image, "ts")) {
        		escapeType=DataTypeManager.DefaultDataClasses.TIMESTAMP;
        	} else if (StringUtil.endsWithIgnoreCase(image, "d")) {
        		escapeType=DataTypeManager.DefaultDataClasses.DATE;
        	} else {
        		escapeType=DataTypeManager.DefaultDataClasses.TIME;
        	}
		  } strVal=stringVal() { 
	        	try {
	        		constant = new Constant(DataTypeManager.transformValue(strVal, escapeType), escapeType);
	        	} catch (TransformationException e) {
	        		throw new ParseException(e.getMessage()); //$NON-NLS-1$
	        	}
	        } <RBRACE> ) 

	)
	{
		return constant;
	}
}

/*
name=unsigned numeric literal
description=An unsigned numeric literal value.
example={code:sql}1.234{code}
*/
Constant unsignedNumericLiteral(String strVal) :
{
    Token t = null;
}
{
		(t=<UNSIGNEDINTEGER> 	{ 
			try { 
			    strVal = prependSign(strVal, t.image);
				// First try as integer - this should nearly always work
				return new Constant(Integer.valueOf(strVal), DataTypeManager.DefaultDataClasses.INTEGER); 				
			} catch(NumberFormatException e1) { 
				try { 
					// Second try as a long
					return new Constant(Long.valueOf(strVal), DataTypeManager.DefaultDataClasses.LONG);
				} catch(NumberFormatException e2) {
					try {
						// Third try as a biginteger
						return new Constant(new BigInteger(strVal), DataTypeManager.DefaultDataClasses.BIG_INTEGER);
					} catch(NumberFormatException e3) {
						Object[] params = new Object[] { strVal };
						throw new ParseException(QueryPlugin.Util.getString("SQLParser.Integer_parse", params)); //$NON-NLS-1$
					}
				}
			}			
		}	| t=<FLOATVAL>	{ 
		    strVal = prependSign(strVal, t.image);
			try { 
				return new Constant(Double.valueOf(strVal), DataTypeManager.DefaultDataClasses.DOUBLE); 
			} catch(NumberFormatException e) {
				Object[] params = new Object[] { strVal };
				throw new ParseException(QueryPlugin.Util.getString("SQLParser.Float_parse", params)); //$NON-NLS-1$
			}
		}  | t=<DECIMALVAL>	{ 
     		strVal = prependSign(strVal, t.image);
			try { 
			    if (DECIMAL_AS_DOUBLE) {
			    	return new Constant(Double.valueOf(strVal), DataTypeManager.DefaultDataClasses.DOUBLE); 
				} else {
			    	return new Constant(new java.math.BigDecimal(strVal), DataTypeManager.DefaultDataClasses.BIG_DECIMAL); 
				}
			} catch(NumberFormatException e) {
				Object[] params = new Object[] { t.image };
				throw new ParseException(QueryPlugin.Util.getString("SQLParser.decimal_parse", params)); //$NON-NLS-1$
			}
		} )
}

/*
unused=true
*/
 void parseMetadata(MetadataFactory factory) :
{
}
{
    (ddlStmt(factory))*
    <EOF>     
}

/*
name=ddl statement
description=A data definition statement.
example={code:sql}CREATE FOREIGN TABLE X (Y STRING){code}
index=true
*/
void ddlStmt(MetadataFactory factory) :
{
	Command command = null; 
}
{
	(LOOKAHEAD(3) 	createTable(factory) | 
	 LOOKAHEAD(4)	createDDLProcedure(factory, ParseInfo.DEFAULT_INSTANCE) |
	 LOOKAHEAD(2)	createNameSpace(factory) |
	 LOOKAHEAD(4)	alterStatement(factory) |
	 LOOKAHEAD(4) 	command=createTrigger(ParseInfo.DEFAULT_INSTANCE)
	 {
	 	createDDLTrigger(factory, (AlterTrigger)command);
	 }
	)
	[<SEMICOLON>]
}

/*
name=option namespace
description=A namespace used to shorten the full name of an option key.
example={code:sql}SET NAMESPACE 'http://foo' AS foo{code}
*/
void createNameSpace(MetadataFactory factory) :
{
	String uri = null;
	String prefix = null;
}
{ 
	<SET> <NAMESPACE> uri = stringVal() <AS> prefix = id(Boolean.FALSE)
	{
		factory.addNamespace(prefix, uri);
	}
}

/*
name=create procedure
description=Defines a procedure or function invocation.
example={code:sql}CREATE FOREIGN PROCEDURE proc (param STRING) RETURNS STRING{code}
*/
CreateProcedureCommand createDDLProcedure(MetadataFactory factory, ParseInfo info) :
{
	boolean virtual = true;
	boolean function = false;
	Procedure proc = null;
	String procName = null;
    CreateProcedureCommand procCmd = new CreateProcedureCommand();
    Statement stmt = null; 	
    ParsedDataType returnDataType = null;
    Token comment = null;    
}
{
	<CREATE> [<VIRTUAL> | <FOREIGN> {virtual = false;}] (<PROCEDURE> | <FUNCTION> {function = true;})
	//the below is optional beacuse to allow the designer based metadata
	(	procName = id(null)
		{
			proc = factory.addProcedure(procName);
		}
	
		<LPAREN>
		[procedureParameter(factory, proc)
		(<COMMA> procedureParameter(factory, proc))*]	
		<RPAREN>
		
		[<RETURNS> (([<TABLE>] <LPAREN>
	        procedureRsColumn(factory, proc)
	        (<COMMA> procedureRsColumn(factory, proc))*
	    <RPAREN>)
	     |
	     returnDataType = parseDataType())
	    ]
	    [ optionsClause(proc, factory)
		   {
		      setProcedureOptions(proc);
		   }
		]
		[<AS> { comment = getToken(1).specialToken; } 
		stmt = statement(info)])
		{
			proc.setVirtual(virtual);
			proc.setFunction(function);
			if (stmt != null){
			    if (function || !virtual) {
			         throw new MetadataException(QueryPlugin.Util.getString("SQLParser.function_def", procName));
			    }
				proc.setQueryPlan((comment != null?comment.image+" ":"") + stmt.toString());
			}
			
	        if (returnDataType != null){
				addProcColumn(factory, proc, "return", returnDataType, false);
	        }
	        if (function) {
	        	replaceProcedureWithFunction(factory, proc);
	        }
	        return procCmd; 		
		}
}

/*
name=procedure parameter
description=A procedure or function parameter
example={code:sql}OUT x INTEGER{code}
*/
void procedureParameter(MetadataFactory factory, Procedure proc) :
{
	String name = null;
	ParsedDataType type = null;
	ProcedureParameter.Type ppType = ProcedureParameter.Type.In;
	ProcedureParameter param = null;
	String defaultValue = null;
	boolean notNull = false;
	boolean vararg = false;
}
{
	[LOOKAHEAD(1) ((<IN> {ppType = ProcedureParameter.Type.In ;})
	 |(<OUT> {ppType = ProcedureParameter.Type.Out;})
	 |(<INOUT> {ppType = ProcedureParameter.Type.InOut;})
	 |(<VARIADIC> {ppType = ProcedureParameter.Type.In ; vararg = true;}))]
	name = id(Boolean.TRUE)
	type = parseDataType()
	[<NOT> <NULL> {notNull = true;}]
	[<RESULT>
	  {
	    if (ppType != ProcedureParameter.Type.Out) {
	    	throw new ParseException(QueryPlugin.Util.getString("SQLParser.param_out", proc.getName(), name));
	    }
	    ppType = ProcedureParameter.Type.ReturnValue;
	  }
	]
	{
		param = factory.addProcedureParameter(name, type.type, ppType, proc);
		setTypeInfo(type, param);
		if (notNull) {
			param.setNullType(Column.NullType.No_Nulls);
		}
		if (vararg) {
			param.setVarArg(vararg);
		}			
	}
	[<DEFAULT_KEYWORD> defaultValue = stringVal() {param.setDefaultValue(defaultValue);}]
	[optionsClause(param, factory)
	   {
	      setColumnOptions(param);
	   }
	]	
}

/*
name=procedure result column
description=A procedure result column.
example={code:sql}x INTEGER{code}
*/
void procedureRsColumn(MetadataFactory factory, Procedure proc) :
{
	String name = null;
	ParsedDataType type = null;
	boolean notNull = false;
	BaseColumn column = null; 
}
{
      name = id(Boolean.TRUE)
	  type = parseDataType()
	  {
	  	column = addProcColumn(factory, proc, name, type, true);
	  }
	  [<NOT> <NULL> {column.setNullType(Column.NullType.No_Nulls);}]	  
	  [optionsClause(column, factory)
	   {
	      setColumnOptions(column);
	   }
	  ]
}

/*
name=create table
description=Defines a table or view.
example={code:sql}CREATE VIEW vw AS SELECT 1{code}
*/
void createTable(MetadataFactory factory) :
{
	boolean view = false;
	boolean globalTemp = false;
	String tableName = null;
	Table table = null;
	Token comment = null;
	Command query = null;
}
{
	<CREATE> (
		(<FOREIGN> <TABLE>) | 
		([<VIRTUAL>] <VIEW> {view = true;}) |
		(<GLOBAL> <TEMPORARY> <TABLE> {globalTemp = true; view = true;})
	) 
	tableName = id(null)
	{
	    table = factory.addTable(tableName);
	    table.setVirtual(view);
	    if (globalTemp) {
	    	table.setTableType(Table.Type.TemporaryTable);
	    }
	}
	(createTableBody(table, factory) | [optionsClause(table, factory)
	   {
	      setTableOptions(table);
	   }
	])
	[<AS> { comment = getToken(1).specialToken; } query = queryExpression(ParseInfo.DEFAULT_INSTANCE)
	  {
		   if (!view || globalTemp) {
     		   throw new MetadataException(QueryPlugin.Util.getString("SQLParser.view_def", table.getName()));
		   }
		   table.setSelectTransformation((comment != null?comment.image+" ":"") + query.toString());	
	  }
	]
}

/*
name=create foreign temp table
description=Defines a foreign temp table
example={code:sql}CREATE FOREIGN TEMPORARY TABLE t (x string) ON z{code}
*/
Create createForeignTempTable(ParseInfo info) :
{
	Table table = null;
	String tableName = null;
	String model = null;
	MetadataFactory factory = getTempMetadataFactory();
}
{
	<CREATE> [<LOCAL>] <FOREIGN> <TEMPORARY> <TABLE>
	tableName = id(null)
	{
		table = new Table();
		//need to set a dummy/stable uuid
		table.setUUID("tid:0");
		table.setTableType(Table.Type.TemporaryTable);
		table.setName(tableName);
	} 
	createTableBody(table, factory)
	<ON> model = id(null)
	{
		Create create = new Create();
		create.setTableMetadata(table);
		create.setOn(model);
		return create;
	}
}

/*
name=create table body
description=Defines a table.
example={code:sql}(x string) OPTIONS (CARDINALITY 100){code}
*/
void createTableBody(Table table, MetadataFactory factory) :
{
	String name = null;
	AbstractMetadataRecord constraint = null;
}
{
	<LPAREN>
    createColumn(factory, table)
    (LOOKAHEAD(3) <COMMA>
      createColumn(factory, table)
    )*	
    (<COMMA>{name=null;} [<CONSTRAINT> name = id(null)] (constraint = primaryKey(factory, table, name) | constraint = constraint(factory, table, name, ParseInfo.DEFAULT_INSTANCE) | constraint = foreignKey(factory, table, name))
       [optionsClause(constraint, factory)
	   {
	      setCommonProperties(constraint, constraint.getProperties());
	   }
	]
    )*
    <RPAREN>    
    [optionsClause(table, factory)
	   {
	      setTableOptions(table);
	   }
	]
	{
		if (table.getColumns() != null && !table.supportsUpdate()) {
			for (Column c : table.getColumns()) {
				c.setUpdatable(false);
			}
		}
	}
}

/*
name=foreign key
description=Defines the foreign key referential constraint.
example={code:sql}FOREIGN KEY (a, b) REFERENCES tbl (x, y){code}
*/
AbstractMetadataRecord foreignKey(MetadataFactory factory, Table table, String name) :
{
	List<String> columnNames = null;
	String viewName = null;
	Table reference = null;
	KeyRecord pk = null;
	Column column = null;
	List<String> pkColumnNames = null;
	KeyRecord key = null;
}
{
	<FOREIGN> <KEY>
	columnNames = columnList(true, false)
    <REFERENCES>
    viewName = id(null)
    [pkColumnNames = columnList(true, false)]
    {
	    return factory.addForiegnKey(name != null?name:("FK"+table.getForeignKeys().size()), columnNames, pkColumnNames, viewName, table);   
    }
}

/*
name=primary key
description=Defines the primary key.
example={code:sql}PRIMARY KEY (a, b){code}
*/
AbstractMetadataRecord primaryKey(MetadataFactory factory, Table table, String name) :
{
	List<String> columnNames = null;
	Column column = null;
	KeyRecord key = null;
}
{
	<PRIMARY> <KEY>
	columnNames = columnList(true, false)
    {
	    if (table.getPrimaryKey() != null){
	    	throw new MetadataException(QueryPlugin.Util.getString("SQLParser.pk_exists", table.getName()));
	 	}
 		return factory.addPrimaryKey(name!=null?name:"PK", columnNames, table);
    }
}

/*
name=other constraints
description=Defines ACCESSPATTERN and UNIQUE constraints and INDEXes. 
example={code:sql}UNIQUE (a){code}
*/
AbstractMetadataRecord constraint(MetadataFactory factory, Table table, String name, ParseInfo info) :
{
	KeyRecord keyrecord = null;
	Column column = null;
	List<String> columnNames = null;
	Token type = null;
	KeyRecord key = null;
	List<Expression> expressions = null;
}
{
	(( type = <UNIQUE> | type = <ACCESSPATTERN>)
	columnNames = columnList(true, false)	    
	{
		if (type.image.equalsIgnoreCase("UNIQUE")) { 
    		return factory.addIndex(name != null?name:("UNIQUE"+table.getUniqueKeys().size()), false, columnNames, table);
    	} else if (type.image.equalsIgnoreCase("ACCESSPATTERN")) { 
    		return factory.addAccessPattern(name != null?name:("AP"+table.getAccessPatterns().size()), columnNames, table);
    	}
    }) | (
    type = <INDEX>
    <LPAREN>
 	expressions = expressionList(info)
 	<RPAREN>
 	 {
 		return addFBI(factory, expressions, table, name);
 	 }
 	)
}

/*
name=column list
description=A list of column names.
example={code:sql}(a, b){code}
*/
List columnList(boolean validate, boolean asElementSymbols) :
{
	ArrayList list = new ArrayList();
	String id = null;
}
{
	<LPAREN>
	  id = id(validate?Boolean.TRUE:null)
	  {
		if (asElementSymbols) {
			list.add(new ElementSymbol(id));
		} else {
	  		list.add(id);
	  	}
	  }
	  (<COMMA> id = id(validate?Boolean.TRUE:null)
	   {
		if (asElementSymbols) {
			list.add(new ElementSymbol(id));
		} else {
	  		list.add(id);
	  	}
	   }
	  )*
	 <RPAREN> 
	 {
	 	return list;
	 }
}

/*
name=table element
description=Defines a table column.
example={code:sql}x INTEGER NOT NULL{code}
*/
void createColumn(MetadataFactory factory, Table table) :
{
	String element = null;
	ParsedDataType type = null;
	boolean autoIncrement = false;
	boolean notNull = false;
	String defalt = null;
	Column column = null;
	List<String> columnName = new ArrayList<String>();
	boolean index = false;
	boolean unique = false;
	boolean pk = false;
	Token word = null;
}
{
	 element = id(Boolean.TRUE)
	 (
      <SERIAL>
      {
        type = new ParsedDataType("INTEGER");
        autoIncrement = true;
        notNull = true;
      }
      |
      (
	    type = parseDataType()
	    [<NOT> <NULL> { notNull = true; }]
        [<AUTO_INCREMENT> {  autoIncrement = true; }]
	  )
	 )
	 {
       column = factory.addColumn(element, type.type, table);
       column.setUpdatable(true);
       setTypeInfo(type, column);                
       columnName.add(element);
     }
	 ((<PRIMARY> <KEY> { pk = true; }) | ([<UNIQUE> { unique = true;  }] [<INDEX> { index = true; }]))
	 [<DEFAULT_KEYWORD> defalt = stringVal() {column.setDefaultValue(defalt);}]
	 [optionsClause(column, factory)
	   {
	      setColumnOptions(column);
	   }
	 ]
	 {
	    if (index){
	 		factory.addIndex("INDEX"+table.getIndexes().size(), true, columnName, table);
	 	} else if (unique){
	 		factory.addIndex("UNIQUE"+table.getIndexes().size(), false, columnName, table);
	 	} else if (pk) {
	    	if (table.getPrimaryKey() != null) {
	    		throw new MetadataException(QueryPlugin.Util.getString("SQLParser.pk_exists", table.getName()));
	    	}
	 		factory.addPrimaryKey("PK", columnName, table);
	 	}
	 	if (notNull) {
	       column.setNullType(Column.NullType.No_Nulls);
	    }
	    column.setAutoIncremented(autoIncrement);
	 }
}

/*
name=options clause
description=A list of statement options.
example={code:sql}OPTIONS ('x' 'y', 'a' 'b'){code}
*/
void optionsClause(AbstractMetadataRecord record, MetadataFactory factory) :
{
}
{
  <OPTIONS> <LPAREN>
  optionPair(record, factory) (<COMMA> optionPair(record, factory))*	   
  <RPAREN> 
}

/*
name=option pair
description=An option key/value pair.
example={code:sql}'key' 'value'{code}
*/
void optionPair(AbstractMetadataRecord record, MetadataFactory factory) :
{
	Constant value = null;
	String key = null;
	Token t = null;
	String strVal = null;
}
{
	 key = id(null)
	 (value = nonNumericLiteral()
	 | [strVal = plusMinus()] value = unsignedNumericLiteral(strVal))
	 { 
	 	key = resolvePropertyKey(factory, key);
	 	String val = null;
	 	if (!value.isNull()) {
	 		val = value.getValue().toString();
	 	}
	 	record.setProperty(key, val); 
	 } 
}

/*
name=alter options
description=alters options of tables/procedure
example={code:sql}ALTER FOREIGN TABLE foo OPTIONS (ADD cardinality 100){code}
*/
void alterStatement(MetadataFactory factory) :
{
	boolean table = false;
	boolean proc = false;
	boolean func = false;
	String objectName = null;
	AbstractMetadataRecord record = null;
}
{ 
	<ALTER> [<VIRTUAL> | <FOREIGN>] (<TABLE> {table = true;} | <VIEW> {table = true;} | <PROCEDURE> {proc=true;})
	objectName = id(null)
	{
		if (table){
			record = factory.getSchema().getTable(objectName);
			if (record == null){
				throw new MetadataException(QueryPlugin.Util.getString("SQLParser.alter_table_doesnot_exist", objectName));
			}
		}
		if (proc){
			record = factory.getSchema().getProcedure(objectName);
			if (record == null){
				throw new MetadataException(QueryPlugin.Util.getString("SQLParser.alter_procedure_doesnot_exist", objectName));
			}
		}		
	}
	
	(alterOptionsList(record, factory) | 
	 alterColumn(record, factory))
}

/*
name=alter options list
description=a list of alterations to options
example={code:sql}OPTIONS (ADD updatable true){code}
*/
void alterOptionsList(AbstractMetadataRecord record, MetadataFactory factory) :
{

}
{
  <OPTIONS> <LPAREN>
  (addSetOption(record, factory) |
   dropOption(record))
  (<COMMA> (addSetOption(record, factory) |
   dropOption(record)))*
  <RPAREN>
  {
 		setOptions(record);
  }
}

/*
name=drop option
description=drop option
example={code:sql}DROP updatable{code}
*/
void dropOption(AbstractMetadataRecord record) :
{
	String key = null;
}
{
   <DROP> key = id(null) 
   {
 		removeOption(key, record);
   }
}

/*
name=add set option
description=add or set an option pair
example={code:sql}ADD updatable true{code}
*/
void addSetOption(AbstractMetadataRecord record, MetadataFactory factory) :
{
}
{
   (<ADD>|<SET>) optionPair(record, factory) 
}

/*
name=alter column options
description=alters a set of column options
example={code:sql}ALTER COLUMN bar OPTIONS (ADD updatable true){code}
*/
void alterColumn(AbstractMetadataRecord parent, MetadataFactory factory) :
{
	boolean param = false;
	String objectName = null;
	AbstractMetadataRecord record = null;
	String key = null;
}
{ 
	<ALTER> [<COLUMN>|<PARAMETER>{param=true;}]
	objectName = id(null)
	{
		record = getChild(objectName, parent, param);
	}
	alterOptionsList(record, factory)
}