/*
 *  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 2 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 Library General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include <stdio.h>
#include <glib.h>
#include <strings.h>
#include <string.h>

#include "sql_parser.h"
#include "sql_tree.h"
#include "mem.h"

#ifndef _
#define _(x) (x)
#endif

extern char *sqltext;
extern void sql_switch_to_buffer (void *buffer);
extern void *sql_scan_string (const char *string);

int sqlerror (char *error);

sql_statement *sql_result;
GError **sql_error;

int sqlparse (void);

static int sql_destroy_select (sql_select_statement * select);
static int sql_destroy_insert (sql_insert_statement * insert);
static char *sql_select_stringify (sql_select_statement * select);
static int sql_destroy_field (sql_field * field);
static int sql_destroy_param_spec (param_spec * pspec);

static char *sql_field_stringify (sql_field * field);

/**
 * sqlerror:
 * 
 * Internal function for displaying error messages used by the lexer parser.
 */
int
sqlerror (char *string)
{
	if (sql_error) {
		if (!strcmp (string, "parse error"))
			g_set_error (sql_error, 0, 0, _("Parse error near `%s'\n"), sqltext);
		if (!strcmp (string, "syntax error"))
			g_set_error (sql_error, 0, 0, _("Syntax error near `%s'\n"), sqltext);
	}
	else
		fprintf (stderr, "SQL Parser error: %s near `%s'\n", string, sqltext);
	return 0;
}

static int
sql_destroy_field_item (sql_field_item * item)
{
	GList *walk;

	if (!item)
		return 0;

	switch (item->type) {
	case SQL_name:
		for (walk = item->d.name; walk != NULL; walk = walk->next)
			memsql_free (walk->data);

		g_list_free (item->d.name);
		break;

	case SQL_equation:
		sql_destroy_field_item (item->d.equation.left);
		sql_destroy_field_item (item->d.equation.right);
		break;

	case SQL_inlineselect:
		sql_destroy_select (item->d.select);
		break;

	case SQL_function:
		memsql_free (item->d.function.funcname);
		for (walk = item->d.function.funcarglist; walk != NULL; walk = walk->next)
			sql_destroy_field (walk->data);

		g_list_free (item->d.function.funcarglist);
	}


	memsql_free (item);
	return 0;
}

static int
sql_destroy_field (sql_field * field)
{
	if (!field)
		return 0;

	sql_destroy_field_item (field->item);
	memsql_free (field->as);
	if (field->param_spec) {
		GList *walk;
		for (walk = field->param_spec; walk != NULL; walk = walk->next)
			sql_destroy_param_spec ((param_spec *) walk->data);
		g_list_free (field->param_spec);
	}
	memsql_free (field);
	return 0;
}


static int
sql_destroy_param_spec (param_spec * pspec)
{
	if (!pspec)
		return 0;

	memsql_free (pspec->content);
	memsql_free (pspec);
	return 0;
}

static int
sql_destroy_condition (sql_condition * cond)
{
	if (!cond)
		return 0;

	switch (cond->op) {
	case SQL_eq:
	case SQL_is:
	case SQL_isnot:
	case SQL_in:
	case SQL_notin:
	case SQL_like:
	case SQL_gt:
	case SQL_lt:
	case SQL_geq:
	case SQL_leq:
		sql_destroy_field (cond->d.pair.left);
		sql_destroy_field (cond->d.pair.right);
		break;

	case SQL_between:
		sql_destroy_field (cond->d.between.field);
		sql_destroy_field (cond->d.between.lower);
		sql_destroy_field (cond->d.between.upper);
		break;
	}

	memsql_free (cond);
	return 0;
}

static int
sql_destroy_where (sql_where * where)
{
	if (!where)
		return 0;

	switch (where->type) {
	case SQL_single:
		sql_destroy_condition (where->d.single);
		break;

	case SQL_negated:
		sql_destroy_where (where->d.negated);
		break;

	case SQL_pair:
		sql_destroy_where (where->d.pair.left);
		sql_destroy_where (where->d.pair.right);
		break;
	}

	memsql_free (where);
	return 0;
}

