%{ /* -*-text-*- */

/*#define YYDEBUG 1*/
/*-------------------------------------------------------------------------
 * 
 * gram.y--
 *    POSTGRES SQL YACC rules/actions
 * 
 * Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *    $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 1.36 1997/08/19 04:44:01 vadim Exp $
 *
 * HISTORY
 *    AUTHOR		DATE		MAJOR EVENT
 *    Andrew Yu		Sept, 1994	POSTQUEL to SQL conversion
 *    Andrew Yu		Oct, 1994	lispy code conversion
 *
 * NOTES
 *    CAPITALS are used to represent terminal symbols.
 *    non-capitals are used to represent non-terminals.
 *
 *    if you use list, make sure the datum is a node so that the printing
 *    routines work
 *
 * WARNING
 *    sometimes we assign constants to makeStrings. Make sure we don't free
 *    those.
 *
 *-------------------------------------------------------------------------
 */
#include <string.h>
#include <ctype.h>

#include "postgres.h"
#include "nodes/parsenodes.h"
#include "parser/gramparse.h"
#include "parser/catalog_utils.h"
#include "parser/parse_query.h"
#include "storage/smgr.h"
#include "utils/acl.h"
#include "catalog/catname.h"
#include "utils/elog.h"
#include "access/xact.h"

static char saved_relname[NAMEDATALEN];  /* need this for complex attributes */
static bool QueryIsRule = FALSE;
static Node *saved_In_Expr;
extern List *parsetree;

/*
 * If you need access to certain yacc-generated variables and find that 
 * they're static by default, uncomment the next line.  (this is not a
 * problem, yet.)
 */
/*#define __YYSCLASS*/

static char *xlateSqlType(char *);
static Node *makeA_Expr(int oper, char *opname, Node *lexpr, Node *rexpr);

/* old versions of flex define this as a macro */
#if defined(yywrap)
#undef yywrap
#endif /* yywrap */
%}


%union {
    double 		dval;
    int			ival;
    char                chr;
    char		*str;
    bool		boolean;
    List		*list;
    Node		*node;
    Value		*value;

    Attr		*attr;

    ColumnDef		*coldef;
    TypeName		*typnam;
    DefElem		*defelt;
    ParamString		*param;
    SortGroupBy		*sortgroupby;
    IndexElem		*ielem;
    RangeVar		*range;
    RelExpr		*relexp;
    TimeRange		*trange;
    A_Indices		*aind;
    ResTarget		*target;
    ParamNo		*paramno;
	
    VersionStmt		*vstmt;
    DefineStmt		*dstmt;
    PurgeStmt		*pstmt;
    RuleStmt		*rstmt;
    AppendStmt		*astmt;
}

%type <node>	stmt, 
	AddAttrStmt, ClosePortalStmt,
	CopyStmt, CreateStmt, CreateSeqStmt, DefineStmt, DestroyStmt, 
	ExtendStmt, FetchStmt,	GrantStmt,
	IndexStmt, MoveStmt, ListenStmt, OptimizableStmt, 
        ProcedureStmt, PurgeStmt,
	RecipeStmt, RemoveAggrStmt, RemoveOperStmt, RemoveFuncStmt, RemoveStmt,
	RenameStmt, RevokeStmt, RuleStmt, TransactionStmt, ViewStmt, LoadStmt,
	CreatedbStmt, DestroydbStmt, VacuumStmt, RetrieveStmt, CursorStmt,
	ReplaceStmt, AppendStmt, NotifyStmt, DeleteStmt, ClusterStmt,
	ExplainStmt, VariableSetStmt, VariableShowStmt, VariableResetStmt

%type <str>	relation_name, copy_file_name, copy_delimiter, def_name,
	database_name, access_method_clause, access_method, attr_name,
	class, index_name, name, file_name, recipe_name,
	var_name, aggr_argtype

%type <str>	opt_id, opt_portal_name,
	before_clause, after_clause, all_Op, MathOp, opt_name, opt_unique,
	result, OptUseOp, opt_class, opt_range_start, opt_range_end,
	SpecialRuleRelation

%type <str>	privileges, operation_commalist, grantee
%type <chr>	operation

%type <list>	stmtblock, stmtmulti,
	relation_name_list, OptTableElementList,
	tableElementList, OptInherit, definition,
	opt_with, def_args, def_name_list, func_argtypes,
	oper_argtypes, OptStmtList, OptStmtBlock, OptStmtMulti,
	opt_column_list, columnList, opt_va_list, va_list,
	sort_clause, sortby_list, index_params, index_list,
	name_list, from_clause, from_list, opt_array_bounds, nest_array_bounds,
	expr_list, attrs, res_target_list, res_target_list2,
	def_list, opt_indirection, group_clause, groupby_list

%type <boolean>	opt_inh_star, opt_binary, opt_instead, opt_with_copy,
		index_opt_unique, opt_verbose, opt_analyze, opt_null

%type <ival>	copy_dirn, archive_type, OptArchiveType, OptArchiveLocation, 
	def_type, opt_direction, remove_type, opt_column, event

%type <ival>	OptLocation, opt_move_where, fetch_how_many 

%type <list>	OptSeqList
%type <defelt>	OptSeqElem

%type <dstmt>	def_rest
%type <pstmt>	purge_quals
%type <astmt>	insert_rest

%type <typnam>	Typename, typname, opt_type
%type <coldef>	columnDef
%type <defelt>	def_elem
%type <node>	def_arg, columnElem, where_clause, 
		a_expr, a_expr_or_null, AexprConst,
		in_expr_nodes, not_in_expr_nodes,
		having_clause
%type <value>	NumConst
%type <attr>	event_object, attr
%type <sortgroupby>	groupby
%type <sortgroupby>	sortby
%type <ielem>	index_elem, func_index
%type <range>	from_val
%type <relexp>	relation_expr
%type <trange>	time_range
%type <target>	res_target_el, res_target_el2
%type <paramno>	ParamNo

%type <ival>	Iconst
%type <str>	Sconst
%type <str>	Id, date, var_value


/*
 * If you make any token changes, remember to:
 *	- use "yacc -d" and update parse.h
 * 	- update the keyword table in parser/keywords.c
 */

/* Keywords */
%token	ABORT_TRANS, ACL, ADD, AFTER, AGGREGATE, ALL, ALTER, ANALYZE, 
	AND, APPEND, ARCHIVE, ARCH_STORE, AS, ASC, 
	BACKWARD, BEFORE, BEGIN_TRANS, BETWEEN, BINARY, BY, 
	CAST, CHANGE, CLOSE, CLUSTER, COLUMN, COMMIT, COPY, CREATE,
	CURRENT, CURSOR, DATABASE, DECLARE, DELETE, DELIMITERS, DESC, 
	DISTINCT, DO, DROP, END_TRANS,
	EXTEND, FETCH, FOR, FORWARD, FROM, FUNCTION, GRANT, GROUP, 
	HAVING, HEAVY, IN, INDEX, INHERITS, INSERT, INSTEAD, INTO, IS,
	ISNULL, LANGUAGE, LIGHT, LISTEN, LOAD, MERGE, MOVE, NEW, 
	NONE, NOT, NOTHING, NOTIFY, NOTNULL, 
        OIDS, ON, OPERATOR, OPTION, OR, ORDER, 
        PNULL, PRIVILEGES, PUBLIC, PURGE, P_TYPE, 
        RENAME, REPLACE, RESET, RETRIEVE, RETURNS, REVOKE, ROLLBACK, RULE, 
        SELECT, SET, SETOF, SHOW, STDIN, STDOUT, STORE, 
	TABLE, TO, TRANSACTION, UNIQUE, UPDATE, USING, VACUUM, VALUES
	VERBOSE, VERSION, VIEW, WHERE, WITH, WORK
%token	EXECUTE, RECIPE, EXPLAIN, LIKE, SEQUENCE

/* Special keywords, not in the query language - see the "lex" file */
%token <str>	IDENT, SCONST, Op 
%token <ival>	ICONST, PARAM
%token <dval>	FCONST

/* these are not real. they are here so that they gets generated as #define's*/
%token		OP

/* precedence */
%left	OR
%left	AND
%right	NOT
%right 	'='
%nonassoc LIKE
%nonassoc BETWEEN
%nonassoc IN
%nonassoc Op
%nonassoc NOTNULL
%nonassoc ISNULL
%nonassoc IS
%left  	'+' '-'
%left  	'*' '/'
%left	'|'		/* this is the relation union op, not logical or */
%right  ':'		/* Unary Operators      */
%left	';'		/* end of statement or natural log    */
%nonassoc  '<' '>'
%right   UMINUS
%left	'.'
%left  	'[' ']' 
%nonassoc TYPECAST
%nonassoc REDUCE
%%

