working dbfs.lookup
authorTero Marttila <terom@fixme.fi>
Sun, 12 Oct 2008 14:57:06 +0300
changeset 24 82cfdb6680d1
parent 23 1dee73ae4ad0
child 25 99a41f48e29b
working dbfs.lookup
Makefile
doc/evfuse_db.txt
doc/fuse_db.sql
src/dbfs.c
src/evpq.c
src/evsql.c
src/evsql.h
src/lib/error.h
src/lib/log.c
src/lib/log.h
src/lib/misc.h
--- a/Makefile	Sun Oct 12 01:09:00 2008 +0300
+++ b/Makefile	Sun Oct 12 14:57:06 2008 +0300
@@ -9,7 +9,7 @@
 DEFINES = -D_FILE_OFFSET_BITS=64
 MY_CFLAGS = -Wall -g -std=gnu99
 
-BIN_NAMES = helloworld hello simple_hello evpq_test url_test
+BIN_NAMES = helloworld hello simple_hello evpq_test url_test dbfs
 BIN_PATHS = $(addprefix bin/,$(BIN_NAMES))
 
 # first target
@@ -21,6 +21,7 @@
 bin/simple_hello: obj/evfuse.o obj/dirbuf.o obj/lib/log.o obj/lib/signals.o obj/simple.o
 bin/evpq_test: obj/evpq.o obj/lib/log.o
 bin/url_test: obj/lib/url.o obj/lib/lex.o obj/lib/log.o
+bin/dbfs: obj/evsql.o obj/evpq.o obj/evfuse.o obj/dirbuf.o obj/lib/log.o obj/lib/signals.o
 
 # computed
 LDFLAGS = ${LIBRARY_PATHS} ${LIBRARY_LIST}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/evfuse_db.txt	Sun Oct 12 14:57:06 2008 +0300
