write+setattr
authorTero Marttila <terom@fixme.fi>
Fri, 17 Oct 2008 02:04:03 +0300
changeset 31 7804cd7b5cd5
parent 30 d8fabd347a8e
child 32 90e14e0df133
write+setattr
Makefile
doc/fuse_db.sql
src/dbfs.c
src/dbfs/attr.c
src/dbfs/common.c
src/dbfs/core.c
src/dbfs/dbfs.c
src/dbfs/dbfs.h
src/dbfs/fileop.c
src/dbfs/ops.h
src/evsql.h
src/evsql/evsql.c
src/evsql/util.c
src/lib/log.h
--- a/Makefile	Thu Oct 16 22:56:29 2008 +0300
+++ b/Makefile	Fri Oct 17 02:04:03 2008 +0300
@@ -20,7 +20,7 @@
 
 # complex modules
 EVSQL_OBJS = obj/evsql/evsql.o obj/evsql/util.o obj/evpq.o
-DBFS_OBJS = obj/dbfs/dbfs.o obj/dbfs/common.o obj/dbfs/core.o obj/dbfs/op_base.o obj/dbfs/dirop.o obj/dirbuf.o obj/dbfs/fileop.o
+DBFS_OBJS = obj/dbfs/dbfs.o obj/dbfs/common.o obj/dbfs/core.o obj/dbfs/op_base.o obj/dbfs/dirop.o obj/dirbuf.o obj/dbfs/fileop.o obj/dbfs/attr.o
 
 # first target
 all: ${BIN_PATHS}
@@ -44,7 +44,9 @@
 
 # other targets
 clean :