stmtblock: stmtmulti
		{ parsetree = $1; }
	|  stmt
		{ parsetree = lcons($1,NIL); }
	;

stmtmulti: stmtmulti stmt ';'
		{ $$ = lappend($1, $2); }
	|  stmtmulti stmt
		{ $$ = lappend($1, $2); }
	|  stmt ';'
		{ $$ = lcons($1,NIL); }
	;

stmt :	  AddAttrStmt
	| ClosePortalStmt
	| CopyStmt
	| CreateStmt
	| CreateSeqStmt
	| ClusterStmt
	| DefineStmt
	| DestroyStmt
	| ExtendStmt
	| ExplainStmt
	| FetchStmt
        | GrantStmt
	| IndexStmt
	| MoveStmt
        | ListenStmt
	| ProcedureStmt
	| PurgeStmt			
	| RecipeStmt
	| RemoveAggrStmt
	| RemoveOperStmt
	| RemoveFuncStmt
	| RemoveStmt
	| RenameStmt
        | RevokeStmt
	| OptimizableStmt	
	| RuleStmt			
	| TransactionStmt
  	| ViewStmt
	| LoadStmt
	| CreatedbStmt
	| DestroydbStmt
	| VacuumStmt
	| VariableSetStmt
	| VariableShowStmt
	| VariableResetStmt
	;

/*****************************************************************************
 * 
 * Set PG internal variable
 *    SET var_name TO 'var_value'
 * 
 *****************************************************************************/

VariableSetStmt: SET var_name TO var_value
		{
		    VariableSetStmt *n = makeNode(VariableSetStmt);
		    n->name  = $2;
		    n->value = $4;
		    $$ = (Node *) n;
		}
	;

var_value:	Sconst		{ $$ = $1; }
	;

VariableShowStmt: SHOW var_name
		{
		    VariableShowStmt *n = makeNode(VariableShowStmt);
		    n->name  = $2;
		    $$ = (Node *) n;
		}
	;

VariableResetStmt: RESET var_name
		{
		    VariableResetStmt *n = makeNode(VariableResetStmt);
		    n->name  = $2;
		    $$ = (Node *) n;
		}
	;

/*****************************************************************************
 *
 *	QUERY :
 *		addattr ( attr1 = type1 .. attrn = typen ) to <relname> [*]
 *
 *****************************************************************************/

AddAttrStmt:  ALTER TABLE relation_name opt_inh_star ADD COLUMN columnDef
		{
		    AddAttrStmt *n = makeNode(AddAttrStmt);
		    n->relname = $3;
		    n->inh = $4;
		    n->colDef = $7;
		    $$ = (Node *)n;
		}
	;

columnDef:  Id Typename opt_null
		{  
		    $$ = makeNode(ColumnDef);
		    $$->colname = $1;
		    $$->typename = $2;
                    $$->is_not_null = $3;
		}
	;

opt_null:   PNULL                         { $$ = false; }
            | NOT PNULL                   { $$ = true; }
            | NOTNULL                     { $$ = true; }
            | /* EMPTY */                 { $$ = false; }
        ;
	
/*****************************************************************************
 *	
 *	QUERY :
 *		close <optname>
 *	
 *****************************************************************************/

ClosePortalStmt:  CLOSE opt_id	
		{  
		    ClosePortalStmt *n = makeNode(ClosePortalStmt);
		    n->portalname = $2;
		    $$ = (Node *)n;
		}
	;


/*****************************************************************************
 *
 *	QUERY :
 *		COPY [BINARY] <relname> FROM/TO 
 *              [USING DELIMITERS <delimiter>]
 *
 *****************************************************************************/

CopyStmt:  COPY opt_binary relation_name opt_with_copy copy_dirn copy_file_name copy_delimiter
		{
		    CopyStmt *n = makeNode(CopyStmt);
		    n->binary = $2;
		    n->relname = $3;
		    n->oids = $4;
		    n->direction = $5;
		    n->filename = $6;
		    n->delimiter = $7;
		    $$ = (Node *)n;
		}
	;

copy_dirn:  TO 
		{ $$ = TO; }
	|  FROM
		{ $$ = FROM; }
	;

/* 
 * copy_file_name NULL indicates stdio is used. Whether stdin or stdout is
 * used depends on the direction. (It really doesn't make sense to copy from
 * stdout. We silently correct the "typo".	 - AY 9/94
 */
copy_file_name:  Sconst				{ $$ = $1; }
	| STDIN					{ $$ = NULL; }
	| STDOUT				{ $$ = NULL; }
	;

opt_binary: BINARY				{ $$ = TRUE; }
	|  /*EMPTY*/				{ $$ = FALSE; }
	;

opt_with_copy:  WITH OIDS			{ $$ = TRUE; }
	|  /* EMPTY */				{ $$ = FALSE; }
	;

/*
 * the default copy delimiter is tab but the user can configure it
 */
copy_delimiter: USING DELIMITERS Sconst { $$ = $3;}
	| /* EMPTY */  { $$ = "\t"; }
	;


/*****************************************************************************
 *
 *	QUERY :
 *		CREATE relname
 *
 *****************************************************************************/

CreateStmt:  CREATE TABLE relation_name '(' OptTableElementList ')'
		OptInherit OptArchiveType OptLocation OptArchiveLocation
		{
		    CreateStmt *n = makeNode(CreateStmt);
		    n->relname = $3;
		    n->tableElts = $5;
		    n->inhRelnames = $7;
		    n->archiveType = $8;
		    n->location = $9;
		    n->archiveLoc = $10;
		    $$ = (Node *)n;
		}
	;

OptTableElementList:  tableElementList		{ $$ = $1; }
	| /* EMPTY */				{ $$ = NULL; }
	;

tableElementList :
	  tableElementList ',' columnDef	
		{ $$ = lappend($1, $3); }
	| columnDef			
		{ $$ = lcons($1, NIL); }
	;


OptArchiveType:  ARCHIVE '=' archive_type		{ $$ = $3; }
	| /*EMPTY*/					{ $$ = ARCH_NONE; }
	;

archive_type:  HEAVY 					{ $$ = ARCH_HEAVY; }
	| LIGHT 					{ $$ = ARCH_LIGHT; }
	| NONE						{ $$ = ARCH_NONE; }
	;

OptLocation:  STORE '=' Sconst
		{  $$ = smgrin($3);  }
	| /*EMPTY*/				
		{  $$ = -1;  }
	;

OptArchiveLocation: ARCH_STORE '=' Sconst
		{  $$ = smgrin($3);  }
	| /*EMPTY*/				
		{  $$ = -1;  }
	;

OptInherit:  INHERITS '(' relation_name_list ')'	{ $$ = $3; }
	|  /*EMPTY*/					{ $$ = NIL; }
	;


/*****************************************************************************
 *
 *	QUERY :
 *		CREATE SEQUENCE seqname
 *
 *****************************************************************************/

CreateSeqStmt:  CREATE SEQUENCE relation_name OptSeqList
		{
		    CreateSeqStmt *n = makeNode(CreateSeqStmt);
		    n->seqname = $3;
		    n->options = $4;
		    $$ = (Node *)n;
		}
	;

OptSeqList:	
		OptSeqList OptSeqElem
			{ $$ = lappend($1, $2); }
	| 		{ $$ = NIL; }
	;

OptSeqElem:	IDENT NumConst
		{ 
		    $$ = makeNode(DefElem);
		    $$->defname = $1;
		    $$->arg = (Node *)$2;
		}
	|	IDENT
		{
		    $$ = makeNode(DefElem);
		    $$->defname = $1;
		    $$->arg = (Node *)NULL;
		}
	;


/*****************************************************************************
 *
 *  	QUERY :
 *		define (type,operator,aggregate)
 *
 *****************************************************************************/

DefineStmt:  CREATE def_type def_rest
		{
		    $3->defType = $2;
		    $$ = (Node *)$3;
		}
	;

def_rest:  def_name definition 
		{
		    $$ = makeNode(DefineStmt);
		    $$->defname = $1;
		    $$->definition = $2;
		}
	;

def_type:  OPERATOR 				{ $$ = OPERATOR; }
	|  Type 				{ $$ = P_TYPE; }
	|  AGGREGATE				{ $$ = AGGREGATE; }
	;

def_name:  Id 	|  MathOp |  Op
	;


