implementing 'get_closest' API for sqlite and postgres datacache plugins
[oweals/gnunet.git] / src / datacache / plugin_datacache_postgres.c
index 836a24251c420a41e8801348e3b05d7ec79da95b..1156d214e0106312bda5c79d979a8517921f2e61 100644 (file)
@@ -1,6 +1,6 @@
 /*
      This file is part of GNUnet
-     Copyright (C) 2006, 2009, 2010, 2012 Christian Grothoff (and other contributing authors)
+     Copyright (C) 2006, 2009, 2010, 2012, 2015 Christian Grothoff (and other contributing authors)
 
      GNUnet is free software; you can redistribute it and/or modify
      it under the terms of the GNU General Public License as published
@@ -156,6 +156,16 @@ init_connection (struct Plugin *plugin)
                                 "getm",
                                 "SELECT length(value),oid,key FROM gn090dc "
                                 "ORDER BY discard_time ASC LIMIT 1", 0)) ||
+      (GNUNET_OK !=
+       GNUNET_POSTGRES_prepare (plugin->dbh,
+                                "get_random",
+                                "SELECT discard_time,type,value,path,key FROM gn090dc "
+                                "ORDER BY key ASC LIMIT 1 OFFSET $1", 1)) ||
+      (GNUNET_OK !=
+       GNUNET_POSTGRES_prepare (plugin->dbh,
+                                "get_closest",
+                                "SELECT discard_time,type,value,path,key FROM gn090dc "
+                                "WHERE key>=$1 ORDER BY key ASC LIMIT $2", 1)) ||
       (GNUNET_OK !=
        GNUNET_POSTGRES_prepare (plugin->dbh,
                                 "delrow",
@@ -254,11 +264,11 @@ postgres_plugin_get (void *cls,
 
   const char *paramValues[] = {
     (const char *) key,
-    (const char *) &btype,
+    (const char *) &btype
   };
   int paramLengths[] = {
     sizeof (struct GNUNET_HashCode),
-    sizeof (btype),
+    sizeof (btype)
   };
   const int paramFormats[] = { 1, 1 };
   struct GNUNET_TIME_Absolute expiration_time;
@@ -404,6 +414,226 @@ postgres_plugin_del (void *cls)
 }
 
 
+/**
+ * Obtain a random key-value pair from the datacache.
+ *
+ * @param cls closure (our `struct Plugin`)
+ * @param iter maybe NULL (to just count)
+ * @param iter_cls closure for @a iter
+ * @return the number of results found, zero (datacache empty) or one
+ */
+static unsigned int
+postgres_plugin_get_random (void *cls,
+                            GNUNET_DATACACHE_Iterator iter,
+                            void *iter_cls)
+{
+  struct Plugin *plugin = cls;
+  unsigned int off;
+  uint32_t off_be;
+  struct GNUNET_TIME_Absolute expiration_time;
+  uint32_t size;
+  unsigned int path_len;
+  const struct GNUNET_PeerIdentity *path;
+  const struct GNUNET_HashCode *key;
+  unsigned int type;
+  PGresult *res;
+  const char *paramValues[] = {
+    (const char *) &off_be
+  };
+  int paramLengths[] = {
+    sizeof (off_be)
+  };
+  const int paramFormats[] = { 1 };
+
+  if (0 == plugin->num_items)
+    return 0;
+  if (NULL == iter)
+    return 1;
+  off = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_NONCE,
+                                  plugin->num_items);
+  off_be = htonl (off);
+  res =
+    PQexecPrepared (plugin->dbh, "get_random",
+                    1, paramValues, paramLengths, paramFormats,
+                    1);
+  if (GNUNET_OK !=
+      GNUNET_POSTGRES_check_result (plugin->dbh,
+                                    res,
+                                    PGRES_TUPLES_OK,
+                                    "PQexecPrepared",
+                                   "get_random"))
+  {
+    GNUNET_break (0);
+    return 0;
+  }
+  if (0 == PQntuples (res))
+  {
+    GNUNET_break (0);
+    return 0;
+  }
+  if ( (5 != PQnfields (res)) ||
+       (sizeof (uint64_t) != PQfsize (res, 0)) ||
+       (sizeof (uint32_t) != PQfsize (res, 1)) ||
+       (sizeof (struct GNUNET_HashCode) != PQfsize (res, 4)) )
+  {
+    GNUNET_break (0);
+    PQclear (res);
+    return 0;
+  }
+  expiration_time.abs_value_us =
+    GNUNET_ntohll (*(uint64_t *) PQgetvalue (res, 0, 0));
+  type = ntohl (*(uint32_t *) PQgetvalue (res, 0, 1));
+  size = PQgetlength (res, 0, 2);
+  path_len = PQgetlength (res, 0, 3);
+  if (0 != (path_len % sizeof (struct GNUNET_PeerIdentity)))
+  {
+    GNUNET_break (0);
+    path_len = 0;
+  }
+  path_len %= sizeof (struct GNUNET_PeerIdentity);
+  path = (const struct GNUNET_PeerIdentity *) PQgetvalue (res, 0, 3);
+  key = (const struct GNUNET_HashCode *) PQgetvalue (res, 0, 4);
+  LOG (GNUNET_ERROR_TYPE_DEBUG,
+       "Found random value with key %s of size %u bytes and type %u in database\n",
+       GNUNET_h2s (key),
+       (unsigned int) size,
+       (unsigned int) type);
+  (void) iter (iter_cls,
+               key,
+               size,
+               PQgetvalue (res, 0, 2),
+               (enum GNUNET_BLOCK_Type) type,
+               expiration_time,
+               path_len,
+               path);
+  PQclear (res);
+  return 1;
+}
+
+
+/**
+ * Iterate over the results that are "close" to a particular key in
+ * the datacache.  "close" is defined as numerically larger than @a
+ * key (when interpreted as a circular address space), with small
+ * distance.
+ *
+ * @param cls closure (internal context for the plugin)
+ * @param key area of the keyspace to look into
+ * @param num_results number of results that should be returned to @a iter
+ * @param iter maybe NULL (to just count)
+ * @param iter_cls closure for @a iter
+ * @return the number of results found
+ */
+static unsigned int
+postgres_plugin_get_closest (void *cls,
+                             const struct GNUNET_HashCode *key,
+                             unsigned int num_results,
+                             GNUNET_DATACACHE_Iterator iter,
+                             void *iter_cls)
+{
+  struct Plugin *plugin = cls;
+  uint32_t nbo_limit = htonl (num_results);
+  const char *paramValues[] = {
+    (const char *) key,
+    (const char *) &nbo_limit,
+  };
+  int paramLengths[] = {
+    sizeof (struct GNUNET_HashCode),
+    sizeof (nbo_limit)
+
+  };
+  const int paramFormats[] = { 1, 1 };
+  struct GNUNET_TIME_Absolute expiration_time;
+  uint32_t size;
+  unsigned int type;
+  unsigned int cnt;
+  unsigned int i;
+  unsigned int path_len;
+  const struct GNUNET_PeerIdentity *path;
+  PGresult *res;
+
+  res =
+      PQexecPrepared (plugin->dbh,
+                      "get_closest",
+                      2,
+                      paramValues,
+                      paramLengths,
+                      paramFormats,
+                      1);
+  if (GNUNET_OK !=
+      GNUNET_POSTGRES_check_result (plugin->dbh,
+                                    res,
+                                    PGRES_TUPLES_OK,
+                                    "PQexecPrepared",
+                                   "get_closest"))
+  {
+    LOG (GNUNET_ERROR_TYPE_DEBUG,
+        "Ending iteration (postgres error)\n");
+    return 0;
+  }
+
+  if (0 == (cnt = PQntuples (res)))
+  {
+    /* no result */
+    LOG (GNUNET_ERROR_TYPE_DEBUG,
+        "Ending iteration (no more results)\n");
+    PQclear (res);
+    return 0;
+  }
+  if (NULL == iter)
+  {
+    PQclear (res);
+    return cnt;
+  }
+  if ( (5 != PQnfields (res)) ||
+       (sizeof (uint64_t) != PQfsize (res, 0)) ||
+       (sizeof (uint32_t) != PQfsize (res, 1)) ||
+       (sizeof (struct GNUNET_HashCode) != PQfsize (res, 4)) )
+  {
+    GNUNET_break (0);
+    PQclear (res);
+    return 0;
+  }
+  for (i = 0; i < cnt; i++)
+  {
+    expiration_time.abs_value_us =
+        GNUNET_ntohll (*(uint64_t *) PQgetvalue (res, i, 0));
+    type = ntohl (*(uint32_t *) PQgetvalue (res, i, 1));
+    size = PQgetlength (res, i, 2);
+    path_len = PQgetlength (res, i, 3);
+    if (0 != (path_len % sizeof (struct GNUNET_PeerIdentity)))
+    {
+      GNUNET_break (0);
+      path_len = 0;
+    }
+    path_len %= sizeof (struct GNUNET_PeerIdentity);
+    path = (const struct GNUNET_PeerIdentity *) PQgetvalue (res, i, 3);
+    key = (const struct GNUNET_HashCode *) PQgetvalue (res, i, 4);
+    LOG (GNUNET_ERROR_TYPE_DEBUG,
+        "Found result of size %u bytes and type %u in database\n",
+        (unsigned int) size,
+         (unsigned int) type);
+    if (GNUNET_SYSERR ==
+        iter (iter_cls,
+              key,
+              size,
+              PQgetvalue (res, i, 2),
+              (enum GNUNET_BLOCK_Type) type,
+             expiration_time,
+             path_len,
+             path))
+    {
+      LOG (GNUNET_ERROR_TYPE_DEBUG,
+          "Ending iteration (client error)\n");
+      PQclear (res);
+      return cnt;
+    }
+  }
+  PQclear (res);
+  return cnt;
+}
+
+
 /**
  * Entry point for the plugin.
  *
@@ -431,6 +661,8 @@ libgnunet_plugin_datacache_postgres_init (void *cls)
   api->get = &postgres_plugin_get;
   api->put = &postgres_plugin_put;
   api->del = &postgres_plugin_del;
+  api->get_random = &postgres_plugin_get_random;
+  api->get_closest = &postgres_plugin_get_closest;
   LOG (GNUNET_ERROR_TYPE_INFO,
        "Postgres datacache running\n");
   return api;