/*
 * 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 <libpq-fe.h>

//#include <internal/postgres_fe.h>
// Replaces postgres_fe.h until it gets fixed
#ifndef FRONTEND
#define FRONTEND 1
#endif

#include <internal/c.h>
// end of the "fix"

//#include <catalog/pg_type.h>
// catalog/pg_type.h has been removed from Debian, this fixes its lack
#define BOOLOID			16
#define INT8OID			20
#define INT2OID			21
#define INT4OID			23
#define TIDOID			27
#define OIDOID			26
#define FLOAT4OID		700
#define FLOAT8OID		701
#define NUMERICOID		1700
#define DATEOID			1082
#define TIMESTAMPOID	1114
#define TIMESTAMPTZOID	1184
#define TIMEOID			1083
#define TIMETZOID		1266
#define BYTEAOID		17
#define ABSTIMEOID		702
#define RELTIMEOID		703
#define TINTERVALOID	704
#define CHAROID			18
#define TEXTOID			25
#define NAMEOID			19
#define BPCHAROID		1042
#define VARCHAROID		1043
// end of the "fix"

// Macros to avoid redefinition warnings for constants
#undef PACKAGE_BUGREPORT
#undef PACKAGE_NAME
#undef PACKAGE_STRING
#undef PACKAGE_TARNAME
#undef PACKAGE_VERSION
#undef _

#include <stdlib.h>
#include <string.h>
#include <db/db-row.h>
#include "db-pg.h"

G_DEFINE_TYPE (DbPg, db_pg, DB_TYPE_PLUGIN);

static SqlMultiStmt * info_query_stmt;

//+++++++++++++++++++++++++++++++++++++++++++++++++++ Private

static void db_pg_close (DbPlugin * obj)
{
	DbPg * pg = DB_PG (obj);

	if (pg->pg_cancel)
	{
		PQfreeCancel (pg->pg_cancel);
		pg->pg_cancel = NULL;
	}

	if (pg->conn)
	{
		PQfinish (pg->conn);
		pg->conn = NULL;
		pg->socket = -1;
	}
}

static gboolean db_pg_open (DbPlugin * obj, const gchar * host,
	const gchar * schema, const gchar * user, const gchar * pass, GError ** err)
{
	DbPg * pg = DB_PG (obj);

	pg->conn = PQsetdbLogin (
		 host
		,NULL
		,NULL
		,NULL
		,schema
		,user
		,pass
	);

	if (!pg->conn)
	{
		g_set_error (err
			,DB_CONN_LOG_DOMAIN
			,DB_CONN_ERROR_OPENING
			,_("Can't allocate the needed memory")
		);
		return FALSE;
	}

	if (PQstatus (pg->conn) != CONNECTION_OK)
	{
		g_set_error (err
			,DB_CONN_LOG_DOMAIN
			,DB_CONN_ERROR_OPENING
			,"%s", PQerrorMessage (pg->conn)
		);
		db_pg_close (obj);
		return FALSE;
	}

	pg->pg_cancel = PQgetCancel (pg->conn);
	pg->socket = PQsocket (pg->conn);
	return TRUE;
}

static void db_pg_set_ssl (DbPg * obj, const gchar * ca)
{
	g_message ("DbPg: SSL not supported by this plugin");
}

//+++++++ Internal Methods for db_pg_query ()

static GType db_pg_get_g_type (Oid type)
{
	switch (type)
	{
		case BOOLOID:
			return G_TYPE_BOOLEAN;
		case INT8OID:
			return G_TYPE_INT64;
		case INT2OID:
		case INT4OID:
			return G_TYPE_INT;
		case TIDOID:
		case OIDOID:
			return G_TYPE_UINT;
		case FLOAT4OID:
			return G_TYPE_FLOAT;
		case FLOAT8OID:
		case NUMERICOID:
			return G_TYPE_DOUBLE;
		case DATEOID:
			return G_TYPE_DATE;
		case TIMESTAMPOID:
		case TIMESTAMPTZOID:
			return G_TYPE_DATE_TIME;
		case TIMEOID:
		case TIMETZOID:
			return GVN_TYPE_TIME;
		case BYTEAOID:
			return G_TYPE_BYTES;
		case ABSTIMEOID:
		case RELTIMEOID:
		case TINTERVALOID:
		case CHAROID:
		case TEXTOID:
		case NAMEOID:
		case BPCHAROID:
		case VARCHAROID:
		default:
			return G_TYPE_STRING;
	}
}

static void db_pg_set_date_value (GValue * val, Oid oid, gchar * pg_val)
{
	gpointer date = NULL;

	switch(oid)
	{
		case TIMEOID: // hh:mm:ss
		{
			gchar ** hms = g_strsplit (pg_val, ":", 3);

			if (g_strv_length (hms) >= 3)
				date = gvn_time_new (atoi (hms[0]), atoi (hms[1]),
					atoi (hms[2]), 0);

			g_value_init (val, GVN_TYPE_TIME);

			g_strfreev (hms);
			break;
		}
		case TIMETZOID: // hh:mm:ss±zz
		{
			gchar ** hmsz = g_strsplit_set (pg_val, ":+-", 4);

			if (g_strv_length (hmsz) >= 4)
				date = gvn_time_new (atoi (hmsz[0]), atoi(hmsz[1]), 
					atoi(hmsz[2]), atoi (hmsz[3]));

			g_value_init (val, GVN_TYPE_TIME);

			g_strfreev (hmsz);
			break;
		}
		case TIMESTAMPOID: // yyyy-mm-dd hh:mn:ss
		{
			gchar ** dt = g_strsplit_set (pg_val, " -:", 6);

			if (g_strv_length (dt) >= 6)
				date = g_date_time_new_local
					(atoi (dt[0]), atoi (dt[1]), atoi (dt[2]),
					atoi (dt[3]), atoi (dt[4]), atoi (dt[5]));

			g_value_init (val, G_TYPE_DATE_TIME);

			g_strfreev (dt);
			break;
		}
		case TIMESTAMPTZOID: // yyyy-mm-dd hh:mm:ss±zz
		{
			GTimeZone * tz = NULL;
			gchar ** dtz = g_strsplit_set (pg_val, " -:+", 7);

			if (strlen (pg_val) >= 16 && g_strv_length (dtz) >= 6)
				date = g_date_time_new
					(tz = g_time_zone_new (pg_val + 16),
					atoi (dtz[0]), atoi (dtz[1]), atoi (dtz[2]),
					atoi (dtz[3]), atoi (dtz[4]), atoi (dtz[5]));

			g_value_init (val, G_TYPE_DATE_TIME);

			g_strfreev (dtz);
			g_time_zone_unref (tz);
			break;
		}
		case DATEOID: // yyyy-mm-dd
		{
			gchar ** ymd = g_strsplit (pg_val, "-", 3);

			if (g_strv_length (ymd) >= 3)
				date = g_date_new_dmy
					(atoi (ymd[2]), atoi (ymd[1]) ,atoi (ymd[0]));

			g_value_init (val, G_TYPE_DATE);

			g_strfreev (ymd);
			break;
		}
		case TINTERVALOID:
			// TODO If needed
			break;
	}

	g_value_take_boxed (val, date);
}

static void db_pg_set_g_value (GValue * val, GType type, gchar * pg_val)
{
	val = g_value_init (val, type);

	switch (type)
	{
		case G_TYPE_BOOLEAN:
			if(!g_strcmp0(pg_val, "t"))
				g_value_set_boolean (val, TRUE);
			else
				g_value_set_boolean (val, FALSE);
			break;
		case G_TYPE_UINT:
			g_value_set_uint(val, (guint) g_ascii_strtoull (pg_val, NULL, 10));
			break;
		case G_TYPE_INT:
			g_value_set_int (val, atoi (pg_val));
			break;
		case G_TYPE_INT64:
			g_value_set_int64 (val, g_ascii_strtoll (pg_val, NULL, 10));
			break;
		case G_TYPE_FLOAT:
		case G_TYPE_DOUBLE:
			g_value_set_double (val, g_strtod (pg_val, NULL));
			break;
		case G_TYPE_STRING:
			g_value_set_string (val, pg_val);
			break;
		default:
		if (type == G_TYPE_BYTES)
		{
			gsize l;
			guchar * esc = PQunescapeBytea ((guchar *) pg_val, &l);
			GBytes * bin = g_bytes_new (esc, l);
			g_free (esc);
			g_value_set_boxed (val, bin);
			g_bytes_unref (bin);
		}
	}
}

static inline gint added (GSList * list, const gchar * target)
{
	gint r = 0;
	GSList * n;

	for (n = list; n; n = n->next, r++)
		if (!g_strcmp0 ((gchar *) n->data, target))
			return r;

	return -1;
}

static inline void free_array (const gpointer array)
{
	g_ptr_array_free (array, TRUE);
}

static DbResultSet * __db_pg_query
	(DbPlugin * obj, const gchar * sql, gboolean columns, GError ** err)
{
	DbPg * pg = DB_PG (obj);

	PGresult * res = NULL;
	DbResult * result;
	DbResultSet * set;
	gint i = 0, j = 0;
	gint ntup = 0, ncol = 0;
	guint error_code = 0;
	guint query_count = 0;
	guint sel_count = 0;
	GSList * list = NULL;
	gboolean failed = FALSE;
	guint s = 5;
	GPtrArray * ind_select = g_ptr_array_sized_new (s); 
	GPtrArray * types = g_ptr_array_new_full (s, (GDestroyNotify) g_free);
	GPtrArray * oid_types = g_ptr_array_new_full (s, (GDestroyNotify) g_free);
	GPtrArray * rel_oid = g_ptr_array_new_full (s, (GDestroyNotify) free_array);
	GPtrArray * col = g_ptr_array_new_full (s,(GDestroyNotify) free_array);
	GPtrArray * name = g_ptr_array_new_full (s,(GDestroyNotify) free_array);
	
	set = db_result_set_new ();

	if (PQsendQuery (pg->conn, sql) != 1)
	{
		g_set_error (err
			,DB_CONN_LOG_DOMAIN
			,DB_CONN_ERROR_QUERY_FAILED
			,"%s", PQerrorMessage (pg->conn)
		);
		failed = TRUE;
	}
	else while ((res = PQgetResult (pg->conn)))
	{
		result = g_new (DbResult, 1);
		result->data = NULL;
		result->column = NULL;
		result->nrows = 0;
		result->ncols = 0;

		switch (PQresultStatus (res))
		{
			case PGRES_COMMAND_OK:
			case PGRES_SINGLE_TUPLE:
			case PGRES_TUPLES_OK:
			{
				gchar ** q_t = NULL;

				q_t = g_strsplit (PQcmdStatus (res), " ", G_MAXINT);

				if (!g_strcmp0 (q_t[0],"SELECT"))
				{
					ntup = PQntuples (res);
					result->nrows = ntup;
					ncol = PQnfields (res);
					result->ncols = ncol;

					gchar * pg_val;
					GPtrArray * row = g_ptr_array_new_full
						((guint) ntup + 15, (GDestroyNotify) db_row_free);
					GPtrArray * oid = g_ptr_array_sized_new ((guint) ncol);
					GPtrArray * col_num = g_ptr_array_sized_new ((guint) ncol);
					GType * type = g_new (GType, ncol);
					Oid * type_oid = g_new (Oid, ncol);
					GPtrArray * col_name = g_ptr_array_new_full ((guint) ncol, (GDestroyNotify) g_free);

					sel_count++;

					for (j = 0; j < ncol; j++)
					{
						type_oid[j] = PQftype (res, j);
						type[j] = db_pg_get_g_type (type_oid[j]);

						g_ptr_array_add (col_num, GINT_TO_POINTER (PQftablecol (res, j)));
						g_ptr_array_add (oid, GUINT_TO_POINTER (PQftable (res, j)));
						g_ptr_array_add (col_name, g_strdup (PQfname (res, j)));
					}

					if (ntup > 0)
						for (i = 0; i < ntup; i++)
						{
							DbRow * r = db_row_new (result->ncols, i);

							for (j = 0; j < ncol; j++)
							{
								if (!PQgetisnull (res, i, j))
								{
									pg_val = PQgetvalue (res, i, j);

									if (type[j] == G_TYPE_DATE
									|| type[j] == G_TYPE_DATE_TIME
									|| type[j] == GVN_TYPE_TIME)
										db_pg_set_date_value (&(r->value)[j],
											type_oid[j], pg_val);
									else
										db_pg_set_g_value (&(r->value)[j], type[j], pg_val);
								}
								else // IF NULL: GvnNull type
									g_value_init (&(r->value[j]), GVN_TYPE_NULL);
							}

							g_ptr_array_add (row, r);
						}

					result->data = row;

					g_ptr_array_add (rel_oid, oid);
					g_ptr_array_add (col, col_num);
					g_ptr_array_add (name, col_name);
					g_ptr_array_add (types, type);
					g_ptr_array_add (oid_types, type_oid);
					g_ptr_array_add (ind_select, GUINT_TO_POINTER (query_count));
				}

				if (!g_strcmp0 (q_t[0], "INSERT")
				|| !g_strcmp0 (q_t[0], "UPDATE")
				|| !g_strcmp0 (q_t[0], "DELETE"))
					result->nrows = atoi (PQcmdTuples (res));

				g_strfreev (q_t);
				break;
			}
			case PGRES_BAD_RESPONSE:
				error_code = DB_CONN_ERROR_BAD_RESPONSE;
				break;
			case PGRES_EMPTY_QUERY:
				error_code = DB_CONN_ERROR_QUERY_EMPTY;
				break;
			case PGRES_COPY_OUT:
			case PGRES_COPY_IN:
			case PGRES_COPY_BOTH:
			case PGRES_NONFATAL_ERROR:
				error_code = DB_CONN_ERROR_QUERY_NONFATAL;
				break;
			case PGRES_FATAL_ERROR:
				error_code = DB_CONN_ERROR_QUERY_FATAL;
				break;
		} // switch STATUS

		if (error_code && !failed)
		{
			PGresult * discarded_res;
		
			while ((discarded_res = PQgetResult (pg->conn)))
				PQclear (discarded_res);

			g_set_error (err
				,DB_CONN_LOG_DOMAIN
				,error_code
				,"%s", PQresultErrorMessage (res)
			);
			failed = TRUE;
		}

		query_count++;
		list = g_slist_prepend (list, result);

		PQclear (res);
	} // while (res) END

	if (err && *err && PQstatus (pg->conn) != CONNECTION_OK)
		(*err)->code = DB_CONN_ERROR_LOST;

	list = g_slist_reverse (list);

	if (!failed && sel_count > 0 && columns)
	{
		gint k;
		SqlBatch * batch = sql_batch_new ();
		gchar * info_query = g_new0 (gchar, 1);

		for (i = 0; i < (gint) sel_count; i++)
		{
			gchar * buff;
			GHashTable * added_tables = g_hash_table_new (g_int_hash, g_int_equal);
			SqlList * fields_list, * tables_list;
			SqlObject * fields_set = sql_set_new (),
				* tables_set = sql_set_new ();

			GPtrArray * oid = g_ptr_array_index (rel_oid, i),
				* col_num = g_ptr_array_index (col, i);
			gint ncolumns = (gint) oid->len;

			fields_list = sql_list_new (SQL_TYPE_EXPR);
			sql_object_set (fields_set, "exprs", SQL_OBJECT (fields_list));

			tables_list = sql_list_new (SQL_TYPE_EXPR);
			sql_object_set (tables_set, "exprs", SQL_OBJECT (tables_list));

			for (j = 0; j < ncolumns; j++)
			{
				SqlList * list;
				Oid table_oid = GPOINTER_TO_UINT (g_ptr_array_index (oid, j));
				guint column = GPOINTER_TO_INT (g_ptr_array_index (col_num, j));
				SqlObject * tab, * col, * set = sql_set_new ();

				GValue tab_val = G_VALUE_INIT;
				g_value_set_uint (g_value_init (&tab_val, G_TYPE_UINT), table_oid);
				tab = sql_value_new_with_value (&tab_val);
				g_value_unset (&tab_val);

				GValue col_val = G_VALUE_INIT;
				g_value_set_uint (g_value_init (&col_val, G_TYPE_UINT), column);
				col = sql_value_new_with_value (&col_val);
				g_value_unset (&col_val);

				list = sql_list_new_with_items (SQL_TYPE_EXPR, tab, col, NULL);
				sql_object_set (set, "exprs", SQL_OBJECT (list));

				sql_list_add (fields_list, set);

				if (g_hash_table_insert (added_tables, &table_oid, NULL))
					sql_list_add (tables_list, tab);
			}

			sql_batch_add (batch, "fields", fields_set);
			sql_batch_add (batch, "tables", tables_set);
			buff = info_query;
			info_query = g_strconcat (buff,
				db_plugin_render (DB_PLUGIN (obj), info_query_stmt, batch, NULL),
				";", NULL);
			g_free (buff);

			g_hash_table_destroy (added_tables);
		}

		PGresult * res_col;
		PQsendQuery (pg->conn, info_query);

		DbResult * r = NULL;
		GValue * def = NULL;
		Oid * tab_oid = NULL;
		gboolean * nullable = NULL;
		guint * col_n = NULL;
		i = 0;

		while ((res_col = PQgetResult (pg->conn)) && !failed)
		{
			if (PQresultStatus (res_col) != PGRES_TUPLES_OK)
			{
				g_set_error (err
					,DB_CONN_LOG_DOMAIN
					,DB_CONN_ERROR_QUERY_FAILED
					,"%s", PQresultErrorMessage (res_col)
				);
				failed = TRUE;
			}
			else
			{
				guint ind = i/2;
				ntup = PQntuples (res_col);
				guint x = i%2;

				if (!x)
				{
					GPtrArray * col_iter = g_ptr_array_index (col, ind),
						* rel_iter = g_ptr_array_index (rel_oid, ind),
						* name_array;

					r = g_slist_nth_data
						(list, GPOINTER_TO_UINT (g_ptr_array_index (ind_select, ind)));
					ncol = r->ncols;
					r->column = g_new (DbColumn, ncol);
					// Relates each column with its corresponding row in the aux query
					gint col_tup[ncol];					
					// Array of the default values for the columns
					def = g_new0 (GValue, ncol);
					// Array of GvnParamSpecFlags for the columns
					nullable = g_new (gboolean, ncol);
					tab_oid = g_new (Oid, ncol);
					col_n = g_new (guint, ncol);

					for (j = 0; j < ncol; j++)
					{
						r->column[j].spec = NULL;
						r->column[j].info = 0;
						r->column[j].name = NULL;
						r->column[j].table = NULL;
						r->column[j].table_alias = NULL;
						r->column[j].schema = NULL;

						if (GPOINTER_TO_INT (g_ptr_array_index (col_iter, j)) == 0)
						{
							gchar * fname;

							name_array = g_ptr_array_index (name, ind);
							fname = g_strdup (g_ptr_array_index (name_array, j));

							// Set the metadata if it is a *CALCULATED FIELD*
							col_tup[j] = -1;

							if (fname && fname[0] == '?')
							{
								r->column[j].name = g_strdup ("");
								g_free (fname);
							}
							else
								r->column[j].name = fname;

							r->column[j].alias = g_strdup (r->column[j].name);
							r->column[j].table = g_strdup ("");
							r->column[j].table_alias = g_strdup ("");
							r->column[j].spec = gvn_param_spec_new_with_attrs
								(((GType*) g_ptr_array_index (types, ind))[j]
									, FALSE, FALSE, NULL);
						}
						else
							for (k = 0; k < ntup; k++)
							if (GPOINTER_TO_INT (g_ptr_array_index (col_iter, j))
								== atoi (PQgetvalue (res_col, k, 4))
							&& GPOINTER_TO_INT (g_ptr_array_index (rel_iter, j))
								== atoi (PQgetvalue (res_col, k, 1)))
							{
								col_tup[j] = k;
								break;
							}

						if (col_tup[j] >= 0) // NOT a calculated field.
						{
							gint ctup = col_tup[j];
							GType type = ((GType*) g_ptr_array_index (types, ind))[j];
							Oid oid = ((Oid*) g_ptr_array_index (oid_types, ind))[j];
							gchar * fname = PQgetvalue (res_col, ctup, 0);
							gchar * fdisp;

							name_array = g_ptr_array_index (name, ind);
							fdisp = g_ptr_array_index (name_array, j);

							r->column[j].name = g_strdup (fname);
							r->column[j].alias = g_strdup (fdisp);

							// Getting the default value from res_col //FIXME use the parser
							if (!PQgetisnull (res_col, ctup, 3))
							{
								gchar * pg_val = PQgetvalue (res_col, ctup, 3);

								if (type == G_TYPE_STRING || type == G_TYPE_CHAR
								|| type == G_TYPE_BYTES || type == G_TYPE_DATE
								|| type == G_TYPE_DATE_TIME || type == GVN_TYPE_TIME)
								{
									gchar ** split = NULL;

									if (type == G_TYPE_DATE
									|| type == G_TYPE_DATE_TIME
									|| type == GVN_TYPE_TIME)
										db_pg_set_date_value (&def[j], oid
											,(split = g_strsplit
												(pg_val, "'", G_MAXINT))[1]);
									else
										db_pg_set_g_value (&def[j], type
											,(split = g_strsplit
												(pg_val, "'", G_MAXINT))[1]);

									g_strfreev (split);
								}
								else
								{
									if (g_str_has_prefix (pg_val, "nextval"))
									{// Serial fields
										GValue * v = g_new0 (GValue, 1);
										gchar ** split = g_strsplit_set (pg_val, "(':)", G_MAXINT8);
										SqlObject * function = sql_function_new ("currval", NULL);
										SqlList * params = sql_list_new (SQL_TYPE_EXPR);
										g_value_set_string (g_value_init (v, G_TYPE_STRING), split[2]);
										sql_list_add (params, sql_value_new_with_value (v));
										g_object_set (function, "params", params, NULL);
										g_value_unset (v);
										g_free (v);
										g_value_take_object (g_value_init (&def[j], SQL_TYPE_FUNCTION),
											g_object_ref_sink (function));
										g_strfreev (split);
									}
									else
										db_pg_set_g_value (&def[j], type, pg_val);
								}
							}
							else
								g_value_init (&def[j], GVN_TYPE_NULL);

							// Checking whether the column can be NULL
							nullable[j] = !g_strcmp0 (PQgetvalue (res_col, ctup, 2),"t")
								? FALSE : TRUE;

							tab_oid[j] = atoi (PQgetvalue (res_col, ctup, 1));
							r->column[j].info = 0;
							col_n[j] = atoi (PQgetvalue (res_col, ctup, 4));
						}
					}
				}
				else
				{
					guint l, nkeys, nedit = 0;
					gchar ** pkey = NULL;
					GSList * prev_tables = NULL;
					struct
					{
						gchar * name;
						guint keys[ncol];
						guint num;
						guint total;
					}
					edit[ntup];

					for (j = 0; j < ncol; j++)
					{
						GType type;

						for (k = 0; k < ntup; k++)
						if (tab_oid[j] == atoi (PQgetvalue (res_col, k, 0)))
						{
							guint n;

							if (!r->column[j].table)
							{
								r->column[j].table =
									g_strdup (PQgetvalue (res_col, k, 1));
								r->column[j].table_alias =
									g_strdup (r->column[j].table);
							}

							g_strfreev (pkey);
							pkey = g_strsplit (PQgetvalue (res_col, k, 2), " ", G_MAXINT);
							nkeys = g_strv_length (pkey);

							for (n = 0; n < nkeys; n++)
							if (atoi (pkey[n]) == col_n[j])
							{
								gint pos = added (prev_tables,
									r->column[j].table);

								if (pos >= 0)
								{
									edit[pos].keys[edit[pos].num] = j;
									edit[pos].num++;
								}
								else
								{
									edit[nedit].name = r->column[j].table;
									edit[nedit].keys[0] = j;
									edit[nedit].num = 1;
									edit[nedit].total = nkeys;
									nedit++;
									prev_tables = g_slist_append
										(prev_tables, r->column[j].table);
								}
							}
						}

						type = ((GType *) g_ptr_array_index (types, ind))[j];

						if (!r->column[j].spec)
							r->column[j].spec = gvn_param_spec_new_with_attrs
								(type, FALSE, nullable[j], &def[j]);

						if (G_IS_VALUE (&def[j]))
							g_value_unset (&def[j]);
					}

					for (j = 0; j < ncol; j++)
						for (k = 0; k < nedit; k++)
							if (edit[k].num == edit[k].total
							&& !g_strcmp0 (r->column[j].table, edit[k].name))
								for (l = 0; l < edit[k].num; l++)
									r->column[edit[k].keys[l]].info |= DB_COLUMN_PRI_KEY;

					g_free (nullable);
					g_free (def);
					g_free (col_n);
					g_free (tab_oid);
					g_strfreev (pkey);
					g_slist_free (prev_tables);
				}
			}

			i++;
			PQclear (res_col);
		}

		g_free (info_query);

		PQclear (res_col);
	}

	free_array (ind_select);
	free_array (types);
	free_array (oid_types);
	free_array (rel_oid);
	free_array (name);
	free_array (col);

	set->results = list;

	if (failed)
	{
		db_result_set_free (set);
		return NULL;
	}

	return set;
}

static DbResultSet * db_pg_query (DbPlugin * obj, const gchar * sql, GError ** err)
{
	return __db_pg_query (obj, sql, TRUE, err);
}

static void db_pg_kill_query (DbPg * obj)
{
	if (obj->conn)
	{
		gboolean killed = FALSE;

		if (obj->pg_cancel)
		{
			gchar errbuf[256] = "";

			if (PQcancel (obj->pg_cancel, errbuf, 256) == 1)
				killed = TRUE;
			else
				g_warning ("DbPg: %s", errbuf);
		}

		if (!killed && obj->socket != -1 && !shutdown (obj->socket, SHUT_RDWR))
			obj->socket = -1;
	}
}

static void db_pg_value_render (SqlValue * obj, SqlRender * render, SqlBatch * batch)
{
	if (G_VALUE_TYPE (obj->value) == G_TYPE_BYTES)
	{
		gsize len;
		gchar * buffer;
		GBytes * bin = g_value_get_boxed (obj->value);
		buffer = (gchar *) PQescapeByteaConn (
			 DB_PG (render->data)->conn
			,g_bytes_get_data (bin, NULL)
			,g_bytes_get_size (bin)
			,&len
		);
		sql_render_printf (render, "E'%s'::bytea", buffer);
		PQfreemem (buffer);
	}
	else
		sql_object_render (SQL_OBJECT (obj), render, batch);
}

/*
 * Called from the class initializer to set the file global variable
 * 'info_query_stmt' which will be used to obtain information about the
 * requested tables and fields in the db_pg_query method.
 */