static int
sql_destroy_table (sql_table * table)
{
	if (!table)
		return 0;

	switch (table->type) {
	case SQL_simple:
		memsql_free (table->d.simple);
		break;

	case SQL_nestedselect:
		sql_destroy_select (table->d.select);
		break;
	}

	if (table->join_cond)
		sql_destroy_where (table->join_cond);

	memsql_free (table);
	return 0;
}

static int
sql_destroy_insert (sql_insert_statement * insert)
{
	GList *walk;

	sql_destroy_table (insert->table);

	for (walk = insert->fields; walk != NULL; walk = walk->next)
		sql_destroy_field (walk->data);
	g_list_free (insert->fields);

	for (walk = insert->values; walk != NULL; walk = walk->next)
		sql_destroy_field (walk->data);
	g_list_free (insert->values);

	memsql_free (insert);

	return 0;
}

static int
sql_destroy_select (sql_select_statement * select)
{
	GList *walk;

	for (walk = select->fields; walk != NULL; walk = walk->next)
		sql_destroy_field (walk->data);

	for (walk = select->from; walk != NULL; walk = walk->next)
		sql_destroy_table (walk->data);

	for (walk = select->order; walk != NULL; walk = walk->next) {
		GList *walk2 = ((sql_order_field *) walk->data)->name;
		
		for (; walk2 != NULL; walk2 = walk2->next)
			memsql_free (walk2->data);
		memsql_free (walk->data);
	}

	for (walk = select->group; walk != NULL; walk = walk->next)
		sql_destroy_field (walk->data);

	g_list_free (select->fields);
	g_list_free (select->from);
	g_list_free (select->order);
	g_list_free (select->group);

	sql_destroy_where (select->where);

	memsql_free (select);

	return 0;
}

static int
sql_destroy_update (sql_update_statement * update)
{
	GList *walk;

	sql_destroy_table (update->table);

	for (walk = update->set; walk != NULL; walk = walk->next)
		sql_destroy_condition (walk->data);
	g_list_free (update->set);

	sql_destroy_where (update->where);

	memsql_free (update);

	return 0;
}

static int
sql_destroy_delete (sql_delete_statement * delete)
{
	sql_destroy_table (delete->table);
	sql_destroy_where (delete->where);

	memsql_free (delete);

	return 0;
}

/**
 * sql_destroy:
 * @statement: Sql statement generated by sql_parse()
 * 
 * Free up a sql_statement generated by sql_parse().
 */

int
sql_destroy (sql_statement * statement)
{
	if (!statement)
		return 0;

	switch (statement->type) {
	case SQL_select:
		sql_destroy_select (statement->statement);
		break;

	case SQL_insert:
		sql_destroy_insert (statement->statement);
		break;

	case SQL_update:
		sql_destroy_update (statement->statement);
		break;
	case SQL_delete:
		sql_destroy_delete (statement->statement);
		break;

	default:
		fprintf (stderr, "Unknown statement type: %d\n", statement->type);
	}

	memsql_free (statement->full_query);
	memsql_free (statement);
	return 0;
}

/**
 * sql_parse_with_error:
 * @sqlquery: A SQL query string. ie SELECT * FROM FOO
 * @error: a place where to store an error, or %NULL
 * 
 * Generate in memory a structure of the @sqlquery in an easy
 * to view way.  You can also modify the returned structure and
 * regenerate the sql query using sql_stringify().  The structure
 * contains information on what type of sql statement it is, what
 * tables its getting from, what fields are selected, the where clause,
 * joins etc.
 * 
 * Returns: A generated sql_statement or %NULL on error.
 */
sql_statement *
sql_parse_with_error (char *sqlquery, GError ** error)
{
	if (sqlquery == NULL) {
		if (error)
			g_set_error (error, 0, 0, _("Empty query to parse"));
		else
			fprintf (stderr, "SQL parse error, you can not specify NULL");
		return NULL;

	}

	sql_error = error;
	sql_switch_to_buffer (sql_scan_string (sqlquery));

	if (sqlparse () == 0) {
		sql_result->full_query = memsql_strdup (sqlquery);
		return sql_result;
	}
	else {
		if (!error)
			fprintf (stderr, "Error on SQL statement: %s\n", sqlquery);
		return NULL;
	}
}

