/* * 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 . */ #include #include #include // Macros to avoid redefinition warnings for constants #undef PACKAGE_BUGREPORT #undef PACKAGE_NAME #undef PACKAGE_STRING #undef PACKAGE_TARNAME #undef PACKAGE_VERSION #undef _ #include #include #include #include "db-pg.h" G_DEFINE_TYPE (DbPg, db_pg, DB_TYPE_PLUGIN); //+++++++++++++++++++++++++++++++++++++++++++++++++++ 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_warning ("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 XMLOID: case CSTRINGOID: 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_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; Oid t_oid; GPtrArray * col_num = NULL; GPtrArray * oid = NULL; gchar * info_query = g_strdup (""); gchar * pkey_query; gchar * pkey_buff = NULL // buffers for memory leak avoidance ,* info_buff = NULL; // Set info_query string using the previously stored table OIDs and column numbers for (i = 0; i < (gint) sel_count; i++) { pkey_query = g_strdup (""); oid = g_ptr_array_index (rel_oid, i); col_num = g_ptr_array_index (col, i); info_buff = info_query; info_query = g_strconcat (info_query, "SELECT attname, attrelid, attnotnull, adsrc, attnum " "FROM pg_attribute " "INNER JOIN pg_class c ON c.oid = attrelid " "LEFT JOIN pg_attrdef a " "ON a.adrelid = attrelid AND a.adnum = attnum " "WHERE attisdropped = false AND (c.oid, attnum) IN (" , NULL); g_free (info_buff); pkey_buff = pkey_query; pkey_query = g_strconcat (pkey_query, "SELECT c.oid, relname, indkey " "FROM pg_class c " "LEFT JOIN pg_index i ON indrelid = c.oid " "AND indisprimary " "WHERE c.oid IN (" , NULL); g_free (pkey_buff); gint ncolumns = (gint) oid->len; gchar * buff_j = NULL; for (j = 0; j < ncolumns; j++) { t_oid = GPOINTER_TO_UINT (g_ptr_array_index (oid, j)); buff_j = g_strdup_printf ("%d", t_oid); pkey_buff = pkey_query; pkey_query = g_strconcat (pkey_query, buff_j, NULL); g_free (pkey_buff); g_free (buff_j); if (j < ncolumns-1 && ncolumns > 1) { pkey_buff = pkey_query; pkey_query = g_strconcat (pkey_query, ",", NULL); g_free (pkey_buff); } buff_j = g_strdup_printf ("(%d,%d)" ,t_oid ,GPOINTER_TO_INT (g_ptr_array_index (col_num, j))); info_buff = info_query; info_query = g_strconcat (info_query, buff_j, NULL); g_free (buff_j); g_free (info_buff); info_buff = info_query; if (j < ncolumns-1 && ncolumns > 1) info_query = g_strconcat (info_query, ",", NULL); else info_query = g_strconcat (info_query, ")", NULL); g_free (info_buff); } pkey_buff = pkey_query; pkey_query = g_strconcat (pkey_query, ");", NULL); g_free (pkey_buff); info_buff = info_query; info_query = g_strconcat (info_query, ";", NULL); g_free (info_buff); info_buff = info_query; info_query = g_strconcat (info_query, pkey_query, NULL); g_free (info_buff); g_free (pkey_query); } 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; 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[0] == '?') { r->column[j].name = g_strdup (""); g_free (fname); } else r->column[j].name = fname; r->column[j].display = g_strdup (r->column[j].name); r->column[j].table = 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].display = 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); g_value_set_string (g_value_init (v, G_TYPE_STRING), split[2]); sql_object_add_child (function, "params", sql_value_new_with_value (v)); 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 nkeys; gchar ** pkey = NULL; GSList * prev_tables = NULL; guint nedit = 0, l; 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)); 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) { 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); } //+++++++++++++++++++++++++++++++++++++++++++++++++++ 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; }