/* src/interfaces/ecpg/preproc/ecpg.trailer */

statements: /* EMPTY */
	| statements statement
	{
		/* Reclaim local storage used while processing statement */
		reclaim_local_storage();
		/* Clean up now-dangling location pointer */
		@$ = "";
	}
	;

statement: ecpgstart at toplevel_stmt ';'
	{
		if (connection)
			free(connection);
		connection = NULL;
	}
	| ecpgstart toplevel_stmt ';'
	{
		if (connection)
			free(connection);
		connection = NULL;
	}
	| ecpgstart ECPGVarDeclaration
	{
		fprintf(base_yyout, "%s", @$);
		output_line_number();
	}
	| ECPGDeclaration
	| c_thing
	{
		fprintf(base_yyout, "%s", @$);
	}
	| CPP_LINE
	{
		fprintf(base_yyout, "%s", @$);
	}
	| '{'
	{
		braces_open++;
		fputs("{", base_yyout);
	}
	| '}'
	{
		if (braces_open > 0)
		{
			remove_typedefs(braces_open);
			remove_variables(braces_open);
			if (--braces_open == 0)
			{
				free(current_function);
				current_function = NULL;
			}
		}
		fputs("}", base_yyout);
	}
	;

CreateAsStmt: CREATE OptTemp TABLE create_as_target AS
	{
		FoundInto = 0;
	} SelectStmt opt_with_data
	{
		if (FoundInto == 1)
			mmerror(PARSE_ERROR, ET_ERROR, "CREATE TABLE AS cannot specify INTO");
	}
	| CREATE OptTemp TABLE IF_P NOT EXISTS create_as_target AS
	{
		FoundInto = 0;
	} SelectStmt opt_with_data
	{
		if (FoundInto == 1)
			mmerror(PARSE_ERROR, ET_ERROR, "CREATE TABLE AS cannot specify INTO");
	}
	;

at: AT connection_object
	{
		if (connection)
			free(connection);
		connection = mm_strdup(@2);

		/*
		 * Do we have a variable as connection target?  Remove the variable
		 * from the variable list or else it will be used twice.
		 */
		if (argsinsert != NULL)
			argsinsert = NULL;
	}
	;

/*
 * the exec sql connect statement: connect to the given database
 */
ECPGConnect: SQL_CONNECT TO connection_target opt_connection_name opt_user
	{
		@$ = cat_str(5, @3, ",", @5, ",", @4);
	}
	| SQL_CONNECT TO DEFAULT
	{
		@$ = "NULL, NULL, NULL, \"DEFAULT\"";
	}
	/* also allow ORACLE syntax */
	| SQL_CONNECT ora_user
	{
		@$ = cat_str(3, "NULL,", @2, ", NULL");
	}
	| DATABASE connection_target
	{
		@$ = cat2_str(@2, ", NULL, NULL, NULL");
	}
	;

connection_target: opt_database_name opt_server opt_port
	{
		/* old style: dbname[@server][:port] */
		if (strlen(@2) > 0 && *(@2) != '@')
			mmerror(PARSE_ERROR, ET_ERROR, "expected \"@\", found \"%s\"", @2);

		/* C strings need to be handled differently */
		if (@1[0] == '\"')
			@$ = @1;
		else
			@$ = make3_str("\"", make3_str(@1, @2, @3), "\"");
	}
	| db_prefix ':' server opt_port '/' opt_database_name opt_options
	{
		/* new style: <tcp|unix>:postgresql://server[:port][/dbname] */
		if (strncmp(@1, "unix:postgresql", strlen("unix:postgresql")) != 0 && strncmp(@1, "tcp:postgresql", strlen("tcp:postgresql")) != 0)
			mmerror(PARSE_ERROR, ET_ERROR, "only protocols \"tcp\" and \"unix\" and database type \"postgresql\" are supported");

		if (strncmp(@3, "//", strlen("//")) != 0)
			mmerror(PARSE_ERROR, ET_ERROR, "expected \"://\", found \"%s\"", @3);

		if (strncmp(@1, "unix", strlen("unix")) == 0 &&
			strncmp(@3 + strlen("//"), "localhost", strlen("localhost")) != 0 &&
			strncmp(@3 + strlen("//"), "127.0.0.1", strlen("127.0.0.1")) != 0)
			mmerror(PARSE_ERROR, ET_ERROR, "Unix-domain sockets only work on \"localhost\" but not on \"%s\"", @3 + strlen("//"));

		@$ = make3_str(make3_str("\"", @1, ":"), @3, make3_str(make3_str(@4, "/", @6), @7, "\""));
	}
	| char_variable
	| ecpg_sconst
	{
		/*
		 * We can only process double quoted strings not single quoted ones,
		 * so we change the quotes. Note that the rule for ecpg_sconst adds
		 * these single quotes.
		 */
		char   *str = loc_strdup(@1);

		str[0] = '\"';
		str[strlen(str) - 1] = '\"';
		@$ = str;
	}
	;

opt_database_name: name
	| /* EMPTY */
	;

db_prefix: ecpg_ident cvariable
	{
		if (strcmp(@2, "postgresql") != 0 && strcmp(@2, "postgres") != 0)
			mmerror(PARSE_ERROR, ET_ERROR, "expected \"postgresql\", found \"%s\"", @2);

		if (strcmp(@1, "tcp") != 0 && strcmp(@1, "unix") != 0)
			mmerror(PARSE_ERROR, ET_ERROR, "invalid connection type: %s", @1);

		@$ = make3_str(@1, ":", @2);
	}
	;

server: Op server_name
	{
		if (strcmp(@1, "@") != 0 && strcmp(@1, "//") != 0)
			mmerror(PARSE_ERROR, ET_ERROR, "expected \"@\" or \"://\", found \"%s\"", @1);

		@$ = make2_str(@1, @2);
	}
	;

opt_server: server
	| /* EMPTY */
	;

server_name: ColId
	| ColId '.' server_name
	| IP
	;

opt_port: ':' Iconst
	{
		@$ = make2_str(":", @2);
	}
	| /* EMPTY */
	;

opt_connection_name: AS connection_object
	{
		@$ = @2;
	}
	| /* EMPTY */
	{
		@$ = "NULL";
	}
	;

opt_user: USER ora_user
	{
		@$ = @2;
	}
	| /* EMPTY */
	{
		@$ = "NULL, NULL";
	}
	;

ora_user: user_name
	{
		@$ = cat2_str(@1, ", NULL");
	}
	| user_name '/' user_name
	{
		@$ = cat_str(3, @1, ",", @3);
	}
	| user_name SQL_IDENTIFIED BY user_name
	{
		@$ = cat_str(3, @1, ",", @4);
	}
	| user_name USING user_name
	{
		@$ = cat_str(3, @1, ",", @3);
	}
	;

user_name: RoleId
	{
		if (@1[0] == '\"')
			@$ = @1;
		else
			@$ = make3_str("\"", @1, "\"");
	}
	| ecpg_sconst
	{
		if (@1[0] == '\"')
			@$ = @1;
		else
			@$ = make3_str("\"", @1, "\"");
	}
	| civar
	{
		enum ECPGttype type = argsinsert->variable->type->type;

		/* if array see what's inside */
		if (type == ECPGt_array)
			type = argsinsert->variable->type->u.element->type;

		/* handle varchars */
		if (type == ECPGt_varchar)
			@$ = make2_str(argsinsert->variable->name, ".arr");
		else
			@$ = argsinsert->variable->name;
	}
	;

char_variable: cvariable
	{
		/* check if we have a string variable */
		struct variable *p = find_variable(@1);
		enum ECPGttype type = p->type->type;

		/* If we have just one character this is not a string */
		if (atol(p->type->size) == 1)
			mmerror(PARSE_ERROR, ET_ERROR, "invalid data type");
		else
		{
			/* if array see what's inside */
			if (type == ECPGt_array)
				type = p->type->u.element->type;

			switch (type)
			{
				case ECPGt_char:
				case ECPGt_unsigned_char:
				case ECPGt_string:
					@$ = @1;
					break;
				case ECPGt_varchar:
					@$ = make2_str(@1, ".arr");
					break;
				default:
					mmerror(PARSE_ERROR, ET_ERROR, "invalid data type");
					@$ = @1;
					break;
			}
		}
	}
	;