/**
 * sql_parse:
 * @sqlquery: A SQL query string. ie SELECT * FROM FOO
 *
 * Generate in memory a structure of the @sqlquery in an easy
 * to view way.  You can also modify the returned structure and
 * regenerate the sql query using sql_stringify().  The structure
 * contains information on what type of sql statement it is, what
 * tables its getting from, what fields are selected, the where clause,
 * joins etc.
 *
 * Returns: A generated sql_statement or %NULL on error.
 */
sql_statement *
sql_parse (char *sqlquery)
{
	return sql_parse_with_error (sqlquery, NULL);
}

static char *
sql_field_op_stringify (sql_field_operator op)
{
	switch (op) {
	case SQL_plus:
		return memsql_strdup ("+");
	case SQL_minus:
		return memsql_strdup ("-");
	case SQL_times:
		return memsql_strdup ("*");
	case SQL_div:
		return memsql_strdup ("/");
	}

	fprintf (stderr, "Invalid op: %d\n", op);

	return NULL;
}

static char *
sql_field_name_stringify (GList * name)
{
	GList *walk;
	char *result = NULL;

	for (walk = name; walk != NULL; walk = walk->next) {
		result = memsql_strappend_free (result, memsql_strdup (walk->data));
		if (walk->next)
			result = memsql_strappend_free (result, memsql_strdup ("."));

	}

	return result;
}

static char *
sql_field_item_stringify (sql_field_item * item)
{
	char *retval = NULL;
	GList *walk;

	if (!item)
		return NULL;

	switch (item->type) {
	case SQL_name:
		retval = sql_field_name_stringify (item->d.name);
		break;

	case SQL_equation:
		retval =
			memsql_strappend_free (sql_field_item_stringify (item->d.equation.left),
					       sql_field_op_stringify (item->d.equation.op));
		retval =
			memsql_strappend_free (retval,
					       sql_field_item_stringify (item->d.equation.
									 right));
		break;

	case SQL_inlineselect:
		retval =
			memsql_strappend_free (memsql_strdup ("("),
					       sql_select_stringify (item->d.select));
		retval = memsql_strappend_free (retval, memsql_strdup (")"));
		break;

	case SQL_function:
		retval =
			memsql_strappend_free (memsql_strdup (item->d.function.funcname),
					       memsql_strdup ("("));
		for (walk = item->d.function.funcarglist; walk != NULL; walk = walk->next) {
			retval =
				memsql_strappend_free (retval, sql_field_stringify (walk->data));
			if (walk->next)
				retval = memsql_strappend_free (retval, memsql_strdup (", "));
		}
		retval = memsql_strappend_free (retval, memsql_strdup (")"));
		break;

	}

	return retval;
}

static char *
sql_field_stringify (sql_field * field)
{
	char *retval;

	if (!field)
		return NULL;

	retval = sql_field_item_stringify (field->item);

	if (field->as) {
		retval = memsql_strappend_free (retval, memsql_strdup (" as "));
		retval = memsql_strappend_free (retval, memsql_strdup (field->as));
	}

	return retval;
}

static char *
sql_condition_op_stringify (sql_condition_operator op)
{
	switch (op) {
	case SQL_eq:
		return memsql_strdup ("=");
	case SQL_is:
		return memsql_strdup ("is");
	case SQL_isnot:
		return memsql_strdup ("is not");
	case SQL_like:
		return memsql_strdup ("like");
	case SQL_in:
		return memsql_strdup ("in");
	case SQL_notin:
		return memsql_strdup ("not in");
	default:
		fprintf (stderr, "Invalid condition op: %d\n", op);
	}
	return NULL;
}

static char *
sql_condition_stringify (sql_condition * cond)
{
	char *retval;

	if (!cond)
		return NULL;
	switch (cond->op) {
	case SQL_eq:
	case SQL_is:
	case SQL_isnot:
	case SQL_in:
	case SQL_notin:
	case SQL_like:
		retval =
			memsql_strappend_free (sql_field_stringify (cond->d.pair.left),
					       memsql_strdup (" "));
		retval =
			memsql_strappend_free (retval, sql_condition_op_stringify (cond->op));
		retval = memsql_strappend_free (retval, memsql_strdup (" "));
		retval =
			memsql_strappend_free (retval,
					       sql_field_stringify (cond->d.pair.right));
		break;

	case SQL_between:
		retval =
			memsql_strappend_free (sql_field_stringify (cond->d.between.field),
					       memsql_strdup (" between "));
		retval =
			memsql_strappend_free (retval,
					       sql_field_stringify (cond->d.between.lower));
		retval = memsql_strappend_free (retval, memsql_strdup (" and "));
		retval =
			memsql_strappend_free (retval,
					       sql_field_stringify (cond->d.between.upper));
		break;

	default:
		fprintf (stderr, "Invalid  condition type: %d\n", cond->op);
		retval = NULL;
	}

	return retval;
}

