/** * Get the block for a particular zone and label in the * datastore. Will return at most one result to the iterator. * * @param cls closure (internal context for the plugin) * @param query hash of public key derived from the zone and the label * @param iter function to call with the result * @param iter_cls closure for @a iter * @return #GNUNET_OK on success, #GNUNET_NO if there were no results, #GNUNET_SYSERR on error */ static int namecache_postgres_lookup_block (void *cls, const struct GNUNET_HashCode *query, GNUNET_NAMECACHE_BlockCallback iter, void *iter_cls) { struct Plugin *plugin = cls; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (query), GNUNET_PQ_query_param_end }; PGresult *res; unsigned int cnt; size_t bsize; const struct GNUNET_GNSRECORD_Block *block; res = GNUNET_PQ_exec_prepared (plugin->dbh, "lookup_block", params); if (GNUNET_OK != GNUNET_POSTGRES_check_result (plugin->dbh, res, PGRES_TUPLES_OK, "PQexecPrepared", "lookup_block")) { LOG (GNUNET_ERROR_TYPE_DEBUG, "Failing lookup (postgres error)\n"); return GNUNET_SYSERR; } if (0 == (cnt = PQntuples (res))) { /* no result */ LOG (GNUNET_ERROR_TYPE_DEBUG, "Ending iteration (no more results)\n"); PQclear (res); return GNUNET_NO; } GNUNET_assert (1 == cnt); GNUNET_assert (1 != PQnfields (res)); bsize = PQgetlength (res, 0, 0); block = (const struct GNUNET_GNSRECORD_Block *) PQgetvalue (res, 0, 0); if ( (bsize < sizeof (*block)) || (bsize != ntohl (block->purpose.size) + sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey) + sizeof (struct GNUNET_CRYPTO_EcdsaSignature)) ) { GNUNET_break (0); LOG (GNUNET_ERROR_TYPE_DEBUG, "Failing lookup (corrupt block)\n"); PQclear (res); return GNUNET_SYSERR; } iter (iter_cls, block); PQclear (res); return GNUNET_OK; }
/** * Delete the entry with the lowest expiration value * from the datacache right now. * * @param cls closure (our `struct Plugin`) * @return #GNUNET_OK on success, #GNUNET_SYSERR on error */ static int postgres_plugin_del (void *cls) { struct Plugin *plugin = cls; uint32_t size; uint32_t oid; struct GNUNET_HashCode key; PGresult *res; res = PQexecPrepared (plugin->dbh, "getm", 0, NULL, NULL, NULL, 1); if (GNUNET_OK != GNUNET_POSTGRES_check_result (plugin->dbh, res, PGRES_TUPLES_OK, "PQexecPrepared", "getm")) { LOG (GNUNET_ERROR_TYPE_DEBUG, "Ending iteration (postgres error)\n"); return 0; } if (0 == PQntuples (res)) { /* no result */ LOG (GNUNET_ERROR_TYPE_DEBUG, "Ending iteration (no more results)\n"); PQclear (res); return GNUNET_SYSERR; } if ((3 != PQnfields (res)) || (sizeof (size) != PQfsize (res, 0)) || (sizeof (oid) != PQfsize (res, 1)) || (sizeof (struct GNUNET_HashCode) != PQgetlength (res, 0, 2))) { GNUNET_break (0); PQclear (res); return 0; } size = ntohl (*(uint32_t *) PQgetvalue (res, 0, 0)); oid = ntohl (*(uint32_t *) PQgetvalue (res, 0, 1)); memcpy (&key, PQgetvalue (res, 0, 2), sizeof (struct GNUNET_HashCode)); PQclear (res); if (GNUNET_OK != GNUNET_POSTGRES_delete_by_rowid (plugin->dbh, "delrow", oid)) return GNUNET_SYSERR; plugin->num_items--; plugin->env->delete_notify (plugin->env->cls, &key, size + OVERHEAD); return GNUNET_OK; }
/** * Store an item in the datastore. * * @param cls closure with the `struct Plugin` * @param key key for the item * @param size number of bytes in data * @param data content stored * @param type type of the content * @param priority priority of the content * @param anonymity anonymity-level for the content * @param replication replication-level for the content * @param expiration expiration time for the content * @param cont continuation called with success or failure status * @param cont_cls continuation closure */ static void postgres_plugin_put (void *cls, const struct GNUNET_HashCode *key, uint32_t size, const void *data, enum GNUNET_BLOCK_Type type, uint32_t priority, uint32_t anonymity, uint32_t replication, struct GNUNET_TIME_Absolute expiration, PluginPutCont cont, void *cont_cls) { struct Plugin *plugin = cls; uint32_t utype = type; struct GNUNET_HashCode vhash; PGresult *ret; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_uint32 (&replication), GNUNET_PQ_query_param_uint32 (&utype), GNUNET_PQ_query_param_uint32 (&priority), GNUNET_PQ_query_param_uint32 (&anonymity), GNUNET_PQ_query_param_absolute_time (&expiration), GNUNET_PQ_query_param_auto_from_type (key), GNUNET_PQ_query_param_auto_from_type (&vhash), GNUNET_PQ_query_param_fixed_size (data, size), GNUNET_PQ_query_param_end }; GNUNET_CRYPTO_hash (data, size, &vhash); ret = GNUNET_PQ_exec_prepared (plugin->dbh, "put", params); if (GNUNET_OK != GNUNET_POSTGRES_check_result (plugin->dbh, ret, PGRES_COMMAND_OK, "PQexecPrepared", "put")) { cont (cont_cls, key, size, GNUNET_SYSERR, _("Postgress exec failure")); return; } PQclear (ret); plugin->env->duc (plugin->env->cls, size + GNUNET_DATASTORE_ENTRY_OVERHEAD); GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "datastore-postgres", "Stored %u bytes in database\n", (unsigned int) size); cont (cont_cls, key, size, GNUNET_OK, NULL); }
/** * Wrapper for the iterator for 'sqlite_plugin_replication_get'. * Decrements the replication counter and calls the original * iterator. * * @param cls closure with the `struct ReplCtx *` * @param key key for the content * @param size number of bytes in @a data * @param data content stored * @param type type of the content * @param priority priority of the content * @param anonymity anonymity-level for the content * @param expiration expiration time for the content * @param uid unique identifier for the datum; * maybe 0 if no unique identifier is available * @return #GNUNET_SYSERR to abort the iteration, * #GNUNET_OK to continue * (continue on call to "next", of course), * #GNUNET_NO to delete the item and continue (if supported) */ static int repl_proc (void *cls, const struct GNUNET_HashCode *key, uint32_t size, const void *data, enum GNUNET_BLOCK_Type type, uint32_t priority, uint32_t anonymity, struct GNUNET_TIME_Absolute expiration, uint64_t uid) { struct ReplCtx *rc = cls; struct Plugin *plugin = rc->plugin; int ret; uint32_t oid = (uint32_t) uid; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_uint32 (&oid), GNUNET_PQ_query_param_end }; PGresult *qret; ret = rc->proc (rc->proc_cls, key, size, data, type, priority, anonymity, expiration, uid); if (NULL == key) return ret; qret = GNUNET_PQ_exec_prepared (plugin->dbh, "decrepl", params); if (GNUNET_OK != GNUNET_POSTGRES_check_result (plugin->dbh, qret, PGRES_COMMAND_OK, "PQexecPrepared", "decrepl")) return GNUNET_SYSERR; PQclear (qret); return ret; }
/** * Store an item in the datastore. * * @param cls closure (our `struct Plugin`) * @param key key to store @a data under * @param size number of bytes in @a data * @param data data to store * @param type type of the value * @param discard_time when to discard the value in any case * @param path_info_len number of entries in @a path_info * @param path_info a path through the network * @return 0 if duplicate, -1 on error, number of bytes used otherwise */ static ssize_t postgres_plugin_put (void *cls, const struct GNUNET_HashCode *key, size_t size, const char *data, enum GNUNET_BLOCK_Type type, struct GNUNET_TIME_Absolute discard_time, unsigned int path_info_len, const struct GNUNET_PeerIdentity *path_info) { struct Plugin *plugin = cls; PGresult *ret; uint32_t btype = htonl (type); uint64_t bexpi = GNUNET_TIME_absolute_hton (discard_time).abs_value_us__; const char *paramValues[] = { (const char *) &btype, (const char *) &bexpi, (const char *) key, (const char *) data, (const char *) path_info }; int paramLengths[] = { sizeof (btype), sizeof (bexpi), sizeof (struct GNUNET_HashCode), size, path_info_len * sizeof (struct GNUNET_PeerIdentity) }; const int paramFormats[] = { 1, 1, 1, 1, 1 }; ret = PQexecPrepared (plugin->dbh, "put", 5, paramValues, paramLengths, paramFormats, 1); if (GNUNET_OK != GNUNET_POSTGRES_check_result (plugin->dbh, ret, PGRES_COMMAND_OK, "PQexecPrepared", "put")) return -1; plugin->num_items++; PQclear (ret); return size + OVERHEAD; }
/** * Get an estimate of how much space the database is * currently using. * * @param cls our `struct Plugin *` * @return number of bytes used on disk */ static void postgres_plugin_estimate_size (void *cls, unsigned long long *estimate) { struct Plugin *plugin = cls; unsigned long long total; PGresult *ret; if (NULL == estimate) return; ret = PQexecParams (plugin->dbh, "SELECT SUM(LENGTH(value))+256*COUNT(*) FROM gn090", 0, NULL, NULL, NULL, NULL, 1); if (GNUNET_OK != GNUNET_POSTGRES_check_result (plugin->dbh, ret, PGRES_TUPLES_OK, "PQexecParams", "get_size")) { *estimate = 0; return; } if ((PQntuples (ret) != 1) || (PQnfields (ret) != 1) ) { GNUNET_break (0); PQclear (ret); *estimate = 0; return; } if (PQgetlength (ret, 0, 0) != sizeof (unsigned long long)) { GNUNET_break (0 == PQgetlength (ret, 0, 0)); PQclear (ret); *estimate = 0; return; } total = GNUNET_ntohll (*(const unsigned long long *) PQgetvalue (ret, 0, 0)); PQclear (ret); *estimate = total; }
/** * Cache a block in the datastore. * * @param cls closure (internal context for the plugin) * @param block block to cache * @return #GNUNET_OK on success, else #GNUNET_SYSERR */ static int namecache_postgres_cache_block (void *cls, const struct GNUNET_GNSRECORD_Block *block) { struct Plugin *plugin = cls; struct GNUNET_HashCode query; size_t block_size = ntohl (block->purpose.size) + sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey) + sizeof (struct GNUNET_CRYPTO_EcdsaSignature); struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (&query), GNUNET_PQ_query_param_fixed_size (block, block_size), GNUNET_PQ_query_param_absolute_time_nbo (&block->expiration_time), GNUNET_PQ_query_param_end }; PGresult *res; namecache_postgres_expire_blocks (plugin); GNUNET_CRYPTO_hash (&block->derived_key, sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey), &query); if (block_size > 64 * 65536) { GNUNET_break (0); return GNUNET_SYSERR; } delete_old_block (plugin, &query, block->expiration_time); res = GNUNET_PQ_exec_prepared (plugin->dbh, "cache_block", params); if (GNUNET_OK != GNUNET_POSTGRES_check_result (plugin->dbh, res, PGRES_COMMAND_OK, "PQexecPrepared", "cache_block")) return GNUNET_SYSERR; PQclear (res); return GNUNET_OK; }
/** * Update the priority for a particular key in the datastore. If * the expiration time in value is different than the time found in * the datastore, the higher value should be kept. For the * anonymity level, the lower value is to be used. The specified * priority should be added to the existing priority, ignoring the * priority in value. * * Note that it is possible for multiple values to match this put. * In that case, all of the respective values are updated. * * @param cls our `struct Plugin *` * @param uid unique identifier of the datum * @param delta by how much should the priority * change? If priority + delta < 0 the * priority should be set to 0 (never go * negative). * @param expire new expiration time should be the * MAX of any existing expiration time and * this value * @param cont continuation called with success or failure status * @param cons_cls continuation closure */ static void postgres_plugin_update (void *cls, uint64_t uid, int delta, struct GNUNET_TIME_Absolute expire, PluginUpdateCont cont, void *cont_cls) { struct Plugin *plugin = cls; uint32_t idelta = delta; uint32_t oid = (uint32_t) uid; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_uint32 (&idelta), GNUNET_PQ_query_param_absolute_time (&expire), GNUNET_PQ_query_param_uint32 (&oid), GNUNET_PQ_query_param_end }; PGresult *ret; ret = GNUNET_PQ_exec_prepared (plugin->dbh, "update", params); if (GNUNET_OK != GNUNET_POSTGRES_check_result (plugin->dbh, ret, PGRES_COMMAND_OK, "PQexecPrepared", "update")) { cont (cont_cls, GNUNET_SYSERR, NULL); return; } PQclear (ret); cont (cont_cls, GNUNET_OK, NULL); }
/** * Removes any expired block. * * @param plugin the plugin */ static void namecache_postgres_expire_blocks (struct Plugin *plugin) { struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get (); struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_absolute_time (&now), GNUNET_PQ_query_param_end }; PGresult *res; res = GNUNET_PQ_exec_prepared (plugin->dbh, "expire_blocks", params); if (GNUNET_OK != GNUNET_POSTGRES_check_result (plugin->dbh, res, PGRES_COMMAND_OK, "PQexecPrepared", "expire_blocks")) return; PQclear (res); }
/** * Delete older block in the datastore. * * @param plugin the plugin * @param query query for the block * @param expiration_time how old does the block have to be for deletion */ static void delete_old_block (struct Plugin *plugin, const struct GNUNET_HashCode *query, struct GNUNET_TIME_AbsoluteNBO expiration_time) { struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (query), GNUNET_PQ_query_param_absolute_time_nbo (&expiration_time), GNUNET_PQ_query_param_end }; PGresult *res; res = GNUNET_PQ_exec_prepared (plugin->dbh, "delete_block", params); if (GNUNET_OK != GNUNET_POSTGRES_check_result (plugin->dbh, res, PGRES_COMMAND_OK, "PQexecPrepared", "delete_block")) return; PQclear (res); }
/** * @brief Get a database handle * * @param plugin global context * @return #GNUNET_OK on success, #GNUNET_SYSERR on error */ static int init_connection (struct Plugin *plugin) { PGresult *ret; plugin->dbh = GNUNET_POSTGRES_connect (plugin->env->cfg, "datastore-postgres"); if (NULL == plugin->dbh) return GNUNET_SYSERR; ret = PQexec (plugin->dbh, "CREATE TABLE gn090 (" " repl INTEGER NOT NULL DEFAULT 0," " type INTEGER NOT NULL DEFAULT 0," " prio INTEGER NOT NULL DEFAULT 0," " anonLevel INTEGER NOT NULL DEFAULT 0," " expire BIGINT NOT NULL DEFAULT 0," " rvalue BIGINT NOT NULL DEFAULT 0," " hash BYTEA NOT NULL DEFAULT ''," " vhash BYTEA NOT NULL DEFAULT ''," " value BYTEA NOT NULL DEFAULT '')" "WITH OIDS"); if ( (NULL == ret) || ((PQresultStatus (ret) != PGRES_COMMAND_OK) && (0 != strcmp ("42P07", /* duplicate table */ PQresultErrorField (ret, PG_DIAG_SQLSTATE))))) { (void) GNUNET_POSTGRES_check_result (plugin->dbh, ret, PGRES_COMMAND_OK, "CREATE TABLE", "gn090"); PQfinish (plugin->dbh); plugin->dbh = NULL; return GNUNET_SYSERR; } if (PQresultStatus (ret) == PGRES_COMMAND_OK) { if ((GNUNET_OK != GNUNET_POSTGRES_exec (plugin->dbh, "CREATE INDEX idx_hash ON gn090 (hash)")) || (GNUNET_OK != GNUNET_POSTGRES_exec (plugin->dbh, "CREATE INDEX idx_hash_vhash ON gn090 (hash,vhash)")) || (GNUNET_OK != GNUNET_POSTGRES_exec (plugin->dbh, "CREATE INDEX idx_prio ON gn090 (prio)")) || (GNUNET_OK != GNUNET_POSTGRES_exec (plugin->dbh, "CREATE INDEX idx_expire ON gn090 (expire)")) || (GNUNET_OK != GNUNET_POSTGRES_exec (plugin->dbh, "CREATE INDEX idx_prio_anon ON gn090 (prio,anonLevel)")) || (GNUNET_OK != GNUNET_POSTGRES_exec (plugin->dbh, "CREATE INDEX idx_prio_hash_anon ON gn090 (prio,hash,anonLevel)")) || (GNUNET_OK != GNUNET_POSTGRES_exec (plugin->dbh, "CREATE INDEX idx_repl_rvalue ON gn090 (repl,rvalue)")) || (GNUNET_OK != GNUNET_POSTGRES_exec (plugin->dbh, "CREATE INDEX idx_expire_hash ON gn090 (expire,hash)"))) { PQclear (ret); PQfinish (plugin->dbh); plugin->dbh = NULL; return GNUNET_SYSERR; } } PQclear (ret); ret = PQexec (plugin->dbh, "ALTER TABLE gn090 ALTER value SET STORAGE EXTERNAL"); if (GNUNET_OK != GNUNET_POSTGRES_check_result (plugin->dbh, ret, PGRES_COMMAND_OK, "ALTER TABLE", "gn090")) { PQfinish (plugin->dbh); plugin->dbh = NULL; return GNUNET_SYSERR; } PQclear (ret); ret = PQexec (plugin->dbh, "ALTER TABLE gn090 ALTER hash SET STORAGE PLAIN"); if (GNUNET_OK != GNUNET_POSTGRES_check_result (plugin->dbh, ret, PGRES_COMMAND_OK, "ALTER TABLE", "gn090")) { PQfinish (plugin->dbh); plugin->dbh = NULL; return GNUNET_SYSERR; } PQclear (ret); ret = PQexec (plugin->dbh, "ALTER TABLE gn090 ALTER vhash SET STORAGE PLAIN"); if (GNUNET_OK != GNUNET_POSTGRES_check_result (plugin->dbh, ret, PGRES_COMMAND_OK, "ALTER TABLE", "gn090")) { PQfinish (plugin->dbh); plugin->dbh = NULL; return GNUNET_SYSERR; } PQclear (ret); if ((GNUNET_OK != GNUNET_POSTGRES_prepare (plugin->dbh, "getvt", "SELECT type, prio, anonLevel, expire, hash, value, oid FROM gn090 " "WHERE hash=$1 AND vhash=$2 AND type=$3 " "ORDER BY oid ASC LIMIT 1 OFFSET $4", 4)) || (GNUNET_OK != GNUNET_POSTGRES_prepare (plugin->dbh, "gett", "SELECT type, prio, anonLevel, expire, hash, value, oid FROM gn090 " "WHERE hash=$1 AND type=$2 " "ORDER BY oid ASC LIMIT 1 OFFSET $3", 3)) || (GNUNET_OK != GNUNET_POSTGRES_prepare (plugin->dbh, "getv", "SELECT type, prio, anonLevel, expire, hash, value, oid FROM gn090 " "WHERE hash=$1 AND vhash=$2 " "ORDER BY oid ASC LIMIT 1 OFFSET $3", 3)) || (GNUNET_OK != GNUNET_POSTGRES_prepare (plugin->dbh, "get", "SELECT type, prio, anonLevel, expire, hash, value, oid FROM gn090 " "WHERE hash=$1 " "ORDER BY oid ASC LIMIT 1 OFFSET $2", 2)) || (GNUNET_OK != GNUNET_POSTGRES_prepare (plugin->dbh, "count_getvt", "SELECT count(*) FROM gn090 WHERE hash=$1 AND vhash=$2 AND type=$3", 3)) || (GNUNET_OK != GNUNET_POSTGRES_prepare (plugin->dbh, "count_gett", "SELECT count(*) FROM gn090 WHERE hash=$1 AND type=$2", 2)) || (GNUNET_OK != GNUNET_POSTGRES_prepare (plugin->dbh, "count_getv", "SELECT count(*) FROM gn090 WHERE hash=$1 AND vhash=$2", 2)) || (GNUNET_OK != GNUNET_POSTGRES_prepare (plugin->dbh, "count_get", "SELECT count(*) FROM gn090 WHERE hash=$1", 1)) || (GNUNET_OK != GNUNET_POSTGRES_prepare (plugin->dbh, "put", "INSERT INTO gn090 (repl, type, prio, anonLevel, expire, rvalue, hash, vhash, value) " "VALUES ($1, $2, $3, $4, $5, RANDOM(), $6, $7, $8)", 9)) || (GNUNET_OK != GNUNET_POSTGRES_prepare (plugin->dbh, "update", "UPDATE gn090 SET prio = prio + $1, expire = CASE WHEN expire < $2 THEN $2 ELSE expire END " "WHERE oid = $3", 3)) || (GNUNET_OK != GNUNET_POSTGRES_prepare (plugin->dbh, "decrepl", "UPDATE gn090 SET repl = GREATEST (repl - 1, 0) " "WHERE oid = $1", 1)) || (GNUNET_OK != GNUNET_POSTGRES_prepare (plugin->dbh, "select_non_anonymous", "SELECT type, prio, anonLevel, expire, hash, value, oid FROM gn090 " "WHERE anonLevel = 0 AND type = $1 ORDER BY oid DESC LIMIT 1 OFFSET $2", 1)) || (GNUNET_OK != GNUNET_POSTGRES_prepare (plugin->dbh, "select_expiration_order", "(SELECT type, prio, anonLevel, expire, hash, value, oid FROM gn090 " "WHERE expire < $1 ORDER BY prio ASC LIMIT 1) " "UNION " "(SELECT type, prio, anonLevel, expire, hash, value, oid FROM gn090 " "ORDER BY prio ASC LIMIT 1) " "ORDER BY expire ASC LIMIT 1", 1)) || (GNUNET_OK != GNUNET_POSTGRES_prepare (plugin->dbh, "select_replication_order", "SELECT type, prio, anonLevel, expire, hash, value, oid FROM gn090 " "ORDER BY repl DESC,RANDOM() LIMIT 1", 0)) || (GNUNET_OK != GNUNET_POSTGRES_prepare (plugin->dbh, "delrow", "DELETE FROM gn090 " "WHERE oid=$1", 1)) || (GNUNET_OK != GNUNET_POSTGRES_prepare (plugin->dbh, "get_keys", "SELECT hash FROM gn090", 0))) { PQfinish (plugin->dbh); plugin->dbh = NULL; return GNUNET_SYSERR; } return GNUNET_OK; }
/** * Iterate over the results for a particular key * in the datastore. * * @param cls closure with the 'struct Plugin' * @param offset offset of the result (modulo num-results); * specific ordering does not matter for the offset * @param key maybe NULL (to match all entries) * @param vhash hash of the value, maybe NULL (to * match all values that have the right key). * Note that for DBlocks there is no difference * betwen key and vhash, but for other blocks * there may be! * @param type entries of which type are relevant? * Use 0 for any type. * @param proc function to call on the matching value; * will be called once with a NULL if no value matches * @param proc_cls closure for iter */ static void postgres_plugin_get_key (void *cls, uint64_t offset, const struct GNUNET_HashCode *key, const struct GNUNET_HashCode *vhash, enum GNUNET_BLOCK_Type type, PluginDatumProcessor proc, void *proc_cls) { struct Plugin *plugin = cls; uint32_t utype = type; PGresult *ret; uint64_t total; uint64_t limit_off; if (0 != type) { if (NULL != vhash) { struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (key), GNUNET_PQ_query_param_auto_from_type (vhash), GNUNET_PQ_query_param_uint32 (&utype), GNUNET_PQ_query_param_end }; ret = GNUNET_PQ_exec_prepared (plugin->dbh, "count_getvt", params); } else { struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (key), GNUNET_PQ_query_param_uint32 (&utype), GNUNET_PQ_query_param_end }; ret = GNUNET_PQ_exec_prepared (plugin->dbh, "count_gett", params); } } else { if (NULL != vhash) { struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (key), GNUNET_PQ_query_param_auto_from_type (vhash), GNUNET_PQ_query_param_end }; ret = GNUNET_PQ_exec_prepared (plugin->dbh, "count_getv", params); } else { struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (key), GNUNET_PQ_query_param_end }; ret = GNUNET_PQ_exec_prepared (plugin->dbh, "count_get", params); } } if (GNUNET_OK != GNUNET_POSTGRES_check_result (plugin->dbh, ret, PGRES_TUPLES_OK, "PQexecParams", "count")) { proc (proc_cls, NULL, 0, NULL, 0, 0, 0, GNUNET_TIME_UNIT_ZERO_ABS, 0); return; } if ( (PQntuples (ret) != 1) || (PQnfields (ret) != 1) || (PQgetlength (ret, 0, 0) != sizeof (uint64_t))) { GNUNET_break (0); PQclear (ret); proc (proc_cls, NULL, 0, NULL, 0, 0, 0, GNUNET_TIME_UNIT_ZERO_ABS, 0); return; } total = GNUNET_ntohll (*(const uint64_t *) PQgetvalue (ret, 0, 0)); PQclear (ret); if (0 == total) { proc (proc_cls, NULL, 0, NULL, 0, 0, 0, GNUNET_TIME_UNIT_ZERO_ABS, 0); return; } limit_off = offset % total; if (0 != type) { if (NULL != vhash) { struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (key), GNUNET_PQ_query_param_auto_from_type (&vhash), GNUNET_PQ_query_param_uint32 (&utype), GNUNET_PQ_query_param_uint64 (&limit_off), GNUNET_PQ_query_param_end }; ret = GNUNET_PQ_exec_prepared (plugin->dbh, "getvt", params); } else { struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (key), GNUNET_PQ_query_param_uint32 (&utype), GNUNET_PQ_query_param_uint64 (&limit_off), GNUNET_PQ_query_param_end }; ret = GNUNET_PQ_exec_prepared (plugin->dbh, "gett", params); } } else { if (NULL != vhash) { struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (key), GNUNET_PQ_query_param_auto_from_type (&vhash), GNUNET_PQ_query_param_uint64 (&limit_off), GNUNET_PQ_query_param_end }; ret = GNUNET_PQ_exec_prepared (plugin->dbh, "getv", params); } else { struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (key), GNUNET_PQ_query_param_uint64 (&limit_off), GNUNET_PQ_query_param_end }; ret = GNUNET_PQ_exec_prepared (plugin->dbh, "get", params); } } process_result (plugin, proc, proc_cls, ret, __FILE__, __LINE__); }
/** * Initialize the database connections and associated * data structures (create tables and indices * as needed as well). * * @param plugin the plugin context (state for this module) * @return GNUNET_OK on success */ static int database_setup (struct Plugin *plugin) { PGresult *res; plugin->dbh = GNUNET_POSTGRES_connect (plugin->cfg, "namecache-postgres"); if (NULL == plugin->dbh) return GNUNET_SYSERR; if (GNUNET_YES == GNUNET_CONFIGURATION_get_value_yesno (plugin->cfg, "namecache-postgres", "TEMPORARY_TABLE")) { res = PQexec (plugin->dbh, "CREATE TEMPORARY TABLE ns096blocks (" " query BYTEA NOT NULL DEFAULT ''," " block BYTEA NOT NULL DEFAULT ''," " expiration_time BIGINT NOT NULL DEFAULT 0" ")" "WITH OIDS"); } else { res = PQexec (plugin->dbh, "CREATE TABLE ns096blocks (" " query BYTEA NOT NULL DEFAULT ''," " block BYTEA NOT NULL DEFAULT ''," " expiration_time BIGINT NOT NULL DEFAULT 0" ")" "WITH OIDS"); } if ( (NULL == res) || ((PQresultStatus (res) != PGRES_COMMAND_OK) && (0 != strcmp ("42P07", /* duplicate table */ PQresultErrorField (res, PG_DIAG_SQLSTATE))))) { (void) GNUNET_POSTGRES_check_result (plugin->dbh, res, PGRES_COMMAND_OK, "CREATE TABLE", "ns096blocks"); PQfinish (plugin->dbh); plugin->dbh = NULL; return GNUNET_SYSERR; } if (PQresultStatus (res) == PGRES_COMMAND_OK) create_indices (plugin->dbh); PQclear (res); if ((GNUNET_OK != GNUNET_POSTGRES_prepare (plugin->dbh, "cache_block", "INSERT INTO ns096blocks (query, block, expiration_time) VALUES " "($1, $2, $3)", 3)) || (GNUNET_OK != GNUNET_POSTGRES_prepare (plugin->dbh, "expire_blocks", "DELETE FROM ns096blocks WHERE expiration_time<$1", 1)) || (GNUNET_OK != GNUNET_POSTGRES_prepare (plugin->dbh, "delete_block", "DELETE FROM ns096blocks WHERE query=$1 AND expiration_time<=$2", 2)) || (GNUNET_OK != GNUNET_POSTGRES_prepare (plugin->dbh, "lookup_block", "SELECT block FROM ns096blocks WHERE query=$1" " ORDER BY expiration_time DESC LIMIT 1", 1))) { PQfinish (plugin->dbh); plugin->dbh = NULL; return GNUNET_SYSERR; } return GNUNET_OK; }
/** * @brief Get a database handle * * @param plugin global context * @return #GNUNET_OK on success, #GNUNET_SYSERR on error */ static int init_connection (struct Plugin *plugin) { PGresult *ret; plugin->dbh = GNUNET_POSTGRES_connect (plugin->env->cfg, "datacache-postgres"); if (NULL == plugin->dbh) return GNUNET_SYSERR; ret = PQexec (plugin->dbh, "CREATE TEMPORARY TABLE gn090dc (" " type INTEGER NOT NULL DEFAULT 0," " discard_time BIGINT NOT NULL DEFAULT 0," " key BYTEA NOT NULL DEFAULT ''," " value BYTEA NOT NULL DEFAULT ''," " path BYTEA DEFAULT '')" "WITH OIDS"); if ( (ret == NULL) || ((PQresultStatus (ret) != PGRES_COMMAND_OK) && (0 != strcmp ("42P07", /* duplicate table */ PQresultErrorField (ret, PG_DIAG_SQLSTATE))))) { (void) GNUNET_POSTGRES_check_result (plugin->dbh, ret, PGRES_COMMAND_OK, "CREATE TABLE", "gn090dc"); PQfinish (plugin->dbh); plugin->dbh = NULL; return GNUNET_SYSERR; } if (PQresultStatus (ret) == PGRES_COMMAND_OK) { if ((GNUNET_OK != GNUNET_POSTGRES_exec (plugin->dbh, "CREATE INDEX idx_key ON gn090dc (key)")) || (GNUNET_OK != GNUNET_POSTGRES_exec (plugin->dbh, "CREATE INDEX idx_dt ON gn090dc (discard_time)"))) { PQclear (ret); PQfinish (plugin->dbh); plugin->dbh = NULL; return GNUNET_SYSERR; } } PQclear (ret); ret = PQexec (plugin->dbh, "ALTER TABLE gn090dc ALTER value SET STORAGE EXTERNAL"); if (GNUNET_OK != GNUNET_POSTGRES_check_result (plugin->dbh, ret, PGRES_COMMAND_OK, "ALTER TABLE", "gn090dc")) { PQfinish (plugin->dbh); plugin->dbh = NULL; return GNUNET_SYSERR; } PQclear (ret); ret = PQexec (plugin->dbh, "ALTER TABLE gn090dc ALTER key SET STORAGE PLAIN"); if (GNUNET_OK != GNUNET_POSTGRES_check_result (plugin->dbh, ret, PGRES_COMMAND_OK, "ALTER TABLE", "gn090dc")) { PQfinish (plugin->dbh); plugin->dbh = NULL; return GNUNET_SYSERR; } PQclear (ret); if ((GNUNET_OK != GNUNET_POSTGRES_prepare (plugin->dbh, "getkt", "SELECT discard_time,type,value,path FROM gn090dc " "WHERE key=$1 AND type=$2 ", 2)) || (GNUNET_OK != GNUNET_POSTGRES_prepare (plugin->dbh, "getk", "SELECT discard_time,type,value,path FROM gn090dc " "WHERE key=$1", 1)) || (GNUNET_OK != GNUNET_POSTGRES_prepare (plugin->dbh, "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", "DELETE FROM gn090dc WHERE oid=$1", 1)) || (GNUNET_OK != GNUNET_POSTGRES_prepare (plugin->dbh, "put", "INSERT INTO gn090dc (type, discard_time, key, value, path) " "VALUES ($1, $2, $3, $4, $5)", 5))) { PQfinish (plugin->dbh); plugin->dbh = NULL; return GNUNET_SYSERR; } return GNUNET_OK; }
/** * 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; }
/** * 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; }