opt_options: Op connect_options
	{
		if (strlen(@1) == 0)
			mmerror(PARSE_ERROR, ET_ERROR, "incomplete statement");

		if (strcmp(@1, "?") != 0)
			mmerror(PARSE_ERROR, ET_ERROR, "unrecognized token \"%s\"", @1);

		@$ = make2_str("?", @2);
	}
	| /* EMPTY */
	;

connect_options: ColId opt_opt_value
	{
		@$ = make2_str(@1, @2);
	}
	| ColId opt_opt_value Op connect_options
	{
		if (strlen(@3) == 0)
			mmerror(PARSE_ERROR, ET_ERROR, "incomplete statement");

		if (strcmp(@3, "&") != 0)
			mmerror(PARSE_ERROR, ET_ERROR, "unrecognized token \"%s\"", @3);

		@$ = make3_str(make2_str(@1, @2), @3, @4);
	}
	;

opt_opt_value: /* EMPTY */
	| '=' Iconst
	{
		@$ = make2_str("=", @2);
	}
	| '=' ecpg_ident
	{
		@$ = make2_str("=", @2);
	}
	| '=' civar
	{
		@$ = make2_str("=", @2);
	}
	;

prepared_name: name
	{
		size_t		slen = strlen(@1);

		if (@1[0] == '\"' && @1[slen - 1] == '\"')	/* already quoted? */
			@$ = @1;
		else					/* not quoted => convert to lowercase */
		{
			char	   *str = loc_alloc(slen + 3);

			str[0] = '\"';
			for (size_t i = 0; i < slen; i++)
				str[i + 1] = tolower((unsigned char) @1[i]);
			str[slen + 1] = '\"';
			str[slen + 2] = '\0';
			@$ = str;
		}
	}
	| char_variable
	;

/*
 * Declare Statement
 */
ECPGDeclareStmt: DECLARE prepared_name STATEMENT
	{
		struct declared_list *ptr;

		/* Check whether the declared name has been defined or not */
		for (ptr = g_declared_list; ptr != NULL; ptr = ptr->next)
		{
			if (strcmp(@2, ptr->name) == 0)
			{
				/* re-definition is not allowed */
				mmerror(PARSE_ERROR, ET_ERROR, "name \"%s\" is already declared", ptr->name);
			}
		}

		/* Add a new declared name into the g_declared_list */
		ptr = (struct declared_list *) mm_alloc(sizeof(struct declared_list));
		if (ptr)
		{
			/* initial definition */
			ptr->name = mm_strdup(@2);
			if (connection)
				ptr->connection = mm_strdup(connection);
			else
				ptr->connection = NULL;

			ptr->next = g_declared_list;
			g_declared_list = ptr;
		}

		@$ = cat_str(3, "/* declare ", @2, " as an SQL identifier */");
	}
	;

/*
 * Declare a prepared cursor. The syntax is different from the standard
 * declare statement, so we create a new rule.
 */
ECPGCursorStmt: DECLARE cursor_name cursor_options CURSOR opt_hold FOR prepared_name
	{
		struct cursor *ptr,
				   *this;
		const char *cursor_marker = @2[0] == ':' ? "$0" : @2;
		int			(*strcmp_fn) (const char *, const char *) = ((@2[0] == ':' || @2[0] == '"') ? strcmp : pg_strcasecmp);
		struct variable *thisquery = (struct variable *) mm_alloc(sizeof(struct variable));
		char	   *comment;
		char	   *con;

		if (INFORMIX_MODE && pg_strcasecmp(@2, "database") == 0)
			mmfatal(PARSE_ERROR, "\"database\" cannot be used as cursor name in INFORMIX mode");

		check_declared_list(@7);
		con = connection ? connection : "NULL";
		for (ptr = cur; ptr != NULL; ptr = ptr->next)
		{
			if (strcmp_fn(@2, ptr->name) == 0)
			{
				/* re-definition is a bug */
				if (@2[0] == ':')
					mmerror(PARSE_ERROR, ET_ERROR, "using variable \"%s\" in different declare statements is not supported", @2 + 1);
				else
					mmerror(PARSE_ERROR, ET_ERROR, "cursor \"%s\" is already defined", @2);
			}
		}

		this = (struct cursor *) mm_alloc(sizeof(struct cursor));

		/* initial definition */
		this->next = cur;
		this->name = mm_strdup(@2);
		this->function = (current_function ? mm_strdup(current_function) : NULL);
		this->connection = connection ? mm_strdup(connection) : NULL;
		this->opened = false;
		this->command = mm_strdup(cat_str(6, "declare", cursor_marker, @3, "cursor", @5, "for $1"));
		this->argsresult = NULL;
		this->argsresult_oos = NULL;

		thisquery->type = &ecpg_query;
		thisquery->brace_level = 0;
		thisquery->next = NULL;
		thisquery->name = (char *) mm_alloc(sizeof("ECPGprepared_statement(, , __LINE__)") + strlen(con) + strlen(@7));
		sprintf(thisquery->name, "ECPGprepared_statement(%s, %s, __LINE__)", con, @7);

		this->argsinsert = NULL;
		this->argsinsert_oos = NULL;
		if (@2[0] == ':')
		{
			struct variable *var = find_variable(@2 + 1);

			remove_variable_from_list(&argsinsert, var);
			add_variable_to_head(&(this->argsinsert), var, &no_indicator);
		}
		add_variable_to_head(&(this->argsinsert), thisquery, &no_indicator);

		cur = this;

		comment = cat_str(3, "/*", this->command, "*/");

		@$ = cat_str(2, adjust_outofscope_cursor_vars(this),
					 comment);
	}
	;

ECPGExecuteImmediateStmt: EXECUTE IMMEDIATE execstring
	{
		/*
		 * execute immediate means prepare the statement and immediately
		 * execute it
		 */
		@$ = @3;
	}
	;

/*
 * variable declaration outside exec sql declare block
 */
ECPGVarDeclaration: single_vt_declaration;

single_vt_declaration: type_declaration
	| var_declaration
	;

precision: NumericOnly
	;

opt_scale: ',' NumericOnly
	{
		@$ = @2;
	}
	| /* EMPTY */
	;

ecpg_interval: opt_interval
	| YEAR_P TO MINUTE_P
	| YEAR_P TO SECOND_P
	| DAY_P TO DAY_P
	| MONTH_P TO MONTH_P
	;

/*
 * variable declaration inside exec sql declare block
 */
ECPGDeclaration: sql_startdeclare
	{
		fputs("/* exec sql begin declare section */", base_yyout);
	}
	var_type_declarations sql_enddeclare
	{
		fprintf(base_yyout, "%s/* exec sql end declare section */", @3);
		output_line_number();
	}
	;

sql_startdeclare: ecpgstart BEGIN_P DECLARE SQL_SECTION ';'
	{
	}
	;

sql_enddeclare: ecpgstart END_P DECLARE SQL_SECTION ';'
	{
	}
	;

var_type_declarations: /* EMPTY */
	| vt_declarations
	;

vt_declarations: single_vt_declaration
	| CPP_LINE
	| vt_declarations single_vt_declaration
	| vt_declarations CPP_LINE
	;

variable_declarations: var_declaration
	| variable_declarations var_declaration
	;

type_declaration: S_TYPEDEF
	{
		/* reset this variable so we see if there was */
		/* an initializer specified */
		initializer = 0;
	}
	var_type	opt_pointer ECPGColLabel opt_array_bounds ';'
	{
		add_typedef(@5, $6.index1, $6.index2, $3.type_enum, $3.type_dimension, $3.type_index, initializer, *@4 ? 1 : 0);

		fprintf(base_yyout, "typedef %s %s %s %s;\n", $3.type_str, *@4 ? "*" : "", @5, $6.str);
		output_line_number();
		@$ = "";
	}
	;