definition:  '(' def_list ')'			{ $$ = $2; }
	;


def_list:  def_elem
		{ $$ = lcons($1, NIL); }
	|  def_list ',' def_elem
		{ $$ = lappend($1, $3); }
	;

def_elem:  def_name '=' def_arg
		{ 
		    $$ = makeNode(DefElem);
		    $$->defname = $1;
		    $$->arg = (Node *)$3;
		}
	|  def_name
		{
		    $$ = makeNode(DefElem);
		    $$->defname = $1;
		    $$->arg = (Node *)NULL;
		}
	;

def_arg:  Id			{  $$ = (Node *)makeString($1); }
	| all_Op		{  $$ = (Node *)makeString($1); }
	| NumConst		{  $$ = (Node *)$1; /* already a Value */ }
      	| Sconst		{  $$ = (Node *)makeString($1); }
	| SETOF Id		{ 
				   TypeName *n = makeNode(TypeName);
				   n->name = $2;
				   n->setof = TRUE;
				   n->arrayBounds = NULL;
				   $$ = (Node *)n;
				}				
	;


/*****************************************************************************
 *
 *	QUERY:	
 *		destroy <relname1> [, <relname2> .. <relnameN> ]
 *
 *****************************************************************************/

DestroyStmt:	DROP TABLE relation_name_list
		{ 
		    DestroyStmt *n = makeNode(DestroyStmt);
		    n->relNames = $3;
		    n->sequence = false;
		    $$ = (Node *)n;
		}
	|	DROP SEQUENCE relation_name_list
		{ 
		    DestroyStmt *n = makeNode(DestroyStmt);
		    n->relNames = $3;
		    n->sequence = true;
		    $$ = (Node *)n;
		}
	;


/*****************************************************************************
 *
 *	QUERY:
 *		fetch [forward | backward] [number | all ] [ in <portalname> ]
 *
 *****************************************************************************/

FetchStmt:  FETCH opt_direction fetch_how_many opt_portal_name
		{
		    FetchStmt *n = makeNode(FetchStmt);
		    n->direction = $2;
		    n->howMany = $3;
		    n->portalname = $4;
		    $$ = (Node *)n;
	        }
	;

opt_direction:  FORWARD				{ $$ = FORWARD; }
	| BACKWARD				{ $$ = BACKWARD; }
	| /*EMPTY*/				{ $$ = FORWARD; /* default */ }
	;

fetch_how_many:  Iconst			
	       { $$ = $1;
		 if ($1 <= 0) elog(WARN,"Please specify nonnegative count for fetch"); }
	|  ALL				{ $$ = 0; /* 0 means fetch all tuples*/}
	|  /*EMPTY*/			{ $$ = 1; /*default*/ }
	;

/*****************************************************************************
 *
 *	QUERY:
 *		GRANT [privileges] ON [relation_name_list] TO [GROUP] grantee
 *
 *****************************************************************************/

GrantStmt: GRANT privileges ON relation_name_list TO grantee opt_with_grant
           {
		$$ = (Node*)makeAclStmt($2,$4,$6,'+');
		free($2);
		free($6);
           }
           ;

privileges:  ALL PRIVILEGES
		{
		 $$ = aclmakepriv("rwaR",0);
		}
           | ALL
		{
		 $$ = aclmakepriv("rwaR",0);
		}
           | operation_commalist {
		$$ = $1;
		}
           ;

operation_commalist: operation {
			$$ = aclmakepriv("",$1);
			}
                   | operation_commalist ',' operation
			{
				$$ = aclmakepriv($1,$3);
				free($1);
			}
                   ;

operation:    SELECT  {
		$$ = ACL_MODE_RD_CHR;
		}
            | INSERT {
		$$ = ACL_MODE_AP_CHR;
		}
            | UPDATE {
		$$ = ACL_MODE_WR_CHR;
		}
            | DELETE {
		$$ = ACL_MODE_WR_CHR;
		}
	    | RULE {
		$$ = ACL_MODE_RU_CHR;
		}
            ;

grantee:      PUBLIC {
		$$ = aclmakeuser("A","");
		}
	    | GROUP Id {
		$$ = aclmakeuser("G",$2);
		}
            | Id {
		$$ = aclmakeuser("U",$1);
	 	}
            ;

opt_with_grant : /* empty */
            |   WITH GRANT OPTION 
                {
                    yyerror("WITH GRANT OPTION is not supported.  Only relation owners can set privileges");
                }
            ;
/*****************************************************************************
 *
 *	QUERY:
 *		REVOKE [privileges] ON [relation_name] FROM [user]
 *
 *****************************************************************************/

RevokeStmt: REVOKE privileges ON relation_name_list FROM grantee
              {
		$$ = (Node*)makeAclStmt($2,$4,$6,'-');
		free($2);
		free($6);
              }
            ;

/*****************************************************************************
 *
 *	QUERY:
 *		move [<dirn>] [<whereto>] [<portalname>]
 *
 *****************************************************************************/

MoveStmt:  MOVE opt_direction opt_move_where opt_portal_name
		{ 
		    MoveStmt *n = makeNode(MoveStmt);
		    n->direction = $2;
		    n->to = FALSE;
		    n->where = $3;
		    n->portalname = $4;
		    $$ = (Node *)n;
		}
	|  MOVE opt_direction TO Iconst opt_portal_name
		{ 
		    MoveStmt *n = makeNode(MoveStmt);
		    n->direction = $2;
		    n->to = TRUE;
		    n->where = $4;
		    n->portalname = $5;
		    $$ = (Node *)n;
		}
	;

opt_move_where: Iconst				{ $$ = $1; }
	| /*EMPTY*/				{ $$ = 1; /* default */ }
	;

opt_portal_name: IN name			{ $$ = $2;}
	| /*EMPTY*/				{ $$ = NULL; }
	;


/*****************************************************************************
 *
 *	QUERY:
 *		define [archive] index <indexname> on <relname>
 *		  using <access> "(" (<col> with <op>)+ ")" [with
 *		  <target_list>]
 *
 *  [where <qual>] is not supported anymore
 *****************************************************************************/

IndexStmt:  CREATE index_opt_unique INDEX index_name ON relation_name
	    access_method_clause '(' index_params ')' opt_with
		{
		    /* should check that access_method is valid,
		       etc ... but doesn't */
		    IndexStmt *n = makeNode(IndexStmt);
		    n->unique = $2;
		    n->idxname = $4;
		    n->relname = $6;
		    n->accessMethod = $7;
		    n->indexParams = $9;
		    n->withClause = $11;
		    n->whereClause = NULL;
		    $$ = (Node *)n;
		}
	;

access_method_clause:   USING access_method      { $$ = $2; }
		      | /* empty -- 'btree' is default access method */
						 { $$ = "btree"; }
	;

index_opt_unique: UNIQUE				 { $$ = TRUE; }
		  | /*empty*/                            { $$ = FALSE; }
	;

/*****************************************************************************
 *
 *	QUERY:
 *		extend index <indexname> [where <qual>]
 *
 *****************************************************************************/

ExtendStmt:  EXTEND INDEX index_name where_clause
		{
		    ExtendStmt *n = makeNode(ExtendStmt);
		    n->idxname = $3;
		    n->whereClause = $4;
		    $$ = (Node *)n;
		}
	;

/*****************************************************************************
 *
 *	QUERY:
 *		execute recipe <recipeName> 
 *
 *****************************************************************************/

RecipeStmt:  EXECUTE RECIPE recipe_name 
		{
		    RecipeStmt *n;
		    if (!IsTransactionBlock())
			elog(WARN, "EXECUTE RECIPE may only be used in begin/end transaction blocks.");

		    n = makeNode(RecipeStmt);
		    n->recipeName = $3;
		    $$ = (Node *)n;
		}
	;


/*****************************************************************************
 *
 *	QUERY:
 *              define function <fname>
 *                     (language = <lang>, returntype = <typename> 
 *                      [, arch_pct = <percentage | pre-defined>]
 *                      [, disk_pct = <percentage | pre-defined>]
 *                      [, byte_pct = <percentage | pre-defined>]
 *                      [, perbyte_cpu = <int | pre-defined>]
 *                      [, percall_cpu = <int | pre-defined>]
 *                      [, iscachable])
 *                      [arg is (<type-1> { , <type-n>})]
 *                      as <filename or code in language as appropriate>
 *
 *****************************************************************************/

