/* * 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 "db-conn.h" #include #include #define IS_OPEN(obj) (obj->status & DB_CONN_OPEN) #define IS_CLOSING(obj) (obj->status & DB_CONN_CLOSING) #define IS_OPENING(obj) (obj->status & DB_CONN_OPENING) #define IS_TRANSACTION(obj) (obj->status & DB_CONN_TRANSACTION) #define IS_LOST(obj) (obj->status & DB_CONN_LOST) typedef struct { DbConn * obj; gpointer data; } IdleData; enum { STATUS_CHANGED ,ERROR ,LAST_SIGNAL }; static guint signals[LAST_SIGNAL] = {0}; /** * SECTION: db-conn * @Short_description: connection managing and wrapping class. * @Title: DbConn * @See_also: #DbPg * * This class manages a connection to any kind of database (as long as it has * a plugin to acces to it). It uses the plugin set on the configuration * parameters (not yet implemented) to connect, query and disconnect. * * To do this use the methods db_conn_open(), db_conn_query() and * db_conn_close() respectively. * * It is also possible to parse a SQL query and get an #SqlStmt object using the * db_conn_parse() method. * * This class is thread safe. **/ G_DEFINE_TYPE (DbConn, db_conn, G_TYPE_OBJECT); /** * db_conn_new: * * Creates a new connection. * * Return value: the #DbConn **/ DbConn * db_conn_new () { return g_object_new (DB_TYPE_CONN, NULL); } //+++++++++++++++++++++++++++++++++++++++++++++++++++ Private /* * Frees the #IdleData struct. */ static void idle_data_free (IdleData * idle_data) { g_object_unref (idle_data->obj); g_free (idle_data); } /* * Notifies that an error happened. */ static gboolean db_conn_idle_error (IdleData * idle_data) { GError * error = idle_data->data; g_signal_emit (idle_data->obj, signals[ERROR], 0, error); g_error_free (error); return FALSE; } /* * Sets and error. */ static void db_conn_set_error (DbConn * obj, const GError * error) { if (error) { IdleData * idle_data = g_new (IdleData, 1); idle_data->obj = g_object_ref (obj); idle_data->data = g_error_copy (error); g_idle_add_full (G_PRIORITY_DEFAULT, (GSourceFunc) db_conn_idle_error, idle_data, (GDestroyNotify) idle_data_free); } } /* * Notifies that connection status has changed. */ static gboolean db_conn_idle_status (IdleData * idle_data) { DbConnStatus status = GPOINTER_TO_INT (idle_data->data); g_signal_emit (idle_data->obj, signals[STATUS_CHANGED], 0, status); return FALSE; } /* * Sets the status of the connection. */ static void db_conn_set_status (DbConn * obj, DbConnStatus status) { IdleData * idle_data; if (obj->status != status) { idle_data = g_new (IdleData, 1); idle_data->obj = g_object_ref (obj); idle_data->data = GINT_TO_POINTER (status); g_idle_add_full (G_PRIORITY_DEFAULT, (GSourceFunc) db_conn_idle_status, idle_data, (GDestroyNotify) idle_data_free); obj->status = status; } } /* * Cancels all outstanding requests. */ static void db_conn_cancel_requests (DbConn * obj) { DbRequest * request; while ((request = g_queue_pop_head (obj->requests))) { db_request_cancel (request); db_request_complete (request); } db_plugin_kill_query (obj->plugin); } /* * Executes asynchronous requests. */ static void db_conn_thread (DbConn * obj) { gboolean exit = FALSE; gboolean exec_success; DbRequest * request; g_mutex_lock (obj->mutex); while (!exit) { if (IS_OPEN (obj) && !IS_TRANSACTION (obj)) { db_conn_set_status (obj, obj->status | DB_CONN_LOADING); while (TRUE) { if (!(request = g_queue_pop_head (obj->requests))) break; g_mutex_unlock (obj->mutex); exec_success = db_request_exec (request, NULL); g_mutex_lock (obj->mutex); if (!exec_success && IS_LOST (obj)) { g_queue_push_head (obj->requests, request); } else { db_request_complete (request); g_object_unref (request); } if (IS_LOST (obj)) break; } db_conn_set_status (obj, obj->status & ~DB_CONN_LOADING); } if (!IS_CLOSING (obj)) g_cond_wait (obj->thread_cond, obj->mutex); else exit = TRUE; } g_mutex_unlock (obj->mutex); } /* * Adds a request to the queue. */ static void db_conn_add_request (DbConn * obj, DbRequest * request) { g_mutex_lock (obj->mutex); if ((IS_OPEN (obj) || IS_LOST (obj)) && !IS_CLOSING (obj)) { g_queue_push_tail (obj->requests, g_object_ref (request)); if (!IS_TRANSACTION (obj)) g_cond_signal (obj->thread_cond); } else { db_request_cancel (request); db_request_complete (request); } g_mutex_unlock (obj->mutex); } //+++++++++++++++++++++++++++++++++++++++++++++++++++ Public /** * db_conn_load_plugin: * @obj: a #DbConn * @plugin: the plugin name * @err: return address of a #GError or #NULL * * Tries to load a plugin. * * Return vale: %TRUE if plugin was loaded successfully, %FALSE ortherwise **/ gboolean db_conn_load_plugin (DbConn * obj, const gchar * plugin, GError ** err) { gchar * aux; gchar * path; gchar * plugin_name; GModule * module; DbPluginGetTypeFunc symbol; gboolean ret = FALSE; g_return_val_if_fail (DB_IS_CONN (obj), FALSE); g_return_val_if_fail (plugin, FALSE); g_mutex_lock (obj->mutex); if (!obj->plugin) { aux = g_strdup_printf (_PLUGIN_DIR, plugin); plugin_name = g_strdup_printf ("db%s", plugin); path = g_module_build_path (aux, plugin_name); g_free (aux); if ((module = g_module_open (path, 0))) g_module_make_resident (module); aux = g_strdup_printf ("db_%s_get_type", plugin); if (module && g_module_symbol (module, aux, (gpointer) &symbol)) { obj->plugin_name = g_strdup (plugin); obj->plugin = g_object_new (symbol (), NULL); ret = TRUE; } else if (err) g_set_error (err ,DB_CONN_LOG_DOMAIN ,DB_CONN_ERROR_PLUGIN ,_("Can't load DbPlugin '%s': %s") ,plugin ,g_module_error () ); else g_warning (_("Can't load DbPlugin '%s': %s") ,plugin ,g_module_error () ); g_free (plugin_name); g_free (path); g_free (aux); } else if (err) g_set_error (err ,DB_CONN_LOG_DOMAIN ,DB_CONN_ERROR_PLUGIN ,_("Plugin can't be loaded") ); g_mutex_unlock (obj->mutex); return ret; } /** * db_conn_set_query_path: * @obj: a #DbConn * @path: the path * * Sets the query search path. **/ void db_conn_set_query_path (DbConn * obj, const gchar * path) { g_return_if_fail (DB_IS_CONN (obj)); g_mutex_lock (obj->settings_mutex); g_free (obj->query_path); g_strfreev (obj->query_dirs); obj->query_path = g_strdup (path); obj->query_dirs = g_strsplit (obj->query_path, G_SEARCHPATH_SEPARATOR_S, 0); g_mutex_unlock (obj->settings_mutex); } /** * db_conn_get_query_path: * @obj: a #DbConn * * Sets the query search path. * * Return value: the path, should be freed with g_free() **/ gchar * db_conn_get_query_path (DbConn * obj) { gchar * path; g_return_val_if_fail (DB_IS_CONN (obj), NULL); g_mutex_lock (obj->settings_mutex); path = g_strdup (obj->query_path); g_mutex_unlock (obj->settings_mutex); return path; } /** * db_conn_open: * @obj: a #DbConn * @host: Host name * @schema: Database name * @user: User name * @pass: Password * * Opens a new connection to the database and creates the thread that handles * all requests made to it asincronously. * * Return value: %TRUE if the connection was successfully open, %FALSE otherwise **/ gboolean db_conn_open (DbConn * obj, const gchar * host, const gchar * schema, const gchar * user, const gchar * pass, GError ** err) { gboolean opened = FALSE; g_return_val_if_fail (DB_IS_CONN (obj), FALSE); g_mutex_lock (obj->mutex); if (!IS_OPEN (obj) && !IS_OPENING (obj) && !IS_CLOSING (obj)) { DbConnStatus last_status = obj->status; db_conn_set_status (obj, obj->status | DB_CONN_OPENING | DB_CONN_LOADING); g_mutex_unlock (obj->mutex); opened = db_plugin_open (obj->plugin ,host ,schema ,user ,pass ,err ); g_mutex_lock (obj->mutex); if (opened) { g_mutex_lock (obj->settings_mutex); g_free (obj->host); g_free (obj->schema); g_free (obj->user); g_free (obj->pass); obj->host = g_strdup (host); obj->schema = g_strdup (schema); obj->user = g_strdup (user); obj->pass = g_strdup (pass); g_mutex_unlock (obj->settings_mutex); db_conn_set_status (obj, DB_CONN_OPEN); if (last_status & DB_CONN_LOST) g_cond_signal (obj->thread_cond); else obj->thread = g_thread_new ("DbConn", (GThreadFunc) db_conn_thread, obj); } else db_conn_set_status (obj, last_status); } else g_set_error (err ,DB_CONN_LOG_DOMAIN ,DB_CONN_ERROR_OPENING ,_("Can't open a new connection, it's already open.") ); g_mutex_unlock (obj->mutex); return opened; } void db_conn_set_ssl (DbConn * obj, const gchar * ca) { g_return_if_fail (DB_IS_CONN (obj)); db_plugin_set_ssl (obj->plugin, ca); } /** * db_conn_reconnect: * @obj: a #DbConn * * Tries to reconnect to database. **/ gboolean db_conn_reconnect (DbConn * obj, GError ** err) { gboolean opened; gchar * host; gchar * schema; gchar * user; gchar * pass; g_return_val_if_fail (DB_IS_CONN (obj), FALSE); g_mutex_lock (obj->settings_mutex); host = g_strdup (obj->host); schema = g_strdup (obj->schema); user = g_strdup (obj->user); pass = g_strdup (obj->pass); g_mutex_unlock (obj->settings_mutex); opened = db_conn_open (obj, host, schema, user, pass, err); g_free (host); g_free (schema); g_free (user); g_free (pass); return opened; } /** * db_conn_close: * @obj: a #DbConn * @wait: specifies whether to wait for pending requests before close the connection * * Closes the current connection to the database and the associated thread. **/ void db_conn_close (DbConn * obj, gboolean wait) { g_return_if_fail (DB_IS_CONN (obj)); g_mutex_lock (obj->mutex); if ((IS_OPEN (obj) || (IS_LOST (obj) && !IS_OPENING (obj))) && !IS_CLOSING (obj)) { db_conn_set_status (obj, obj->status | DB_CONN_CLOSING | DB_CONN_LOADING); if (!wait) db_conn_cancel_requests (obj); g_cond_signal (obj->thread_cond); g_mutex_unlock (obj->mutex); g_thread_join (obj->thread); obj->thread = NULL; db_plugin_close (obj->plugin); g_mutex_lock (obj->mutex); db_conn_set_status (obj, DB_CONN_CLOSED); g_mutex_lock (obj->settings_mutex); g_free (obj->host); g_free (obj->schema); g_free (obj->user); g_free (obj->pass); obj->host = NULL; obj->schema = NULL; obj->user = NULL; obj->pass = NULL; g_mutex_unlock (obj->settings_mutex); } g_mutex_unlock (obj->mutex); } /** * db_conn_exec: * @obj: a #DbConn * @sql: the SQL statement * @err: (out) (allow-none): return location for a #GError or %NULL * * Sends a query to the database and waits for the resultset. The user is * responsible for releasing the result with db_result_set_free(). * * Return value: (transfer full) (allow-none): the #DbResultSet on success, %NULL on failure **/ DbResultSet * db_conn_exec (DbConn * obj, const gchar * sql, GError ** err) { GError * query_error = NULL; g_return_val_if_fail (DB_IS_CONN (obj), NULL); if (sql && g_strcmp0 (sql, "")) { DbResultSet * set; // XXX: Only for debug purposes. g_message ("DbConn: Query sent: %s", sql); set = db_plugin_query (obj->plugin, sql, &query_error); if (!set && query_error) { if (query_error->code == DB_CONN_ERROR_LOST) { g_mutex_lock (obj->mutex); if (IS_OPEN (obj) && !IS_CLOSING (obj)) db_conn_set_status (obj, DB_CONN_CLOSED | DB_CONN_LOST); g_mutex_unlock (obj->mutex); } db_conn_set_error (obj, query_error); g_propagate_error (err, query_error); } return set; } else { query_error = g_error_new ( DB_CONN_LOG_DOMAIN ,DB_CONN_ERROR_QUERY_EMPTY ,_("The query is empty.") ); db_conn_set_error (obj, query_error); g_propagate_error (err, query_error); } return NULL; } /** * db_conn_query: * @obj: the #DbConn * @sql: the SQL statement * * Sends a query to the database and waits for the result. * * Return value: (transfer full): the new #DbRequest **/ DbRequest * db_conn_query (DbConn * obj, const gchar * sql) { DbRequest * request; g_return_val_if_fail (DB_IS_CONN (obj), NULL); request = db_request_new (obj, sql); db_request_exec (request, NULL); return request; } /** * db_conn_query_with_stmt: * @obj: the #DbConn * @stmt: the #SqlStmt * * Sends a stmt to the database and waits for the result. * * Return value: (transfer full): the new #DbRequest **/ DbRequest * db_conn_query_with_stmt (DbConn * obj, SqlStmt * stmt) { DbRequest * request; g_return_val_if_fail (DB_IS_CONN (obj), NULL); request = db_request_new_with_stmt (obj, stmt); db_request_exec (request, NULL); return request; } /** * db_conn_query_async: * @obj: the #DbConn * @sql: the SQL statement * @callback: * @user_data: * @notify: * * Return value: (transfer full): the new #DbRequest **/ DbRequest * db_conn_query_async (DbConn * obj, const gchar * sql, DbRequestDoneCallback callback, gpointer user_data, GDestroyNotify notify) { DbRequest * request; g_return_val_if_fail (DB_IS_CONN (obj), NULL); request = db_request_new (obj, sql); db_request_set_callback (request, callback, user_data, notify); db_conn_add_request (obj, request); return request; } /** * db_conn_query_with_stmt_async: * @obj: the #DbConn * @stmt: the #SqlStmt * @callback: * @user_data: * @notify: * * Return value: (transfer full): the new #DbRequest **/ DbRequest * db_conn_query_with_stmt_async (DbConn * obj, SqlStmt * stmt, DbRequestDoneCallback callback, gpointer user_data, GDestroyNotify notify) { DbRequest * request; g_return_val_if_fail (DB_IS_CONN (obj), NULL); request = db_request_new_with_stmt (obj, stmt); db_request_set_callback (request, callback, user_data, notify); db_conn_add_request (obj, request); return request; } /** * db_conn_query_value: * @obj: a #DbConn * @sql: a SQL statement * @value: (out): return location for a #GValue * @err: (out) (allow-none): return location for a #GError or %NULL * * Sends a sql to the database and takes the first row value of the result. * * Return value: %TRUE on success, %FALSE on failure **/ gboolean db_conn_query_value (DbConn * obj, const gchar * sql, GValue * value, GError ** err) { gboolean success; DbRequest * request; g_return_val_if_fail (DB_IS_CONN (obj), FALSE); request = db_conn_query (obj, sql); success = db_request_fetch_value (request, value, err); g_object_unref (request); return success; } /** * db_conn_kill_query: * @obj: a #DbConn * * Attempts to kill the query that is running, if any. **/ void db_conn_kill_query (DbConn * obj) { g_return_if_fail (DB_IS_CONN (obj)); db_plugin_kill_query (obj->plugin); } /** * db_conn_retry: * @obj: a #DbConn * * Tries rerunning pending requests. **/ void db_conn_retry (DbConn * obj) { g_return_if_fail (DB_IS_CONN (obj)); g_cond_signal (obj->thread_cond); } /** * db_conn_parse: * @obj: a #DbConn * @sql: an SQL string * * Return value: (transfer full): a #SqlStmt **/ SqlStmt * db_conn_parse (DbConn * obj, gchar * sql) { g_return_val_if_fail (DB_IS_CONN (obj), NULL); return db_plugin_parse (obj->plugin, sql); } /** * db_conn_render: * @obj: a #DbConn. * @object: the #GObject to render * @err: (out) (allow-none): the return location for #GError * * Renders a #GObject object as a SQL string to send it in a database * query. It takes the connection to know the codification in wich to escape * the data. * * Return value: (transfer full): the rendered string, or %NULL if error. **/ gchar * db_conn_render (DbConn * obj, gpointer object, GError ** err) { g_return_val_if_fail (DB_IS_CONN (obj), NULL); g_return_val_if_fail (G_IS_OBJECT (object), NULL); return db_plugin_render (obj->plugin, object, err); } /** * db_conn_start_transaction: * @obj: a #DbConn * * Enter the transaction state, all requests made are retained until you call * the method db_conn_comit. **/ void db_conn_start_transaction (DbConn * obj) { g_return_if_fail (DB_IS_CONN (obj)); g_mutex_lock (obj->mutex); if (IS_OPEN (obj) && !IS_CLOSING (obj)) { obj->transaction++; db_conn_set_status (obj, obj->status | DB_CONN_TRANSACTION); } g_mutex_unlock (obj->mutex); } /** * db_conn_commit: * @obj: a #DbConn * * Commits the current transaction. If transaction arrives to 0, all * outstanding requests in the queue are sent. **/ void db_conn_commit (DbConn * obj) { g_return_if_fail (DB_IS_CONN (obj)); g_mutex_lock (obj->mutex); if (IS_TRANSACTION (obj)) { obj->transaction--; if (!obj->transaction) { db_conn_set_status (obj, obj->status & ~DB_CONN_TRANSACTION); g_mutex_unlock (obj->mutex); db_conn_retry (obj); return; } } g_mutex_unlock (obj->mutex); } /** * db_conn_rollback: * @obj: a #DbConn * * Deletes all pending requests from the input queue and rollbacks the current * transactions, if any. **/ void db_conn_rollback (DbConn * obj) { g_return_if_fail (DB_IS_CONN (obj)); g_mutex_lock (obj->mutex); if (IS_TRANSACTION (obj)) { db_conn_cancel_requests (obj); obj->transaction = 0; db_conn_set_status (obj, obj->status & ~DB_CONN_TRANSACTION); } g_mutex_unlock (obj->mutex); } /** * db_conn_get_user: * @obj: a #DbConn * * Gets the connection user. * * Return value: the user, should be freed using g_free() **/ gchar * db_conn_get_user (DbConn * obj) { gchar * user; g_return_val_if_fail (DB_IS_CONN (obj), NULL); g_mutex_lock (obj->settings_mutex); user = g_strdup (obj->user); g_mutex_unlock (obj->settings_mutex); return user; } /** * db_conn_get_host: * @obj: a #DbConn * * Gets the connection host. * * Return value: the host, should be freed using g_free() **/ gchar * db_conn_get_host (DbConn * obj) { gchar * host; g_return_val_if_fail (DB_IS_CONN (obj), NULL); g_mutex_lock (obj->settings_mutex); host = g_strdup (obj->host); g_mutex_unlock (obj->settings_mutex); return host; } /** * db_conn_get_schema: * @obj: a #DbConn * * Gets the connection schema. * * Return value: the schema, should be freed using g_free() **/ gchar * db_conn_get_schema (DbConn * obj) { gchar * schema; g_return_val_if_fail (DB_IS_CONN (obj), NULL); g_mutex_lock (obj->settings_mutex); schema = g_strdup (obj->schema); g_mutex_unlock (obj->settings_mutex); return schema; } /** * db_conn_create_stmt_from_file: * @obj: a #DbConn * @query_file: path to a file containing SQL statement * * Reads an SQL query from the specified query_file and creates a new * #SqlString. The file path must be relative to any path in the "query-path" * property. * * Return value: (transfer full): an #SqlString **/ SqlString * db_conn_create_stmt_from_file (DbConn * obj, const gchar * query_file) { gint i; gchar * file; SqlString * stmt = NULL; g_return_val_if_fail (DB_IS_CONN (obj), NULL); g_return_val_if_fail (query_file, NULL); file = g_strconcat (query_file, ".sql", NULL); if (obj->query_dirs) for (i = 0; obj->query_dirs[i] && !stmt; i++) { gchar * buffer; gchar * path = g_build_filename (obj->query_dirs[i], file, NULL); if (g_file_get_contents (path, &buffer, NULL, NULL)) { stmt = sql_string_new (buffer); g_free (buffer); } g_free (path); } if (!stmt) g_warning ("DbConn: Can't create statement from file: %s", file); g_free (file); return stmt; } //+++++++++++++++++++++++++++++++++++++++++++++++++++ Properties enum { PROP_PLUGIN = 1 ,PROP_QUERY_PATH ,PROP_HOST ,PROP_USER ,PROP_DB }; static void db_conn_set_property (DbConn * obj, guint id, const GValue * value, GParamSpec * pspec) { switch (id) { case PROP_PLUGIN: db_conn_load_plugin (obj, g_value_get_string (value), NULL); break; case PROP_QUERY_PATH: db_conn_set_query_path (obj, g_value_get_string (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, id, pspec); } } static void db_conn_get_property (DbConn * obj, guint id, GValue * value, GParamSpec * pspec) { switch (id) { case PROP_PLUGIN: g_value_set_string (value, obj->plugin_name); break; case PROP_QUERY_PATH: g_value_set_string (value, db_conn_get_query_path (obj)); break; case PROP_HOST: g_value_take_string (value, db_conn_get_host (obj)); break; case PROP_USER: g_value_take_string (value, db_conn_get_user (obj)); break; case PROP_DB: g_value_take_string (value, db_conn_get_schema (obj)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, id, pspec); } } //+++++++++++++++++++++++++++++++++++++++++++++++++++ Class static void db_conn_init (DbConn * obj) { obj->plugin = NULL; obj->plugin_name = NULL; obj->query_path = NULL; obj->query_dirs = NULL; obj->schema = NULL; obj->host = NULL; obj->user = NULL; obj->pass = NULL; obj->thread = NULL; obj->thread_cond = g_new (GCond, 1); obj->transaction = 0; obj->status = DB_CONN_CLOSED; obj->requests = g_queue_new (); obj->mutex = g_new (GMutex, 1); obj->settings_mutex = g_new (GMutex, 1); g_mutex_init (obj->mutex); g_mutex_init (obj->settings_mutex); g_cond_init (obj->thread_cond); } static void db_conn_finalize (DbConn * obj) { db_conn_close (obj, FALSE); if (obj->plugin) g_free (obj->plugin_name); g_mutex_clear (obj->settings_mutex); g_mutex_clear (obj->mutex); g_cond_clear (obj->thread_cond); g_free (obj->query_path); g_strfreev (obj->query_dirs); g_free (obj->settings_mutex); g_free (obj->mutex); g_free (obj->thread_cond); g_free (obj->host); g_free (obj->user); g_free (obj->pass); g_free (obj->schema); g_queue_free_full (obj->requests, (GDestroyNotify) g_object_unref); G_OBJECT_CLASS (db_conn_parent_class)->finalize (G_OBJECT (obj)); } static void db_conn_class_init (DbConnClass * k) { GObjectClass * klass = G_OBJECT_CLASS (k); klass->set_property = (GObjectSetPropertyFunc) db_conn_set_property; klass->get_property = (GObjectGetPropertyFunc) db_conn_get_property; klass->finalize = (GObjectFinalizeFunc) db_conn_finalize; signals[STATUS_CHANGED] = g_signal_new ("status-changed", DB_TYPE_CONN, G_SIGNAL_RUN_FIRST, 0, NULL, NULL, g_cclosure_marshal_VOID__INT, G_TYPE_NONE, 1, G_TYPE_INT ); signals[ERROR] = g_signal_new ("error", DB_TYPE_CONN, G_SIGNAL_RUN_FIRST, 0, NULL, NULL, g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER ); g_object_class_install_property (klass, PROP_PLUGIN, g_param_spec_string ("plugin" ,_("Plugin") ,_("The name of the plugin") ,NULL ,G_PARAM_READWRITE )); g_object_class_install_property (klass, PROP_QUERY_PATH, g_param_spec_string ("query-path" ,_("Query path") ,_("The path where query files are located") ,NULL ,G_PARAM_READWRITE )); g_object_class_install_property (klass, PROP_HOST, g_param_spec_string ("host" ,_("Host") ,_("The host name to connect to") ,NULL ,G_PARAM_READABLE )); g_object_class_install_property (klass, PROP_USER, g_param_spec_string ("user" ,_("User") ,_("The user name") ,NULL ,G_PARAM_READABLE )); g_object_class_install_property (klass, PROP_DB, g_param_spec_string ("db" ,_("DB name") ,_("The default schema") ,NULL ,G_PARAM_READABLE )); }