var_declaration:
	storage_declaration var_type
	{
		actual_type[struct_level].type_storage = loc_strdup(@1);
		actual_type[struct_level].type_enum = $2.type_enum;
		actual_type[struct_level].type_str = $2.type_str;
		actual_type[struct_level].type_dimension = $2.type_dimension;
		actual_type[struct_level].type_index = $2.type_index;
		actual_type[struct_level].type_sizeof = $2.type_sizeof;

		actual_startline[struct_level] = hashline_number();
	}
	variable_list ';'
	{
		@$ = cat_str(5, actual_startline[struct_level], @1, $2.type_str, @4, ";\n");
	}
	| var_type
	{
		actual_type[struct_level].type_storage = loc_strdup("");
		actual_type[struct_level].type_enum = $1.type_enum;
		actual_type[struct_level].type_str = $1.type_str;
		actual_type[struct_level].type_dimension = $1.type_dimension;
		actual_type[struct_level].type_index = $1.type_index;
		actual_type[struct_level].type_sizeof = $1.type_sizeof;

		actual_startline[struct_level] = hashline_number();
	}
	variable_list ';'
	{
		@$ = cat_str(4, actual_startline[struct_level], $1.type_str, @3, ";\n");
	}
	| struct_union_type_with_symbol ';'
	;

opt_bit_field: ':' Iconst
	| /* EMPTY */
	;

storage_declaration: storage_clause storage_modifier
	| storage_clause
	| storage_modifier
	;

storage_clause: S_EXTERN
	| S_STATIC
	| S_REGISTER
	| S_AUTO
	;

storage_modifier: S_CONST
	| S_VOLATILE
	;

var_type: simple_type
	{
		$$.type_enum = $1;
		$$.type_str = loc_strdup(ecpg_type_name($1));
		$$.type_dimension = "-1";
		$$.type_index = "-1";
		$$.type_sizeof = NULL;
	}
	| struct_union_type
	{
		$$.type_str = loc_strdup(@1);
		$$.type_dimension = "-1";
		$$.type_index = "-1";

		if (strncmp(@1, "struct", sizeof("struct") - 1) == 0)
		{
			$$.type_enum = ECPGt_struct;
			$$.type_sizeof = ECPGstruct_sizeof;
		}
		else
		{
			$$.type_enum = ECPGt_union;
			$$.type_sizeof = NULL;
		}
	}
	| enum_type
	{
		$$.type_str = loc_strdup(@1);
		$$.type_enum = ECPGt_int;
		$$.type_dimension = "-1";
		$$.type_index = "-1";
		$$.type_sizeof = NULL;
	}
	| NUMERIC '(' precision opt_scale ')'
	{
		$$.type_enum = ECPGt_numeric;
		$$.type_str = "numeric";
		$$.type_dimension = "-1";
		$$.type_index = "-1";
		$$.type_sizeof = NULL;
	}
	| DECIMAL_P '(' precision opt_scale ')'
	{
		$$.type_enum = ECPGt_decimal;
		$$.type_str = "decimal";
		$$.type_dimension = "-1";
		$$.type_index = "-1";
		$$.type_sizeof = NULL;
	}
	| IDENT '(' precision opt_scale ')'
	{
		/*
		 * In C parsing mode, NUMERIC and DECIMAL are not keywords, so they
		 * will show up here as a plain identifier, and we need this duplicate
		 * code to recognize them.
		 */
		if (strcmp(@1, "numeric") == 0)
		{
			$$.type_enum = ECPGt_numeric;
			$$.type_str = "numeric";
		}
		else if (strcmp(@1, "decimal") == 0)
		{
			$$.type_enum = ECPGt_decimal;
			$$.type_str = "decimal";
		}
		else
		{
			mmerror(PARSE_ERROR, ET_ERROR, "only data types numeric and decimal have precision/scale argument");
			$$.type_enum = ECPGt_numeric;
			$$.type_str = "numeric";
		}

		$$.type_dimension = "-1";
		$$.type_index = "-1";
		$$.type_sizeof = NULL;
	}
	| VARCHAR
	{
		$$.type_enum = ECPGt_varchar;
		$$.type_str = "";	/* "varchar"; */
		$$.type_dimension = "-1";
		$$.type_index = "-1";
		$$.type_sizeof = NULL;
	}
	| FLOAT_P
	{
		/* Note: DOUBLE is handled in simple_type */
		$$.type_enum = ECPGt_float;
		$$.type_str = "float";
		$$.type_dimension = "-1";
		$$.type_index = "-1";
		$$.type_sizeof = NULL;
	}
	| NUMERIC
	{
		$$.type_enum = ECPGt_numeric;
		$$.type_str = "numeric";
		$$.type_dimension = "-1";
		$$.type_index = "-1";
		$$.type_sizeof = NULL;
	}
	| DECIMAL_P
	{
		$$.type_enum = ECPGt_decimal;
		$$.type_str = "decimal";
		$$.type_dimension = "-1";
		$$.type_index = "-1";
		$$.type_sizeof = NULL;
	}
	| TIMESTAMP
	{
		$$.type_enum = ECPGt_timestamp;
		$$.type_str = "timestamp";
		$$.type_dimension = "-1";
		$$.type_index = "-1";
		$$.type_sizeof = NULL;
	}
	| STRING_P
	{
		if (INFORMIX_MODE)
		{
			/* In Informix mode, "string" is automatically a typedef */
			$$.type_enum = ECPGt_string;
			$$.type_str = "char";
			$$.type_dimension = "-1";
			$$.type_index = "-1";
			$$.type_sizeof = NULL;
		}
		else
		{
			/* Otherwise, legal only if user typedef'ed it */
			struct typedefs *this = get_typedef("string", false);

			$$.type_str = (this->type->type_enum == ECPGt_varchar || this->type->type_enum == ECPGt_bytea) ? mm_strdup("") : mm_strdup(this->name);
			$$.type_enum = this->type->type_enum;
			$$.type_dimension = this->type->type_dimension;
			$$.type_index = this->type->type_index;
			if (this->type->type_sizeof && strlen(this->type->type_sizeof) != 0)
				$$.type_sizeof = this->type->type_sizeof;
			else
				$$.type_sizeof = cat_str(3, "sizeof(", this->name, ")");

			ECPGfree_struct_member(struct_member_list[struct_level]);
			struct_member_list[struct_level] = ECPGstruct_member_dup(this->struct_member_list);
		}
	}
	| INTERVAL ecpg_interval
	{
		$$.type_enum = ECPGt_interval;
		$$.type_str = "interval";
		$$.type_dimension = "-1";
		$$.type_index = "-1";
		$$.type_sizeof = NULL;
	}
	| IDENT ecpg_interval
	{
		/*
		 * In C parsing mode, the above SQL type names are not keywords, so
		 * they will show up here as a plain identifier, and we need this
		 * duplicate code to recognize them.
		 *
		 * Note that we also handle the type names bytea, date, and datetime
		 * here, but not above because those are not currently SQL keywords.
		 * If they ever become so, they must gain duplicate productions above.
		 */
		if (strlen(@2) != 0 && strcmp(@1, "datetime") != 0 && strcmp(@1, "interval") != 0)
			mmerror(PARSE_ERROR, ET_ERROR, "interval specification not allowed here");

		if (strcmp(@1, "varchar") == 0)
		{
			$$.type_enum = ECPGt_varchar;
			$$.type_str = "";	/* "varchar"; */
			$$.type_dimension = "-1";
			$$.type_index = "-1";
			$$.type_sizeof = NULL;
		}
		else if (strcmp(@1, "bytea") == 0)
		{
			$$.type_enum = ECPGt_bytea;
			$$.type_str = "";
			$$.type_dimension = "-1";
			$$.type_index = "-1";
			$$.type_sizeof = NULL;
		}
		else if (strcmp(@1, "float") == 0)
		{
			$$.type_enum = ECPGt_float;
			$$.type_str = "float";
			$$.type_dimension = "-1";
			$$.type_index = "-1";
			$$.type_sizeof = NULL;
		}
		else if (strcmp(@1, "double") == 0)
		{
			$$.type_enum = ECPGt_double;
			$$.type_str = "double";
			$$.type_dimension = "-1";
			$$.type_index = "-1";
			$$.type_sizeof = NULL;
		}
		else if (strcmp(@1, "numeric") == 0)
		{
			$$.type_enum = ECPGt_numeric;
			$$.type_str = "numeric";
			$$.type_dimension = "-1";
			$$.type_index = "-1";
			$$.type_sizeof = NULL;
		}
		else if (strcmp(@1, "decimal") == 0)
		{
			$$.type_enum = ECPGt_decimal;
			$$.type_str = "decimal";
			$$.type_dimension = "-1";
			$$.type_index = "-1";
			$$.type_sizeof = NULL;
		}
		else if (strcmp(@1, "date") == 0)
		{
			$$.type_enum = ECPGt_date;
			$$.type_str = "date";
			$$.type_dimension = "-1";
			$$.type_index = "-1";
			$$.type_sizeof = NULL;
		}
		else if (strcmp(@1, "timestamp") == 0)
		{
			$$.type_enum = ECPGt_timestamp;
			$$.type_str = "timestamp";
			$$.type_dimension = "-1";
			$$.type_index = "-1";
			$$.type_sizeof = NULL;
		}
		else if (strcmp(@1, "interval") == 0)
		{
			$$.type_enum = ECPGt_interval;
			$$.type_str = "interval";
			$$.type_dimension = "-1";
			$$.type_index = "-1";
			$$.type_sizeof = NULL;
		}
		else if (strcmp(@1, "datetime") == 0)
		{
			$$.type_enum = ECPGt_timestamp;
			$$.type_str = "timestamp";
			$$.type_dimension = "-1";
			$$.type_index = "-1";
			$$.type_sizeof = NULL;
		}
		else if ((strcmp(@1, "string") == 0) && INFORMIX_MODE)
		{
			$$.type_enum = ECPGt_string;
			$$.type_str = "char";
			$$.type_dimension = "-1";
			$$.type_index = "-1";
			$$.type_sizeof = NULL;
		}
		else
		{
			/* Otherwise, it must be a user-defined typedef name */
			struct typedefs *this = get_typedef(@1, false);

			$$.type_str = (this->type->type_enum == ECPGt_varchar || this->type->type_enum == ECPGt_bytea) ? "" : this->name;
			$$.type_enum = this->type->type_enum;
			$$.type_dimension = this->type->type_dimension;
			$$.type_index = this->type->type_index;
			if (this->type->type_sizeof && strlen(this->type->type_sizeof) != 0)
				$$.type_sizeof = this->type->type_sizeof;
			else
				$$.type_sizeof = cat_str(3, "sizeof(", this->name, ")");

			ECPGfree_struct_member(struct_member_list[struct_level]);
			struct_member_list[struct_level] = ECPGstruct_member_dup(this->struct_member_list);
		}
	}
	| s_struct_union_symbol
	{
		/* this is for named structs/unions */
		char	   *name;
		struct typedefs *this;
		bool		forward = (forward_name != NULL && strcmp($1.symbol, forward_name) == 0 && strcmp($1.su, "struct") == 0);

		name = cat2_str($1.su, $1.symbol);
		/* Do we have a forward definition? */
		if (!forward)
		{
			/* No */

			this = get_typedef(name, false);
			$$.type_str = this->name;
			$$.type_enum = this->type->type_enum;
			$$.type_dimension = this->type->type_dimension;
			$$.type_index = this->type->type_index;
			$$.type_sizeof = this->type->type_sizeof;
			ECPGfree_struct_member(struct_member_list[struct_level]);
			struct_member_list[struct_level] = ECPGstruct_member_dup(this->struct_member_list);
		}
		else
		{
			$$.type_str = name;
			$$.type_enum = ECPGt_long;
			$$.type_dimension = "-1";
			$$.type_index = "-1";
			$$.type_sizeof = "";
			ECPGfree_struct_member(struct_member_list[struct_level]);
			struct_member_list[struct_level] = NULL;
		}
	}
	;