-	-rm obj/* bin/* build/deps/*
+	-rm obj/*.o obj/*/*.o
+	-rm bin/* 
+	-rm build/deps/*.d build/deps/*/*.d
 
 clean-deps:
 	-rm build/deps/*/*.d 
--- a/doc/fuse_db.sql	Thu Oct 16 22:56:29 2008 +0300
+++ b/doc/fuse_db.sql	Fri Oct 17 02:04:03 2008 +0300
@@ -1,11 +1,27 @@
-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);
+CREATE TABLE inodes (
+    ino serial4 primary key, 
+    type char(3) NOT NULL, 
+    mode int2 NOT NULL, 
+    data oid
+);
 
-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);
+CREATE TABLE file_tree (
+    "offset" serial4 primary key, 
+    name varchar(256), 
+    parent int4 references inodes(ino), 
+    inode int4 references inodes(ino) NOT NULL
+);
 
-ALTER TABLE file_tree ALTER COLUMN name DROP NOT NULL;
-ALTER TABLE file_tree ALTER COLUMN parent DROP NOT NULL;
-INSERT INTO file_tree (name, parent, inode) VALUES (NULL, NULL, 1);
+INSERT INTO inodes VALUES 
+    (1, 'DIR', 365, NULL),
+    (2, 'REG', 292, lo_create(0));
 
+INSERT INTO file_tree (name, parent, inode) VALUES 
+    (NULL,  NULL,   1   ),
+    ('foo', 1,      2   );
+
+CREATE FUNCTION lo_size (oid) RETURNS int4 LANGUAGE SQL STABLE RETURNS NULL ON NULL INPUT AS 'select lo_lseek(lo_open($1, 262144), 0, 2);';
+CREATE FUNCTION lo_pread (IN fd int4, IN len int4, IN "off" int4) RETURNS bytea LANGUAGE SQL STRICT AS 'select lo_lseek($1, $3, 0); select loread($1, $2);';
+CREATE FUNCTION lo_pwrite (IN fd int4, IN buf bytea, IN "off" int4) RETURNS int4 LANGUAGE SQL STRICT AS 'select lo_lseek($1, $3, 0); select lowrite($1, $2);';
+CREATE FUNCTION lo_otruncate (IN oid, IN len int4) RETURNS oid LANGUAGE SQL STRICT AS 'select lo_truncate(lo_open($1, 393216), $2); select $1;';
+
--- a/src/dbfs.c	Thu Oct 16 22:56:29 2008 +0300
+++ b/src/dbfs.c	Fri Oct 17 02:04:03 2008 +0300
@@ -17,7 +17,7 @@
 #include "lib/signals.h"
 #include "lib/misc.h"
 
-#define CONNINFO_DEFAULT "dbname=test"
+#define CONNINFO_DEFAULT "dbname=dbfs port=5433"
 
 int main (int argc, char **argv) {
     struct event_base *ev_base = NULL;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/dbfs/attr.c	Fri Oct 17 02:04:03 2008 +0300
@@ -0,0 +1,175 @@
+
+#include "dbfs.h"
+#include "../lib/log.h"
+#include "../lib/misc.h"
+
+// max. size for a setattr UPDATE query
+#define DBFS_SETATTR_SQL_MAX 512
+
+// for building the setattr UPDATE
+#define FIELD(to_set, flag, field, value) ((to_set) & (flag)) ? (field " = " value ", ") : ""
+
+void _dbfs_attr_res (const struct evsql_result_info *res, void *arg) {
+    struct fuse_req *req = arg;
+    struct stat st; ZINIT(st);
+    int err = 0;
+    
+    uint32_t ino;
+
+    // check the results
+    if ((err = _dbfs_check_res(res, 1, 5)))
+        SERROR(err = (err ==  1 ? ENOENT : EIO));
+        
+    // get our data
+    if (0
+        ||  evsql_result_uint32(res, 0, 0, &ino,        0 ) // inodes.ino
+    )
+        EERROR(err = EIO, "invalid db data");
+ 
+        
+    INFO("[dbfs.getattr %p] -> ino=%lu, stat follows", req, (unsigned long int) ino);
+    
+    // stat attrs
+    if ((err = _dbfs_stat_info(&st, res, 0, 1)))
+        goto error;
+
+    // XXX: we don't have the ino
+    st.st_ino = ino;
+
+    // reply
+    if ((err = fuse_reply_attr(req, &st, st.st_nlink ? CACHE_TIMEOUT : 0)))
+        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_getattr (struct fuse_req *req, fuse_ino_t ino, struct fuse_file_info *fi) {
+    struct dbfs *ctx = fuse_req_userdata(req);
+    int err;
+    
+    (void) fi;
+
+    INFO("[dbfs.getattr %p] ino=%lu", req, ino);
+
+    const char *sql =
+        "SELECT"
+        " inodes.ino, " DBFS_STAT_COLS
+        " FROM inodes"
+        " WHERE inodes.ino = $1::int4"
+        " GROUP BY inodes.ino, inodes.type, inodes.mode, data";
+
+    static struct evsql_query_params params = EVSQL_PARAMS(EVSQL_FMT_BINARY) {
+        EVSQL_PARAM ( UINT32 ),
+
+        EVSQL_PARAMS_END
+    };
+
+    // build params
+    if (0
+        ||  evsql_param_uint32(&params, 0, ino)
+    )
+        SERROR(err = EIO);
+        
+    // query
+    if (evsql_query_params(ctx->db, NULL, sql, &params, _dbfs_attr_res, req) == NULL)
+        SERROR(err = EIO);
+
+    // XXX: handle interrupts
+    
+    // wait
+    return;
+
+error:
+    if ((err = fuse_reply_err(req, err)))
+        EWARNING(err, "fuse_reply_err");
+}
+
+
+void dbfs_setattr (struct fuse_req *req, fuse_ino_t ino, struct stat *attr, int to_set, struct fuse_file_info *fi) {
+    struct dbfs *ctx = fuse_req_userdata(req);
+    int err;
+    int ret;
+    
+    char sql_buf[DBFS_SETATTR_SQL_MAX];
+    
+    static struct evsql_query_params params = EVSQL_PARAMS(EVSQL_FMT_BINARY) {
+        EVSQL_PARAM ( UINT16 ), // inodes.mode
+        EVSQL_PARAM ( UINT32 ), // inodes.uid
+        EVSQL_PARAM ( UINT32 ), // inodes.gid
+        EVSQL_PARAM ( UINT32 ), // data size
+        EVSQL_PARAM ( UINT32 ), // ino
+
+        EVSQL_PARAMS_END
+    };
+
+    // log
+    INFO("[dbfs.setattr %p] ino=%lu, fileop=%p: ", req, ino, fi && fi->fh ? (void*) fi->fh : NULL);
+    
+    if (to_set & FUSE_SET_ATTR_MODE) {
+        // ignore the S_IFMT
+        attr->st_mode &= 07777;
+
+        INFO("\tmode    = %08o", attr->st_mode);
+    }
+
+    if (to_set & FUSE_SET_ATTR_UID)
+        INFO("\tuid     = %u", attr->st_uid);
+
+    if (to_set & FUSE_SET_ATTR_GID)
+        INFO("\tgid     = %u", attr->st_gid);
+
+    if (to_set & FUSE_SET_ATTR_SIZE)
+        INFO("\tsize    = %lu", attr->st_size);
+
+    if (to_set & FUSE_SET_ATTR_ATIME)
+        INFO("\tatime   = %lu", attr->st_atime);
+
+    if (to_set & FUSE_SET_ATTR_MTIME)
+        INFO("\tmtime   = %lu", attr->st_mtime);
+
+    // the SQL
+    if ((ret = snprintf(sql_buf, DBFS_SETATTR_SQL_MAX,
+        "UPDATE inodes SET"
+        " %s%s%s%s ino = ino"
+        " WHERE inodes.ino = $5::int4"
+        " RETURNING inodes.ino, " DBFS_STAT_COLS_NOAGGREGATE,
+        
+        FIELD(to_set, FUSE_SET_ATTR_MODE,   "mode", "$1::int2"),
+        FIELD(to_set, FUSE_SET_ATTR_UID,    "uid",  "$2::int4"),
+        FIELD(to_set, FUSE_SET_ATTR_GID,    "gid",  "$3::int4"),
+        FIELD(to_set, FUSE_SET_ATTR_SIZE,   "data", "lo_otruncate(data, $4::int4)")
+    )) >= DBFS_SETATTR_SQL_MAX && (err = EIO))
+        ERROR("sql_buf is too small: %i", ret);
+    
+    // the params...
+    if (0
+        || (                                  evsql_params_clear(&params)                    )
+        || ((to_set & FUSE_SET_ATTR_MODE ) && evsql_param_uint16(&params, 0, attr->st_mode)  )
+        || ((to_set & FUSE_SET_ATTR_UID  ) && evsql_param_uint32(&params, 1, attr->st_uid)   )
+        || ((to_set & FUSE_SET_ATTR_GID  ) && evsql_param_uint32(&params, 2, attr->st_gid)   )
+        || ((to_set & FUSE_SET_ATTR_SIZE ) && evsql_param_uint32(&params, 3, attr->st_size)  )
+        || (                                  evsql_param_uint32(&params, 4, ino)            )
+    )
+        SERROR(err = EIO);
+
+    // trace the query
+    evsql_query_debug(sql_buf, &params);
+    
+    // query... we can pretend it's a getattr :)
+    if (evsql_query_params(ctx->db, NULL, sql_buf, &params, _dbfs_attr_res, req) == NULL)
+        SERROR(err = EIO);
+
+    // XXX: handle interrupts
+    
+    // wait
+    return;
+
+error:
+    if ((err = fuse_reply_err(req, err)))
+        EWARNING(err, "fuse_reply_err");
+}
--- a/src/dbfs/common.c	Thu Oct 16 22:56:29 2008 +0300
+++ b/src/dbfs/common.c	Fri Oct 17 02:04:03 2008 +0300
@@ -51,17 +51,17 @@
     
     uint16_t mode;
     uint32_t size = 0;  // NULL for non-REG inodes
-    uint64_t nlink;
+    uint64_t nlink = 0; // will be NULL for non-GROUP BY queries
     const char *type;
     
     // extract the data
-    if (0
+    if ((0
         ||  evsql_result_string(res, row, col_offset + 0, &type,       0 ) // inodes.type
         ||  evsql_result_uint16(res, row, col_offset + 1, &mode,       0 ) // inodes.mode
         ||  evsql_result_uint32(res, row, col_offset + 2, &size,       1 ) // size
-        ||  evsql_result_uint64(res, row, col_offset + 3, &nlink,      0 ) // count(*)
-    )
-        EERROR(err = EIO, "invalid db data");
+        ||  evsql_result_uint64(res, row, col_offset + 3, &nlink,      1 ) // count(*)
+    ) && (err = EIO))
+        ERROR("invalid db data");
 
     INFO("\tst_mode=S_IF%s | %ho, st_nlink=%llu, st_size=%llu", type, mode, (long long unsigned int) nlink, (long long unsigned int) size);
 
@@ -74,7 +74,7 @@
     return 0;
 
 error:
-    return -1;
+    return err;
 }
 
 
--- a/src/dbfs/core.c	Thu Oct 16 22:56:29 2008 +0300
+++ b/src/dbfs/core.c	Fri Oct 17 02:04:03 2008 +0300
@@ -1,9 +1,8 @@
 
+#include "dbfs.h"
 #include "../lib/log.h"
 #include "../lib/misc.h"
 
-#include "dbfs.h"
-
 /*
  * Core fs functionality like lookup, getattr
  */