@@ -0,0 +1,19 @@
+
+Just a simple pure-data filesystem stored in a SQL database. Implementation using PostgreSQL.
+No weird dynamic magic.
+
+
+file_tree:
+    offset              serial4                 ephemeral counter used to tell one file entry from another
+    name                varchar(256)            the filename
+    parent              int4 -> inodes.ino      file entry's parent
+    inode               int4 -> inodes.ino      the file's data
+
+inodes:
+    ino                 serial4                 inode number
+    type                char(3)
+            REG                         normal file
+            DIR                         directory
+    mode                int2                    file access modes 
+    size                int8                    file content size (?)
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/fuse_db.sql	Sun Oct 12 14:57:06 2008 +0300
@@ -0,0 +1,7 @@
+CREATE TABLE inodes (ino serial4 primary key, type char(3), mode int2, size int8);
+CREATE TABLE file_tree ("offset" serial4 primary key, name varchar(256) NOT NULL, parent int4 references inodes(ino) NOT NULL, inode int4 references inodes(ino) NOT NULL);
+
+INSERT INTO inodes VALUES (1, 'DIR', 365, 0);
+INSERT INTO inodes VALUES (2, 'REG', 292, 0);
+INSERT INTO file_tree (name, parent, inode) VALUES ('foo', 1, 2);
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/dbfs.c	Sun Oct 12 14:57:06 2008 +0300
@@ -0,0 +1,223 @@
+
+/*
+ * A simple PostgreSQL-based filesystem.
+ */
+
+#include <string.h>
+#include <errno.h>
+
+#include <event2/event.h>
+
+#include "evfuse.h"
+#include "evsql.h"
+#include "dirbuf.h"
+#include "lib/log.h"
+#include "lib/signals.h"
+#include "lib/misc.h"
+
+#define SERROR(val) do { (val); goto error; } while(0)
+
+struct dbfs {
+    struct event_base *ev_base;
+    struct signals *signals;
+    
+    const char *db_conninfo;
+    struct evsql *db;
+
+    struct evfuse *ev_fuse;
+};
+
+#define CONNINFO_DEFAULT "dbname=test"
+
+// XXX: not sure how this should work
+#define CACHE_TIMEOUT 1.0
+
+mode_t _dbfs_mode (const char *type) {
+    if (!strcmp(type, "DIR"))
+        return S_IFDIR;
+
+    if (!strcmp(type, "REG"))
+        return S_IFREG;
+
+    else {
+        WARNING("[dbfs] weird mode-type: %s", type);
+        return 0;
+    }
+}
+
+void dbfs_init (void *userdata, struct fuse_conn_info *conn) {
+    INFO("[dbfs.init] userdata=%p, conn=%p", userdata, conn);
+
+}
+
+void dbfs_destroy (void *userdata) {
+    INFO("[dbfs.destroy] userdata=%p", userdata);
+
+
+}
+
+void _dbfs_lookup_result (const struct evsql_result_info *res, void *arg) {
+    struct fuse_req *req = arg;
+    struct fuse_entry_param e; ZINIT(e);
+    int err = 0;
+    
+    uint16_t mode;
+    uint32_t ino;
+    uint64_t size, nlink;
+    const char *type;
+    
+    // check if it failed
+    if (res->error && (err = EIO))
+        NERROR(evsql_result_error(res));
+
+    // duplicate rows?
+    if (evsql_result_rows(res) > 1)
+        EERROR(err = EIO, "multiple rows returned");
+
+    // not found?
+    if (evsql_result_rows(res) == 0)
+        SERROR(err = ENOENT);
+    
+    // correct number of columns
+    if (evsql_result_cols(res) != 5)
+        EERROR(err = EIO, "wrong number of columns: %zu", evsql_result_cols(res));
+    
+    // get the data
+    if (0
+        ||  evsql_result_uint32(res, 0, 0, &ino,        0 ) // inodes.ino
+        ||  evsql_result_string(res, 0, 1, &type,       0 ) // inodes.type
+        ||  evsql_result_uint16(res, 0, 2, &mode,       0 ) // inodes.mode
+        ||  evsql_result_uint64(res, 0, 3, &size,       0 ) // inodes.size
+        ||  evsql_result_uint64(res, 0, 4, &nlink,      0 ) // count(*)
+    )
+        EERROR(err = EIO, "invalid db data");
+    
+    INFO("[dbfs.look] -> ino=%u, st_mode=S_IF%s | %ho, st_nlink=%llu, st_size=%llu", ino, type, mode, (long long unsigned int) nlink, (long long unsigned int) size);
+
+    // convert and store
+    e.ino = e.attr.st_ino = ino;
+    e.attr.st_mode = _dbfs_mode(type) | mode;
+    e.attr.st_nlink = nlink;
+    e.attr.st_size = size;
+    
+    // XXX: timeouts
+    e.attr_timeout = CACHE_TIMEOUT;
+    e.entry_timeout = CACHE_TIMEOUT;
+        
+    // reply
+    if ((err = fuse_reply_entry(req, &e)))
+        EERROR(err, "fuse_reply_entry");
+
+error:
+    if (err && (err = fuse_reply_err(req, err)))
+        EWARNING(err, "fuse_reply_err");
+
+    // free
+    evsql_result_free(res);
+}
+
+void dbfs_lookup (struct fuse_req *req, fuse_ino_t parent, const char *name) {
+    struct dbfs *ctx = fuse_req_userdata(req);
+    int err;
+
+    INFO("[dbfs.lookup] parent=%lu name=%s", parent, name);
+    
+    // query and params
+    const char *sql = 
+        "SELECT"
+        " inodes.ino, inodes.type, inodes.mode, inodes.size, count(*)"
+        " FROM file_tree INNER JOIN inodes ON (file_tree.inode = inodes.ino)"
+        " WHERE file_tree.parent = $1::int AND file_tree.name = $2::varchar"
+        " GROUP BY inodes.ino, inodes.type, inodes.mode, inodes.size";
+    
+    static struct evsql_query_params params = EVSQL_PARAMS(EVSQL_FMT_BINARY) {
+        EVSQL_PARAM ( UINT32 ),
+        EVSQL_PARAM ( STRING ),
+
+        EVSQL_PARAMS_END
+    };
+    
+    // build params
+    if (0
+        ||  evsql_param_uint32(&params, 0, parent)
+        ||  evsql_param_string(&params, 1, name)
+    )
+        EERROR(err = EIO, "evsql_param_*");
+
+    // query
+    if (evsql_query_params(ctx->db, sql, &params, _dbfs_lookup_result, req) == NULL)
+        EERROR(err = EIO, "evsql_query_params");
+
+    // XXX: handle interrupts
+    
+    // wait
+    return;
+
+error:
+    if ((err = fuse_reply_err(req, err)))
+        EWARNING(err, "fuse_reply_err");
+}
+
+struct fuse_lowlevel_ops dbfs_llops = {
+    .init           = dbfs_init,
+    .destroy        = dbfs_destroy,
+    
+    .lookup         = dbfs_lookup,
+};
+
+void dbfs_sql_error (struct evsql *evsql, void *arg) {
+    struct dbfs *ctx = arg;
+
+    // AAAAAAAAAA.... panic
+    WARNING("[dbfs] SQL error: BREAKING MAIN LOOP LIEK NAO");
+
+    event_base_loopbreak(ctx->ev_base);
+}
+
+int main (int argc, char **argv) {
+    struct fuse_args fuse_args = FUSE_ARGS_INIT(argc, argv);
+    struct dbfs ctx; ZINIT(ctx);
+    
+    // parse args, XXX: fuse_args
+    ctx.db_conninfo = CONNINFO_DEFAULT;
+    
+    // init libevent
+    if ((ctx.ev_base = event_base_new()) == NULL)
+        ERROR("event_base_new");
+    
+    // setup signals
+    if ((ctx.signals = signals_default(ctx.ev_base)) == NULL)
+        ERROR("signals_default");
+
+    // open sql
+    if ((ctx.db = evsql_new_pq(ctx.ev_base, ctx.db_conninfo, dbfs_sql_error, &ctx)) == NULL)
+        ERROR("evsql_new_pq");
+
+    // open fuse
+    if ((ctx.ev_fuse = evfuse_new(ctx.ev_base, &fuse_args, &dbfs_llops, &ctx)) == NULL)
+        ERROR("evfuse_new");
+
+    // run libevent
+    INFO("running libevent loop");
+
+    if (event_base_dispatch(ctx.ev_base))
+        PERROR("event_base_dispatch");
+    
+    // clean shutdown
+
+error :
+    // cleanup
+    if (ctx.ev_fuse)
+        evfuse_close(ctx.ev_fuse);
+
+    // XXX: ctx.db
+    
+    if (ctx.signals)
+        signals_free(ctx.signals);
+
+    if (ctx.ev_base)
+        event_base_free(ctx.ev_base);
+    
+    fuse_opt_free_args(&fuse_args);
+}
+
--- a/src/evpq.c	Sun Oct 12 01:09:00 2008 +0300
+++ b/src/evpq.c	Sun Oct 12 14:57:06 2008 +0300
@@ -288,7 +288,7 @@
         ERROR("PQsendQuery: %s", PQerrorMessage(conn->pg_conn));
     
     // handle it