ProcedureStmt:  CREATE FUNCTION def_name def_args 
		   RETURNS def_arg opt_with AS Sconst LANGUAGE Sconst
                {
		    ProcedureStmt *n = makeNode(ProcedureStmt);
		    n->funcname = $3;
		    n->defArgs = $4;
		    n->returnType = (Node *)$6;
		    n->withClause = $7;
		    n->as = $9;
		    n->language = $11;
		    $$ = (Node *)n;
		};

opt_with:  WITH definition			{ $$ = $2; }
	|  /* EMPTY */				{ $$ = NIL; }
	;

def_args:  '(' def_name_list ')'		{ $$ = $2; }
        |  '(' ')'				{ $$ = NIL; }
	;

def_name_list: 	name_list;	


/*****************************************************************************
 *
 *	QUERY:
 *		purge <relname> [before <date>] [after <date>]
 *		  or
 *		purge <relname>  [after<date>][before <date>] 
 *	
 *****************************************************************************/

PurgeStmt:  PURGE relation_name purge_quals
		{ 
		    $3->relname = $2;
		    $$ = (Node *)$3;
		}
	;

purge_quals:  before_clause
		{ 
		    $$ = makeNode(PurgeStmt);
		    $$->beforeDate = $1;
		    $$->afterDate = NULL;
		}
	|  after_clause
		{ 
		    $$ = makeNode(PurgeStmt);
		    $$->beforeDate = NULL;
		    $$->afterDate = $1;
		}
	|  before_clause after_clause
		{ 
		    $$ = makeNode(PurgeStmt);
		    $$->beforeDate = $1;
		    $$->afterDate = $2;
		}
	|  after_clause before_clause
		{ 
		    $$ = makeNode(PurgeStmt);
		    $$->beforeDate = $2;
		    $$->afterDate = $1;
		}
	|  /*EMPTY*/
		{ 
		    $$ = makeNode(PurgeStmt);
		    $$->beforeDate = NULL;
		    $$->afterDate = NULL;
		}
	;

before_clause:	BEFORE date		{ $$ = $2; }
after_clause:	AFTER date		{ $$ = $2; }


/*****************************************************************************
 *
 *	QUERY:
 *
 *	remove function <funcname>
 *		(REMOVE FUNCTION "funcname" (arg1, arg2, ...))
 *	remove aggregate <aggname>
 *		(REMOVE AGGREGATE "aggname" "aggtype")
 *	remove operator <opname>
 *		(REMOVE OPERATOR "opname" (leftoperand_typ rightoperand_typ))
 *	remove type <typename>
 *		(REMOVE TYPE "typename")
 *	remove rule <rulename>
 *		(REMOVE RULE "rulename")
 *
 *****************************************************************************/

RemoveStmt:  DROP remove_type name
		{
		    RemoveStmt *n = makeNode(RemoveStmt);
		    n->removeType = $2;
		    n->name = $3;
		    $$ = (Node *)n;
		}
	;

remove_type:  Type 				{  $$ = P_TYPE; }
	|  INDEX	 			{  $$ = INDEX; }
	|  RULE 				{  $$ = RULE; }
	|  VIEW					{  $$ = VIEW; }
 	;

RemoveAggrStmt:  DROP AGGREGATE name aggr_argtype
		{
			RemoveAggrStmt *n = makeNode(RemoveAggrStmt);
			n->aggname = $3;
			n->aggtype = $4;
			$$ = (Node *)n;
		}
	;

aggr_argtype:  name				{ $$ = $1; }
	|  '*'					{ $$ = NULL; }
	;

RemoveFuncStmt:  DROP FUNCTION name '(' func_argtypes ')'
                {
		    RemoveFuncStmt *n = makeNode(RemoveFuncStmt);
		    n->funcname = $3;
		    n->args = $5;
		    $$ = (Node *)n;
	        }
          ;

func_argtypes:  name_list			{ $$ = $1; }
        |  /*EMPTY*/				{ $$ = NIL; }
	;

RemoveOperStmt:  DROP OPERATOR all_Op '(' oper_argtypes ')'
		{
		    RemoveOperStmt *n = makeNode(RemoveOperStmt);
		    n->opname = $3;
		    n->args = $5;
		    $$ = (Node *)n;
		}
        ;

all_Op: Op | MathOp;

MathOp:    '+' 	 	{ $$ = "+"; }
	|  '-'   	{ $$ = "-"; }
	|  '*'   	{ $$ = "*"; }
	|  '/'   	{ $$ = "/"; }
	|  '<'   	{ $$ = "<"; }
	|  '>'   	{ $$ = ">"; }
	|  '='   	{ $$ = "="; }
	;

oper_argtypes:  name	
		{ 
		   elog(WARN, "parser: argument type missing (use NONE for unary operators)");
		}
	| name ',' name
		{ $$ = makeList(makeString($1), makeString($3), -1); }
	| NONE ',' name		/* left unary */
		{ $$ = makeList(NULL, makeString($3), -1); }
	| name ',' NONE		/* right unary */
		{ $$ = makeList(makeString($1), NULL, -1); }
	;

/*****************************************************************************
 *
 *	QUERY:	 
 *		rename <attrname1> in <relname> [*] to <attrname2>
 *		rename <relname1> to <relname2>
 *	
 *****************************************************************************/

RenameStmt:  ALTER TABLE relation_name opt_inh_star 
		  RENAME opt_column opt_name TO name
		{ 
		    RenameStmt *n = makeNode(RenameStmt);
		    n->relname = $3;
		    n->inh = $4;
		    n->column = $7;
		    n->newname = $9;
		    $$ = (Node *)n;
		}
	;

opt_name:  name				{ $$ = $1; }
	|  /*EMPTY*/			{ $$ = NULL; }
	;

opt_column:  COLUMN			{ $$ = COLUMN; }
	| /*EMPTY*/			{ $$ = 0; }
	;


/*****************************************************************************
 *	 
 *	QUERY:	Define Rewrite Rule , Define Tuple Rule 
 *		Define Rule <old rules >
 *
 *      only rewrite rule is supported -- ay 9/94
 *	 
 *****************************************************************************/

RuleStmt:  CREATE RULE name AS 
	   { QueryIsRule=TRUE; }
	   ON event TO event_object where_clause
	   DO opt_instead OptStmtList
		{
		    RuleStmt *n = makeNode(RuleStmt);
		    n->rulename = $3;
		    n->event = $7;
		    n->object = $9;
		    n->whereClause = $10;
		    n->instead = $12;
		    n->actions = $13;
		    $$ = (Node *)n;
		}
	;

OptStmtList:  NOTHING			{ $$ = NIL; }
	| OptimizableStmt		{ $$ = lcons($1, NIL); }	
	| '[' OptStmtBlock ']'		{ $$ = $2; }
        ;

OptStmtBlock:  OptStmtMulti
               {  $$ = $1; }
	|  OptimizableStmt
		{ $$ = lcons($1, NIL); }
	;
	
OptStmtMulti:  OptStmtMulti OptimizableStmt ';'
               {  $$ = lappend($1, $2); }
	|  OptStmtMulti OptimizableStmt
               {  $$ = lappend($1, $2); }
	|  OptimizableStmt ';'
		{ $$ = lcons($1, NIL); }
	;
	
event_object: relation_name '.' attr_name
		{ 
		    $$ = makeNode(Attr);
		    $$->relname = $1;
		    $$->paramNo = NULL;
		    $$->attrs = lcons(makeString($3), NIL);
		    $$->indirection = NIL;
		}
	| relation_name
		{
		    $$ = makeNode(Attr);
		    $$->relname = $1;
		    $$->paramNo = NULL;
		    $$->attrs = NIL;
		    $$->indirection = NIL;
		}
  	;

/* change me to select, update, etc. some day */
event: 	SELECT				{ $$ = CMD_SELECT; }
	| UPDATE 			{ $$ = CMD_UPDATE; }
	| DELETE 			{ $$ = CMD_DELETE; }
	| INSERT			{ $$ = CMD_INSERT; }
	 ;

opt_instead:  INSTEAD			{ $$ = TRUE; }
	| /* EMPTY */			{ $$ = FALSE; }
	;


/*****************************************************************************
 *
 *	QUERY:
 *		NOTIFY <relation_name>  can appear both in rule bodies and
 *		as a query-level command
 *
 *****************************************************************************/

NotifyStmt: NOTIFY relation_name 
		{
		    NotifyStmt *n = makeNode(NotifyStmt);
		    n->relname = $2;
		    $$ = (Node *)n;
		}
	;

ListenStmt: LISTEN relation_name 
		{
		    ListenStmt *n = makeNode(ListenStmt);
		    n->relname = $2;
		    $$ = (Node *)n;
		}