enum_type: ENUM_P symbol enum_definition
	| ENUM_P enum_definition
	| ENUM_P symbol
	;

enum_definition: '{' c_list '}'
	;

struct_union_type_with_symbol: s_struct_union_symbol
	{
		ECPGfree_struct_member(struct_member_list[struct_level]);
		struct_member_list[struct_level++] = NULL;
		if (struct_level >= STRUCT_DEPTH)
			mmerror(PARSE_ERROR, ET_ERROR, "too many levels in nested structure/union definition");
		forward_name = mm_strdup($1.symbol);
	}
	'{' variable_declarations '}'
	{
		struct typedefs *ptr,
				   *this;
		struct this_type su_type;

		ECPGfree_struct_member(struct_member_list[struct_level]);
		struct_member_list[struct_level] = NULL;
		struct_level--;
		if (strcmp($1.su, "struct") == 0)
			su_type.type_enum = ECPGt_struct;
		else
			su_type.type_enum = ECPGt_union;
		su_type.type_str = cat2_str($1.su, $1.symbol);
		free(forward_name);
		forward_name = NULL;

		/*
		 * This is essentially a typedef but needs the keyword struct/union as
		 * well. So we create the typedef for each struct definition with
		 * symbol
		 */
		for (ptr = types; ptr != NULL; ptr = ptr->next)
		{
			if (strcmp(su_type.type_str, ptr->name) == 0)
				/* re-definition is a bug */
				mmerror(PARSE_ERROR, ET_ERROR, "type \"%s\" is already defined", su_type.type_str);
		}

		this = (struct typedefs *) mm_alloc(sizeof(struct typedefs));

		/* initial definition */
		this->next = types;
		this->name = mm_strdup(su_type.type_str);
		this->brace_level = braces_open;
		this->type = (struct this_type *) mm_alloc(sizeof(struct this_type));
		this->type->type_storage = NULL;
		this->type->type_enum = su_type.type_enum;
		this->type->type_str = mm_strdup(su_type.type_str);
		this->type->type_dimension = mm_strdup("-1");	/* dimension of array */
		this->type->type_index = mm_strdup("-1");	/* length of string */
		this->type->type_sizeof = ECPGstruct_sizeof ? mm_strdup(ECPGstruct_sizeof) : NULL;
		this->struct_member_list = ECPGstruct_member_dup(struct_member_list[struct_level]);

		types = this;
		@$ = cat_str(4, su_type.type_str, "{", @4, "}");
	}
	;

struct_union_type: struct_union_type_with_symbol
	| s_struct_union
	{
		ECPGfree_struct_member(struct_member_list[struct_level]);
		struct_member_list[struct_level++] = NULL;
		if (struct_level >= STRUCT_DEPTH)
			mmerror(PARSE_ERROR, ET_ERROR, "too many levels in nested structure/union definition");
	}
	'{' variable_declarations '}'
	{
		ECPGfree_struct_member(struct_member_list[struct_level]);
		struct_member_list[struct_level] = NULL;
		struct_level--;
		@$ = cat_str(4, @1, "{", @4, "}");
	}
	;

s_struct_union_symbol: SQL_STRUCT symbol
	{
		$$.su = "struct";
		$$.symbol = @2;
		free(ECPGstruct_sizeof);
		ECPGstruct_sizeof = mm_strdup(cat_str(3, "sizeof(",
											  cat2_str($$.su, $$.symbol),
											  ")"));
	}
	| UNION symbol
	{
		$$.su = "union";
		$$.symbol = @2;
	}
	;

s_struct_union: SQL_STRUCT
	{
		free(ECPGstruct_sizeof);
		ECPGstruct_sizeof = mm_strdup("");	/* This must not be NULL to
											 * distinguish from simple types. */
		@$ = "struct";
	}
	| UNION
	{
		@$ = "union";
	}
	;

simple_type: unsigned_type
	| opt_signed signed_type			{ $$ = $2; }
	;

unsigned_type: SQL_UNSIGNED SQL_SHORT	{ $$ = ECPGt_unsigned_short; }
	| SQL_UNSIGNED SQL_SHORT INT_P		{ $$ = ECPGt_unsigned_short; }
	| SQL_UNSIGNED						{ $$ = ECPGt_unsigned_int; }
	| SQL_UNSIGNED INT_P				{ $$ = ECPGt_unsigned_int; }
	| SQL_UNSIGNED SQL_LONG				{ $$ = ECPGt_unsigned_long; }
	| SQL_UNSIGNED SQL_LONG INT_P		{ $$ = ECPGt_unsigned_long; }
	| SQL_UNSIGNED SQL_LONG SQL_LONG	{ $$ = ECPGt_unsigned_long_long; }
	| SQL_UNSIGNED SQL_LONG SQL_LONG INT_P	{ $$ = ECPGt_unsigned_long_long; }
	| SQL_UNSIGNED CHAR_P				{ $$ = ECPGt_unsigned_char; }
	;