-    if (_evpq_handle_query(con))
+    if (_evpq_handle_query(conn))
         goto error;
 
     // success
@@ -308,7 +308,7 @@
         ERROR("PQsendQueryParams: %s", PQerrorMessage(conn->pg_conn));
     
     // handle it
-    if (_evpq_handle_query(con))
+    if (_evpq_handle_query(conn))
         goto error;
 
     // success
--- a/src/evsql.c	Sun Oct 12 01:09:00 2008 +0300
+++ b/src/evsql.c	Sun Oct 12 14:57:06 2008 +0300
@@ -114,14 +114,13 @@
 /*
  * Dequeue the query, execute the callback, and free it.
  */
-static void _evsql_query_done (struct evsql_query *query, const struct evsql_result_info *result_info) {
-
+static void _evsql_query_done (struct evsql_query *query, const struct evsql_result_info *res) {
     // dequeue
     TAILQ_REMOVE(&query->evsql->queue, query, entry);
     
-    if (result_info) 
+    if (res) 
         // call the callback
-        query->cb_fn(*result_info, query->cb_arg);
+        query->cb_fn(res, query->cb_arg);
     
     // free
     _evsql_query_free(query);
@@ -131,14 +130,14 @@
  * A query has failed, notify the user and remove it.
  */
 static void _evsql_query_failure (struct evsql *evsql, struct evsql_query *query) {
-    struct evsql_result_info result; ZINIT(result);
+    struct evsql_result_info res; ZINIT(res);
 
     // set up the result_info
-    result.evsql = evsql;
-    result.error = 1;
+    res.evsql = evsql;
+    res.error = 1;
 
     // finish it off
-    _evsql_query_done(query, &result);
+    _evsql_query_done(query, &res);
 }
 
 /*
@@ -146,18 +145,18 @@
  *
  * If result_info is given, each query will also recieve it via their callback, and the error_fn will be called.
  */
-static void _evsql_destroy (struct evsql *evsql, const struct evsql_result_info *result_info) {
+static void _evsql_destroy (struct evsql *evsql, const struct evsql_result_info *res) {
     struct evsql_query *query;
     
     // clear the queue
     while ((query = TAILQ_FIRST(&evsql->queue)) != NULL) {
-        _evsql_query_done(query, result_info);
+        _evsql_query_done(query, res);
         
         TAILQ_REMOVE(&evsql->queue, query, entry);
     }
     
     // do the error callback if required
-    if (result_info)
+    if (res)
         evsql->error_fn(evsql, evsql->cb_arg);
     
     // free
@@ -214,27 +213,32 @@
 static void _evsql_evpq_done (struct evpq_conn *conn, void *arg) {
     struct evsql *evsql = arg;
     struct evsql_query *query;
-    struct evsql_result_info result; ZINIT(result);
+    struct evsql_result_info res; ZINIT(res);
 
     assert((query = TAILQ_FIRST(&evsql->queue)) != NULL);
     
     // set up the result_info
-    result.evsql = evsql;
+    res.evsql = evsql;
     
     if (query->result.evpq == NULL) {
         // if a query didn't return any results (bug?), warn and fail the query
         WARNING("[evsql] evpq query didn't return any results");
 
-        result.error = 1;
+        res.error = 1;
+    
+    } else if (strcmp(PQresultErrorMessage(query->result.evpq), "") != 0) {
+        // the query failed with some error
+        res.error = 1;
+        res.result.pq = query->result.evpq;
 
     } else {
-        result.error = 0;
-        result.result.pq = query->result.evpq;
+        res.error = 0;
+        res.result.pq = query->result.evpq;
 
     }
 
     // finish it off
-    _evsql_query_done(query, &result);
+    _evsql_query_done(query, &res);
 
     // pump the next one
     _evsql_pump(evsql);
@@ -311,7 +315,7 @@
  *      0       connection idle, can query immediately
  *      1       connection busy, must queue query
  */
-static int _evsql_query_idle (struct evsql *evsql) {
+static int _evsql_query_busy (struct evsql *evsql) {
     switch (evsql->type) {
         case EVSQL_EVPQ: {
             enum evpq_state state = evpq_state(evsql->engine.evpq);
@@ -359,23 +363,24 @@
 }
 
 static int _evsql_query_enqueue (struct evsql *evsql, struct evsql_query *query, const char *command) {
-    int idle;
+    int busy;
     
     // check state
-    if ((idle = _evsql_query_idle(evsql)) < 0)
+    if ((busy = _evsql_query_busy(evsql)) < 0)
         ERROR("connection is not valid");
     
-    if (idle) {
+    if (busy) {
+        // copy the command for later execution
+        if ((query->command = strdup(command)) == NULL)
+            ERROR("strdup");
+
+    } else {
         assert(TAILQ_EMPTY(&evsql->queue));
 
         // execute directly
         if (_evsql_query_exec(evsql, query, command))
             goto error;
 
-    } else {
-        // copy the command for later execution
-        if ((query->command = strdup(command)) == NULL)
-            ERROR("strdup");
     }
     
     // store it on the list
@@ -408,9 +413,9 @@
     return NULL;
 }
 
-struct evsql_query *evsql_query_params (struct evsql *evsql, const char *command, struct evsql_query_params params, evsql_query_cb query_fn, void *cb_arg) {
+struct evsql_query *evsql_query_params (struct evsql *evsql, const char *command, const struct evsql_query_params *params, evsql_query_cb query_fn, void *cb_arg) {
     struct evsql_query *query = NULL;
-    struct evsql_query_param *param;
+    const struct evsql_query_param *param;
     int idx;
     
     // alloc new query
@@ -418,7 +423,7 @@
         goto error;
 
     // count the params
-    for (param = params.list; param->value || param->length; param++) 
+    for (param = params->list; param->type; param++) 
         query->params.count++;
 
     // allocate the vertical storage for the parameters
@@ -432,22 +437,31 @@
         ERROR("calloc");
 
     // transform
-    for (param = params.list, idx = 0; param->value || param->length; param++, idx++) {
+    for (param = params->list, idx = 0; param->type; param++, idx++) {
         // `types` stays NULL
         // query->params.types[idx] = 0;
         
         // values
-        query->params.values[idx] = param->value;
+        query->params.values[idx] = param->data_raw;
 
-        // lengths (nonzero for NULLs)
-        query->params.lengths[idx] = param->value ? param->length : 0;
+        // lengths
+        query->params.lengths[idx] = param->length;
 
         // formats, binary if length is nonzero
-        query->params.formats[idx] = param->value && param->length;
+        query->params.formats[idx] = param->length ? 1 : 0;
     }
 
     // result format
-    query->params.result_format = params.result_binary ? 1 : 0;
+    switch (params->result_fmt) {
+        case EVSQL_FMT_TEXT:
+            query->params.result_format = 0; break;
+
+        case EVSQL_FMT_BINARY:
+            query->params.result_format = 1; break;
+
+        default:
+            FATAL("params.result_fmt: %d", params->result_fmt);
+    }
 
     // execute it
     if (_evsql_query_enqueue(evsql, query, command))
@@ -462,3 +476,166 @@
     return NULL;
 }
 
+int evsql_param_string (struct evsql_query_params *params, size_t param, const char *ptr) {
+    struct evsql_query_param *p = &params->list[param];
+    
+    assert(p->type == EVSQL_PARAM_STRING);
+
+    p->data_raw = ptr;
+    p->length = 0;
+
+    return 0;
+}
+
+int evsql_param_uint32 (struct evsql_query_params *params, size_t param, uint32_t uval) {
+    struct evsql_query_param *p = &params->list[param];
+    
+    assert(p->type == EVSQL_PARAM_UINT32);
+
+    p->data.uint32 = htonl(uval);
+    p->data_raw = (const char *) &p->data.uint32;
+    p->length = sizeof(uval);
+
+    return 0;
+}
+
+const char *evsql_result_error (const struct evsql_result_info *res) {
+    if (!res->error)
+        return "No error";
+
+    switch (res->evsql->type) {
+        case EVSQL_EVPQ:
+            if (!res->result.pq)
+                return "unknown error (no result)";
+            
+            return PQresultErrorMessage(res->result.pq);
+
+        default:
+            FATAL("res->evsql->type");
+    }
+
+}
+
+size_t evsql_result_rows (const struct evsql_result_info *res) {
+    switch (res->evsql->type) {
+        case EVSQL_EVPQ:
+            return PQntuples(res->result.pq);
+
+        default:
+            FATAL("res->evsql->type");
+    }
+}
+
+size_t evsql_result_cols (const struct evsql_result_info *res) {
+    switch (res->evsql->type) {
+        case EVSQL_EVPQ:
+            return PQnfields(res->result.pq);
+
+        default:
+            FATAL("res->evsql->type");
+    }
+}
+
+int evsql_result_binary (const struct evsql_result_info *res, size_t row, size_t col, const char **ptr, size_t size, int nullok) {
+    *ptr = NULL;
+
+    switch (res->evsql->type) {
+        case EVSQL_EVPQ:
+            if (PQgetisnull(res->result.pq, row, col)) {
+                if (nullok)
+                    return 0;
+                else
+                    ERROR("[%zu:%zu] field is null", row, col);
+            }
+
+            if (PQfformat(res->result.pq, col) != 1)
+                ERROR("[%zu:%zu] PQfformat is not binary: %d", row, col, PQfformat(res->result.pq, col));
+    
+            if (size && PQgetlength(res->result.pq, row, col) != size)
+                ERROR("[%zu:%zu] field size mismatch: %zu -> %d", row, col, size, PQgetlength(res->result.pq, row, col));
+
+            *ptr = PQgetvalue(res->result.pq, row, col);
+
+            return 0;
+
+        default:
+            FATAL("res->evsql->type");
+    }
+
+error:
+    return -1;
+}
+
+int evsql_result_string (const struct evsql_result_info *res, size_t row, size_t col, const char **ptr, int nullok) {
+    return evsql_result_binary(res, row, col, ptr, 0, nullok);
+}
+
+int evsql_result_uint16 (const struct evsql_result_info *res, size_t row, size_t col, uint16_t *uval, int nullok) {
+    const char *data;
+    int16_t sval;
+
+    if (evsql_result_binary(res, row, col, &data, sizeof(*uval), nullok))
+        goto error;
+
+    sval = ntohs(*((int16_t *) data));
+
+    if (sval < 0)
+        ERROR("negative value for unsigned: %d", sval);
+
+    *uval = sval;
+    
+    return 0;
+
+error:
+    return nullok ? 0 : -1;
+}
+
+int evsql_result_uint32 (const struct evsql_result_info *res, size_t row, size_t col, uint32_t *uval, int nullok) {
+    const char *data;
+    int32_t sval;
+
+    if (evsql_result_binary(res, row, col, &data, sizeof(*uval), nullok))
+        goto error;
+
+    sval = ntohl(*(int32_t *) data);
+
+    if (sval < 0)
+        ERROR("negative value for unsigned: %d", sval);
+
+    *uval = sval;
+    
+    return 0;
+
+error:
+    return nullok ? 0 : -1;
+}
+
+int evsql_result_uint64 (const struct evsql_result_info *res, size_t row, size_t col, uint64_t *uval, int nullok) {
+    const char *data;
+    int64_t sval;
+
+    if (evsql_result_binary(res, row, col, &data, sizeof(*uval), nullok))
+        goto error;
+
+    sval = ntohq(*(int64_t *) data);
+
+    if (sval < 0)
+        ERROR("negative value for unsigned: %ld", sval);
+
+    *uval = sval;
+    
+    return 0;
+
+error:
+    return nullok ? 0 : -1;
+}
+
+void evsql_result_free (const struct evsql_result_info *res) {
+    switch (res->evsql->type) {
+        case EVSQL_EVPQ:
+            return PQclear(res->result.pq);
+
+        default:
+            FATAL("res->evsql->type");
+    }
+}
--- a/src/evsql.h	Sun Oct 12 01:09:00 2008 +0300
+++ b/src/evsql.h	Sun Oct 12 14:57:06 2008 +0300
@@ -20,42 +20,74 @@
 struct evsql_query;
 
 /*
+ * Parameter type
+ */
+enum evsql_param_format {
+    EVSQL_FMT_TEXT,
+    EVSQL_FMT_BINARY,
+};
+
+enum evsql_param_type {
+    EVSQL_PARAM_INVALID,
+
+    EVSQL_PARAM_NULL,
+
+    EVSQL_PARAM_STRING,
+    EVSQL_PARAM_UINT16,
+    EVSQL_PARAM_UINT32,
+    EVSQL_PARAM_UINT64,
+};
+
+/*
  * Query parameter info.
  *
  * Use the EVSQL_PARAM_* macros to define the value of list
  */
 struct evsql_query_params {
     // nonzero to get results in binary format
-    int result_binary;
+    enum evsql_param_format result_fmt;
     
     // the list of parameters, terminated by { 0, 0 }
     struct evsql_query_param {
-        // the textual or binary value for this parameter
-        char *value;
+        // the param type
+        enum evsql_param_type type;
+        
+        // pointer to the raw data
+        const char *data_raw;
+        
+        // the value
+        union {
+            uint16_t uint16;
+            uint32_t uint32;
+            uint64_t uint64;
+        } data;
 
-        // the explicit length of the parameter if it's binary. Must be non-zero for NULL values.
-        int length;
+        // the explicit length of the parameter if it's binary, zero for text.
+        // set to -1 to indicate that the value is still missing
+        ssize_t length;
     } list[];
 };
 
 // macros for defining evsql_query_params
-#define EVSQL_PARAM_NULL                    { NULL, 1 }
-#define EVSQL_PARAM_TEXT(value)             { value, 0 }
-#define EVSQL_PARAM_BINARY(value, length)   { value, length }
+#define EVSQL_PARAMS(result_fmt)            { result_fmt, 
+#define EVSQL_PARAM(typenam)                    { EVSQL_PARAM_ ## typenam, NULL }
+#define EVSQL_PARAMS_END                        { EVSQL_PARAM_INVALID, NULL } \
+                                              } // <<<
 
 /*
  * Result type
  */
+union evsql_result {
+    // libpq
+    PGresult *pq;
+};
+
 struct evsql_result_info {
     struct evsql *evsql;
     
     int error;
 
-    union {
-        // XXX: libpq
-        PGresult *pq;
-
-    } result;
+    union evsql_result result;
 };
 
 /*
@@ -64,7 +96,7 @@
  * The query has completed, either succesfully or unsuccesfully (look at info.error).
  * info.result contains the result à la the evsql's type.
  */
-typedef void (*evsql_query_cb)(struct evsql_result_info info, void *arg);
+typedef void (*evsql_query_cb)(const struct evsql_result_info *res, void *arg);
 
 /*
  * Callback for handling connection-level errors.
@@ -86,7 +118,48 @@
 /*
  * Same, but uses the SQL-level support for binding parameters.
  */
-struct evsql_query *evsql_query_params (struct evsql *evsql, const char *command, struct evsql_query_params params, evsql_query_cb query_fn, void *cb_arg);
+struct evsql_query *evsql_query_params (struct evsql *evsql, const char *command, const struct evsql_query_params *params, evsql_query_cb query_fn, void *cb_arg);
+
+/*
+ * Param-building functions
+ */
+int evsql_param_string (struct evsql_query_params *params, size_t param, const char *ptr);
+int evsql_param_uint32 (struct evsql_query_params *params, size_t param, uint32_t uval);
+
+/*
+ * Result-handling functions
+ */
+
+// get error message associated with function
+const char *evsql_result_error (const struct evsql_result_info *res);
+
+// number of rows in the result
+size_t evsql_result_rows (const struct evsql_result_info *res);
+
+// number of columns in the result
+size_t evsql_result_cols (const struct evsql_result_info *res);
+
+// fetch the raw binary value from a result set, and return it via ptr
+// if size is nonzero, check that the size of the field data matches
+int evsql_result_binary (const struct evsql_result_info *res, size_t row, size_t col, const char **ptr, size_t size, int nullok);
+int evsql_result_string (const struct evsql_result_info *res, size_t row, size_t col, const char **ptr, int nullok);
+
+// fetch certain kinds of values from a binary result set
+int evsql_result_uint16 (const struct evsql_result_info *res, size_t row, size_t col, uint16_t *uval, int nullok);
+int evsql_result_uint32 (const struct evsql_result_info *res, size_t row, size_t col, uint32_t *uval, int nullok);
+int evsql_result_uint64 (const struct evsql_result_info *res, size_t row, size_t col, uint64_t *uval, int nullok);
+
+// release the result set, freeing its memory
+void evsql_result_free (const struct evsql_result_info *res);
+
+// platform-dependant aliases
+#define evsql_result_ushort evsql_result_uint16
+
+#if _LP64
+#define evsql_result_ulong evsql_result_uint64
+#else
+#define evsql_result_ulong evsql_result_uint32
+#endif /* _LP64 */
 
 /*
  * Close a connection. Callbacks for waiting queries will not be run.
--- a/src/lib/error.h	Sun Oct 12 01:09:00 2008 +0300
+++ b/src/lib/error.h	Sun Oct 12 14:57:06 2008 +0300
@@ -6,6 +6,7 @@
 #define ERROR(...) do { err_func(__func__, __VA_ARGS__); goto error; } while (0)
 #define PERROR(...) do { perr_func(__func__, __VA_ARGS__); goto error; } while (0)
 #define EERROR(_err, ...) do { eerr_func(__func__, (_err), __VA_ARGS__); goto error; } while (0)
+#define NERROR(...) do { err_func_nonl(__func__, __VA_ARGS__); goto error; } while (0)
 
 #define FATAL(...) err_func_exit(__func__, __VA_ARGS__)
 #define PFATAL(...) perr_func_exit(__func__, __VA_ARGS__)
--- a/src/lib/log.c	Sun Oct 12 01:09:00 2008 +0300
+++ b/src/lib/log.c	Sun Oct 12 14:57:06 2008 +0300
@@ -15,7 +15,7 @@
     vfprintf(stream, fmt, va);
     
     if (flags & LOG_DISPLAY_PERR)
-        fprintf(stream, ": %s\n", strerror(err == 0 ? errno : -err));
+        fprintf(stream, ": %s\n", strerror(err == 0 ? errno : err));
     
     if (!(flags & LOG_DISPLAY_NONL))
         fprintf(stream, "\n");
--- a/src/lib/log.h	Sun Oct 12 01:09:00 2008 +0300
+++ b/src/lib/log.h	Sun Oct 12 14:57:06 2008 +0300
@@ -41,6 +41,7 @@
 #define perr(...)                   _generic_err(       LOG_DISPLAY_STDERR | LOG_DISPLAY_PERR,  NULL, 0,    __VA_ARGS__ )
 #define perr_exit(...)              _generic_err_exit(  LOG_DISPLAY_STDERR | LOG_DISPLAY_PERR,  NULL, 0,    __VA_ARGS__ )
 #define err_func(func, ...)         _generic_err(       LOG_DISPLAY_STDERR,                     func, 0,    __VA_ARGS__ )
+#define err_func_nonl(func, ...)    _generic_err(       LOG_DISPLAY_STDERR | LOG_DISPLAY_NONL,  func, 0,    __VA_ARGS__ )
 #define err_func_exit(func, ...)    _generic_err_exit(  LOG_DISPLAY_STDERR,                     func, 0,    __VA_ARGS__ )
 #define perr_func(func, ...)        _generic_err(       LOG_DISPLAY_STDERR | LOG_DISPLAY_PERR,  func, 0,    __VA_ARGS__ )
 #define perr_func_exit(func, ...)   _generic_err_exit(  LOG_DISPLAY_STDERR | LOG_DISPLAY_PERR,  func, 0,    __VA_ARGS__ )
--- a/src/lib/misc.h	Sun Oct 12 01:09:00 2008 +0300
+++ b/src/lib/misc.h	Sun Oct 12 14:57:06 2008 +0300
@@ -1,10 +1,24 @@
 #ifndef LIB_UTIL_H
 #define LIB_UTIL_H
 
+#include <arpa/inet.h>
+
 /*
  * Initialize the given *value* with zeros
  */
 #define ZINIT(obj) memset(&(obj), 0, sizeof((obj)))
 
+/*
+ * 64-bit hton{s,l,q}
+ */
+#ifndef WORDS_BIGENDIAN /* i.e. if (little endian) */
+#define htonq(x) (((uint64_t)htonl((x)>>32))|(((uint64_t)htonl(x))<<32))
+#define ntohq(x) htonq(x)
+#else
+#define htonq(x) ((uint64_t)(x))
+#define ntohq(x) ((uint64_t)(x))
+#endif
+
+
 #endif /* LIB_UTIL_H */