static char *
sql_table_stringify (sql_table * table)
{
	char *retval;

	if (!table)
		return NULL;

	/* join type */
	switch (table->join_type) {
	case SQL_cross_join:
		retval = NULL;
		break;
	case SQL_inner_join:
		retval = memsql_strdup (" join ");
		break;
	case SQL_left_join:
		retval = memsql_strdup (" left join ");
		break;
	case SQL_right_join:
		retval = memsql_strdup (" right join ");
		break;
	case SQL_full_join:
		retval = memsql_strdup (" full join ");
		break;
	}

	/* table */
	switch (table->type) {
	case SQL_simple:
		retval = memsql_strappend_free (retval, memsql_strdup (table->d.simple));
		break;

	case SQL_nestedselect:
		retval = memsql_strappend_free (retval, memsql_strdup ("("));
		retval = memsql_strappend_free (retval,
						sql_select_stringify (table->d.select));
		retval = memsql_strappend_free (retval, memsql_strdup (")"));
		break;

	default:
		fprintf (stderr, "Invalid table type: %d\n", table->type);
		retval = NULL;
	}

	/* join condition */
	if (table->join_cond) {
		retval = memsql_strappend_free (retval, memsql_strdup (" on "));
		retval = memsql_strappend_free (retval,
						sql_condition_stringify (table->join_cond));
	}

	return retval;
}

static char *
sql_logic_op_stringify (sql_logic_operator op)
{
	switch (op) {
	case SQL_and:
		return memsql_strdup ("and");
	case SQL_or:
		return memsql_strdup ("or");
	default:
		fprintf (stderr, "invalid logic op: %d", op);
	}
	return NULL;
}

static char *
sql_where_stringify (sql_where * where)
{
	char *retval = NULL;

	if (!where)
		return NULL;

	switch (where->type) {
	case SQL_single:
		retval = sql_condition_stringify (where->d.single);
		break;

	case SQL_negated:
		retval =
			memsql_strappend_free (memsql_strdup ("not "),
					       sql_where_stringify (where->d.negated));
		break;

	case SQL_pair:
		retval =
			memsql_strappend_free (sql_where_stringify (where->d.pair.left),
					       memsql_strdup (" "));
		retval =
			memsql_strappend_free (retval,
					       sql_logic_op_stringify (where->d.pair.op));
		retval = memsql_strappend_free (retval, memsql_strdup (" "));
		retval =
			memsql_strappend_free (retval,
					       sql_where_stringify (where->d.pair.right));
	}

	retval = memsql_strappend_free (memsql_strdup ("("), retval);
	retval = memsql_strappend_free (retval, memsql_strdup (")"));

	return retval;
}

static char *
sql_insert_stringify (sql_insert_statement * insert)
{
	char *result;
	GList *walk;

	result = memsql_strdup ("insert into ");
	result =
		memsql_strappend_free (result, sql_table_stringify (insert->table));

	if (insert->fields) {
		result = memsql_strappend_free (result, memsql_strdup (" ("));
		for (walk = insert->fields; walk != NULL; walk = walk->next) {
			result =
				memsql_strappend_free (result, sql_field_stringify (walk->data));
			if (walk->next)
				result = memsql_strappend_free (result, memsql_strdup (", "));
		}
		result = memsql_strappend_free (result, memsql_strdup (")"));
	}

	result = memsql_strappend_free (result, memsql_strdup (" ("));

	for (walk = insert->values; walk != NULL; walk = walk->next) {
		result = memsql_strappend_free (result, sql_field_stringify (walk->data));
		if (walk->next)
			result = memsql_strappend_free (result, memsql_strdup (", "));
	}

	result = memsql_strappend_free (result, memsql_strdup (")"));

	return result;
}