signed_type: SQL_SHORT					{ $$ = ECPGt_short; }
	| SQL_SHORT INT_P					{ $$ = ECPGt_short; }
	| INT_P								{ $$ = ECPGt_int; }
	| SQL_LONG							{ $$ = ECPGt_long; }
	| SQL_LONG INT_P					{ $$ = ECPGt_long; }
	| SQL_LONG SQL_LONG					{ $$ = ECPGt_long_long; }
	| SQL_LONG SQL_LONG INT_P			{ $$ = ECPGt_long_long; }
	| SQL_BOOL							{ $$ = ECPGt_bool; }
	| CHAR_P							{ $$ = ECPGt_char; }
	| DOUBLE_P							{ $$ = ECPGt_double; }
	;

opt_signed: SQL_SIGNED
	| /* EMPTY */
	;

variable_list: variable
	| variable_list ',' variable
	{
		if (actual_type[struct_level].type_enum == ECPGt_varchar || actual_type[struct_level].type_enum == ECPGt_bytea)
			@$ = cat_str(4, @1, ";", actual_type[struct_level].type_storage, @3);
		else
			@$ = cat_str(3, @1, ",", @3);
	}
	;

variable: opt_pointer ECPGColLabel opt_array_bounds opt_bit_field opt_initializer
	{
		struct ECPGtype *type;
		const char *dimension = $3.index1;	/* dimension of array */
		const char *length = $3.index2; /* length of string */
		char	   *dim_str;
		char		vcn[32];
		int		   *varlen_type_counter;
		char	   *struct_name;

		adjust_array(actual_type[struct_level].type_enum,
					 &dimension, &length,
					 actual_type[struct_level].type_dimension,
					 actual_type[struct_level].type_index,
					 strlen(@1), false);
		switch (actual_type[struct_level].type_enum)
		{
			case ECPGt_struct:
			case ECPGt_union:
				if (atoi(dimension) < 0)
					type = ECPGmake_struct_type(struct_member_list[struct_level], actual_type[struct_level].type_enum, actual_type[struct_level].type_str, actual_type[struct_level].type_sizeof);
				else
					type = ECPGmake_array_type(ECPGmake_struct_type(struct_member_list[struct_level], actual_type[struct_level].type_enum, actual_type[struct_level].type_str, actual_type[struct_level].type_sizeof), dimension);

				@$ = cat_str(5, @1, @2, $3.str, @4, @5);
				break;

			case ECPGt_varchar:
			case ECPGt_bytea:
				if (actual_type[struct_level].type_enum == ECPGt_varchar)
				{
					varlen_type_counter = &varchar_counter;
					struct_name = " struct varchar_";
				}
				else
				{
					varlen_type_counter = &bytea_counter;
					struct_name = " struct bytea_";
				}
				if (atoi(dimension) < 0)
					type = ECPGmake_simple_type(actual_type[struct_level].type_enum, length, *varlen_type_counter);
				else
					type = ECPGmake_array_type(ECPGmake_simple_type(actual_type[struct_level].type_enum, length, *varlen_type_counter), dimension);

				if (strcmp(dimension, "0") == 0 || abs(atoi(dimension)) == 1)
					dim_str = "";
				else
					dim_str = cat_str(3, "[", dimension, "]");

				/*
				 * cannot check for atoi <= 0 because a defined constant will
				 * yield 0 here as well
				 */
				if (atoi(length) < 0 || strcmp(length, "0") == 0)
					mmerror(PARSE_ERROR, ET_ERROR, "pointers to varchar are not implemented");

				/*
				 * make sure varchar struct name is unique by adding a unique
				 * counter to its definition
				 */
				snprintf(vcn, sizeof(vcn), "%d", *varlen_type_counter);
				if (strcmp(dimension, "0") == 0)
					@$ = cat_str(7, make2_str(struct_name, vcn), " { int len; char arr[", length, "]; } *", @2, @4, @5);
				else
					@$ = cat_str(8, make2_str(struct_name, vcn), " { int len; char arr[", length, "]; } ", @2, dim_str, @4, @5);
				(*varlen_type_counter)++;
				break;

			case ECPGt_char:
			case ECPGt_unsigned_char:
			case ECPGt_string:
				if (atoi(dimension) == -1)
				{
					int			i = strlen(@5);

					if (atoi(length) == -1 && i > 0)	/* char <var>[] =
														 * "string" */
					{
						/*
						 * if we have an initializer but no string size set,
						 * let's use the initializer's length
						 */
						char   *buf = loc_alloc(32);

						snprintf(buf, 32, "sizeof(%s)", @5 + 2);
						length = buf;
					}
					type = ECPGmake_simple_type(actual_type[struct_level].type_enum, length, 0);
				}
				else
					type = ECPGmake_array_type(ECPGmake_simple_type(actual_type[struct_level].type_enum, length, 0), dimension);

				@$ = cat_str(5, @1, @2, $3.str, @4, @5);
				break;

			default:
				if (atoi(dimension) < 0)
					type = ECPGmake_simple_type(actual_type[struct_level].type_enum, "1", 0);
				else
					type = ECPGmake_array_type(ECPGmake_simple_type(actual_type[struct_level].type_enum, "1", 0), dimension);

				@$ = cat_str(5, @1, @2, $3.str, @4, @5);
				break;
		}

		if (struct_level == 0)
			new_variable(@2, type, braces_open);
		else
			ECPGmake_struct_member(@2, type, &(struct_member_list[struct_level - 1]));
	}
	;

opt_initializer: /* EMPTY */
	| '=' c_term
	{
		initializer = 1;
	}
	;

opt_pointer: /* EMPTY */
	| '*'
	| '*' '*'
	{
		@$ = "**";
	}
	;

/*
 * We try to simulate the correct DECLARE syntax here so we get dynamic SQL
 */
ECPGDeclare: DECLARE STATEMENT ecpg_ident
	{
		/* this is only supported for compatibility */
		@$ = cat_str(3, "/* declare statement", @3, "*/");
	}
	;
/*
 * the exec sql disconnect statement: disconnect from the given database
 */
ECPGDisconnect: SQL_DISCONNECT dis_name
	{
		@$ = @2;
	}
	;

dis_name: connection_object
	| CURRENT_P
	{
		@$ = "\"CURRENT\"";
	}
	| ALL
	{
		@$ = "\"ALL\"";
	}
	| /* EMPTY */
	{
		@$ = "\"CURRENT\"";
	}
	;

connection_object: name
	{
		@$ = make3_str("\"", @1, "\"");
	}
	| DEFAULT
	{
		@$ = "\"DEFAULT\"";
	}
	| char_variable
	;

execstring: char_variable
	| CSTRING
	{
		@$ = make3_str("\"", @1, "\"");
	}
	;

/*
 * the exec sql free command to deallocate a previously
 * prepared statement
 */
ECPGFree: SQL_FREE cursor_name
	{
		@$ = @2;
	}
	| SQL_FREE ALL
	{
		@$ = "all";
	}
	;

/*
 * open is an open cursor, at the moment this has to be removed
 */
ECPGOpen: SQL_OPEN cursor_name opt_ecpg_using
	{
		if (@2[0] == ':')
			remove_variable_from_list(&argsinsert, find_variable(@2 + 1));
		@$ = @2;
	}
	;

opt_ecpg_using: /* EMPTY */
	| ecpg_using
	;

ecpg_using: USING using_list
	{
		@$ = "";
	}
	| using_descriptor
	;

using_descriptor: USING SQL_P SQL_DESCRIPTOR quoted_ident_stringvar
	{
		add_variable_to_head(&argsinsert, descriptor_variable(@4, 0), &no_indicator);
		@$ = "";
	}
	| USING SQL_DESCRIPTOR name
	{
		add_variable_to_head(&argsinsert, sqlda_variable(@3), &no_indicator);
		@$ = "";
	}
	;

into_descriptor: INTO SQL_P SQL_DESCRIPTOR quoted_ident_stringvar
	{
		add_variable_to_head(&argsresult, descriptor_variable(@4, 1), &no_indicator);
		@$ = "";
	}
	| INTO SQL_DESCRIPTOR name
	{
		add_variable_to_head(&argsresult, sqlda_variable(@3), &no_indicator);
		@$ = "";
	}
	;

into_sqlda: INTO name
	{
		add_variable_to_head(&argsresult, sqlda_variable(@2), &no_indicator);
		@$ = "";
	}
	;

using_list: UsingValue | UsingValue ',' using_list
	;

