--- a/.hgignore Sat Dec 13 20:58:27 2008 +0200
+++ b/.hgignore Sun Mar 08 01:33:45 2009 +0200
@@ -1,7 +1,5 @@
type: re
-^bin/
-^obj/
-^build/deps/
+^build
\.[^/]+.sw[op]$
^doc/(\w+/)?html$
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/CMakeLists.txt Sun Mar 08 01:33:45 2009 +0200
@@ -0,0 +1,15 @@
+# policy
+cmake_minimum_required (VERSION 2.6)
+
+# project attributes
+project (evsql C)
+
+# cmake paths
+set(CMAKE_MODULE_PATH "${evsql_SOURCE_DIR}/cmake/Modules/")
+
+# dependancies
+find_package(LibEvent REQUIRED)
+
+# add the src subdir
+add_subdirectory (src)
+
--- a/Makefile Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,94 +0,0 @@
-LIBEVENT_PATH = ../libs/libevent-dev
-LIBFUSE_PATH = ../opt
-
-LIBRARY_PATHS = -L${LIBEVENT_PATH}/lib -L${LIBFUSE_PATH}/lib
-INCLUDE_PATHS = -I${LIBEVENT_PATH}/include -I${LIBFUSE_PATH}/include
-LDLIBS = -levent -lfuse -lpq
-
-# default is test
-ifndef MODE
-MODE = test
-endif
-
-ifeq ($(MODE), debug)
-MODE_CFLAGS = -g -DDEBUG_ENABLED
-else ifeq ($(MODE), dev)
-MODE_CFLAGS = -g
-else ifeq ($(MODE), test)
-MODE_CFLAGS = -g -DINFO_DISABLED
-else ifeq ($(MODE), release)
-MODE_CFLAGS = -DINFO_DISABLED -O2
-endif
-
-# XXX: ugh... use `pkg-config fuse`
-DEFINES = -D_FILE_OFFSET_BITS=64
-FIXED_CFLAGS = -Wall -std=gnu99
-
-BIN_NAMES = helloworld hello simple_hello evpq_test url_test dbfs evsql_test
-BIN_PATHS = $(addprefix bin/,$(BIN_NAMES))
-
-# modules
-module_objs = $(patsubst src/%.c,obj/%.o,$(wildcard src/$(1)/*.c))
-
-# complex modules
-CORE_OBJS = obj/lib/log.o obj/lib/signals.o
-EVSQL_OBJS = $(call module_objs,evsql) obj/evpq.o
-DBFS_OBJS = $(call module_objs,dbfs) obj/dirbuf.o
-
-# first target
-all: ${BIN_PATHS}
-
-# binaries
-bin/helloworld:
-bin/hello: obj/evfuse.o obj/dirbuf.o ${CORE_OBJS}
-bin/simple_hello: obj/evfuse.o obj/dirbuf.o obj/simple.o ${CORE_OBJS}
-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: ${DBFS_OBJS} ${EVSQL_OBJS} obj/evfuse.o ${CORE_OBJS}
-bin/evsql_test: ${EVSQL_OBJS} ${CORE_OBJS}
-
-# computed
-LDFLAGS = ${LIBRARY_PATHS}
-
-CPPFLAGS = ${INCLUDE_PATHS} ${DEFINES}
-CFLAGS = ${MODE_CFLAGS} ${FIXED_CFLAGS}
-
-SRC_PATHS = $(wildcard src/*.c) $(wildcard src/*/*.c)
-SRC_NAMES = $(patsubst src/%,%,$(SRC_PATHS))
-SRC_DIRS = $(dir $(SRC_NAMES))
-
-# other targets
-clean :
- -rm obj/*.o obj/*/*.o
- -rm bin/*
- -rm build/deps/*.d build/deps/*/*.d
-
-clean-deps:
- -rm build/deps/*/*.d
- -rm build/deps/*.d
-
-#obj-dirs:
-# python build/make_obj_dirs.py $(BIN_PATHS)
-
-build/deps/%.d : src/%.c
- @set -e; rm -f $@; \
- $(CC) -MM -MT __ $(CPPFLAGS) $< > $@.$$$$; \
- sed 's,__[ :]*,obj/$*.o $@ : ,g' < $@.$$$$ > $@; \
- rm -f $@.$$$$
-
-include $(SRC_NAMES:%.c=build/deps/%.d)
-
-obj/%.o : src/%.c
- $(CC) -c $(CPPFLAGS) $(CFLAGS) $< -o $@
-
-bin/% : obj/%.o
- $(CC) $(LDFLAGS) $+ $(LOADLIBES) $(LDLIBS) -o $@
-
-# documentation
-DOXYGEN_PATH = /usr/bin/doxygen
-DOXYGEN_CONF_PATH = doc/doxygen.conf
-DOXYGEN_OUTPUT_FILE = doc/html/index.html
-
-docs :
- ${DOXYGEN_PATH} ${DOXYGEN_CONF_PATH}
-
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/cmake/Modules/FindLibEvent.cmake Sun Mar 08 01:33:45 2009 +0200
@@ -0,0 +1,27 @@
+# Find libevent
+# Once done, this will define:
+#
+# LibEvent_FOUND
+# LibEvent_INCLUDE_DIRS
+# LibEvent_LIBRARIES
+#
+# Currently, this only supports libevent-svn (i.e. 1.5/2.0), so it's kind of useless for real use :)
+
+include (LibFindMacros)
+
+# include dir
+find_path (LibEvent_INCLUDE_DIR
+ NAMES "event2/event.h"
+ PATHS "$ENV{LIBEVENT_PREFIX}/include"
+)
+
+# library
+find_library (LibEvent_LIBRARY
+ NAMES "event"
+ PATHS "$ENV{LIBEVENT_PREFIX}/lib"
+)
+
+# set the external vars
+set (LibEvent_PROCESS_INCLUDES LibEvent_INCLUDE_DIR)
+set (LibEvent_PROCESS_LIBS LibEvent_LIBRARY)
+libfind_process (LibEvent)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/cmake/Modules/LibFindMacros.cmake Sun Mar 08 01:33:45 2009 +0200
@@ -0,0 +1,99 @@
+# Works the same as find_package, but forwards the "REQUIRED" and "QUIET" arguments
+# used for the current package. For this to work, the first parameter must be the
+# prefix of the current package, then the prefix of the new package etc, which are
+# passed to find_package.
+macro (libfind_package PREFIX)
+ set (LIBFIND_PACKAGE_ARGS ${ARGN})
+ if (${PREFIX}_FIND_QUIETLY)
+ set (LIBFIND_PACKAGE_ARGS ${LIBFIND_PACKAGE_ARGS} QUIET)
+ endif (${PREFIX}_FIND_QUIETLY)
+ if (${PREFIX}_FIND_REQUIRED)
+ set (LIBFIND_PACKAGE_ARGS ${LIBFIND_PACKAGE_ARGS} REQUIRED)
+ endif (${PREFIX}_FIND_REQUIRED)
+ find_package(${LIBFIND_PACKAGE_ARGS})
+endmacro (libfind_package)
+
+# Damn CMake developers made the UsePkgConfig system deprecated in the same release (2.6)
+# where they added pkg_check_modules. Consequently I need to support both in my scripts
+# to avoid those deprecated warnings. Here's a helper that does just that.
+# Works identically to pkg_check_modules, except that no checks are needed prior to use.
+macro (libfind_pkg_check_modules PREFIX PKGNAME)
+ if (${CMAKE_MAJOR_VERSION} EQUAL 2 AND ${CMAKE_MINOR_VERSION} EQUAL 4)
+ include(UsePkgConfig)
+ pkgconfig(${PKGNAME} ${PREFIX}_INCLUDE_DIRS ${PREFIX}_LIBRARY_DIRS ${PREFIX}_LDFLAGS ${PREFIX}_CFLAGS)
+ else (${CMAKE_MAJOR_VERSION} EQUAL 2 AND ${CMAKE_MINOR_VERSION} EQUAL 4)
+ find_package(PkgConfig)
+ if (PKG_CONFIG_FOUND)
+ pkg_check_modules(${PREFIX} ${PKGNAME})
+ endif (PKG_CONFIG_FOUND)
+ endif (${CMAKE_MAJOR_VERSION} EQUAL 2 AND ${CMAKE_MINOR_VERSION} EQUAL 4)
+endmacro (libfind_pkg_check_modules)
+
+# Do the final processing once the paths have been detected.
+# If include dirs are needed, ${PREFIX}_PROCESS_INCLUDES should be set to contain
+# all the variables, each of which contain one include directory.
+# Ditto for ${PREFIX}_PROCESS_LIBS and library files.
+# Will set ${PREFIX}_FOUND, ${PREFIX}_INCLUDE_DIRS and ${PREFIX}_LIBRARIES.
+# Also handles errors in case library detection was required, etc.
+macro (libfind_process PREFIX)
+ # Skip processing if already processed during this run
+ if (NOT ${PREFIX}_FOUND)
+ # Start with the assumption that the library was found
+ set (${PREFIX}_FOUND TRUE)
+
+ # Process all includes and set _FOUND to false if any are missing
+ foreach (i ${${PREFIX}_PROCESS_INCLUDES})
+ if (${i})
+ set (${PREFIX}_INCLUDE_DIRS ${${PREFIX}_INCLUDE_DIRS} ${${i}})
+ mark_as_advanced(${i})
+ else (${i})
+ set (${PREFIX}_FOUND FALSE)
+ endif (${i})
+ endforeach (i)
+
+ # Process all libraries and set _FOUND to false if any are missing
+ foreach (i ${${PREFIX}_PROCESS_LIBS})
+ if (${i})
+ set (${PREFIX}_LIBRARIES ${${PREFIX}_LIBRARIES} ${${i}})
+ mark_as_advanced(${i})
+ else (${i})
+ set (${PREFIX}_FOUND FALSE)
+ endif (${i})
+ endforeach (i)
+
+ # Print message and/or exit on fatal error
+ if (${PREFIX}_FOUND)
+ if (NOT ${PREFIX}_FIND_QUIETLY)
+ message (STATUS "Found ${PREFIX} ${${PREFIX}_VERSION}")
+ endif (NOT ${PREFIX}_FIND_QUIETLY)
+ else (${PREFIX}_FOUND)
+ if (${PREFIX}_FIND_REQUIRED)
+ foreach (i ${${PREFIX}_PROCESS_INCLUDES} ${${PREFIX}_PROCESS_LIBS})
+ message("${i}=${${i}}")
+ endforeach (i)
+ message (FATAL_ERROR "Required library ${PREFIX} NOT FOUND.\nInstall the library (dev version) and try again. If the library is already installed, use ccmake to set the missing variables manually.")
+ endif (${PREFIX}_FIND_REQUIRED)
+ endif (${PREFIX}_FOUND)
+ endif (NOT ${PREFIX}_FOUND)
+endmacro (libfind_process)
+
+macro(libfind_library PREFIX basename)
+ set(TMP "")
+ if(MSVC80)
+ set(TMP -vc80)
+ endif(MSVC80)
+ if(MSVC90)
+ set(TMP -vc90)
+ endif(MSVC90)
+ set(${PREFIX}_LIBNAMES ${basename}${TMP})
+ if(${ARGC} GREATER 2)
+ set(${PREFIX}_LIBNAMES ${basename}${TMP}-${ARGV2})
+ string(REGEX REPLACE "\\." "_" TMP ${${PREFIX}_LIBNAMES})
+ set(${PREFIX}_LIBNAMES ${${PREFIX}_LIBNAMES} ${TMP})
+ endif(${ARGC} GREATER 2)
+ find_library(${PREFIX}_LIBRARY
+ NAMES ${${PREFIX}_LIBNAMES}
+ PATHS ${${PREFIX}_PKGCONF_LIBRARY_DIRS}
+ )
+endmacro(libfind_library)
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/include/evsql.h Sun Mar 08 01:33:45 2009 +0200
@@ -0,0 +1,817 @@
+#ifndef EVSQL_H
+#define EVSQL_H
+
+/**
+ * @file src/evsql.h
+ *
+ * A SQL library designed for use with libevent and PostgreSQL's libpq. Provides support for queueing non-transactional
+ * requests, transaction support, parametrized queries and result iteration.
+ *
+ * Currently, the API does not expose the underlying libpq data structures, but since it is currently the only
+ * underlying implementation, there is no guarantee that the same API will actually work with other databases' interface
+ * libraries...
+ *
+ * The order of function calls and callbacks goes something like this:
+ *
+ * - evsql_new_pq()
+ *
+ * - evsql_trans()
+ * - evsql_trans_abort()
+ * - evsql_trans_error_cb()
+ * - evsql_trans_ready_cb()
+ *
+ * - evsql_query(), \ref evsql_param_ + evsql_query_params(), evsql_query_exec()
+ * - evsql_query_abort()
+ * - evsql_query_cb()
+ * - \ref evsql_result_
+ * - evsql_result_free()
+ *
+ * - evsql_trans_commit()
+ * - evsql_trans_done_cb()
+ *
+ */
+
+/**
+ * System includes
+ */
+#include <stdint.h>
+#include <stdbool.h>
+#include <event2/event.h>
+
+/**
+ * XXX: err_t
+ */
+#ifndef LIB_ERR_H
+#define LIB_ERR_H
+#include <errno.h>
+typedef unsigned int err_t;
+#endif
+
+/**
+ * @struct evsql
+ *
+ * The generic session handle used to manage a single "database connector" with multiple queries/transactions.
+ *
+ * @see \ref evsql_
+ */
+struct evsql;
+
+/**
+ * @struct evsql_trans
+ *
+ * Opaque transaction handle returned by evsql_trans() and used for the \ref evsql_query_ functions
+ *
+ * @see \ref evsql_trans_
+ */
+struct evsql_trans;
+
+/**
+ * @struct evsql_query
+ *
+ * Opaque query handle returned by the \ref evsql_query_ functions and used for evsql_query_abort()
+ *
+ * @see \ref evsql_query_
+ */
+struct evsql_query;
+
+/**
+ * @struct evsql_result
+ *
+ * Opaque result handle received by evsql_query_cb(), and used with the \ref evsql_result_ functions
+ *
+ * @see evsql_query_cb
+ * @see \ref evsql_result_
+ */
+struct evsql_result;
+
+/**
+ * Various transaction isolation levels for conveniance
+ *
+ * @see evsql_trans
+ */
+enum evsql_trans_type {
+ EVSQL_TRANS_DEFAULT,
+ EVSQL_TRANS_SERIALIZABLE,
+ EVSQL_TRANS_REPEATABLE_READ,
+ EVSQL_TRANS_READ_COMMITTED,
+ EVSQL_TRANS_READ_UNCOMMITTED,
+};
+
+/**
+ * An item can be in different formats, the classical text-based format (i.e. snprintf "1234") or a more low-level
+ * binary format (i.e uint16_t 0x04F9 in network-byte order).
+ */
+enum evsql_item_format {
+ /** Format values as text strings */
+ EVSQL_FMT_TEXT,
+
+ /** Type-specific binary encoding */
+ EVSQL_FMT_BINARY,
+};
+
+/**
+ * An item has a specific type, these correspond somewhat to the native database types.
+ */
+enum evsql_item_type {
+ /** End marker, zero */
+ EVSQL_TYPE_INVALID,
+
+ /** A SQL NULL */
+ EVSQL_TYPE_NULL_,
+
+ /** A `struct evsql_item_binary` */
+ EVSQL_TYPE_BINARY,
+
+ /** A NUL-terminated char* */
+ EVSQL_TYPE_STRING,
+
+ /** A uint16_t value */
+ EVSQL_TYPE_UINT16,
+
+ /** A uint32_t value */
+ EVSQL_TYPE_UINT32,
+
+ /** A uint64_t value */
+ EVSQL_TYPE_UINT64,
+
+ EVSQL_TYPE_MAX
+};
+
+/**
+ * Value for use with EVSQL_TYPE_BINARY, this just a non-NUL-terminated char* and an explicit length
+ */
+struct evsql_item_binary {
+ /** The binary data */
+ const char *ptr;
+
+ /** Number of bytes pointed to by ptr */
+ size_t len;
+};
+
+/**
+ * Metadata about the format and type of an item, this does not hold any actual value.
+ */
+struct evsql_item_info {
+ /** The format */
+ enum evsql_item_format format;
+
+ /** The type type */
+ enum evsql_item_type type;
+
+ /** Various flags */
+ struct evsql_item_flags {
+ /** The value may be NULL @see evsql_result_next */
+ bool null_ok : 1;
+ } flags;
+};
+
+/**
+ * An union to provide storage for the values of small types
+ *
+ * @see evsql_item
+ */
+union evsql_item_value {
+ /** 16-bit unsigned integer */
+ uint16_t uint16;
+
+ /** 32-bit unsigned integer */
+ uint32_t uint32;
+
+ /** 64-bit unsigned integer */
+ uint64_t uint64;
+};
+
+/**
+ * A generic structure containing the type and value of a query parameter or a result field.
+ *
+ * @see evsql_query_info
+ * @see evsql_query_params
+ * @see evsql_result_info
+ */
+struct evsql_item {
+ /** The "header" containing the type and format */
+ struct evsql_item_info info;
+
+ /**
+ * Pointer to the raw databytes.
+ * Set to NULL for SQL NULLs, otherwise &value or an external buf
+ */
+ const char *bytes;
+
+ /**
+ * Size of the byte array pointed to by bytes, zero for EVSQL_FMT_TEXT data.
+ */
+ size_t length;
+
+ /**
+ * Inline storage for small values
+ */
+ union evsql_item_value value;
+
+ /** Internal flags */
+ struct {
+ /**
+ * The item has a value stored in `value`
+ */
+ bool has_value : 1;
+ } flags;
+};
+
+/**
+ * Query meta-info, similar to a prepared statement.
+ *
+ * Contains the literal SQL query and the types of the parameters, but no more.
+ *
+ * @see evsql_query_exec
+ */
+struct evsql_query_info {
+ /** The SQL query itself */
+ const char *sql;
+
+ /**
+ * A variable-length array of the item_info parameters, terminated by an EVSQL_TYPE_INVALID entry.
+ */
+ struct evsql_item_info params[];
+};
+
+/**
+ * Contains the query parameter types and their actual values
+ *
+ * @see evsql_query_params
+ */
+struct evsql_query_params {
+ /** Requested result format for this query. XXX: move elsewhere */
+ enum evsql_item_format result_format;
+
+ /**
+ * A variable-length array of the item parameter-values, terminated by an EVSQL_TYPE_INVALID entry.
+ */
+ struct evsql_item list[];
+};
+
+/**
+ * Result layout metadata. This contains the stucture needed to decode result rows.
+ *
+ * @see evsql_result_begin
+ */
+struct evsql_result_info {
+ /** XXX: make up something useful to stick here */
+ int _unused;
+
+ /**
+ * A variable-length array of the item_info column types.
+ */
+ struct evsql_item_info columns[];
+};
+
+/**
+ * Magic macros for defining param/result info -lists
+ *
+ * @code
+ * static struct evsql_query_params params = EVSQL_PARAMS(EVSQL_FMT_BINARY) {
+ * EVSQL_PARAM( UINT32 ),
+ * ...,
+ *
+ * EVSQL_PARAMS_END
+ * };
+ * @endcode
+ *
+ * @name EVSQL_TYPE/PARAM_*
+ * @{
+ */
+
+/**
+ * A `struct evsql_item_info` initializer, using FMT_BINARY and the given EVSQL_TYPE_ -suffix.
+ *
+ * @param typenam the suffix of an evsql_item_type name
+ *
+ * @see struct evsql_item_info
+ * @see enum evsql_item_type
+ */
+#define EVSQL_TYPE(typenam) { EVSQL_FMT_BINARY, EVSQL_TYPE_ ## typenam }
+
+/**
+ * End marker for a `struct evsql_item_info` array.
+ *
+ * @see struct evsql_item_info
+ */
+#define EVSQL_TYPE_END { EVSQL_FMT_BINARY, EVSQL_TYPE_INVALID }
+
+/**
+ * Initializer block for an evsql_query_params struct
+ */
+#define EVSQL_PARAMS(result_fmt) { result_fmt,
+
+/**
+ * An evsql_item initializer
+ */
+#define EVSQL_PARAM(typenam) { EVSQL_TYPE(typenam) }
+
+/**
+ * Include the ending item and terminate the pseudo-block started using #EVSQL_PARAMS
+ */
+#define EVSQL_PARAMS_END { EVSQL_TYPE_END } \
+ } // <<<
+
+// @}
+
+/**
+ * Callback definitions
+ *
+ * @name evsql_*_cb
+ * @{
+ */
+
+/**
+ * Callback for handling query results.
+ *
+ * The query has completed, either succesfully or unsuccesfully.
+ *
+ * Use the \ref evsql_result_ functions to manipulate the results, and call evsql_result_free() (or equivalent) once done.
+ *
+ * @param res The result handle that must be result_free'd after use
+ * @param arg The void* passed to \ref evsql_query_
+ *
+ * @see evsql_query
+ */
+typedef void (*evsql_query_cb)(struct evsql_result *res, void *arg);
+
+/**
+ * Callback for handling global-level errors.
+ *
+ * The evsql is not useable anymore.
+ *
+ * XXX: this is not actually called yet, as no retry logic is implemented, so an evsql itself never fails.
+ *
+ * @see evsql_new_pq
+ */
+typedef void (*evsql_error_cb)(struct evsql *evsql, void *arg);
+
+/**
+ * Callback for handling transaction-level errors. This may be called at any time during a transaction's lifetime,
+ * including from within the \ref evsql_query_ functions (but not always).
+ *
+ * The transaction is not useable anymore.
+ *
+ * @param trans the transaction in question
+ * @param arg the void* passed to evsql_trans
+ *
+ * @see evsql_trans
+ */
+typedef void (*evsql_trans_error_cb)(struct evsql_trans *trans, void *arg);
+
+/**
+ * Callback for handling evsql_trans/evsql_query_abort completion. The transaction is ready for use with \ref evsql_query_.
+ *
+ * @param trans the transaction in question
+ * @param arg the void* passed to evsql_trans
+ *
+ * @see evsql_trans
+ * @see evsql_query_abort
+ */
+typedef void (*evsql_trans_ready_cb)(struct evsql_trans *trans, void *arg);
+
+/**
+ * Callback for handling evsql_trans_commit completion. The transaction was commited, and should not be used anymore.
+ *
+ * @param trans the transaction in question
+ * @param arg the void* passed to evsql_trans
+ *
+ * @see evsql_trans
+ * @see evsql_trans_commit
+ */
+typedef void (*evsql_trans_done_cb)(struct evsql_trans *trans, void *arg);
+
+// @}
+
+/**
+ * Session functions
+ *
+ * @defgroup evsql_* Session interface
+ * @see evsql.h
+ * @{
+ */
+
+/**
+ * Session creation functions
+ *
+ * @defgroup evsql_new_* Session creation interface
+ * @see evsql.h
+ * @{
+ */
+
+/**
+ * Create a new PostgreSQL/libpq (evpq) -based evsql using the given conninfo.
+ *
+ * The given \a pq_conninfo pointer must stay valid for the duration of the evsql's lifetime.
+ *
+ * See the libpq reference manual for the syntax of pq_conninfo
+ *
+ * @param ev_base the libevent base to use
+ * @param pq_conninfo the libpq connection information
+ * @param error_fn XXX: not used, may be NULL
+ * @param cb_arg: XXX: not used, argument for error_fn
+ * @return the evsql context handle for use with other functions
+ */
+struct evsql *evsql_new_pq (struct event_base *ev_base, const char *pq_conninfo,
+ evsql_error_cb error_fn,
+ void *cb_arg
+);
+
+// @}
+
+/**
+ * Close a connection. Callbacks for waiting queries will not be run.
+ *
+ * XXX: not implemented yet.
+ *
+ * @param evsql the context handle from \ref evsql_new_
+ */
+void evsql_close (struct evsql *evsql);
+
+// @}
+
+/**
+ * Query API
+ *
+ * @defgroup evsql_query_* Query interface
+ * @see evsql.h
+ * @{
+ */
+
+/**
+ * Queue the given query for execution.
+ *
+ * If \a trans is given (i.e. not NULL), then the transaction must be idle, and the query will be executed in that
+ * transaction's context. Otherwise, the query will be executed without a transaction using an idle connection, or
+ * enqueued for later execution.
+ *
+ * Once the query is complete (got a result, got an error, the connection failed), then \a query_fn will be called.
+ * The callback can use the \ref evsql_result_ functions to manipulate the query results.
+ *
+ * The returned evsql_query handle can be passed to evsql_query_abort at any point before \a query_fn being called.
+ *
+ * @param evsql the context handle from \ref evsql_new_
+ * @param trans the optional transaction handle from evsql_trans
+ * @param command the raw SQL command itself
+ * @param query_fn the evsql_query_cb() to call once the query is complete
+ * @param cb_arg the void* passed to the above
+ * @return the evsql_query handle that can be used to abort the query
+ */
+struct evsql_query *evsql_query (struct evsql *evsql, struct evsql_trans *trans, const char *command, evsql_query_cb query_fn, void *cb_arg);
+
+/**
+ * Execute the given SQL query using the list of parameter types/values given via evsql_query_params.
+ *
+ * Use the EVSQL_PARAMS macros to declare \a params, and the \ref evsql_param_ functions to populate the values.
+ *
+ * See evsql_query() for more info about behaviour.
+ *
+ * See the <a href="http://www.postgresql.org/docs/8.3/static/libpq-exec.html#LIBPQ-EXEC-MAIN">libpq PQexecParams tip</a>
+ * for the parameter syntax to use.
+ *
+ * @param evsql the context handle from \ref evsql_new_
+ * @param trans the optional transaction handle from evsql_trans
+ * @param command the SQL command to bind the parameters to
+ * @param params the parameter types and values
+ * @param query_fn the evsql_query_cb() to call once the query is complete
+ * @param cb_arg the void* passed to the above
+ * @see evsql_query
+ */
+struct evsql_query *evsql_query_params (struct evsql *evsql, struct evsql_trans *trans,
+ const char *command, const struct evsql_query_params *params,
+ evsql_query_cb query_fn, void *cb_arg
+);
+
+/**
+ * Execute the given \a query_info's SQL query with the values given as variable arguments, using the \a query_info to
+ * resolve the types.
+ *
+ * See evsql_query() for more info about behaviour.
+ *
+ * @param evsql the context handle from \ref evsql_new_
+ * @param trans the optional transaction handle from evsql_trans
+ * @param query_info the SQL query information
+ * @param query_fn the evsql_query_cb() to call once the query is complete
+ * @param cb_arg the void* passed to the above
+ * @see evsql_query
+ */
+struct evsql_query *evsql_query_exec (struct evsql *evsql, struct evsql_trans *trans,
+ const struct evsql_query_info *query_info,
+ evsql_query_cb query_fn, void *cb_arg,
+ ...
+);
+
+/**
+ * Abort a \a query returned by \ref evsql_query_ that has not yet completed (query_fn has not been called yet).
+ *
+ * The actual query itself may or may not be aborted (and hence may or may not be executed on the server), but \a query_fn
+ * will not be called anymore, and the query will dispose of itself and any results returned.
+ *
+ * If the \a query is part of a transaction, then \a trans must be given, and the query must be the query that is currently
+ * executing on that trans. The transaction's \a ready_fn will be called once the query has been aborted and the
+ * transaction is now idle again.
+ *
+ * @param trans if the query is part of a transaction, then it MUST be given here
+ * @param query the in-progress query to abort
+ */
+void evsql_query_abort (struct evsql_trans *trans, struct evsql_query *query);
+
+/**
+ * Print out a textual dump of the given \a sql query and \a params using DEBUG
+ *
+ * @param sql the SQL query command
+ * @param params the list of parameter types and values
+ */
+void evsql_query_debug (const char *sql, const struct evsql_query_params *params);
+
+// @}
+
+/**
+ * Transaction API
+ *
+ * @defgroup evsql_trans_* Transaction interface
+ * @see evsql.h
+ * @{
+ */
+
+/**
+ * Create a new transaction.
+ *
+ * A transaction will be allocated its own connection, and the "BEGIN TRANSACTION ..." query will be sent (use the
+ * \a type argument to specify this).
+ *
+ * Once the transaction has been opened, the given \a ready_fn will be triggered, and the transaction can then
+ * be used (see \ref evsql_query_).
+ *
+ * If, at any point, the transaction-connection fails, any pending query will be forgotten (i.e. the query callback
+ * will NOT be called), and the given \a error_fn will be called. Note that this includes some, but not all,
+ * cases where \ref evsql_query_ returns an error.
+ *
+ * Once you are done with the transaction, call either evsql_trans_commit() or evsql_trans_abort().
+ *
+ * @param evsql the context handle from \ref evsql_new_
+ * @param type the type of transaction to create
+ * @param error_fn the evsql_trans_error_cb() to call if this transaction fails
+ * @param ready_fn the evsql_trans_ready_cb() to call once this transaction is ready for use
+ * @param done_fn the evsql_trans_done_cb() to call once this transaction has been commmited
+ * @param cb_arg the void* to pass to the above
+ * @return the evsql_trans handle for use with other functions
+ */
+struct evsql_trans *evsql_trans (struct evsql *evsql, enum evsql_trans_type type,
+ evsql_trans_error_cb error_fn,
+ evsql_trans_ready_cb ready_fn,
+ evsql_trans_done_cb done_fn,
+ void *cb_arg
+);
+
+/**
+ * Commit a transaction using "COMMIT TRANSACTION".
+ *
+ * The transaction must be idle, just like for evsql_query. Once the transaction has been commited, the transaction's
+ * \a done_fn will be called, after which the transaction must not be used anymore.
+ *
+ * You cannot abort a COMMIT, calling trans_abort() on trans after a succesful trans_commit is an error.
+ *
+ * Note that \a done_fn will never be called directly, always indirectly via the event loop.
+ *
+ * @param trans the transaction handle from evsql_trans to commit
+ * @see evsql_trans
+ */
+int evsql_trans_commit (struct evsql_trans *trans);
+
+/**
+ * Abort a transaction, using "ROLLBACK TRANSACTION".
+ *
+ * No more transaction callbacks will be called. If there was a query running, it will be aborted, and the transaction
+ * then rollback'd.
+ *
+ * You cannot abort a COMMIT, calling trans_abort on \a trans after a call to trans_commit is an error.
+ *
+ * Do not call evsql_trans_abort from within evsql_trans_error_cb()!
+ *
+ * @param trans the transaction from evsql_trans to abort
+ * @see evsql_trans
+ */
+void evsql_trans_abort (struct evsql_trans *trans);
+
+/**
+ * Retrieve the transaction-specific error message from the underlying engine.
+ *
+ * Intended to be called from evsql_trans_error_cb()
+ */
+const char *evsql_trans_error (struct evsql_trans *trans);
+
+// @}
+
+/**
+ * Parameter-building functions.
+ *
+ * These manipulate the value of the given parameter index.
+ *
+ * @defgroup evsql_param_* Parameter interface
+ * @see evsql.h
+ * @{
+ */
+
+/**
+ * Sets the value of the parameter at the given index
+ *
+ * @param params the evsql_query_params struct
+ * @param param the parameter index
+ * @param ptr pointer to the binary data
+ * @param len size of the binary data in bytes
+ * @return zero on success, <0 on error
+ */
+int evsql_param_binary (struct evsql_query_params *params, size_t param, const char *ptr, size_t len);
+
+/** @see evsql_param_binary */
+int evsql_param_string (struct evsql_query_params *params, size_t param, const char *ptr);
+
+/** @see evsql_param_binary */
+int evsql_param_uint16 (struct evsql_query_params *params, size_t param, uint16_t uval);
+
+/** @see evsql_param_binary */
+int evsql_param_uint32 (struct evsql_query_params *params, size_t param, uint32_t uval);
+
+/**
+ * Sets the given parameter to NULL
+ *
+ * @param params the evsql_query_params struct
+ * @param param the parameter index
+ * @return zero on success, <0 on error
+ */
+int evsql_param_null (struct evsql_query_params *params, size_t param);
+
+/**
+ * Clears all the parameter values (sets them to NULL)
+ *
+ * @param params the evsql_query_params struct
+ * @return zero on success, <0 on error
+ */
+int evsql_params_clear (struct evsql_query_params *params);
+
+// @}
+
+/**
+ * Result-handling functions
+ *
+ * @defgroup evsql_result_* Result interface
+ * @see evsql.h
+ * @see evsql_result
+ * @{
+ */
+
+/**
+ * Check the result for errors. Intended for use with non-data queries, i.e. CREATE, etc.
+ *
+ * Returns zero if the query was OK, err otherwise. EIO indicates an SQL error, the error message can be retrived
+ * using evsql_result_error.
+ *
+ * @param res the result handle passed to evsql_query_cb()
+ * @return zero on success, EIO on SQL error, positive error code otherwise
+ */
+err_t evsql_result_check (struct evsql_result *res);
+
+/**
+ * The iterator-based interface results interface.
+ *
+ * Define an evsql_result_info struct that describes the columns returned by the query, and call evsql_result_begin on
+ * the evsql_result. This verifies the query result, and then prepares it for iteration using evsql_result_next.
+ *
+ * Call evsql_result_end once you've stopped iteration.
+ *
+ * Returns zero if the evsql_result is ready for iteration, err otherwise. EIO indicates an SQL error, the error
+ * message can be retreived using evsql_result_error. The result must be released in both cases.
+ *
+ * Note: currently the iterator state is simply stored in evsql_result, so only one iterator at a time per evsql_result.
+ *
+ * @param info the metadata to use to handle the result row columns
+ * @param res the result handle passed to evsql_query_cb()
+ * @return zero on success, +err on error
+ */
+err_t evsql_result_begin (struct evsql_result_info *info, struct evsql_result *res);
+
+/**
+ * Reads the next result row from the result prepared using evsql_result_begin. Stores the field values into to given
+ * pointer arguments based on the evsql_result_info given to evsql_result_begin.
+ *
+ * If a field is NULL, and the result_info's evsql_item_type has flags.null_ok set, the given pointer is left
+ * untouched, otherwise, an error is returned.
+ *
+ * @param res the result handle previous prepared using evsql_result_begin
+ * @param ... a set of pointers corresponding to the evsql_result_info specified using evsql_result_begin
+ * @return >0 when a row was read, zero when there are no more rows left, and -err on error
+ */
+int evsql_result_next (struct evsql_result *res, ...);
+
+/**
+ * Ends the result iteration, releasing any associated resources and the result itself.
+ *
+ * The result should not be iterated or accessed anymore.
+ *
+ * Note: this does the same thing as evsql_result_free, and works regardless of evsql_result_begin returning
+ * succesfully or not.
+ *
+ * @param res the result handle passed to evsql_query_cb()
+ * @see evsql_result_free
+ */
+void evsql_result_end (struct evsql_result *res);
+
+/**
+ * Get the error message associated with the result, intended for use after evsql_result_check/begin return an error
+ * code.
+ *
+ * @param res the result handle passed to evsql_query_cb()
+ * @return a char* containing the NUL-terminated error string. Valid until evsql_result_free is called.
+ */
+const char *evsql_result_error (const struct evsql_result *res);
+
+/**
+ * Get the number of data rows returned by the query
+ *
+ * @param res the result handle passed to evsql_query_cb()
+ * @return the number of rows, >= 0
+ */
+size_t evsql_result_rows (const struct evsql_result *res);
+
+/**
+ * Get the number of columns in the data results from the query
+ *
+ * @param res the result handle passed to evsql_query_cb()
+ * @return the number of columns, presumeably zero if there were no results
+ */
+size_t evsql_result_cols (const struct evsql_result *res);
+
+/**
+ * Get the number of rows affected by an UPDATE/INSERT/etc query.
+ *
+ * @param res the result handle passed to evsql_query_cb()
+ * @return the number of rows affected, >= 0
+ */
+size_t evsql_result_affected (const struct evsql_result *res);
+
+/**
+ * Fetch the raw binary value for the given field, returning it via ptr/size.
+ *
+ * The given row/col must be within bounds as returned by evsql_result_rows/cols.
+ *
+ * *ptr will point to *size bytes of read-only memory allocated internally.
+ *
+ * @param res the result handle passed to evsql_query_cb()
+ * @param row the row index to access
+ * @param col the column index to access
+ * @param ptr where to store a pointer to the read-only field data, free'd upon evsql_result_free
+ * @param size updated to the size of the field value pointed to by ptr
+ * @param nullok when true and the field value is NULL, *ptr and *size are not modified, otherwise NULL means an error
+ * @return zero on success, <0 on error
+ */
+int evsql_result_binary (const struct evsql_result *res, size_t row, size_t col, const char **ptr, size_t *size, bool nullok);
+
+/**
+ * Fetch the textual value of the given field, returning it via ptr.
+ *
+ * The given row/col must be within bounds as returned by evsql_result_rows/cols.
+ *
+ * *ptr will point to a NUL-terminated string allocated internally.
+ *
+ * @param res the result handle passed to evsql_query_cb()
+ * @param row the row index to access
+ * @param col the column index to access
+ * @param ptr where to store a pointer to the read-only field data, free'd upon evsql_result_free
+ * @param nullok when true and the field value is NULL, *ptr and *size are not modified, otherwise NULL means an error
+ * @return zero on success, <0 on error
+ */
+int evsql_result_string (const struct evsql_result *res, size_t row, size_t col, const char **ptr, int nullok);
+
+/**
+ * Use evsql_result_binary to read a binary field value, and then convert it using ntoh[slq], storing the value in
+ * *val.
+ *
+ * The given row/col must be within bounds as returned by evsql_result_rows/cols.
+ *
+ * @param res the result handle passed to evsql_query_cb()
+ * @param row the row index to access
+ * @param col the column index to access
+ * @param uval where to store the decoded value
+ * @param nullok when true and the field value is NULL, *ptr and *size are not modified, otherwise NULL means an error
+ * @return zero on success, <0 on error
+ */
+int evsql_result_uint16 (const struct evsql_result *res, size_t row, size_t col, uint16_t *uval, int nullok);
+
+/** @see evsql_result_uint16 */
+int evsql_result_uint32 (const struct evsql_result *res, size_t row, size_t col, uint32_t *uval, int nullok);
+
+/** @see evsql_result_uint16 */
+int evsql_result_uint64 (const struct evsql_result *res, size_t row, size_t col, uint64_t *uval, int nullok);
+
+/**
+ * Every result handle passed to evsql_query_cb() MUST be released by the user, using this function.
+ *
+ * @param res the result handle passed to evsql_query_cb()
+ */
+void evsql_result_free (struct evsql_result *res);
+
+// @}
+
+#endif /* EVSQL_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/CMakeLists.txt Sun Mar 08 01:33:45 2009 +0200
@@ -0,0 +1,15 @@
+# add our include path
+include_directories (${evsql_SOURCE_DIR}/include ${LibEvent_INCLUDE_DIRS})
+
+# define our source code modules
+set (LIB_SOURCES "lib/log.c")
+set (EVPQ_SOURCES evpq.c)
+set (EVSQL_SOURCES core.c util.c)
+
+# XXX: silly cmake does silly things when you SET with only one arg
+set (EVSQL_SOURCES lib/log.c evpq.c core.c query.c result.c util.c)
+
+# add our library
+add_library (evsql STATIC ${EVSQL_SOURCES})
+target_link_libraries (evsql ${LibEvent_LIBRARIES})
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/core.c Sun Mar 08 01:33:45 2009 +0200
@@ -0,0 +1,915 @@
+#define _GNU_SOURCE
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+
+#include "internal.h"
+#include "lib/log.h"
+#include "lib/error.h"
+#include "lib/misc.h"
+
+/*
+ * A couple function prototypes
+ */
+static void _evsql_pump (struct evsql *evsql, struct evsql_conn *conn);
+
+/*
+ * Actually execute the given query.
+ *
+ * The backend should be able to accept the query at this time.
+ *
+ * You should assume that if trying to execute a query fails, then the connection should also be considred as failed.
+ */
+static int _evsql_query_exec (struct evsql_conn *conn, struct evsql_query *query, const char *command) {
+ int err;
+
+ DEBUG("evsql.%p: exec query=%p on trans=%p on conn=%p:", conn->evsql, query, conn->trans, conn);
+
+ switch (conn->evsql->type) {
+ case EVSQL_EVPQ:
+ // got params?
+ if (query->params.count) {
+ err = evpq_query_params(conn->engine.evpq, command,
+ query->params.count,
+ query->params.types,
+ query->params.values,
+ query->params.lengths,
+ query->params.formats,
+ query->params.result_format
+ );
+
+ } else {
+ // plain 'ole query
+ err = evpq_query(conn->engine.evpq, command);
+ }
+
+ if (err) {
+ if (PQstatus(evpq_pgconn(conn->engine.evpq)) != CONNECTION_OK)
+ WARNING("conn failed");
+ else
+ WARNING("query failed, dropping conn as well");
+ }
+
+ break;
+
+ default:
+ FATAL("evsql->type");
+ }
+
+ if (!err)
+ // assign the query
+ conn->query = query;
+
+ return err;
+}
+
+void _evsql_query_free (struct evsql_query *query) {
+ if (!query)
+ return;
+
+ assert(query->command == NULL);
+
+ // free params if present
+ free(query->params.types);
+ free(query->params.values);
+ free(query->params.lengths);
+ free(query->params.formats);
+
+ // free the query itself
+ free(query);
+}
+
+/*
+ * Execute the callback if res is given, and free the query.
+ *
+ * The query has been aborted, it will simply be freed
+ */
+static void _evsql_query_done (struct evsql_query *query, struct evsql_result *res) {
+ if (res) {
+ if (query->cb_fn)
+ // call the callback
+ query->cb_fn(res, query->cb_arg);
+ else {
+ WARNING("supressing cb_fn because query was aborted");
+
+ // free the results
+ evsql_result_free(res);
+ }
+ }
+
+ // free
+ _evsql_query_free(query);
+}
+
+/*
+ * XXX:
+ * /
+static void _evsql_destroy (struct evsql *evsql, const struct evsql_result *res) {
+ struct evsql_query *query;
+
+ // clear the queue
+ while ((query = TAILQ_FIRST(&evsql->query_queue)) != NULL) {
+ _evsql_query_done(query, res);
+
+ TAILQ_REMOVE(&evsql->query_queue, query, entry);
+ }
+
+ // free
+ free(evsql);
+}
+*/
+
+/*
+ * Free the transaction, it should already be deassociated from the query and conn.
+ */
+static void _evsql_trans_free (struct evsql_trans *trans) {
+ // ensure we don't leak anything
+ assert(trans->query == NULL);
+ assert(trans->conn == NULL);
+
+ // free
+ free(trans);
+}
+
+/*
+ * Release a connection. It should already be deassociated from the trans and query.
+ *
+ * Releases the engine, removes from the conn_list and frees this.
+ */
+static void _evsql_conn_release (struct evsql_conn *conn) {
+ // ensure we don't leak anything
+ assert(conn->trans == NULL);
+ assert(conn->query == NULL);
+
+ // release the engine
+ switch (conn->evsql->type) {
+ case EVSQL_EVPQ:
+ evpq_release(conn->engine.evpq);
+ break;
+
+ default:
+ FATAL("evsql->type");
+ }
+
+ // remove from list
+ LIST_REMOVE(conn, entry);
+
+ // catch deadlocks
+ assert(!LIST_EMPTY(&conn->evsql->conn_list) || TAILQ_EMPTY(&conn->evsql->query_queue));
+
+ // free
+ free(conn);
+}
+
+/*
+ * Release a transaction, it should already be deassociated from the query.
+ *
+ * Perform a two-way-deassociation with the conn, and then free the trans.
+ */
+static void _evsql_trans_release (struct evsql_trans *trans) {
+ assert(trans->query == NULL);
+ assert(trans->conn != NULL);
+
+ // deassociate the conn
+ trans->conn->trans = NULL; trans->conn = NULL;
+
+ // free the trans
+ _evsql_trans_free(trans);
+}
+
+/*
+ * Fail a single query, this will trigger the callback and free it.
+ *
+ * NOTE: Only for *TRANSACTIONLESS* queries.
+ */
+static void _evsql_query_fail (struct evsql* evsql, struct evsql_query *query) {
+ struct evsql_result res; ZINIT(res);
+
+ // set up the result_info
+ res.evsql = evsql;
+ res.error = 1;
+
+ // finish off the query
+ _evsql_query_done(query, &res);
+}
+
+/*
+ * Fail a transaction, this will silently drop any query, trigger the error callback, two-way-deassociate/release the
+ * conn, and then free the trans.
+ */
+static void _evsql_trans_fail (struct evsql_trans *trans) {
+ if (trans->query) {
+ // free the query silently
+ _evsql_query_free(trans->query); trans->query = NULL;
+
+ // also deassociate it from the conn!
+ trans->conn->query = NULL;
+ }
+
+ // tell the user
+ // XXX: trans is in a bad state during this call
+ if (trans->error_fn)
+ trans->error_fn(trans, trans->cb_arg);
+ else
+ WARNING("supressing error because error_fn was NULL");
+
+ // deassociate and release the conn
+ trans->conn->trans = NULL; _evsql_conn_release(trans->conn); trans->conn = NULL;
+
+ // pump the queue for requests that were waiting for this connection
+ _evsql_pump(trans->evsql, NULL);
+
+ // free the trans
+ _evsql_trans_free(trans);
+}
+
+/*
+ * Fail a connection. If the connection is transactional, this will just call _evsql_trans_fail, but otherwise it will
+ * fail any ongoing query, and then release the connection.
+ */
+static void _evsql_conn_fail (struct evsql_conn *conn) {
+ if (conn->trans) {
+ // let transactions handle their connection failures
+ _evsql_trans_fail(conn->trans);
+
+ } else {
+ if (conn->query) {
+ // fail the in-progress query
+ _evsql_query_fail(conn->evsql, conn->query); conn->query = NULL;
+ }
+
+ // finish off the whole connection
+ _evsql_conn_release(conn);
+ }
+}
+
+/*
+ * Processes enqueued non-transactional queries until the queue is empty, or we managed to exec a query.
+ *
+ * If execing a query on a connection fails, both the query and the connection are failed (in that order).
+ *
+ * Any further queries will then also be failed, because there's no reconnection/retry logic yet.
+ *
+ * This means that if conn is NULL, all queries are failed.
+ */
+static void _evsql_pump (struct evsql *evsql, struct evsql_conn *conn) {
+ struct evsql_query *query;
+ int err;
+
+ // look for waiting queries
+ while ((query = TAILQ_FIRST(&evsql->query_queue)) != NULL) {
+ // zero err
+ err = 0;
+
+ // dequeue
+ TAILQ_REMOVE(&evsql->query_queue, query, entry);
+
+ if (conn) {
+ // try and execute it
+ err = _evsql_query_exec(conn, query, query->command);
+ }
+
+ // free the command buf
+ free(query->command); query->command = NULL;
+
+ if (err || !conn) {
+ if (!conn) {
+ // warn when dropping queries
+ WARNING("failing query becuse there are no conns");
+ }
+
+ // fail the query
+ _evsql_query_fail(evsql, query);
+
+ if (conn) {
+ // fail the connection
+ WARNING("failing the connection because a query-exec failed");
+
+ _evsql_conn_fail(conn); conn = NULL;
+ }
+
+ } else {
+ // we have succesfully enqueued a query, and we can wait for this connection to complete
+ break;
+
+ }
+
+ // handle the rest of the queue
+ }
+
+ // ok
+ return;
+}
+
+/*
+ * Callback for a trans's 'BEGIN' query, which means the transaction is now ready for use.
+ */
+static void _evsql_trans_ready (struct evsql_result *res, void *arg) {
+ struct evsql_trans *trans = arg;
+
+ assert(trans != NULL);
+
+ // check for errors
+ if (res->error)
+ ERROR("transaction 'BEGIN' failed: %s", evsql_result_error(res));
+
+ // transaction is now ready for use
+ trans->ready_fn(trans, trans->cb_arg);
+
+ // good
+ return;
+
+error:
+ _evsql_trans_fail(trans);
+}
+
+/*
+ * The transaction's connection is ready, send the 'BEGIN' query.
+ *
+ * If anything fails, calls _evsql_trans_fail and returns nonzero, zero on success
+ */
+static int _evsql_trans_conn_ready (struct evsql *evsql, struct evsql_trans *trans) {
+ char trans_sql[EVSQL_QUERY_BEGIN_BUF];
+ const char *isolation_level;
+ int ret;
+
+ // determine the isolation_level to use
+ switch (trans->type) {
+ case EVSQL_TRANS_DEFAULT:
+ isolation_level = NULL; break;
+
+ case EVSQL_TRANS_SERIALIZABLE:
+ isolation_level = "SERIALIZABLE"; break;
+
+ case EVSQL_TRANS_REPEATABLE_READ:
+ isolation_level = "REPEATABLE READ"; break;
+
+ case EVSQL_TRANS_READ_COMMITTED:
+ isolation_level = "READ COMMITTED"; break;
+
+ case EVSQL_TRANS_READ_UNCOMMITTED:
+ isolation_level = "READ UNCOMMITTED"; break;
+
+ default:
+ FATAL("trans->type: %d", trans->type);
+ }
+
+ // build the trans_sql
+ if (isolation_level)
+ ret = snprintf(trans_sql, EVSQL_QUERY_BEGIN_BUF, "BEGIN TRANSACTION ISOLATION LEVEL %s", isolation_level);
+ else
+ ret = snprintf(trans_sql, EVSQL_QUERY_BEGIN_BUF, "BEGIN TRANSACTION");
+
+ // make sure it wasn't truncated
+ if (ret >= EVSQL_QUERY_BEGIN_BUF)
+ ERROR("trans_sql overflow: %d >= %d", ret, EVSQL_QUERY_BEGIN_BUF);
+
+ // execute the query
+ if (evsql_query(evsql, trans, trans_sql, _evsql_trans_ready, trans) == NULL)
+ ERROR("evsql_query");
+
+ // success
+ return 0;
+
+error:
+ // fail the transaction
+ _evsql_trans_fail(trans);
+
+ return -1;
+}
+
+/*
+ * The evpq connection was succesfully established.
+ */
+static void _evsql_evpq_connected (struct evpq_conn *_conn, void *arg) {
+ struct evsql_conn *conn = arg;
+
+ if (conn->trans)
+ // notify the transaction
+ // don't care about errors
+ (void) _evsql_trans_conn_ready(conn->evsql, conn->trans);
+
+ else
+ // pump any waiting transactionless queries
+ _evsql_pump(conn->evsql, conn);
+}
+
+/*
+ * Got one result on this evpq connection.
+ */
+static void _evsql_evpq_result (struct evpq_conn *_conn, PGresult *result, void *arg) {
+ struct evsql_conn *conn = arg;
+ struct evsql_query *query = conn->query;
+
+ assert(query != NULL);
+
+ // if we get multiple results, only return the first one
+ if (query->result.pq) {
+ WARNING("[evsql] evpq query returned multiple results, discarding previous one");
+
+ PQclear(query->result.pq); query->result.pq = NULL;
+ }
+
+ // remember the result
+ query->result.pq = result;
+}
+
+/*
+ * No more results for this query.
+ */
+static void _evsql_evpq_done (struct evpq_conn *_conn, void *arg) {
+ struct evsql_conn *conn = arg;
+ struct evsql_query *query = conn->query;
+ struct evsql_result res; ZINIT(res);
+
+ assert(query != NULL);
+
+ // set up the result_info
+ res.evsql = conn->evsql;
+ res.result = query->result;
+
+ if (query->result.pq == NULL) {
+ // if a query didn't return any results (bug?), warn and fail the query
+ WARNING("[evsql] evpq query didn't return any results");
+
+ res.error = 1;
+
+ } else if (strcmp(PQresultErrorMessage(query->result.pq), "") != 0) {
+ // the query failed with some error
+ res.error = 1;
+
+ } else {
+ // the query succeeded \o/
+ res.error = 0;
+
+ }
+
+ // de-associate the query from the connection
+ conn->query = NULL;
+
+ // how we handle query completion depends on if we're a transaction or not
+ if (conn->trans) {
+ // we can deassign the trans's query
+ conn->trans->query = NULL;
+
+ // was an abort?
+ if (!query->cb_fn)
+ // notify the user that the transaction query has been aborted
+ conn->trans->ready_fn(conn->trans, conn->trans->cb_arg);
+
+ // then hand the query to the user
+ _evsql_query_done(query, &res);
+
+ } else {
+ // a transactionless query, so just finish it off and pump any other waiting ones
+ _evsql_query_done(query, &res);
+
+ // pump the next one
+ _evsql_pump(conn->evsql, conn);
+ }
+}
+
+/*
+ * The connection failed.
+ */
+static void _evsql_evpq_failure (struct evpq_conn *_conn, void *arg) {
+ struct evsql_conn *conn = arg;
+
+ // just fail the conn
+ _evsql_conn_fail(conn);
+}
+
+/*
+ * Our evpq behaviour
+ */
+static struct evpq_callback_info _evsql_evpq_cb_info = {
+ .fn_connected = _evsql_evpq_connected,
+ .fn_result = _evsql_evpq_result,
+ .fn_done = _evsql_evpq_done,
+ .fn_failure = _evsql_evpq_failure,
+};
+
+/*
+ * Allocate the generic evsql context.
+ */
+static struct evsql *_evsql_new_base (struct event_base *ev_base, evsql_error_cb error_fn, void *cb_arg) {
+ struct evsql *evsql = NULL;
+
+ // allocate it
+ if ((evsql = calloc(1, sizeof(*evsql))) == NULL)
+ ERROR("calloc");
+
+ // store
+ evsql->ev_base = ev_base;
+ evsql->error_fn = error_fn;
+ evsql->cb_arg = cb_arg;
+
+ // init
+ LIST_INIT(&evsql->conn_list);
+ TAILQ_INIT(&evsql->query_queue);
+
+ // done
+ return evsql;
+
+error:
+ return NULL;
+}
+
+/*
+ * Start a new connection and add it to the list, it won't be ready until _evsql_evpq_connected is called
+ */
+static struct evsql_conn *_evsql_conn_new (struct evsql *evsql) {
+ struct evsql_conn *conn = NULL;
+
+ // allocate
+ if ((conn = calloc(1, sizeof(*conn))) == NULL)
+ ERROR("calloc");
+
+ // init
+ conn->evsql = evsql;
+
+ // connect the engine
+ switch (evsql->type) {
+ case EVSQL_EVPQ:
+ if ((conn->engine.evpq = evpq_connect(evsql->ev_base, evsql->engine_conf.evpq, _evsql_evpq_cb_info, conn)) == NULL)
+ goto error;
+
+ break;
+
+ default:
+ FATAL("evsql->type");
+ }
+
+ // add it to the list
+ LIST_INSERT_HEAD(&evsql->conn_list, conn, entry);
+
+ // success
+ return conn;
+
+error:
+ free(conn);
+
+ return NULL;
+}
+
+struct evsql *evsql_new_pq (struct event_base *ev_base, const char *pq_conninfo, evsql_error_cb error_fn, void *cb_arg) {
+ struct evsql *evsql = NULL;
+
+ // base init
+ if ((evsql = _evsql_new_base (ev_base, error_fn, cb_arg)) == NULL)
+ goto error;
+
+ // store conf
+ evsql->engine_conf.evpq = pq_conninfo;
+
+ // pre-create one connection
+ if (_evsql_conn_new(evsql) == NULL)
+ goto error;
+
+ // done
+ return evsql;
+
+error:
+ // XXX: more complicated than this?
+ free(evsql);
+
+ return NULL;
+}
+
+/*
+ * Checks if the connection is already allocated for some other trans/query.
+ *
+ * Returns:
+ * 0 connection idle, can be allocated
+ * >1 connection busy
+ */
+static int _evsql_conn_busy (struct evsql_conn *conn) {
+ // transactions get the connection to themselves
+ if (conn->trans)
+ return 1;
+
+ // if it has a query assigned, it's busy
+ if (conn->query)
+ return 1;
+
+ // otherwise, it's all idle
+ return 0;
+}
+
+/*
+ * Checks if the connection is ready for use (i.e. _evsql_evpq_connected was called).
+ *
+ * The connection should not already have a query running.
+ *
+ * Returns
+ * <0 the connection is not valid (failed, query in progress)
+ * 0 the connection is still pending, and will become ready at some point
+ * >0 it's ready
+ */
+static int _evsql_conn_ready (struct evsql_conn *conn) {
+ switch (conn->evsql->type) {
+ case EVSQL_EVPQ: {
+ enum evpq_state state = evpq_state(conn->engine.evpq);
+
+ switch (state) {
+ case EVPQ_CONNECT:
+ return 0;
+
+ case EVPQ_CONNECTED:
+ return 1;
+
+ case EVPQ_QUERY:
+ case EVPQ_INIT:
+ case EVPQ_FAILURE:
+ return -1;
+
+ default:
+ FATAL("evpq_state: %d", state);
+ }
+
+ }
+
+ default:
+ FATAL("evsql->type: %d", conn->evsql->type);
+ }
+}
+
+/*
+ * Allocate a connection for use and return it via *conn_ptr, or if may_queue is nonzero and the connection pool is
+ * getting full, return NULL (query should be queued).
+ *
+ * Note that the returned connection might not be ready for use yet (if we created a new one, see _evsql_conn_ready).
+ *
+ * Returns zero if a connection was found or the request should be queued, or nonzero if something failed and the
+ * request should be dropped.
+ */
+static int _evsql_conn_get (struct evsql *evsql, struct evsql_conn **conn_ptr, int may_queue) {
+ int have_nontrans = 0;
+ *conn_ptr = NULL;
+
+ // find a connection that isn't busy and is ready (unless the query queue is empty).
+ LIST_FOREACH(*conn_ptr, &evsql->conn_list, entry) {
+ // we can only have a query enqueue itself if there is a non-trans conn it can later use
+ if (!(*conn_ptr)->trans)
+ have_nontrans = 1;
+
+ // skip busy conns always
+ if (_evsql_conn_busy(*conn_ptr))
+ continue;
+
+ // accept pending conns as long as there are NO enqueued queries (might cause deadlock otherwise)
+ if (_evsql_conn_ready(*conn_ptr) == 0 && TAILQ_EMPTY(&evsql->query_queue))
+ break;
+
+ // accept conns that are in a fully ready state
+ if (_evsql_conn_ready(*conn_ptr) > 0)
+ break;
+ }
+
+ // if we found an idle connection, we can just return that right away
+ if (*conn_ptr)
+ return 0;
+
+ // return NULL if may_queue and we have a non-trans conn that we can, at some point, use
+ if (may_queue && have_nontrans)
+ return 0;
+
+ // we need to open a new connection
+ if ((*conn_ptr = _evsql_conn_new(evsql)) == NULL)
+ goto error;
+
+ // good
+ return 0;
+error:
+ return -1;
+}
+
+struct evsql_trans *evsql_trans (struct evsql *evsql, enum evsql_trans_type type, evsql_trans_error_cb error_fn, evsql_trans_ready_cb ready_fn, evsql_trans_done_cb done_fn, void *cb_arg) {
+ struct evsql_trans *trans = NULL;
+
+ // allocate it
+ if ((trans = calloc(1, sizeof(*trans))) == NULL)
+ ERROR("calloc");
+
+ // store
+ trans->evsql = evsql;
+ trans->ready_fn = ready_fn;
+ trans->done_fn = done_fn;
+ trans->cb_arg = cb_arg;
+ trans->type = type;
+
+ // find a connection
+ if (_evsql_conn_get(evsql, &trans->conn, 0))
+ ERROR("_evsql_conn_get");
+
+ // associate the conn
+ trans->conn->trans = trans;
+
+ // is it already ready?
+ if (_evsql_conn_ready(trans->conn) > 0) {
+ // call _evsql_trans_conn_ready directly, it will handle cleanup (silently, !error_fn)
+ if (_evsql_trans_conn_ready(evsql, trans)) {
+ // return NULL directly
+ return NULL;
+ }
+
+ } else {
+ // otherwise, wait for the conn to be ready
+
+ }
+
+ // and let it pass errors to the user
+ trans->error_fn = error_fn;
+
+ // ok
+ return trans;
+
+error:
+ free(trans);
+
+ return NULL;
+}
+
+/*
+ * Internal query functions
+ */
+struct evsql_query *_evsql_query_new (struct evsql *evsql, struct evsql_trans *trans, evsql_query_cb query_fn, void *cb_arg) {
+ struct evsql_query *query = NULL;
+
+ // if it's part of a trans, then make sure the trans is idle
+ if (trans && trans->query)
+ ERROR("transaction is busy");
+
+ // allocate it
+ if ((query = calloc(1, sizeof(*query))) == NULL)
+ ERROR("calloc");
+
+ // store
+ query->cb_fn = query_fn;
+ query->cb_arg = cb_arg;
+
+ // success
+ return query;
+
+error:
+ return NULL;
+}
+
+int _evsql_query_enqueue (struct evsql *evsql, struct evsql_trans *trans, struct evsql_query *query, const char *command) {
+ // transaction queries are handled differently
+ if (trans) {
+ // it's an in-transaction query
+ assert(trans->query == NULL);
+
+ // assign the query
+ trans->query = query;
+
+ // execute directly
+ if (_evsql_query_exec(trans->conn, query, command)) {
+ // ack, fail the transaction
+ _evsql_trans_fail(trans);
+
+ // caller frees query
+ goto error;
+ }
+
+ } else {
+ struct evsql_conn *conn;
+
+ // find an idle connection
+ if ((_evsql_conn_get(evsql, &conn, 1)))
+ ERROR("couldn't allocate a connection for the query");
+
+ // we must enqueue if no idle conn or the conn is not yet ready
+ if (conn && _evsql_conn_ready(conn) > 0) {
+ // execute directly
+ if (_evsql_query_exec(conn, query, command)) {
+ // ack, fail the connection
+ _evsql_conn_fail(conn);
+
+ // make sure we don't deadlock any queries, but if this query got a conn directly, then we shouldn't
+ // have any queries enqueued anyways
+ assert(TAILQ_EMPTY(&evsql->query_queue));
+
+ // caller frees query
+ goto error;
+ }
+
+ } else {
+ // copy the command for later execution
+ if ((query->command = strdup(command)) == NULL)
+ ERROR("strdup");
+
+ // enqueue until some connection pumps the queue
+ TAILQ_INSERT_TAIL(&evsql->query_queue, query, entry);
+ }
+ }
+
+ // ok, good
+ return 0;
+
+error:
+ return -1;
+}
+
+
+void _evsql_trans_commit_res (struct evsql_result *res, void *arg) {
+ struct evsql_trans *trans = arg;
+
+ // check for errors
+ if (res->error)
+ ERROR("transaction 'COMMIT' failed: %s", evsql_result_error(res));
+
+ // transaction is now done
+ trans->done_fn(trans, trans->cb_arg);
+
+ // release it
+ _evsql_trans_release(trans);
+
+ // success
+ return;
+
+error:
+ _evsql_trans_fail(trans);
+}
+
+int evsql_trans_commit (struct evsql_trans *trans) {
+ static const char *sql = "COMMIT TRANSACTION";
+
+ if (trans->query)
+ ERROR("cannot COMMIT because transaction is still busy");
+
+ // query
+ if (evsql_query(trans->evsql, trans, sql, _evsql_trans_commit_res, trans) == NULL)
+ goto error;
+
+ // mark it as commited in case someone wants to abort it
+ trans->has_commit = 1;
+
+ // success
+ return 0;
+
+error:
+ return -1;
+}
+
+void _evsql_trans_rollback_res (struct evsql_result *res, void *arg) {
+ struct evsql_trans *trans = arg;
+
+ // fail the connection on errors
+ if (res->error)
+ ERROR("transaction 'ROLLBACK' failed: %s", evsql_result_error(res));
+
+ // release it
+ _evsql_trans_release(trans);
+
+ // success
+ return;
+
+error:
+ // fail the connection too, errors are supressed
+ _evsql_trans_fail(trans);
+}
+
+/*
+ * Used as the ready_fn callback in case of abort, otherwise directly
+ */
+void _evsql_trans_rollback (struct evsql_trans *trans, void *arg) {
+ static const char *sql = "ROLLBACK TRANSACTION";
+
+ (void) arg;
+
+ // query
+ if (evsql_query(trans->evsql, trans, sql, _evsql_trans_rollback_res, trans) == NULL) {
+ // fail the transaction/connection, errors are supressed
+ _evsql_trans_fail(trans);
+ }
+
+}
+
+void evsql_trans_abort (struct evsql_trans *trans) {
+ // supress errors
+ trans->error_fn = NULL;
+
+ if (trans->has_commit) {
+ // abort after commit doesn't make sense
+ FATAL("transaction was already commited");
+ }
+
+ if (trans->query) {
+ // gah, some query is running
+ WARNING("aborting pending query");
+
+ // prepare to rollback once complete by hijacking ready_fn
+ trans->ready_fn = _evsql_trans_rollback;
+
+ // abort
+ evsql_query_abort(trans, trans->query);
+
+ } else {
+ // just rollback directly
+ _evsql_trans_rollback(trans, NULL);
+
+ }
+}
+
--- a/src/dbfs.c Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,64 +0,0 @@
-
-/*
- * A simple PostgreSQL-based filesystem.
- */
-
-#include <stdlib.h>
-#include <string.h>
-#include <errno.h>
-#include <assert.h>
-
-#include <event2/event.h>
-
-#include "dbfs.h"
-#include "evfuse.h"
-#include "evsql.h"
-#include "lib/log.h"
-#include "lib/signals.h"
-#include "lib/misc.h"
-
-#define CONNINFO_DEFAULT "dbname=dbfs port=5433"
-
-int main (int argc, char **argv) {
- struct event_base *ev_base = NULL;
- struct signals *signals = NULL;
- struct dbfs *ctx = NULL;
- const char *db_conninfo;
- struct fuse_args fuse_args = FUSE_ARGS_INIT(argc, argv);
-
- // parse args, XXX: fuse_args
- db_conninfo = CONNINFO_DEFAULT;
-
- // init libevent
- if ((ev_base = event_base_new()) == NULL)
- ERROR("event_base_new");
-
- // setup signals
- if ((signals = signals_default(ev_base)) == NULL)
- ERROR("signals_default");
-
- // setup dbfs
- if ((ctx = dbfs_new(ev_base, &fuse_args, db_conninfo)) == NULL)
- ERROR("dbfs_new");
-
- // run libevent
- INFO("running libevent loop");
-
- if (event_base_dispatch(ev_base))
- PERROR("event_base_dispatch");
-
- // clean shutdown
-
-error :
- if (ctx)
- dbfs_free(ctx);
-
- if (signals)
- signals_free(signals);
-
- if (ev_base)
- event_base_free(ev_base);
-
- fuse_opt_free_args(&fuse_args);
-}
-
--- a/src/dbfs.h Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,25 +0,0 @@
-#ifndef DBFS_H
-#define DBFS_H
-
-#include "evfuse.h"
-
-/*
- * External interface for dbfs
- */
-
-/*
- * Context struct.
- */
-struct dbfs;
-
-/*
- * Create the evsql and evfuse contexts and run the fs
- */
-struct dbfs *dbfs_new (struct event_base *ev_base, struct fuse_args *args, const char *db_conninfo);
-
-/*
- * Release the dbfs's resources and free it
- */
-void dbfs_free (struct dbfs *ctx);
-
-#endif /* DBFS_H */
--- a/src/dbfs/attr.c Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,185 +0,0 @@
-
-#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 (struct evsql_result *res, void *arg) {
- struct fuse_req *req = arg;
- struct stat st; ZINIT(st);
- int err = 0;
-
- uint32_t ino;
- struct dbfs_stat_values stat_values;
-
- // result info
- static struct evsql_result_info result_info = {
- 0, {
- { EVSQL_FMT_BINARY, EVSQL_TYPE_UINT32 }, // inodes.ino
- DBFS_STAT_RESULT_INFO,
- { 0, 0 }
- }
- };
-
- // begin
- if ((err = evsql_result_begin(&result_info, res)))
- EERROR(err, "query failed");
-
- // get the one row of data
- if ((err = evsql_result_next(res, &ino, DBFS_STAT_RESULT_VALUES(&stat_values))) <= 0)
- EERROR(err = (err ? err : ENOENT), "evsql_result_next");
-
- INFO("\t[dbfs.getattr %p] -> ino=%lu, stat follows", req, (unsigned long int) ino);
-
- // inode
- st.st_ino = ino;
-
- // stat attrs
- if ((err = _dbfs_stat_info(&st, &stat_values)))
- goto error;
-
- // 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);
- struct evsql_query *query;
- 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";
-
- static struct evsql_query_params params = EVSQL_PARAMS(EVSQL_FMT_BINARY) {
- EVSQL_PARAM ( UINT32 ),
-
- EVSQL_PARAMS_END
- };
-
- // build params
- if (0
- || evsql_param_uint32(¶ms, 0, ino)
- )
- SERROR(err = EIO);
-
- // query
- if ((query = evsql_query_params(ctx->db, NULL, sql, ¶ms, _dbfs_attr_res, req)) == NULL)
- SERROR(err = EIO);
-
- // handle interrupts
- fuse_req_interrupt_func(req, dbfs_interrupt_query, query);
-
- // 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);
- struct evsql_query *query;
- 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,
-
- 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(¶ms) )
- || ((to_set & FUSE_SET_ATTR_MODE ) && evsql_param_uint16(¶ms, 0, attr->st_mode) )
- || ((to_set & FUSE_SET_ATTR_UID ) && evsql_param_uint32(¶ms, 1, attr->st_uid) )
- || ((to_set & FUSE_SET_ATTR_GID ) && evsql_param_uint32(¶ms, 2, attr->st_gid) )
- || ((to_set & FUSE_SET_ATTR_SIZE ) && evsql_param_uint32(¶ms, 3, attr->st_size) )
- || ( evsql_param_uint32(¶ms, 4, ino) )
- )
- SERROR(err = EIO);
-
- // trace the query
- evsql_query_debug(sql_buf, ¶ms);
-
- // query... we can pretend it's a getattr :)
- if ((query = evsql_query_params(ctx->db, NULL, sql_buf, ¶ms, _dbfs_attr_res, req)) == NULL)
- SERROR(err = EIO);
-
- // handle interrupts
- fuse_req_interrupt_func(req, dbfs_interrupt_query, query);
-
- // wait
- return;
-
-error:
- if ((err = fuse_reply_err(req, err)))
- EWARNING(err, "fuse_reply_err");
-}
--- a/src/dbfs/common.c Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,95 +0,0 @@
-
-#include <string.h>
-
-#include "dbfs.h"
-#include "../lib/log.h"
-
-mode_t _dbfs_mode (const char *type) {
- if (!strcmp(type, "DIR"))
- return S_IFDIR;
-
- if (!strcmp(type, "REG"))
- return S_IFREG;
-
- if (!strcmp(type, "LNK"))
- return S_IFLNK;
-
- else {
- WARNING("[dbfs] weird mode-type: %s", type);
- return 0;
- }
-}
-
-int _dbfs_check_res (struct evsql_result *res, size_t rows, size_t cols) {
- int err = 0;
-
- // check if it failed
- if (evsql_result_check(res))
- NERROR(evsql_result_error(res));
-
- // not found?
- if (evsql_result_rows(res) == 0 && evsql_result_affected(res) == 0)
- SERROR(err = 1);
-
- // duplicate rows?
- if (rows && evsql_result_rows(res) != rows)
- ERROR("wrong number of rows returned");
-
- // correct number of columns
- if (evsql_result_cols(res) != cols)
- ERROR("wrong number of columns: %zu", evsql_result_cols(res));
-
- // good
- return 0;
-
-error:
- if (!err)
- err = -1;
-
- return err;
-}
-
-err_t dbfs_check_result (struct evsql_result *res, size_t rows, size_t cols) {
- err_t err;
-
- // number of rows returned/affected
- size_t nrows = evsql_result_rows(res) ? : evsql_result_affected(res);
-
- // did the query fail outright?
- if (evsql_result_check(res))
- // dump error message
- NXERROR(err = EIO, evsql_result_error(res));
-
- // SELECT/DELETE/UPDATE WHERE didn't match any rows -> ENOENT
- if (nrows == 0)
- XERROR(err = ENOENT, "no rows returned/affected");
-
- // duplicate rows where one expected?
- if (rows && nrows != rows)
- XERROR(err = EIO, "wrong number of rows: %zu -> %zu", rows, nrows);
-
- // correct number of columns
- if (evsql_result_cols(res) != cols)
- XERROR(err = EIO, "wrong number of columns: %zu -> %zu", cols, evsql_result_cols(res));
-
- // good
- return 0;
-
-error:
- return err;
-}
-
-int _dbfs_stat_info (struct stat *st, struct dbfs_stat_values *values) {
- INFO("\tst_mode=S_IF%s | %ho, st_nlink=%llu, st_size=%llu", values->type, values->mode, (long long unsigned int) values->nlink, (long long unsigned int) values->size);
-
- // convert and store
- st->st_mode = _dbfs_mode(values->type) | values->mode;
- st->st_nlink = values->nlink;
- st->st_size = values->size;
-
- // good
- return 0;
-}
-
-
-
--- a/src/dbfs/dbfs.c Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,100 +0,0 @@
-
-#include <stdlib.h>
-
-#include "dbfs.h"
-#include "../dbfs.h"
-#include "../lib/log.h"
-
-static struct fuse_lowlevel_ops dbfs_llops = {
- .init = dbfs_init,
- .destroy = dbfs_destroy,
- .lookup = dbfs_lookup,
- // .forget // not needed
- .getattr = dbfs_getattr,
- .setattr = dbfs_setattr,
- .readlink = dbfs_readlink,
- .mknod = dbfs_mknod,
- .mkdir = dbfs_mkdir,
- .unlink = dbfs_unlink,
- .rmdir = dbfs_unlink, // this behaves just the same
- .symlink = dbfs_symlink,
- .rename = dbfs_rename,
- .link = dbfs_link,
- .open = dbfs_open,
- .read = dbfs_read,
- .write = dbfs_write,
- .flush = dbfs_flush,
- .release = dbfs_release,
- // .fsync // not needed
- .opendir = dbfs_opendir,
- .readdir = dbfs_readdir,
- .releasedir = dbfs_releasedir,
-};
-
-void dbfs_init (void *userdata, struct fuse_conn_info *conn) {
- INFO("[dbfs.init] userdata=%p, conn=%p", userdata, conn);
-
-}
-
-void dbfs_destroy (void *arg) {
- struct dbfs *ctx = arg;
- INFO("[dbfs.destroy %p]", ctx);
-
- // exit libevent
- event_base_loopexit(ctx->ev_base, NULL);
-}
-
-
-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);
-}
-
-struct dbfs *dbfs_new (struct event_base *ev_base, struct fuse_args *args, const char *db_conninfo) {
- struct dbfs *ctx = NULL;
-
- // alloc ctx
- if ((ctx = calloc(1, sizeof(*ctx))) == NULL)
- ERROR("calloc");
-
- ctx->ev_base = ev_base;
- ctx->db_conninfo = db_conninfo;
-
- // 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, args, &dbfs_llops, ctx)) == NULL)
- ERROR("evfuse_new");
-
- // success
- return ctx;
-
-error:
- if (ctx)
- dbfs_free(ctx);
-
- return NULL;
-}
-
-void dbfs_free (struct dbfs *ctx) {
- // cleanup
- if (ctx->ev_fuse) {
- evfuse_free(ctx->ev_fuse);
-
- ctx->ev_fuse = NULL;
- }
-
- if (ctx->db) {
- // XXX: not yet implemented
- // evsql_close(ctx->db);
- // ctx->db = NULL;
- }
-
- free(ctx);
-}
--- a/src/dbfs/dbfs.h Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,130 +0,0 @@
-#ifndef DBFS_DBFS_H
-#define DBFS_DBFS_H
-
-#include <sys/stat.h>
-#include <errno.h>
-
-#include <event2/event.h>
-
-#include "ops.h"
-#include "../evfuse.h"
-#include "../evsql.h"
-#include "../lib/error.h"
-
-/*
- * Structs and functions shared between all dbfs components
- */
-
-struct dbfs {
- struct event_base *ev_base;
-
- const char *db_conninfo;
- struct evsql *db;
-
- struct evfuse *ev_fuse;
-};
-
-// XXX: not sure how this should work
-#define CACHE_TIMEOUT 1.0
-
-/*
- * Convert the CHAR(4) inodes.type from SQL into a mode_t.
- *
- * Returns zero for unknown types.
- */
-mode_t _dbfs_mode (const char *type);
-
-/*
- * Check that the number of rows and columns in the result set matches what we expect.
- *
- * If rows is nonzero, there must be exactly that many rows (mostly useful for rows=1).
- * The number of columns must always be given, and match.
- *
- * Returns;
- * -1 if the query failed, the columns/rows do not match
- * 0 the results match
- * 1 there were no results (zero rows)
- */
-int _dbfs_check_res (struct evsql_result *res, size_t rows, size_t cols);
-
-/*
- * Same as _dbfs_check_res, but returns ENOENT/EIO directly
- */
-err_t dbfs_check_result (struct evsql_result *res, size_t rows, size_t cols);
-
-/*
- * Stat fields
- */
-
-// columns used for stat_info
-#define DBFS_STAT_COLS " inodes.type, inodes.mode, dbfs_size(inodes.type, inodes.data, inodes.link_path), (SELECT COUNT(*) FROM inodes i LEFT JOIN file_tree ft ON (i.ino = ft.ino) WHERE i.ino = inodes.ino) AS nlink"
-
-
-#define DBFS_STAT_RESULT_INFO \
- { EVSQL_FMT_BINARY, EVSQL_TYPE_STRING }, \
- { EVSQL_FMT_BINARY, EVSQL_TYPE_UINT16 }, \
- { EVSQL_FMT_BINARY, EVSQL_TYPE_UINT32 }, \
- { EVSQL_FMT_BINARY, EVSQL_TYPE_UINT64 }
-
-struct dbfs_stat_values {
- const char *type;
- uint16_t mode;
- uint32_t size;
- uint64_t nlink;
-};
-
-#define DBFS_STAT_RESULT_VALUES(ptr) \
- &(ptr)->type, &(ptr)->mode, &(ptr)->size, &(ptr)->nlink
-
-/*
- * Fill a `struct state` with info retrieved from a SQL query.
- *
- * The result must contain four columns, starting at the given offset:
- * inodes.type, inodes.mode, inodes.size, count(*) AS nlink
- *
- * Note that this does not fill the st_ino field
- */
-int _dbfs_stat_info (struct stat *st, struct dbfs_stat_values *values);
-
-/** interrupt.c
- *
- * Fuse interrupts are handled using fuse_req_interrupt_func. Calling this registers a callback function with the req,
- * which may or may not be called either by fuse_req_interrupt_func, or later on via evfuse's event handler. It is
- * assumed that this will never be called after a call to fuse_reply_*.
- *
- * Hence, to handle an interrupt, we must first ensure that fuse_reply_* will not be called afterwards (it'll return
- * an error), and then we must call fuse_reply_err(req, EINTR).
- *
- * In the simplest case, we can simply submit a query, and then abort it once the req is interrupted (now or later).
- * In the more complicated case, we can check if the request was interrupted, if not, do the query and handle
- * interrupts.
- */
-
-/*
- * Useable as a callback to fuse_req_interrupt_func, will abort the given query and err the req.
- *
- * Due to a locking bug in libfuse 2.7.4, this will actually delay the fuse_req_err until the next event-loop iteration.
- */
-void dbfs_interrupt_query (struct fuse_req *req, void *query_ptr);
-
-/*
- * XXX: More complicated state, is this actually needed?
- */
-struct dbfs_interrupt_ctx {
- struct fuse_req *req;
- struct evsql_query *query;
-
- int interrupted : 1;
-};
-
-/*
- * Register as a fuse interrupt function for simple requests that only run one query without allocating any resources.
- *
- * This will abort the query if the interrupt is run, causing it's callback to not be called.
- *
- * Returns nonzero if the request was already interrupted, zero otherwise. Be careful that the interrupt does not get
- * fired between you checking for it and setting query.
- */
-int dbfs_interrupt_register (struct fuse_req *req, struct dbfs_interrupt_ctx *ctx);
-
-#endif /* DBFS_DBFS_H */
--- a/src/dbfs/dirop.c Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,303 +0,0 @@
-
-#include <stdlib.h>
-#include <assert.h>
-
-#include "dbfs.h"
-#include "op_base.h"
-#include "../dirbuf.h"
-#include "../lib/log.h"
-
-/*
- * Directory related functionality like opendir, readdir, releasedir
- */
-struct dbfs_dirop {
- struct dbfs_op base;
-
- // parent dir inodes
- uint32_t parent;
-
- // for readdir
- struct dirbuf dirbuf;
-};
-
-/*
- * Release the dirbuf.
- */
-static void _dbfs_dirop_free (struct dbfs_op *op_base) {
- struct dbfs_dirop *dirop = (struct dbfs_dirop *) op_base;
-
- // just release the dirbuf
- dirbuf_release(&dirop->dirbuf);
-}
-
-/*
- * Handle the results for the initial attribute lookup for the dir itself during opendir ops.
- */
-static void dbfs_opendir_res (struct evsql_result *res, void *arg) {
- struct dbfs_dirop *dirop = arg;
- int err;
-
- assert(dirop->base.req);
- assert(dirop->base.trans); // query callbacks don't get called if the trans fails
- assert(!dirop->base.open);
-
- // check the results
- if ((err = _dbfs_check_res(res, 1, 2)))
- SERROR(err = (err == 1 ? ENOENT : EIO));
-
- const char *type;
-
- // extract the data
- if (0
- || evsql_result_uint32(res, 0, 0, &dirop->parent, 1 ) // file_tree.parent
- || evsql_result_string(res, 0, 1, &type, 0 ) // inodes.type
- )
- SERROR(err = EIO);
-
- // is it a dir?
- if (_dbfs_mode(type) != S_IFDIR)
- EERROR(err = ENOTDIR, "wrong type: %s", type);
-
- INFO("\t[dbfs.opendir %p:%p] -> ino=%lu, parent=%lu, type=%s", dirop, dirop->base.req, (unsigned long int) dirop->base.ino, (unsigned long int) dirop->parent, type);
-
- // open_fn done, do the open_reply
- if ((err = dbfs_op_open_reply(&dirop->base)))
- goto error;
-
- // success, fallthrough for evsql_result_free
- err = 0;
-
-error:
- if (err)
- // fail it
- dbfs_op_fail(&dirop->base, err);
-
- // free
- evsql_result_free(res);
-}
-
-/*
- * The opendir transaction is ready for use. Query for the given dir's info
- */
-static void dbfs_dirop_open (struct dbfs_op *op_base) {
- struct dbfs_dirop *dirop = (struct dbfs_dirop *) op_base;
- struct dbfs *ctx = fuse_req_userdata(dirop->base.req);
- int err;
-
- assert(dirop->base.trans);
- assert(dirop->base.req);
- assert(!dirop->base.open);
-
- INFO("\t[dbfs.opendir %p:%p] -> trans=%p", dirop, dirop->base.req, dirop->base.trans);
-
- // first fetch info about the dir itself
- const char *sql =
- "SELECT"
- " file_tree.parent, inodes.type"
- " FROM file_tree LEFT OUTER JOIN inodes ON (file_tree.ino = inodes.ino)"
- " WHERE file_tree.ino = $1::int4";
-
- static struct evsql_query_params params = EVSQL_PARAMS(EVSQL_FMT_BINARY) {
- EVSQL_PARAM ( UINT32 ),
-
- EVSQL_PARAMS_END
- };
-
- // build params
- if (0
- || evsql_param_uint32(¶ms, 0, dirop->base.ino)
- )
- SERROR(err = EIO);
-
- // query
- if (evsql_query_params(ctx->db, dirop->base.trans, sql, ¶ms, dbfs_opendir_res, dirop) == NULL)
- SERROR(err = EIO);
-
- // ok, wait for the info results
- return;
-
-error:
- // fail it
- dbfs_op_fail(&dirop->base, err);
-}
-
-/*
- * Handle opendir(), this means starting a new op.
- */
-void dbfs_opendir (struct fuse_req *req, fuse_ino_t ino, struct fuse_file_info *fi) {
- struct dbfs *ctx = fuse_req_userdata(req);
- struct dbfs_dirop *dirop = NULL;
- int err;
-
- // allocate it
- if ((dirop = calloc(1, sizeof(*dirop))) == NULL && (err = EIO))
- ERROR("calloc");
-
- // do the op_open
- if ((err = dbfs_op_open(ctx, &dirop->base, req, ino, fi, _dbfs_dirop_free, dbfs_dirop_open)))
- ERROR("dbfs_op_open");
-
- INFO("[dbfs.opendir %p:%p] ino=%lu, fi=%p", dirop, req, ino, fi);
-
- // wait
- return;
-
-error:
- if (dirop) {
- // we can fail normally
- dbfs_op_fail(&dirop->base, err);
-
- } else {
- // must error out manually as we couldn't alloc the context
- if ((err = fuse_reply_err(req, err)))
- EWARNING(err, "fuse_reply_err");
- }
-}
-
-/*
- * Got the list of files for our readdir() request.
- *
- * Fill up the dirbuf, and then send the reply.
- *
- */
-static void dbfs_readdir_res (struct evsql_result *res, void *arg) {
- struct dbfs_dirop *dirop = arg;
- int err;
- size_t row;
-
- assert(dirop->base.req);
- assert(dirop->base.trans); // query callbacks don't get called if the trans fails
- assert(dirop->base.open);
-
- // check the results
- if ((err = _dbfs_check_res(res, 0, 4)) < 0)
- SERROR(err = EIO);
-
- INFO("\t[dbfs.readdir %p:%p] -> files: res_rows=%zu", dirop, dirop->base.req, evsql_result_rows(res));
-
- // iterate over the rows
- for (row = 0; row < evsql_result_rows(res); row++) {
- uint32_t off, ino;
- const char *name, *type;
-
- // extract the data
- if (0
- || evsql_result_uint32(res, row, 0, &off, 0 ) // file_tree.offset
- || evsql_result_string(res, row, 1, &name, 0 ) // file_tree.name
- || evsql_result_uint32(res, row, 2, &ino, 0 ) // inodes.ino
- || evsql_result_string(res, row, 3, &type, 0 ) // inodes.type
- )
- SERROR(err = EIO);
-
- INFO("\t%zu: off=%lu+2, name=%s, ino=%lu, type=%s", row, (long unsigned int) off, name, (long unsigned int) ino, type);
-
- // add to the dirbuf
- // offsets are just offset + 2
- if ((err = dirbuf_add(dirop->base.req, &dirop->dirbuf, off + 2, off + 3, name, ino, _dbfs_mode(type))) < 0 && (err = EIO))
- ERROR("failed to add dirent for inode=%lu", (long unsigned int) ino);
-
- // stop if it's full
- if (err > 0)
- break;
- }
-
- // send it
- if ((err = dirbuf_done(dirop->base.req, &dirop->dirbuf)))
- EERROR(err, "failed to send buf");
-
- // handled the req
- if ((err = dbfs_op_req_done(&dirop->base)))
- goto error;
-
- // good, fallthrough
- err = 0;
-
-error:
- if (err)
- dbfs_op_fail(&dirop->base, err);
-
- // free
- evsql_result_free(res);
-}
-
-/*
- * Handle a readdir request. This will execute a SQL query inside the transaction to get the files at the given offset,
- * and dbfs_readdir_res will handle the results.
- *
- * If trans failed earlier, detect that and return an error.
- */
-void dbfs_readdir (struct fuse_req *req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi) {
- struct dbfs *ctx = fuse_req_userdata(req);
- struct dbfs_dirop *dirop;
- int err;
-
- // get the op
- if ((dirop = (struct dbfs_dirop *) dbfs_op_req(req, ino, fi)) == NULL)
- return;
-
- INFO("[dbfs.readdir %p:%p] ino=%lu, size=%zu, off=%zu, fi=%p : trans=%p", dirop, req, ino, size, off, fi, dirop->base.trans);
-
- // create the dirbuf
- if (dirbuf_init(&dirop->dirbuf, size, off))
- SERROR(err = EIO);
-
- // add . and ..
- // we set the next offset to 2, because all dirent offsets will be larger than that
- // assume that these two should *always* fit
- if ((err = (0
- || dirbuf_add(req, &dirop->dirbuf, 0, 1, ".", dirop->base.ino, S_IFDIR )
- || dirbuf_add(req, &dirop->dirbuf, 1, 2, "..",
- dirop->parent ? dirop->parent : dirop->base.ino, S_IFDIR )
- )) && (err = EIO))
- ERROR("failed to add . and .. dirents");
-
- // select all relevant file entries
- const char *sql =
- "SELECT"
- " file_tree.\"offset\", file_tree.name, inodes.ino, inodes.type"
- " FROM file_tree LEFT OUTER JOIN inodes ON (file_tree.ino = inodes.ino)"
- " WHERE file_tree.parent = $1::int4 AND file_tree.\"offset\" >= $2::int4"
- " ORDER BY file_tree.\"offset\""
- " LIMIT $3::int4";
-
- static struct evsql_query_params params = EVSQL_PARAMS(EVSQL_FMT_BINARY) {
- EVSQL_PARAM ( UINT32 ),
- EVSQL_PARAM ( UINT32 ),
- EVSQL_PARAM ( UINT32 ),
-
- EVSQL_PARAMS_END
- };
-
- // adjust offset to take . and .. into account
- if (off > 2)
- off -= 2;
-
- // build params
- if (0
- || evsql_param_uint32(¶ms, 0, ino)
- || evsql_param_uint32(¶ms, 1, off)
- || evsql_param_uint32(¶ms, 2, dirbuf_estimate(&dirop->dirbuf, 0))
- )
- SERROR(err = EIO);
-
- // query
- if (evsql_query_params(ctx->db, dirop->base.trans, sql, ¶ms, dbfs_readdir_res, dirop) == NULL)
- SERROR(err = EIO);
-
- // good, wait
- return;
-
-error:
- dbfs_op_fail(&dirop->base, err);
-}
-
-/*
- * "For every [succesfull] opendir call there will be exactly one releasedir call."
- *
- * The dirop may be in a failed state.
- */
-void dbfs_releasedir (struct fuse_req *req, fuse_ino_t ino, struct fuse_file_info *fi) {
- // just passthrough to dbfs_op
- dbfs_op_release(req, ino, fi);
-}
-
--- a/src/dbfs/fileop.c Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,310 +0,0 @@
-#include <stdlib.h>
-#include <assert.h>
-
-#include <postgresql/libpq/libpq-fs.h>
-
-#include "dbfs.h"
-#include "op_base.h"
-#include "../lib/log.h"
-
-/*
- * A file-operation, i.e. a sequence consisting of an OPEN, a multitude of READ/WRITE, followed by zero or more FLUSHes, and finally a single RELEASE.
- *
- * For historical reasons this opens a transaction and keeps it between open/release, but reads/writes now use the oid directly and are transactionless.
- */
-
-struct dbfs_fileop {
- struct dbfs_op base;
-
- uint32_t oid;
-// uint32_t lo_fd;
-};
-
-static void _dbfs_fileop_free (struct dbfs_op *op_base) {
- struct dbfs_fileop *fop = (struct dbfs_fileop *) op_base;
-
- /* no-op */
- (void) fop;
-}
-
-static void dbfs_open_res (struct evsql_result *res, void *arg) {
- struct dbfs_fileop *fop = arg;
- int err;
-
- // check the results
- if ((err = _dbfs_check_res(res, 1, 2)))
- SERROR(err = (err == 1 ? ENOENT : EIO));
-
- const char *type;
-
- // extract the data
- if (0
- || evsql_result_string(res, 0, 0, &type, 0 ) // inodes.type
- || evsql_result_uint32(res, 0, 1, &fop->oid, 0 ) // inodes.data
- )
- SERROR(err = EIO);
-
- // is it a dir?
- if (_dbfs_mode(type) != S_IFREG)
- EERROR(err = EINVAL, "wrong type: %s", type);
-
- INFO("\t[dbfs.open %p:%p] -> ino=%lu, type=%s", fop, fop->base.req, (unsigned long int) fop->base.ino, type);
-
- // open_fn done, do the open_reply
- if ((err = dbfs_op_open_reply(&fop->base)))
- goto error;
-
- // success, fallthrough for evsql_result_free
- err = 0;
-
-error:
- if (err)
- // fail it
- dbfs_op_fail(&fop->base, err);
-
- // free
- evsql_result_free(res);
-}
-
-static void dbfs_fileop_open (struct dbfs_op *op_base) {
- struct dbfs_fileop *fop = (struct dbfs_fileop *) op_base;
- struct dbfs *ctx = fuse_req_userdata(fop->base.req);
- int err;
-
- // make sure the file actually exists
- const char *sql =
- "SELECT"
- " inodes.type, inodes.data"
- " FROM inodes"
- " WHERE inodes.ino = $1::int4";
-
- static struct evsql_query_params params = EVSQL_PARAMS(EVSQL_FMT_BINARY) {
- EVSQL_PARAM ( UINT32 ),
-
- EVSQL_PARAMS_END
- };
-
- // build params
- if (0
- || evsql_param_uint32(¶ms, 0, fop->base.ino)
- )
- SERROR(err = EIO);
-
- // query
- if (evsql_query_params(ctx->db, fop->base.trans, sql, ¶ms, dbfs_open_res, fop) == NULL)
- SERROR(err = EIO);
-
- // ok, wait for the info results
- return;
-
-error:
- // fail it
- dbfs_op_fail(&fop->base, err);
-}
-
-void dbfs_open (struct fuse_req *req, fuse_ino_t ino, struct fuse_file_info *fi) {
- struct dbfs *ctx = fuse_req_userdata(req);
- struct dbfs_fileop *fop = NULL;
- int err;
-
- // allocate it
- if ((fop = calloc(1, sizeof(*fop))) == NULL && (err = EIO))
- ERROR("calloc");
-
- // do the op_open
- if ((err = dbfs_op_open(ctx, &fop->base, req, ino, fi, _dbfs_fileop_free, dbfs_fileop_open)))
- ERROR("dbfs_op_open");
-
- // log
- INFO("[dbfs.open %p:%p] ino=%lu, fi->flags=%04X", fop, req, ino, fi->flags);
-
- // wait
- return;
-
-error:
- if (fop) {
- // we can fail normally
- dbfs_op_fail(&fop->base, err);
-
- } else {
- // must error out manually as we couldn't alloc the context
- if ((err = -fuse_reply_err(req, err)))
- EWARNING(err, "fuse_reply_err");
- }
-}
-
-void dbfs_read_res (struct evsql_result *res, void *arg) {
- struct fuse_req *req = arg;
- int err;
- const char *buf;
- size_t size;
-
- // check the results
- if ((err = _dbfs_check_res(res, 1, 1)) < 0)
- SERROR(err = EIO);
-
- // get the data
- if (evsql_result_binary(res, 0, 0, &buf, &size, 0))
- SERROR(err = EIO);
-
- INFO("\t[dbfs.read %p] -> size=%zu", req, size);
-
- // send it
- if ((err = -fuse_reply_buf(req, buf, size)))
- EERROR(err, "fuse_reply_buf");
-
- // good, fallthrough
- err = 0;
-
-error:
- if (err)
- fuse_reply_err(req, err);
-
-
- // free
- evsql_result_free(res);
-}
-
-void dbfs_read (struct fuse_req *req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi) {
- struct dbfs *ctx = fuse_req_userdata(req);
- int err;
-
- // log
- INFO("[dbfs.read %p] ino=%lu, size=%zu, off=%lu, fi->flags=%04X", req, ino, size, off, fi->flags);
-
- // query
- const char *sql =
- "SELECT"
- " lo_pread_oid(data, $1::int4, $2::int4)"
- " FROM inodes"
- " WHERE ino = $3::int4";
-
- static struct evsql_query_params params = EVSQL_PARAMS(EVSQL_FMT_BINARY) {
- EVSQL_PARAM ( UINT32 ), // len
- EVSQL_PARAM ( UINT32 ), // off
- EVSQL_PARAM ( UINT32 ), // ino
-
- EVSQL_PARAMS_END
- };
-
- // build params
- if (0
- || evsql_param_uint32(¶ms, 0, size)
- || evsql_param_uint32(¶ms, 1, off)
- || evsql_param_uint32(¶ms, 2, ino)
- )
- SERROR(err = EIO);
-
- // query, transactionless
- if (evsql_query_params(ctx->db, NULL, sql, ¶ms, dbfs_read_res, req) == NULL)
- SERROR(err = EIO);
-
- // ok, wait for the info results
- return;
-
-error:
- fuse_reply_err(req, err);
-}
-
-void dbfs_write_res (struct evsql_result *res, void *arg) {
- struct fuse_req *req = 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("\t[dbfs.write %p] -> size=%lu", req, (long unsigned int) size);
-
- // send it
- if ((err = -fuse_reply_write(req, size)))
- EERROR(err, "fuse_reply_write");
-
- // good, fallthrough
- err = 0;
-
-error:
- if (err)
- fuse_reply_err(req, 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);
- int err;
-
- // log
- INFO("[dbfs.write %p] ino=%lu, size=%zu, off=%lu, fi->flags=%04X", req, ino, size, off, fi->flags);
-
- // query
- const char *sql =
- "SELECT"
- " lo_pwrite_oid(data, $1::bytea, $2::int4)"
- " FROM inodes"
- " WHERE ino = $3::int4";
-
- static struct evsql_query_params params = EVSQL_PARAMS(EVSQL_FMT_BINARY) {
- EVSQL_PARAM ( BINARY ), // buf
- EVSQL_PARAM ( UINT32 ), // off
- EVSQL_PARAM ( UINT32 ), // oid
-
- EVSQL_PARAMS_END
- };
-
- // build params
- if (0
- || evsql_param_binary(¶ms, 0, buf, size)
- || evsql_param_uint32(¶ms, 1, off)
- || evsql_param_uint32(¶ms, 2, ino)
- )
- SERROR(err = EIO);
-
- // query
- if (evsql_query_params(ctx->db, NULL, sql, ¶ms, dbfs_write_res, req) == NULL)
- SERROR(err = EIO);
-
- // ok, wait for the info results
- return;
-
-error:
- fuse_reply_err(req, err);
-}
-
-void dbfs_flush (struct fuse_req *req, fuse_ino_t ino, struct fuse_file_info *fi) {
- struct dbfs_fileop *fop;
- int err;
-
- // get the fop
- if ((fop = (struct dbfs_fileop *) dbfs_op_req(req, ino, fi)) == NULL)
- return;
-
- // log
- INFO("[dbfs.flush %p:%p] ino=%lu", fop, req, ino);
-
- // and reply...
- if ((err = -fuse_reply_err(req, 0)))
- EWARNING(err, "fuse_reply_err");
-
- // done
- if ((err = dbfs_op_req_done(&fop->base)))
- goto error;
-
- // good
- return;
-
-error:
- dbfs_op_fail(&fop->base, err);
-}
-
-void dbfs_release (struct fuse_req *req, fuse_ino_t ino, struct fuse_file_info *fi) {
- // just passthrough to dbfs_op
- // the lo_fd will be closed automatically
- dbfs_op_release(req, ino, fi);
-}
--- a/src/dbfs/interrupt.c Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,61 +0,0 @@
-
-#include "dbfs.h"
-
-void _dbfs_interrupt_reply (evutil_socket_t _unused1, short _unused2, void *req_ptr) {
- struct fuse_req *req = req_ptr;
- err_t err;
-
- // error the req
- if ((err = -fuse_reply_err(req, EINTR)))
- EWARNING(err, "fuse_reply_err");
-}
-
-void dbfs_interrupt_query (struct fuse_req *req, void *query_ptr) {
- struct dbfs *ctx = fuse_req_userdata(req);
- struct evsql_query *query = query_ptr;
- struct timeval tv;
- err_t err;
-
- // abort query
- evsql_query_abort(NULL, query);
-
- // error the req
- if ((err = -fuse_reply_err(req, EINTR)))
- EWARNING(err, "fuse_reply_err");
-
- /*
- * Due to a locking bug in libfuse (at least 2.7.4), we can't call fuse_reply_err from the interrupt function, so we must
- * schedule after this function returns.
- * /
- tv.tv_sec = 0;
- tv.tv_usec = 0;
- if (event_base_once(ctx->ev_base, -1, EV_TIMEOUT, _dbfs_interrupt_reply, req, &tv))
- PWARNING("event_base_once failed, dropping req reply: %p", req);
- */
-}
-
-void _dbfs_interrupt_ctx (struct fuse_req *req, void *ctx_ptr) {
- // dereference ctx
- struct dbfs_interrupt_ctx *ctx = ctx_ptr;
-
- // just cancel query if pending
- if (ctx->query) {
- evsql_query_abort(NULL, ctx->query);
- ctx->query = NULL;
- }
-
- // mark as interrupted
- ctx->interrupted = 1;
-}
-
-int dbfs_interrupt_register (struct fuse_req *req, struct dbfs_interrupt_ctx *ctx) {
- // initialize
- ctx->query = NULL;
- ctx->interrupted = 0;
-
- // just pass over to fuse_req_interrupt_func
- fuse_req_interrupt_func(req, _dbfs_interrupt_ctx, ctx);
-
- // XXX: not implemented fully
- return -1;
-}
--- a/src/dbfs/link.c Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,285 +0,0 @@
-#include "dbfs.h"
-
-/*
- * Handling simple ino-related ops, like lookup, readlink, unlink and link
- */
-
-#include "../lib/log.h"
-#include "../lib/misc.h"
-
-/*
- * Used for lookup and link
- */
-void dbfs_entry_res (struct evsql_result *res, void *arg) {
- struct fuse_req *req = arg;
- struct fuse_entry_param e; ZINIT(e);
- err_t err = 0;
-
- uint32_t ino;
- struct dbfs_stat_values stat_values;
-
- // result info
- static struct evsql_result_info result_info = {
- 0, {
- { EVSQL_FMT_BINARY, EVSQL_TYPE_UINT32 }, // inodes.ino
- DBFS_STAT_RESULT_INFO,
- { 0, 0 }
- }
- };
-
- // begin
- if ((err = evsql_result_begin(&result_info, res)))
- EERROR(err, "query failed");
-
- // get the one row of data
- if ((err = evsql_result_next(res, &ino, DBFS_STAT_RESULT_VALUES(&stat_values))) <= 0)
- EERROR(err = (err ? err : ENOENT), "evsql_result_next");
-
- INFO("\t[dbfs.lookup] -> ino=%u", ino);
-
- // stat attrs
- if ((err = _dbfs_stat_info(&e.attr, &stat_values)))
- goto error;
-
- // other attrs
- e.ino = e.attr.st_ino = ino;
- 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");
-
- // done
- evsql_result_end(res);
-}
-
-void dbfs_lookup (struct fuse_req *req, fuse_ino_t parent, const char *name) {
- struct dbfs *ctx = fuse_req_userdata(req);
- struct evsql_query *query;
- int err;
-
- INFO("[dbfs.lookup] parent=%lu name=%s", parent, name);
-
- // query info
- static struct evsql_query_info query_info = {
- .sql = "SELECT"
- " inodes.ino, " DBFS_STAT_COLS
- " FROM file_tree INNER JOIN inodes ON (file_tree.ino = inodes.ino)"
- " WHERE file_tree.parent = $1::int4 AND file_tree.name = $2::varchar",
-
- .params = {
- EVSQL_TYPE ( UINT32 ),
- EVSQL_TYPE ( STRING ),
-
- EVSQL_TYPE_END
- }
- };
-
- // query
- if ((query = evsql_query_exec(ctx->db, NULL, &query_info, dbfs_entry_res, req,
- (uint32_t) parent,
- (const char *) name
- )) == NULL)
- EERROR(err = EIO, "evsql_query_params");
-
- // handle interrupts
- fuse_req_interrupt_func(req, dbfs_interrupt_query, query);
-
- // wait
- return;
-
-error:
- if ((err = -fuse_reply_err(req, err)))
- EWARNING(err, "fuse_reply_err");
-}
-
-void dbfs_readlink_res (struct evsql_result *res, void *arg) {
- struct fuse_req *req = arg;
- int err = 0;
-
- uint32_t ino;
- const char *type, *link;
-
- // result info
- static struct evsql_result_info result_info = {
- 0, {
- EVSQL_TYPE ( UINT32 ),
- EVSQL_TYPE ( STRING ),
- EVSQL_TYPE ( STRING ),
-
- EVSQL_TYPE_END
- }
- };
-
- // begin
- if ((err = evsql_result_begin(&result_info, res)))
- EERROR(err, "evsql_result_begin");
-
- // get the row of data
- if ((err = evsql_result_next(res,
- &ino, &type, &link
- )) <= 0)
- SERROR(err = err || ENOENT);
-
- // is it a symlink?
- if (_dbfs_mode(type) != S_IFLNK)
- EERROR(err = EINVAL, "wrong type: %s", type);
-
- INFO("\t[dbfs.readlink %p] -> ino=%lu, type=%s, link=%s", req, (unsigned long int) ino, type, link);
-
- // reply
- if ((err = -fuse_reply_readlink(req, link)))
- EERROR(err, "fuse_reply_readlink");
-
-error:
- if (err && (err = -fuse_reply_err(req, err)))
- EWARNING(err, "fuse_reply_err");
-
- // free
- evsql_result_free(res);
-}
-
-void dbfs_readlink (struct fuse_req *req, fuse_ino_t ino) {
- struct dbfs *ctx = fuse_req_userdata(req);
- struct evsql_query *query;
- int err;
-
- INFO("[dbfs.readlink %p] ino=%lu", req, ino);
-
- // query info
- static struct evsql_query_info query_info = {
- .sql = "SELECT"
- " inodes.ino, inodes.type, inodes.link_path"
- " FROM inodes"
- " WHERE inodes.ino = $1::int4",
-
- .params = {
- EVSQL_TYPE ( UINT32 ),
-
- EVSQL_TYPE_END
- }
- };
-
- // query
- if ((query = evsql_query_exec(ctx->db, NULL, &query_info, dbfs_readlink_res, req,
- (uint32_t) ino
- )) == NULL)
- SERROR(err = EIO);
-
- // handle interrupts
- fuse_req_interrupt_func(req, dbfs_interrupt_query, query);
-
- // wait
- return;
-
-error:
- if ((err = -fuse_reply_err(req, err)))
- EWARNING(err, "fuse_reply_err");
-
-}
-
-void dbfs_unlink_res (struct evsql_result *res, void *arg) {
- struct fuse_req *req = arg;
- int err = 0;
-
- // check
- if ((err = evsql_result_check(res)))
- ERROR("evsql_result_check: %s", evsql_result_error(res));
-
- INFO("\t[dbfs.unlink %p] -> OK", req);
-
- // reply
- if ((err = -fuse_reply_err(req, 0)))
- EERROR(err, "fuse_reply_err");
-
-error:
- if (err && (err = -fuse_reply_err(req, err)))
- EWARNING(err, "fuse_reply_err");
-
- // free
- evsql_result_free(res);
-}
-
-void dbfs_unlink (struct fuse_req *req, fuse_ino_t parent, const char *name) {
- struct dbfs *ctx = fuse_req_userdata(req);
- struct evsql_query *query;
- int err;
-
- INFO("[dbfs.unlink %p] parent=%lu, name=%s", req, parent, name);
-
- // query info
- static struct evsql_query_info query_info = {
- .sql = "DELETE"
- " FROM file_tree"
- " WHERE parent = $1::int4 AND name = $2::varchar",
-
- .params = {
- EVSQL_TYPE ( UINT32 ),
- EVSQL_TYPE ( STRING ),
-
- EVSQL_TYPE_END
- }
- };
-
- // query
- if ((query = evsql_query_exec(ctx->db, NULL, &query_info, dbfs_unlink_res, req,
- (uint32_t) parent,
- (const char *) name
- )) == NULL)
- SERROR(err = EIO);
-
- // handle interrupts
- fuse_req_interrupt_func(req, dbfs_interrupt_query, query);
-
- // wait
- return;
-
-error:
- if ((err = -fuse_reply_err(req, err)))
- EWARNING(err, "fuse_reply_err");
-}
-
-void dbfs_link (struct fuse_req *req, fuse_ino_t ino, fuse_ino_t newparent, const char *newname) {
- struct dbfs *ctx = fuse_req_userdata(req);
- struct evsql_query *query;
- int err;
-
- INFO("[dbfs.link %p] ino=%lu, newparent=%lu, newname=%s", req, ino, newparent, newname);
-
- // query info
- static struct evsql_query_info query_info = {
- .sql = "SELECT ino, type, mode, size, nlink FROM dbfs_link($1::int4, $2::int4, $3::varchar)",
-
- .params = {
- EVSQL_TYPE ( UINT32 ),
- EVSQL_TYPE ( UINT32 ),
- EVSQL_TYPE ( STRING ),
-
- EVSQL_TYPE_END
- }
- };
-
- // query
- if ((query = evsql_query_exec(ctx->db, NULL, &query_info, dbfs_entry_res, req,
- (uint32_t) ino,
- (uint32_t) newparent,
- (const char *) newname
- )) == NULL)
- SERROR(err = EIO);
-
- // handle interrupts
- fuse_req_interrupt_func(req, dbfs_interrupt_query, query);
-
- // wait
- return;
-
-error:
- if ((err = -fuse_reply_err(req, err)))
- EWARNING(err, "fuse_reply_err");
-}
-
--- a/src/dbfs/mk.c Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,243 +0,0 @@
-#include <stdlib.h>
-#include <assert.h>
-#include <string.h>
-
-#include "trans.h"
-#include "../lib/log.h"
-
-struct dbfs_mk_ctx {
- struct dbfs_trans base;
-
- const char *type, *data_expr;
- char *link, *name;
- uint16_t mode;
- uint32_t ino, parent;
-
- unsigned char is_dir : 1;
-};
-
-// default mode for symlinks
-#define DBFS_SYMLINK_MODE 0777
-
-// max. size for an dbfs_mk INSERT query
-#define DBFS_MK_SQL_MAX 512
-
-void dbfs_mk_free (struct dbfs_trans *ctx_base) {
- struct dbfs_mk_ctx *ctx = (struct dbfs_mk_ctx *) ctx_base;
-
- free(ctx->link);
- free(ctx->name);
-}
-
-void dbfs_mk_commit (struct dbfs_trans *ctx_base) {
- struct dbfs_mk_ctx *ctx = (struct dbfs_mk_ctx *) ctx_base;
- struct fuse_entry_param e;
- int err;
-
- // build entry
- e.ino = e.attr.st_ino = ctx->ino;
- e.attr.st_mode = _dbfs_mode(ctx->type) | ctx->mode;
- e.attr.st_size = ctx->link ? strlen(ctx->link) : 0;
- e.attr.st_nlink = 1;
- e.attr_timeout = e.entry_timeout = CACHE_TIMEOUT;
-
- // reply
- if ((err = fuse_reply_entry(ctx_base->req, &e)))
- goto error;
-
- // req good
- ctx_base->req = NULL;
-
- // free
- dbfs_trans_free(ctx_base);
-
- // return
- return;
-
-error:
- dbfs_trans_fail(ctx_base, err);
-}
-
-void dbfs_mk_filetree (struct evsql_result *res, void *arg) {
- struct dbfs_mk_ctx *ctx = arg;
- int err = EIO;
-
- // check results
- if (_dbfs_check_res(res, 0, 0) < 0)
- goto error;
-
- // commit
- dbfs_trans_commit(&ctx->base);
-
- // fallthrough for result_free
- err = 0;
-
-error:
- if (err)
- dbfs_trans_fail(&ctx->base, err);
-
- evsql_result_free(res);
-}
-
-void dbfs_mk_inode (struct evsql_result *res, void *arg) {
- struct dbfs_mk_ctx *ctx = arg;
- struct dbfs *dbfs_ctx = fuse_req_userdata(ctx->base.req);
- int err = EIO;
-
- // check result
- if ((err = _dbfs_check_res(res, 1, 1)))
- SERROR(err = err > 0 ? ENOENT : EIO);
-
- // get ino
- if (evsql_result_uint32(res, 0, 0, &ctx->ino, 0))
- goto error;
-
- // insert file_tree entry
- const char *sql =
- "INSERT"
- " INTO file_tree (name, parent, ino, ino_dir)"
- " VALUES ($1::varchar, $2::int4, $3::int4, $4::int4)";
-
- static struct evsql_query_params params = EVSQL_PARAMS(EVSQL_FMT_BINARY) {
- EVSQL_PARAM ( STRING ),
- EVSQL_PARAM ( UINT32 ),
- EVSQL_PARAM ( UINT32 ),
- EVSQL_PARAM ( UINT32 ),
-
- EVSQL_PARAMS_END
- };
-
- if (0
- || evsql_param_string(¶ms, 0, ctx->name)
- || evsql_param_uint32(¶ms, 1, ctx->parent)
- || evsql_param_uint32(¶ms, 2, ctx->ino)
- || ctx->is_dir ? evsql_param_uint32(¶ms, 3, ctx->ino) : evsql_param_null(¶ms, 3)
- )
- goto error;
-
- // query
- if (evsql_query_params(dbfs_ctx->db, ctx->base.trans, sql, ¶ms, dbfs_mk_filetree, ctx))
- goto error;
-
- // fallthrough for result_free
- err = 0;
-
-error:
- if (err)
- dbfs_trans_fail(&ctx->base, err);
-
- evsql_result_free(res);
-}
-
-void dbfs_mk_begin (struct dbfs_trans *ctx_base) {
- struct dbfs_mk_ctx *ctx = (struct dbfs_mk_ctx *) ctx_base;
- struct dbfs *dbfs_ctx = fuse_req_userdata(ctx_base->req);
- int ret;
-
- // insert inode
- char sql_buf[DBFS_MK_SQL_MAX];
-
- if ((ret = snprintf(sql_buf, DBFS_MK_SQL_MAX,
- "INSERT"
- " INTO inodes (type, mode, data, link_path)"
- " VALUES ($1::char(3), $2::int2, %s, $3::varchar)"
- " RETURNING inodes.ino", ctx->data_expr ? ctx->data_expr : "NULL"
- )) >= DBFS_MK_SQL_MAX)
- ERROR("sql_buf is too small: %d", ret);
-
- static struct evsql_query_params params = EVSQL_PARAMS(EVSQL_FMT_BINARY) {
- EVSQL_PARAM ( STRING ),
- EVSQL_PARAM ( UINT16 ),
- EVSQL_PARAM ( STRING ),
-
- EVSQL_PARAMS_END
- };
-
- if (0
- || evsql_param_string(¶ms, 0, ctx->type)
- || evsql_param_uint16(¶ms, 1, ctx->mode)
- || evsql_param_string(¶ms, 2, ctx->link)
- )
- goto error;
-
- if (evsql_query_params(dbfs_ctx->db, ctx_base->trans, sql_buf, ¶ms, dbfs_mk_inode, ctx) == NULL)
- goto error;
-
- return;
-
-error:
- dbfs_trans_fail(ctx_base, EIO);
-}
-
-/*
- * It is assumed that name and link_path must be copied, but type remains useable
- */
-void dbfs_mk (struct fuse_req *req, fuse_ino_t parent, const char *name, const char *type, uint16_t mode, const char *data_expr, const char *link, unsigned char is_dir) {
- struct dbfs_mk_ctx *ctx = NULL;
-
- // alloc
- if ((ctx = calloc(1, sizeof(*ctx))) == NULL)
- ERROR("calloc");
-
- // start trans
- if (dbfs_trans_init(&ctx->base, req))
- goto error;
-
- // callbacks
- ctx->base.free_fn = dbfs_mk_free;
- ctx->base.begin_fn = dbfs_mk_begin;
- ctx->base.commit_fn = dbfs_mk_commit;
-
- // state
- ctx->ino = 0;
- ctx->parent = parent;
- ctx->type = type;
- ctx->data_expr = data_expr;
- ctx->mode = mode;
- ctx->is_dir = is_dir;
-
- // copy volatile strings
- if (
- (link && (ctx->link = strdup(link)) == NULL)
- || (name && (ctx->name = strdup(name)) == NULL)
- )
- ERROR("strdup");
-
- // log
- INFO("[dbfs.mk %p:%p] parent=%lu, name=%s, type=%s, mode=%#04o data_expr=%s link=%s is_dir=%hhd", ctx, req, parent, name, type, mode, data_expr, link, is_dir);
-
- // wait
- return;
-
-error:
- if (ctx)
- dbfs_trans_fail(&ctx->base, EIO);
-}
-
-/*
- * These are all just aliases to dbfs_mk
- */
-void dbfs_mknod (struct fuse_req *req, fuse_ino_t parent, const char *name, mode_t mode, dev_t rdev) {
- int err;
-
- if ((mode & S_IFMT) != S_IFREG)
- EERROR(err = EINVAL, "mode is not REG: %#08o", mode);
-
- dbfs_mk(req, parent, name, "REG", mode & 07777, "lo_create(0)", NULL, 0);
-
- return;
-
-error:
- if ((err = fuse_reply_err(req, err)))
- EWARNING(err, "fuse_reply_error");
-}
-
-void dbfs_mkdir (struct fuse_req *req, fuse_ino_t parent, const char *name, mode_t mode) {
- dbfs_mk(req, parent, name, "DIR", mode, NULL, NULL, 1);
-}
-
-
-void dbfs_symlink (struct fuse_req *req, const char *link, fuse_ino_t parent, const char *name) {
- dbfs_mk(req, parent, name, "LNK", DBFS_SYMLINK_MODE, NULL, link, 0);
-}
-
--- a/src/dbfs/op_base.c Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,299 +0,0 @@
-#include <stdlib.h>
-#include <assert.h>
-
-#include "op_base.h"
-#include "../lib/log.h"
-
-/*
- * Free the op.
- *
- * The op must any oustanding request responded to first, must not be open, and must not have a transaction.
- *
- * The op will be free'd.
- */
-static void dbfs_op_free (struct dbfs_op *op) {
- assert(op);
- assert(!op->open);
- assert(!op->req);
- assert(!op->trans);
-
- // free_fn
- if (op->free_fn)
- op->free_fn(op);
-
- // and then free the op
- free(op);
-}
-
-void dbfs_op_fail (struct dbfs_op *op, int err) {
- assert(op->req);
-
- if (op->trans) {
- // abort the trans
- evsql_trans_abort(op->trans);
-
- op->trans = NULL;
- }
-
- // send an error reply
- if ((err = fuse_reply_err(op->req, err)))
- // XXX: handle these failures /somehow/, or requests will hang and interrupts might handle invalid ops
- EFATAL(err, "\tdbfs_op.fail %p:%p -> reply with fuse_reply_err", op, op->req);
-
- // drop the req
- op->req = NULL;
-
- // is it open?
- if (!op->open) {
- // no, we can free it now and then forget about the whole thing
- dbfs_op_free(op);
-
- } else {
- // we need to wait for release
-
- }
-}
-
-/*
- * The op_open transaction is ready for use.
- */
-static void dbfs_op_ready (struct evsql_trans *trans, void *arg) {
- struct dbfs_op *op = arg;
-
- assert(trans == op->trans);
- assert(op->req);
- assert(!op->open);
-
- INFO("\tdbfs_op.ready %p:%p -> trans=%p", op, op->req, trans);
-
- // remember the transaction
- op->trans = trans;
-
- // ready
- op->open_fn(op);
-
- // good
- return;
-}
-
-/*
- * The op trans was committed, i.e. release has completed
- */
-static void dbfs_op_done (struct evsql_trans *trans, void *arg) {
- struct dbfs_op *op = arg;
- int err;
-
- assert(trans == op->trans);
- assert(op->req);
- assert(!op->open); // should not be considered as open anymore at this point, as errors should release
-
- INFO("\tdbfs_op.done %p:%p -> OK", op, op->req);
-
- // forget trans
- op->trans = NULL;
-
- // just reply
- if ((err = fuse_reply_err(op->req, 0)))
- // XXX: handle these failures /somehow/, or requests will hang and interrupts might handle invalid ops
- EFATAL(err, "dbfs_op.done %p:%p -> reply with fuse_reply_err", op, op->req);
-
- // req is done
- op->req = NULL;
-
- // then we can just free op
- dbfs_op_free(op);
-}
-
-/*
- * The op trans has failed, somehow, at some point, some where.
- *
- * This might happend during the open evsql_trans, during a read evsql_query, during the release
- * evsql_trans_commit, or at any point in between.
- *
- * 1) loose the transaction
- * 2) if op has a req, we handle failing it
- */
-static void dbfs_op_error (struct evsql_trans *trans, void *arg) {
- struct dbfs_op *op = arg;
-
- // unless we fail
- assert(trans == op->trans);
-
- INFO("\tdbfs_op.error %p:%p -> evsql transaction error: %s", op, op->req, evsql_trans_error(trans));
-
- // deassociate the trans
- op->trans = NULL;
-
- // if we were answering a req, error it out, and if the op isn't open, free
- // if we didn't have a req outstanding, the op must be open, so we wouldn't free it in any case, and must wait
- // for the next read/release to detect this and return an error reply
- if (op->req)
- dbfs_op_fail(op, EIO);
- else
- assert(op->open);
-}
-
-int dbfs_op_open (struct dbfs *ctx, struct dbfs_op *op, struct fuse_req *req, fuse_ino_t ino, struct fuse_file_info *fi, dbfs_op_free_cb free_fn, dbfs_op_open_cb open_fn) {
- int err;
-
- assert(op && req && ino && fi);
- assert(!(op->req || op->ino));
-
- // initialize the op
- op->req = req;
- op->ino = ino;
- // copy *fi since it's on the stack
- op->fi = *fi;
- op->fi.fh = (uint64_t) op;
- op->free_fn = free_fn;
- op->open_fn = open_fn;
-
- // start a new transaction
- if ((op->trans = evsql_trans(ctx->db, EVSQL_TRANS_SERIALIZABLE, dbfs_op_error, dbfs_op_ready, dbfs_op_done, op)) == NULL)
- SERROR(err = EIO);
-
- // XXX: handle interrupts
-
- // good
- return 0;
-
-error:
- // nothing of ours to cleanup
- return err;
-}
-
-int dbfs_op_open_reply (struct dbfs_op *op) {
- int err;
-
- // detect earlier failures
- if (!op->trans && (err = EIO))
- ERROR("op trans has failed");
-
- // send the openddir reply
- if ((err = fuse_reply_open(op->req, &op->fi)))
- EERROR(err, "fuse_reply_open");
-
- // req is done
- op->req = NULL;
-
- // op is now open
- op->open = 1;
-
- // good
- return 0;
-
-error:
- return err;
-}
-
-struct dbfs_op *dbfs_op_req (struct fuse_req *req, fuse_ino_t ino, struct fuse_file_info *fi) {
- struct dbfs_op *op = (struct dbfs_op *) fi->fh;
- int err;
-
- // validate
- assert(op);
- assert(op->open);
- assert(op->ino == ino);
-
- // detect concurrent requests
- if (op->req) {
- // must handle req ourself, shouldn't fail the other req
- WARNING("op.%p: concurrent req: %p -> %p", op, op->req, req);
-
- // XXX: ignore error errors...
- fuse_reply_err(req, EBUSY);
-
- return NULL;
-
- } else
- // store the new req
- op->req = req;
-
- // inodes change?
- if (op->ino != ino)
- XERROR(err = EBADF, "op.%p: wrong ino: %u -> %lu", op, op->ino, ino);
-
- // detect earlier failures
- if (!op->trans && (err = EIO))
- ERROR("op trans has failed");
-
- // good
- return op;
-
-error:
- dbfs_op_fail(op, err);
-
- return NULL;
-}
-
-int dbfs_op_req_done (struct dbfs_op *op) {
- // validate
- assert(op);
- assert(op->req);
- assert(op->open);
-
- // unassign the req
- op->req = NULL;
-
- // k
- return 0;
-}
-
-void dbfs_op_release (struct fuse_req *req, fuse_ino_t ino, struct fuse_file_info *fi) {
- struct dbfs_op *op = (struct dbfs_op *) fi->fh;
- int err;
-
- assert(op);
- assert(!op->req);
- assert(op->ino == ino);
-
- // update to this req
- op->req = req;
-
- // fi is irrelevant, we don't touch the flags anyways
- (void) fi;
-
- // handle failed trans
- if (!op->trans && (err = EIO))
- ERROR("trans has failed");
-
- // log
- INFO("\tdbfs_op.release %p:%p : ino=%lu, fi=%p : trans=%p", op, req, ino, fi, op->trans);
-
- // we must commit the transaction.
- // Note that this might cause dbfs_op_error to be called, we can tell if that happaned by looking at op->req
- // or op->trans - this means that we need to keep the op open when calling trans_commit, so that op_error
- // doesn't free it out from underneath us.
- if (evsql_trans_commit(op->trans))
- SERROR(err = EIO);
-
- // fall-through to cleanup
- err = 0;
-
-error:
- // the op is not open anymore and can be free'd next, because we either:
- // a) already caught an error
- // b) we get+send an error later on
- // c) we get+send the done/no-error later on
- op->open = 0;
-
- // did the commit/pre-commit-checks fail?
- if (err) {
- // a) the trans failed earlier (read), so we have a req but no trans
- // b) the trans commit failed, op_error got called -> no req and no trans
- // c) the trans commit failed, op_error did not get called -> have req and trans
- // we either have a req (may or may not have trans), or we don't have a trans either
- // i.e. there is no situation where we don't have a req but do have a trans
-
- if (op->req)
- dbfs_op_fail(op, err);
- else
- assert(!op->trans);
-
- } else {
- // shouldn't slip by, op_done should not get called directly. Once it does, it will handle both.
- assert(op->req);
- assert(op->trans);
- }
-}
-
--- a/src/dbfs/op_base.h Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,96 +0,0 @@
-#ifndef DBFS_OP_BASE_H
-#define DBFS_OP_BASE_H
-
-#include "dbfs.h"
-
-// forward-declaration for callbacks
-struct dbfs_op;
-
-/*
- * Called by dbfs_op_free to release any resources when the op is free'd (i.e. not open anymore).
- */
-typedef void (*dbfs_op_free_cb) (struct dbfs_op *op_base);
-
-/*
- * Called after the transaction has been opened, and before reply_open.
- *
- * You can do any at-open initialization here.
- */
-typedef void (*dbfs_op_open_cb) (struct dbfs_op *op_base);
-
-// the base op state
-struct dbfs_op {
- struct fuse_file_info fi;
- struct fuse_req *req;
-
- struct evsql_trans *trans;
-
- // op target inode
- uint32_t ino;
-
- // open has returned and release hasn't been called yet
- int open;
-
- // callbacks
- dbfs_op_free_cb free_fn;
- dbfs_op_open_cb open_fn;
-};
-
-/*
- * This will handle failures during requests.
- *
- * 1) if we have a trans, abort it
- * 2) fail the req (mandatory) with the given err
- *
- * If the op is open, then we don't release it, but if it's not open, then the op will be free'd completely.
- *
- */
-void dbfs_op_fail (struct dbfs_op *op, int err);
-
-/*
- * Open the op, that is, store all the initial state, and open a new transaction.
- *
- * The op must be pre-allocated and zero-initialized.
- *
- * This will always set op->req, so op is safe for dbfs_op_fail after this.
- *
- * This does not fail the dirop, handle error replies yourself.
- *
- * Returns zero on success, err on failure.
- */
-int dbfs_op_open (struct dbfs *ctx, struct dbfs_op *op, struct fuse_req *req, fuse_ino_t ino, struct fuse_file_info *fi, dbfs_op_free_cb free_fn, dbfs_op_open_cb ready_fn);
-
-/*
- * Should be called from open_fn to send the fuse_reply_open with fi and mark the op as open.
- *
- * If the op has failed earlier or fuse_reply_open fails, this will return nonzero. Fail the op yourself.
- */
-int dbfs_op_open_reply (struct dbfs_op *op);
-
-/*
- * Start handling a normal op requests.
- *
- * Lookup the op for the given fi, validate params, and assign the new req.
- *
- * In case the op failed previously, this will error the req and return NULL, indicating that the req has been handled.
- *
- * Repeat, if this returns NULL, consider req invalid.
- */
-struct dbfs_op *dbfs_op_req (struct fuse_req *req, fuse_ino_t ino, struct fuse_file_info *fi);
-
-/*
- * Done handling a request, adjust state accordingly.
- *
- * req *must* have been replied to.
- */
-int dbfs_op_req_done (struct dbfs_op *op);
-
-/*
- * Handle the op release.
- *
- * This will take care of committing the transaction, sending any reply/error, closing the op and freeing it.
- */
-void dbfs_op_release (struct fuse_req *req, fuse_ino_t, struct fuse_file_info *fi);
-
-
-#endif /* DBFS_OP_BASE_H */
--- a/src/dbfs/ops.h Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,40 +0,0 @@
-#ifndef DBFS_OPS_H
-#define DBFS_OPS_H
-
-#include "../evfuse.h"
-
-/* dbfs.c */
-void dbfs_init (void *userdata, struct fuse_conn_info *conn);
-void dbfs_destroy (void *arg);
-
-/* attr.c */
-void dbfs_getattr (struct fuse_req *req, fuse_ino_t ino, struct fuse_file_info *fi);
-void dbfs_setattr(struct fuse_req *req, fuse_ino_t ino, struct stat *attr, int to_set, struct fuse_file_info *fi);
-
-/* link.c */
-void dbfs_lookup (struct fuse_req *req, fuse_ino_t parent, const char *name);
-void dbfs_readlink (struct fuse_req *req, fuse_ino_t ino);
-void dbfs_unlink (struct fuse_req *req, fuse_ino_t parent, const char *name);
-void dbfs_link (struct fuse_req *req, fuse_ino_t ino, fuse_ino_t newparent, const char *newname);
-
-/* 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);
-void dbfs_releasedir (struct fuse_req *req, fuse_ino_t ino, struct fuse_file_info *fi);
-
-/* mk.c */
-void dbfs_mknod (struct fuse_req *req, fuse_ino_t parent, const char *name, mode_t mode, dev_t rdev);
-void dbfs_mkdir (struct fuse_req *req, fuse_ino_t parent, const char *name, mode_t mode);
-void dbfs_symlink (struct fuse_req *req, const char *link, fuse_ino_t parent, const char *name);
-
-/* tree.c */
-void dbfs_rename (struct fuse_req *req, fuse_ino_t parent, const char *name, fuse_ino_t newparent, const char *newname);
-
-/* 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);
-
-#endif /* DBFS_OPS_H */
--- a/src/dbfs/trans.c Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,138 +0,0 @@
-
-#include <stdlib.h>
-#include <assert.h>
-
-#include "trans.h"
-#include "../lib/log.h"
-
-void dbfs_trans_free (struct dbfs_trans *ctx) {
- assert(!ctx->req);
- assert(!ctx->trans);
-
- if (ctx->free_fn)
- ctx->free_fn(ctx);
-
- free(ctx);
-}
-
-void dbfs_trans_fail (struct dbfs_trans *ctx, int err) {
- if (ctx->req) {
- if ((err = fuse_reply_err(ctx->req, err)))
- EWARNING(err, "fuse_reply_err: request hangs");
-
- ctx->req = NULL;
- }
-
- if (ctx->trans) {
- evsql_trans_abort(ctx->trans);
-
- ctx->trans = NULL;
- }
-
- dbfs_trans_free(ctx);
-}
-
-static void dbfs_trans_error (struct evsql_trans *trans, void *arg) {
- struct dbfs_trans *ctx = arg;
-
- // deassociate trans
- ctx->trans = NULL;
-
- // log error
- INFO("\t[dbfs_trans.err %p:%p] %s", ctx, ctx->req, evsql_trans_error(trans));
-
- // mark
- if (ctx->err_ptr)
- *ctx->err_ptr = EIO;
-
- // fail
- dbfs_trans_fail(ctx, EIO);
-}
-
-static void dbfs_trans_ready (struct evsql_trans *trans, void *arg) {
- struct dbfs_trans *ctx = arg;
-
- // associate trans
- ctx->trans = trans;
-
- // log
- INFO("\t[dbfs_trans.ready %p:%p] -> trans=%p", ctx, ctx->req, trans);
-
- // trigger the callback
- ctx->begin_fn(ctx);
-}
-
-static void dbfs_trans_done (struct evsql_trans *trans, void *arg) {
- struct dbfs_trans *ctx = arg;
-
- // deassociate trans
- ctx->trans = NULL;
-
- // log
- INFO("\t[dbfs_trans.done %p:%p]", ctx, ctx->req);
-
- // trigger the callback
- ctx->commit_fn(ctx);
-}
-
-int dbfs_trans_init (struct dbfs_trans *ctx, struct fuse_req *req) {
- struct dbfs *dbfs_ctx = fuse_req_userdata(req);
- int err;
-
- // store
- ctx->req = req;
-
- // trans
- if ((ctx->trans = evsql_trans(dbfs_ctx->db, EVSQL_TRANS_SERIALIZABLE, dbfs_trans_error, dbfs_trans_ready, dbfs_trans_done, ctx)) == NULL)
- EERROR(err = EIO, "evsql_trans");
-
- // good
- return 0;
-
-error:
- return -1;
-}
-
-struct dbfs_trans *dbfs_trans_new (struct fuse_req *req) {
- struct dbfs_trans *ctx = NULL;
-
- // alloc
- if ((ctx = calloc(1, sizeof(*ctx))) == NULL)
- ERROR("calloc");
-
- // init
- if (dbfs_trans_init(ctx, req))
- goto error;
-
- // good
- return ctx;
-
-error:
- free(ctx);
-
- return NULL;
-}
-
-void dbfs_trans_commit (struct dbfs_trans *ctx) {
- int err, trans_err = 0;
-
- // detect errors
- ctx->err_ptr = &trans_err;
-
- // attempt commit
- if (evsql_trans_commit(ctx->trans))
- SERROR(err = EIO);
-
- // drop err_ptr
- ctx->err_ptr = NULL;
-
- // ok, wait for done or error
- return;
-
-error:
- // fail if not already failed
- if (!trans_err)
- dbfs_trans_fail(ctx, err);
-}
-
-
--- a/src/dbfs/trans.h Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,69 +0,0 @@
-#ifndef DBFS_TRANS_H
-#define DBFS_TRANS_H
-
-/*
- * Support for single-fuse_req transactions.
- */
-
-#include "dbfs.h"
-
-// forward-declaration
-struct dbfs_trans;
-
-// generic callback
-typedef void (*dbfs_trans_cb) (struct dbfs_trans *ctx);
-
-/*
- * Request/transaction state.
- */
-struct dbfs_trans {
- struct fuse_req *req;
- struct evsql_trans *trans;
-
- // called when the dbfs_trans is being free'd
- dbfs_trans_cb free_fn;
-
- // called once the transaction is ready
- dbfs_trans_cb begin_fn;
-
- // called once the transaction has been commited
- dbfs_trans_cb commit_fn;
-
- // set by trans_error to EIO if !NULL
- int *err_ptr;
-};
-
-/*
- * Call from commit_fn once you've set req to NULL. Also called from trans_fail.
- *
- * Will call free_fn if present.
- */
-void dbfs_trans_free (struct dbfs_trans *ctx);
-
-/*
- * Fail the dbfs_trans, aborting any trans, erroring out any req and freeing the ctx.
- */
-void dbfs_trans_fail (struct dbfs_trans *ctx, int err);
-
-/*
- * Initialize the ctx with the given req (stays the same during the lifetime), and opens the transaction.
- *
- * You should set the callback functions after/before calling this.
- *
- * begin_fn will be called once the transaction is open, if that fails, the req will be errored for you.
- *
- * If opening the transaction fails, this will return nonzero and not touch req, otherwise zero.
- */
-int dbfs_trans_init (struct dbfs_trans *ctx, struct fuse_req *req);
-
-/*
- * Same as init, but allocates/frees-on-error the dbfs_trans for you.
- */
-struct dbfs_trans *dbfs_trans_new (struct fuse_req *req);
-
-/*
- * Commit the dbfs_trans. After calling this, the ctx may or may not be valid, and commit_fn may or may not be called.
- */
-void dbfs_trans_commit (struct dbfs_trans *ctx);
-
-#endif /* DBFS_TRANS_H */
--- a/src/dbfs/tree.c Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,71 +0,0 @@
-
-#include "../lib/log.h"
-
-#include "dbfs.h"
-
-void dbfs_rename_res (struct evsql_result *res, void *arg) {
- struct fuse_req *req = arg;
- int err;
-
- // check the results
- if ((err = _dbfs_check_res(res, 0, 0)))
- SERROR(err = (err == 1 ? ENOENT : EIO));
-
- // just reply
- if ((err = -fuse_reply_err(req, 0)))
- EERROR(err, "fuse_reply_err");
-
- // log
- INFO("[dbfs.rename %p] -> OK", req);
-
- // fallthrough for result_free
- err = 0;
-
-error:
- if (err && (err = -fuse_reply_err(req, err)))
- EWARNING(err, "fuse_reply_err");
-
- evsql_result_free(res);
-}
-
-void dbfs_rename (struct fuse_req *req, fuse_ino_t parent, const char *name, fuse_ino_t newparent, const char *newname) {
- struct dbfs *dbfs_ctx = fuse_req_userdata(req);
- int err;
-
- INFO("[dbfs.rename %p] parent=%lu, name=%s, newparent=%lu, newname=%s", req, parent, name, newparent, newname);
-
- // just one UPDATE
- const char *sql =
- "UPDATE"
- " file_tree"
- " SET parent = $1::int4, name = $2::varchar"
- " WHERE parent = $3::int4 AND name = $4::varchar";
-
- static struct evsql_query_params params = EVSQL_PARAMS(EVSQL_FMT_BINARY) {
- EVSQL_PARAM ( UINT32 ),
- EVSQL_PARAM ( STRING ),
- EVSQL_PARAM ( UINT32 ),
- EVSQL_PARAM ( STRING ),
-
- EVSQL_PARAMS_END
- };
-
- if (0
- || evsql_param_uint32(¶ms, 0, newparent)
- || evsql_param_string(¶ms, 1, newname)
- || evsql_param_uint32(¶ms, 2, parent)
- || evsql_param_string(¶ms, 3, name)
- )
- SERROR(err = EIO);
-
- // query
- if (evsql_query_params(dbfs_ctx->db, NULL, sql, ¶ms, dbfs_rename_res, req) == NULL)
- SERROR(err = EIO);
-
- // good, wait
- return;
-
-error:
- if ((err = fuse_reply_err(req, err)))
- EWARNING(err, "fuse_reply_err");
-}
--- a/src/dirbuf.c Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,87 +0,0 @@
-
-#include <stdlib.h>
-
-#include "dirbuf.h"
-#include "lib/log.h"
-#include "lib/math.h"
-
-int dirbuf_init (struct dirbuf *buf, size_t req_size, off_t req_off) {
- buf->buf = NULL;
- buf->len = req_size;
- buf->off = 0;
- buf->req_off = req_off;
-
- DEBUG("\tdirbuf.init: req_size=%zu", req_size);
-
- // allocate the mem
- if ((buf->buf = malloc(buf->len)) == NULL)
- ERROR("malloc");
-
- // ok
- return 0;
-
-error:
- return -1;
-}
-
-size_t dirbuf_estimate (struct dirbuf *buf, size_t min_namelen) {
- char namebuf[DIRBUF_NAME_MAX];
- int i;
-
- // build a dummy string of the right length
- for (i = 0; i < min_namelen && i < DIRBUF_NAME_MAX - 1; i++)
- namebuf[i] = 'x';
-
- namebuf[i] = '\0';
-
- return buf->len / (fuse_add_direntry(NULL, NULL, 0, namebuf, NULL, 0));
-}
-
-int dirbuf_add (fuse_req_t req, struct dirbuf *buf, off_t ent_off, off_t next_off, const char *ent_name, fuse_ino_t ent_ino, mode_t ent_mode) {
- struct stat stbuf;
- size_t ent_size;
-
- DEBUG("\tdirbuf.add: req_off=%zu, buf->len=%zu, buf->off=%zu, ent_off=%zu, next_off=%zu, ent_name=`%s`, ent_ino=%lu, ent_mode=%07o",
- buf->req_off, buf->len, buf->off, ent_off, next_off, ent_name, ent_ino, ent_mode);
-
- // skip entries as needed
- if (ent_off < buf->req_off)
- return 0;
-
- // set ino
- stbuf.st_ino = ent_ino;
- stbuf.st_mode = ent_mode;
-
- // try and add the dirent, and see if it fits
- if ((ent_size = fuse_add_direntry(req, buf->buf + buf->off, buf->len - buf->off, ent_name, &stbuf, next_off)) > (buf->len - buf->off)) {
- // 'tis full
- return 1;
-
- } else {
- // it fit
- buf->off += ent_size;
- }
-
- // success
- return 0;
-}
-
-int dirbuf_done (fuse_req_t req, struct dirbuf *buf) {
- int err;
-
- // send the reply, return the error later
- err = -fuse_reply_buf(req, buf->buf, buf->off);
-
- DEBUG("\tdirbuf.done: size=%zu/%zu, err=%d", buf->off, buf->len, err);
-
- // free the dirbuf
- dirbuf_release(buf);
-
- // return the error code
- return err;
-}
-
-void dirbuf_release (struct dirbuf *buf) {
- free(buf->buf); buf->buf = NULL;
-}
-
--- a/src/dirbuf.h Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,60 +0,0 @@
-#ifndef DIRBUF_H
-#define DIRBUF_H
-
-/*
- * Simple dirent building
- */
-
-#include "evfuse.h"
-
-/*
- * Holds the dir entries
- */
-struct dirbuf {
- char *buf;
- size_t len;
- off_t off, req_off;
-};
-
-// maximum length for a dirbuf name, including NUL byte
-#define DIRBUF_NAME_MAX 256
-
-/*
- * Initialize a dirbuf for a request. The dirbuf will be filled with at most req_size bytes of dir entries.
- *
- * req_size - how many bytes of dirbuf data we want, at most
- * req_off - the offset of the first dirent to include
- */
-int dirbuf_init (struct dirbuf *buf, size_t req_size, off_t req_off);
-
-/*
- * Estimate how many dir entries will, at most, fit into a difbuf of the given size, based on a minimum filename size.
- */
-size_t dirbuf_estimate (struct dirbuf *buf, size_t min_namelen);
-
-/*
- * Add an dir entry to the dirbuf. The dirbuf should not be full.
- *
- * Offsets are followed:
- * ent_off - the offset of this dirent
- * next_off - the offset of the next dirent
- *
- * Only the S_IFMT bits of ent_mode are relevant.
- *
- * Returns 0 if the ent was added or skipped, -1 on error, 1 if the dirbuf is full (no more ents should be added).
- */
-int dirbuf_add (fuse_req_t req, struct dirbuf *buf, off_t ent_off, off_t next_off, const char *ent_name, fuse_ino_t ent_ino, mode_t ent_mode);
-
-/*
- * Attempt to send the readdir reply, free the buf, and return the error code from fuse_reply_buf
- */
-int dirbuf_done (fuse_req_t req, struct dirbuf *buf);
-
-/*
- * Release the dirop without sending any reply back.
- *
- * This is safe to be called multiple times.
- */
-void dirbuf_release (struct dirbuf *buf);
-
-#endif /* DIRBUF_H */
--- a/src/evfuse.c Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,153 +0,0 @@
-
-#include <errno.h>
-#include <string.h>
-#include <stdlib.h>
-
-#include "evfuse.h"
-#include "lib/log.h"
-
-struct evfuse {
- // our mountpoint
- char *mountpoint;
-
- // the /dev/fuse fd/channel that we get from fuse_mount
- struct fuse_chan *chan;
-
- // the session that we use to process the fuse stuff
- struct fuse_session *session;
-
- // the event that we use to receive requests
- struct event *ev;
-
- // what our receive-message length is
- size_t recv_size;
-
- // the buffer that we use to receive events
- char *recv_buf;
-};
-
-// prototypes
-void evfuse_close (struct evfuse *ctx);
-
-static void _evfuse_ev_read (evutil_socket_t fd, short what, void *arg) {
- struct evfuse *ctx = arg;
- struct fuse_chan *ch = ctx->chan;
- int res;
-
- // loop until we complete a recv
- do {
- // a new fuse_req is available
- res = fuse_chan_recv(&ch, ctx->recv_buf, ctx->recv_size);
- } while (res == -EINTR);
-
- if (res == 0)
- ERROR("fuse_chan_recv gave EOF");
-
- if (res < 0 && res != -EAGAIN)
- ERROR("fuse_chan_recv failed: %s", strerror(-res));
-
- if (res > 0) {
- DEBUG("got %d bytes from /dev/fuse", res);
-
- // received a fuse_req, so process it
- fuse_session_process(ctx->session, ctx->recv_buf, res, ch);
- }
-
- // reschedule
- if (event_add(ctx->ev, NULL))
- PERROR("event_add");
-
- // ok, wait for the next event
- return;
-
-error:
- // close, but don't free
- evfuse_close(ctx);
-}
-
-struct evfuse *evfuse_new (struct event_base *evbase, struct fuse_args *args, struct fuse_lowlevel_ops *llops, void *cb_data) {
- struct evfuse *ctx = NULL;
- int multithreaded, foreground;
-
- // allocate our context
- if ((ctx = calloc(1, sizeof(*ctx))) == NULL)
- ERROR("calloc");
-
- // parse the commandline for the mountpoint
- if (fuse_parse_cmdline(args, &ctx->mountpoint, &multithreaded, &foreground) == -1)
- ERROR("fuse_parse_cmdline");
-
- // mount it
- if ((ctx->chan = fuse_mount(ctx->mountpoint, args)) == NULL)
- PERROR("fuse_mount_common");
-
- // the receive buffer stufff
- ctx->recv_size = fuse_chan_bufsize(ctx->chan);
-
- // allocate the recv buffer
- if ((ctx->recv_buf = malloc(ctx->recv_size)) == NULL)
- ERROR("malloc");
-
- // allocate a low-level session
- if ((ctx->session = fuse_lowlevel_new(args, llops, sizeof(*llops), cb_data)) == NULL)
- PERROR("fuse_lowlevel_new");
-
- // add the channel to the session
- // this isn't strictly necessary, but let's do it anyways
- fuse_session_add_chan(ctx->session, ctx->chan);
-
- // now, we can start listening for events on the channel
- if ((ctx->ev = event_new(evbase, fuse_chan_fd(ctx->chan), EV_READ, &_evfuse_ev_read, ctx)) == NULL)
- ERROR("event_new");
-
- if (event_add(ctx->ev, NULL))
- PERROR("event_add");
-
- // and then we wait
- return ctx;
-
-error:
- evfuse_free(ctx);
-
- return NULL;
-}
-
-void evfuse_close (struct evfuse *ctx) {
- if (ctx->ev) {
- // remove our event
- if (event_del(ctx->ev))
- PWARNING("event_del");
-
- ctx->ev = NULL;
- }
-
- if (ctx->session) {
- // remove the chan
- fuse_session_remove_chan(ctx->chan);
-
- // destroy the session
- fuse_session_destroy(ctx->session);
-
- ctx->session = NULL;
- }
-
- if (ctx->chan) {
- // unmount
- fuse_unmount(ctx->mountpoint, ctx->chan);
-
- ctx->chan = NULL;
- }
-
- // free
- free(ctx->recv_buf); ctx->recv_buf = NULL;
- free(ctx->mountpoint); ctx->mountpoint = NULL;
-}
-
-void evfuse_free (struct evfuse *ctx) {
- if (ctx) {
- evfuse_close(ctx);
-
- free(ctx);
- }
-}
-
--- a/src/evfuse.h Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,27 +0,0 @@
-#ifndef EVFUSE_H
-#define EVFUSE_H
-
-#define FUSE_USE_VERSION 26
-
-#include <event2/event.h>
-#include <fuse/fuse_lowlevel.h>
-
-/*
- * A wrapper for the fuse + libevent context
- */
-struct evfuse;
-
-/*
- * Create a new new evfuse context.
- */
-struct evfuse *evfuse_new (struct event_base *evbase, struct fuse_args *args, struct fuse_lowlevel_ops *llops, void *cb_data);
-
-/*
- * Close and free evfuse context.
- *
- * Safe to call after errors/llops.destroy
- */
-void evfuse_free (struct evfuse *ctx);
-
-#endif /* EVFUSE_H */
-
--- a/src/evsql.h Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,813 +0,0 @@
-#ifndef EVSQL_H
-#define EVSQL_H
-
-/**
- * @file src/evsql.h
- *
- * A SQL library designed for use with libevent and PostgreSQL's libpq. Provides support for queueing non-transactional
- * requests, transaction support, parametrized queries and result iteration.
- *
- * Currently, the API does not expose the underlying libpq data structures, but since it is currently the only
- * underlying implementation, there is no guarantee that the same API will actually work with other databases' interface
- * libraries...
- *
- * The order of function calls and callbacks goes something like this:
- *
- * - evsql_new_pq()
- *
- * - evsql_trans()
- * - evsql_trans_abort()
- * - evsql_trans_error_cb()
- * - evsql_trans_ready_cb()
- *
- * - evsql_query(), \ref evsql_param_ + evsql_query_params(), evsql_query_exec()
- * - evsql_query_abort()
- * - evsql_query_cb()
- * - \ref evsql_result_
- * - evsql_result_free()
- *
- * - evsql_trans_commit()
- * - evsql_trans_done_cb()
- *
- */
-
-/**
- * System includes
- */
-#include <stdint.h>
-#include <stdbool.h>
-#include <event2/event.h>
-
-/**
- * For err_t.
- */
-#include "lib/err.h"
-
-/**
- * @struct evsql
- *
- * The generic session handle used to manage a single "database connector" with multiple queries/transactions.
- *
- * @see \ref evsql_
- */
-struct evsql;
-
-/**
- * @struct evsql_trans
- *
- * Opaque transaction handle returned by evsql_trans() and used for the \ref evsql_query_ functions
- *
- * @see \ref evsql_trans_
- */
-struct evsql_trans;
-
-/**
- * @struct evsql_query
- *
- * Opaque query handle returned by the \ref evsql_query_ functions and used for evsql_query_abort()
- *
- * @see \ref evsql_query_
- */
-struct evsql_query;
-
-/**
- * @struct evsql_result
- *
- * Opaque result handle received by evsql_query_cb(), and used with the \ref evsql_result_ functions
- *
- * @see evsql_query_cb
- * @see \ref evsql_result_
- */
-struct evsql_result;
-
-/**
- * Various transaction isolation levels for conveniance
- *
- * @see evsql_trans
- */
-enum evsql_trans_type {
- EVSQL_TRANS_DEFAULT,
- EVSQL_TRANS_SERIALIZABLE,
- EVSQL_TRANS_REPEATABLE_READ,
- EVSQL_TRANS_READ_COMMITTED,
- EVSQL_TRANS_READ_UNCOMMITTED,
-};
-
-/**
- * An item can be in different formats, the classical text-based format (i.e. snprintf "1234") or a more low-level
- * binary format (i.e uint16_t 0x04F9 in network-byte order).
- */
-enum evsql_item_format {
- /** Format values as text strings */
- EVSQL_FMT_TEXT,
-
- /** Type-specific binary encoding */
- EVSQL_FMT_BINARY,
-};
-
-/**
- * An item has a specific type, these correspond somewhat to the native database types.
- */
-enum evsql_item_type {
- /** End marker, zero */
- EVSQL_TYPE_INVALID,
-
- /** A SQL NULL */
- EVSQL_TYPE_NULL_,
-
- /** A `struct evsql_item_binary` */
- EVSQL_TYPE_BINARY,
-
- /** A NUL-terminated char* */
- EVSQL_TYPE_STRING,
-
- /** A uint16_t value */
- EVSQL_TYPE_UINT16,
-
- /** A uint32_t value */
- EVSQL_TYPE_UINT32,
-
- /** A uint64_t value */
- EVSQL_TYPE_UINT64,
-
- EVSQL_TYPE_MAX
-};
-
-/**
- * Value for use with EVSQL_TYPE_BINARY, this just a non-NUL-terminated char* and an explicit length
- */
-struct evsql_item_binary {
- /** The binary data */
- const char *ptr;
-
- /** Number of bytes pointed to by ptr */
- size_t len;
-};
-
-/**
- * Metadata about the format and type of an item, this does not hold any actual value.
- */
-struct evsql_item_info {
- /** The format */
- enum evsql_item_format format;
-
- /** The type type */
- enum evsql_item_type type;
-
- /** Various flags */
- struct evsql_item_flags {
- /** The value may be NULL @see evsql_result_next */
- bool null_ok : 1;
- } flags;
-};
-
-/**
- * An union to provide storage for the values of small types
- *
- * @see evsql_item
- */
-union evsql_item_value {
- /** 16-bit unsigned integer */
- uint16_t uint16;
-
- /** 32-bit unsigned integer */
- uint32_t uint32;
-
- /** 64-bit unsigned integer */
- uint64_t uint64;
-};
-
-/**
- * A generic structure containing the type and value of a query parameter or a result field.
- *
- * @see evsql_query_info
- * @see evsql_query_params
- * @see evsql_result_info
- */
-struct evsql_item {
- /** The "header" containing the type and format */
- struct evsql_item_info info;
-
- /**
- * Pointer to the raw databytes.
- * Set to NULL for SQL NULLs, otherwise &value or an external buf
- */
- const char *bytes;
-
- /**
- * Size of the byte array pointed to by bytes, zero for EVSQL_FMT_TEXT data.
- */
- size_t length;
-
- /**
- * Inline storage for small values
- */
- union evsql_item_value value;
-
- /** Internal flags */
- struct {
- /**
- * The item has a value stored in `value`
- */
- bool has_value : 1;
- } flags;
-};
-
-/**
- * Query meta-info, similar to a prepared statement.
- *
- * Contains the literal SQL query and the types of the parameters, but no more.
- *
- * @see evsql_query_exec
- */
-struct evsql_query_info {
- /** The SQL query itself */
- const char *sql;
-
- /**
- * A variable-length array of the item_info parameters, terminated by an EVSQL_TYPE_INVALID entry.
- */
- struct evsql_item_info params[];
-};
-
-/**
- * Contains the query parameter types and their actual values
- *
- * @see evsql_query_params
- */
-struct evsql_query_params {
- /** Requested result format for this query. XXX: move elsewhere */
- enum evsql_item_format result_format;
-
- /**
- * A variable-length array of the item parameter-values, terminated by an EVSQL_TYPE_INVALID entry.
- */
- struct evsql_item list[];
-};
-
-/**
- * Result layout metadata. This contains the stucture needed to decode result rows.
- *
- * @see evsql_result_begin
- */
-struct evsql_result_info {
- /** XXX: make up something useful to stick here */
- int _unused;
-
- /**
- * A variable-length array of the item_info column types.
- */
- struct evsql_item_info columns[];
-};
-
-/**
- * Magic macros for defining param/result info -lists
- *
- * @code
- * static struct evsql_query_params params = EVSQL_PARAMS(EVSQL_FMT_BINARY) {
- * EVSQL_PARAM( UINT32 ),
- * ...,
- *
- * EVSQL_PARAMS_END
- * };
- * @endcode
- *
- * @name EVSQL_TYPE/PARAM_*
- * @{
- */
-
-/**
- * A `struct evsql_item_info` initializer, using FMT_BINARY and the given EVSQL_TYPE_ -suffix.
- *
- * @param typenam the suffix of an evsql_item_type name
- *
- * @see struct evsql_item_info
- * @see enum evsql_item_type
- */
-#define EVSQL_TYPE(typenam) { EVSQL_FMT_BINARY, EVSQL_TYPE_ ## typenam }
-
-/**
- * End marker for a `struct evsql_item_info` array.
- *
- * @see struct evsql_item_info
- */
-#define EVSQL_TYPE_END { EVSQL_FMT_BINARY, EVSQL_TYPE_INVALID }
-
-/**
- * Initializer block for an evsql_query_params struct
- */
-#define EVSQL_PARAMS(result_fmt) { result_fmt,
-
-/**
- * An evsql_item initializer
- */
-#define EVSQL_PARAM(typenam) { EVSQL_TYPE(typenam) }
-
-/**
- * Include the ending item and terminate the pseudo-block started using #EVSQL_PARAMS
- */
-#define EVSQL_PARAMS_END { EVSQL_TYPE_END } \
- } // <<<
-
-// @}
-
-/**
- * Callback definitions
- *
- * @name evsql_*_cb
- * @{
- */
-
-/**
- * Callback for handling query results.
- *
- * The query has completed, either succesfully or unsuccesfully.
- *
- * Use the \ref evsql_result_ functions to manipulate the results, and call evsql_result_free() (or equivalent) once done.
- *
- * @param res The result handle that must be result_free'd after use
- * @param arg The void* passed to \ref evsql_query_
- *
- * @see evsql_query
- */
-typedef void (*evsql_query_cb)(struct evsql_result *res, void *arg);
-
-/**
- * Callback for handling global-level errors.
- *
- * The evsql is not useable anymore.
- *
- * XXX: this is not actually called yet, as no retry logic is implemented, so an evsql itself never fails.
- *
- * @see evsql_new_pq
- */
-typedef void (*evsql_error_cb)(struct evsql *evsql, void *arg);
-
-/**
- * Callback for handling transaction-level errors. This may be called at any time during a transaction's lifetime,
- * including from within the \ref evsql_query_ functions (but not always).
- *
- * The transaction is not useable anymore.
- *
- * @param trans the transaction in question
- * @param arg the void* passed to evsql_trans
- *
- * @see evsql_trans
- */
-typedef void (*evsql_trans_error_cb)(struct evsql_trans *trans, void *arg);
-
-/**
- * Callback for handling evsql_trans/evsql_query_abort completion. The transaction is ready for use with \ref evsql_query_.
- *
- * @param trans the transaction in question
- * @param arg the void* passed to evsql_trans
- *
- * @see evsql_trans
- * @see evsql_query_abort
- */
-typedef void (*evsql_trans_ready_cb)(struct evsql_trans *trans, void *arg);
-
-/**
- * Callback for handling evsql_trans_commit completion. The transaction was commited, and should not be used anymore.
- *
- * @param trans the transaction in question
- * @param arg the void* passed to evsql_trans
- *
- * @see evsql_trans
- * @see evsql_trans_commit
- */
-typedef void (*evsql_trans_done_cb)(struct evsql_trans *trans, void *arg);
-
-// @}
-
-/**
- * Session functions
- *
- * @defgroup evsql_* Session interface
- * @see evsql.h
- * @{
- */
-
-/**
- * Session creation functions
- *
- * @defgroup evsql_new_* Session creation interface
- * @see evsql.h
- * @{
- */
-
-/**
- * Create a new PostgreSQL/libpq (evpq) -based evsql using the given conninfo.
- *
- * The given \a pq_conninfo pointer must stay valid for the duration of the evsql's lifetime.
- *
- * See the libpq reference manual for the syntax of pq_conninfo
- *
- * @param ev_base the libevent base to use
- * @param pq_conninfo the libpq connection information
- * @param error_fn XXX: not used, may be NULL
- * @param cb_arg: XXX: not used, argument for error_fn
- * @return the evsql context handle for use with other functions
- */
-struct evsql *evsql_new_pq (struct event_base *ev_base, const char *pq_conninfo,
- evsql_error_cb error_fn,
- void *cb_arg
-);
-
-// @}
-
-/**
- * Close a connection. Callbacks for waiting queries will not be run.
- *
- * XXX: not implemented yet.
- *
- * @param evsql the context handle from \ref evsql_new_
- */
-void evsql_close (struct evsql *evsql);
-
-// @}
-
-/**
- * Query API
- *
- * @defgroup evsql_query_* Query interface
- * @see evsql.h
- * @{
- */
-
-/**
- * Queue the given query for execution.
- *
- * If \a trans is given (i.e. not NULL), then the transaction must be idle, and the query will be executed in that
- * transaction's context. Otherwise, the query will be executed without a transaction using an idle connection, or
- * enqueued for later execution.
- *
- * Once the query is complete (got a result, got an error, the connection failed), then \a query_fn will be called.
- * The callback can use the \ref evsql_result_ functions to manipulate the query results.
- *
- * The returned evsql_query handle can be passed to evsql_query_abort at any point before \a query_fn being called.
- *
- * @param evsql the context handle from \ref evsql_new_
- * @param trans the optional transaction handle from evsql_trans
- * @param command the raw SQL command itself
- * @param query_fn the evsql_query_cb() to call once the query is complete
- * @param cb_arg the void* passed to the above
- * @return the evsql_query handle that can be used to abort the query
- */
-struct evsql_query *evsql_query (struct evsql *evsql, struct evsql_trans *trans, const char *command, evsql_query_cb query_fn, void *cb_arg);
-
-/**
- * Execute the given SQL query using the list of parameter types/values given via evsql_query_params.
- *
- * Use the EVSQL_PARAMS macros to declare \a params, and the \ref evsql_param_ functions to populate the values.
- *
- * See evsql_query() for more info about behaviour.
- *
- * See the <a href="http://www.postgresql.org/docs/8.3/static/libpq-exec.html#LIBPQ-EXEC-MAIN">libpq PQexecParams tip</a>
- * for the parameter syntax to use.
- *
- * @param evsql the context handle from \ref evsql_new_
- * @param trans the optional transaction handle from evsql_trans
- * @param command the SQL command to bind the parameters to
- * @param params the parameter types and values
- * @param query_fn the evsql_query_cb() to call once the query is complete
- * @param cb_arg the void* passed to the above
- * @see evsql_query
- */
-struct evsql_query *evsql_query_params (struct evsql *evsql, struct evsql_trans *trans,
- const char *command, const struct evsql_query_params *params,
- evsql_query_cb query_fn, void *cb_arg
-);
-
-/**
- * Execute the given \a query_info's SQL query with the values given as variable arguments, using the \a query_info to
- * resolve the types.
- *
- * See evsql_query() for more info about behaviour.
- *
- * @param evsql the context handle from \ref evsql_new_
- * @param trans the optional transaction handle from evsql_trans
- * @param query_info the SQL query information
- * @param query_fn the evsql_query_cb() to call once the query is complete
- * @param cb_arg the void* passed to the above
- * @see evsql_query
- */
-struct evsql_query *evsql_query_exec (struct evsql *evsql, struct evsql_trans *trans,
- const struct evsql_query_info *query_info,
- evsql_query_cb query_fn, void *cb_arg,
- ...
-);
-
-/**
- * Abort a \a query returned by \ref evsql_query_ that has not yet completed (query_fn has not been called yet).
- *
- * The actual query itself may or may not be aborted (and hence may or may not be executed on the server), but \a query_fn
- * will not be called anymore, and the query will dispose of itself and any results returned.
- *
- * If the \a query is part of a transaction, then \a trans must be given, and the query must be the query that is currently
- * executing on that trans. The transaction's \a ready_fn will be called once the query has been aborted and the
- * transaction is now idle again.
- *
- * @param trans if the query is part of a transaction, then it MUST be given here
- * @param query the in-progress query to abort
- */
-void evsql_query_abort (struct evsql_trans *trans, struct evsql_query *query);
-
-/**
- * Print out a textual dump of the given \a sql query and \a params using DEBUG
- *
- * @param sql the SQL query command
- * @param params the list of parameter types and values
- */
-void evsql_query_debug (const char *sql, const struct evsql_query_params *params);
-
-// @}
-
-/**
- * Transaction API
- *
- * @defgroup evsql_trans_* Transaction interface
- * @see evsql.h
- * @{
- */
-
-/**
- * Create a new transaction.
- *
- * A transaction will be allocated its own connection, and the "BEGIN TRANSACTION ..." query will be sent (use the
- * \a type argument to specify this).
- *
- * Once the transaction has been opened, the given \a ready_fn will be triggered, and the transaction can then
- * be used (see \ref evsql_query_).
- *
- * If, at any point, the transaction-connection fails, any pending query will be forgotten (i.e. the query callback
- * will NOT be called), and the given \a error_fn will be called. Note that this includes some, but not all,
- * cases where \ref evsql_query_ returns an error.
- *
- * Once you are done with the transaction, call either evsql_trans_commit() or evsql_trans_abort().
- *
- * @param evsql the context handle from \ref evsql_new_
- * @param type the type of transaction to create
- * @param error_fn the evsql_trans_error_cb() to call if this transaction fails
- * @param ready_fn the evsql_trans_ready_cb() to call once this transaction is ready for use
- * @param done_fn the evsql_trans_done_cb() to call once this transaction has been commmited
- * @param cb_arg the void* to pass to the above
- * @return the evsql_trans handle for use with other functions
- */
-struct evsql_trans *evsql_trans (struct evsql *evsql, enum evsql_trans_type type,
- evsql_trans_error_cb error_fn,
- evsql_trans_ready_cb ready_fn,
- evsql_trans_done_cb done_fn,
- void *cb_arg
-);
-
-/**
- * Commit a transaction using "COMMIT TRANSACTION".
- *
- * The transaction must be idle, just like for evsql_query. Once the transaction has been commited, the transaction's
- * \a done_fn will be called, after which the transaction must not be used anymore.
- *
- * You cannot abort a COMMIT, calling trans_abort() on trans after a succesful trans_commit is an error.
- *
- * Note that \a done_fn will never be called directly, always indirectly via the event loop.
- *
- * @param trans the transaction handle from evsql_trans to commit
- * @see evsql_trans
- */
-int evsql_trans_commit (struct evsql_trans *trans);
-
-/**
- * Abort a transaction, using "ROLLBACK TRANSACTION".
- *
- * No more transaction callbacks will be called. If there was a query running, it will be aborted, and the transaction
- * then rollback'd.
- *
- * You cannot abort a COMMIT, calling trans_abort on \a trans after a call to trans_commit is an error.
- *
- * Do not call evsql_trans_abort from within evsql_trans_error_cb()!
- *
- * @param trans the transaction from evsql_trans to abort
- * @see evsql_trans
- */
-void evsql_trans_abort (struct evsql_trans *trans);
-
-/**
- * Retrieve the transaction-specific error message from the underlying engine.
- *
- * Intended to be called from evsql_trans_error_cb()
- */
-const char *evsql_trans_error (struct evsql_trans *trans);
-
-// @}
-
-/**
- * Parameter-building functions.
- *
- * These manipulate the value of the given parameter index.
- *
- * @defgroup evsql_param_* Parameter interface
- * @see evsql.h
- * @{
- */
-
-/**
- * Sets the value of the parameter at the given index
- *
- * @param params the evsql_query_params struct
- * @param param the parameter index
- * @param ptr pointer to the binary data
- * @param len size of the binary data in bytes
- * @return zero on success, <0 on error
- */
-int evsql_param_binary (struct evsql_query_params *params, size_t param, const char *ptr, size_t len);
-
-/** @see evsql_param_binary */
-int evsql_param_string (struct evsql_query_params *params, size_t param, const char *ptr);
-
-/** @see evsql_param_binary */
-int evsql_param_uint16 (struct evsql_query_params *params, size_t param, uint16_t uval);
-
-/** @see evsql_param_binary */
-int evsql_param_uint32 (struct evsql_query_params *params, size_t param, uint32_t uval);
-
-/**
- * Sets the given parameter to NULL
- *
- * @param params the evsql_query_params struct
- * @param param the parameter index
- * @return zero on success, <0 on error
- */
-int evsql_param_null (struct evsql_query_params *params, size_t param);
-
-/**
- * Clears all the parameter values (sets them to NULL)
- *
- * @param params the evsql_query_params struct
- * @return zero on success, <0 on error
- */
-int evsql_params_clear (struct evsql_query_params *params);
-
-// @}
-
-/**
- * Result-handling functions
- *
- * @defgroup evsql_result_* Result interface
- * @see evsql.h
- * @see evsql_result
- * @{
- */
-
-/**
- * Check the result for errors. Intended for use with non-data queries, i.e. CREATE, etc.
- *
- * Returns zero if the query was OK, err otherwise. EIO indicates an SQL error, the error message can be retrived
- * using evsql_result_error.
- *
- * @param res the result handle passed to evsql_query_cb()
- * @return zero on success, EIO on SQL error, positive error code otherwise
- */
-err_t evsql_result_check (struct evsql_result *res);
-
-/**
- * The iterator-based interface results interface.
- *
- * Define an evsql_result_info struct that describes the columns returned by the query, and call evsql_result_begin on
- * the evsql_result. This verifies the query result, and then prepares it for iteration using evsql_result_next.
- *
- * Call evsql_result_end once you've stopped iteration.
- *
- * Returns zero if the evsql_result is ready for iteration, err otherwise. EIO indicates an SQL error, the error
- * message can be retreived using evsql_result_error. The result must be released in both cases.
- *
- * Note: currently the iterator state is simply stored in evsql_result, so only one iterator at a time per evsql_result.
- *
- * @param info the metadata to use to handle the result row columns
- * @param res the result handle passed to evsql_query_cb()
- * @return zero on success, +err on error
- */
-err_t evsql_result_begin (struct evsql_result_info *info, struct evsql_result *res);
-
-/**
- * Reads the next result row from the result prepared using evsql_result_begin. Stores the field values into to given
- * pointer arguments based on the evsql_result_info given to evsql_result_begin.
- *
- * If a field is NULL, and the result_info's evsql_item_type has flags.null_ok set, the given pointer is left
- * untouched, otherwise, an error is returned.
- *
- * @param res the result handle previous prepared using evsql_result_begin
- * @param ... a set of pointers corresponding to the evsql_result_info specified using evsql_result_begin
- * @return >0 when a row was read, zero when there are no more rows left, and -err on error
- */
-int evsql_result_next (struct evsql_result *res, ...);
-
-/**
- * Ends the result iteration, releasing any associated resources and the result itself.
- *
- * The result should not be iterated or accessed anymore.
- *
- * Note: this does the same thing as evsql_result_free, and works regardless of evsql_result_begin returning
- * succesfully or not.
- *
- * @param res the result handle passed to evsql_query_cb()
- * @see evsql_result_free
- */
-void evsql_result_end (struct evsql_result *res);
-
-/**
- * Get the error message associated with the result, intended for use after evsql_result_check/begin return an error
- * code.
- *
- * @param res the result handle passed to evsql_query_cb()
- * @return a char* containing the NUL-terminated error string. Valid until evsql_result_free is called.
- */
-const char *evsql_result_error (const struct evsql_result *res);
-
-/**
- * Get the number of data rows returned by the query
- *
- * @param res the result handle passed to evsql_query_cb()
- * @return the number of rows, >= 0
- */
-size_t evsql_result_rows (const struct evsql_result *res);
-
-/**
- * Get the number of columns in the data results from the query
- *
- * @param res the result handle passed to evsql_query_cb()
- * @return the number of columns, presumeably zero if there were no results
- */
-size_t evsql_result_cols (const struct evsql_result *res);
-
-/**
- * Get the number of rows affected by an UPDATE/INSERT/etc query.
- *
- * @param res the result handle passed to evsql_query_cb()
- * @return the number of rows affected, >= 0
- */
-size_t evsql_result_affected (const struct evsql_result *res);
-
-/**
- * Fetch the raw binary value for the given field, returning it via ptr/size.
- *
- * The given row/col must be within bounds as returned by evsql_result_rows/cols.
- *
- * *ptr will point to *size bytes of read-only memory allocated internally.
- *
- * @param res the result handle passed to evsql_query_cb()
- * @param row the row index to access
- * @param col the column index to access
- * @param ptr where to store a pointer to the read-only field data, free'd upon evsql_result_free
- * @param size updated to the size of the field value pointed to by ptr
- * @param nullok when true and the field value is NULL, *ptr and *size are not modified, otherwise NULL means an error
- * @return zero on success, <0 on error
- */
-int evsql_result_binary (const struct evsql_result *res, size_t row, size_t col, const char **ptr, size_t *size, bool nullok);
-
-/**
- * Fetch the textual value of the given field, returning it via ptr.
- *
- * The given row/col must be within bounds as returned by evsql_result_rows/cols.
- *
- * *ptr will point to a NUL-terminated string allocated internally.
- *
- * @param res the result handle passed to evsql_query_cb()
- * @param row the row index to access
- * @param col the column index to access
- * @param ptr where to store a pointer to the read-only field data, free'd upon evsql_result_free
- * @param nullok when true and the field value is NULL, *ptr and *size are not modified, otherwise NULL means an error
- * @return zero on success, <0 on error
- */
-int evsql_result_string (const struct evsql_result *res, size_t row, size_t col, const char **ptr, int nullok);
-
-/**
- * Use evsql_result_binary to read a binary field value, and then convert it using ntoh[slq], storing the value in
- * *val.
- *
- * The given row/col must be within bounds as returned by evsql_result_rows/cols.
- *
- * @param res the result handle passed to evsql_query_cb()
- * @param row the row index to access
- * @param col the column index to access
- * @param uval where to store the decoded value
- * @param nullok when true and the field value is NULL, *ptr and *size are not modified, otherwise NULL means an error
- * @return zero on success, <0 on error
- */
-int evsql_result_uint16 (const struct evsql_result *res, size_t row, size_t col, uint16_t *uval, int nullok);
-
-/** @see evsql_result_uint16 */
-int evsql_result_uint32 (const struct evsql_result *res, size_t row, size_t col, uint32_t *uval, int nullok);
-
-/** @see evsql_result_uint16 */
-int evsql_result_uint64 (const struct evsql_result *res, size_t row, size_t col, uint64_t *uval, int nullok);
-
-/**
- * Every result handle passed to evsql_query_cb() MUST be released by the user, using this function.
- *
- * @param res the result handle passed to evsql_query_cb()
- */
-void evsql_result_free (struct evsql_result *res);
-
-// @}
-
-#endif /* EVSQL_H */
--- a/src/evsql/evsql.c Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,915 +0,0 @@
-#define _GNU_SOURCE
-#include <stdlib.h>
-#include <assert.h>
-#include <string.h>
-
-#include "evsql.h"
-#include "../lib/log.h"
-#include "../lib/error.h"
-#include "../lib/misc.h"
-
-/*
- * A couple function prototypes
- */
-static void _evsql_pump (struct evsql *evsql, struct evsql_conn *conn);
-
-/*
- * Actually execute the given query.
- *
- * The backend should be able to accept the query at this time.
- *
- * You should assume that if trying to execute a query fails, then the connection should also be considred as failed.
- */
-static int _evsql_query_exec (struct evsql_conn *conn, struct evsql_query *query, const char *command) {
- int err;
-
- DEBUG("evsql.%p: exec query=%p on trans=%p on conn=%p:", conn->evsql, query, conn->trans, conn);
-
- switch (conn->evsql->type) {
- case EVSQL_EVPQ:
- // got params?
- if (query->params.count) {
- err = evpq_query_params(conn->engine.evpq, command,
- query->params.count,
- query->params.types,
- query->params.values,
- query->params.lengths,
- query->params.formats,
- query->params.result_format
- );
-
- } else {
- // plain 'ole query
- err = evpq_query(conn->engine.evpq, command);
- }
-
- if (err) {
- if (PQstatus(evpq_pgconn(conn->engine.evpq)) != CONNECTION_OK)
- WARNING("conn failed");
- else
- WARNING("query failed, dropping conn as well");
- }
-
- break;
-
- default:
- FATAL("evsql->type");
- }
-
- if (!err)
- // assign the query
- conn->query = query;
-
- return err;
-}
-
-void _evsql_query_free (struct evsql_query *query) {
- if (!query)
- return;
-
- assert(query->command == NULL);
-
- // free params if present
- free(query->params.types);
- free(query->params.values);
- free(query->params.lengths);
- free(query->params.formats);
-
- // free the query itself
- free(query);
-}
-
-/*
- * Execute the callback if res is given, and free the query.
- *
- * The query has been aborted, it will simply be freed
- */
-static void _evsql_query_done (struct evsql_query *query, struct evsql_result *res) {
- if (res) {
- if (query->cb_fn)
- // call the callback
- query->cb_fn(res, query->cb_arg);
- else {
- WARNING("supressing cb_fn because query was aborted");
-
- // free the results
- evsql_result_free(res);
- }
- }
-
- // free
- _evsql_query_free(query);
-}
-
-/*
- * XXX:
- * /
-static void _evsql_destroy (struct evsql *evsql, const struct evsql_result *res) {
- struct evsql_query *query;
-
- // clear the queue
- while ((query = TAILQ_FIRST(&evsql->query_queue)) != NULL) {
- _evsql_query_done(query, res);
-
- TAILQ_REMOVE(&evsql->query_queue, query, entry);
- }
-
- // free
- free(evsql);
-}
-*/
-
-/*
- * Free the transaction, it should already be deassociated from the query and conn.
- */
-static void _evsql_trans_free (struct evsql_trans *trans) {
- // ensure we don't leak anything
- assert(trans->query == NULL);
- assert(trans->conn == NULL);
-
- // free
- free(trans);
-}
-
-/*
- * Release a connection. It should already be deassociated from the trans and query.
- *
- * Releases the engine, removes from the conn_list and frees this.
- */
-static void _evsql_conn_release (struct evsql_conn *conn) {
- // ensure we don't leak anything
- assert(conn->trans == NULL);
- assert(conn->query == NULL);
-
- // release the engine
- switch (conn->evsql->type) {
- case EVSQL_EVPQ:
- evpq_release(conn->engine.evpq);
- break;
-
- default:
- FATAL("evsql->type");
- }
-
- // remove from list
- LIST_REMOVE(conn, entry);
-
- // catch deadlocks
- assert(!LIST_EMPTY(&conn->evsql->conn_list) || TAILQ_EMPTY(&conn->evsql->query_queue));
-
- // free
- free(conn);
-}
-
-/*
- * Release a transaction, it should already be deassociated from the query.
- *
- * Perform a two-way-deassociation with the conn, and then free the trans.
- */
-static void _evsql_trans_release (struct evsql_trans *trans) {
- assert(trans->query == NULL);
- assert(trans->conn != NULL);
-
- // deassociate the conn
- trans->conn->trans = NULL; trans->conn = NULL;
-
- // free the trans
- _evsql_trans_free(trans);
-}
-
-/*
- * Fail a single query, this will trigger the callback and free it.
- *
- * NOTE: Only for *TRANSACTIONLESS* queries.
- */
-static void _evsql_query_fail (struct evsql* evsql, struct evsql_query *query) {
- struct evsql_result res; ZINIT(res);
-
- // set up the result_info
- res.evsql = evsql;
- res.error = 1;
-
- // finish off the query
- _evsql_query_done(query, &res);
-}
-
-/*
- * Fail a transaction, this will silently drop any query, trigger the error callback, two-way-deassociate/release the
- * conn, and then free the trans.
- */
-static void _evsql_trans_fail (struct evsql_trans *trans) {
- if (trans->query) {
- // free the query silently
- _evsql_query_free(trans->query); trans->query = NULL;
-
- // also deassociate it from the conn!
- trans->conn->query = NULL;
- }
-
- // tell the user
- // XXX: trans is in a bad state during this call
- if (trans->error_fn)
- trans->error_fn(trans, trans->cb_arg);
- else
- WARNING("supressing error because error_fn was NULL");
-
- // deassociate and release the conn
- trans->conn->trans = NULL; _evsql_conn_release(trans->conn); trans->conn = NULL;
-
- // pump the queue for requests that were waiting for this connection
- _evsql_pump(trans->evsql, NULL);
-
- // free the trans
- _evsql_trans_free(trans);
-}
-
-/*
- * Fail a connection. If the connection is transactional, this will just call _evsql_trans_fail, but otherwise it will
- * fail any ongoing query, and then release the connection.
- */
-static void _evsql_conn_fail (struct evsql_conn *conn) {
- if (conn->trans) {
- // let transactions handle their connection failures
- _evsql_trans_fail(conn->trans);
-
- } else {
- if (conn->query) {
- // fail the in-progress query
- _evsql_query_fail(conn->evsql, conn->query); conn->query = NULL;
- }
-
- // finish off the whole connection
- _evsql_conn_release(conn);
- }
-}
-
-/*
- * Processes enqueued non-transactional queries until the queue is empty, or we managed to exec a query.
- *
- * If execing a query on a connection fails, both the query and the connection are failed (in that order).
- *
- * Any further queries will then also be failed, because there's no reconnection/retry logic yet.
- *
- * This means that if conn is NULL, all queries are failed.
- */
-static void _evsql_pump (struct evsql *evsql, struct evsql_conn *conn) {
- struct evsql_query *query;
- int err;
-
- // look for waiting queries
- while ((query = TAILQ_FIRST(&evsql->query_queue)) != NULL) {
- // zero err
- err = 0;
-
- // dequeue
- TAILQ_REMOVE(&evsql->query_queue, query, entry);
-
- if (conn) {
- // try and execute it
- err = _evsql_query_exec(conn, query, query->command);
- }
-
- // free the command buf
- free(query->command); query->command = NULL;
-
- if (err || !conn) {
- if (!conn) {
- // warn when dropping queries
- WARNING("failing query becuse there are no conns");
- }
-
- // fail the query
- _evsql_query_fail(evsql, query);
-
- if (conn) {
- // fail the connection
- WARNING("failing the connection because a query-exec failed");
-
- _evsql_conn_fail(conn); conn = NULL;
- }
-
- } else {
- // we have succesfully enqueued a query, and we can wait for this connection to complete
- break;
-
- }
-
- // handle the rest of the queue
- }
-
- // ok
- return;
-}
-
-/*
- * Callback for a trans's 'BEGIN' query, which means the transaction is now ready for use.
- */
-static void _evsql_trans_ready (struct evsql_result *res, void *arg) {
- struct evsql_trans *trans = arg;
-
- assert(trans != NULL);
-
- // check for errors
- if (res->error)
- ERROR("transaction 'BEGIN' failed: %s", evsql_result_error(res));
-
- // transaction is now ready for use
- trans->ready_fn(trans, trans->cb_arg);
-
- // good
- return;
-
-error:
- _evsql_trans_fail(trans);
-}
-
-/*
- * The transaction's connection is ready, send the 'BEGIN' query.
- *
- * If anything fails, calls _evsql_trans_fail and returns nonzero, zero on success
- */
-static int _evsql_trans_conn_ready (struct evsql *evsql, struct evsql_trans *trans) {
- char trans_sql[EVSQL_QUERY_BEGIN_BUF];
- const char *isolation_level;
- int ret;
-
- // determine the isolation_level to use
- switch (trans->type) {
- case EVSQL_TRANS_DEFAULT:
- isolation_level = NULL; break;
-
- case EVSQL_TRANS_SERIALIZABLE:
- isolation_level = "SERIALIZABLE"; break;
-
- case EVSQL_TRANS_REPEATABLE_READ:
- isolation_level = "REPEATABLE READ"; break;
-
- case EVSQL_TRANS_READ_COMMITTED:
- isolation_level = "READ COMMITTED"; break;
-
- case EVSQL_TRANS_READ_UNCOMMITTED:
- isolation_level = "READ UNCOMMITTED"; break;
-
- default:
- FATAL("trans->type: %d", trans->type);
- }
-
- // build the trans_sql
- if (isolation_level)
- ret = snprintf(trans_sql, EVSQL_QUERY_BEGIN_BUF, "BEGIN TRANSACTION ISOLATION LEVEL %s", isolation_level);
- else
- ret = snprintf(trans_sql, EVSQL_QUERY_BEGIN_BUF, "BEGIN TRANSACTION");
-
- // make sure it wasn't truncated
- if (ret >= EVSQL_QUERY_BEGIN_BUF)
- ERROR("trans_sql overflow: %d >= %d", ret, EVSQL_QUERY_BEGIN_BUF);
-
- // execute the query
- if (evsql_query(evsql, trans, trans_sql, _evsql_trans_ready, trans) == NULL)
- ERROR("evsql_query");
-
- // success
- return 0;
-
-error:
- // fail the transaction
- _evsql_trans_fail(trans);
-
- return -1;
-}
-
-/*
- * The evpq connection was succesfully established.
- */
-static void _evsql_evpq_connected (struct evpq_conn *_conn, void *arg) {
- struct evsql_conn *conn = arg;
-
- if (conn->trans)
- // notify the transaction
- // don't care about errors
- (void) _evsql_trans_conn_ready(conn->evsql, conn->trans);
-
- else
- // pump any waiting transactionless queries
- _evsql_pump(conn->evsql, conn);
-}
-
-/*
- * Got one result on this evpq connection.
- */
-static void _evsql_evpq_result (struct evpq_conn *_conn, PGresult *result, void *arg) {
- struct evsql_conn *conn = arg;
- struct evsql_query *query = conn->query;
-
- assert(query != NULL);
-
- // if we get multiple results, only return the first one
- if (query->result.pq) {
- WARNING("[evsql] evpq query returned multiple results, discarding previous one");
-
- PQclear(query->result.pq); query->result.pq = NULL;
- }
-
- // remember the result
- query->result.pq = result;
-}
-
-/*
- * No more results for this query.
- */
-static void _evsql_evpq_done (struct evpq_conn *_conn, void *arg) {
- struct evsql_conn *conn = arg;
- struct evsql_query *query = conn->query;
- struct evsql_result res; ZINIT(res);
-
- assert(query != NULL);
-
- // set up the result_info
- res.evsql = conn->evsql;
- res.result = query->result;
-
- if (query->result.pq == NULL) {
- // if a query didn't return any results (bug?), warn and fail the query
- WARNING("[evsql] evpq query didn't return any results");
-
- res.error = 1;
-
- } else if (strcmp(PQresultErrorMessage(query->result.pq), "") != 0) {
- // the query failed with some error
- res.error = 1;
-
- } else {
- // the query succeeded \o/
- res.error = 0;
-
- }
-
- // de-associate the query from the connection
- conn->query = NULL;
-
- // how we handle query completion depends on if we're a transaction or not
- if (conn->trans) {
- // we can deassign the trans's query
- conn->trans->query = NULL;
-
- // was an abort?
- if (!query->cb_fn)
- // notify the user that the transaction query has been aborted
- conn->trans->ready_fn(conn->trans, conn->trans->cb_arg);
-
- // then hand the query to the user
- _evsql_query_done(query, &res);
-
- } else {
- // a transactionless query, so just finish it off and pump any other waiting ones
- _evsql_query_done(query, &res);
-
- // pump the next one
- _evsql_pump(conn->evsql, conn);
- }
-}
-
-/*
- * The connection failed.
- */
-static void _evsql_evpq_failure (struct evpq_conn *_conn, void *arg) {
- struct evsql_conn *conn = arg;
-
- // just fail the conn
- _evsql_conn_fail(conn);
-}
-
-/*
- * Our evpq behaviour
- */
-static struct evpq_callback_info _evsql_evpq_cb_info = {
- .fn_connected = _evsql_evpq_connected,
- .fn_result = _evsql_evpq_result,
- .fn_done = _evsql_evpq_done,
- .fn_failure = _evsql_evpq_failure,
-};
-
-/*
- * Allocate the generic evsql context.
- */
-static struct evsql *_evsql_new_base (struct event_base *ev_base, evsql_error_cb error_fn, void *cb_arg) {
- struct evsql *evsql = NULL;
-
- // allocate it
- if ((evsql = calloc(1, sizeof(*evsql))) == NULL)
- ERROR("calloc");
-
- // store
- evsql->ev_base = ev_base;
- evsql->error_fn = error_fn;
- evsql->cb_arg = cb_arg;
-
- // init
- LIST_INIT(&evsql->conn_list);
- TAILQ_INIT(&evsql->query_queue);
-
- // done
- return evsql;
-
-error:
- return NULL;
-}
-
-/*
- * Start a new connection and add it to the list, it won't be ready until _evsql_evpq_connected is called
- */
-static struct evsql_conn *_evsql_conn_new (struct evsql *evsql) {
- struct evsql_conn *conn = NULL;
-
- // allocate
- if ((conn = calloc(1, sizeof(*conn))) == NULL)
- ERROR("calloc");
-
- // init
- conn->evsql = evsql;
-
- // connect the engine
- switch (evsql->type) {
- case EVSQL_EVPQ:
- if ((conn->engine.evpq = evpq_connect(evsql->ev_base, evsql->engine_conf.evpq, _evsql_evpq_cb_info, conn)) == NULL)
- goto error;
-
- break;
-
- default:
- FATAL("evsql->type");
- }
-
- // add it to the list
- LIST_INSERT_HEAD(&evsql->conn_list, conn, entry);
-
- // success
- return conn;
-
-error:
- free(conn);
-
- return NULL;
-}
-
-struct evsql *evsql_new_pq (struct event_base *ev_base, const char *pq_conninfo, evsql_error_cb error_fn, void *cb_arg) {
- struct evsql *evsql = NULL;
-
- // base init
- if ((evsql = _evsql_new_base (ev_base, error_fn, cb_arg)) == NULL)
- goto error;
-
- // store conf
- evsql->engine_conf.evpq = pq_conninfo;
-
- // pre-create one connection
- if (_evsql_conn_new(evsql) == NULL)
- goto error;
-
- // done
- return evsql;
-
-error:
- // XXX: more complicated than this?
- free(evsql);
-
- return NULL;
-}
-
-/*
- * Checks if the connection is already allocated for some other trans/query.
- *
- * Returns:
- * 0 connection idle, can be allocated
- * >1 connection busy
- */
-static int _evsql_conn_busy (struct evsql_conn *conn) {
- // transactions get the connection to themselves
- if (conn->trans)
- return 1;
-
- // if it has a query assigned, it's busy
- if (conn->query)
- return 1;
-
- // otherwise, it's all idle
- return 0;
-}
-
-/*
- * Checks if the connection is ready for use (i.e. _evsql_evpq_connected was called).
- *
- * The connection should not already have a query running.
- *
- * Returns
- * <0 the connection is not valid (failed, query in progress)
- * 0 the connection is still pending, and will become ready at some point
- * >0 it's ready
- */
-static int _evsql_conn_ready (struct evsql_conn *conn) {
- switch (conn->evsql->type) {
- case EVSQL_EVPQ: {
- enum evpq_state state = evpq_state(conn->engine.evpq);
-
- switch (state) {
- case EVPQ_CONNECT:
- return 0;
-
- case EVPQ_CONNECTED:
- return 1;
-
- case EVPQ_QUERY:
- case EVPQ_INIT:
- case EVPQ_FAILURE:
- return -1;
-
- default:
- FATAL("evpq_state: %d", state);
- }
-
- }
-
- default:
- FATAL("evsql->type: %d", conn->evsql->type);
- }
-}
-
-/*
- * Allocate a connection for use and return it via *conn_ptr, or if may_queue is nonzero and the connection pool is
- * getting full, return NULL (query should be queued).
- *
- * Note that the returned connection might not be ready for use yet (if we created a new one, see _evsql_conn_ready).
- *
- * Returns zero if a connection was found or the request should be queued, or nonzero if something failed and the
- * request should be dropped.
- */
-static int _evsql_conn_get (struct evsql *evsql, struct evsql_conn **conn_ptr, int may_queue) {
- int have_nontrans = 0;
- *conn_ptr = NULL;
-
- // find a connection that isn't busy and is ready (unless the query queue is empty).
- LIST_FOREACH(*conn_ptr, &evsql->conn_list, entry) {
- // we can only have a query enqueue itself if there is a non-trans conn it can later use
- if (!(*conn_ptr)->trans)
- have_nontrans = 1;
-
- // skip busy conns always
- if (_evsql_conn_busy(*conn_ptr))
- continue;
-
- // accept pending conns as long as there are NO enqueued queries (might cause deadlock otherwise)
- if (_evsql_conn_ready(*conn_ptr) == 0 && TAILQ_EMPTY(&evsql->query_queue))
- break;
-
- // accept conns that are in a fully ready state
- if (_evsql_conn_ready(*conn_ptr) > 0)
- break;
- }
-
- // if we found an idle connection, we can just return that right away
- if (*conn_ptr)
- return 0;
-
- // return NULL if may_queue and we have a non-trans conn that we can, at some point, use
- if (may_queue && have_nontrans)
- return 0;
-
- // we need to open a new connection
- if ((*conn_ptr = _evsql_conn_new(evsql)) == NULL)
- goto error;
-
- // good
- return 0;
-error:
- return -1;
-}
-
-struct evsql_trans *evsql_trans (struct evsql *evsql, enum evsql_trans_type type, evsql_trans_error_cb error_fn, evsql_trans_ready_cb ready_fn, evsql_trans_done_cb done_fn, void *cb_arg) {
- struct evsql_trans *trans = NULL;
-
- // allocate it
- if ((trans = calloc(1, sizeof(*trans))) == NULL)
- ERROR("calloc");
-
- // store
- trans->evsql = evsql;
- trans->ready_fn = ready_fn;
- trans->done_fn = done_fn;
- trans->cb_arg = cb_arg;
- trans->type = type;
-
- // find a connection
- if (_evsql_conn_get(evsql, &trans->conn, 0))
- ERROR("_evsql_conn_get");
-
- // associate the conn
- trans->conn->trans = trans;
-
- // is it already ready?
- if (_evsql_conn_ready(trans->conn) > 0) {
- // call _evsql_trans_conn_ready directly, it will handle cleanup (silently, !error_fn)
- if (_evsql_trans_conn_ready(evsql, trans)) {
- // return NULL directly
- return NULL;
- }
-
- } else {
- // otherwise, wait for the conn to be ready
-
- }
-
- // and let it pass errors to the user
- trans->error_fn = error_fn;
-
- // ok
- return trans;
-
-error:
- free(trans);
-
- return NULL;
-}
-
-/*
- * Internal query functions
- */
-struct evsql_query *_evsql_query_new (struct evsql *evsql, struct evsql_trans *trans, evsql_query_cb query_fn, void *cb_arg) {
- struct evsql_query *query = NULL;
-
- // if it's part of a trans, then make sure the trans is idle
- if (trans && trans->query)
- ERROR("transaction is busy");
-
- // allocate it
- if ((query = calloc(1, sizeof(*query))) == NULL)
- ERROR("calloc");
-
- // store
- query->cb_fn = query_fn;
- query->cb_arg = cb_arg;
-
- // success
- return query;
-
-error:
- return NULL;
-}
-
-int _evsql_query_enqueue (struct evsql *evsql, struct evsql_trans *trans, struct evsql_query *query, const char *command) {
- // transaction queries are handled differently
- if (trans) {
- // it's an in-transaction query
- assert(trans->query == NULL);
-
- // assign the query
- trans->query = query;
-
- // execute directly
- if (_evsql_query_exec(trans->conn, query, command)) {
- // ack, fail the transaction
- _evsql_trans_fail(trans);
-
- // caller frees query
- goto error;
- }
-
- } else {
- struct evsql_conn *conn;
-
- // find an idle connection
- if ((_evsql_conn_get(evsql, &conn, 1)))
- ERROR("couldn't allocate a connection for the query");
-
- // we must enqueue if no idle conn or the conn is not yet ready
- if (conn && _evsql_conn_ready(conn) > 0) {
- // execute directly
- if (_evsql_query_exec(conn, query, command)) {
- // ack, fail the connection
- _evsql_conn_fail(conn);
-
- // make sure we don't deadlock any queries, but if this query got a conn directly, then we shouldn't
- // have any queries enqueued anyways
- assert(TAILQ_EMPTY(&evsql->query_queue));
-
- // caller frees query
- goto error;
- }
-
- } else {
- // copy the command for later execution
- if ((query->command = strdup(command)) == NULL)
- ERROR("strdup");
-
- // enqueue until some connection pumps the queue
- TAILQ_INSERT_TAIL(&evsql->query_queue, query, entry);
- }
- }
-
- // ok, good
- return 0;
-
-error:
- return -1;
-}
-
-
-void _evsql_trans_commit_res (struct evsql_result *res, void *arg) {
- struct evsql_trans *trans = arg;
-
- // check for errors
- if (res->error)
- ERROR("transaction 'COMMIT' failed: %s", evsql_result_error(res));
-
- // transaction is now done
- trans->done_fn(trans, trans->cb_arg);
-
- // release it
- _evsql_trans_release(trans);
-
- // success
- return;
-
-error:
- _evsql_trans_fail(trans);
-}
-
-int evsql_trans_commit (struct evsql_trans *trans) {
- static const char *sql = "COMMIT TRANSACTION";
-
- if (trans->query)
- ERROR("cannot COMMIT because transaction is still busy");
-
- // query
- if (evsql_query(trans->evsql, trans, sql, _evsql_trans_commit_res, trans) == NULL)
- goto error;
-
- // mark it as commited in case someone wants to abort it
- trans->has_commit = 1;
-
- // success
- return 0;
-
-error:
- return -1;
-}
-
-void _evsql_trans_rollback_res (struct evsql_result *res, void *arg) {
- struct evsql_trans *trans = arg;
-
- // fail the connection on errors
- if (res->error)
- ERROR("transaction 'ROLLBACK' failed: %s", evsql_result_error(res));
-
- // release it
- _evsql_trans_release(trans);
-
- // success
- return;
-
-error:
- // fail the connection too, errors are supressed
- _evsql_trans_fail(trans);
-}
-
-/*
- * Used as the ready_fn callback in case of abort, otherwise directly
- */
-void _evsql_trans_rollback (struct evsql_trans *trans, void *arg) {
- static const char *sql = "ROLLBACK TRANSACTION";
-
- (void) arg;
-
- // query
- if (evsql_query(trans->evsql, trans, sql, _evsql_trans_rollback_res, trans) == NULL) {
- // fail the transaction/connection, errors are supressed
- _evsql_trans_fail(trans);
- }
-
-}
-
-void evsql_trans_abort (struct evsql_trans *trans) {
- // supress errors
- trans->error_fn = NULL;
-
- if (trans->has_commit) {
- // abort after commit doesn't make sense
- FATAL("transaction was already commited");
- }
-
- if (trans->query) {
- // gah, some query is running
- WARNING("aborting pending query");
-
- // prepare to rollback once complete by hijacking ready_fn
- trans->ready_fn = _evsql_trans_rollback;
-
- // abort
- evsql_query_abort(trans, trans->query);
-
- } else {
- // just rollback directly
- _evsql_trans_rollback(trans, NULL);
-
- }
-}
-
--- a/src/evsql/evsql.h Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,192 +0,0 @@
-#ifndef EVSQL_INTERNAL_H
-#define EVSQL_INTERNAL_H
-
-/*
- * Internal interfaces
- */
-
-#include <sys/queue.h>
-
-#include <event2/event.h>
-
-#include "../evsql.h"
-#include "../evpq.h"
-
-/*
- * The engine type
- */
-enum evsql_type {
- EVSQL_EVPQ, // evpq
-};
-
-/*
- * Contains the type, engine configuration, list of connections and waiting query queue.
- */
-struct evsql {
- // what event_base to use
- struct event_base *ev_base;
-
- // what engine we use
- enum evsql_type type;
-
- // callbacks
- evsql_error_cb error_fn;
- void *cb_arg;
-
- // engine-specific connection configuration
- union {
- const char *evpq;
- } engine_conf;
-
- // list of connections that are open
- LIST_HEAD(evsql_conn_list, evsql_conn) conn_list;
-
- // list of queries running or waiting to run
- TAILQ_HEAD(evsql_query_queue, evsql_query) query_queue;
-};
-
-/*
- * A single connection to the server.
- *
- * Contains the engine connection, may have a transaction associated, and may have a query associated.
- */
-struct evsql_conn {
- // evsql we belong to
- struct evsql *evsql;
-
- // engine-specific connection info
- union {
- struct evpq_conn *evpq;
- } engine;
-
- // our position in the conn list
- LIST_ENTRY(evsql_conn) entry;
-
- // are we running a transaction?
- struct evsql_trans *trans;
-
- // are we running a transactionless query?
- struct evsql_query *query;
-};
-
-/*
- * A single transaction.
- *
- * Has a connection associated and possibly a query (which will also be associated with the connection)
- */
-struct evsql_trans {
- // our evsql_conn/evsql
- struct evsql *evsql;
- struct evsql_conn *conn;
-
- // callbacks
- evsql_trans_error_cb error_fn;
- evsql_trans_ready_cb ready_fn;
- evsql_trans_done_cb done_fn;
- void *cb_arg;
-
- // the transaction type
- enum evsql_trans_type type;
-
- // has evsql_trans_commit be called?
- int has_commit : 1;
-
- // our current query
- struct evsql_query *query;
-
-};
-
-/*
- * Backend result handle
- */
-union evsql_result_handle {
- PGresult *pq;
-};
-
-/*
- * A single query.
- *
- * Has the info needed to exec the query (as these may be queued), and the callback/result info.
- */
-struct evsql_query {
- // the actual SQL query, this may or may not be ours, see _evsql_query_exec
- char *command;
-
- // possible query params
- struct evsql_query_params_pq {
- int count;
-
- Oid *types;
- const char **values;
- int *lengths;
- int *formats;
-
- // storage for numeric values
- union evsql_item_value *item_vals;
-
- int result_format;
- } params;
-
- // our callback
- evsql_query_cb cb_fn;
- void *cb_arg;
-
- // the result we get
- union evsql_result_handle result;
-
- // our position in the query list
- TAILQ_ENTRY(evsql_query) entry;
-};
-
-// the result
-struct evsql_result {
- struct evsql *evsql;
-
- // possible error code
- int error;
-
- // the actual result
- union evsql_result_handle result;
-
- // result_* state
- struct evsql_result_info *info;
- size_t row_offset;
-};
-
-
-// maximum length for a 'BEGIN TRANSACTION ...' query
-#define EVSQL_QUERY_BEGIN_BUF 512
-
-// the should the OID of some valid psql type... *ANY* valid psql type, doesn't matter, only used for NULLs
-// 16 = bool in 8.3
-#define EVSQL_PQ_ARBITRARY_TYPE_OID 16
-
-/*
- * Core query-submission interface.
- *
- * This performs some error-checking on the trans, allocates the evsql_query and does some basic initialization.
- *
- * This does not actually enqueue the query anywhere, no reference is stored anywhere.
- *
- * Returns the new evsql_query on success, NULL on failure.
- */
-struct evsql_query *_evsql_query_new (struct evsql *evsql, struct evsql_trans *trans, evsql_query_cb query_fn, void *cb_arg);
-
-/*
- * Begin processing the given query, which should now be fully filled out.
- *
- * If trans is given, it MUST be idle, and the query will be executed. Otherwise, it will either be executed directly
- * or enqueued for future execution.
- *
- * Returns zero on success, nonzero on failure.
- */
-int _evsql_query_enqueue (struct evsql *evsql, struct evsql_trans *trans, struct evsql_query *query, const char *command);
-
-/*
- * Free the query and related resources, doesn't trigger any callbacks or remove from any queues.
- *
- * The command should already be taken care of (NULL).
- */
-void _evsql_query_free (struct evsql_query *query);
-
-#endif /* EVSQL_INTERNAL_H */
--- a/src/evsql/query.c Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,256 +0,0 @@
-
-#include "evsql.h"
-#include "../lib/error.h"
-#include "../lib/misc.h"
-
-#include <stdlib.h>
-#include <assert.h>
-
-/*
- * Initialize params->types/values/lengths/formats, params->count, params->result_format based on the given args
- */
-static int _evsql_query_params_init_pq (struct evsql_query_params_pq *params, size_t param_count, enum evsql_item_format result_format) {
- // set count
- params->count = param_count;
-
- // allocate vertical storage for the parameters
- if (0
-
- || !(params->types = calloc(param_count, sizeof(Oid)))
- || !(params->values = calloc(param_count, sizeof(char *)))
- || !(params->lengths = calloc(param_count, sizeof(int)))
- || !(params->formats = calloc(param_count, sizeof(int)))
- || !(params->item_vals = calloc(param_count, sizeof(union evsql_item_value)))
- )
- ERROR("calloc");
-
- // result format
- switch (result_format) {
- case EVSQL_FMT_TEXT:
- params->result_format = 0; break;
-
- case EVSQL_FMT_BINARY:
- params->result_format = 1; break;
-
- default:
- FATAL("params.result_fmt: %d", result_format);
- }
-
- // good
- return 0;
-
-error:
- return -1;
-}
-
-struct evsql_query *evsql_query (struct evsql *evsql, struct evsql_trans *trans, const char *command, evsql_query_cb query_fn, void *cb_arg) {
- struct evsql_query *query = NULL;
-
- // alloc new query
- if ((query = _evsql_query_new(evsql, trans, query_fn, cb_arg)) == NULL)
- goto error;
-
- // just execute the command string directly
- if (_evsql_query_enqueue(evsql, trans, query, command))
- goto error;
-
- // ok
- return query;
-
-error:
- _evsql_query_free(query);
-
- return NULL;
-}
-
-struct evsql_query *evsql_query_params (struct evsql *evsql, struct evsql_trans *trans,
- const char *command, const struct evsql_query_params *params,
- evsql_query_cb query_fn, void *cb_arg
-) {
- struct evsql_query *query = NULL;
- const struct evsql_item *param;
- size_t count = 0, idx;
-
- // alloc new query
- if ((query = _evsql_query_new(evsql, trans, query_fn, cb_arg)) == NULL)
- goto error;
-
- // count the params
- for (param = params->list; param->info.type; param++)
- count++;
-
- // initialize params
- _evsql_query_params_init_pq(&query->params, count, params->result_format);
-
- // transform
- for (param = params->list, idx = 0; param->info.type; param++, idx++) {
- // set for NULLs, otherwise not
- query->params.types[idx] = (param->bytes || param->flags.has_value) ? 0 : EVSQL_PQ_ARBITRARY_TYPE_OID;
-
- // scalar values
- query->params.item_vals[idx] = param->value;
-
- // values
- // point this at the value stored in the item_vals union if flagged as such
- query->params.values[idx] = param->flags.has_value ? (const char *) &query->params.item_vals[idx] : param->bytes;
-
- // lengths
- query->params.lengths[idx] = param->length;
-
- // XXX: this assumes that format is FMT_BINARY...
- query->params.formats[idx] = param->info.format;
- }
-
- // execute it
- if (_evsql_query_enqueue(evsql, trans, query, command))
- goto error;
-
-#ifdef DEBUG_ENABLED
- // debug it?
- DEBUG("evsql.%p: enqueued query=%p on trans=%p", evsql, query, trans);
- evsql_query_debug(command, params);
-#endif /* DEBUG_ENABLED */
-
- // ok
- return query;
-
-error:
- _evsql_query_free(query);
-
- return NULL;
-}
-
-struct evsql_query *evsql_query_exec (struct evsql *evsql, struct evsql_trans *trans,
- const struct evsql_query_info *query_info,
- evsql_query_cb query_fn, void *cb_arg,
- ...
-) {
- va_list vargs;
- struct evsql_query *query = NULL;
- const struct evsql_item_info *param;
- size_t count = 0, idx;
- err_t err = 1;
-
- // varargs
- va_start(vargs, cb_arg);
-
- // alloc new query
- if ((query = _evsql_query_new(evsql, trans, query_fn, cb_arg)) == NULL)
- goto error;
-
- // count the params
- for (param = query_info->params; param->type; param++)
- count++;
-
- // initialize params
- _evsql_query_params_init_pq(&query->params, count, EVSQL_FMT_BINARY);
-
- // transform
- for (param = query_info->params, idx = 0; param->type; param++, idx++) {
- // default type to 0 (implicit)
- query->params.types[idx] = 0;
-
- // default format to binary
- query->params.formats[idx] = EVSQL_FMT_BINARY;
-
- // consume argument
- switch (param->type) {
- case EVSQL_TYPE_NULL_: {
- // explicit type + text fmt
- query->params.types[idx] = EVSQL_PQ_ARBITRARY_TYPE_OID;
- query->params.values[idx] = NULL;
- query->params.lengths[idx] = 0;
- query->params.formats[idx] = EVSQL_FMT_TEXT;
- } break;
-
- case EVSQL_TYPE_BINARY: {
- struct evsql_item_binary item = va_arg(vargs, struct evsql_item_binary);
-
- // value + explicit len
- query->params.values[idx] = item.ptr;
- query->params.lengths[idx] = item.len;
- } break;
-
- case EVSQL_TYPE_STRING: {
- const char *str = va_arg(vargs, const char *);
-
- // value + automatic length, text format
- query->params.values[idx] = str;
- query->params.lengths[idx] = 0;
- query->params.formats[idx] = EVSQL_FMT_TEXT;
- } break;
-
- case EVSQL_TYPE_UINT16: {
- // XXX: uint16_t is passed as `int'?
- uint16_t uval = va_arg(vargs, int);
-
- if (uval != (int16_t) uval)
- ERROR("param $%zu: uint16 overflow: %d", idx + 1, uval);
-
- // network-byte-order value + explicit len
- query->params.item_vals[idx].uint16 = htons(uval);
- query->params.values[idx] = (const char *) &query->params.item_vals[idx];
- query->params.lengths[idx] = sizeof(uint16_t);
- } break;
-
- case EVSQL_TYPE_UINT32: {
- uint32_t uval = va_arg(vargs, uint32_t);
-
- if (uval != (int32_t) uval)
- ERROR("param $%zu: uint32 overflow: %ld", idx + 1, (unsigned long) uval);
-
- // network-byte-order value + explicit len
- query->params.item_vals[idx].uint32 = htonl(uval);
- query->params.values[idx] = (const char *) &query->params.item_vals[idx];
- query->params.lengths[idx] = sizeof(uint32_t);
- } break;
-
- case EVSQL_TYPE_UINT64: {
- uint64_t uval = va_arg(vargs, uint64_t);
-
- if (uval != (int64_t) uval)
- ERROR("param $%zu: uint16 overflow: %lld", idx + 1, (unsigned long long) uval);
-
- // network-byte-order value + explicit len
- query->params.item_vals[idx].uint64 = htonq(uval);
- query->params.values[idx] = (const char *) &query->params.item_vals[idx];
- query->params.lengths[idx] = sizeof(uint64_t);
- } break;
-
- default:
- FATAL("param $%zu: invalid type: %d", idx + 1, param->type);
- }
- }
-
- // execute it
- if (_evsql_query_enqueue(evsql, trans, query, query_info->sql))
- goto error;
-
- // no error, fallthrough for va_end
- err = 0;
-
-error:
- // possible cleanup
- if (err)
- _evsql_query_free(query);
-
- // end varargs
- va_end(vargs);
-
- // return
- return err ? NULL : query;
-}
-
-
-void evsql_query_abort (struct evsql_trans *trans, struct evsql_query *query) {
- assert(query);
-
- if (trans) {
- // must be the right query
- assert(trans->query == query);
- }
-
- // just strip the callback and wait for it to complete as normal
- query->cb_fn = NULL;
-}
-
--- a/src/evsql/result.c Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,258 +0,0 @@
-
-#include "evsql.h"
-#include "../lib/error.h"
-#include "../lib/misc.h"
-
-#include <stdlib.h>
-#include <assert.h>
-
-const char *evsql_result_error (const struct evsql_result *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 *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 *res) {
- switch (res->evsql->type) {
- case EVSQL_EVPQ:
- return PQnfields(res->result.pq);
-
- default:
- FATAL("res->evsql->type");
- }
-}
-
-size_t evsql_result_affected (const struct evsql_result *res) {
- switch (res->evsql->type) {
- case EVSQL_EVPQ:
- // XXX: errors?
- return strtol(PQcmdTuples(res->result.pq), NULL, 10);
-
- default:
- FATAL("res->evsql->type");
- }
-}
-
-
-int evsql_result_null (const struct evsql_result *res, size_t row, size_t col) {
- switch (res->evsql->type) {
- case EVSQL_EVPQ:
- return PQgetisnull(res->result.pq, row, col);
-
- default:
- FATAL("res->evsql->type");
- }
-}
-
-int evsql_result_field (const struct evsql_result *res, size_t row, size_t col, const char **ptr, size_t *size) {
- *ptr = NULL;
-
- switch (res->evsql->type) {
- case EVSQL_EVPQ:
- if (PQfformat(res->result.pq, col) != 1)
- ERROR("[%zu:%zu] PQfformat is not binary: %d", row, col, PQfformat(res->result.pq, 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;
-}
-
-err_t evsql_result_check (struct evsql_result *res) {
- // so simple...
- return res->error ? EIO : 0;
-}
-
-err_t evsql_result_begin (struct evsql_result_info *info, struct evsql_result *res) {
- struct evsql_item_info *col;
- size_t cols = 0, nrows;
- err_t err;
-
- // count info columns
- for (col = info->columns; col->type; col++)
- cols++;
-
- // number of rows returned/affected
- nrows = evsql_result_rows(res) || evsql_result_affected(res);
-
- // did the query fail outright?
- if (res->error)
- // dump error message
- NXERROR(err = EIO, evsql_result_error(res));
-
-/*
- // SELECT/DELETE/UPDATE WHERE didn't match any rows -> ENOENT
- if (nrows == 0)
- XERROR(err = ENOENT, "no rows returned/affected");
-*/
-
- // correct number of columns
- if (evsql_result_cols(res) != cols)
- XERROR(err = EINVAL, "wrong number of columns: %zu, should be %zu", evsql_result_cols(res), cols);
-
- // assign
- res->info = info;
- res->row_offset = 0;
-
- // good
- return 0;
-
-error:
- return err;
-
-}
-
-int evsql_result_next (struct evsql_result *res, ...) {
- va_list vargs;
- struct evsql_item_info *col;
- size_t col_idx, row_idx = res->row_offset;
- err_t err;
-
- // ensure that evsql_result_begin has been called
- assert(res->info);
-
- // check if we're past the end
- if (row_idx >= evsql_result_rows(res))
- return 0;
-
- // varargs
- va_start(vargs, res);
-
- for (col = res->info->columns, col_idx = 0; col->type; col++, col_idx++) {
- const char *value = NULL;
- size_t length = 0;
-
- // check for NULLs, then try and get the field value
- if (evsql_result_null(res, row_idx, col_idx)) {
- if (!col->flags.null_ok)
- XERROR(err = EINVAL, "r%zu:c%zu: NULL", row_idx, col_idx);
-
- } else if (evsql_result_field(res, row_idx, col_idx, &value, &length)) {
- SERROR(err = EINVAL);
-
- }
-
- // read the arg
- switch (col->type) {
- case EVSQL_TYPE_BINARY: {
- struct evsql_item_binary *item_ptr = va_arg(vargs, struct evsql_item_binary *);
-
- if (value) {
- item_ptr->ptr = value;
- item_ptr->len = length;
- }
- } break;
-
- case EVSQL_TYPE_STRING: {
- const char **str_ptr = va_arg(vargs, const char **);
-
- if (value) {
- *str_ptr = value;
- }
-
- } break;
-
- case EVSQL_TYPE_UINT16: {
- uint16_t *uval_ptr = va_arg(vargs, uint16_t *);
-
- if (!value) break;
-
- if (length != sizeof(uint16_t)) XERROR(err = EINVAL, "r%zu:c%zu: wrong size for uint16_t: %zu", row_idx, col_idx, length);
-
- int16_t sval = ntohs(*((int16_t *) value));
-
- if (sval < 0) XERROR(err = ERANGE, "r%zu:c%zu: out of range for uint16_t: %hd", row_idx, col_idx, (signed short) sval);
-
- *uval_ptr = sval;
- } break;
-
- case EVSQL_TYPE_UINT32: {
- uint32_t *uval_ptr = va_arg(vargs, uint32_t *);
-
- if (!value) break;
-
- if (length != sizeof(uint32_t)) XERROR(err = EINVAL, "r%zu:c%zu: wrong size for uint32_t: %zu", row_idx, col_idx, length);
-
- int32_t sval = ntohl(*((int32_t *) value));
-
- if (sval < 0) XERROR(err = ERANGE, "r%zu:c%zu: out of range for uint32_t: %ld", row_idx, col_idx, (signed long) sval);
-
- *uval_ptr = sval;
- } break;
-
- case EVSQL_TYPE_UINT64: {
- uint64_t *uval_ptr = va_arg(vargs, uint64_t *);
-
- if (!value) break;
-
- if (length != sizeof(uint64_t)) XERROR(err = EINVAL, "r%zu:c%zu: wrong size for uint64_t: %zu", row_idx, col_idx, length);
-
- int64_t sval = ntohq(*((int64_t *) value));
-
- if (sval < 0) XERROR(err = ERANGE, "r%zu:c%zu: out of range for uint64_t: %lld", row_idx, col_idx, (signed long long) sval);
-
- *uval_ptr = sval;
- } break;
-
- default:
- XERROR(err = EINVAL, "r%zu:c%zu: invalid type: %d", row_idx, col_idx, col->type);
- }
- }
-
- // advance row index
- res->row_offset++;
-
- // row handled succesfully
- return 1;
-
-error:
- return -err;
-}
-
-void evsql_result_end (struct evsql_result *res) {
- // not much more to it...
- evsql_result_free(res);
-}
-
-void evsql_result_free (struct evsql_result *res) {
- // note that the result itself might be NULL...
- // in the case of internal-error results, these may be free'd multiple times!
- switch (res->evsql->type) {
- case EVSQL_EVPQ:
- if (res->result.pq)
- return PQclear(res->result.pq);
-
- default:
- FATAL("res->evsql->type");
- }
-}
-
-
--- a/src/evsql/util.c Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,280 +0,0 @@
-#include <stdlib.h>
-#include <assert.h>
-
-#include "evsql.h"
-#include "../lib/log.h"
-#include "../lib/misc.h"
-
-#define _PARAM_TYPE_CASE(typenam) case EVSQL_TYPE_ ## typenam: return #typenam
-
-#define _PARAM_VAL_BUF_MAX 120
-#define _PARAM_VAL_CASE(typenam, ...) case EVSQL_TYPE_ ## typenam: if (item->bytes) ret = snprintf(buf, _PARAM_VAL_BUF_MAX, __VA_ARGS__); else return "(null)"; break
-
-const char *evsql_item_type (const struct evsql_item_info *item_info) {
- switch (item_info->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_item_val (const struct evsql_item *item) {
- static char buf[_PARAM_VAL_BUF_MAX];
- int ret;
-
- switch (item->info.type) {
- _PARAM_VAL_CASE (INVALID, "???" );
- _PARAM_VAL_CASE (NULL_, "(null)" );
- _PARAM_VAL_CASE (BINARY, "%zu:%s", item->length, "... " );
- _PARAM_VAL_CASE (STRING, "%s", item->bytes );
- _PARAM_VAL_CASE (UINT16, "%hu", (unsigned short int) ntohs(item->value.uint16) );
- _PARAM_VAL_CASE (UINT32, "%lu", (unsigned long int) ntohl(item->value.uint32) );
- _PARAM_VAL_CASE (UINT64, "%llu", (unsigned long long int) ntohq(item->value.uint64) );
- default: return "???";
- }
-
- return buf;
-}
-
-int evsql_params_clear (struct evsql_query_params *params) {
- struct evsql_item *param;
-
- for (param = params->list; param->info.type; param++) {
- param->bytes = NULL;
- param->flags.has_value = 0;
- }
-
- return 0;
-}
-
-int evsql_param_null (struct evsql_query_params *params, size_t param) {
- struct evsql_item *p = ¶ms->list[param];
-
- p->bytes = NULL;
- p->flags.has_value = 0;
-
- return 0;
-}
-
-int evsql_param_binary (struct evsql_query_params *params, size_t param, const char *ptr, size_t len) {
- struct evsql_item *p = ¶ms->list[param];
-
- assert(p->info.type == EVSQL_TYPE_BINARY);
-
- p->bytes = ptr;
- p->length = len;
-
- return 0;
-}
-
-int evsql_param_string (struct evsql_query_params *params, size_t param, const char *ptr) {
- struct evsql_item *p = ¶ms->list[param];
-
- assert(p->info.type == EVSQL_TYPE_STRING);
-
- // XXX: hmm...
- p->info.format = EVSQL_FMT_TEXT;
-
- p->bytes = ptr;
- p->length = 0;
-
- return 0;
-}
-
-int evsql_param_uint16 (struct evsql_query_params *params, size_t param, uint16_t uval) {
- struct evsql_item *p = ¶ms->list[param];
-
- assert(p->info.type == EVSQL_TYPE_UINT16);
-
- p->value.uint16 = htons(uval);
- p->length = sizeof(uval);
- p->flags.has_value = 1;
-
- return 0;
-}
-
-int evsql_param_uint32 (struct evsql_query_params *params, size_t param, uint32_t uval) {
- struct evsql_item *p = ¶ms->list[param];
-
- assert(p->info.type == EVSQL_TYPE_UINT32);
-
- p->value.uint32 = htonl(uval);
- p->length = sizeof(uval);
- p->flags.has_value = 1;
-
- return 0;
-}
-
-void evsql_query_debug (const char *sql, const struct evsql_query_params *params) {
- const struct evsql_item *param;
- size_t param_count = 0, idx = 0;
-
- // count the params
- for (param = params->list; param->info.type; param++)
- param_count++;
-
- DEBUG("sql: %s", sql);
- DEBUG("params: %zu", param_count);
-
- for (param = params->list; param->info.type; param++) {
- DEBUG("\t%2zu : %8s = %s", ++idx, evsql_item_type(¶m->info), evsql_item_val(param));
- }
-}
-
-int evsql_result_binary (const struct evsql_result *res, size_t row, size_t col, const char **ptr, size_t *size, bool 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));
-
- *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_binlen (const struct evsql_result *res, size_t row, size_t col, const char **ptr, size_t size, int nullok) {
- size_t real_size = 0;
-
- 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);
-
- return 0;
-
-error:
- return -1;
-}
-
-int evsql_result_string (const struct evsql_result *res, size_t row, size_t col, const char **ptr, int 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 *res, size_t row, size_t col, uint16_t *uval, int nullok) {
- const char *data;
- int16_t sval;
-
- if (evsql_result_binlen(res, row, col, &data, sizeof(*uval), nullok))
- goto error;
-
- if (!data)
- return 0;
-
- 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 *res, size_t row, size_t col, uint32_t *uval, int nullok) {
- const char *data;
- int32_t sval;
-
- if (evsql_result_binlen(res, row, col, &data, sizeof(*uval), nullok))
- goto error;
-
- if (!data)
- return 0;
-
- 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 *res, size_t row, size_t col, uint64_t *uval, int nullok) {
- const char *data;
- int64_t sval;
-
- if (evsql_result_binlen(res, row, col, &data, sizeof(*uval), nullok))
- goto error;
-
- if (!data)
- return 0;
-
- sval = ntohq(*(int64_t *) data);
-
- if (sval < 0)
- ERROR("negative value for unsigned: %ld", sval);
-
- *uval = sval;
-
- return 0;
-
-error:
- return nullok ? 0 : -1;
-}
-
-const char *evsql_conn_error (struct evsql_conn *conn) {
- switch (conn->evsql->type) {
- case EVSQL_EVPQ:
- if (!conn->engine.evpq)
- return "unknown error (no conn)";
-
- return evpq_error_message(conn->engine.evpq);
-
- default:
- FATAL("res->evsql->type");
- }
-}
-
-const char *evsql_trans_error (struct evsql_trans *trans) {
- if (trans->conn == NULL)
- return "unknown error (no trans conn)";
-
- return evsql_conn_error(trans->conn);
-}
-
--- a/src/hello.c Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,247 +0,0 @@
-#include <string.h>
-#include <errno.h>
-#include <stdlib.h>
-
-#include <event2/event.h>
-#include <fuse/fuse_opt.h>
-
-#include "lib/log.h"
-#include "lib/math.h"
-#include "lib/signals.h"
-#include "evfuse.h"
-#include "dirbuf.h"
-
-const char *file_name = "hello";
-const char *file_data = "Hello World\n";
-
-static struct hello {
- struct event_base *ev_base;
-
- struct signals *signals;
-
- struct evfuse *ev_fuse;
-
-} ctx;
-
-void hello_init (void *userdata, struct fuse_conn_info *conn) {
- INFO("[hello.init] userdata=%p, conn=%p", userdata, conn);
-}
-
-void hello_destroy (void *userdata) {
- INFO("[hello.destroy] userdata=%p", userdata);
-}
-
-void hello_lookup (fuse_req_t req, fuse_ino_t parent, const char *name) {
- struct fuse_entry_param e;
-
- INFO("[hello.lookup] (uid=%d, pid=%d) parent=%lu name=%s", fuse_req_ctx(req)->uid, fuse_req_ctx(req)->pid, parent, name);
-
- // the world is flat
- if (parent != 1 || strcmp(name, file_name)) {
- fuse_reply_err(req, ENOENT);
-
- return;
- }
-
- // set up the entry
- memset(&e, 0, sizeof(e));
- e.ino = 2;
- e.attr_timeout = 1.0;
- e.entry_timeout = 1.0;
- e.attr.st_mode = S_IFREG | 0444;
- e.attr.st_nlink = 1;
- e.attr.st_size = strlen(file_data);
-
- // reply
- fuse_reply_entry(req, &e);
-}
-
-void hello_getattr (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) {
- struct stat stbuf;
-
- INFO("[hello.getattr] (uid=%d, pid=%d) ino=%lu, fi=%p", fuse_req_ctx(req)->uid, fuse_req_ctx(req)->pid, ino, fi);
-
- memset(&stbuf, 0, sizeof(stbuf));
-
- // the root dir, or the file?
- if (ino == 1) {
- stbuf.st_mode = S_IFDIR | 0555;
- stbuf.st_nlink = 2;
-
- } else if (ino == 2) {
- stbuf.st_mode = S_IFREG | 0444;
- stbuf.st_nlink = 1;
- stbuf.st_size = strlen(file_data);
-
- } else {
- fuse_reply_err(req, ENOENT);
- return;
- }
-
- // reply
- fuse_reply_attr(req, &stbuf, 1.0);
-}
-
-
-void hello_readdir (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi) {
- int err = 0;
- struct dirbuf buf;
-
- INFO("[hello.readdir] ino=%lu, size=%zu, off=%zu, fi=%p", ino, size, off, fi);
-
- // there exists only one dir
- if (ino != 1) {
- fuse_reply_err(req, ENOTDIR);
- return;
- }
-
- // fill in the dirbuf
- if (dirbuf_init(&buf, size, off))
- ERROR("failed to init dirbuf");
-
- err = dirbuf_add(req, &buf, 0, 1, ".", 1, S_IFDIR )
- || dirbuf_add(req, &buf, 1, 2, "..", 1, S_IFDIR )
- || dirbuf_add(req, &buf, 2, 3, file_name, 2, S_IFREG );
-
- if (err < 0)
- ERROR("failed to add dirents to buf");
-
- // send it
- if ((err = -dirbuf_done(req, &buf)))
- EERROR(-err, "failed to send buf");
-
- // success
- return;
-
-error:
- if ((err = fuse_reply_err(req, err ? err : EIO)))
- EWARNING(err, "failed to send error reply");
-}
-
-void hello_open (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) {
- int err = 0;
-
- INFO("[hello.open] ino=%lu, fi=%p, fi->flags=%08X", ino, fi, fi->flags);
-
- if (ino != 2) {
- // must open our only file, not the dir
- fuse_reply_err(req, ino == 1 ? EISDIR : ENOENT);
- return;
-
- } else if ((fi->flags & 0x03) != O_RDONLY) {
- // "permission denied"
- fuse_reply_err(req, EACCES);
- return;
- }
-
- // XXX: update fi stuff?
-
- // open it!
- if ((err = fuse_reply_open(req, fi)))
- EERROR(err, "fuse_reply_open");
-
- // success
- return;
-
-error:
- if ((err = fuse_reply_err(req, err ? err : EIO)))
- EWARNING(err, "failed to send error reply");
-}
-
-void hello_read (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi) {
- int err = 0;
-
- // fi is unused
- (void) fi;
-
- INFO("[hello.read] ino=%lu, size=%zu, off=%zu, fi=%p", ino, size, off, fi);
-
- if (ino != 2) {
- // EEK!
- FATAL("wrong inode");
- }
-
- if (off >= strlen(file_data)) {
- // offset is out-of-file, so return EOF
- err = fuse_reply_buf(req, NULL, 0);
-
- } else {
- // reply with the requested file data
- err = fuse_reply_buf(req, file_data + off, MIN(strlen(file_data) - off, size));
- }
-
- // reply
- if (err)
- PERROR("fuse_reply_buf");
-
- // success
- return;
-
-error:
- if ((err = fuse_reply_err(req, err ? err : EIO)))
- EWARNING(err, "failed to send error reply");
-}
-
-void hello_getxattr (fuse_req_t req, fuse_ino_t ino, const char *name, size_t size) {
- INFO("[hello.getxattr] ino=%lu, name=`%s', size=%zu", ino, name, size);
-
- fuse_reply_err(req, ENOSYS);
-}
-
-struct fuse_lowlevel_ops hello_llops = {
- .init = &hello_init,
- .destroy = &hello_destroy,
-
- .lookup = &hello_lookup,
- .getattr = &hello_getattr,
-
- .open = &hello_open,
-
- .read = &hello_read,
-
- .readdir = &hello_readdir,
-
- .getxattr = hello_getxattr,
-};
-
-
-int main (int argc, char **argv) {
- struct fuse_args fuse_args = FUSE_ARGS_INIT(argc, argv);
-
- // zero
- memset(&ctx, 0, sizeof(ctx));
-
- // 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 fuse
- if ((ctx.ev_fuse = evfuse_new(ctx.ev_base, &fuse_args, &hello_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_free(ctx.ev_fuse);
-
- 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/helloworld.c Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,7 +0,0 @@
-#include <stdio.h>
-
-int main (void) {
- printf("Hello World\n");
-
- return 0;
-}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/internal.h Sun Mar 08 01:33:45 2009 +0200
@@ -0,0 +1,192 @@
+#ifndef EVSQL_INTERNAL_H
+#define EVSQL_INTERNAL_H
+
+/*
+ * Internal interfaces
+ */
+
+#include <sys/queue.h>
+
+#include <event2/event.h>
+
+#include "evsql.h"
+#include "evpq.h"
+
+/*
+ * The engine type
+ */
+enum evsql_type {
+ EVSQL_EVPQ, // evpq
+};
+
+/*
+ * Contains the type, engine configuration, list of connections and waiting query queue.
+ */
+struct evsql {
+ // what event_base to use
+ struct event_base *ev_base;
+
+ // what engine we use
+ enum evsql_type type;
+
+ // callbacks
+ evsql_error_cb error_fn;
+ void *cb_arg;
+
+ // engine-specific connection configuration
+ union {
+ const char *evpq;
+ } engine_conf;
+
+ // list of connections that are open
+ LIST_HEAD(evsql_conn_list, evsql_conn) conn_list;
+
+ // list of queries running or waiting to run
+ TAILQ_HEAD(evsql_query_queue, evsql_query) query_queue;
+};
+
+/*
+ * A single connection to the server.
+ *
+ * Contains the engine connection, may have a transaction associated, and may have a query associated.
+ */
+struct evsql_conn {
+ // evsql we belong to
+ struct evsql *evsql;
+
+ // engine-specific connection info
+ union {
+ struct evpq_conn *evpq;
+ } engine;
+
+ // our position in the conn list
+ LIST_ENTRY(evsql_conn) entry;
+
+ // are we running a transaction?
+ struct evsql_trans *trans;
+
+ // are we running a transactionless query?
+ struct evsql_query *query;
+};
+
+/*
+ * A single transaction.
+ *
+ * Has a connection associated and possibly a query (which will also be associated with the connection)
+ */
+struct evsql_trans {
+ // our evsql_conn/evsql
+ struct evsql *evsql;
+ struct evsql_conn *conn;
+
+ // callbacks
+ evsql_trans_error_cb error_fn;
+ evsql_trans_ready_cb ready_fn;
+ evsql_trans_done_cb done_fn;
+ void *cb_arg;
+
+ // the transaction type
+ enum evsql_trans_type type;
+
+ // has evsql_trans_commit be called?
+ int has_commit : 1;
+
+ // our current query
+ struct evsql_query *query;
+
+};
+
+/*
+ * Backend result handle
+ */
+union evsql_result_handle {
+ PGresult *pq;
+};
+
+/*
+ * A single query.
+ *
+ * Has the info needed to exec the query (as these may be queued), and the callback/result info.
+ */
+struct evsql_query {
+ // the actual SQL query, this may or may not be ours, see _evsql_query_exec
+ char *command;
+
+ // possible query params
+ struct evsql_query_params_pq {
+ int count;
+
+ Oid *types;
+ const char **values;
+ int *lengths;
+ int *formats;
+
+ // storage for numeric values
+ union evsql_item_value *item_vals;
+
+ int result_format;
+ } params;
+
+ // our callback
+ evsql_query_cb cb_fn;
+ void *cb_arg;
+
+ // the result we get
+ union evsql_result_handle result;
+
+ // our position in the query list
+ TAILQ_ENTRY(evsql_query) entry;
+};
+
+// the result
+struct evsql_result {
+ struct evsql *evsql;
+
+ // possible error code
+ int error;
+
+ // the actual result
+ union evsql_result_handle result;
+
+ // result_* state
+ struct evsql_result_info *info;
+ size_t row_offset;
+};
+
+
+// maximum length for a 'BEGIN TRANSACTION ...' query
+#define EVSQL_QUERY_BEGIN_BUF 512
+
+// the should the OID of some valid psql type... *ANY* valid psql type, doesn't matter, only used for NULLs
+// 16 = bool in 8.3
+#define EVSQL_PQ_ARBITRARY_TYPE_OID 16
+
+/*
+ * Core query-submission interface.
+ *
+ * This performs some error-checking on the trans, allocates the evsql_query and does some basic initialization.
+ *
+ * This does not actually enqueue the query anywhere, no reference is stored anywhere.
+ *
+ * Returns the new evsql_query on success, NULL on failure.
+ */
+struct evsql_query *_evsql_query_new (struct evsql *evsql, struct evsql_trans *trans, evsql_query_cb query_fn, void *cb_arg);
+
+/*
+ * Begin processing the given query, which should now be fully filled out.
+ *
+ * If trans is given, it MUST be idle, and the query will be executed. Otherwise, it will either be executed directly
+ * or enqueued for future execution.
+ *
+ * Returns zero on success, nonzero on failure.
+ */
+int _evsql_query_enqueue (struct evsql *evsql, struct evsql_trans *trans, struct evsql_query *query, const char *command);
+
+/*
+ * Free the query and related resources, doesn't trigger any callbacks or remove from any queues.
+ *
+ * The command should already be taken care of (NULL).
+ */
+void _evsql_query_free (struct evsql_query *query);
+
+#endif /* EVSQL_INTERNAL_H */
--- a/src/lib/lex.c Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,181 +0,0 @@
-
-#include <stdlib.h>
-
-#include "lex.h"
-#include "error.h"
-#include "log.h"
-
-#define INITIAL_BUF_SIZE 4096
-
-int lexer (const struct lex *lex, const char *input, void *arg) {
- // handling error returns
- int err = -1, cb_err;
-
- // token buffer
- char *buf = NULL, *buf_ptr;
- size_t buf_size = INITIAL_BUF_SIZE;
-
- // state
- int prev_state = LEX_INITIAL, cur_state = lex->initial_state, next_state = LEX_INITIAL;
-
- // input chars
- const char *c = input;
-
- // lookups
- const struct lex_transition *trans = NULL;
-
- // allocate the buffer
- if ((buf = malloc(sizeof(char) * buf_size)) == NULL)
- goto error;
-
- // set buf_ptr initial position
- buf_ptr = buf;
-
- // clear input
- DEBUG("*cough*");
- DEBUGN("%s", "");
-
- // process input
- do {
- if (*c) {
- // look up the next state
- for (trans = lex->state_list[cur_state - 1].trans_list; trans->next_state > 0 || trans->flags; trans++) {
- // accept defaults
- if (trans->flags & LEX_TRANS_DEFAULT)
- break;
-
- // disregard non-matches
- if (trans->left > *c || *c > trans->right)
- continue;
-
- // abort on invalids
- if (trans->flags & LEX_TRANS_INVALID) {
- goto error;
-
- } else {
- // accept it
- break;
- }
- }
-
- // did we find a transition with a valid next state?
- if (!(next_state = trans->next_state))
- goto error;
-
- // call the char handler
- if (lex->char_fn && (cb_err = lex->char_fn(*c, cur_state, next_state, arg)))
- goto error;
-
- } else {
- // EOF!
- next_state = LEX_EOF;
-
- // is cur_state a valid end state?
- if (!(lex->state_list[cur_state - 1].flags & LEX_STATE_END))
- goto error;
-
- // note: we don't pass the NUL byte to the char handler
- }
-
- // if this char is part of the next token...
- if (next_state != cur_state) {
- // terminate the buffer and reset buf_ptr
- *buf_ptr = 0; buf_ptr = buf;
-
- // dump state transitions
- DEBUGF("\n\t%25s -> %25s -> %25s",
- LEX_STATE_NAME(lex, prev_state),
- LEX_STATE_NAME(lex, cur_state),
- LEX_STATE_NAME(lex, next_state)
- );
-
- // pass in the complete token to the handler
- if (lex->token_fn && (cb_err = lex->token_fn(cur_state, buf, next_state, prev_state, arg)))
- goto error;
-
- // update states
- prev_state = cur_state;
- cur_state = next_state;
- next_state = LEX_INITIAL;
- }
-
- // dump chars
- if (next_state == LEX_INITIAL)
- DEBUGN("%c", *c);
- else
- DEBUGNF("%c", *c);
-
- // store this char in the buffer
- *(buf_ptr++) = *c;
-
- // grow the buffer if needed
- if (buf_ptr - buf >= buf_size) {
- // remember the offset, as buf_ptr might get invalidated if buf is moved
- size_t buf_offset = buf_ptr - buf;
-
- // calc new size
- buf_size *= 2;
-
- // grow/move
- if ((buf = realloc(buf, buf_size)) == NULL)
- goto error;
-
- // fix buf_ptr
- buf_ptr = buf + buf_offset;
- }
- } while (*(c++));
-
- // call the end handler
- if (lex->end_fn && (cb_err = lex->end_fn(cur_state, arg)))
- goto error;
-
- // successfully parsed!
- err = 0;
-
-error:
- DEBUGNF("\n");
-
- if (cb_err)
- err = cb_err;
-
- // dump debug info on error
- if (err) {
- const char *cc;
-
- // figure out the error
- if (!buf)
- WARNING("malloc/realloc");
-
- else if (trans && trans->flags & LEX_TRANS_INVALID)
- WARNING("hit invalid transition match");
-
- else if (!next_state)
- WARNING("no valid transition found");
-
- else if (next_state == LEX_EOF && !(lex->state_list[cur_state - 1].flags & LEX_STATE_END))
- WARNING("invalid end state");
-
- else
- WARNING("unknown error condition (!?)");
-
- DEBUG("%s", input);
- DEBUGN("%s", "");
-
- for (cc = input; cc < c; cc++)
- DEBUGNF(" ");
-
- DEBUGF("^\t%s -> %s -> %s",
- LEX_STATE_NAME(lex, prev_state),
- LEX_STATE_NAME(lex, cur_state),
- LEX_STATE_NAME(lex, next_state)
- );
- }
-
- // free stuff
- free(buf);
-
- // return
- return err;
-}
-
-
--- a/src/lib/lex.h Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,143 +0,0 @@
-#ifndef LIB_LEXER_H
-#define LIB_LEXER_H
-
-/*
- * Simple FSM lexing
- *
- * The lexer is implemented as a Finite State Machine, consisting for a number of states, which then contain a set of
- * transitions, which move the lexer from state to state based on each char of input at a time.
- *
- * Whenever the state changes, the token callback is triggered with the collected token data.
- */
-
-#include <sys/types.h>
-
-/*
- * Transition flags
- */
-enum lex_transition_flags {
- LEX_TRANS_DEFAULT = 0x01,
- /* not supported
- LEX_TRANS_FINAL = 0x02, */
- LEX_TRANS_INVALID = 0x04,
-};
-
-/*
- * A transition from one state to another.
- */
-struct lex_transition {
- // applies to chars [left, right]
- char left, right;
-
- // flags from lex_transition_flags
- char flags;
-
- // next state to enter
- int next_state;
-};
-
-/*
- * State flags
- */
-enum lex_state_flags {
- LEX_STATE_END = 0x01,
-};
-
-/*
- * A state
- */
-struct lex_state {
- // the state name (for debugging)
- const char *name;
-
- // flags from lex_state_flags
- char flags;
-
- // list of transitions for this state, terminated by a transition with next_state=0
- struct lex_transition trans_list[15];
-};
-
-/*
- * Special states, these are all defined as zero
- */
-
-// shows up in token_fn as the value of next_token when this_token is the last token.
-#define LEX_EOF 0
-
-// shows up as the initial value of prev_token
-#define LEX_INITIAL 0
-
-/*
- * Lex machine
- */
-struct lex {
- /*
- * Core token handler. Everytime a full token is lexed (i.e. the state changes), this will be called.
- * `this_token` represents the full token that was parsed, and `token_data` is the token's value. `next_token`
- * is the state that terminated this token, and `prev_token` was the token before this one.
- *
- * `token_data` is a buffer allocated by the lexer that the actual input data is copied into. Thence, it can be
- * modified, as its contents will be replaced by the next token. Hence, if you need to keep hold of it, copy it.
- *
- * Return zero to have lexing continue, nonzero to stop lexing.
- */
- int (*token_fn) (int this_token, char *token_data, int next_token, int prev_token, void *arg);
-
- /*
- * Called on every char handled by the lexer.
- *
- * The NUL byte at the end of the input string is not passed to char_fn (why not?).
- *
- * Return zero to have lexing continue, nonzero to stop lexing.
- */
- int (*char_fn) (char token_char, int from_token, int to_token, void *arg);
-
- /*
- * Called when the end of input has been reached, `last_token` is the state that we terminated in.
- *
- * Return zero to indiciate that the input was valid, nonzero to indicate an error.
- */
- int (*end_fn) (int last_token, void *arg);
-
- // number of states
- size_t state_count;
-
- // initial state
- int initial_state;
-
- // array of lex_states, indexable by the state id.
- struct lex_state state_list[];
-};
-
-/*
- * Helper macros for building the state_list
- */
-#define LEX_STATE(enum_val) { #enum_val, 0,
-#define LEX_STATE_END(enum_val) { #enum_val, LEX_STATE_END,
-
- #define LEX_CHAR(c, to) { c, c, 0, to }
- #define LEX_RANGE(l, r, to) { l, r, 0, to }
- #define LEX_ALPHA(to) LEX_RANGE('a', 'z', to), LEX_RANGE('A', 'Z', to)
- #define LEX_NUMBER(to) LEX_RANGE('0', '9', to)
- #define LEX_ALNUM(to) LEX_ALPHA(to), LEX_NUMBER(to), LEX_CHAR('-', to), LEX_CHAR('_', to)
- #define LEX_WHITESPACE(to) LEX_CHAR(' ', to), LEX_CHAR('\n', to), LEX_CHAR('\t', to)
- #define LEX_INVALID(c) { c, c, LEX_TRANS_INVALID, 0 }
-
- #define LEX_DEFAULT(to) { 0, 0, LEX_TRANS_DEFAULT, to } \
- }
- #define LEX_END { 0, 0, 0, 0 } \
- }
-
-/*
- * Helpers for handling states
- */
-#define LEX_STATE_NAME(lex, state) ((state) ? (lex)->state_list[(state) - 1].name : "...")
-
-/*
- * Lex it!
- *
- * Return zero to indiciate that the input was valid, nonzero otherwise.
- */
-int lexer (const struct lex *lex, const char *input, void *arg);
-
-#endif /* LIB_LEXER_H */
--- a/src/lib/signals.c Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,109 +0,0 @@
-#define _GNU_SOURCE
-#include <signal.h>
-#include <string.h>
-#include <stdlib.h>
-#include <assert.h>
-
-#include "signals.h"
-#include "log.h"
-
-struct signals {
- struct event_base *ev_base;
-
- struct signal {
- struct event *ev;
- } sig_list[MAX_SIGNALS];
-
- int sig_count;
-};
-
-void signals_loopexit (int signal, short event, void *arg) {
- struct signals *signals = arg;
-
- INFO("[signal] caught %s: exiting the event loop", strsignal(signal));
-
- if (event_base_loopexit(signals->ev_base, NULL))
- FATAL("event_base_loopexit");
-}
-
-void signals_ignore (int signal, short event, void *arg) {
- struct signals *signals = arg;
-
- (void) signals;
-
- /* ignore */
-}
-
-struct signals *signals_alloc (struct event_base *ev_base) {
- struct signals *signals = NULL;
-
- if ((signals = calloc(1, sizeof(*signals))) == NULL)
- ERROR("calloc");
-
- // simple attributes
- signals->ev_base = ev_base;
-
- // done
- return signals;
-
-error:
- return NULL;
-}
-
-int signals_add (struct signals *signals, int sigval, void (*sig_handler)(evutil_socket_t, short, void *)) {
- struct signal *sig_info;
-
- // find our sig_info
- assert(signals->sig_count < MAX_SIGNALS);
- sig_info = &signals->sig_list[signals->sig_count++];
-
- // set up the libevent signal events
- if ((sig_info->ev = signal_new(signals->ev_base, sigval, sig_handler, signals)) == NULL)
- PERROR("signal_new");
-
- if (signal_add(sig_info->ev, NULL))
- PERROR("signal_add");
-
- // success
- return 0;
-
-error:
- return -1;
-}
-
-struct signals *signals_default (struct event_base *ev_base) {
- struct signals *signals = NULL;
-
- // alloc signals
- if ((signals = signals_alloc(ev_base)) == NULL)
- return NULL;
-
- // add the set of default signals
- if ( signals_add(signals, SIGPIPE, &signals_ignore)
- || signals_add(signals, SIGINT, &signals_loopexit)
- ) ERROR("signals_add");
-
- // success
- return signals;
-
-error:
- if (signals)
- signals_free(signals);
-
- return NULL;
-}
-
-void signals_free (struct signals *signals) {
- int i;
-
- // free all events
- for (i = 0; i < signals->sig_count; i++) {
- if (signal_del(signals->sig_list[i].ev))
- PWARNING("signal_del");
-
- }
-
- // free the info itself
- free(signals);
-}
-
--- a/src/lib/signals.h Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,54 +0,0 @@
-#ifndef LIB_SIGNAL_H
-#define LIB_SIGNAL_H
-
-/*
- * Handle signals in a libevent-sane way
- */
-
-#include <event2/event.h>
-
-/*
- * How many signals we can define actions for
- */
-#define MAX_SIGNALS 8
-
-/*
- * info about a set of signals
- */
-struct signals;
-
-/*
- * Used as a handler for signals that should cause a loopexit.
- */
-void signals_loopexit (int signal, short event, void *arg);
-
-/*
- * Used to receive signals, but discard them.
- */
-void signals_ignore (int signal, short event, void *arg);
-
-/*
- * Allocate a signals struct, acting on the given ev_base.
- *
- * Returns NULL on failure
- */
-struct signals *signals_alloc (struct event_base *ev_base);
-
-/*
- * Add a signal to be handled by the given signals struct with the given handler.
- */
-int signals_add (struct signals *signals, int sigval, void (*sig_handler)(evutil_socket_t, short, void *));
-
-/*
- * Add a set of default signals
- * SIGPIPE signals_ignore
- * SIGINT signals_loopexit
- */
-struct signals *signals_default (struct event_base *ev_base);
-
-/*
- * Free the resources/handlers associated with the given signal handler
- */
-void signals_free (struct signals *signals);
-
-#endif /* LIB_SIGNAL_H */
--- a/src/lib/url.c Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,587 +0,0 @@
-#define _GNU_SOURCE
-#include <stdlib.h>
-#include <string.h>
-
-#include "url.h"
-#include "lex.h"
-#include "error.h"
-#include "log.h"
-#include "misc.h"
-
-enum url_token {
- URL_INVALID,
-
- URL_BEGIN,
-
- // kludge to resolve ambiguous URL_SCHEME/URL_USERNAME+URL_PASSWORD/URL_HOSTNAME+URL_SERVICE at the beginning
- URL_BEGIN_ALNUM,
- URL_BEGIN_COLON,
-
- URL_SCHEME,
- URL_SCHEME_SEP,
- URL_SCHEME_END_COL,
- URL_SCHEME_END_SLASH1,
- URL_SCHEME_END_SLASH2,
-
- // kludge to resolve ambiguous URL_USERNAME+URL_PASSWORD/URL_HOSTNAME+URL_SERVICE after a scheme
- URL_USERHOST_ALNUM,
- URL_USERHOST_COLON,
- URL_USERHOST_ALNUM2,
-
- URL_USERNAME,
- URL_PASSWORD_SEP,
- URL_PASSWORD,
- URL_USERNAME_END,
-
- URL_HOSTNAME,
-
- URL_SERVICE_SEP,
- URL_SERVICE,
-
- URL_PATH_START,
- URL_PATH,
-
- URL_OPT_START,
- URL_OPT_KEY,
- URL_OPT_EQ,
- URL_OPT_VAL,
- URL_OPT_SEP,
-
- URL_MAX,
-};
-
-/*
- * Parser state
- */
-struct url_state {
- // the URL to parse into
- struct url *url;
-
- // our lookahead-kludge
- const char *alnum, *alnum2;
-
-};
-
-static int _url_append_scheme (struct url *url, const char *data, int copy) {
- if (!url->schema) {
- if ((url->schema = malloc(sizeof(struct url_schema) + (1 * sizeof(const char *)))) == NULL)
- ERROR("malloc");
-
- url->schema->count = 1;
-
- } else {
- url->schema->count++;
-
- // I'm starting to hate flexible array members...
- if ((url->schema = realloc(url->schema, sizeof(struct url_schema) + url->schema->count * sizeof(const char *))) == NULL)
- ERROR("realloc");
- }
-
- if ((url->schema->list[url->schema->count - 1] = copy ? strdup(data) : data) == NULL)
- ERROR("strdup");
-
- // k
- return 0;
-
-error:
- return -1;
-}
-
-static struct url_opt *_url_get_opt (struct url *url, int new) {
- if (!url->opts) {
- if ((url->opts = malloc(sizeof(struct url_opts) + (1 * sizeof(struct url_opt)))) == NULL)
- ERROR("malloc");
-
- url->opts->count = 1;
-
- } else if (new) {
- url->opts->count++;
-
- if ((url->opts = realloc(url->opts, sizeof(struct url_opts) + url->opts->count * sizeof(struct url_opt))) == NULL)
- ERROR("realloc");
- }
-
- // success
- return &url->opts->list[url->opts->count - 1];
-
-error:
- return NULL;
-}
-
-static int _url_append_opt_key (struct url *url, const char *key) {
- struct url_opt *opt;
-
- if ((opt = _url_get_opt(url, 1)) == NULL)
- goto error;
-
- if ((opt->key = strdup(key)) == NULL)
- ERROR("strdup");
-
- opt->value = NULL;
-
- return 0;
-
-error:
- return -1;
-}
-
-static int _url_append_opt_val (struct url *url, const char *value) {
- struct url_opt *opt;
-
- if ((opt = _url_get_opt(url, 0)) == NULL)
- goto error;
-
- if ((opt->value = strdup(value)) == NULL)
- ERROR("strdup");
-
- return 0;
-
-error:
- return -1;
-}
-
-static int url_lex_token (int _this_token, char *token_data, int _next_token, int _prev_token, void *arg);
-
-static struct lex url_lex = {
- .token_fn = url_lex_token,
- .char_fn = NULL,
- .end_fn = NULL,
-
- .state_count = URL_MAX,
- .initial_state = URL_BEGIN,
- .state_list = {
- LEX_STATE ( URL_BEGIN ) {
- LEX_ALNUM ( URL_BEGIN_ALNUM ),
- LEX_CHAR ( ':', URL_SERVICE_SEP ),
- LEX_CHAR ( '/', URL_PATH_START ),
- LEX_CHAR ( '?', URL_OPT_START ),
- LEX_END
- },
-
- // this can be URL_SCHEME, URL_USERNAME or URL_HOSTNAME
- LEX_STATE_END ( URL_BEGIN_ALNUM ) {
- LEX_CHAR ( '+', URL_SCHEME_SEP ), // it was URL_SCHEME
- LEX_CHAR ( ':', URL_BEGIN_COLON ),
- LEX_CHAR ( '@', URL_USERNAME_END ), // it was URL_USERNAME
- LEX_CHAR ( '/', URL_PATH_START ), // it was URL_HOSTNAME
- LEX_CHAR ( '?', URL_OPT_START ), // it was URL_HOSTNAME
- LEX_DEFAULT ( URL_BEGIN_ALNUM )
- },
-
- // this can be URL_SCHEME_END_COL, URL_USERNAME_END or URL_SERVICE_SEP
- LEX_STATE ( URL_BEGIN_COLON ) {
- LEX_CHAR ( '/', URL_SCHEME_END_SLASH1 ), // it was URL_SCHEME
- LEX_ALNUM ( URL_USERHOST_ALNUM2 ),
- LEX_END
- },
-
-
- LEX_STATE ( URL_SCHEME ) {
- LEX_ALNUM ( URL_SCHEME ),
- LEX_CHAR ( '+', URL_SCHEME_SEP ),
- LEX_CHAR ( ':', URL_SCHEME_END_COL ),
- LEX_END
- },
-
- LEX_STATE ( URL_SCHEME_SEP ) {
- LEX_ALNUM ( URL_SCHEME ),
- LEX_END
- },
-
- LEX_STATE ( URL_SCHEME_END_COL ) {
- LEX_CHAR ( '/', URL_SCHEME_END_SLASH1 ),
- LEX_END
- },
-
- LEX_STATE ( URL_SCHEME_END_SLASH1 ) {
- LEX_CHAR ( '/', URL_SCHEME_END_SLASH2 ),
- LEX_END
- },
-
- LEX_STATE_END ( URL_SCHEME_END_SLASH2 ) {
- LEX_ALNUM ( URL_USERHOST_ALNUM ),
- LEX_CHAR ( ':', URL_SERVICE_SEP ),
- LEX_CHAR ( '/', URL_PATH_START ),
- LEX_CHAR ( '?', URL_OPT_START ),
- LEX_END
- },
-
- // this can be URL_USERNAME or URL_HOSTNAME
- LEX_STATE_END ( URL_USERHOST_ALNUM ) {
- LEX_CHAR ( ':', URL_USERHOST_COLON ),
- LEX_CHAR ( '@', URL_USERNAME_END ), // it was URL_USERNAME
- LEX_CHAR ( '/', URL_PATH_START ), // it was URL_HOSTNAME
- LEX_CHAR ( '?', URL_OPT_START ), // it was URL_HOSTNAME
- LEX_DEFAULT ( URL_USERHOST_ALNUM ),
- },
-
- // this can be URL_USERNAME_END or URL_SERVICE_SEP
- LEX_STATE ( URL_USERHOST_COLON ) {
- LEX_ALNUM ( URL_USERHOST_ALNUM2 ),
- LEX_END
- },
-
- // this can be URL_PASSWORD or URL_SERVICE
- LEX_STATE_END ( URL_USERHOST_ALNUM2 ) {
- LEX_CHAR ( '@', URL_USERNAME_END ), // it was URL_PASSSWORD
- LEX_CHAR ( '/', URL_PATH_START ), // it was URL_SERVICE
- LEX_CHAR ( '?', URL_OPT_START ), // it was URL_SERVICE
- LEX_DEFAULT ( URL_USERHOST_ALNUM2 ),
- },
-
- // dummy states, covered by URL_USERHOST_ALNUM/URL_USERHOST_COLON/URL_USERHOST_ALNUM2
- LEX_STATE ( URL_USERNAME ) {
- LEX_END
- },
-
- LEX_STATE ( URL_PASSWORD_SEP ) {
- LEX_END
- },
-
- LEX_STATE ( URL_PASSWORD ) {
- LEX_END
- },
-
-
- LEX_STATE_END ( URL_USERNAME_END ) {
- LEX_ALNUM ( URL_HOSTNAME ),
- LEX_CHAR ( ':', URL_SERVICE_SEP ),
- LEX_CHAR ( '/', URL_PATH_START ),
- LEX_CHAR ( '?', URL_OPT_START ),
- LEX_END
- },
-
-
- LEX_STATE_END ( URL_HOSTNAME ) {
- LEX_ALNUM ( URL_HOSTNAME ),
- LEX_CHAR ( ':', URL_SERVICE_SEP ),
- LEX_CHAR ( '/', URL_PATH_START ),
- LEX_CHAR ( '?', URL_OPT_START ),
- LEX_END
- },
-
-
- LEX_STATE ( URL_SERVICE_SEP ) {
- LEX_ALNUM ( URL_SERVICE ),
- LEX_CHAR ( '/', URL_PATH_START ),
- LEX_CHAR ( '?', URL_OPT_START ),
- LEX_END
- },
-
- LEX_STATE_END ( URL_SERVICE ) {
- LEX_ALNUM ( URL_SERVICE ),
- LEX_CHAR ( '/', URL_PATH_START ),
- LEX_CHAR ( '?', URL_OPT_START ),
- LEX_END
- },
-
-
- LEX_STATE_END ( URL_PATH_START ) {
- LEX_CHAR ( '?', URL_OPT_START ),
- LEX_DEFAULT ( URL_PATH ),
- },
-
- LEX_STATE_END ( URL_PATH ) {
- LEX_CHAR ( '?', URL_OPT_START ),
- LEX_DEFAULT ( URL_PATH ),
- },
-
-
- LEX_STATE_END ( URL_OPT_START ) {
- LEX_CHAR ( '&', URL_OPT_SEP ),
- LEX_INVALID ( '=' ),
- LEX_DEFAULT ( URL_OPT_KEY ),
- },
-
- LEX_STATE_END ( URL_OPT_KEY ) {
- LEX_CHAR ( '&', URL_OPT_SEP ),
- LEX_CHAR ( '=', URL_OPT_EQ ),
- LEX_DEFAULT ( URL_OPT_KEY ),
- },
-
- LEX_STATE_END ( URL_OPT_EQ ) {
- LEX_CHAR ( '&', URL_OPT_SEP ),
- LEX_INVALID ( '=' ),
- LEX_DEFAULT ( URL_OPT_VAL ),
- },
-
- LEX_STATE_END ( URL_OPT_VAL ) {
- LEX_CHAR ( '&', URL_OPT_SEP ),
- LEX_INVALID ( '=' ),
- LEX_DEFAULT ( URL_OPT_VAL ),
- },
-
- LEX_STATE_END ( URL_OPT_SEP ) {
- LEX_CHAR ( '&', URL_OPT_SEP ),
- LEX_INVALID ( '=' ),
- LEX_DEFAULT ( URL_OPT_KEY ),
- },
-
- LEX_STATE ( URL_ERROR ) {
- LEX_END
- },
- }
-};
-
-static int url_lex_token (int _this_token, char *token_data, int _next_token, int _prev_token, void *arg) {
- enum url_token this_token = _this_token, next_token = _next_token, prev_token = _prev_token;
- struct url_state *state = arg;
- const char **copy_to = NULL;
-
- (void) prev_token;
-
- switch (this_token) {
- case URL_BEGIN:
- // irrelevant
- break;
-
- case URL_BEGIN_ALNUM:
- switch (next_token) {
- case URL_SCHEME_SEP:
- // store the scheme
- if (_url_append_scheme(state->url, token_data, 1))
- goto error;
-
- break;
-
- case URL_USERNAME_END:
- // store the username
- copy_to = &state->url->username; break;
-
- case URL_PATH_START:
- case URL_OPT_START:
- case LEX_EOF:
- // store the hostname
- copy_to = &state->url->hostname; break;
-
- case URL_BEGIN_COLON:
- // gah...
- copy_to = &state->alnum; break;
-
-
- default:
- FATAL("weird next token");
- }
-
- break;
-
- case URL_BEGIN_COLON:
- switch (next_token) {
- case URL_SCHEME_END_SLASH1:
- // store the schema
- if (_url_append_scheme(state->url, state->alnum, 0))
- goto error;
-
- state->alnum = NULL;
-
- break;
-
- case URL_USERHOST_ALNUM2:
- // gah..
- break;
-
- default:
- FATAL("weird next token");
- }
-
- break;
-
- case URL_SCHEME:
- // store the scheme
- if (_url_append_scheme(state->url, token_data, 1))
- goto error;
-
- break;
-
- case URL_SCHEME_SEP:
- // ignore
- break;
-
- case URL_SCHEME_END_COL:
- case URL_SCHEME_END_SLASH1:
- case URL_SCHEME_END_SLASH2:
- // ignore
- break;
-
- case URL_USERHOST_ALNUM:
- switch (next_token) {
- case URL_USERNAME_END:
- // store the username
- copy_to = &state->url->username; break;
-
- case URL_PATH_START:
- case URL_OPT_START:
- case LEX_EOF:
- // store the hostname
- copy_to = &state->url->hostname; break;
-
- case URL_USERHOST_COLON:
- // gah...
- copy_to = &state->alnum; break;
-
- default:
- FATAL("weird next token");
- }
-
- break;
-
- case URL_USERHOST_COLON:
- // ignore
- break;
-
- case URL_USERHOST_ALNUM2:
- switch (next_token) {
- case URL_USERNAME_END:
- // store the username and password
- state->url->username = state->alnum; state->alnum = NULL;
- copy_to = &state->url->password;
-
- break;
-
- case URL_PATH_START:
- case URL_OPT_START:
- case LEX_EOF:
- // store the hostname and service
- state->url->hostname = state->alnum; state->alnum = NULL;
- copy_to = &state->url->service; break;
-
- default:
- FATAL("weird next token");
- }
-
- break;
-
- case URL_USERNAME:
- case URL_PASSWORD_SEP:
- case URL_PASSWORD:
- FATAL("these should be overshadowed");
-
- case URL_USERNAME_END:
- // ignore
- break;
-
- case URL_HOSTNAME:
- // store
- copy_to = &state->url->hostname; break;
-
- case URL_SERVICE_SEP:
- // ignore
- break;
-
- case URL_SERVICE:
- // store
- copy_to = &state->url->service; break;
-
- case URL_PATH_START:
- // ignore
- break;
-
- case URL_PATH:
- // store
- copy_to = &state->url->path; break;
-
- case URL_OPT_START:
- // ignore
- break;
-
- case URL_OPT_KEY:
- // store
- if (_url_append_opt_key(state->url, token_data))
- goto error;
-
- break;
-
- case URL_OPT_EQ:
- // ignore
- break;
-
- case URL_OPT_VAL:
- // store
- if (_url_append_opt_val(state->url, token_data))
- goto error;
-
- break;
-
- case URL_OPT_SEP:
- // ignore
- break;
-
- default:
- ERROR("invalid token");
- }
-
- if (copy_to) {
- // copy the token data
- if ((*copy_to = strdup(token_data)) == NULL)
- ERROR("strdup");
- }
-
- // good
- return 0;
-
-error:
- DEBUG("token: %s -> %s -> %s: %s",
- LEX_STATE_NAME(&url_lex, prev_token), LEX_STATE_NAME(&url_lex, this_token), LEX_STATE_NAME(&url_lex, next_token),
- token_data
- );
- return -1;
-}
-
-
-int url_parse (struct url *url, const char *text) {
- struct url_state state; ZINIT(state);
- int ret;
-
- // set up state
- state.url = url;
-
- // parse it
- if ((ret = lexer(&url_lex, text, &state)))
- ERROR("invalid URL");
-
- // success
- return 0;
-
-error:
- return -1;
-}
-
-static void _url_dump_part (const char *field, const char *val, FILE *stream) {
- if (val) {
- fprintf(stream, "%s=%s ", field, val);
- }
-}
-
-void url_dump (const struct url *url, FILE *stream) {
- int i;
-
- if (url->schema) {
- fprintf(stream, "schema=(");
-
- for (i = 0; i < url->schema->count; i++) {
- if (i > 0)
- fprintf(stream, ",");
-
- fprintf(stream, "%s", url->schema->list[i]);
- }
-
- fprintf(stream, ") ");
- }
-
- _url_dump_part("username", url->username, stream);
- _url_dump_part("password", url->password, stream);
- _url_dump_part("hostname", url->hostname, stream);
- _url_dump_part("service", url->service, stream);
- _url_dump_part("path", url->path, stream);
-
- if (url->opts) {
- fprintf(stream, "opts: ");
-
- for (i = 0; i < url->opts->count; i++) {
- fprintf(stream, "%s=%s ", url->opts->list[i].key, url->opts->list[i].value);
- }
- }
-
- fprintf(stream, "\n");
-}
-
--- a/src/lib/url.h Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,63 +0,0 @@
-#ifndef LIB_URL_H
-#define LIB_URL_H
-
-/*
- * A trivial parser for simple URLs
- *
- * [ <scheme> [ "+" <scheme> [ ... ] ] "://" ] [ <username> [ ":" <password> ] "@" ] [ <hostname>Â ] [ ":" <service> ] [ "/" <path> ] [ "?" [ <key> [ "=" <value> ] ] [ "&" [ <key> [ "=" <value> ] ] [ ... ] ]
- *
- * example.com
- * tcp://example.com:7348/
- * psql://postgres@localhost/test_db?charset=utf8
- *
- */
-
-#include <sys/types.h>
-#include <stdio.h>
-
-/*
- * The schema
- */
-struct url_schema {
- size_t count;
- const char *list[];
-};
-
-/*
- * The options at the end
- */
-struct url_opts {
- size_t count;
- struct url_opt {
- const char *key;
- const char *value;
- } list[];
-};
-
-/*
- * A parsed URL
- */
-struct url {
- struct url_schema *schema;
- const char *username;
- const char *password;
- const char *hostname;
- const char *service;
- const char *path;
- struct url_opts *opts;
-};
-
-/*
- * Parse the given `text` as an URL, returning the result in `url`. Optional fields that are missing in the text will
- * cause those values to be returned unmodified.
- *
- * Returns zero if the url was valid and was parsed, nonzero if it was invalid.
- */
-int url_parse (struct url *url, const char *text);
-
-/*
- * Prints a url in a debug-output format.
- */
-void url_dump (const struct url *url, FILE *stream);
-
-#endif /* LIB_URL_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/query.c Sun Mar 08 01:33:45 2009 +0200
@@ -0,0 +1,256 @@
+
+#include "internal.h"
+#include "lib/error.h"
+#include "lib/misc.h"
+
+#include <stdlib.h>
+#include <assert.h>
+
+/*
+ * Initialize params->types/values/lengths/formats, params->count, params->result_format based on the given args
+ */
+static int _evsql_query_params_init_pq (struct evsql_query_params_pq *params, size_t param_count, enum evsql_item_format result_format) {
+ // set count
+ params->count = param_count;
+
+ // allocate vertical storage for the parameters
+ if (0
+
+ || !(params->types = calloc(param_count, sizeof(Oid)))
+ || !(params->values = calloc(param_count, sizeof(char *)))
+ || !(params->lengths = calloc(param_count, sizeof(int)))
+ || !(params->formats = calloc(param_count, sizeof(int)))
+ || !(params->item_vals = calloc(param_count, sizeof(union evsql_item_value)))
+ )
+ ERROR("calloc");
+
+ // result format
+ switch (result_format) {
+ case EVSQL_FMT_TEXT:
+ params->result_format = 0; break;
+
+ case EVSQL_FMT_BINARY:
+ params->result_format = 1; break;
+
+ default:
+ FATAL("params.result_fmt: %d", result_format);
+ }
+
+ // good
+ return 0;
+
+error:
+ return -1;
+}
+
+struct evsql_query *evsql_query (struct evsql *evsql, struct evsql_trans *trans, const char *command, evsql_query_cb query_fn, void *cb_arg) {
+ struct evsql_query *query = NULL;
+
+ // alloc new query
+ if ((query = _evsql_query_new(evsql, trans, query_fn, cb_arg)) == NULL)
+ goto error;
+
+ // just execute the command string directly
+ if (_evsql_query_enqueue(evsql, trans, query, command))
+ goto error;
+
+ // ok
+ return query;
+
+error:
+ _evsql_query_free(query);
+
+ return NULL;
+}
+
+struct evsql_query *evsql_query_params (struct evsql *evsql, struct evsql_trans *trans,
+ const char *command, const struct evsql_query_params *params,
+ evsql_query_cb query_fn, void *cb_arg
+) {
+ struct evsql_query *query = NULL;
+ const struct evsql_item *param;
+ size_t count = 0, idx;
+
+ // alloc new query
+ if ((query = _evsql_query_new(evsql, trans, query_fn, cb_arg)) == NULL)
+ goto error;
+
+ // count the params
+ for (param = params->list; param->info.type; param++)
+ count++;
+
+ // initialize params
+ _evsql_query_params_init_pq(&query->params, count, params->result_format);
+
+ // transform
+ for (param = params->list, idx = 0; param->info.type; param++, idx++) {
+ // set for NULLs, otherwise not
+ query->params.types[idx] = (param->bytes || param->flags.has_value) ? 0 : EVSQL_PQ_ARBITRARY_TYPE_OID;
+
+ // scalar values
+ query->params.item_vals[idx] = param->value;
+
+ // values
+ // point this at the value stored in the item_vals union if flagged as such
+ query->params.values[idx] = param->flags.has_value ? (const char *) &query->params.item_vals[idx] : param->bytes;
+
+ // lengths
+ query->params.lengths[idx] = param->length;
+
+ // XXX: this assumes that format is FMT_BINARY...
+ query->params.formats[idx] = param->info.format;
+ }
+
+ // execute it
+ if (_evsql_query_enqueue(evsql, trans, query, command))
+ goto error;
+
+#ifdef DEBUG_ENABLED
+ // debug it?
+ DEBUG("evsql.%p: enqueued query=%p on trans=%p", evsql, query, trans);
+ evsql_query_debug(command, params);
+#endif /* DEBUG_ENABLED */
+
+ // ok
+ return query;
+
+error:
+ _evsql_query_free(query);
+
+ return NULL;
+}
+
+struct evsql_query *evsql_query_exec (struct evsql *evsql, struct evsql_trans *trans,
+ const struct evsql_query_info *query_info,
+ evsql_query_cb query_fn, void *cb_arg,
+ ...
+) {
+ va_list vargs;
+ struct evsql_query *query = NULL;
+ const struct evsql_item_info *param;
+ size_t count = 0, idx;
+ err_t err = 1;
+
+ // varargs
+ va_start(vargs, cb_arg);
+
+ // alloc new query
+ if ((query = _evsql_query_new(evsql, trans, query_fn, cb_arg)) == NULL)
+ goto error;
+
+ // count the params
+ for (param = query_info->params; param->type; param++)
+ count++;
+
+ // initialize params
+ _evsql_query_params_init_pq(&query->params, count, EVSQL_FMT_BINARY);
+
+ // transform
+ for (param = query_info->params, idx = 0; param->type; param++, idx++) {
+ // default type to 0 (implicit)
+ query->params.types[idx] = 0;
+
+ // default format to binary
+ query->params.formats[idx] = EVSQL_FMT_BINARY;
+
+ // consume argument
+ switch (param->type) {
+ case EVSQL_TYPE_NULL_: {
+ // explicit type + text fmt
+ query->params.types[idx] = EVSQL_PQ_ARBITRARY_TYPE_OID;
+ query->params.values[idx] = NULL;
+ query->params.lengths[idx] = 0;
+ query->params.formats[idx] = EVSQL_FMT_TEXT;
+ } break;
+
+ case EVSQL_TYPE_BINARY: {
+ struct evsql_item_binary item = va_arg(vargs, struct evsql_item_binary);
+
+ // value + explicit len
+ query->params.values[idx] = item.ptr;
+ query->params.lengths[idx] = item.len;
+ } break;
+
+ case EVSQL_TYPE_STRING: {
+ const char *str = va_arg(vargs, const char *);
+
+ // value + automatic length, text format
+ query->params.values[idx] = str;
+ query->params.lengths[idx] = 0;
+ query->params.formats[idx] = EVSQL_FMT_TEXT;
+ } break;
+
+ case EVSQL_TYPE_UINT16: {
+ // XXX: uint16_t is passed as `int'?
+ uint16_t uval = va_arg(vargs, int);
+
+ if (uval != (int16_t) uval)
+ ERROR("param $%zu: uint16 overflow: %d", idx + 1, uval);
+
+ // network-byte-order value + explicit len
+ query->params.item_vals[idx].uint16 = htons(uval);
+ query->params.values[idx] = (const char *) &query->params.item_vals[idx];
+ query->params.lengths[idx] = sizeof(uint16_t);
+ } break;
+
+ case EVSQL_TYPE_UINT32: {
+ uint32_t uval = va_arg(vargs, uint32_t);
+
+ if (uval != (int32_t) uval)
+ ERROR("param $%zu: uint32 overflow: %ld", idx + 1, (unsigned long) uval);
+
+ // network-byte-order value + explicit len
+ query->params.item_vals[idx].uint32 = htonl(uval);
+ query->params.values[idx] = (const char *) &query->params.item_vals[idx];
+ query->params.lengths[idx] = sizeof(uint32_t);
+ } break;
+
+ case EVSQL_TYPE_UINT64: {
+ uint64_t uval = va_arg(vargs, uint64_t);
+
+ if (uval != (int64_t) uval)
+ ERROR("param $%zu: uint16 overflow: %lld", idx + 1, (unsigned long long) uval);
+
+ // network-byte-order value + explicit len
+ query->params.item_vals[idx].uint64 = htonq(uval);
+ query->params.values[idx] = (const char *) &query->params.item_vals[idx];
+ query->params.lengths[idx] = sizeof(uint64_t);
+ } break;
+
+ default:
+ FATAL("param $%zu: invalid type: %d", idx + 1, param->type);
+ }
+ }
+
+ // execute it
+ if (_evsql_query_enqueue(evsql, trans, query, query_info->sql))
+ goto error;
+
+ // no error, fallthrough for va_end
+ err = 0;
+
+error:
+ // possible cleanup
+ if (err)
+ _evsql_query_free(query);
+
+ // end varargs
+ va_end(vargs);
+
+ // return
+ return err ? NULL : query;
+}
+
+
+void evsql_query_abort (struct evsql_trans *trans, struct evsql_query *query) {
+ assert(query);
+
+ if (trans) {
+ // must be the right query
+ assert(trans->query == query);
+ }
+
+ // just strip the callback and wait for it to complete as normal
+ query->cb_fn = NULL;
+}
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/result.c Sun Mar 08 01:33:45 2009 +0200
@@ -0,0 +1,258 @@
+
+#include "internal.h"
+#include "lib/error.h"
+#include "lib/misc.h"
+
+#include <stdlib.h>
+#include <assert.h>
+
+const char *evsql_result_error (const struct evsql_result *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 *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 *res) {
+ switch (res->evsql->type) {
+ case EVSQL_EVPQ:
+ return PQnfields(res->result.pq);
+
+ default:
+ FATAL("res->evsql->type");
+ }
+}
+
+size_t evsql_result_affected (const struct evsql_result *res) {
+ switch (res->evsql->type) {
+ case EVSQL_EVPQ:
+ // XXX: errors?
+ return strtol(PQcmdTuples(res->result.pq), NULL, 10);
+
+ default:
+ FATAL("res->evsql->type");
+ }
+}
+
+
+int evsql_result_null (const struct evsql_result *res, size_t row, size_t col) {
+ switch (res->evsql->type) {
+ case EVSQL_EVPQ:
+ return PQgetisnull(res->result.pq, row, col);
+
+ default:
+ FATAL("res->evsql->type");
+ }
+}
+
+int evsql_result_field (const struct evsql_result *res, size_t row, size_t col, const char **ptr, size_t *size) {
+ *ptr = NULL;
+
+ switch (res->evsql->type) {
+ case EVSQL_EVPQ:
+ if (PQfformat(res->result.pq, col) != 1)
+ ERROR("[%zu:%zu] PQfformat is not binary: %d", row, col, PQfformat(res->result.pq, 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;
+}
+
+err_t evsql_result_check (struct evsql_result *res) {
+ // so simple...
+ return res->error ? EIO : 0;
+}
+
+err_t evsql_result_begin (struct evsql_result_info *info, struct evsql_result *res) {
+ struct evsql_item_info *col;
+ size_t cols = 0, nrows;
+ err_t err;
+
+ // count info columns
+ for (col = info->columns; col->type; col++)
+ cols++;
+
+ // number of rows returned/affected
+ nrows = evsql_result_rows(res) || evsql_result_affected(res);
+
+ // did the query fail outright?
+ if (res->error)
+ // dump error message
+ NXERROR(err = EIO, evsql_result_error(res));
+
+/*
+ // SELECT/DELETE/UPDATE WHERE didn't match any rows -> ENOENT
+ if (nrows == 0)
+ XERROR(err = ENOENT, "no rows returned/affected");
+*/
+
+ // correct number of columns
+ if (evsql_result_cols(res) != cols)
+ XERROR(err = EINVAL, "wrong number of columns: %zu, should be %zu", evsql_result_cols(res), cols);
+
+ // assign
+ res->info = info;
+ res->row_offset = 0;
+
+ // good
+ return 0;
+
+error:
+ return err;
+
+}
+
+int evsql_result_next (struct evsql_result *res, ...) {
+ va_list vargs;
+ struct evsql_item_info *col;
+ size_t col_idx, row_idx = res->row_offset;
+ err_t err;
+
+ // ensure that evsql_result_begin has been called
+ assert(res->info);
+
+ // check if we're past the end
+ if (row_idx >= evsql_result_rows(res))
+ return 0;
+
+ // varargs
+ va_start(vargs, res);
+
+ for (col = res->info->columns, col_idx = 0; col->type; col++, col_idx++) {
+ const char *value = NULL;
+ size_t length = 0;
+
+ // check for NULLs, then try and get the field value
+ if (evsql_result_null(res, row_idx, col_idx)) {
+ if (!col->flags.null_ok)
+ XERROR(err = EINVAL, "r%zu:c%zu: NULL", row_idx, col_idx);
+
+ } else if (evsql_result_field(res, row_idx, col_idx, &value, &length)) {
+ SERROR(err = EINVAL);
+
+ }
+
+ // read the arg
+ switch (col->type) {
+ case EVSQL_TYPE_BINARY: {
+ struct evsql_item_binary *item_ptr = va_arg(vargs, struct evsql_item_binary *);
+
+ if (value) {
+ item_ptr->ptr = value;
+ item_ptr->len = length;
+ }
+ } break;
+
+ case EVSQL_TYPE_STRING: {
+ const char **str_ptr = va_arg(vargs, const char **);
+
+ if (value) {
+ *str_ptr = value;
+ }
+
+ } break;
+
+ case EVSQL_TYPE_UINT16: {
+ uint16_t *uval_ptr = va_arg(vargs, uint16_t *);
+
+ if (!value) break;
+
+ if (length != sizeof(uint16_t)) XERROR(err = EINVAL, "r%zu:c%zu: wrong size for uint16_t: %zu", row_idx, col_idx, length);
+
+ int16_t sval = ntohs(*((int16_t *) value));
+
+ if (sval < 0) XERROR(err = ERANGE, "r%zu:c%zu: out of range for uint16_t: %hd", row_idx, col_idx, (signed short) sval);
+
+ *uval_ptr = sval;
+ } break;
+
+ case EVSQL_TYPE_UINT32: {
+ uint32_t *uval_ptr = va_arg(vargs, uint32_t *);
+
+ if (!value) break;
+
+ if (length != sizeof(uint32_t)) XERROR(err = EINVAL, "r%zu:c%zu: wrong size for uint32_t: %zu", row_idx, col_idx, length);
+
+ int32_t sval = ntohl(*((int32_t *) value));
+
+ if (sval < 0) XERROR(err = ERANGE, "r%zu:c%zu: out of range for uint32_t: %ld", row_idx, col_idx, (signed long) sval);
+
+ *uval_ptr = sval;
+ } break;
+
+ case EVSQL_TYPE_UINT64: {
+ uint64_t *uval_ptr = va_arg(vargs, uint64_t *);
+
+ if (!value) break;
+
+ if (length != sizeof(uint64_t)) XERROR(err = EINVAL, "r%zu:c%zu: wrong size for uint64_t: %zu", row_idx, col_idx, length);
+
+ int64_t sval = ntohq(*((int64_t *) value));
+
+ if (sval < 0) XERROR(err = ERANGE, "r%zu:c%zu: out of range for uint64_t: %lld", row_idx, col_idx, (signed long long) sval);
+
+ *uval_ptr = sval;
+ } break;
+
+ default:
+ XERROR(err = EINVAL, "r%zu:c%zu: invalid type: %d", row_idx, col_idx, col->type);
+ }
+ }
+
+ // advance row index
+ res->row_offset++;
+
+ // row handled succesfully
+ return 1;
+
+error:
+ return -err;
+}
+
+void evsql_result_end (struct evsql_result *res) {
+ // not much more to it...
+ evsql_result_free(res);
+}
+
+void evsql_result_free (struct evsql_result *res) {
+ // note that the result itself might be NULL...
+ // in the case of internal-error results, these may be free'd multiple times!
+ switch (res->evsql->type) {
+ case EVSQL_EVPQ:
+ if (res->result.pq)
+ return PQclear(res->result.pq);
+
+ default:
+ FATAL("res->evsql->type");
+ }
+}
+
+
--- a/src/simple.c Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,283 +0,0 @@
-#include <string.h>
-#include <errno.h>
-#include <stdlib.h>
-#include <assert.h>
-
-#include "simple.h"
-#include "dirbuf.h"
-#include "lib/log.h"
-#include "lib/math.h"
-#include "lib/misc.h"
-
-struct simple_fs {
- const struct simple_node *inode_table;
-
- size_t inode_count;
-};
-
-/*
- * Used for stat/entry timeouts... not sure how this should really be set.
- */
-#define CACHE_TIMEOUT 1.0
-
-static void _simple_stat (struct stat *stat, const struct simple_node *node) {
- stat->st_ino = node->inode;
- stat->st_mode = node->mode_type | node->mode_perm;
- stat->st_nlink = 1;
- stat->st_size = node->data ? strlen(node->data) : 0;
-}
-
-/*
- * Fetch the simple_node for the given inode.
- *
- * Returns NULL for invalid inodes.
- */
-static const struct simple_node *_simple_get_ino (struct simple_fs *fs, fuse_ino_t ino) {
- // make sure it's a valid inode
- if (ino < 1 || ino > fs->inode_count) {
- WARNING("invalid inode=%zu", ino);
- return NULL;
- }
-
- // return the node
- return fs->inode_table + (ino - 1);
-}
-
-static void simple_lookup (fuse_req_t req, fuse_ino_t parent, const char *name) {
- struct simple_fs *fs = fuse_req_userdata(req);
- const struct simple_node *node;
- struct fuse_entry_param e; ZINIT(e);
- int err;
-
- INFO("[simple.lookup %p] parent=%lu, name=`%s'", fs, parent, name);
-
- // find the matching node
- for (node = fs->inode_table; node->inode > 0; node++) {
- if (node->parent == parent && strcmp(node->name, name) == 0)
- break;
-
- }
-
- // did we find it?
- if (node->inode) {
- // set up the entry
- e.ino = node->inode;
- e.generation = 0x01;
- _simple_stat(&e.attr, node);
- e.attr_timeout = CACHE_TIMEOUT;
- e.entry_timeout = CACHE_TIMEOUT;
-
- // reply
- if ((err = fuse_reply_entry(req, &e)))
- EERROR(err, "fuse_reply_entry");
-
- } else {
- // not found
- err = ENOENT;
- goto error;
- }
-
- // success
- return;
-
-error:
- if ((err = fuse_reply_err(req, err)))
- EWARNING(err, "fuse_reply_err");
-}
-
-static void simple_getattr (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) {
- struct simple_fs *fs = fuse_req_userdata(req);
- const struct simple_node *node;
- struct stat stbuf; ZINIT(stbuf);
- int err;
-
- INFO("[simple.getattr %p] ino=%lu", fs, ino);
-
- // look up the node
- if ((node = _simple_get_ino(fs, ino)) == NULL)
- EERROR(err = EINVAL, "bad inode");
-
- // set up the stbuf
- _simple_stat(&stbuf, node);
-
- // reply
- if ((err = fuse_reply_attr(req, &stbuf, CACHE_TIMEOUT)))
- EERROR(err, "fuse_reply_attr");
-
- // suceccss
- return;
-
-error:
- if ((err = fuse_reply_err(req, err)))
- EWARNING(err, "fuse_reply_err");
-}
-
-static void simple_readlink (fuse_req_t req, fuse_ino_t ino) {
- struct simple_fs *fs = fuse_req_userdata(req);
- const struct simple_node *node;
- int err;
-
- INFO("[simple.readlink %p] ino=%lu", fs, ino);
-
- // look up the node
- if ((node = _simple_get_ino(fs, ino)) == NULL)
- EERROR(err = EINVAL, "bad inode");
-
- // check that it's a symlink
- if (node->mode_type != S_IFLNK)
- EERROR(err = EINVAL, "bad mode");
-
- // return the contents
- if ((err = fuse_reply_readlink(req, node->data)))
- EERROR(err, "fuse_reply_readlink");
-
- // suceccss
- return;
-
-error:
- if ((err = fuse_reply_err(req, err)))
- EWARNING(err, "fuse_reply_err");
-
-}
-
-static void simple_readdir (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi) {
- struct simple_fs *fs = fuse_req_userdata(req);
- const struct simple_node *dir_node, *node;
- struct dirbuf buf;
- int err;
-
- INFO("[simple.readdir] ino=%lu, size=%zu, off=%zu, fi=%p", ino, size, off, fi);
-
- // look up the inode
- if ((dir_node = _simple_get_ino(fs, ino)) == NULL)
- EERROR(err = EINVAL, "bad inode");
-
- // check that it's a dir
- if (dir_node->mode_type != S_IFDIR)
- EERROR(err = ENOTDIR, "bad mode");
-
- // fill in the dirbuf
- if (dirbuf_init(&buf, size, off))
- ERROR("failed to init dirbuf");
-
- // add . and ..
- // we set the next offset to 2, because all dirent offsets will be larger than that
- err = dirbuf_add(req, &buf, 0, 1, ".", dir_node->inode, S_IFDIR )
- || dirbuf_add(req, &buf, 1, 2, "..", dir_node->inode, S_IFDIR );
-
- if (err != 0)
- EERROR(err, "failed to add . and .. dirents");
-
- // look up all child nodes
- for (node = fs->inode_table; node->inode; node++) {
- // skip non-children
- if (node->parent != dir_node->inode)
- continue;
-
- // child node offsets are just inode + 2
- if ((err = dirbuf_add(req, &buf, node->inode + 2, node->inode + 3, node->name, node->inode, node->mode_type)) < 0)
- EERROR(err, "failed to add dirent for inode=%lu", node->inode);
-
- // stop if it's full
- if (err > 0)
- break;
- }
-
- // send it
- if ((err = -dirbuf_done(req, &buf)))
- EERROR(err, "failed to send buf");
-
- // success
- return;
-
-error:
- if ((err = fuse_reply_err(req, err)))
- EWARNING(err, "fuse_reply_err");
-}
-
-static void simple_read (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi) {
- struct simple_fs *fs = fuse_req_userdata(req);
- const struct simple_node *node;
- int err ;
-
- // fi is unused
- (void) fi;
-
- INFO("[simple.read] ino=%lu, size=%zu, off=%zu, fi=%p", ino, size, off, fi);
-
- // look up the inode
- if ((node = _simple_get_ino(fs, ino)) == NULL)
- EERROR(err = EINVAL, "bad inode");
-
- // check that it's a dir
- if (node->mode_type != S_IFREG)
- EERROR(err = (node->mode_type == S_IFDIR ? EISDIR : EINVAL), "bad mode");
-
- // seek past EOF?
- if (off >= strlen(node->data)) {
- // offset is out-of-file, so return EOF
- if ((err = fuse_reply_buf(req, NULL, 0)))
- EERROR(err, "fuse_reply_buf size=0");
-
- } else {
- // reply with the requested file data
- if ((err = fuse_reply_buf(req, node->data + off, MIN(strlen(node->data) - off, size))))
- EERROR(err, "fuse_reply_buf buf=%p + %zu, size=MIN(%zu, %zu)", node->data, off, strlen(node->data) - off, size);
- }
-
- // success
- return;
-
-error:
- if ((err = fuse_reply_err(req, err)))
- EWARNING(err, "fuse_reply_err");
-}
-
-
-/*
- * Define our fuse_lowlevel_ops struct.
- */
-static struct fuse_lowlevel_ops simple_ops = {
- .lookup = simple_lookup,
-
- .getattr = simple_getattr,
-
- .readlink = simple_readlink,
-
- .readdir = simple_readdir,
-
- .read = simple_read,
-};
-
-struct fuse_lowlevel_ops *simple_init () {
- return &simple_ops;
-}
-
-struct simple_fs *simple_new (const struct simple_node *node_list) {
- struct simple_fs *fs = NULL;
- const struct simple_node *node;
-
- // generate
- if ((fs = calloc(1, sizeof(*fs))) == NULL)
- ERROR("calloc");
-
- // remember node_list
- fs->inode_count = 0;
- fs->inode_table = node_list;
-
- // validate it
- for (node = fs->inode_table; node->inode; node++) {
- // update inode_count
- fs->inode_count++;
-
- // check that parent is valid
- assert(node->inode == fs->inode_count);
- assert(node->parent < node->inode);
- }
-
- // success
- return fs;
-
-error:
- return NULL;
-}
--- a/src/simple.h Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,46 +0,0 @@
-#ifndef SIMPLE_H
-#define SIMPLE_H
-
-/*
- * A simple static in-memory filesystem structure.
- */
-
-#include "evfuse.h"
-
-/*
- * A simple file/dir.
- */
-struct simple_node {
- // inode number
- fuse_ino_t inode;
-
- // mode
- mode_t mode_type;
- mode_t mode_perm;
-
- // parent node
- fuse_ino_t parent;
-
- // name
- const char *name;
-
- // data
- const char *data;
-};
-
-/*
- * General information.
- */
-struct simple_fs;
-
-/*
- * Initialize simple, and get the fuse_lowlevel_ops.
- */
-struct fuse_lowlevel_ops *simple_init ();
-
-/*
- * Create a new simple_fs.
- */
-struct simple_fs *simple_new (const struct simple_node *node_list);
-
-#endif /* SIMPLE_H */
--- a/src/simple_hello.c Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,77 +0,0 @@
-
-#include <event2/event.h>
-
-#include "lib/log.h"
-#include "lib/signals.h"
-#include "evfuse.h"
-#include "simple.h"
-
-static struct hello {
- struct event_base *ev_base;
-
- struct signals *signals;
-
- struct simple_fs *fs;
-
- struct evfuse *ev_fuse;
-
-} ctx;
-
-static struct simple_node node_list[] = {
- { 1, S_IFDIR, 0555, 0, NULL, NULL },
- { 2, S_IFREG, 0444, 1, "hello", "Hello World!\n" },
- { 3, S_IFREG, 0444, 1, "foo", "Foo\n" },
- { 4, S_IFREG, 0444, 1, "bar", "Bar\n" },
- { 5, S_IFDIR, 0555, 1, "test", NULL },
- { 6, S_IFREG, 0444, 5, "file0", "data0\n" },
- { 7, S_IFREG, 0444, 5, "file1", "data1\n" },
- { 8, S_IFLNK, 0444, 1, "lnk0", "test/file0" },
- { 0, 0, 0, 0, NULL, NULL },
-};
-
-int main (int argc, char **argv) {
- struct fuse_args fuse_args = FUSE_ARGS_INIT(argc, argv);
-
- // 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");
-
- // setup fs
- if ((ctx.fs = simple_new(node_list)) == NULL)
- ERROR("simple_new");
-
- // open fuse
- if ((ctx.ev_fuse = evfuse_new(ctx.ev_base, &fuse_args, simple_init(), ctx.fs)) == 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_free(ctx.ev_fuse);
-
-/*
- if (ctx.fs)
- simple_close(ctx.fs);
-*/
-
- 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/url_test.c Sat Dec 13 20:58:27 2008 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,198 +0,0 @@
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-
-#include "lib/url.h"
-
-#define FAIL(...) do { printf("FAIL: "); printf(__VA_ARGS__); printf("\n"); return -1; } while (0)
-
-struct url_schema
- basic_http = { 1, { "http" } },
- svn_ssh = { 2, { "svn", "ssh" } },
- schema_unix = { 1, { "unix" } }
- ;
-
-struct url_opts
- opts_single = { 1, { { "key0", "val0" } } },
- opts_multi = { 2, { { "key0", "val0" }, { "key1", "val1" } } },
- opts_nullval = { 1, { { "keyN", NULL } } }
- ;
-
-struct url_test {
- const char *url;
- const struct url expected;
-} url_tests[] = {
- { "localhost:http", {
- NULL, NULL, NULL, "localhost", "http", NULL, NULL
- } },
-
- { "http://example.com/path", {
- &basic_http, NULL, NULL, "example.com", NULL, "path", NULL
- } },
-
- { "svn+ssh://user:passwd@someplace:someport/something", {
- &svn_ssh, "user", "passwd", "someplace", "someport", "something"
- } },
-
- { "user@:service/", {
- NULL, "user", NULL, NULL, "service", NULL
- } },
-
- { "unix:////tmp/foo.sock", {
- &schema_unix, NULL, NULL, NULL, NULL, "/tmp/foo.sock"
- } },
-
- { "unix:///tmp/foo.sock", {
- &schema_unix, NULL, NULL, NULL, NULL, "tmp/foo.sock"
- } },
-
- { "/tmp/foo.sock", {
- NULL, NULL, NULL, NULL, NULL, "tmp/foo.sock"
- } },
-
- { "?key0=val0", {
- NULL, NULL, NULL, NULL, NULL, NULL, &opts_single
- } },
-
- { "http://foo.com/index.php?key0=val0&key1=val1", {
- &basic_http, NULL, NULL, "foo.com", NULL, "index.php", &opts_multi
- } },
-
- { "example.org:81/?keyN", {
- NULL, NULL, NULL, "example.org", "81", NULL, &opts_nullval
- } },
-
- { NULL, { } },
-};
-
-int cmp_url_str (const char *field, const char *test, const char *real) {
- if (!test) {
- if (real)
- FAIL("%s shouldn't be present", field);
-
- } else if (!real) {
- FAIL("%s is missing", field);
-
- } else {
- if (strcmp(test, real) != 0)
- FAIL("%s differs: %s -> %s", field, test, real);
- }
-
- // ok
- return 0;
-}
-
-int cmp_url (const struct url *test, const struct url *real) {
- int i;
-
- // test schema
- if (!test->schema) {
- if (real->schema)
- FAIL("test has no schema, but real does");
-
- } else if (!real->schema) {
- FAIL("test has a schema, but real doesn't");
-
- } else {
- if (test->schema->count != test->schema->count)
- FAIL("inconsistent scheme count");
-
- for (i = 0; i < test->schema->count; i++) {
- if (strcmp(test->schema->list[i], real->schema->list[i]) != 0)
- FAIL("differing scheme #%d", i);
- }
- }
-
- // test username
- if (cmp_url_str("username", test->username, real->username))
- goto error;
-
- // test password
- if (cmp_url_str("password", test->password, real->password))
- goto error;
-
- // test hostname
- if (cmp_url_str("hostname", test->hostname, real->hostname))
- goto error;
-
- // test service
- if (cmp_url_str("service", test->service, real->service))
- goto error;
-
- // test path
- if (cmp_url_str("path", test->path, real->path))
- goto error;
-
- // test query
- if (!test->opts) {
- if (real->opts)
- FAIL("test has no opts, but real does");
-
- } else if (!real->opts) {
- FAIL("test has opts, but real doesn't");
-
- } else {
- if (test->opts->count != test->opts->count)
- FAIL("inconsistent opts count");
-
- for (i = 0; i < test->opts->count; i++) {
- if (cmp_url_str("opt key", test->opts->list[i].key, real->opts->list[i].key))
- FAIL("differing opt key #%d", i);
-
- if (cmp_url_str("opt value", test->opts->list[i].value, real->opts->list[i].value))
- FAIL("differing opt value #%d", i);
- }
- }
-
- // ok
- return 0;
-
-error:
- return -1;
-}
-
-void usage (const char *exec_name) {
- printf("Usage: %s\n\n\tNo arguments are accepted\n", exec_name);
-
- exit(EXIT_FAILURE);
-}
-
-int main (int argc, char **argv) {
- const struct url_test *test;
- struct url url;
-
- if (argc > 1)
- usage(argv[0]);
-
- // run the tests
- for (test = url_tests; test->url; test++) {
- // first output the URL we are handling...
- printf("%-80s - ", test->url);
- fflush(stdout);
-
- // parse the URL
- memset(&url, 0, sizeof(url));
-
- if (url_parse(&url, test->url)) {
- printf("FATAL: url_parse failed\n");
- return EXIT_FAILURE;
- }
-
- // compare it
- if (cmp_url(&test->expected, &url)) {
- printf("\texpected: ");
- url_dump(&test->expected, stdout);
-
- printf("\tresult: ");
- url_dump(&url, stdout);
-
- } else {
- printf("OK\n\t");
- url_dump(&url, stdout);
- }
-
- printf("\n");
- }
-}
-
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/util.c Sun Mar 08 01:33:45 2009 +0200
@@ -0,0 +1,280 @@
+#include <stdlib.h>
+#include <assert.h>
+
+#include "internal.h"
+#include "lib/log.h"
+#include "lib/misc.h"
+
+#define _PARAM_TYPE_CASE(typenam) case EVSQL_TYPE_ ## typenam: return #typenam
+
+#define _PARAM_VAL_BUF_MAX 120
+#define _PARAM_VAL_CASE(typenam, ...) case EVSQL_TYPE_ ## typenam: if (item->bytes) ret = snprintf(buf, _PARAM_VAL_BUF_MAX, __VA_ARGS__); else return "(null)"; break
+
+const char *evsql_item_type (const struct evsql_item_info *item_info) {
+ switch (item_info->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_item_val (const struct evsql_item *item) {
+ static char buf[_PARAM_VAL_BUF_MAX];
+ int ret;
+
+ switch (item->info.type) {
+ _PARAM_VAL_CASE (INVALID, "???" );
+ _PARAM_VAL_CASE (NULL_, "(null)" );
+ _PARAM_VAL_CASE (BINARY, "%zu:%s", item->length, "... " );
+ _PARAM_VAL_CASE (STRING, "%s", item->bytes );
+ _PARAM_VAL_CASE (UINT16, "%hu", (unsigned short int) ntohs(item->value.uint16) );
+ _PARAM_VAL_CASE (UINT32, "%lu", (unsigned long int) ntohl(item->value.uint32) );
+ _PARAM_VAL_CASE (UINT64, "%llu", (unsigned long long int) ntohq(item->value.uint64) );
+ default: return "???";
+ }
+
+ return buf;
+}
+
+int evsql_params_clear (struct evsql_query_params *params) {
+ struct evsql_item *param;
+
+ for (param = params->list; param->info.type; param++) {
+ param->bytes = NULL;
+ param->flags.has_value = 0;
+ }
+
+ return 0;
+}
+
+int evsql_param_null (struct evsql_query_params *params, size_t param) {
+ struct evsql_item *p = ¶ms->list[param];
+
+ p->bytes = NULL;
+ p->flags.has_value = 0;
+
+ return 0;
+}
+
+int evsql_param_binary (struct evsql_query_params *params, size_t param, const char *ptr, size_t len) {
+ struct evsql_item *p = ¶ms->list[param];
+
+ assert(p->info.type == EVSQL_TYPE_BINARY);
+
+ p->bytes = ptr;
+ p->length = len;
+
+ return 0;
+}
+
+int evsql_param_string (struct evsql_query_params *params, size_t param, const char *ptr) {
+ struct evsql_item *p = ¶ms->list[param];
+
+ assert(p->info.type == EVSQL_TYPE_STRING);
+
+ // XXX: hmm...
+ p->info.format = EVSQL_FMT_TEXT;
+
+ p->bytes = ptr;
+ p->length = 0;
+
+ return 0;
+}
+
+int evsql_param_uint16 (struct evsql_query_params *params, size_t param, uint16_t uval) {
+ struct evsql_item *p = ¶ms->list[param];
+
+ assert(p->info.type == EVSQL_TYPE_UINT16);
+
+ p->value.uint16 = htons(uval);
+ p->length = sizeof(uval);
+ p->flags.has_value = 1;
+
+ return 0;
+}
+
+int evsql_param_uint32 (struct evsql_query_params *params, size_t param, uint32_t uval) {
+ struct evsql_item *p = ¶ms->list[param];
+
+ assert(p->info.type == EVSQL_TYPE_UINT32);
+
+ p->value.uint32 = htonl(uval);
+ p->length = sizeof(uval);
+ p->flags.has_value = 1;
+
+ return 0;
+}
+
+void evsql_query_debug (const char *sql, const struct evsql_query_params *params) {
+ const struct evsql_item *param;
+ size_t param_count = 0, idx = 0;
+
+ // count the params
+ for (param = params->list; param->info.type; param++)
+ param_count++;
+
+ DEBUG("sql: %s", sql);
+ DEBUG("params: %zu", param_count);
+
+ for (param = params->list; param->info.type; param++) {
+ DEBUG("\t%2zu : %8s = %s", ++idx, evsql_item_type(¶m->info), evsql_item_val(param));
+ }
+}
+
+int evsql_result_binary (const struct evsql_result *res, size_t row, size_t col, const char **ptr, size_t *size, bool 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));
+
+ *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_binlen (const struct evsql_result *res, size_t row, size_t col, const char **ptr, size_t size, int nullok) {
+ size_t real_size = 0;
+
+ 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);
+
+ return 0;
+
+error:
+ return -1;
+}
+
+int evsql_result_string (const struct evsql_result *res, size_t row, size_t col, const char **ptr, int 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 *res, size_t row, size_t col, uint16_t *uval, int nullok) {
+ const char *data;
+ int16_t sval;
+
+ if (evsql_result_binlen(res, row, col, &data, sizeof(*uval), nullok))
+ goto error;
+
+ if (!data)
+ return 0;
+
+ 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 *res, size_t row, size_t col, uint32_t *uval, int nullok) {
+ const char *data;
+ int32_t sval;
+
+ if (evsql_result_binlen(res, row, col, &data, sizeof(*uval), nullok))
+ goto error;
+
+ if (!data)
+ return 0;
+
+ 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 *res, size_t row, size_t col, uint64_t *uval, int nullok) {
+ const char *data;
+ int64_t sval;
+
+ if (evsql_result_binlen(res, row, col, &data, sizeof(*uval), nullok))
+ goto error;
+
+ if (!data)
+ return 0;
+
+ sval = ntohq(*(int64_t *) data);
+
+ if (sval < 0)
+ ERROR("negative value for unsigned: %ld", sval);
+
+ *uval = sval;
+
+ return 0;
+
+error:
+ return nullok ? 0 : -1;
+}
+
+const char *evsql_conn_error (struct evsql_conn *conn) {
+ switch (conn->evsql->type) {
+ case EVSQL_EVPQ:
+ if (!conn->engine.evpq)
+ return "unknown error (no conn)";
+
+ return evpq_error_message(conn->engine.evpq);
+
+ default:
+ FATAL("res->evsql->type");
+ }
+}
+
+const char *evsql_trans_error (struct evsql_trans *trans) {
+ if (trans->conn == NULL)
+ return "unknown error (no trans conn)";
+
+ return evsql_conn_error(trans->conn);
+}
+