static char *
sql_select_stringify (sql_select_statement * select)
{
	char *result;
	char *fields;
	char *tables;
	char *where;
	char *order;
	char *group;
	char *temp;
	GList *walk;

	result = memsql_strdup ("select ");

	if (select->distinct)
		result = memsql_strappend_free (result, memsql_strdup ("distinct "));

	fields = NULL;
	for (walk = select->fields; walk != NULL; walk = walk->next) {
		temp = sql_field_stringify (walk->data);
		fields = memsql_strappend_free (fields, temp);

		if (walk->next)
			fields = memsql_strappend_free (fields, memsql_strdup (", "));

	}

	result = memsql_strappend_free (result, fields);
	result = memsql_strappend_free (result, memsql_strdup (" from "));

	tables = NULL;
	for (walk = select->from; walk != NULL; walk = walk->next) {
		temp = sql_table_stringify (walk->data);
		tables = memsql_strappend_free (tables, temp);
		if (walk->next)
			tables = memsql_strappend_free (tables, memsql_strdup (", "));
	}

	result = memsql_strappend_free (result, tables);

	if (select->where)
		where =
			memsql_strappend_free (memsql_strdup (" where "),
					       sql_where_stringify (select->where));
	else
		where = NULL;

	result = memsql_strappend_free (result, where);

	if (select->order) {
		order = memsql_strdup (" order by ");

		for (walk = select->order; walk != NULL; walk = walk->next) {
			order = memsql_strappend_free (order, sql_field_stringify (walk->data));
			if (walk->next)
				order = memsql_strappend_free (order, memsql_strdup (", "));
		}
	}
	else
		order = NULL;
	result = memsql_strappend_free (result, order);

	if (select->group) {
		group = memsql_strdup (" group by ");

		for (walk = select->group; walk != NULL; walk = walk->next) {
			group = memsql_strappend_free (group, sql_field_stringify (walk->data));
			if (walk->next)
				group = memsql_strappend_free (group, memsql_strdup (", "));
		}
	}
	else
		group = NULL;

	result = memsql_strappend_free (result, group);

	return result;
}

static char *
sql_update_stringify (sql_update_statement * update)
{
	char *result;
	GList *walk;

	result =
		memsql_strappend_free (memsql_strdup ("update "),
				       sql_table_stringify (update->table));
	result = memsql_strappend_free (result, memsql_strdup (" set "));

	for (walk = update->set; walk != NULL; walk = walk->next) {
		result =
			memsql_strappend_free (result, sql_condition_stringify (walk->data));
		if (walk->next)
			result = memsql_strappend_free (result, memsql_strdup (", "));
	}

	if (update->where) {
		result = memsql_strappend_free (result, memsql_strdup (" where "));
		result =
			memsql_strappend_free (result, sql_where_stringify (update->where));
	}

	return result;
}

static char *
sql_delete_stringify (sql_delete_statement * delete)
{
	char *result;

	result =
		memsql_strappend_free (memsql_strdup ("delete from "),
				       sql_table_stringify (delete->table));
	if (delete->where) {
		result = memsql_strappend_free (result, memsql_strdup (" where "));
		result =
			memsql_strappend_free (result, sql_where_stringify (delete->where));
	}

	return result;
}

/**
 * sql_stringify:
 * 
 * Covert a sql_statement into a string.  This is very useful for building
 * up sql queries.
 * 
 * Returns: The SQL statement or a %NULL.  Free after use.
 */

char *
sql_stringify (sql_statement * statement)
{
	char *result = NULL;
	char *final;

	if (!statement)
		return NULL;

	switch (statement->type) {
	case SQL_select:
		result = sql_select_stringify (statement->statement);
		break;

	case SQL_insert:
		result = sql_insert_stringify (statement->statement);
		break;

	case SQL_update:
		result = sql_update_stringify (statement->statement);
		break;

	case SQL_delete:
		result = sql_delete_stringify (statement->statement);
		break;

	default:
		fprintf (stderr, "Invalid statement type: %d\n", statement->type);
	}

	if (result)
		final = g_strdup (result);
	else
		final = NULL;

	memsql_free (result);

	return final;
}