;


/*****************************************************************************
 *
 *	Transactions:
 *
 *	abort transaction
 *		(ABORT)
 *	begin transaction
 *		(BEGIN)
 *	end transaction
 *		(END)
 *	
 *****************************************************************************/

TransactionStmt:  ABORT_TRANS TRANSACTION
		{ 
		    TransactionStmt *n = makeNode(TransactionStmt); 
		    n->command = ABORT_TRANS; 
		    $$ = (Node *)n;
		}
	| BEGIN_TRANS TRANSACTION
		{ 
		    TransactionStmt *n = makeNode(TransactionStmt); 
		    n->command = BEGIN_TRANS;
		    $$ = (Node *)n;
		}
	| BEGIN_TRANS WORK
		{
		    TransactionStmt *n = makeNode(TransactionStmt); 
		    n->command = BEGIN_TRANS;
		    $$ = (Node *)n;
		}
	| COMMIT WORK
		{
		    TransactionStmt *n = makeNode(TransactionStmt); 
		    n->command = END_TRANS;
		    $$ = (Node *)n;
		}
	| END_TRANS TRANSACTION
		{ 
		    TransactionStmt *n = makeNode(TransactionStmt); 
		    n->command = END_TRANS;
		    $$ = (Node *)n;
		}
	| ROLLBACK WORK
		{
		    TransactionStmt *n = makeNode(TransactionStmt); 
		    n->command = ABORT_TRANS;
		    $$ = (Node *)n;
		}

	| ABORT_TRANS
		{ 
		    TransactionStmt *n = makeNode(TransactionStmt); 
		    n->command = ABORT_TRANS; 
		    $$ = (Node *)n;
		}
	| BEGIN_TRANS
		{ 
		    TransactionStmt *n = makeNode(TransactionStmt); 
		    n->command = BEGIN_TRANS;
		    $$ = (Node *)n;
		}
	| COMMIT
		{
		    TransactionStmt *n = makeNode(TransactionStmt); 
		    n->command = END_TRANS;
		    $$ = (Node *)n;
		}

	| END_TRANS
		{ 
		    TransactionStmt *n = makeNode(TransactionStmt); 
		    n->command = END_TRANS;
		    $$ = (Node *)n;
		}
	| ROLLBACK
		{
		    TransactionStmt *n = makeNode(TransactionStmt); 
		    n->command = ABORT_TRANS;
		    $$ = (Node *)n;
		}
	;


/*****************************************************************************
 *
 *	QUERY:
 *		define view <viewname> '('target-list ')' [where <quals> ]
 *
 *****************************************************************************/

ViewStmt:  CREATE VIEW name AS RetrieveStmt
		{ 
		    ViewStmt *n = makeNode(ViewStmt);
		    n->viewname = $3;
		    n->query = (Query *)$5;
		    $$ = (Node *)n;
		}
	;


/*****************************************************************************
 *
 *	QUERY:
 *		load "filename"
 *
 *****************************************************************************/

LoadStmt: LOAD file_name
                { 
		    LoadStmt *n = makeNode(LoadStmt);
		    n->filename = $2;
		    $$ = (Node *)n;
		}
        ;


/*****************************************************************************
 *
 *	QUERY:
 *	        createdb dbname
 *
 *****************************************************************************/

CreatedbStmt:  CREATE DATABASE database_name
                {
		    CreatedbStmt *n = makeNode(CreatedbStmt);
		    n->dbname = $3;
		    $$ = (Node *)n;
		}
        ;


/*****************************************************************************
 *
 *	QUERY:
 *	        destroydb dbname
 *
 *****************************************************************************/

DestroydbStmt:  DROP DATABASE database_name
                {
		    DestroydbStmt *n = makeNode(DestroydbStmt);
		    n->dbname = $3;
		    $$ = (Node *)n;
		}
        ;


/*****************************************************************************
 *
 *	QUERY:
 *		cluster <index_name> on <relation_name>
 *
 *****************************************************************************/

ClusterStmt:  CLUSTER index_name ON relation_name 
		{
		   ClusterStmt *n = makeNode(ClusterStmt);
		   n->relname = $4;
		   n->indexname = $2;
		   $$ = (Node*)n;
		}
	;

/*****************************************************************************
 *
 *	QUERY:
 *		vacuum
 *
 *****************************************************************************/

VacuumStmt:  VACUUM opt_verbose opt_analyze
               {
                   VacuumStmt *n = makeNode(VacuumStmt);
                   n->verbose = $2;
                   n->analyze = $3;
                   n->vacrel = NULL;
                   n->va_spec = NIL;
                   $$ = (Node *)n;
               }
         | VACUUM opt_verbose relation_name opt_analyze opt_va_list
               {
                   VacuumStmt *n = makeNode(VacuumStmt);
                   n->verbose = $2;
                   n->analyze = $4;
                   n->vacrel = $3;
                   n->va_spec = $5;
                   if ( $5 != NIL && !$4 )
                   	elog (WARN, "parser: syntax error at or near \"(\"");
                   $$ = (Node *)n;
               }
       ;

opt_verbose:  VERBOSE			{ $$ = TRUE; }
	| /* EMPTY */			{ $$ = FALSE; }
	;

opt_analyze:  ANALYZE			{ $$ = TRUE; }
	| /* EMPTY */			{ $$ = FALSE; }
	;

opt_va_list: '(' va_list ')'
		{ $$ = $2; }
	| /* EMPTY */
		{ $$ = NIL; }
	;	

va_list: name
		{ $$=lcons($1,NIL); }
	| va_list ',' name
		{ $$=lappend($1,$3); }
	;	
       
/*****************************************************************************
 *
 *	QUERY:
 *		EXPLAIN query
 *
 *****************************************************************************/

ExplainStmt:  EXPLAIN opt_verbose OptimizableStmt
		{
		    ExplainStmt *n = makeNode(ExplainStmt);
		    n->verbose = $2;
		    n->query = (Query*)$3;
		    $$ = (Node *)n;
		}
	;

/*****************************************************************************
 *                                                                           *
 *	Optimizable Stmts:                                                   *
 *                                                                           *
 *	one of the five queries processed by the planner                     *
 *                                                                           *
 *	[ultimately] produces query-trees as specified                       *
 *	in the query-spec document in ~postgres/ref                          *
 *                                                                           *
 *****************************************************************************/

OptimizableStmt:  RetrieveStmt
	| CursorStmt	
	| ReplaceStmt
	| AppendStmt
        | NotifyStmt
        | DeleteStmt			/* by default all are $$=$1 */
	;


/*****************************************************************************
 *
 *	QUERY:
 *		INSERT STATEMENTS
 *  
 *****************************************************************************/

AppendStmt:  INSERT INTO relation_name opt_column_list insert_rest
		{
		    $5->relname = $3;
		    $5->cols = $4;
		    $$ = (Node *)$5;
                }
	;

insert_rest: VALUES '(' res_target_list2 ')'
		{
		    $$ = makeNode(AppendStmt);
		    $$->targetList = $3;
		    $$->fromClause = NIL;
		    $$->whereClause = NULL;
		}
	| SELECT res_target_list2 from_clause where_clause
		{
		    $$ = makeNode(AppendStmt);
		    $$->targetList = $2;
		    $$->fromClause = $3;
		    $$->whereClause = $4;
		}
	;

opt_column_list: '(' columnList ')'		{ $$ = $2; }
	| /*EMPTY*/				{ $$ = NIL; }
	;

columnList:	
	  columnList ',' columnElem
		{ $$ = lappend($1, $3); }
	| columnElem
		{ $$ = lcons($1, NIL); }
	;

columnElem: Id opt_indirection
	  	{
		    Ident *id = makeNode(Ident);
		    id->name = $1;
		    id->indirection = $2;
		    $$ = (Node *)id;
		}
	;

/*****************************************************************************
 *
 *	QUERY:
 *		DELETE STATEMENTS
 *
 *****************************************************************************/
   
DeleteStmt:  DELETE FROM relation_name
	     where_clause
                {
		    DeleteStmt *n = makeNode(DeleteStmt);
		    n->relname = $3;
		    n->whereClause = $4;
		    $$ = (Node *)n;
		}
        ;


/*****************************************************************************
 *
 *	QUERY:
 *		ReplaceStmt (UPDATE)
 *
 *****************************************************************************/

ReplaceStmt:  UPDATE relation_name 
	      SET res_target_list
	      from_clause
	      where_clause
                {
		    ReplaceStmt *n = makeNode(ReplaceStmt);
		    n->relname = $2;
		    n->targetList = $4;
		    n->fromClause = $5;
		    n->whereClause = $6;
		    $$ = (Node *)n;
                }
        ;
		 

