2 This file is part of GNUnet
3 Copyright (C) 2006, 2009, 2015 Christian Grothoff (and other contributing authors)
5 GNUnet is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published
7 by the Free Software Foundation; either version 3, or (at your
8 option) any later version.
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with GNUnet; see the file COPYING. If not, write to the
17 Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18 Boston, MA 02111-1307, USA.
22 * @file datacache/plugin_datacache_sqlite.c
23 * @brief sqlite for an implementation of a database backend for the datacache
24 * @author Christian Grothoff
27 #include "gnunet_util_lib.h"
28 #include "gnunet_datacache_plugin.h"
31 #define LOG(kind,...) GNUNET_log_from (kind, "datacache-sqlite", __VA_ARGS__)
33 #define LOG_STRERROR_FILE(kind,op,fn) GNUNET_log_from_strerror_file (kind, "datacache-sqlite", op, fn)
37 * How much overhead do we assume per entry in the
40 #define OVERHEAD (sizeof(struct GNUNET_HashCode) + 32)
43 * Context for all functions in this plugin.
48 * Our execution environment.
50 struct GNUNET_DATACACHE_PluginEnvironment *env;
53 * Handle to the sqlite database.
58 * Filename used for the DB.
63 * Number of key-value pairs in the database.
65 unsigned int num_items;
70 * Log an error message at log-level 'level' that indicates
71 * a failure of the command 'cmd' on file 'filename'
72 * with the message given by strerror(errno).
74 #define LOG_SQLITE(db, level, cmd) do { LOG (level, _("`%s' failed at %s:%d with error: %s\n"), cmd, __FILE__, __LINE__, sqlite3_errmsg(db)); } while(0)
77 #define SQLITE3_EXEC(db, cmd) do { emsg = NULL; if (SQLITE_OK != sqlite3_exec(db, cmd, NULL, NULL, &emsg)) { LOG (GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, _("`%s' failed at %s:%d with error: %s\n"), "sqlite3_exec", __FILE__, __LINE__, emsg); sqlite3_free(emsg); } } while(0)
81 * @brief Prepare a SQL statement
84 sq_prepare (sqlite3 * dbh, const char *zSql, /* SQL statement, UTF-8 encoded */
85 sqlite3_stmt ** ppStmt)
86 { /* OUT: Statement handle */
89 return sqlite3_prepare (dbh, zSql, strlen (zSql), ppStmt,
90 (const char **) &dummy);
95 * Store an item in the datastore.
97 * @param cls closure (our "struct Plugin")
98 * @param key key to store data under
99 * @param size number of bytes in data
100 * @param data data to store
101 * @param type type of the value
102 * @param discard_time when to discard the value in any case
103 * @param path_info_len number of entries in @a path_info
104 * @param path_info array of peers that have processed the request
105 * @return 0 if duplicate, -1 on error, number of bytes used otherwise
108 sqlite_plugin_put (void *cls,
109 const struct GNUNET_HashCode *key,
110 size_t size, const char *data,
111 enum GNUNET_BLOCK_Type type,
112 struct GNUNET_TIME_Absolute discard_time,
113 unsigned int path_info_len,
114 const struct GNUNET_PeerIdentity *path_info)
116 struct Plugin *plugin = cls;
120 LOG (GNUNET_ERROR_TYPE_DEBUG,
121 "Processing `%s' of %u bytes with key `%4s' and expiration %s\n",
122 "PUT", (unsigned int) size, GNUNET_h2s (key),
123 GNUNET_STRINGS_relative_time_to_string (GNUNET_TIME_absolute_get_remaining (discard_time), GNUNET_YES));
124 dval = (int64_t) discard_time.abs_value_us;
129 "INSERT INTO ds090 (type, expire, key, value, path) VALUES (?, ?, ?, ?, ?)",
132 LOG_SQLITE (plugin->dbh, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
136 if ((SQLITE_OK != sqlite3_bind_int (stmt, 1, type)) ||
137 (SQLITE_OK != sqlite3_bind_int64 (stmt, 2, dval)) ||
139 sqlite3_bind_blob (stmt, 3,
140 key, sizeof (struct GNUNET_HashCode),
141 SQLITE_TRANSIENT)) ||
142 (SQLITE_OK != sqlite3_bind_blob (stmt, 4,
144 SQLITE_TRANSIENT)) ||
145 (SQLITE_OK != sqlite3_bind_blob (stmt, 5,
147 path_info_len * sizeof (struct GNUNET_PeerIdentity),
150 LOG_SQLITE (plugin->dbh, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
152 sqlite3_finalize (stmt);
155 if (SQLITE_DONE != sqlite3_step (stmt))
157 LOG_SQLITE (plugin->dbh, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
159 sqlite3_finalize (stmt);
163 if (SQLITE_OK != sqlite3_finalize (stmt))
164 LOG_SQLITE (plugin->dbh, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
166 return size + OVERHEAD;
171 * Iterate over the results for a particular key
174 * @param cls closure (our "struct Plugin")
176 * @param type entries of which type are relevant?
177 * @param iter maybe NULL (to just count)
178 * @param iter_cls closure for iter
179 * @return the number of results found
182 sqlite_plugin_get (void *cls, const struct GNUNET_HashCode * key,
183 enum GNUNET_BLOCK_Type type, GNUNET_DATACACHE_Iterator iter,
186 struct Plugin *plugin = cls;
188 struct GNUNET_TIME_Absolute now;
189 struct GNUNET_TIME_Absolute exp;
198 const struct GNUNET_PeerIdentity *path;
200 now = GNUNET_TIME_absolute_get ();
201 LOG (GNUNET_ERROR_TYPE_DEBUG, "Processing `%s' for key `%4s'\n", "GET",
205 "SELECT count(*) FROM ds090 WHERE key=? AND type=? AND expire >= ?",
208 LOG_SQLITE (plugin->dbh, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
212 ntime = (int64_t) now.abs_value_us;
213 GNUNET_assert (ntime >= 0);
215 sqlite3_bind_blob (stmt, 1, key, sizeof (struct GNUNET_HashCode),
216 SQLITE_TRANSIENT)) ||
217 (SQLITE_OK != sqlite3_bind_int (stmt, 2, type)) ||
218 (SQLITE_OK != sqlite3_bind_int64 (stmt, 3, now.abs_value_us)))
220 LOG_SQLITE (plugin->dbh, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
222 sqlite3_finalize (stmt);
226 if (SQLITE_ROW != sqlite3_step (stmt))
228 LOG_SQLITE (plugin->dbh, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
230 sqlite3_finalize (stmt);
231 LOG (GNUNET_ERROR_TYPE_DEBUG,
232 "No content found when processing `%s' for key `%4s'\n", "GET",
236 total = sqlite3_column_int (stmt, 0);
237 sqlite3_finalize (stmt);
238 if ((total == 0) || (iter == NULL))
241 LOG (GNUNET_ERROR_TYPE_DEBUG,
242 "No content found when processing `%s' for key `%4s'\n", "GET",
248 off = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, total);
251 off = (off + 1) % total;
252 GNUNET_snprintf (scratch, sizeof (scratch),
253 "SELECT value,expire,path FROM ds090 WHERE key=? AND type=? AND expire >= ? LIMIT 1 OFFSET %u",
255 if (sq_prepare (plugin->dbh, scratch, &stmt) != SQLITE_OK)
257 LOG_SQLITE (plugin->dbh, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
262 sqlite3_bind_blob (stmt, 1, key, sizeof (struct GNUNET_HashCode),
263 SQLITE_TRANSIENT)) ||
264 (SQLITE_OK != sqlite3_bind_int (stmt, 2, type)) ||
265 (SQLITE_OK != sqlite3_bind_int64 (stmt, 3, now.abs_value_us)))
267 LOG_SQLITE (plugin->dbh, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
269 sqlite3_finalize (stmt);
272 if (sqlite3_step (stmt) != SQLITE_ROW)
274 size = sqlite3_column_bytes (stmt, 0);
275 dat = sqlite3_column_blob (stmt, 0);
276 exp.abs_value_us = sqlite3_column_int64 (stmt, 1);
277 psize = sqlite3_column_bytes (stmt, 2);
278 if (0 != psize % sizeof (struct GNUNET_PeerIdentity))
283 psize /= sizeof (struct GNUNET_PeerIdentity);
285 path = sqlite3_column_blob (stmt, 2);
288 ntime = (int64_t) exp.abs_value_us;
289 if (ntime == INT64_MAX)
290 exp = GNUNET_TIME_UNIT_FOREVER_ABS;
292 LOG (GNUNET_ERROR_TYPE_DEBUG,
293 "Found %u-byte result when processing `%s' for key `%4s'\n",
294 (unsigned int) size, "GET", GNUNET_h2s (key));
295 if (GNUNET_OK != iter (iter_cls, key, size, dat, type, exp, psize, path))
297 sqlite3_finalize (stmt);
300 sqlite3_finalize (stmt);
307 * Delete the entry with the lowest expiration value
308 * from the datacache right now.
310 * @param cls closure (our "struct Plugin")
311 * @return GNUNET_OK on success, GNUNET_SYSERR on error
314 sqlite_plugin_del (void *cls)
316 struct Plugin *plugin = cls;
317 unsigned long long rowid;
321 struct GNUNET_HashCode hc;
323 LOG (GNUNET_ERROR_TYPE_DEBUG, "Processing `%s'\n", "DEL");
328 "SELECT _ROWID_,key,value FROM ds090 ORDER BY expire ASC LIMIT 1",
331 LOG_SQLITE (plugin->dbh, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
334 (void) sqlite3_finalize (stmt);
335 return GNUNET_SYSERR;
337 if (SQLITE_ROW != sqlite3_step (stmt))
339 LOG_SQLITE (plugin->dbh, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
341 (void) sqlite3_finalize (stmt);
342 return GNUNET_SYSERR;
344 rowid = sqlite3_column_int64 (stmt, 0);
345 GNUNET_assert (sqlite3_column_bytes (stmt, 1) == sizeof (struct GNUNET_HashCode));
346 memcpy (&hc, sqlite3_column_blob (stmt, 1), sizeof (struct GNUNET_HashCode));
347 dsize = sqlite3_column_bytes (stmt, 2);
348 if (SQLITE_OK != sqlite3_finalize (stmt))
349 LOG_SQLITE (plugin->dbh, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
351 if (sq_prepare (plugin->dbh, "DELETE FROM ds090 WHERE _ROWID_=?", &dstmt) !=
354 LOG_SQLITE (plugin->dbh, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
357 (void) sqlite3_finalize (stmt);
358 return GNUNET_SYSERR;
360 if (SQLITE_OK != sqlite3_bind_int64 (dstmt, 1, rowid))
362 LOG_SQLITE (plugin->dbh, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
364 (void) sqlite3_finalize (dstmt);
365 return GNUNET_SYSERR;
367 if (sqlite3_step (dstmt) != SQLITE_DONE)
369 LOG_SQLITE (plugin->dbh, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
371 (void) sqlite3_finalize (dstmt);
372 return GNUNET_SYSERR;
375 plugin->env->delete_notify (plugin->env->cls, &hc, dsize + OVERHEAD);
376 if (SQLITE_OK != sqlite3_finalize (dstmt))
377 LOG_SQLITE (plugin->dbh, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
384 * Entry point for the plugin.
386 * @param cls closure (the "struct GNUNET_DATACACHE_PluginEnvironmnet")
387 * @return the plugin's closure (our "struct Plugin")
390 libgnunet_plugin_datacache_sqlite_init (void *cls)
392 struct GNUNET_DATACACHE_PluginEnvironment *env = cls;
393 struct GNUNET_DATACACHE_PluginFunctions *api;
394 struct Plugin *plugin;
401 GNUNET_CONFIGURATION_get_value_yesno (env->cfg,
405 if (SQLITE_OK != sqlite3_open (":memory:", &dbh))
411 fn = GNUNET_DISK_mktemp ("gnunet-datacache");
417 /* fn should be UTF-8-encoded. If it isn't, it's a bug. */
418 fn_utf8 = GNUNET_strdup (fn);
419 if (SQLITE_OK != sqlite3_open (fn_utf8, &dbh))
422 GNUNET_free (fn_utf8);
428 SQLITE3_EXEC (dbh, "PRAGMA temp_store=MEMORY");
429 SQLITE3_EXEC (dbh, "PRAGMA locking_mode=EXCLUSIVE");
430 SQLITE3_EXEC (dbh, "PRAGMA journal_mode=OFF");
431 SQLITE3_EXEC (dbh, "PRAGMA synchronous=OFF");
432 SQLITE3_EXEC (dbh, "PRAGMA page_size=4092");
434 GNUNET_CONFIGURATION_get_value_yesno (env->cfg,
437 SQLITE3_EXEC (dbh, "PRAGMA sqlite_temp_store=3");
440 "CREATE TABLE ds090 (" " type INTEGER NOT NULL DEFAULT 0,"
441 " expire INTEGER NOT NULL DEFAULT 0,"
442 " key BLOB NOT NULL DEFAULT '',"
443 " value BLOB NOT NULL DEFAULT '',"
444 " path BLOB DEFAULT '')");
445 SQLITE3_EXEC (dbh, "CREATE INDEX idx_hashidx ON ds090 (key,type,expire)");
446 SQLITE3_EXEC (dbh, "CREATE INDEX idx_expire ON ds090 (expire)");
447 plugin = GNUNET_new (struct Plugin);
450 plugin->fn = fn_utf8;
451 api = GNUNET_new (struct GNUNET_DATACACHE_PluginFunctions);
453 api->get = &sqlite_plugin_get;
454 api->put = &sqlite_plugin_put;
455 api->del = &sqlite_plugin_del;
456 LOG (GNUNET_ERROR_TYPE_INFO, _("Sqlite datacache running\n"));
462 * Exit point from the plugin.
464 * @param cls closure (our "struct Plugin")
468 libgnunet_plugin_datacache_sqlite_done (void *cls)
470 struct GNUNET_DATACACHE_PluginFunctions *api = cls;
471 struct Plugin *plugin = api->cls;
474 #if SQLITE_VERSION_NUMBER >= 3007000
478 #if !WINDOWS || defined(__CYGWIN__)
479 if ( (NULL != plugin->fn) &&
480 (0 != UNLINK (plugin->fn)) )
481 LOG_STRERROR_FILE (GNUNET_ERROR_TYPE_WARNING, "unlink", plugin->fn);
482 GNUNET_free_non_null (plugin->fn);
484 result = sqlite3_close (plugin->dbh);
485 #if SQLITE_VERSION_NUMBER >= 3007000
486 if (result == SQLITE_BUSY)
488 LOG (GNUNET_ERROR_TYPE_WARNING,
490 ("Tried to close sqlite without finalizing all prepared statements.\n"));
491 stmt = sqlite3_next_stmt (plugin->dbh, NULL);
494 LOG (GNUNET_ERROR_TYPE_DEBUG, "Closing statement %p\n", stmt);
495 result = sqlite3_finalize (stmt);
496 if (result != SQLITE_OK)
497 LOG (GNUNET_ERROR_TYPE_WARNING, _("Failed to close statement %p: %d\n"),
499 stmt = sqlite3_next_stmt (plugin->dbh, NULL);
501 result = sqlite3_close (plugin->dbh);
504 if (SQLITE_OK != result)
505 LOG_SQLITE (plugin->dbh, GNUNET_ERROR_TYPE_ERROR, "sqlite3_close");
507 #if WINDOWS && !defined(__CYGWIN__)
508 if ( (NULL != plugin->fn) &&
509 (0 != UNLINK (plugin->fn)) )
510 LOG_STRERROR_FILE (GNUNET_ERROR_TYPE_WARNING, "unlink", plugin->fn);
511 GNUNET_free_non_null (plugin->fn);
513 GNUNET_free (plugin);
520 /* end of plugin_datacache_sqlite.c */