static int
sql_statement_select_append_field (sql_select_statement * select,
				   sql_field * field)
{
	select->fields = g_list_append (select->fields, field);
	return 0;
}

/**
 * sql_statement_append_field:
 * @statment: A Sql statement generated from parsing an sql statement
 * @table: Name of table to add, this can be %NULL
 * @fieldname: Field to add to the statment
 *
 * Adds an field into a select statement
 * 
 * Returns: non-zero on error.
 */
int
sql_statement_append_field (sql_statement * statement, char *table,
			    char *fieldname, char *as)
{
	sql_field_item *item;
	sql_field *field;
	GList *name = NULL;

	if (!fieldname)
		return -1;

	if (table)
		name = g_list_append (name, memsql_strdup (table));

	name = g_list_append (name, memsql_strdup (fieldname));
	item = sql_field_item_build (name);
	field = sql_field_build (item);
	if (!as)			/* FIXME ? */
		sql_field_set_as (field, as);

	switch (statement->type) {
	case SQL_select:
		sql_statement_select_append_field (statement->statement, field);
		break;

	case SQL_insert:
	default:
		fprintf (stderr, "Invalid statement type: %d", statement->type);
	}

	return 0;
}

/**
 * sql_statement_append_tablejoin:
 * @statement: A Sql statement generated from parsing an sql statement
 * @table: Name of table to add.
 *
 * Adds a table into a select statement
 * 
 * Returns: non-zero on error.
 */
int
sql_statement_append_tablejoin (sql_statement * statement, char *lefttable,
				char *righttable, char *leftfield,
				char *rightfield)
{
	sql_field_item *leftfitem, *rightfitem;
	sql_field *leftf, *rightf;
	sql_table *table;
	sql_where *where, *otherwhere;
	sql_condition *cond;
	sql_select_statement *select;

	g_assert (lefttable);
	g_assert (righttable);
	g_assert (leftfield);
	g_assert (rightfield);

	if (statement->type != SQL_select) {
		fprintf (stderr, "Invalid statement type: %d", statement->type);
		return -1;
	}

	table = memsql_calloc (sizeof (sql_table));
	table->type = SQL_simple;
	table->d.simple = memsql_strdup (righttable);

	leftf = memsql_calloc (sizeof (sql_field));
	rightf = memsql_calloc (sizeof (sql_field));
	leftfitem = memsql_calloc (sizeof (sql_field_item));
	rightfitem = memsql_calloc (sizeof (sql_field_item));

	leftfitem->type = SQL_name;
	leftfitem->d.name =
		g_list_prepend (leftfitem->d.name,
				memsql_strdup_printf ("%s.%s", lefttable, leftfield));
	rightfitem->type = SQL_name;
	rightfitem->d.name =
		g_list_prepend (rightfitem->d.name,
				memsql_strdup_printf ("%s.%s", righttable, rightfield));
	leftf->item = leftfitem;
	rightf->item = rightfitem;

	cond = memsql_calloc (sizeof (sql_condition));
	cond->op = SQL_eq;
	cond->d.pair.left = leftf;
	cond->d.pair.right = rightf;

	where = memsql_calloc (sizeof (sql_where));
	where->type = SQL_single;
	where->d.single = cond;

	select = statement->statement;
	select->from = g_list_append (select->from, table);

	if (select->where == NULL) {
		select->where = where;
	}
	else {
		otherwhere = select->where;

		select->where = memsql_calloc (sizeof (sql_where));
		select->where->type = SQL_pair;
		select->where->d.pair.left = otherwhere;
		select->where->d.pair.right = where;
		select->where->d.pair.op = SQL_and;
	}
	return 0;
}