/*****************************************************************************
 *
 *	QUERY:
 *		CURSOR STATEMENTS
 *
 *****************************************************************************/

CursorStmt:  DECLARE name opt_binary CURSOR FOR 
	     SELECT opt_unique res_target_list2	
	     from_clause where_clause group_clause sort_clause
		{
		    CursorStmt *n = makeNode(CursorStmt);

		    /* from PORTAL name */
		    /*
		     *  15 august 1991 -- since 3.0 postgres does locking
		     *  right, we discovered that portals were violating
		     *  locking protocol.  portal locks cannot span xacts.
		     *  as a short-term fix, we installed the check here. 
		     *				-- mao
		     */
		    if (!IsTransactionBlock())
			elog(WARN, "Named portals may only be used in begin/end transaction blocks.");

		    n->portalname = $2;
		    n->binary = $3;
		    n->unique = $7;
		    n->targetList = $8;
		    n->fromClause = $9;
		    n->whereClause = $10;
		    n->groupClause = $11;
		    n->sortClause = $12;
		    $$ = (Node *)n;
		}
	;


/*****************************************************************************
 *
 *	QUERY:
 *		SELECT STATEMENTS
 *
 *****************************************************************************/

RetrieveStmt:  SELECT opt_unique res_target_list2
	       result from_clause where_clause 
	       group_clause having_clause
	       sort_clause
  		{
		    RetrieveStmt *n = makeNode(RetrieveStmt);
		    n->unique = $2;
		    n->targetList = $3;
		    n->into = $4;
		    n->fromClause = $5;
		    n->whereClause = $6;
		    n->groupClause = $7;
		    n->havingClause = $8;
		    n->sortClause = $9;
		    $$ = (Node *)n;
		}
	;

result:  INTO TABLE relation_name
		{  $$= $3;  /* should check for archive level */  }
	| /*EMPTY*/
		{  $$ = NULL;  }
	;

opt_unique:  DISTINCT		{ $$ = "*"; }
	| DISTINCT ON Id	{ $$ = $3; }
	| /*EMPTY*/		{ $$ = NULL;}
	;

sort_clause:  ORDER BY sortby_list			{ $$ = $3; }
	|  /*EMPTY*/				{ $$ = NIL; }
	;

sortby_list:  sortby
		{ $$ = lcons($1, NIL); }
	| sortby_list ',' sortby
		{ $$ = lappend($1, $3); }
	;

sortby:  Id OptUseOp
		{ 
		    $$ = makeNode(SortGroupBy);
		    $$->resno = 0;
		    $$->range = NULL;
		    $$->name = $1;
		    $$->useOp = $2;
		}
	| Id '.' Id OptUseOp
		{
		    $$ = makeNode(SortGroupBy);
		    $$->resno = 0;
		    $$->range = $1;
		    $$->name = $3;
		    $$->useOp = $4;
		}
	| Iconst OptUseOp
		{
		    $$ = makeNode(SortGroupBy);
		    $$->resno = $1;
		    $$->range = NULL;
		    $$->name = NULL;
		    $$->useOp = $2;
		}
	;

OptUseOp:  USING Op				{ $$ = $2; }
	|  USING '<'				{ $$ = "<"; }
	|  USING '>'				{ $$ = ">"; }
	|  ASC					{ $$ = "<"; }
	|  DESC					{ $$ = ">"; }
	|  /*EMPTY*/				{ $$ = "<"; /*default*/ }
	;

index_params: index_list			{ $$ = $1; }
	| func_index				{ $$ = lcons($1,NIL); }
	;

index_list:
	  index_list ',' index_elem
		{ $$ = lappend($1, $3); }
	| index_elem
		{ $$ = lcons($1, NIL); }
	;

func_index: name '(' name_list ')' opt_type opt_class
		{
		    $$ = makeNode(IndexElem);
		    $$->name = $1;
		    $$->args = $3;
		    $$->class = $6;
	            $$->tname = $5;
		}
	  ;

index_elem:  attr_name opt_type opt_class
		{
		    $$ = makeNode(IndexElem);
		    $$->name = $1;
		    $$->args = NIL;
		    $$->class = $3;
 	            $$->tname = $2;
		}
	;

opt_type: ':' Typename                          { $$ = $2;}
        |  /*EMPTY*/                            { $$ = NULL;}
        ;

opt_class:  class
	|  WITH class				{ $$ = $2; }
	|  /*EMPTY*/				{ $$ = NULL; }
	;

/*
 *  jimmy bell-style recursive queries aren't supported in the
 *  current system.
 *
 *  ...however, recursive addattr and rename supported.  make special 
 *  cases for these.
 * 
 *  XXX i believe '*' should be the default behavior, but...
 */
opt_inh_star: '*'			{ $$ = TRUE; }
	|  /*EMPTY*/			{ $$ = FALSE; }
	;

relation_name_list:	name_list ;

name_list: name 			
		{ $$=lcons(makeString($1),NIL); }
	| name_list ',' name		
		{ $$=lappend($1,makeString($3)); }
	;	

group_clause: GROUP BY groupby_list		{ $$ = $3; }
	| /*EMPTY*/				{ $$ = NIL; }
	;

groupby_list: groupby				{ $$ = lcons($1, NIL); }
	| groupby_list ',' groupby		{ $$ = lappend($1, $3); }
	;

groupby:  Id
		{
		    $$ = makeNode(SortGroupBy);
		    $$->resno = 0;
		    $$->range = NULL;
		    $$->name = $1;
		    $$->useOp = NULL;
		}
	| Id '.' Id
		{
		    $$ = makeNode(SortGroupBy);
		    $$->resno = 0;
		    $$->range = $1;
		    $$->name = $3;
		    $$->useOp = NULL;
		}
	| Iconst
		{
		    $$ = makeNode(SortGroupBy);
		    $$->resno = $1;
		    $$->range = NULL;
		    $$->name = NULL;
		    $$->useOp = NULL;
		}
	;

having_clause: HAVING a_expr			{ $$ = $2; }
	| /*EMPTY*/				{ $$ = NULL; }
	;

/*****************************************************************************
 *  
 *  clauses common to all Optimizable Stmts:
 *  	from_clause 	-
 *      where_clause 	-
 *	
 *****************************************************************************/

from_clause:  FROM from_list 			{ $$ = $2; }
	| /*EMPTY*/				{ $$ = NIL; }
	;

from_list:  from_list ',' from_val
		{ $$ = lappend($1, $3); }
	|  from_val
		{ $$ = lcons($1, NIL); }
	;

from_val:  relation_expr AS var_name
		{
		    $$ = makeNode(RangeVar);
		    $$->relExpr = $1;
		    $$->name = $3;
		}	
	| relation_expr var_name
		{
		    $$ = makeNode(RangeVar);
		    $$->relExpr = $1;
		    $$->name = $2;
		}
	| relation_expr
		{
		    $$ = makeNode(RangeVar);
		    $$->relExpr = $1;
		    $$->name = NULL;
		}
	;

where_clause:  WHERE a_expr		{ $$ = $2; }
	| /*EMPTY*/		        { $$ = NULL;  /* no qualifiers */ } 
	;

relation_expr:  relation_name
  		{ 
		    /* normal relations */
		    $$ = makeNode(RelExpr);
		    $$->relname = $1;
		    $$->inh = FALSE;
		    $$->timeRange = NULL;
		}
	| relation_name '*'		  %prec '='
		{ 
		    /* inheiritance query */
		    $$ = makeNode(RelExpr);
		    $$->relname = $1;
		    $$->inh = TRUE;
		    $$->timeRange = NULL;
		}
	| relation_name time_range 
		{ 
		    /* time-qualified query */
		    $$ = makeNode(RelExpr);
		    $$->relname = $1;
		    $$->inh = FALSE;
		    $$->timeRange = $2;
		}
	;

	  
time_range:  '[' opt_range_start ',' opt_range_end ']'
        	{ 
		    $$ = makeNode(TimeRange);
		    $$->startDate = $2;
		    $$->endDate = $4;
		}
	| '[' date ']'
		{ 
		    $$ = makeNode(TimeRange);
		    $$->startDate = $2;
		    $$->endDate = NULL;
		}
        ;

opt_range_start:  date
	|  /*EMPTY*/				{ $$ = "epoch"; }
	;

opt_range_end:  date
	|  /*EMPTY*/				{ $$ = "now"; }
	;