static void db_pg_init_info_stmt ()
{
	SqlObject * select;
	SqlList * list;
	SqlSelectField * select_field;
	SqlJoin * join;
	SqlTarget * target;
	SqlObject * and, * equal, * expr, * set,
		* fields_holder, * tables_holder,
		* oid_field = sql_field_new_with_target ("oid", "c", NULL),
		* attrelid_field = sql_field_new ("attrelid"),
		* attnum_field = sql_field_new ("attnum");

	SqlList * stmts = sql_list_new (SQL_TYPE_STMT);
	info_query_stmt = g_object_new (SQL_TYPE_MULTI_STMT, "stmts", stmts, NULL);

	// Fields for the first SELECT

	select = sql_select_new ();
	sql_list_add (stmts, select);

	list = sql_list_new (SQL_TYPE_SELECT_FIELD);

	select_field = g_object_new (SQL_TYPE_SELECT_FIELD,
		"expr", sql_field_new ("attname"), NULL);
	sql_list_add (list, select_field);
	select_field = g_object_new (SQL_TYPE_SELECT_FIELD,
		"expr", attrelid_field, NULL);
	sql_list_add (list, select_field);
	select_field = g_object_new (SQL_TYPE_SELECT_FIELD,
		"expr", sql_field_new ("attnotnull"), NULL);
	sql_list_add (list, select_field);
	select_field = g_object_new (SQL_TYPE_SELECT_FIELD,
		"expr", sql_field_new ("adsrc"), NULL);
	sql_list_add (list, select_field);
	select_field = g_object_new (SQL_TYPE_SELECT_FIELD,
		"expr", attnum_field, NULL);
	sql_list_add (list, select_field);

	sql_object_set (select, "fields", SQL_OBJECT (list));

	// Target for the first SELECT

	target = SQL_TARGET (sql_table_new ("pg_class", NULL));
	sql_target_set_alias (target, "c");
	join = SQL_JOIN (sql_join_new
		(SQL_TARGET (sql_table_new ("pg_attribute", NULL)),
		target,
		SQL_JOIN_TYPE_INNER));

	equal = sql_operation_new (SQL_OPERATION_TYPE_EQUAL);
	list = sql_list_new_with_items (SQL_TYPE_EXPR,
		oid_field,
		attrelid_field,
		NULL);
	sql_operation_set_operands (SQL_OPERATION (equal), list);

	sql_join_set_condition (join, SQL_EXPR (equal));

	target = SQL_TARGET (sql_table_new ("pg_attrdef", NULL));
	sql_target_set_alias (target, "a");
	join = SQL_JOIN (sql_join_new
		(SQL_TARGET (join),
		target,
		SQL_JOIN_TYPE_LEFT));

	equal = sql_operation_new (SQL_OPERATION_TYPE_EQUAL);
	list = sql_list_new_with_items (SQL_TYPE_EXPR,
		sql_field_new_with_target ("adrelid", "a", NULL),
		attrelid_field,
		NULL);
	sql_operation_set_operands (SQL_OPERATION (equal), list);

	expr = sql_operation_new (SQL_OPERATION_TYPE_EQUAL);
	list = sql_list_new_with_items (SQL_TYPE_EXPR,
		sql_field_new_with_target ("adnum", "a", NULL),
		attnum_field,
		NULL);
	sql_operation_set_operands (SQL_OPERATION (expr), list);

	and = sql_operation_new (SQL_OPERATION_TYPE_AND);
	list = sql_list_new_with_items (SQL_TYPE_EXPR, equal, expr, NULL);
	sql_operation_set_operands (SQL_OPERATION (and), list);

	sql_join_set_condition (join, SQL_EXPR (and));
	list = sql_list_new_with_items (SQL_TYPE_TARGET, join, NULL);

	sql_object_set (select, "targets", SQL_OBJECT (list));

	// WHERE clause for the first SELECT

	equal = sql_operation_new (SQL_OPERATION_TYPE_EQUAL);
	GValue val = G_VALUE_INIT;
	g_value_set_boolean (g_value_init (&val, G_TYPE_BOOLEAN), FALSE);
	list = sql_list_new_with_items (SQL_TYPE_EXPR,
		sql_field_new ("attisdropped"),
		sql_value_new_with_value (&val),
		NULL);
	sql_operation_set_operands (SQL_OPERATION (equal), list);
	g_value_unset (&val);

	expr = sql_operation_new (SQL_OPERATION_TYPE_IN);

	set = sql_set_new ();
	list = sql_list_new_with_items (SQL_TYPE_EXPR,
		oid_field,
		attnum_field,
		NULL);
	sql_object_set (set, "exprs", SQL_OBJECT (list));
	fields_holder = sql_holder_new ("fields");

	list = sql_list_new_with_items (SQL_TYPE_EXPR,
		set,
		fields_holder,
		NULL);
	sql_operation_set_operands (SQL_OPERATION (expr), list);

	and = sql_operation_new (SQL_OPERATION_TYPE_AND);
	list = sql_list_new_with_items (SQL_TYPE_EXPR, equal, expr, NULL);
	sql_operation_set_operands (SQL_OPERATION (and), list);

	sql_dml_set_where (SQL_DML (select), and);

	// Fields for the second SELECT

	select = sql_select_new ();
	sql_list_add (stmts, select);

	list = sql_list_new (SQL_TYPE_SELECT_FIELD);

	select_field = g_object_new (SQL_TYPE_SELECT_FIELD,
		"expr", oid_field, NULL);
	sql_list_add (list, select_field);
	select_field = g_object_new (SQL_TYPE_SELECT_FIELD,
		"expr", sql_field_new ("relname"), NULL);
	sql_list_add (list, select_field);
	select_field = g_object_new (SQL_TYPE_SELECT_FIELD,
		"expr", sql_field_new ("indkey"), NULL);
	sql_list_add (list, select_field);

	sql_object_set (select, "fields", SQL_OBJECT (list));

	// Target for the second SELECT

	join = SQL_JOIN (sql_join_new (NULL, NULL, SQL_JOIN_TYPE_LEFT));
	target = SQL_TARGET (sql_table_new ("pg_class", NULL));
	sql_target_set_alias (target, "c");
	sql_join_set_target_left (join, target);
	target = SQL_TARGET (sql_table_new ("pg_index", NULL));
	sql_target_set_alias (target, "i");
	sql_join_set_target_right (join, target);

	equal = sql_operation_new (SQL_OPERATION_TYPE_EQUAL);
	list = sql_list_new_with_items (SQL_TYPE_EXPR,
		sql_field_new ("indrelid"),
		oid_field,
		NULL);
	sql_operation_set_operands (SQL_OPERATION (equal), list);

	and = sql_operation_new (SQL_OPERATION_TYPE_AND);
	list = sql_list_new_with_items (SQL_TYPE_EXPR,
		equal,
		sql_field_new ("indisprimary"),
		NULL);
	sql_operation_set_operands (SQL_OPERATION (and), list);

	sql_join_set_condition (join, SQL_EXPR (and));
	list = sql_list_new_with_items (SQL_TYPE_TARGET, join, NULL);

	sql_object_set (select, "targets", SQL_OBJECT (list));

	// WHERE clause for the first SELECT

	expr = sql_operation_new (SQL_OPERATION_TYPE_IN);
	tables_holder = sql_holder_new ("tables");
	list = sql_list_new_with_items (SQL_TYPE_EXPR,
		oid_field,
		tables_holder,
		NULL);
	sql_operation_set_operands (SQL_OPERATION (expr), list);

	sql_dml_set_where (SQL_DML (select), expr);
}

//+++++++++++++++++++++++++++++++++++++++++++++++++++ Class

static void db_pg_init (DbPg * obj)
{
	SqlRender * render = sql_render_new ('"');
	sql_render_register_function (render, SQL_TYPE_VALUE,
		(SqlRenderFunc) db_pg_value_render);

	DB_PLUGIN (obj)->render = render;
	obj->conn = NULL;
	obj->socket = -1;
}

static void db_pg_class_init (DbPgClass * k)
{
	DbPluginClass * klass = DB_PLUGIN_CLASS (k);
	klass->open = db_pg_open;
	klass->close = db_pg_close;
	klass->set_ssl = (DbPluginSetSSL) db_pg_set_ssl;
	klass->query = db_pg_query;
	klass->kill_query = (DbPluginKillQueryFunc) db_pg_kill_query;
	
	db_pg_init_info_stmt ();
}