int
sql_statement_append_where (sql_statement * statement, char *leftfield,
			    char *rightfield, sql_logic_operator logicopr,
			    sql_condition_operator condopr)
{
	gboolean freerightfield = FALSE;
	sql_field_item *leftfitem, *rightfitem;
	sql_field *leftf, *rightf;
	sql_where *where, *otherwhere, *tmpwhere, *parentwhere;
	sql_condition *cond;
	sql_select_statement *select;

	g_assert (leftfield);
	/* only works for SELECT statements */
	if (statement->type != SQL_select) {
		fprintf (stderr, "Invalid statement type: %d", statement->type);
		return -1;
	}
	/* in case null passed in on the rightfield. Modify it to handle it correctly */
	if (!rightfield) {
		if (condopr == SQL_eq || condopr == SQL_like)
			condopr = SQL_is;
		else
			condopr = SQL_isnot; /* FIXME */
		rightfield = memsql_strdup ("NULL");
		freerightfield = TRUE;
	}
	/* The actual work */
	leftf = memsql_calloc (sizeof (sql_field));
	rightf = memsql_calloc (sizeof (sql_field));
	leftfitem = memsql_calloc (sizeof (sql_field_item));
	rightfitem = memsql_calloc (sizeof (sql_field_item));

	leftfitem->type = SQL_name;
	leftfitem->d.name =
		g_list_prepend (leftfitem->d.name,
				memsql_strdup_printf ("%s", leftfield));
	rightfitem->type = SQL_name;
	rightfitem->d.name =
		g_list_prepend (rightfitem->d.name,
				memsql_strdup_printf ("%s", rightfield));
	leftf->item = leftfitem;
	rightf->item = rightfitem;

	cond = memsql_calloc (sizeof (sql_condition));
	cond->op = condopr;
	cond->d.pair.left = leftf;
	cond->d.pair.right = rightf;

	where = memsql_calloc (sizeof (sql_where));
	where->type = SQL_single;
	where->d.single = cond;

	select = statement->statement;

	if (select->where == NULL) {
		select->where = where;
	}
	else {
		tmpwhere = select->where;
		parentwhere = NULL;
		/* break tree in half if its a and after a Or statement. 
		 * This is to handle the second and statement in this example:
		 * x=y and (x=1 or x=2 or x=3) and y=z */
		if (logicopr == SQL_and) {
			while (tmpwhere->type != SQL_single) {
				if (tmpwhere->d.pair.op == SQL_or) {
					/* insert a record above this or item */
					otherwhere = tmpwhere;

					tmpwhere = memsql_calloc (sizeof (sql_where));
					tmpwhere->type = SQL_pair;
					tmpwhere->d.pair.left = otherwhere;
					tmpwhere->d.pair.right = where;
					tmpwhere->d.pair.op = logicopr;
					/* insert into parent to store this item */
					if (parentwhere == NULL)
						select->where = tmpwhere;
					else
						parentwhere->d.pair.right = tmpwhere;
					return 0;
				}
				parentwhere = tmpwhere;
				tmpwhere = tmpwhere->d.pair.right;
			}
			tmpwhere = select->where;
		}

		/* Find the end of the list */
		while (tmpwhere->type != SQL_single) {
			parentwhere = tmpwhere;
			tmpwhere = tmpwhere->d.pair.right;
		}
		/* Now append onto it */
		otherwhere = tmpwhere;

		tmpwhere = memsql_calloc (sizeof (sql_where));
		tmpwhere->type = SQL_pair;
		tmpwhere->d.pair.left = otherwhere;
		tmpwhere->d.pair.right = where;
		tmpwhere->d.pair.op = logicopr;

		/* insert into parent to store this item */
		if (parentwhere == NULL)
			select->where = tmpwhere;
		else
			parentwhere->d.pair.right = tmpwhere;
	}
	if (freerightfield)
		memsql_free (rightfield);
	return 0;
}

GList *
sql_statement_get_fields (sql_statement * statement)
{
	GList *retval = NULL;
	GList *walk;
	gchar *temp1, *temp2;
	sql_select_statement *select;

	if (!statement)
		return NULL;

	if (!statement->type == SQL_select)
		return NULL;

	select = statement->statement;

	for (walk = select->fields; walk != NULL; walk = walk->next) {
		temp1 = sql_field_stringify (walk->data);
		temp2 = g_strdup (temp1);
		memsql_free (temp1);

		retval = g_list_append (retval, temp2);
	}
	return retval;
}

void
sql_statement_free_fields (GList * fields)
{
	GList *walk;

	for (walk = g_list_first (fields); walk != NULL; walk = walk->next)
		g_free (walk->data);
	g_list_free (fields);
}