opt_array_bounds:  '[' ']' nest_array_bounds
		{  $$ = lcons(makeInteger(-1), $3); }
	| '[' Iconst ']' nest_array_bounds
		{  $$ = lcons(makeInteger($2), $4); }
	| /* EMPTY */				
		{  $$ = NIL; }
	;

nest_array_bounds:  '[' ']' nest_array_bounds
		{  $$ = lcons(makeInteger(-1), $3); }
	| '[' Iconst ']' nest_array_bounds 
		{  $$ = lcons(makeInteger($2), $4); }
	| /*EMPTY*/
		{  $$ = NIL; }
	;

typname:  name  
		{
		    char *tname = xlateSqlType($1);
		    $$ = makeNode(TypeName);
		    $$->name = tname;

		    /* Is this the name of a complex type? If so, implement
		     * it as a set.
		     */
		    if (!strcmp(saved_relname, tname)) {
		 	/* This attr is the same type as the relation 
			 * being defined. The classic example: create
			 * emp(name=text,mgr=emp)
			 */
			$$->setof = TRUE;
		    }else if (get_typrelid((Type)type(tname))
				!= InvalidOid) {
			 /* (Eventually add in here that the set can only 
			  * contain one element.)
			  */
			$$->setof = TRUE;
	    	    } else {
			$$->setof = FALSE;
		    }
		}
        | SETOF name
		{
		    $$ = makeNode(TypeName);
		    $$->name = $2;
		    $$->setof = TRUE;
		}
        ;

Typename:  typname opt_array_bounds		
		{ 
		    $$ = $1;
		    $$->arrayBounds = $2;
		}
	| name '(' Iconst ')'
		{
		    /*
		     * The following implements char() and varchar().
		     * We do it here instead of the 'typname:' production
		     * because we don't want to allow arrays of varchar().
		     * I haven't thought about whether that will work or not.
		      *                             - ay 6/95
		     */
		    $$ = makeNode(TypeName);
		    if (!strcasecmp($1, "char")) {
			$$->name = "bpchar"; /*  strdup("bpchar"); */
		    } else if (!strcasecmp($1, "varchar")) {
			$$->name = "varchar"; /* strdup("varchar"); */
		    } else {
			yyerror("parse error");
		    }
		    if ($3 < 1) {
			elog(WARN, "length for '%s' type must be at least 1",
			     $1);
		    } else if ($3 > 4096) {
			/* we can store a char() of length up to the size
			   of a page (8KB) - page headers and friends but
			   just to be safe here...  - ay 6/95 */
			elog(WARN, "length for '%s' type cannot exceed 4096",
			     $1);
		    }
		    /* we actually implement this sort of like a varlen, so
		       the first 4 bytes is the length. (the difference
		       between this and "text" is that we blank-pad and 
		       truncate where necessary */
		    $$->typlen = 4 + $3;
		}
	;


/*****************************************************************************
 *
 *  expression grammar, still needs some cleanup
 *
 *****************************************************************************/

a_expr_or_null: a_expr
		{ $$ = $1;}
	| Pnull
		{	
		    A_Const *n = makeNode(A_Const);
		    n->val.type = T_Null;
		    $$ = (Node *)n;
		}
		
a_expr:  attr opt_indirection
		{
		    $1->indirection = $2;
		    $$ = (Node *)$1;
		}
	| AexprConst
		{   $$ = $1;  }
	| '-' a_expr %prec UMINUS
		{   $$ = makeA_Expr(OP, "-", NULL, $2); }
	| a_expr '+' a_expr
		{   $$ = makeA_Expr(OP, "+", $1, $3); }
	| a_expr '-' a_expr
		{   $$ = makeA_Expr(OP, "-", $1, $3); }
	| a_expr '/' a_expr
		{   $$ = makeA_Expr(OP, "/", $1, $3); }
	| a_expr '*' a_expr
		{   $$ = makeA_Expr(OP, "*", $1, $3); }
	| a_expr '<' a_expr
		{   $$ = makeA_Expr(OP, "<", $1, $3); }
	| a_expr '>' a_expr
		{   $$ = makeA_Expr(OP, ">", $1, $3); }
	| a_expr '=' a_expr
		{   $$ = makeA_Expr(OP, "=", $1, $3); }
	| ':' a_expr
		{   $$ = makeA_Expr(OP, ":", NULL, $2); }
	| ';' a_expr
		{   $$ = makeA_Expr(OP, ";", NULL, $2); }
	| '|' a_expr
		{   $$ = makeA_Expr(OP, "|", NULL, $2); }
	| AexprConst TYPECAST Typename
		{ 
		    /* AexprConst can be either A_Const or ParamNo */
		    if (nodeTag($1) == T_A_Const) {
			((A_Const *)$1)->typename = $3;
		    }else {
			((ParamNo *)$1)->typename = $3;
		    }
		    $$ = (Node *)$1;
		}
	| CAST AexprConst AS Typename
		{
		    /* AexprConst can be either A_Const or ParamNo */
		    if (nodeTag($2) == T_A_Const) {
			((A_Const *)$2)->typename = $4;
		    }else {
			((ParamNo *)$2)->typename = $4;
		    }
		    $$ = (Node *)$2;
		}
	| '(' a_expr_or_null ')'
		{   $$ = $2; }
	| a_expr Op a_expr
		{   $$ = makeA_Expr(OP, $2, $1, $3); }
	| a_expr LIKE a_expr
		{   $$ = makeA_Expr(OP, "~~", $1, $3); }
	| a_expr NOT LIKE a_expr
		{   $$ = makeA_Expr(OP, "!~~", $1, $4); }
	| Op a_expr
		{   $$ = makeA_Expr(OP, $1, NULL, $2); }
	| a_expr Op
		{   $$ = makeA_Expr(OP, $2, $1, NULL); }
	| Id
		{   /* could be a column name or a relation_name */
		    Ident *n = makeNode(Ident);
		    n->name = $1;
		    n->indirection = NULL;
		    $$ = (Node *)n;
		}
	| name '(' '*' ')'
		{
		    FuncCall *n = makeNode(FuncCall);
		    Ident *star = makeNode(Ident);

		    /* cheap hack for aggregate (eg. count) */
		    star->name = "oid"; 
		    n->funcname = $1;
		    n->args = lcons(star, NIL);
		    $$ = (Node *)n;
		}
	| name '(' ')'
		{
		    FuncCall *n = makeNode(FuncCall);
		    n->funcname = $1;
		    n->args = NIL;
		    $$ = (Node *)n;
		}
	| name '(' expr_list ')'
		{
		    FuncCall *n = makeNode(FuncCall);
		    n->funcname = $1;
		    n->args = $3;
		    $$ = (Node *)n;
		}
	| a_expr ISNULL
		{   $$ = makeA_Expr(ISNULL, NULL, $1, NULL); }
	| a_expr IS PNULL
		{   $$ = makeA_Expr(ISNULL, NULL, $1, NULL); }
	| a_expr NOTNULL
		{   $$ = makeA_Expr(NOTNULL, NULL, $1, NULL); }
	| a_expr IS NOT PNULL
		{   $$ = makeA_Expr(NOTNULL, NULL, $1, NULL); }
	| a_expr BETWEEN AexprConst AND AexprConst
		{   $$ = makeA_Expr(AND, NULL,
			makeA_Expr(OP, ">=", $1, $3),
			makeA_Expr(OP, "<=", $1,$5));
		}
	| a_expr NOT BETWEEN AexprConst AND AexprConst
		{   $$ = makeA_Expr(OR, NULL,
			makeA_Expr(OP, "<", $1, $4),
			makeA_Expr(OP, ">", $1, $6));
		}
	| a_expr IN { saved_In_Expr = $1; } '(' in_expr_nodes ')'
		{   $$ = $5; }
	| a_expr NOT IN { saved_In_Expr = $1; } '(' not_in_expr_nodes ')'
		{   $$ = $6; }
	| a_expr AND a_expr
		{   $$ = makeA_Expr(AND, NULL, $1, $3); }
	| a_expr OR a_expr
		{   $$ = makeA_Expr(OR, NULL, $1, $3); }
	| NOT a_expr
		{   $$ = makeA_Expr(NOT, NULL, NULL, $2); }
	;

opt_indirection:  '[' a_expr ']' opt_indirection 
		{
		    A_Indices *ai = makeNode(A_Indices);
		    ai->lidx = NULL;
		    ai->uidx = $2;
		    $$ = lcons(ai, $4);
		}
	| '[' a_expr ':' a_expr ']' opt_indirection 
		{
		    A_Indices *ai = makeNode(A_Indices);
		    ai->lidx = $2;
		    ai->uidx = $4;
		    $$ = lcons(ai, $6);
		}
	| /* EMPTY */			
		{   $$ = NIL; }
	;
   