@@ -28,7 +27,7 @@
     INFO("[dbfs.lookup] -> ino=%u", ino);
     
     // stat attrs
-    if (_dbfs_stat_info(&e.attr, res, 0, 1))
+    if ((err = _dbfs_stat_info(&e.attr, res, 0, 1)))
         goto error;
 
     // other attrs
@@ -57,7 +56,7 @@
     // query and params
     const char *sql = 
         "SELECT"
-        " inodes.ino, inodes.type, inodes.mode, dbfs_lo_size(data), count(*)"
+        " inodes.ino, " DBFS_STAT_COLS
         " FROM file_tree INNER JOIN inodes ON (file_tree.inode = inodes.ino)"
         " WHERE file_tree.parent = $1::int4 AND file_tree.name = $2::varchar"
         " GROUP BY inodes.ino, inodes.type, inodes.mode, data";
@@ -90,74 +89,3 @@
         EWARNING(err, "fuse_reply_err");
 }
 
-void _dbfs_getattr_result (const struct evsql_result_info *res, void *arg) {
-    struct fuse_req *req = arg;
-    struct stat st; ZINIT(st);
-    int err = 0;
-    
-    // check the results
-    if ((err = _dbfs_check_res(res, 1, 4)))
-        SERROR(err = (err ==  1 ? ENOENT : EIO));
-        
-    INFO("[dbfs.getattr %p] -> (stat follows)", req);
-    
-    // stat attrs
-    if (_dbfs_stat_info(&st, res, 0, 0))
-        goto error;
-
-    // XXX: we don't have the ino
-    st.st_ino = 0;
-
-    // reply
-    if ((err = fuse_reply_attr(req, &st, CACHE_TIMEOUT)))
-        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_getattr (struct fuse_req *req, fuse_ino_t ino, struct fuse_file_info *fi) {
-    struct dbfs *ctx = fuse_req_userdata(req);
-    int err;
-    
-    (void) fi;
-
-    INFO("[dbfs.getattr %p] ino=%lu", req, ino);
-
-    const char *sql =
-        "SELECT"
-        " inodes.type, inodes.mode, dbfs_lo_size(data), count(*)"
-        " FROM inodes"
-        " WHERE inodes.ino = $1::int4"
-        " GROUP BY inodes.type, inodes.mode, data";
-
-    static struct evsql_query_params params = EVSQL_PARAMS(EVSQL_FMT_BINARY) {
-        EVSQL_PARAM ( UINT32 ),
-
-        EVSQL_PARAMS_END
-    };
-
-    // build params
-    if (0
-        ||  evsql_param_uint32(&params, 0, ino)
-    )
-        SERROR(err = EIO);
-        
-    // query
-    if (evsql_query_params(ctx->db, NULL, sql, &params, _dbfs_getattr_result, req) == NULL)
-        SERROR(err = EIO);
-
-    // XXX: handle interrupts
-    
-    // wait
-    return;
-
-error:
-    if ((err = fuse_reply_err(req, err)))
-        EWARNING(err, "fuse_reply_err");
-}
-
--- a/src/dbfs/dbfs.c	Thu Oct 16 22:56:29 2008 +0300
+++ b/src/dbfs/dbfs.c	Fri Oct 17 02:04:03 2008 +0300
@@ -13,10 +13,11 @@
     .lookup         = dbfs_lookup,
 
     .getattr        = dbfs_getattr,
+    .setattr        = dbfs_setattr,
 
     .open           = dbfs_open,
     .read           = dbfs_read,
-    // .write       = dbfs_write,
+    .write          = dbfs_write,
     .flush          = dbfs_flush,
     .release        = dbfs_release,
 
--- a/src/dbfs/dbfs.h	Thu Oct 16 22:56:29 2008 +0300
+++ b/src/dbfs/dbfs.h	Fri Oct 17 02:04:03 2008 +0300
@@ -28,6 +28,10 @@
 // XXX: not sure how this should work
 #define CACHE_TIMEOUT 1.0
 
+// columns used for stat_info
+#define DBFS_STAT_COLS " inodes.type, inodes.mode, lo_size(data), count(*) "
+#define DBFS_STAT_COLS_NOAGGREGATE " inodes.type, inodes.mode, lo_size(data), NULL "
+
 /*
  * Convert the CHAR(4) inodes.type from SQL into a mode_t.
  *
--- a/src/dbfs/fileop.c	Thu Oct 16 22:56:29 2008 +0300
+++ b/src/dbfs/fileop.c	Fri Oct 17 02:04:03 2008 +0300
@@ -139,7 +139,7 @@
         SERROR(err = EIO);
         
     // get the data
-    if (evsql_result_buf(res, 0, 0, &buf, &size, 0))
+    if (evsql_result_binary(res, 0, 0, &buf, &size, 0))
         SERROR(err = EIO);
 
     INFO("[dbfs.read %p:%p] -> size=%zu", fop, fop->base.req, size);
@@ -208,8 +208,83 @@
     dbfs_op_fail(&fop->base, err);
 }
 
+void dbfs_write_res (const struct evsql_result_info *res, void *arg) {
+    struct dbfs_fileop *fop = arg;
+    int err;
+    uint32_t size;
+ 
+    // check the results
+    if ((err = _dbfs_check_res(res, 1, 1)) < 0)
+        SERROR(err = EIO);
+        
+    // get the size
+    if (evsql_result_uint32(res, 0, 0, &size, 0))
+        SERROR(err = EIO);
+
+    INFO("[dbfs.write %p:%p] -> size=%lu", fop, fop->base.req, (long unsigned int) size);
+        
+    // send it
+    if ((err = fuse_reply_write(fop->base.req, size)))
+        EERROR(err, "fuse_reply_write");
+    
+    // ok, req handled
+    if ((err = dbfs_op_req_done(&fop->base)))
+        goto error;
+
+    // good, fallthrough
+    err = 0;
+
+error:
+    if (err)
+        dbfs_op_fail(&fop->base, err);
+
+    // free
+    evsql_result_free(res);
+}
+
 void dbfs_write (struct fuse_req *req, fuse_ino_t ino, const char *buf, size_t size, off_t off, struct fuse_file_info *fi) {
+    struct dbfs *ctx = fuse_req_userdata(req);
+    struct dbfs_fileop *fop;
+    int err;
+    
+    // get the op
+    if ((fop = (struct dbfs_fileop *) dbfs_op_req(req, ino, fi)) == NULL)
+        return;
 
+    // log
+    INFO("[dbfs.write %p:%p] ino=%lu, size=%zu, off=%lu, fi->flags=%04X", fop, req, ino, size, off, fi->flags);
+
+    // query
+    const char *sql = 
+        "SELECT"
+        " lo_pwrite($1::int4, $2::bytea, $3::int4)";
+    
+    static struct evsql_query_params params = EVSQL_PARAMS(EVSQL_FMT_BINARY) {
+        EVSQL_PARAM ( UINT32 ), // fd
+        EVSQL_PARAM ( BINARY ), // buf
+        EVSQL_PARAM ( UINT32 ), // off
+
+        EVSQL_PARAMS_END
+    };
+
+    // build params
+    if (0
+        ||  evsql_param_uint32(&params, 0, fop->lo_fd)
+        ||  evsql_param_binary(&params, 1, buf, size)
+        ||  evsql_param_uint32(&params, 2, off)
+    )
+        SERROR(err = EIO);
+        
+    // query
+    if (evsql_query_params(ctx->db, fop->base.trans, sql, &params, dbfs_write_res, fop) == NULL)
+        SERROR(err = EIO);
+
+    // ok, wait for the info results
+    return;
+
+error:
+    // fail it
+    dbfs_op_fail(&fop->base, err);
 }
 
 void dbfs_flush (struct fuse_req *req, fuse_ino_t ino, struct fuse_file_info *fi) {
--- a/src/dbfs/ops.h	Thu Oct 16 22:56:29 2008 +0300
+++ b/src/dbfs/ops.h	Fri Oct 17 02:04:03 2008 +0300
@@ -11,6 +11,9 @@
 void dbfs_lookup (struct fuse_req *req, fuse_ino_t parent, const char *name);
 void dbfs_getattr (struct fuse_req *req, fuse_ino_t ino, struct fuse_file_info *fi);
 
+/* setattr.c */
+void dbfs_setattr(struct fuse_req *req, fuse_ino_t ino, struct stat *attr, int to_set, struct fuse_file_info *fi);
+
 /* dirop.c */
 void dbfs_opendir (struct fuse_req *req, fuse_ino_t ino, struct fuse_file_info *fi);
 void dbfs_readdir (struct fuse_req *req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi);
@@ -19,6 +22,7 @@
 /* fileop.c */
 void dbfs_open (struct fuse_req *req, fuse_ino_t ino, struct fuse_file_info *fi);
 void dbfs_read (struct fuse_req *req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi);
+void dbfs_write (struct fuse_req *req, fuse_ino_t ino, const char *buf, size_t size, off_t off, struct fuse_file_info *fi);
 void dbfs_flush (struct fuse_req *req, fuse_ino_t ino, struct fuse_file_info *fi);
 void dbfs_release (struct fuse_req *req, fuse_ino_t ino, struct fuse_file_info *fi);
 
--- a/src/evsql.h	Thu Oct 16 22:56:29 2008 +0300
+++ b/src/evsql.h	Fri Oct 17 02:04:03 2008 +0300
@@ -47,8 +47,9 @@
 enum evsql_param_type {
     EVSQL_PARAM_INVALID,
 
-    EVSQL_PARAM_NULL,
-
+    EVSQL_PARAM_NULL_,
+    
+    EVSQL_PARAM_BINARY,
     EVSQL_PARAM_STRING,
     EVSQL_PARAM_UINT16,
     EVSQL_PARAM_UINT32,
@@ -213,8 +214,18 @@
 /*
  * Param-building functions
  */
+int evsql_param_binary (struct evsql_query_params *params, size_t param, const char *ptr, size_t len);
 int evsql_param_string (struct evsql_query_params *params, size_t param, const char *ptr);
+int evsql_param_uint16 (struct evsql_query_params *params, size_t param, uint16_t uval);
 int evsql_param_uint32 (struct evsql_query_params *params, size_t param, uint32_t uval);
+int evsql_params_clear (struct evsql_query_params *params);
+
+/*
+ * Query-handling functions
+ */
+
+// print out a textual repr of the given query/params via DEBUG
+void evsql_query_debug (const char *sql, const struct evsql_query_params *params);
 
 /*
  * Result-handling functions
@@ -231,8 +242,7 @@
 
 // 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_buf    (const struct evsql_result_info *res, size_t row, size_t col, const char **ptr, size_t *size, int nullok);
-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_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
--- a/src/evsql/evsql.c	Thu Oct 16 22:56:29 2008 +0300
+++ b/src/evsql/evsql.c	Fri Oct 17 02:04:03 2008 +0300
@@ -852,7 +852,7 @@
     // allocate the vertical storage for the parameters
     if (0
         
-//            !(query->params.types    = calloc(query->params.count, sizeof(Oid)))
+        ||  !(query->params.types    = calloc(query->params.count, sizeof(Oid)))
         ||  !(query->params.values   = calloc(query->params.count, sizeof(char *)))
         ||  !(query->params.lengths  = calloc(query->params.count, sizeof(int)))
         ||  !(query->params.formats  = calloc(query->params.count, sizeof(int)))
@@ -861,8 +861,8 @@
 
     // transform
     for (param = params->list, idx = 0; param->type; param++, idx++) {
-        // `types` stays NULL
-        // query->params.types[idx] = 0;
+        // `set for NULLs, otherwise not
+        query->params.types[idx] = param->data_raw ? 0 : 16;
         
         // values
         query->params.values[idx] = param->data_raw;
@@ -870,8 +870,8 @@
         // lengths
         query->params.lengths[idx] = param->length;
 
-        // formats, binary if length is nonzero
-        query->params.formats[idx] = param->length ? 1 : 0;
+        // formats, binary if length is nonzero, but text for NULLs
+        query->params.formats[idx] = param->length && param->data_raw ? 1 : 0;
     }
 
     // result format
--- a/src/evsql/util.c	Thu Oct 16 22:56:29 2008 +0300
+++ b/src/evsql/util.c	Fri Oct 17 02:04:03 2008 +0300
@@ -1,9 +1,66 @@
 #include <assert.h>
 
 #include "evsql.h"
-#include "../lib/error.h"
+#include "../lib/log.h"
 #include "../lib/misc.h"
 
+#define _PARAM_TYPE_CASE(typenam) case EVSQL_PARAM_ ## typenam: return #typenam
+
+#define _PARAM_VAL_BUF_MAX 120
+#define _PARAM_VAL_CASE(typenam, ...) case EVSQL_PARAM_ ## typenam: if (param->data_raw) ret = snprintf(buf, _PARAM_VAL_BUF_MAX, __VA_ARGS__); else return "(null)"; break
+
+const char *evsql_param_type (const struct evsql_query_param *param) {
+    switch (param->type) {
+        _PARAM_TYPE_CASE (INVALID   );
+        _PARAM_TYPE_CASE (NULL_     );
+        _PARAM_TYPE_CASE (BINARY    );
+        _PARAM_TYPE_CASE (STRING    );
+        _PARAM_TYPE_CASE (UINT16    );
+        _PARAM_TYPE_CASE (UINT32    );
+        _PARAM_TYPE_CASE (UINT64    );
+        default: return "???";
+    }
+}
+
+
+static const char *evsql_param_val (const struct evsql_query_param *param) {
+    static char buf[_PARAM_VAL_BUF_MAX];
+    int ret;
+
+    switch (param->type) {
+        _PARAM_VAL_CASE (INVALID,   "???"                               );
+        _PARAM_VAL_CASE (NULL_,     "(null)"                            );
+        _PARAM_VAL_CASE (BINARY,    "%zu:%s",   param->length, "..."    );
+        _PARAM_VAL_CASE (STRING,    "%s",       param->data_raw         );
+        _PARAM_VAL_CASE (UINT16,    "%hu",      (unsigned short int)     ntohs(param->data.uint16)  );
+        _PARAM_VAL_CASE (UINT32,    "%lu",      (unsigned long int)      ntohl(param->data.uint32)  );
+        _PARAM_VAL_CASE (UINT64,    "%llu",     (unsigned long long int) ntohq(param->data.uint64)  );
+        default: return "???";
+    }
+
+    return buf;
+}
+
+int evsql_params_clear (struct evsql_query_params *params) {
+    struct evsql_query_param *param;
+
+    for (param = params->list; param->type; param++) 
+        param->data_raw = NULL;
+
+    return 0;
+}
+
+int evsql_param_binary (struct evsql_query_params *params, size_t param, const char *ptr, size_t len) {
+    struct evsql_query_param *p = &params->list[param];
+    
+    assert(p->type == EVSQL_PARAM_BINARY);
+
+    p->data_raw = ptr;
+    p->length = len;
+
+    return 0;
+}
+
 int evsql_param_string (struct evsql_query_params *params, size_t param, const char *ptr) {
     struct evsql_query_param *p = &params->list[param];
     
@@ -15,6 +72,18 @@
     return 0;
 }
 
+int evsql_param_uint16 (struct evsql_query_params *params, size_t param, uint16_t uval) {
+    struct evsql_query_param *p = &params->list[param];
+    
+    assert(p->type == EVSQL_PARAM_UINT16);
+
+    p->data.uint16 = htons(uval);
+    p->data_raw = (const char *) &p->data.uint16;
+    p->length = sizeof(uval);
+
+    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];
     
@@ -27,6 +96,22 @@
     return 0;
 }
 
+void evsql_query_debug (const char *sql, const struct evsql_query_params *params) {
+    const struct evsql_query_param *param;
+    size_t param_count = 0, idx = 0;
+
+    // count the params
+    for (param = params->list; param->type; param++) 
+        param_count++;
+    
+    DEBUG("sql:     %s", sql);
+    DEBUG("params:  %zu", param_count);
+
+    for (param = params->list; param->type; param++) {
+        DEBUG("\t%2zu : %8s = %s", ++idx, evsql_param_type(param), evsql_param_val(param));
+    }
+}
+
 const char *evsql_result_error (const struct evsql_result_info *res) {
     if (!res->error)
         return "No error";
@@ -64,7 +149,7 @@
     }
 }
 
-int evsql_result_buf (const struct evsql_result_info *res, size_t row, size_t col, const char **ptr, size_t *size, int nullok) {
+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) {
@@ -92,11 +177,16 @@
     return -1;
 }
 
-int evsql_result_binary (const struct evsql_result_info *res, size_t row, size_t col, const char **ptr, size_t size, int nullok) {
-    size_t real_size;
+int evsql_result_binlen (const struct evsql_result_info *res, size_t row, size_t col, const char **ptr, size_t size, int nullok) {
+    size_t real_size = 0;
 
-    if (evsql_result_buf(res, row, col, ptr, &real_size, nullok))
+    if (evsql_result_binary(res, row, col, ptr, &real_size, nullok))
         goto error;
+    
+    if (*ptr == NULL) {
+        assert(nullok);
+        return 0;
+    }
 
     if (size && real_size != size)
         ERROR("[%zu:%zu] field size mismatch: %zu -> %zu", row, col, size, real_size);
@@ -108,14 +198,24 @@
 }
 
 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);
+    size_t real_size;
+
+    if (evsql_result_binary(res, row, col, ptr, &real_size, nullok))
+        goto error;
+
+    assert(real_size == strlen(*ptr));
+    
+    return 0;
+
+error:
+    return -1;
 }
 
 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))
+    if (evsql_result_binlen(res, row, col, &data, sizeof(*uval), nullok))
         goto error;
     
     if (!data)
@@ -138,7 +238,7 @@
     const char *data;
     int32_t sval;
 
-    if (evsql_result_binary(res, row, col, &data, sizeof(*uval), nullok))
+    if (evsql_result_binlen(res, row, col, &data, sizeof(*uval), nullok))
         goto error;
     
     if (!data)
@@ -161,7 +261,7 @@
     const char *data;
     int64_t sval;
 
-    if (evsql_result_binary(res, row, col, &data, sizeof(*uval), nullok))
+    if (evsql_result_binlen(res, row, col, &data, sizeof(*uval), nullok))
         goto error;
     
     if (!data)
--- a/src/lib/log.h	Thu Oct 16 22:56:29 2008 +0300
+++ b/src/lib/log.h	Fri Oct 17 02:04:03 2008 +0300
@@ -40,6 +40,7 @@
 
 // various kinds of ways to handle an error, 2**3 of them, *g*
 #define info(...)                   _generic_err(       LOG_DISPLAY_STDOUT,                     NULL, 0,    __VA_ARGS__ )
+#define info_nonl(...)              _generic_err(       LOG_DISPLAY_STDOUT | LOG_DISPLAY_NONL,  NULL, 0,    __VA_ARGS__ )
 #define error(...)                  _generic_err(       LOG_DISPLAY_STDERR,                     NULL, 0,    __VA_ARGS__ )
 #define err_exit(...)               _generic_err_exit(  LOG_DISPLAY_STDERR,                     NULL, 0,    __VA_ARGS__ )
 #define perr(...)                   _generic_err(       LOG_DISPLAY_STDERR | LOG_DISPLAY_PERR,  NULL, 0,    __VA_ARGS__ )
@@ -85,6 +86,7 @@
 
 #if INFO_ENABLED
 #define INFO(...) info(__VA_ARGS__)
+#define INFON(...) info_nonl(__VA_ARGS__)
 #else
 #define INFO(...) (void) (__VA_ARGS__)
 #endif