void
sql_statement_free_tables (GList * tables)
{
	GList *walk;

	for (walk = g_list_first (tables); walk != NULL; walk = walk->next)
		g_free (walk->data);
	g_list_free (tables);
}

GList *
sql_statement_get_tables (sql_statement * statement)
{
	GList *retval = NULL;
	GList *walk;
	gchar *temp1, *temp2;
	sql_select_statement *select;

	if (!statement)
		return NULL;

	if (!statement->type == SQL_select)
		return NULL;

	select = statement->statement;

	for (walk = select->from; walk != NULL; walk = walk->next) {
		temp1 = sql_table_stringify (walk->data);
		temp2 = g_strdup (temp1);
		memsql_free (temp1);

		retval = g_list_append (retval, temp2);
	}

	return retval;
}

gchar *
sql_statement_get_first_table (sql_statement * statement)
{
	sql_select_statement *select;
	gchar *retval, *temp;

	if (!statement)
		return NULL;

	if (!statement->type == SQL_select)
		return NULL;

	select = statement->statement;

	temp = sql_table_stringify (select->from->data);
	retval = g_strdup (temp);
	memsql_free (temp);

	return retval;
}

static sql_where *
sql_statement_searchwhere_rec (sql_where * where, gchar * lookfor)
{
	sql_condition *single;
	sql_where *retwalk;
	sql_field_item *item;
	GList *walk;

	if (where) {
		switch (where->type) {
		case SQL_single:
			single = where->d.single;
			/* Check left pair items */
			item = single->d.pair.left->item;
			if (item->type == SQL_name) {
				for (walk = g_list_first (item->d.name); walk != NULL;
				     walk = walk->next) {
					if (strcasecmp ((gchar *) walk->data, lookfor) == 0)
						return where;
				}
			}
			/* Check right pair items */
			item = single->d.pair.right->item;
			if (item->type == SQL_name) {
				for (walk = g_list_first (item->d.name); walk != NULL;
				     walk = walk->next) {
					if (strcasecmp ((gchar *) walk->data, lookfor) == 0)
						return where;
				}
			}
			/* incase its wierd between case */
			if (single->op == SQL_between) {
				item = single->d.between.upper->item;
				if (item->type == SQL_name) {
					for (walk = g_list_first (item->d.name); walk != NULL;
					     walk = walk->next) {
						if (strcasecmp ((gchar *) walk->data, lookfor) == 0)
							return where;
					}
				}
			}
			break;
		case SQL_negated:
			return sql_statement_searchwhere_rec (where, lookfor);
		case SQL_pair:
			retwalk = sql_statement_searchwhere_rec (where->d.pair.left, lookfor);
			if (retwalk)
				return retwalk;
			return sql_statement_searchwhere_rec (where->d.pair.right, lookfor);
			break;
		}
	}
	return NULL;
}

static gint
sql_statement_get_where_ontable (sql_where * where, gchar * ontable,
				 GList ** leftfield, GList ** rightfield,
				 sql_condition_operator * condopr)
{
	sql_condition *single;
	sql_field_item *item;

	single = where->d.single;
	item = single->d.pair.left->item;
	if (item->type == SQL_name)
		*leftfield = item->d.name;

	item = single->d.pair.right->item;
	if (item->type == SQL_name)
		*leftfield = item->d.name;

	if (*leftfield && *rightfield) {
		*condopr = single->op;
		return 0;
	}

	*leftfield = NULL;
	*rightfield = NULL;
	return -1;
}

gint
sql_statement_get_wherejoin_ontable (sql_statement * statement,
				     gchar * ontable, GList ** leftfield,
				     GList ** rightfield,
				     sql_condition_operator * condopr)
{
	sql_where *where;
	sql_select_statement *select;

	g_assert (statement);
	*leftfield = NULL;
	*rightfield = NULL;
	*condopr = 0;

	if (statement->type != SQL_select) {
		fprintf (stderr, "Invalid statement type: %d", statement->type);
		return -1;
	}
	select = statement->statement;
	/* Check for a where statement */
	where = sql_statement_searchwhere_rec (select->where, ontable);
	if (where) {
		if (where->type == SQL_single)
			if (!sql_statement_get_where_ontable
			    (where, ontable, leftfield, rightfield, condopr))
				return 0;
	}


	/* Check for a join */


	return -1;
}