expr_list: a_expr_or_null
		{ $$ = lcons($1, NIL); }
	|  expr_list ',' a_expr_or_null
		{ $$ = lappend($1, $3); }
	;

in_expr_nodes: AexprConst
		{   $$ = makeA_Expr(OP, "=", saved_In_Expr, $1); }
	|  in_expr_nodes ',' AexprConst
		{   $$ = makeA_Expr(OR, NULL, $1,
			makeA_Expr(OP, "=", saved_In_Expr, $3));
		}
	;

not_in_expr_nodes: AexprConst
		{   $$ = makeA_Expr(OP, "<>", saved_In_Expr, $1); }
	|  not_in_expr_nodes ',' AexprConst
		{   $$ = makeA_Expr(AND, NULL, $1,
			makeA_Expr(OP, "<>", saved_In_Expr, $3));
		}
	;

attr:  relation_name '.' attrs
        	{
		    $$ = makeNode(Attr);
		    $$->relname = $1;
		    $$->paramNo = NULL;
		    $$->attrs = $3;
		    $$->indirection = NULL;
		}
	| ParamNo '.' attrs
		{
		    $$ = makeNode(Attr);
		    $$->relname = NULL;
		    $$->paramNo = $1;
		    $$->attrs = $3;
		    $$->indirection = NULL;
		}
	;

attrs:    attr_name                       	
		{ $$ = lcons(makeString($1), NIL); }
	| attrs '.' attr_name             
		{ $$ = lappend($1, makeString($3)); }
	| attrs '.' '*'
		{ $$ = lappend($1, makeString("*")); }
	;


/*****************************************************************************
 *
 *  target lists
 *
 *****************************************************************************/

res_target_list:  res_target_list ',' res_target_el	
		{   $$ = lappend($1,$3);  }
	| res_target_el 			
		{   $$ = lcons($1, NIL);  }
	| '*'
		{
		    ResTarget *rt = makeNode(ResTarget);
		    Attr *att = makeNode(Attr);
		    att->relname = "*";
		    att->paramNo = NULL;
		    att->attrs = NULL;
		    att->indirection = NIL;
		    rt->name = NULL;
		    rt->indirection = NULL;
		    rt->val = (Node *)att;
		    $$ = lcons(rt, NIL);
		}
	;

res_target_el: Id opt_indirection '=' a_expr_or_null
		{
		    $$ = makeNode(ResTarget);
		    $$->name = $1;
		    $$->indirection = $2;
		    $$->val = (Node *)$4;
		}
	| attr opt_indirection
		{
		    $$ = makeNode(ResTarget);
		    $$->name = NULL;
		    $$->indirection = $2;
		    $$->val = (Node *)$1;
		}
	| relation_name '.' '*'
		{   
		    Attr *att = makeNode(Attr);
		    att->relname = $1;
		    att->paramNo = NULL;
		    att->attrs = lcons(makeString("*"), NIL);
		    att->indirection = NIL;
		    $$ = makeNode(ResTarget);
		    $$->name = NULL;
		    $$->indirection = NULL;
		    $$->val = (Node *)att;
		}
	;		 

/*
** target list for select.
** should get rid of the other but is still needed by the defunct retrieve into
** and update (uses a subset)
*/
res_target_list2:
	  res_target_list2 ',' res_target_el2	
		{   $$ = lappend($1, $3);  }
	| res_target_el2 			
		{   $$ = lcons($1, NIL);  }
	;

/* AS is not optional because shift/red conflict with unary ops */
res_target_el2: a_expr AS Id 
	  	{
		    $$ = makeNode(ResTarget);
		    $$->name = $3;
		    $$->indirection = NULL;
		    $$->val = (Node *)$1;
		}
	| a_expr_or_null
		{
		    $$ = makeNode(ResTarget);
		    $$->name = NULL;
		    $$->indirection = NULL;
		    $$->val = (Node *)$1;
		}
	| relation_name '.' '*'
		{
		    Attr *att = makeNode(Attr);
		    att->relname = $1;
		    att->paramNo = NULL;
		    att->attrs = lcons(makeString("*"), NIL);
		    att->indirection = NIL;
		    $$ = makeNode(ResTarget);
		    $$->name = NULL;
		    $$->indirection = NULL;
		    $$->val = (Node *)att;
		}
	| '*'
		{
		    Attr *att = makeNode(Attr);
		    att->relname = "*";
		    att->paramNo = NULL;
		    att->attrs = NULL;
		    att->indirection = NIL;
		    $$ = makeNode(ResTarget);
		    $$->name = NULL;
		    $$->indirection = NULL;
		    $$->val = (Node *)att;
		}
	;

opt_id:  Id					{ $$ = $1; }
	| /* EMPTY */				{ $$ = NULL; }
	;

relation_name:  SpecialRuleRelation
          	{
                   $$ = $1;
                   strNcpy(saved_relname, $1, NAMEDATALEN-1);
	        }
	| Id
	  	{
		    /* disallow refs to magic system tables */
  		    if (strcmp(LogRelationName, $1) == 0
  		       || strcmp(VariableRelationName, $1) == 0
  		       || strcmp(TimeRelationName, $1) == 0
  		       || strcmp(MagicRelationName, $1) == 0) {
			elog(WARN, "%s cannot be accessed by users", $1);
		    } else {
			$$ = $1;
		    }
                    strNcpy(saved_relname, $1, NAMEDATALEN-1);
		}
	;

database_name:		Id		{ $$ = $1; };
access_method: 		Id		{ $$ = $1; };
attr_name: 		Id		{ $$ = $1; };
class: 			Id		{ $$ = $1; };
index_name: 		Id		{ $$ = $1; };
var_name:		Id		{ $$ = $1; };
name:			Id		{ $$ = $1; };

date:			Sconst		{ $$ = $1; };
file_name:		Sconst		{ $$ = $1; };
recipe_name:		Id		{ $$ = $1; };

AexprConst:  Iconst
		{  
		    A_Const *n = makeNode(A_Const);
		    n->val.type = T_Integer;
		    n->val.val.ival = $1;
		    $$ = (Node *)n;
		}
	| FCONST
		{  
		    A_Const *n = makeNode(A_Const);
		    n->val.type = T_Float;
		    n->val.val.dval = $1;
		    $$ = (Node *)n;
		}
	| Sconst
		{  
		    A_Const *n = makeNode(A_Const);
		    n->val.type = T_String;
		    n->val.val.str = $1;
		    $$ = (Node *)n;
		}
	| ParamNo
		{   $$ = (Node *)$1;  }
	;

ParamNo:  PARAM
		{
		    $$ = makeNode(ParamNo);
		    $$->number = $1;
		}
	;

NumConst:  Iconst			{ $$ = makeInteger($1); }
	|  FCONST 			{ $$ = makeFloat($1); }
	;

Iconst:  ICONST				{ $$ = $1; };
Sconst:	 SCONST				{ $$ = $1; };

Id:  IDENT           			{ $$ = $1; };

SpecialRuleRelation:  CURRENT
		{ 
		    if (QueryIsRule)
			$$ = "*CURRENT*";
		    else 
			elog(WARN,"CURRENT used in non-rule query");
		}
	| NEW
		{ 
		    if (QueryIsRule)
			$$ = "*NEW*";
		    else 
			elog(WARN,"NEW used in non-rule query"); 
		}
	;

Type:	P_TYPE;
Pnull:	PNULL;


%%

static Node *makeA_Expr(int oper, char *opname, Node *lexpr, Node *rexpr)
{
    A_Expr *a = makeNode(A_Expr);
    a->oper = oper;
    a->opname = opname;
    a->lexpr = lexpr;
    a->rexpr = rexpr;
    return (Node *)a;
}

static char *
xlateSqlType(char *name)
{
    if (!strcasecmp(name,"int") ||
	!strcasecmp(name,"integer"))
	return "int4"; /* strdup("int4") --   strdup leaks memory here */
    else if (!strcasecmp(name, "smallint"))
	return "int2";
    else if (!strcasecmp(name, "float") ||
	     !strcasecmp(name, "real"))
	return "float8";
    else
	return name;
}

void parser_init(Oid *typev, int nargs)
{
    QueryIsRule = false;
    saved_relname[0]= '\0';
    saved_In_Expr = NULL;
    
    param_type_init(typev, nargs);
}