X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;f=src%2Fpeerstore%2Fplugin_peerstore_sqlite.c;h=440263d44b5b38075c032bbf267083c3959cd3e2;hb=79fb947eb8fba243ea65e19b40b65e04f8806865;hp=1695938bbb3847b87f8c8fe90d142ae269a241b6;hpb=36367311686410711f46fa3d82484a609c888acf;p=oweals%2Fgnunet.git diff --git a/src/peerstore/plugin_peerstore_sqlite.c b/src/peerstore/plugin_peerstore_sqlite.c index 1695938bb..440263d44 100644 --- a/src/peerstore/plugin_peerstore_sqlite.c +++ b/src/peerstore/plugin_peerstore_sqlite.c @@ -1,6 +1,6 @@ /* * This file is part of GNUnet - * (C) 2013 Christian Grothoff (and other contributing authors) + * Copyright (C) 2013, 2017 GNUnet e.V. * * GNUnet is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published @@ -14,19 +14,21 @@ * * You should have received a copy of the GNU General Public License * along with GNUnet; see the file COPYING. If not, write to the - * Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. */ /** * @file peerstore/plugin_peerstore_sqlite.c * @brief sqlite-based peerstore backend * @author Omar Tarabai + * @author Christian Grothoff */ #include "platform.h" #include "gnunet_peerstore_plugin.h" #include "gnunet_peerstore_service.h" +#include "gnunet_sq_lib.h" #include "peerstore.h" #include @@ -77,49 +79,349 @@ struct Plugin */ sqlite3_stmt *insert_peerstoredata; + /** + * Precompiled SQL for selecting from peerstoredata + */ + sqlite3_stmt *select_peerstoredata; + + /** + * Precompiled SQL for selecting from peerstoredata + */ + sqlite3_stmt *select_peerstoredata_by_pid; + + /** + * Precompiled SQL for selecting from peerstoredata + */ + sqlite3_stmt *select_peerstoredata_by_key; + + /** + * Precompiled SQL for selecting from peerstoredata + */ + sqlite3_stmt *select_peerstoredata_by_all; + + /** + * Precompiled SQL for deleting expired + * records from peerstoredata + */ + sqlite3_stmt *expire_peerstoredata; + + /** + * Precompiled SQL for deleting records + * with given key + */ + sqlite3_stmt *delete_peerstoredata; + }; + +/** + * Delete records with the given key + * + * @param cls closure (internal context for the plugin) + * @param sub_system name of sub system + * @param peer Peer identity (can be NULL) + * @param key entry key string (can be NULL) + * @return number of deleted records, #GNUNE_SYSERR on error + */ +static int +peerstore_sqlite_delete_records (void *cls, + const char *sub_system, + const struct GNUNET_PeerIdentity *peer, + const char *key) +{ + struct Plugin *plugin = cls; + sqlite3_stmt *stmt = plugin->delete_peerstoredata; + struct GNUNET_SQ_QueryParam params[] = { + GNUNET_SQ_query_param_string (sub_system), + GNUNET_SQ_query_param_auto_from_type (peer), + GNUNET_SQ_query_param_string (key), + GNUNET_SQ_query_param_end + }; + int ret; + + if (GNUNET_OK != + GNUNET_SQ_bind (stmt, + params)) + { + LOG_SQLITE (plugin, + GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_bind"); + GNUNET_SQ_reset (plugin->dbh, + stmt); + return GNUNET_SYSERR; + } + if (SQLITE_DONE != + sqlite3_step (stmt)) + { + LOG_SQLITE (plugin, + GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_step"); + ret = GNUNET_SYSERR; + } + else + { + ret = sqlite3_changes (plugin->dbh); + } + GNUNET_SQ_reset (plugin->dbh, + stmt); + return ret; +} + + +/** + * Delete expired records (expiry < now) + * + * @param cls closure (internal context for the plugin) + * @param now time to use as reference + * @param cont continuation called with the number of records expired + * @param cont_cls continuation closure + * @return #GNUNET_OK on success, #GNUNET_SYSERR on error and cont is not + * called + */ +static int +peerstore_sqlite_expire_records (void *cls, struct GNUNET_TIME_Absolute now, + GNUNET_PEERSTORE_Continuation cont, + void *cont_cls) +{ + struct Plugin *plugin = cls; + sqlite3_stmt *stmt = plugin->expire_peerstoredata; + struct GNUNET_SQ_QueryParam params[] = { + GNUNET_SQ_query_param_absolute_time (&now), + GNUNET_SQ_query_param_end + }; + + if (GNUNET_OK != + GNUNET_SQ_bind (stmt, + params)) + { + LOG_SQLITE (plugin, + GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_bind"); + GNUNET_SQ_reset (plugin->dbh, + stmt); + return GNUNET_SYSERR; + } + if (SQLITE_DONE != sqlite3_step (stmt)) + { + LOG_SQLITE (plugin, + GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_step"); + GNUNET_SQ_reset (plugin->dbh, + stmt); + return GNUNET_SYSERR; + } + if (NULL != cont) + cont (cont_cls, + sqlite3_changes (plugin->dbh)); + GNUNET_SQ_reset (plugin->dbh, + stmt); + return GNUNET_OK; +} + + +/** + * Iterate over the records given an optional peer id + * and/or key. + * + * @param cls closure (internal context for the plugin) + * @param sub_system name of sub system + * @param peer Peer identity (can be NULL) + * @param key entry key string (can be NULL) + * @param iter function to call asynchronously with the results, terminated + * by a NULL result + * @param iter_cls closure for @a iter + * @return #GNUNET_OK on success, #GNUNET_SYSERR on error and iter is not + * called + */ +static int +peerstore_sqlite_iterate_records (void *cls, + const char *sub_system, + const struct GNUNET_PeerIdentity *peer, + const char *key, + GNUNET_PEERSTORE_Processor iter, + void *iter_cls) +{ + struct Plugin *plugin = cls; + sqlite3_stmt *stmt; + int err = 0; + int sret; + struct GNUNET_PEERSTORE_Record rec; + + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Executing iterate request on sqlite db.\n"); + if (NULL == peer) + { + if (NULL == key) + { + struct GNUNET_SQ_QueryParam params[] = { + GNUNET_SQ_query_param_string (sub_system), + GNUNET_SQ_query_param_end + }; + + stmt = plugin->select_peerstoredata; + err = GNUNET_SQ_bind (stmt, + params); + } + else + { + struct GNUNET_SQ_QueryParam params[] = { + GNUNET_SQ_query_param_string (sub_system), + GNUNET_SQ_query_param_string (key), + GNUNET_SQ_query_param_end + }; + + stmt = plugin->select_peerstoredata_by_key; + err = GNUNET_SQ_bind (stmt, + params); + } + } + else + { + if (NULL == key) + { + struct GNUNET_SQ_QueryParam params[] = { + GNUNET_SQ_query_param_string (sub_system), + GNUNET_SQ_query_param_auto_from_type (peer), + GNUNET_SQ_query_param_end + }; + + stmt = plugin->select_peerstoredata_by_pid; + err = GNUNET_SQ_bind (stmt, + params); + } + else + { + struct GNUNET_SQ_QueryParam params[] = { + GNUNET_SQ_query_param_string (sub_system), + GNUNET_SQ_query_param_auto_from_type (peer), + GNUNET_SQ_query_param_string (key), + GNUNET_SQ_query_param_end + }; + + stmt = plugin->select_peerstoredata_by_all; + err = GNUNET_SQ_bind (stmt, + params); + } + } + + if (GNUNET_OK != err) + { + LOG_SQLITE (plugin, + GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_bind_XXXX"); + GNUNET_SQ_reset (plugin->dbh, + stmt); + return GNUNET_SYSERR; + } + + err = 0; + while (SQLITE_ROW == (sret = sqlite3_step (stmt))) + { + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Returning a matched record.\n"); + struct GNUNET_SQ_ResultSpec rs[] = { + GNUNET_SQ_result_spec_string (&rec.sub_system), + GNUNET_SQ_result_spec_auto_from_type (&rec.peer), + GNUNET_SQ_result_spec_string (&rec.key), + GNUNET_SQ_result_spec_variable_size (&rec.value, &rec.value_size), + GNUNET_SQ_result_spec_absolute_time (&rec.expiry), + GNUNET_SQ_result_spec_end + }; + + if (GNUNET_OK != + GNUNET_SQ_extract_result (stmt, + rs)) + { + GNUNET_break (0); + break; + } + if (NULL != iter) + iter (iter_cls, + &rec, + NULL); + GNUNET_SQ_cleanup_result (rs); + } + if (SQLITE_DONE != sret) + { + LOG_SQLITE (plugin, + GNUNET_ERROR_TYPE_ERROR, + "sqlite_step"); + err = 1; + } + GNUNET_SQ_reset (plugin->dbh, + stmt); + if (NULL != iter) + iter (iter_cls, + NULL, + err ? "sqlite error" : NULL); + return GNUNET_OK; +} + + /** * Store a record in the peerstore. * Key is the combination of sub system and peer identity. * One key can store multiple values. * * @param cls closure (internal context for the plugin) - * @param peer peer identity * @param sub_system name of the GNUnet sub system responsible + * @param peer peer identity + * @param key record key string * @param value value to be stored * @param size size of value to be stored - * @return #GNUNET_OK on success, else #GNUNET_SYSERR + * @param expiry absolute time after which the record is (possibly) deleted + * @param options options related to the store operation + * @param cont continuation called when record is stored + * @param cont_cls continuation closure + * @return #GNUNET_OK on success, else #GNUNET_SYSERR and cont is not called */ static int peerstore_sqlite_store_record (void *cls, - const struct GNUNET_PeerIdentity *peer, - const char *sub_system, - const void *value, - size_t size) + const char *sub_system, + const struct GNUNET_PeerIdentity *peer, + const char *key, + const void *value, + size_t size, + struct GNUNET_TIME_Absolute expiry, + enum GNUNET_PEERSTORE_StoreOption options, + GNUNET_PEERSTORE_Continuation cont, + void *cont_cls) { struct Plugin *plugin = cls; sqlite3_stmt *stmt = plugin->insert_peerstoredata; - - //FIXME: check if value exists with the same key first - - if(SQLITE_OK != sqlite3_bind_blob(stmt, 2, peer, sizeof(struct GNUNET_PeerIdentity), SQLITE_STATIC) - || SQLITE_OK != sqlite3_bind_text(stmt, 1, sub_system, sizeof(sub_system), SQLITE_STATIC) - || SQLITE_OK != sqlite3_bind_blob(stmt, 3, value, size, SQLITE_STATIC)) - LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, - "sqlite3_bind"); - else if (SQLITE_DONE != sqlite3_step (stmt)) + struct GNUNET_SQ_QueryParam params[] = { + GNUNET_SQ_query_param_string (sub_system), + GNUNET_SQ_query_param_auto_from_type (peer), + GNUNET_SQ_query_param_string (key), + GNUNET_SQ_query_param_fixed_size (value, size), + GNUNET_SQ_query_param_absolute_time (&expiry), + GNUNET_SQ_query_param_end + }; + + if (GNUNET_PEERSTORE_STOREOPTION_REPLACE == options) { - LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, - "sqlite3_step"); + peerstore_sqlite_delete_records (cls, + sub_system, + peer, + key); } - if (SQLITE_OK != sqlite3_reset (stmt)) + if (GNUNET_OK != + GNUNET_SQ_bind (stmt, + params)) + LOG_SQLITE (plugin, + GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_bind"); + else if (SQLITE_DONE != sqlite3_step (stmt)) { - LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, - "sqlite3_reset"); - return GNUNET_SYSERR; + LOG_SQLITE (plugin, + GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_step"); } - + GNUNET_SQ_reset (plugin->dbh, + stmt); + if (NULL != cont) + cont (cont_cls, + GNUNET_OK); return GNUNET_OK; } @@ -132,20 +434,29 @@ peerstore_sqlite_store_record (void *cls, * @return 0 on success */ static int -sql_exec (sqlite3 *dbh, const char *sql) +sql_exec (sqlite3 *dbh, + const char *sql) { int result; - result = sqlite3_exec (dbh, sql, NULL, NULL, NULL); + result = sqlite3_exec (dbh, + sql, + NULL, + NULL, + NULL); LOG (GNUNET_ERROR_TYPE_DEBUG, - "Executed `%s' / %d\n", sql, result); - if (result != SQLITE_OK) + "Executed `%s' / %d\n", + sql, + result); + if (SQLITE_OK != result) LOG (GNUNET_ERROR_TYPE_ERROR, - _("Error executing SQL query: %s\n %s\n"), - sqlite3_errmsg (dbh), sql); + _("Error executing SQL query: %s\n %s\n"), + sqlite3_errmsg (dbh), + sql); return result; } + /** * @brief Prepare a SQL statement * @@ -155,22 +466,32 @@ sql_exec (sqlite3 *dbh, const char *sql) * @return 0 on success */ static int -sql_prepare (sqlite3 *dbh, const char *sql, sqlite3_stmt **stmt) +sql_prepare (sqlite3 *dbh, + const char *sql, + sqlite3_stmt ** stmt) { char *tail; int result; - result = sqlite3_prepare_v2 (dbh, sql, strlen (sql), stmt, + result = sqlite3_prepare_v2 (dbh, + sql, + strlen (sql), + stmt, (const char **) &tail); LOG (GNUNET_ERROR_TYPE_DEBUG, - "Prepared `%s' / %p: %d\n", sql, *stmt, result); - if (result != SQLITE_OK) + "Prepared `%s' / %p: %d\n", + sql, + *stmt, + result); + if (SQLITE_OK != result) LOG (GNUNET_ERROR_TYPE_ERROR, - _("Error preparing SQL query: %s\n %s\n"), - sqlite3_errmsg (dbh), sql); + _("Error preparing SQL query: %s\n %s\n"), + sqlite3_errmsg (dbh), + sql); return result; } + /** * Initialize the database connections and associated * data structures (create tables and indices @@ -185,11 +506,14 @@ database_setup (struct Plugin *plugin) char *filename; if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_filename (plugin->cfg, "peerstore-sqlite", - "FILENAME", &filename)) + GNUNET_CONFIGURATION_get_value_filename (plugin->cfg, + "peerstore-sqlite", + "FILENAME", + &filename)) { GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - "peerstore-sqlite", "FILENAME"); + "peerstore-sqlite", + "FILENAME"); return GNUNET_SYSERR; } if (GNUNET_OK != GNUNET_DISK_file_test (filename)) @@ -203,44 +527,88 @@ database_setup (struct Plugin *plugin) } /* filename should be UTF-8-encoded. If it isn't, it's a bug */ plugin->fn = filename; - /* Open database and precompile statements */ - if (SQLITE_OK != sqlite3_open (plugin->fn, &plugin->dbh)) + if (SQLITE_OK != sqlite3_open (plugin->fn, + &plugin->dbh)) { LOG (GNUNET_ERROR_TYPE_ERROR, - _("Unable to initialize SQLite: %s.\n"), - sqlite3_errmsg (plugin->dbh)); + _("Unable to initialize SQLite: %s.\n"), + sqlite3_errmsg (plugin->dbh)); return GNUNET_SYSERR; } - - sql_exec (plugin->dbh, "PRAGMA temp_store=MEMORY"); - sql_exec (plugin->dbh, "PRAGMA synchronous=NORMAL"); - sql_exec (plugin->dbh, "PRAGMA legacy_file_format=OFF"); - sql_exec (plugin->dbh, "PRAGMA auto_vacuum=INCREMENTAL"); - sql_exec (plugin->dbh, "PRAGMA encoding=\"UTF-8\""); - sql_exec (plugin->dbh, "PRAGMA count_changes=OFF"); - sql_exec (plugin->dbh, "PRAGMA page_size=4096"); - - sqlite3_busy_timeout (plugin->dbh, BUSY_TIMEOUT_MS); - + sql_exec (plugin->dbh, + "PRAGMA temp_store=MEMORY"); + sql_exec (plugin->dbh, + "PRAGMA synchronous=OFF"); + sql_exec (plugin->dbh, + "PRAGMA legacy_file_format=OFF"); + sql_exec (plugin->dbh, + "PRAGMA auto_vacuum=INCREMENTAL"); + sql_exec (plugin->dbh, + "PRAGMA encoding=\"UTF-8\""); + sql_exec (plugin->dbh, + "PRAGMA page_size=4096"); + sqlite3_busy_timeout (plugin->dbh, + BUSY_TIMEOUT_MS); /* Create tables */ - sql_exec (plugin->dbh, "CREATE TABLE IF NOT EXISTS peerstoredata (\n" - " peer_id BLOB NOT NULL,\n" " sub_system TEXT NOT NULL,\n" - " value BLOB NULL" - ");"); - + " peer_id BLOB NOT NULL,\n" + " key TEXT NOT NULL,\n" + " value BLOB NULL,\n" + " expiry INT8 NOT NULL" ");"); + /* Create Indices */ + if (SQLITE_OK != + sqlite3_exec (plugin->dbh, + "CREATE INDEX IF NOT EXISTS peerstoredata_key_index ON peerstoredata (sub_system, peer_id, key)", + NULL, + NULL, + NULL)) + { + LOG (GNUNET_ERROR_TYPE_ERROR, + _("Unable to create indices: %s.\n"), + sqlite3_errmsg (plugin->dbh)); + return GNUNET_SYSERR; + } /* Prepare statements */ sql_prepare (plugin->dbh, - "INSERT INTO peerstoredata (peer_id, sub_system, value) VALUES (?,?,?);", + "INSERT INTO peerstoredata (sub_system, peer_id, key, value, expiry)" + " VALUES (?,?,?,?,?);", &plugin->insert_peerstoredata); - + sql_prepare (plugin->dbh, + "SELECT sub_system,peer_id,key,value,expiry FROM peerstoredata" + " WHERE sub_system = ?", + &plugin->select_peerstoredata); + sql_prepare (plugin->dbh, + "SELECT sub_system,peer_id,key,value,expiry FROM peerstoredata" + " WHERE sub_system = ?" + " AND peer_id = ?", + &plugin->select_peerstoredata_by_pid); + sql_prepare (plugin->dbh, + "SELECT sub_system,peer_id,key,value,expiry FROM peerstoredata" + " WHERE sub_system = ?" + " AND key = ?", + &plugin->select_peerstoredata_by_key); + sql_prepare (plugin->dbh, + "SELECT sub_system,peer_id,key,value,expiry FROM peerstoredata" + " WHERE sub_system = ?" + " AND peer_id = ?" " AND key = ?", + &plugin->select_peerstoredata_by_all); + sql_prepare (plugin->dbh, + "DELETE FROM peerstoredata" + " WHERE expiry < ?", + &plugin->expire_peerstoredata); + sql_prepare (plugin->dbh, + "DELETE FROM peerstoredata" + " WHERE sub_system = ?" + " AND peer_id = ?" " AND key = ?", + &plugin->delete_peerstoredata); return GNUNET_OK; } + /** * Shutdown database connection and associate data * structures. @@ -251,19 +619,25 @@ database_shutdown (struct Plugin *plugin) { int result; sqlite3_stmt *stmt; - while (NULL != (stmt = sqlite3_next_stmt (plugin->dbh, NULL))) + + while (NULL != (stmt = sqlite3_next_stmt (plugin->dbh, + NULL))) { result = sqlite3_finalize (stmt); if (SQLITE_OK != result) LOG (GNUNET_ERROR_TYPE_WARNING, - "Failed to close statement %p: %d\n", stmt, result); + "Failed to close statement %p: %d\n", + stmt, + result); } if (SQLITE_OK != sqlite3_close (plugin->dbh)) - LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR, "sqlite3_close"); - + LOG_SQLITE (plugin, + GNUNET_ERROR_TYPE_ERROR, + "sqlite3_close"); GNUNET_free_non_null (plugin->fn); } + /** * Entry point for the plugin. * @@ -279,7 +653,9 @@ libgnunet_plugin_peerstore_sqlite_init (void *cls) if (NULL != plugin.cfg) return NULL; /* can only initialize once! */ - memset (&plugin, 0, sizeof (struct Plugin)); + memset (&plugin, + 0, + sizeof (struct Plugin)); plugin.cfg = cfg; if (GNUNET_OK != database_setup (&plugin)) { @@ -289,10 +665,14 @@ libgnunet_plugin_peerstore_sqlite_init (void *cls) api = GNUNET_new (struct GNUNET_PEERSTORE_PluginFunctions); api->cls = &plugin; api->store_record = &peerstore_sqlite_store_record; - LOG(GNUNET_ERROR_TYPE_DEBUG, "Sqlite plugin is running\n"); + api->iterate_records = &peerstore_sqlite_iterate_records; + api->expire_records = &peerstore_sqlite_expire_records; + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Sqlite plugin is running\n"); return api; } + /** * Exit point from the plugin. * @@ -308,9 +688,9 @@ libgnunet_plugin_peerstore_sqlite_done (void *cls) database_shutdown (plugin); plugin->cfg = NULL; GNUNET_free (api); - LOG (GNUNET_ERROR_TYPE_DEBUG, "Sqlite plugin is finished\n"); + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Sqlite plugin is finished\n"); return NULL; - } /* end of plugin_peerstore_sqlite.c */