UsingValue: UsingConst
	{
		char		length[32];

		snprintf(length, sizeof(length), "%zu", strlen(@1));
		add_variable_to_head(&argsinsert, new_variable(@1, ECPGmake_simple_type(ECPGt_const, length, 0), 0), &no_indicator);
	}
	| civar
	{
		@$ = "";
	}
	| civarind
	{
		@$ = "";
	}
	;

UsingConst: Iconst
	| '+' Iconst
	| '-' Iconst
	| ecpg_fconst
	| '+' ecpg_fconst
	| '-' ecpg_fconst
	| ecpg_sconst
	| ecpg_bconst
	| ecpg_xconst
	;

/*
 * We accept DESCRIBE [OUTPUT] but do nothing with DESCRIBE INPUT so far.
 */
ECPGDescribe: SQL_DESCRIBE INPUT_P prepared_name using_descriptor
	{
		$$.input = 1;
		$$.stmt_name = @3;
	}
	| SQL_DESCRIBE opt_output prepared_name using_descriptor
	{
		struct variable *var;

		var = argsinsert->variable;
		remove_variable_from_list(&argsinsert, var);
		add_variable_to_head(&argsresult, var, &no_indicator);

		$$.input = 0;
		$$.stmt_name = @3;
	}
	| SQL_DESCRIBE opt_output prepared_name into_descriptor
	{
		$$.input = 0;
		$$.stmt_name = @3;
	}
	| SQL_DESCRIBE INPUT_P prepared_name into_sqlda
	{
		$$.input = 1;
		$$.stmt_name = @3;
	}
	| SQL_DESCRIBE opt_output prepared_name into_sqlda
	{
		$$.input = 0;
		$$.stmt_name = @3;
	}
	;

opt_output: SQL_OUTPUT
	| /* EMPTY */
	;

/*
 * dynamic SQL: descriptor based access
 *	originally written by Christof Petig <christof.petig@wtal.de>
 *			and Peter Eisentraut <peter.eisentraut@credativ.de>
 */

/*
 * allocate a descriptor
 */
ECPGAllocateDescr: SQL_ALLOCATE SQL_DESCRIPTOR quoted_ident_stringvar
	{
		add_descriptor(@3, connection);
		@$ = @3;
	}
	;


/*
 * deallocate a descriptor
 */
ECPGDeallocateDescr: DEALLOCATE SQL_DESCRIPTOR quoted_ident_stringvar
	{
		drop_descriptor(@3, connection);
		@$ = @3;
	}
	;

/*
 * manipulate a descriptor header
 */

ECPGGetDescriptorHeader: SQL_GET SQL_DESCRIPTOR quoted_ident_stringvar ECPGGetDescHeaderItems
	{
		@$ = @3;
	}
	;

ECPGGetDescHeaderItems: ECPGGetDescHeaderItem
	| ECPGGetDescHeaderItems ',' ECPGGetDescHeaderItem
	;

ECPGGetDescHeaderItem: cvariable '=' desc_header_item
	{
		push_assignment(@1, $3);
	}
	;

ECPGSetDescriptorHeader: SET SQL_DESCRIPTOR quoted_ident_stringvar ECPGSetDescHeaderItems
	{
		@$ = @3;
	}
	;

ECPGSetDescHeaderItems: ECPGSetDescHeaderItem
	| ECPGSetDescHeaderItems ',' ECPGSetDescHeaderItem
	;

ECPGSetDescHeaderItem: desc_header_item '=' IntConstVar
	{
		push_assignment(@3, $1);
	}
	;

IntConstVar: Iconst
	{
		char		length[32];

		snprintf(length, sizeof(length), "%zu", strlen(@1));
		new_variable(@1, ECPGmake_simple_type(ECPGt_const, length, 0), 0);
	}
	| cvariable
	;

desc_header_item: SQL_COUNT
	{
		$$ = ECPGd_count;
	}
	;

/*
 * manipulate a descriptor
 */

ECPGGetDescriptor: SQL_GET SQL_DESCRIPTOR quoted_ident_stringvar VALUE_P IntConstVar ECPGGetDescItems
	{
		$$.str = @5;
		$$.name = @3;
	}
	;

ECPGGetDescItems: ECPGGetDescItem
	| ECPGGetDescItems ',' ECPGGetDescItem
	;

ECPGGetDescItem: cvariable '=' descriptor_item
	{
		push_assignment(@1, $3);
	}
	;

ECPGSetDescriptor: SET SQL_DESCRIPTOR quoted_ident_stringvar VALUE_P IntConstVar ECPGSetDescItems
	{
		$$.str = @5;
		$$.name = @3;
	}
	;

ECPGSetDescItems: ECPGSetDescItem
	| ECPGSetDescItems ',' ECPGSetDescItem
	;

ECPGSetDescItem: descriptor_item '=' AllConstVar
	{
		push_assignment(@3, $1);
	}
	;

AllConstVar: ecpg_fconst
	{
		char		length[32];

		snprintf(length, sizeof(length), "%zu", strlen(@1));
		new_variable(@1, ECPGmake_simple_type(ECPGt_const, length, 0), 0);
	}
	| IntConstVar
	| '-' ecpg_fconst
	{
		char		length[32];
		char	   *var = cat2_str("-", @2);

		snprintf(length, sizeof(length), "%zu", strlen(var));
		new_variable(var, ECPGmake_simple_type(ECPGt_const, length, 0), 0);
		@$ = var;
	}
	| '-' Iconst
	{
		char		length[32];
		char	   *var = cat2_str("-", @2);

		snprintf(length, sizeof(length), "%zu", strlen(var));
		new_variable(var, ECPGmake_simple_type(ECPGt_const, length, 0), 0);
		@$ = var;
	}
	| ecpg_sconst
	{
		char		length[32];
		char	   *var;

		/* Strip single quotes from ecpg_sconst */
		var = loc_strdup(@1 + 1);
		var[strlen(var) - 1] = '\0';
		snprintf(length, sizeof(length), "%zu", strlen(var));
		new_variable(var, ECPGmake_simple_type(ECPGt_const, length, 0), 0);
		@$ = var;
	}
	;

descriptor_item: SQL_CARDINALITY		{ $$ = ECPGd_cardinality; }
	| DATA_P							{ $$ = ECPGd_data; }
	| SQL_DATETIME_INTERVAL_CODE		{ $$ = ECPGd_di_code; }
	| SQL_DATETIME_INTERVAL_PRECISION	{ $$ = ECPGd_di_precision; }
	| SQL_INDICATOR						{ $$ = ECPGd_indicator; }
	| SQL_KEY_MEMBER					{ $$ = ECPGd_key_member; }
	| SQL_LENGTH						{ $$ = ECPGd_length; }
	| NAME_P							{ $$ = ECPGd_name; }
	| SQL_NULLABLE						{ $$ = ECPGd_nullable; }
	| SQL_OCTET_LENGTH					{ $$ = ECPGd_octet; }
	| PRECISION							{ $$ = ECPGd_precision; }
	| SQL_RETURNED_LENGTH				{ $$ = ECPGd_length; }
	| SQL_RETURNED_OCTET_LENGTH			{ $$ = ECPGd_ret_octet; }
	| SQL_SCALE							{ $$ = ECPGd_scale; }
	| TYPE_P							{ $$ = ECPGd_type; }
	;

/*
 * set/reset the automatic transaction mode, this needs a different handling
 * as the other set commands
 */
ECPGSetAutocommit: SET SQL_AUTOCOMMIT '=' on_off
	{
		@$ = @4;
	}
	| SET SQL_AUTOCOMMIT TO on_off
	{
		@$ = @4;
	}
	;

on_off: ON
	| OFF
	;

/*
 * set the actual connection, this needs a different handling as the other
 * set commands
 */
ECPGSetConnection: SET CONNECTION TO connection_object
	{
		@$ = @4;
	}
	| SET CONNECTION '=' connection_object
	{
		@$ = @4;
	}
	| SET CONNECTION connection_object
	{
		@$ = @3;
	}
	;

/*
 * define a new type for embedded SQL
 */
ECPGTypedef: TYPE_P
	{
		/* reset this variable so we see if there was */
		/* an initializer specified */
		initializer = 0;
	}
	ECPGColLabel IS var_type opt_array_bounds opt_reference
	{
		add_typedef(@3, $6.index1, $6.index2, $5.type_enum, $5.type_dimension, $5.type_index, initializer, *@7 ? 1 : 0);

		if (auto_create_c == false)
			@$ = cat_str(7, "/* exec sql type", @3, "is", $5.type_str, $6.str, @7, "*/");
		else
			@$ = cat_str(6, "typedef ", $5.type_str, *@7 ? "*" : "", @3, $6.str, ";");
	}
	;

