%include 
{
/*
 * Copyright (C) 2012 - Juan Ferrer Toribio
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

	#include <stdlib.h>
	#include <assert.h>
	#include <sql/sql.h>
	#include "gram.h"

	typedef struct
	{
		SqlObject * object;
		gchar * string;
		gchar * current;
		gboolean error;
	}
	ParseState;

	static inline SqlList * list_add (gpointer item, SqlList * list)
	{
		sql_list_add (list, item);
		return list;
	}

	static inline SqlList * list_new (gpointer item, GType gtype)
	{
		SqlList * list = sql_list_new (gtype);
		return list_add (item, list);
	}
}

%default_type		{SqlObject *}
%token_type			{gchar *}
%token_destructor	{g_free ($$);}
%token_prefix		SQL_PARSER_

// Return pointer
%extra_argument		{ ParseState * state }

// Error treatment
%syntax_error
{
	gchar * pref = ".", * err_str = NULL;

	if (state->current)
	{
		err_str = state->current - 15 >= state->string ?
			state->current - 10 : state->string;
		pref = err_str == state->string ? " near: " : " near: [...]";
	}

	g_log (g_quark_to_string (SQL_PARSER_LOG_DOMAIN), G_LOG_LEVEL_WARNING,
		"Parsing error%s%s", pref, err_str);

	state->error = TRUE;
}

%stack_overflow	
{
	g_log (g_quark_to_string (SQL_PARSER_LOG_DOMAIN)
		,G_LOG_LEVEL_WARNING
		,"Parser stack overflow. Parsing terminated.\n");
}

// Operator precedence
%left		UNION EXCEPT.
%left		INSERSECT.
%left		OR.
%left		XOR.
%left		AND.
%right		NOT.
%right		EQ NE.
%left		GT GE LT LE.
%left		LIKE.
%left		IS.
%left		PLUS MINUS.
%left		STAR DIV MOD.
%right		SIGN.

// Start symbol:
%start_symbol start

start ::= multi_stmt (A).
{
	state->object = SQL_OBJECT (A);
}

//+++++++++++++++++++++++++++++++++++++++++++++++++++ Multi statement

multi_stmt(A) ::= stmt_list(stmts).
{
	guint len = sql_list_length (stmts);

	if (len == 1)
	{
		A = sql_list_get (stmts, 0);
		g_object_unref (g_object_ref_sink (stmts));
	}
	else
		A = g_object_new (SQL_TYPE_MULTI_STMT, "stmts", stmts, NULL);
}

%type stmt_list {SqlList *}
stmt_list(A) ::= stmt(X).					{ A = list_new (X, SQL_TYPE_STMT); }
stmt_list(A) ::= stmt(X) SC.				{ A = list_new (X, SQL_TYPE_STMT); }
stmt_list(A) ::= stmt(X) SC stmt_list(B).	{ A = list_add (X, B); }

//+++++++++++++++++++++++++++++++++++++++++++++++++++ Statemet

stmt(A) ::= select_stmt(X).		{ A = X; }
stmt(A) ::= delete_stmt(X).		{ A = X; }
stmt(A) ::= update_stmt(X).		{ A = X; }
stmt(A) ::= insert_stmt(X).		{ A = X; }

//+++++++++++++++++++++++++++++++++++++++++++++++++++ Select

select_stmt(A) ::= select_stmt(B) set_op(type) simple_select(X).
{
	g_object_set (B, "type", type, "next", X, NULL);
	A = B;
}
select_stmt(A) ::= simple_select(X). { A = X; }

%type set_op {SqlSelectType}
set_op(A) ::= UNION ANY.	{ A = SQL_SELECT_UNION_ANY; }
set_op(A) ::= UNION ALL.	{ A = SQL_SELECT_UNION_ALL; }
set_op(A) ::= UNION.		{ A = SQL_SELECT_UNION_ALL; }
set_op(A) ::= INTERSECT.	{ A = SQL_SELECT_INTERSECT; }
set_op(A) ::= EXCEPT.		{ A = SQL_SELECT_EXCEPT; }

simple_select(A) ::= LP simple_select(X) RP. { A = X; }
simple_select(A) ::= SELECT
					distinct(distinct)
					select_field_list(fields)
					from(targets)
					where(where)
					group(group) having(having)
					order(order)
					limit(limit).
{
	A = g_object_new (SQL_TYPE_SELECT
		,"distinct"	,distinct
		,"fields"	,fields
		,"targets"	,targets
		,"where"	,where
		,"group"	,group
		,"having"	,having
		,"having"	,having
		,"order"	,order
		,NULL
	);

	if (limit)
		g_object_set (A
			,"limit-offset"	,limit->offset
			,"limit-count"	,limit->count
			,NULL
		);
}

%type distinct {gboolean}
distinct(A) ::= DISTINCT.	{ A = TRUE; }
distinct(A) ::= ALL.		{ A = FALSE; }
distinct(A) ::= .			{ A = FALSE; }

select_field(A) ::= expr(X).
	{ A = g_object_new (SQL_TYPE_SELECT_FIELD, "expr", X, NULL); }
select_field(A) ::= expr(X) alias(Y).
	{ A = g_object_new (SQL_TYPE_SELECT_FIELD, "expr", X, "alias", Y, NULL); }

%type select_field_list {SqlList *}
select_field_list(A) ::= select_field(X).							{ A = list_new (X, SQL_TYPE_SELECT_FIELD); }
select_field_list(A) ::= select_field_list(B) CM select_field(X).	{ A = list_add (X, B); }

%type from {SqlList *}
from(A) ::= FROM target_list(X).	{ A = X; }
from(A) ::= .						{ A = NULL; }

%type group {SqlList *}
group(A) ::= GROUP expr_list(X).	{ A = X; }
group(A) ::= .						{ A = NULL; }

having(A) ::= HAVING expr(X).		{ A = X; }
having(A) ::= .						{ A = NULL; }

%type order {SqlList *}
order(A) ::= ORDER order_list (X).	{ A = X; }
order(A) ::= .						{ A = NULL; }

%type order_list {SqlList *}
order_list(A) ::= order_expr(X).					{ A = list_new (X, SQL_TYPE_SELECT_ORDER); }
order_list(A) ::= order_list(B) CM order_expr(X).	{ A = list_add (X, B); }

order_expr(A) ::= expr(X) order_way(Y).	{ A = g_object_new (SQL_TYPE_SELECT_ORDER, "expr", X, "way", Y, NULL); }

%type order_way {SqlSelectOrderWay}
order_way(A) ::= ASC.	{ A = SQL_SELECT_ORDER_ASC; }
order_way(A) ::= DESC.	{ A = SQL_SELECT_ORDER_DESC; }
order_way(A) ::= .		{ A = SQL_SELECT_ORDER_ASC; }

%type limit {SelectLimit *}
%destructor limit {g_free ($$);}
%include
{
	typedef struct
	{
		guint offset;
		guint count;
	}
	SelectLimit;
	
	static inline SelectLimit * create_limit (gchar * offset, gchar * count)
	{
		SelectLimit * limit = g_new (SelectLimit, 1);
		limit->offset = offset ? atoi (offset) : 0;
		limit->count = count ? atoi (count) : 0;
		return limit;
	}
}
limit(A) ::= LIMIT INTEGER(X).						{ A = create_limit (NULL, X); }
limit(A) ::= OFFSET INTEGER(Y).						{ A = create_limit (Y, NULL); }
limit(A) ::= LIMIT INTEGER(X) OFFSET INTEGER(Y).	{ A = create_limit (Y, X); }
limit(A) ::= LIMIT INTEGER(X) CM INTEGER(Y).		{ A = create_limit (Y, X); }
limit(A) ::= .										{ A = NULL; }

//+++++++++++++++++++++++++++++++++++++++++++++++++++ Delete

delete_stmt(A) ::= DELETE table_list(targets) del_using(tables) where(where).
{
	A = g_object_new (SQL_TYPE_DELETE
		,"targets"	,targets
		,"tables"	,tables
		,"where"	,where
		,NULL
	);
}

%type table_list {SqlList *}
table_list(A) ::= table(X).						{ A = list_new (X, SQL_TYPE_TABLE); }
table_list(A) ::= table_list(B) CM table(X).	{ A = list_add (X, B); }

%type del_using {SqlList *}
del_using(A) ::= USING target_list(X).	{ A = X; }
del_using(A) ::= .						{ A = NULL; }

//+++++++++++++++++++++++++++++++++++++++++++++++++++ Update

update_stmt(A) ::= UPDATE table(table) SET update_list(sets) where(where).
{
	A = g_object_new (SQL_TYPE_UPDATE
		,"targets"	,table
		,"sets"		,sets
		,"where"	,where
		,NULL
	);
}

%type update_list {SqlList *}
update_list(A) ::= update_set(X).					{ A = list_new (X, SQL_TYPE_UPDATE_SET); }
update_list(A) ::= update_list(B) CM update_set(X).	{ A = list_add (X, B); }

update_set(A) ::= field(X) EQ def_expr(Y).
{
	A = g_object_new (SQL_TYPE_UPDATE_SET
		,"field"	,X
		,"expr"		,Y
		,NULL
	);
}

//+++++++++++++++++++++++++++++++++++++++++++++++++++ Insert

insert_stmt(A) ::= INSERT table(table) insert_fields(fields) insert_values(values).
{
	A = g_object_new (SQL_TYPE_INSERT
		,"table"	,table
		,"fields"	,fields
		,NULL
	);
	
	if (SQL_IS_SELECT (values))
		g_object_set (A, "select", values, NULL);
	else
		g_object_set (A, "values", values, NULL);
}

%type insert_fields {SqlList *}
insert_fields(A) ::= LP field_list(X) RP.	{ A = X; }
insert_fields(A) ::= .						{ A = NULL; }

%type insert_values {gpointer}
insert_values(A) ::= DEFAULT VALUES.		{ A = NULL; }
insert_values(A) ::= VALUES set_list(X).	{ A = X; }
//insert_values(A) ::= select_stmt(X).		{ A = X; }

%type set_list {SqlList *}
set_list(A) ::= set(X).					{ A = list_new (X, SQL_TYPE_SET); }
set_list(A) ::= set_list(B) CM set(X).	{ A = list_add (X, B); }

set(A) ::= LP def_expr_list(X) RP.		{ A = g_object_new (SQL_TYPE_SET, "exprs", X, NULL); }

%type def_expr_list {SqlList *}
def_expr_list(A) ::= def_expr(X).						{ A = list_new (X, SQL_TYPE_EXPR); }
def_expr_list(A) ::= def_expr_list(B) CM def_expr(X).	{ A = list_add (X, B); }

//+++++++++++++++++++++++++++++++++++++++++++++++++++ Common to statements

where(A) ::= WHERE expr(X). { A = X; }
where(A) ::= .				{ A = NULL; }

def_expr(A) ::= expr(X).	{ A = X; }
def_expr(A) ::= DEFAULT.	{ A = NULL; }

//+++++++++++++++++++++++++++++++++++++++++++++++++++ Target

target(A) ::= unaliased_target(X).				{ A = X; }
target(A) ::= unaliased_target(X) alias(Y).
{
	g_object_set (X, "alias", Y, NULL);
	A = X;
}
//target(A) ::= LP target(X) RP. 				{ A = X; }

%type target_list {SqlList *}
target_list(A) ::= target(X).					{ A = list_new (X, SQL_TYPE_TARGET); }
target_list(A) ::= target_list(B) CM target(X).	{ A = list_add (X, B); }

unaliased_target(A) ::= join(X).		{ A = X; }
unaliased_target(A) ::= subquery(X).	{ A = X; }
unaliased_target(A) ::= table(X).		{ A = X; }

//+++++++++++++++++++++++++++++++++++++++++++++++++++ Join

join(A) ::= target(left) join_type(type) target(right) join_cond(condition).
{
	A = g_object_new (SQL_TYPE_JOIN
		,"target-left"	,left
		,"target-right"	,right
		,"join-type"	,type
		,NULL
	);

	if (SQL_IS_LIST (condition)) // USING
	{
		GList * n;
		SqlList * exprs = sql_list_new (SQL_TYPE_EXPR);
		SqlOperation * op_and = g_object_new (SQL_TYPE_OPERATION
			,"type"		,SQL_OPERATION_TYPE_AND
			,"exprs"	,exprs
			,NULL
		);

		for (n = sql_list_get_items (condition); n; n = n->next)
		{
			SqlList * equal = sql_list_new (SQL_TYPE_EXPR);
			SqlOperation * op = g_object_new (SQL_TYPE_OPERATION
				,"type"		,SQL_OPERATION_TYPE_EQUAL
				,"exprs"	,equal
				,NULL
			);

			gchar * target = SQL_TARGET (left)->alias ? SQL_TARGET (left)->alias : SQL_TABLE (left)->name;
			gchar * schema = SQL_IS_TABLE(left) && SQL_TABLE (left)->schema ?
				SQL_TABLE (left)->schema : NULL;

			g_object_set (n->data, "target", target, "schema", schema, NULL);
			sql_list_add (equal, n->data);

			target = SQL_TARGET (right)->alias ? SQL_TARGET (right)->alias : SQL_TABLE (right)->name;
			schema = SQL_IS_TABLE (right) && SQL_TABLE (right)->schema ?
				SQL_TABLE (right)->schema : NULL;

			sql_list_add (equal,
				sql_field_new_with_target (SQL_FIELD (n->data)->name, target, schema));

			sql_list_add (exprs, op);
		}

		g_object_set (A, "condition", op_and, NULL);
		g_object_unref (g_object_ref_sink (condition));
	}
	else
		g_object_set (A, "condition", condition, NULL);
}

%type join_type {SqlJoinType}
join_type(A) ::= INNER JOIN.	{ A = SQL_JOIN_TYPE_INNER; }
join_type(A) ::= JOIN.			{ A = SQL_JOIN_TYPE_INNER; }
join_type(A) ::= LEFT JOIN.		{ A = SQL_JOIN_TYPE_LEFT; }
join_type(A) ::= RIGHT JOIN.	{ A = SQL_JOIN_TYPE_RIGHT; }

%type join_cond {gpointer}
join_cond(A) ::= ON expr(X).				{ A = X; }
join_cond(A) ::= USING LP field_list(X) RP.	{ A = X; }

//+++++++++++++++++++++++++++++++++++++++++++++++++++ Subquery

subquery(A) ::= LP select_stmt(stmts) RP.
{
	A = g_object_new (SQL_TYPE_SUBQUERY, "stms", stmts, NULL);
}

//+++++++++++++++++++++++++++++++++++++++++++++++++++ Table

table(A) ::= identifier(X).
{
	A = g_object_new (SQL_TYPE_TABLE, "name", X, NULL);
}
table(A) ::= identifier(Y) DOT identifier(X).
{
	A = g_object_new (SQL_TYPE_TABLE, "name", X, "schema", Y, NULL);
}

//+++++++++++++++++++++++++++++++++++++++++++++++++++ Expr

expr(A) ::= LP expr(X) RP.	{ A = X; }

%type expr_list {SqlList *}
expr_list(A) ::= expr(X).					{ A = list_new (X, SQL_TYPE_EXPR); }
expr_list(A) ::= expr_list(B) CM expr(X).	{ A = list_add (X, B); }

expr(A) ::= field(X).		{ A = X; }
expr(A) ::= function(X).	{ A = X; }
expr(A) ::= operation(X).	{ A = X; }
expr(A) ::= value(X).		{ A = X; }

//+++++++++++++++++++++++++++++++++++++++++++++++++++ Field

field(A) ::= unqualified_field(X).										{ A = X; }
field(A) ::= identifier(Y) DOT unqualified_field(X).
{
	g_object_set (X, "target", Y, NULL);
	A = X;
}
field(A) ::= identifier(Z) DOT identifier(Y) DOT unqualified_field(X).
{
	g_object_set (X, "target", Y, "schema", Z, NULL);
	A = X;
}

unqualified_field(A) ::= identifier(X).
{
	A = g_object_new (SQL_TYPE_FIELD, "name", X, NULL);
}
unqualified_field(A) ::= STAR.
{
	A = g_object_new (SQL_TYPE_FIELD, "name", "*", NULL);
}

%type field_list {SqlList *}
field_list(A) ::= field(X).						{ A = list_new (X, SQL_TYPE_FIELD); }
field_list(A) ::= field_list(B) CM field(X).	{ A = list_add (X, B); }

//+++++++++++++++++++++++++++++++++++++++++++++++++++ Function

function(A) ::= unqualified_function(X).					{ A = X; }
function(A) ::= identifier(Z) DOT unqualified_function(X).
{
	g_object_set (X, "schema", Z, NULL);
	A = X;
}

unqualified_function(A) ::= identifier(X) LP function_expr_list(Y) RP.
{
	A = g_object_new (SQL_TYPE_FUNCTION, "name", X, "params", Y, NULL);
}

%type function_expr_list {SqlList *}
function_expr_list(A) ::= .				{ A = NULL; }
function_expr_list(A) ::= expr_list(X).	{ A = X; }

//+++++++++++++++++++++++++++++++++++++++++++++++++++ Operation

%include
{
	static inline SqlObject * create_operation 
		(const gpointer X, const gpointer Y, const SqlOperationType type)
	{
		SqlList * exprs = sql_list_new (SQL_TYPE_EXPR);
		sql_list_add (exprs, X);

		if (Y) // Binary operation
			sql_list_add (exprs, Y);

		return g_object_new (SQL_TYPE_OPERATION, "type", type, "exprs", exprs, NULL);
	}
}
operation(A) ::= MINUS expr(X). [SIGN]
{
	GValue value = {0};
	SqlObject * minus = sql_value_new ();
	g_value_init (&value, G_TYPE_INT);
	g_value_set_int (&value, -1);
	sql_value_set_value (SQL_VALUE (minus), &value);
	A = create_operation (X, minus, SQL_OPERATION_TYPE_MULTIPLICATION);
}

operation(A) ::= NOT expr(X).
	{ A = create_operation (X, NULL, SQL_OPERATION_TYPE_NOT); }

operation(A) ::= PLUS expr(X). [SIGN]
	{ A = create_operation (X, NULL, SQL_OPERATION_TYPE_SUM); }

operation(A) ::= expr(X) PLUS expr(Y).
	{ A = create_operation (X, Y, SQL_OPERATION_TYPE_SUM); }

operation(A) ::= expr(X) MINUS expr(Y).
	{ A = create_operation (X, Y, SQL_OPERATION_TYPE_SUBTRACTION); }

operation(A) ::= expr(X) STAR expr(Y).
	{ A = create_operation (X, Y, SQL_OPERATION_TYPE_MULTIPLICATION); }

operation(A) ::= expr(X) DIV expr(Y).
	{ A = create_operation (X, Y, SQL_OPERATION_TYPE_DIVISION); }

operation(A) ::= expr(X) OR expr(Y).
	{ A = create_operation (X, Y, SQL_OPERATION_TYPE_OR); }

operation(A) ::= expr(X) XOR expr(Y).
	{ A = create_operation (X, Y, SQL_OPERATION_TYPE_XOR); }

operation(A) ::= expr(X) AND expr(Y).
	{ A = create_operation (X, Y, SQL_OPERATION_TYPE_AND); }

operation(A) ::= expr(X) EQ expr(Y).
	{ A = create_operation (X, Y, SQL_OPERATION_TYPE_EQUAL); }

operation(A) ::= expr(X) NE expr(Y).
	{ A = create_operation (X, Y, SQL_OPERATION_TYPE_NOT_EQUAL); }

//XXX the following should be %nonassoc operators but are %left to avoid
// conflicts. The DB will warn about a bad use if used as %left.
operation(A) ::= expr(X) GT expr(Y).
	{ A = create_operation (X, Y, SQL_OPERATION_TYPE_GREATER); }

operation(A) ::= expr(X) GE expr(Y).
	{ A = create_operation (X, Y, SQL_OPERATION_TYPE_GREATER_EQUAL); }

operation(A) ::= expr(X) LT expr(Y).
	{ A = create_operation (X, Y, SQL_OPERATION_TYPE_LOWER); }

operation(A) ::= expr(X) LE expr(Y).
	{ A = create_operation (X, Y, SQL_OPERATION_TYPE_LOWER_EQUAL); }

operation(A) ::= expr(X) LIKE expr(Y).
	{ A = create_operation (X, Y, SQL_OPERATION_TYPE_LIKE); }

operation(A) ::= expr(X) IS expr(Y).
	{ A = create_operation (X, Y, SQL_OPERATION_TYPE_IS); }

operation(A) ::= expr(X) MOD expr(Y).
	{ A = create_operation (X, Y, SQL_OPERATION_TYPE_MOD); }

//+++++++++++++++++++++++++++++++++++++++++++++++++++ Value

value(A) ::= INTEGER(X).
{
	GValue value = {0};
	g_value_set_int (g_value_init (&value, G_TYPE_INT), atoi (X));
	A = g_object_new (SQL_TYPE_VALUE, "value", &value, NULL);
	g_value_unset (&value);
}
value(A) ::= FLOAT(X).
{
	GValue value = {0};
	g_value_set_double
		(g_value_init (&value, G_TYPE_DOUBLE), g_ascii_strtod (X, NULL));
	A = g_object_new (SQL_TYPE_VALUE, "value", &value, NULL);
	g_value_unset (&value);
}
value(A) ::= STRING(X).
{
	GValue value = {0};
	g_value_set_string (g_value_init (&value, G_TYPE_STRING), X);
	A = g_object_new (SQL_TYPE_VALUE, "value", &value, NULL);
	g_value_unset (&value);
}
value(A) ::= BOOLEAN(X).
{
	GValue value = {0};
	g_value_set_boolean (g_value_init (&value, G_TYPE_BOOLEAN)
		,(!g_strcmp0 (X, "TRUE") || !g_strcmp0 (X, "true")) ? TRUE : FALSE);
	A = g_object_new (SQL_TYPE_VALUE, "value", &value, NULL);
	g_value_unset (&value);
}

//+++++++++++++++++++++++++++++++++++++++++++++++++++ Alias

%type alias {gchar *}
alias(A) ::= AS identifier(X).		{ A = X; }
alias(A) ::= identifier(X).			{ A = X; }

%type identifier {gchar *}
identifier(A) ::= IDENTIFIER(X).	{ A = X; }

//+++++++++++++++++++++++++++++++++++++++++++++++++++ Placeholder

placeholder(A) ::= PLACEHOLDER(X). { A = g_object_new (SQL_TYPE_HOLDER, "id", X, NULL); }

stmt ::= placeholder.
target ::= placeholder.
expr ::= placeholder.