opt_reference: SQL_REFERENCE
	| /* EMPTY */
	;

/*
 * define the type of one variable for embedded SQL
 */
ECPGVar: SQL_VAR
	{
		/* reset this variable so we see if there was */
		/* an initializer specified */
		initializer = 0;
	}
	ColLabel	IS var_type opt_array_bounds opt_reference
	{
		struct variable *p = find_variable(@3);
		const char *dimension = $6.index1;
		const char *length = $6.index2;
		struct ECPGtype *type;

		if			(($5.type_enum == ECPGt_struct ||
					  $5.type_enum == ECPGt_union) &&
					 initializer == 1)
			mmerror(PARSE_ERROR, ET_ERROR, "initializer not allowed in EXEC SQL VAR command");
		else
		{
			adjust_array($5.type_enum, &dimension, &length,
						 $5.type_dimension, $5.type_index, *@7 ? 1 : 0, false);

			switch ($5.type_enum)
			{
					case ECPGt_struct:
					case ECPGt_union:
					if (atoi(dimension) < 0)
						type = ECPGmake_struct_type(struct_member_list[struct_level], $5.type_enum, $5.type_str, $5.type_sizeof);
					else
						type = ECPGmake_array_type(ECPGmake_struct_type(struct_member_list[struct_level], $5.type_enum, $5.type_str, $5.type_sizeof), dimension);
					break;

					case ECPGt_varchar:
					case ECPGt_bytea:
					if (atoi(dimension) == -1)
						type = ECPGmake_simple_type($5.type_enum, length, 0);
					else
						type = ECPGmake_array_type(ECPGmake_simple_type($5.type_enum, length, 0), dimension);
					break;

					case ECPGt_char:
					case ECPGt_unsigned_char:
					case ECPGt_string:
					if (atoi(dimension) == -1)
						type = ECPGmake_simple_type($5.type_enum, length, 0);
					else
						type = ECPGmake_array_type(ECPGmake_simple_type($5.type_enum, length, 0), dimension);
					break;

					default:
					if (atoi(length) >= 0)
						mmerror(PARSE_ERROR, ET_ERROR, "multidimensional arrays for simple data types are not supported");

					if (atoi(dimension) < 0)
						type = ECPGmake_simple_type($5.type_enum, "1", 0);
					else
						type = ECPGmake_array_type(ECPGmake_simple_type($5.type_enum, "1", 0), dimension);
					break;
			}

			ECPGfree_type(p->type);
			p->type = type;
		}

					@$ = cat_str(7, "/* exec sql var", @3, "is", $5.type_str, $6.str, @7, "*/");
	}
	;

/*
 * whenever statement: decide what to do in case of error/no data found
 * according to SQL standards we lack: SQLSTATE, CONSTRAINT and SQLEXCEPTION
 */
ECPGWhenever: SQL_WHENEVER SQL_SQLERROR action
	{
		when_error.code = $3.code;
		free(when_error.command);
		when_error.command = $3.command ? mm_strdup($3.command) : NULL;
		@$ = cat_str(3, "/* exec sql whenever sqlerror ", $3.str, "; */");
	}
	| SQL_WHENEVER NOT SQL_FOUND action
	{
		when_nf.code = $4.code;
		free(when_nf.command);
		when_nf.command = $4.command ? mm_strdup($4.command) : NULL;
		@$ = cat_str(3, "/* exec sql whenever not found ", $4.str, "; */");
	}
	| SQL_WHENEVER SQL_SQLWARNING action
	{
		when_warn.code = $3.code;
		free(when_warn.command);
		when_warn.command = $3.command ? mm_strdup($3.command) : NULL;
		@$ = cat_str(3, "/* exec sql whenever sql_warning ", $3.str, "; */");
	}
	;

action: CONTINUE_P
	{
		$$.code = W_NOTHING;
		$$.command = NULL;
		$$.str = "continue";
	}
	| SQL_SQLPRINT
	{
		$$.code = W_SQLPRINT;
		$$.command = NULL;
		$$.str = "sqlprint";
	}
	| SQL_STOP
	{
		$$.code = W_STOP;
		$$.command = NULL;
		$$.str = "stop";
	}
	| SQL_GOTO name
	{
		$$.code = W_GOTO;
		$$.command = loc_strdup(@2);
		$$.str = cat2_str("goto ", @2);
	}
	| SQL_GO TO name
	{
		$$.code = W_GOTO;
		$$.command = loc_strdup(@3);
		$$.str = cat2_str("goto ", @3);
	}
	| DO name '(' c_args ')'
	{
		$$.code = W_DO;
		$$.command = cat_str(4, @2, "(", @4, ")");
		$$.str = cat2_str("do", $$.command);
	}
	| DO SQL_BREAK
	{
		$$.code = W_BREAK;
		$$.command = NULL;
		$$.str = "break";
	}
	| DO CONTINUE_P
	{
		$$.code = W_CONTINUE;
		$$.command = NULL;
		$$.str = "continue";
	}
	| CALL name '(' c_args ')'
	{
		$$.code = W_DO;
		$$.command = cat_str(4, @2, "(", @4, ")");
		$$.str = cat2_str("call", $$.command);
	}
	| CALL name
	{
		$$.code = W_DO;
		$$.command = cat2_str(@2, "()");
		$$.str = cat2_str("call", $$.command);
	}
	;

/* some other stuff for ecpg */

/* additional unreserved keywords */
ECPGKeywords: ECPGKeywords_vanames
	| ECPGKeywords_rest
	;

ECPGKeywords_vanames: SQL_BREAK
	| SQL_CARDINALITY
	| SQL_COUNT
	| SQL_DATETIME_INTERVAL_CODE
	| SQL_DATETIME_INTERVAL_PRECISION
	| SQL_FOUND
	| SQL_GO
	| SQL_GOTO
	| SQL_IDENTIFIED
	| SQL_INDICATOR
	| SQL_KEY_MEMBER
	| SQL_LENGTH
	| SQL_NULLABLE
	| SQL_OCTET_LENGTH
	| SQL_RETURNED_LENGTH
	| SQL_RETURNED_OCTET_LENGTH
	| SQL_SCALE
	| SQL_SECTION
	| SQL_SQLERROR
	| SQL_SQLPRINT
	| SQL_SQLWARNING
	| SQL_STOP
	;

ECPGKeywords_rest: SQL_CONNECT
	| SQL_DESCRIBE
	| SQL_DISCONNECT
	| SQL_OPEN
	| SQL_VAR
	| SQL_WHENEVER
	;

/* additional keywords that can be SQL type names (but not ECPGColLabels) */
ECPGTypeName: SQL_BOOL
	| SQL_LONG
	| SQL_OUTPUT
	| SQL_SHORT
	| SQL_STRUCT
	| SQL_SIGNED
	| SQL_UNSIGNED
	;

symbol: ColLabel
	;

ECPGColId: ecpg_ident
	| unreserved_keyword
	| col_name_keyword
	| ECPGunreserved_interval
	| ECPGKeywords
	| ECPGCKeywords
	| CHAR_P
	| VALUES
	;

/*
 * Name classification hierarchy.
 *
 * These productions should match those in the core grammar, except that
 * we use all_unreserved_keyword instead of unreserved_keyword, and
 * where possible include ECPG keywords as well as core keywords.
 */

/* Column identifier --- names that can be column, table, etc names.
 */
ColId: ecpg_ident
	| all_unreserved_keyword
	| col_name_keyword
	| ECPGKeywords
	| ECPGCKeywords
	| CHAR_P
	| VALUES
	;

/* Type/function identifier --- names that can be type or function names.
 */
type_function_name: ecpg_ident
	| all_unreserved_keyword
	| type_func_name_keyword
	| ECPGKeywords
	| ECPGCKeywords
	| ECPGTypeName
	;

/* Column label --- allowed labels in "AS" clauses.
 * This presently includes *all* Postgres keywords.
 */
ColLabel: ECPGColLabel
	| ECPGTypeName
	| CHAR_P
	| CURRENT_P
	| INPUT_P
	| INT_P
	| TO
	| UNION
	| VALUES
	| ECPGCKeywords
	| ECPGunreserved_interval
	;

ECPGColLabel: ecpg_ident
	| unreserved_keyword
	| col_name_keyword
	| type_func_name_keyword
	| reserved_keyword
	| ECPGKeywords_vanames
	| ECPGKeywords_rest
	| CONNECTION
	;

ECPGCKeywords: S_AUTO
	| S_CONST
	| S_EXTERN
	| S_REGISTER
	| S_STATIC
	| S_TYPEDEF
	| S_VOLATILE
	;

/* "Unreserved" keywords --- available for use as any kind of name.
 */

/*
 * The following symbols must be excluded from ECPGColLabel and directly
 * included into ColLabel to enable C variables to get names from ECPGColLabel:
 * DAY_P, HOUR_P, MINUTE_P, MONTH_P, SECOND_P, YEAR_P.
 *
 * We also have to exclude CONNECTION, CURRENT, and INPUT for various reasons.
 * CONNECTION can be added back in all_unreserved_keyword, but CURRENT and
 * INPUT are reserved for ecpg purposes.
 *
 * The mentioned exclusions are done by $replace_line settings in parse.pl.
 */
all_unreserved_keyword: unreserved_keyword
	| ECPGunreserved_interval
	| CONNECTION
	;

ECPGunreserved_interval: DAY_P
	| HOUR_P
	| MINUTE_P
	| MONTH_P
	| SECOND_P
	| YEAR_P
	;

into_list: coutputvariable | into_list ',' coutputvariable
	;

ecpgstart: SQL_START
	{
		reset_variables();
		pacounter = 1;
		@$ = "";
	}
	;

c_args: /* EMPTY */
	| c_list
	;

coutputvariable: cvariable indicator
	{
		add_variable_to_head(&argsresult, find_variable(@1), find_variable(@2));
	}
	| cvariable
	{
		add_variable_to_head(&argsresult, find_variable(@1), &no_indicator);
	}
	;


civarind: cvariable indicator
	{
		if (find_variable(@2)->type->type == ECPGt_array)
			mmerror(PARSE_ERROR, ET_ERROR, "arrays of indicators are not allowed on input");

		add_variable_to_head(&argsinsert, find_variable(@1), find_variable(@2));
		@$ = create_questionmarks(@1, false);
	}
	;

char_civar: char_variable
	{
		char	   *ptr = strstr(@1, ".arr");

		if (ptr)				/* varchar, we need the struct name here, not
								 * the struct element */
			*ptr = '\0';
		add_variable_to_head(&argsinsert, find_variable(@1), &no_indicator);
	}
	;

civar: cvariable
	{
		add_variable_to_head(&argsinsert, find_variable(@1), &no_indicator);
		@$ = create_questionmarks(@1, false);
	}
	;

indicator: cvariable
	{
		check_indicator((find_variable(@1))->type);
	}
	| SQL_INDICATOR cvariable
	{
		check_indicator((find_variable(@2))->type);
		@$ = @2;
	}
	| SQL_INDICATOR name
	{
		check_indicator((find_variable(@2))->type);
		@$ = @2;
	}
	;

cvariable: CVARIABLE
	{
		/*
		 * As long as multidimensional arrays are not implemented we have to
		 * check for those here
		 */
		const char *ptr = @1;
		int			brace_open = 0,
					brace = false;

		for (; *ptr; ptr++)
		{
			switch (*ptr)
			{
				case '[':
					if (brace)
						mmfatal(PARSE_ERROR, "multidimensional arrays for simple data types are not supported");
					brace_open++;
					break;
				case ']':
					brace_open--;
					if (brace_open == 0)
						brace = true;
					break;
				case '\t':
				case ' ':
					break;
				default:
					if (brace_open == 0)
						brace = false;
					break;
			}
		}
	}
	;

ecpg_param: PARAM
	;

ecpg_bconst: BCONST
	;

ecpg_fconst: FCONST
	;

ecpg_sconst: SCONST
	;

ecpg_xconst: XCONST
	;

ecpg_ident: IDENT
	| CSTRING
	{
		@$ = make3_str("\"", @1, "\"");
	}
	;

quoted_ident_stringvar: name
	{
		@$ = make3_str("\"", @1, "\"");
	}
	| char_variable
	{
		@$ = make3_str("(", @1, ")");
	}
	;

/*
 * C stuff
 */

c_stuff_item: c_anything
	| '(' ')'
	{
		@$ = "()";
	}
	| '(' c_stuff ')'
	;

c_stuff: c_stuff_item
	| c_stuff c_stuff_item
	;

c_list: c_term
	| c_list ',' c_term
	;

c_term: c_stuff
	| '{' c_list '}'
	;

c_thing: c_anything
	| '('
	| ')'
	| ','
	| ';'
	;

/*
 * Note: NULL_P is treated specially to force it to be output in upper case,
 * since it's likely meant as a reference to the standard C macro NULL.
 */
c_anything: ecpg_ident
	| Iconst
	| ecpg_fconst
	| ecpg_sconst
	| '*'
	| '+'
	| '-'
	| '/'
	| '%'
	| NULL_P						{ @$ = "NULL"; }
	| S_ADD
	| S_AND
	| S_ANYTHING
	| S_AUTO
	| S_CONST
	| S_DEC
	| S_DIV
	| S_DOTPOINT
	| S_EQUAL
	| S_EXTERN
	| S_INC
	| S_LSHIFT
	| S_MEMBER
	| S_MEMPOINT
	| S_MOD
	| S_MUL
	| S_NEQUAL
	| S_OR
	| S_REGISTER
	| S_RSHIFT
	| S_STATIC
	| S_SUB
	| S_TYPEDEF
	| S_VOLATILE
	| SQL_BOOL
	| ENUM_P
	| HOUR_P
	| INT_P
	| SQL_LONG
	| MINUTE_P
	| MONTH_P
	| SECOND_P
	| SQL_SHORT
	| SQL_SIGNED
	| SQL_STRUCT
	| SQL_UNSIGNED
	| YEAR_P
	| CHAR_P
	| FLOAT_P
	| TO
	| UNION
	| VARCHAR
	| '['
	| ']'
	| '='
	| ':'
	;

DeallocateStmt: DEALLOCATE prepared_name
	{
		check_declared_list(@2);
		@$ = @2;
	}
	| DEALLOCATE PREPARE prepared_name
	{
		check_declared_list(@3);
		@$ = @3;
	}
	| DEALLOCATE ALL
	{
		@$ = "all";
	}
	| DEALLOCATE PREPARE ALL
	{
		@$ = "all";
	}
	;

Iresult: Iconst
	| '(' Iresult ')'
	| Iresult '+' Iresult
	| Iresult '-' Iresult
	| Iresult '*' Iresult
	| Iresult '/' Iresult
	| Iresult '%' Iresult
	| ecpg_sconst
	| ColId
	| ColId '(' var_type ')'
	{
		if (pg_strcasecmp(@1, "sizeof") != 0)
			mmerror(PARSE_ERROR, ET_ERROR, "operator not allowed in variable definition");
		else
			@$ = cat_str(4, @1, "(", $3.type_str, ")");
	}
	;

execute_rest: /* EMPTY */
	| ecpg_using opt_ecpg_into
	| ecpg_into ecpg_using
	| ecpg_into
	;

ecpg_into: INTO into_list
	{
		/* always suppress this from the constructed string */
		@$ = "";
	}
	| into_descriptor
	;

opt_ecpg_into: /* EMPTY */
	| ecpg_into
	;

ecpg_fetch_into: ecpg_into
	| using_descriptor
	{
		struct variable *var;

		var = argsinsert->variable;
		remove_variable_from_list(&argsinsert, var);
		add_variable_to_head(&argsresult, var, &no_indicator);
	}
	;

opt_ecpg_fetch_into: /* EMPTY */
	| ecpg_fetch_into
	;

%%

void
base_yyerror(const char *error)
{
	/* translator: %s is typically the translation of "syntax error" */
	mmerror(PARSE_ERROR, ET_ERROR, "%s at or near \"%s\"",
			_(error), token_start ? token_start : base_yytext);
}

void
parser_init(void)
{
	/*
	 * This function is empty. It only exists for compatibility with the
	 * backend parser right now.
	 */
}
