summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoel Klinghed <the_jk@yahoo.com>2015-02-20 00:12:45 +0100
committerJoel Klinghed <the_jk@yahoo.com>2015-02-20 00:12:45 +0100
commitb9c7dd3aa8b77d9812455437411de5c7640cce95 (patch)
treec261ac5af10163b220763b9819cbc00dff62558d
Initial DB API with SQLite3 backend
-rw-r--r--.gitignore22
-rw-r--r--Makefile.am7
-rw-r--r--configure.ac65
-rw-r--r--m4/.gitignore5
-rw-r--r--m4/ax_append_compile_flags.m465
-rw-r--r--m4/ax_append_flag.m469
-rw-r--r--m4/ax_cflags_warn_all.m4122
-rw-r--r--m4/ax_check_compile_flag.m474
-rw-r--r--m4/pkg.m4158
-rw-r--r--src/.gitignore5
-rw-r--r--src/Makefile.am11
-rw-r--r--src/common.hh6
-rw-r--r--src/db.cc60
-rw-r--r--src/db.hh426
-rw-r--r--src/sqlite3_db.cc716
-rw-r--r--src/sqlite3_db.hh17
16 files changed, 1828 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1ea0379
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,22 @@
+.deps
+.libs
+*.o
+*.lo
+Makefile
+Makefile.in
+/aclocal.m4
+/autom4te.cache
+/compile
+/config.guess
+/config.log
+/config.rpath
+/config.status
+/config.sub
+/configure
+/depcomp
+/install-sh
+/ltmain.sh
+/libtool
+/missing
+/mkinstalldirs
+/test-driver
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..62391b5
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,7 @@
+ACLOCAL_AMFLAGS = -I m4
+
+MAINTAINERCLEANFILES = Makefile.in aclocal.m4 config.guess config.h.in \
+ config.sub configure depcomp install-sh ltmain.sh \
+ missing config.rpath mkinstalldirs compile
+
+SUBDIRS = src
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..9f1a200
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,65 @@
+AC_INIT([stuff], [0.1], [the_jk@yahoo.com])
+AC_CONFIG_MACRO_DIR([m4])
+
+AM_INIT_AUTOMAKE([dist-bzip2 foreign])
+AM_SILENT_RULES([yes])
+LT_INIT([disable-shared])
+AC_PROG_CXX
+AM_PROG_CC_C_O
+
+AC_LANG([C++])
+
+DEFINES=
+AX_APPEND_COMPILE_FLAGS([-fno-rtti -fno-exceptions],DEFINES)
+
+# Test c++11
+OLDCXXFLAGS="$CXXFLAGS"
+# Check if it just works with -std=c++11
+# The code below was choosen because if you mix a compiler that is C++11
+# compatible with a libc++ that isn't fully (like clang 3.3 with gcc 4.6
+# libstdcxx) you get errors because of a missing copy constructor for
+# std::shared_ptr. Add more tests as we find them.
+CXXFLAGS="-std=c++11 $CXXFLAGS"
+AC_MSG_CHECKING([for C++11 using (-std=c++11)])
+AC_LINK_IFELSE([AC_LANG_PROGRAM([[
+#include <memory>
+]],[[
+std::shared_ptr<int> i(new int(5));
+std::shared_ptr<int> j(i);
+]])],
+ [AC_MSG_RESULT([yes])
+ DEFINES="$DEFINES -std=c++11"],
+ [AC_MSG_RESULT([no])
+ CXXFLAGS="-std=c++11 -stdlib=libc++ $CXXFLAGS"
+ AC_MSG_CHECKING([for C++11 using (-std=c++11 -stdlib=libc++)])
+ AC_LINK_IFELSE([AC_LANG_PROGRAM([[
+#include <memory>
+]],[[
+std::shared_ptr<int> i(new int(5));
+std::shared_ptr<int> j(i);
+]])],
+ [AC_MSG_RESULT([yes])
+ DEFINES="$DEFINES -std=c++11 -stdlib=libc++"],
+ [AC_MSG_RESULT([no])
+ AC_MSG_ERROR([No working C++11 support])])])
+CXXFLAGS="$OLDCXXFLAGS"
+
+AX_CXXFLAGS_WARN_ALL([DEFINES])
+AX_APPEND_COMPILE_FLAGS([-Wextra -Wno-unused-parameter],DEFINES)
+AC_ARG_ENABLE([debug], AC_HELP_STRING([compile with debug options]),
+ if test "x$enableval" = "xyes"; then
+ DEFINES="$DEFINES -g -DDEBUG"
+ else
+ DEFINES="$DEFINES -DNDEBUG"
+ fi)
+AC_SUBST([DEFINES])
+
+# SQLite3
+
+# 3.6.5 so that sqlite3_changes() return correct values for DELETE
+PKG_CHECK_MODULES([SQLITE3],[sqlite3 >= 3.6.5])
+
+# Finish up
+
+AC_CONFIG_HEADERS([src/config.h])
+AC_OUTPUT([Makefile src/Makefile])
diff --git a/m4/.gitignore b/m4/.gitignore
new file mode 100644
index 0000000..94e6f26
--- /dev/null
+++ b/m4/.gitignore
@@ -0,0 +1,5 @@
+/libtool.m4
+/ltoptions.m4
+/ltsugar.m4
+/ltversion.m4
+/lt~obsolete.m4
diff --git a/m4/ax_append_compile_flags.m4 b/m4/ax_append_compile_flags.m4
new file mode 100644
index 0000000..1f8e708
--- /dev/null
+++ b/m4/ax_append_compile_flags.m4
@@ -0,0 +1,65 @@
+# ===========================================================================
+# http://www.gnu.org/software/autoconf-archive/ax_append_compile_flags.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+# AX_APPEND_COMPILE_FLAGS([FLAG1 FLAG2 ...], [FLAGS-VARIABLE], [EXTRA-FLAGS])
+#
+# DESCRIPTION
+#
+# For every FLAG1, FLAG2 it is checked whether the compiler works with the
+# flag. If it does, the flag is added FLAGS-VARIABLE
+#
+# If FLAGS-VARIABLE is not specified, the current language's flags (e.g.
+# CFLAGS) is used. During the check the flag is always added to the
+# current language's flags.
+#
+# If EXTRA-FLAGS is defined, it is added to the current language's default
+# flags (e.g. CFLAGS) when the check is done. The check is thus made with
+# the flags: "CFLAGS EXTRA-FLAGS FLAG". This can for example be used to
+# force the compiler to issue an error when a bad flag is given.
+#
+# NOTE: This macro depends on the AX_APPEND_FLAG and
+# AX_CHECK_COMPILE_FLAG. Please keep this macro in sync with
+# AX_APPEND_LINK_FLAGS.
+#
+# LICENSE
+#
+# Copyright (c) 2011 Maarten Bosmans <mkbosmans@gmail.com>
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation, either version 3 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+# Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+# As a special exception, the respective Autoconf Macro's copyright owner
+# gives unlimited permission to copy, distribute and modify the configure
+# scripts that are the output of Autoconf when processing the Macro. You
+# need not follow the terms of the GNU General Public License when using
+# or distributing such scripts, even though portions of the text of the
+# Macro appear in them. The GNU General Public License (GPL) does govern
+# all other use of the material that constitutes the Autoconf Macro.
+#
+# This special exception to the GPL applies to versions of the Autoconf
+# Macro released by the Autoconf Archive. When you make and distribute a
+# modified version of the Autoconf Macro, you may extend this special
+# exception to the GPL to apply to your modified version as well.
+
+#serial 3
+
+AC_DEFUN([AX_APPEND_COMPILE_FLAGS],
+[AC_REQUIRE([AX_CHECK_COMPILE_FLAG])
+AC_REQUIRE([AX_APPEND_FLAG])
+for flag in $1; do
+ AX_CHECK_COMPILE_FLAG([$flag], [AX_APPEND_FLAG([$flag], [$2])], [], [$3])
+done
+])dnl AX_APPEND_COMPILE_FLAGS
diff --git a/m4/ax_append_flag.m4 b/m4/ax_append_flag.m4
new file mode 100644
index 0000000..1d38b76
--- /dev/null
+++ b/m4/ax_append_flag.m4
@@ -0,0 +1,69 @@
+# ===========================================================================
+# http://www.gnu.org/software/autoconf-archive/ax_append_flag.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+# AX_APPEND_FLAG(FLAG, [FLAGS-VARIABLE])
+#
+# DESCRIPTION
+#
+# FLAG is appended to the FLAGS-VARIABLE shell variable, with a space
+# added in between.
+#
+# If FLAGS-VARIABLE is not specified, the current language's flags (e.g.
+# CFLAGS) is used. FLAGS-VARIABLE is not changed if it already contains
+# FLAG. If FLAGS-VARIABLE is unset in the shell, it is set to exactly
+# FLAG.
+#
+# NOTE: Implementation based on AX_CFLAGS_GCC_OPTION.
+#
+# LICENSE
+#
+# Copyright (c) 2008 Guido U. Draheim <guidod@gmx.de>
+# Copyright (c) 2011 Maarten Bosmans <mkbosmans@gmail.com>
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation, either version 3 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+# Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+# As a special exception, the respective Autoconf Macro's copyright owner
+# gives unlimited permission to copy, distribute and modify the configure
+# scripts that are the output of Autoconf when processing the Macro. You
+# need not follow the terms of the GNU General Public License when using
+# or distributing such scripts, even though portions of the text of the
+# Macro appear in them. The GNU General Public License (GPL) does govern
+# all other use of the material that constitutes the Autoconf Macro.
+#
+# This special exception to the GPL applies to versions of the Autoconf
+# Macro released by the Autoconf Archive. When you make and distribute a
+# modified version of the Autoconf Macro, you may extend this special
+# exception to the GPL to apply to your modified version as well.
+
+#serial 2
+
+AC_DEFUN([AX_APPEND_FLAG],
+[AC_PREREQ(2.59)dnl for _AC_LANG_PREFIX
+AS_VAR_PUSHDEF([FLAGS], [m4_default($2,_AC_LANG_PREFIX[FLAGS])])dnl
+AS_VAR_SET_IF(FLAGS,
+ [case " AS_VAR_GET(FLAGS) " in
+ *" $1 "*)
+ AC_RUN_LOG([: FLAGS already contains $1])
+ ;;
+ *)
+ AC_RUN_LOG([: FLAGS="$FLAGS $1"])
+ AS_VAR_SET(FLAGS, ["AS_VAR_GET(FLAGS) $1"])
+ ;;
+ esac],
+ [AS_VAR_SET(FLAGS,["$1"])])
+AS_VAR_POPDEF([FLAGS])dnl
+])dnl AX_APPEND_FLAG
diff --git a/m4/ax_cflags_warn_all.m4 b/m4/ax_cflags_warn_all.m4
new file mode 100644
index 0000000..0fa3e18
--- /dev/null
+++ b/m4/ax_cflags_warn_all.m4
@@ -0,0 +1,122 @@
+# ===========================================================================
+# http://www.gnu.org/software/autoconf-archive/ax_cflags_warn_all.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+# AX_CFLAGS_WARN_ALL [(shellvar [,default, [A/NA]])]
+# AX_CXXFLAGS_WARN_ALL [(shellvar [,default, [A/NA]])]
+# AX_FCFLAGS_WARN_ALL [(shellvar [,default, [A/NA]])]
+#
+# DESCRIPTION
+#
+# Try to find a compiler option that enables most reasonable warnings.
+#
+# For the GNU compiler it will be -Wall (and -ansi -pedantic) The result
+# is added to the shellvar being CFLAGS, CXXFLAGS, or FCFLAGS by default.
+#
+# Currently this macro knows about the GCC, Solaris, Digital Unix, AIX,
+# HP-UX, IRIX, NEC SX-5 (Super-UX 10), Cray J90 (Unicos 10.0.0.8), and
+# Intel compilers. For a given compiler, the Fortran flags are much more
+# experimental than their C equivalents.
+#
+# - $1 shell-variable-to-add-to : CFLAGS, CXXFLAGS, or FCFLAGS
+# - $2 add-value-if-not-found : nothing
+# - $3 action-if-found : add value to shellvariable
+# - $4 action-if-not-found : nothing
+#
+# NOTE: These macros depend on AX_APPEND_FLAG.
+#
+# LICENSE
+#
+# Copyright (c) 2008 Guido U. Draheim <guidod@gmx.de>
+# Copyright (c) 2010 Rhys Ulerich <rhys.ulerich@gmail.com>
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 3 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+# Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+# As a special exception, the respective Autoconf Macro's copyright owner
+# gives unlimited permission to copy, distribute and modify the configure
+# scripts that are the output of Autoconf when processing the Macro. You
+# need not follow the terms of the GNU General Public License when using
+# or distributing such scripts, even though portions of the text of the
+# Macro appear in them. The GNU General Public License (GPL) does govern
+# all other use of the material that constitutes the Autoconf Macro.
+#
+# This special exception to the GPL applies to versions of the Autoconf
+# Macro released by the Autoconf Archive. When you make and distribute a
+# modified version of the Autoconf Macro, you may extend this special
+# exception to the GPL to apply to your modified version as well.
+
+#serial 14
+
+AC_DEFUN([AX_FLAGS_WARN_ALL],[dnl
+AS_VAR_PUSHDEF([FLAGS],[_AC_LANG_PREFIX[]FLAGS])dnl
+AS_VAR_PUSHDEF([VAR],[ac_cv_[]_AC_LANG_ABBREV[]flags_warn_all])dnl
+AC_CACHE_CHECK([m4_ifval($1,$1,FLAGS) for maximum warnings],
+VAR,[VAR="no, unknown"
+ac_save_[]FLAGS="$[]FLAGS"
+for ac_arg dnl
+in "-warn all % -warn all" dnl Intel
+ "-pedantic % -Wall" dnl GCC
+ "-xstrconst % -v" dnl Solaris C
+ "-std1 % -verbose -w0 -warnprotos" dnl Digital Unix
+ "-qlanglvl=ansi % -qsrcmsg -qinfo=all:noppt:noppc:noobs:nocnd" dnl AIX
+ "-ansi -ansiE % -fullwarn" dnl IRIX
+ "+ESlit % +w1" dnl HP-UX C
+ "-Xc % -pvctl[,]fullmsg" dnl NEC SX-5 (Super-UX 10)
+ "-h conform % -h msglevel 2" dnl Cray C (Unicos)
+ #
+do FLAGS="$ac_save_[]FLAGS "`echo $ac_arg | sed -e 's,%%.*,,' -e 's,%,,'`
+ AC_COMPILE_IFELSE([AC_LANG_PROGRAM],
+ [VAR=`echo $ac_arg | sed -e 's,.*% *,,'` ; break])
+done
+FLAGS="$ac_save_[]FLAGS"
+])
+AS_VAR_POPDEF([FLAGS])dnl
+AC_REQUIRE([AX_APPEND_FLAG])
+case ".$VAR" in
+ .ok|.ok,*) m4_ifvaln($3,$3) ;;
+ .|.no|.no,*) m4_default($4,[m4_ifval($2,[AX_APPEND_FLAG([$2], [$1])])]) ;;
+ *) m4_default($3,[AX_APPEND_FLAG([$VAR], [$1])]) ;;
+esac
+AS_VAR_POPDEF([VAR])dnl
+])dnl AX_FLAGS_WARN_ALL
+dnl implementation tactics:
+dnl the for-argument contains a list of options. The first part of
+dnl these does only exist to detect the compiler - usually it is
+dnl a global option to enable -ansi or -extrawarnings. All other
+dnl compilers will fail about it. That was needed since a lot of
+dnl compilers will give false positives for some option-syntax
+dnl like -Woption or -Xoption as they think of it is a pass-through
+dnl to later compile stages or something. The "%" is used as a
+dnl delimiter. A non-option comment can be given after "%%" marks
+dnl which will be shown but not added to the respective C/CXXFLAGS.
+
+AC_DEFUN([AX_CFLAGS_WARN_ALL],[dnl
+AC_LANG_PUSH([C])
+AX_FLAGS_WARN_ALL([$1], [$2], [$3], [$4])
+AC_LANG_POP([C])
+])
+
+AC_DEFUN([AX_CXXFLAGS_WARN_ALL],[dnl
+AC_LANG_PUSH([C++])
+AX_FLAGS_WARN_ALL([$1], [$2], [$3], [$4])
+AC_LANG_POP([C++])
+])
+
+AC_DEFUN([AX_FCFLAGS_WARN_ALL],[dnl
+AC_LANG_PUSH([Fortran])
+AX_FLAGS_WARN_ALL([$1], [$2], [$3], [$4])
+AC_LANG_POP([Fortran])
+])
diff --git a/m4/ax_check_compile_flag.m4 b/m4/ax_check_compile_flag.m4
new file mode 100644
index 0000000..51df0c0
--- /dev/null
+++ b/m4/ax_check_compile_flag.m4
@@ -0,0 +1,74 @@
+# ===========================================================================
+# http://www.gnu.org/software/autoconf-archive/ax_check_compile_flag.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+# AX_CHECK_COMPILE_FLAG(FLAG, [ACTION-SUCCESS], [ACTION-FAILURE], [EXTRA-FLAGS], [INPUT])
+#
+# DESCRIPTION
+#
+# Check whether the given FLAG works with the current language's compiler
+# or gives an error. (Warnings, however, are ignored)
+#
+# ACTION-SUCCESS/ACTION-FAILURE are shell commands to execute on
+# success/failure.
+#
+# If EXTRA-FLAGS is defined, it is added to the current language's default
+# flags (e.g. CFLAGS) when the check is done. The check is thus made with
+# the flags: "CFLAGS EXTRA-FLAGS FLAG". This can for example be used to
+# force the compiler to issue an error when a bad flag is given.
+#
+# INPUT gives an alternative input source to AC_COMPILE_IFELSE.
+#
+# NOTE: Implementation based on AX_CFLAGS_GCC_OPTION. Please keep this
+# macro in sync with AX_CHECK_{PREPROC,LINK}_FLAG.
+#
+# LICENSE
+#
+# Copyright (c) 2008 Guido U. Draheim <guidod@gmx.de>
+# Copyright (c) 2011 Maarten Bosmans <mkbosmans@gmail.com>
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation, either version 3 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+# Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+# As a special exception, the respective Autoconf Macro's copyright owner
+# gives unlimited permission to copy, distribute and modify the configure
+# scripts that are the output of Autoconf when processing the Macro. You
+# need not follow the terms of the GNU General Public License when using
+# or distributing such scripts, even though portions of the text of the
+# Macro appear in them. The GNU General Public License (GPL) does govern
+# all other use of the material that constitutes the Autoconf Macro.
+#
+# This special exception to the GPL applies to versions of the Autoconf
+# Macro released by the Autoconf Archive. When you make and distribute a
+# modified version of the Autoconf Macro, you may extend this special
+# exception to the GPL to apply to your modified version as well.
+
+#serial 3
+
+AC_DEFUN([AX_CHECK_COMPILE_FLAG],
+[AC_PREREQ(2.59)dnl for _AC_LANG_PREFIX
+AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_[]_AC_LANG_ABBREV[]flags_$4_$1])dnl
+AC_CACHE_CHECK([whether _AC_LANG compiler accepts $1], CACHEVAR, [
+ ax_check_save_flags=$[]_AC_LANG_PREFIX[]FLAGS
+ _AC_LANG_PREFIX[]FLAGS="$[]_AC_LANG_PREFIX[]FLAGS $4 $1"
+ AC_COMPILE_IFELSE([m4_default([$5],[AC_LANG_PROGRAM()])],
+ [AS_VAR_SET(CACHEVAR,[yes])],
+ [AS_VAR_SET(CACHEVAR,[no])])
+ _AC_LANG_PREFIX[]FLAGS=$ax_check_save_flags])
+AS_IF([test x"AS_VAR_GET(CACHEVAR)" = xyes],
+ [m4_default([$2], :)],
+ [m4_default([$3], :)])
+AS_VAR_POPDEF([CACHEVAR])dnl
+])dnl AX_CHECK_COMPILE_FLAGS
diff --git a/m4/pkg.m4 b/m4/pkg.m4
new file mode 100644
index 0000000..bfe1dd1
--- /dev/null
+++ b/m4/pkg.m4
@@ -0,0 +1,158 @@
+# pkg.m4 - Macros to locate and utilise pkg-config. -*- Autoconf -*-
+#
+# Copyright © 2004 Scott James Remnant <scott@netsplit.com>.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+# As a special exception to the GNU General Public License, if you
+# distribute this file as part of a program that contains a
+# configuration script generated by Autoconf, you may include it under
+# the same distribution terms that you use for the rest of that program.
+
+# PKG_PROG_PKG_CONFIG([MIN-VERSION])
+# ----------------------------------
+AC_DEFUN([PKG_PROG_PKG_CONFIG],
+[m4_pattern_forbid([^_?PKG_[A-Z_]+$])
+m4_pattern_allow([^PKG_CONFIG(_PATH)?$])
+AC_ARG_VAR([PKG_CONFIG], [path to pkg-config utility])dnl
+if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then
+ AC_PATH_TOOL([PKG_CONFIG], [pkg-config])
+fi
+if test -n "$PKG_CONFIG"; then
+ _pkg_min_version=m4_default([$1], [0.9.0])
+ AC_MSG_CHECKING([pkg-config is at least version $_pkg_min_version])
+ if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then
+ AC_MSG_RESULT([yes])
+ else
+ AC_MSG_RESULT([no])
+ PKG_CONFIG=""
+ fi
+
+fi[]dnl
+])# PKG_PROG_PKG_CONFIG
+
+# PKG_CHECK_EXISTS(MODULES, [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])
+#
+# Check to see whether a particular set of modules exists. Similar
+# to PKG_CHECK_MODULES(), but does not set variables or print errors.
+#
+#
+# Similar to PKG_CHECK_MODULES, make sure that the first instance of
+# this or PKG_CHECK_MODULES is called, or make sure to call
+# PKG_CHECK_EXISTS manually
+# --------------------------------------------------------------
+AC_DEFUN([PKG_CHECK_EXISTS],
+[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl
+if test -n "$PKG_CONFIG" && \
+ AC_RUN_LOG([$PKG_CONFIG --exists --print-errors "$1"]); then
+ m4_ifval([$2], [$2], [:])
+m4_ifvaln([$3], [else
+ $3])dnl
+fi])
+
+
+# _PKG_CONFIG([VARIABLE], [COMMAND], [MODULES])
+# ---------------------------------------------
+m4_define([_PKG_CONFIG],
+[if test -n "$PKG_CONFIG"; then
+ if test -n "$$1"; then
+ pkg_cv_[]$1="$$1"
+ else
+ PKG_CHECK_EXISTS([$3],
+ [pkg_cv_[]$1=`$PKG_CONFIG --[]$2 "$3" 2>/dev/null`],
+ [pkg_failed=yes])
+ fi
+else
+ pkg_failed=untried
+fi[]dnl
+])# _PKG_CONFIG
+
+# _PKG_SHORT_ERRORS_SUPPORTED
+# -----------------------------
+AC_DEFUN([_PKG_SHORT_ERRORS_SUPPORTED],
+[AC_REQUIRE([PKG_PROG_PKG_CONFIG])
+if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then
+ _pkg_short_errors_supported=yes
+else
+ _pkg_short_errors_supported=no
+fi[]dnl
+])# _PKG_SHORT_ERRORS_SUPPORTED
+
+
+# PKG_CHECK_MODULES(VARIABLE-PREFIX, MODULES, [ACTION-IF-FOUND],
+# [ACTION-IF-NOT-FOUND])
+#
+#
+# Note that if there is a possibility the first call to
+# PKG_CHECK_MODULES might not happen, you should be sure to include an
+# explicit call to PKG_PROG_PKG_CONFIG in your configure.ac
+#
+#
+# --------------------------------------------------------------
+AC_DEFUN([PKG_CHECK_MODULES],
+[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl
+AC_ARG_VAR([$1][_CFLAGS], [C compiler flags for $1, overriding pkg-config])dnl
+AC_ARG_VAR([$1][_LIBS], [linker flags for $1, overriding pkg-config])dnl
+
+pkg_failed=no
+AC_MSG_CHECKING([for $2])
+
+_PKG_CONFIG([$1][_CFLAGS], [cflags], [$2])
+_PKG_CONFIG([$1][_LIBS], [libs], [$2])
+
+m4_define([_PKG_TEXT], [Alternatively, you may set the environment variables $1[]_CFLAGS
+and $1[]_LIBS to avoid the need to call pkg-config.
+See the pkg-config man page for more details.])
+
+if test $pkg_failed = yes; then
+ _PKG_SHORT_ERRORS_SUPPORTED
+ if test $_pkg_short_errors_supported = yes; then
+ $1[]_PKG_ERRORS=`$PKG_CONFIG --short-errors --errors-to-stdout --print-errors "$2"`
+ else
+ $1[]_PKG_ERRORS=`$PKG_CONFIG --errors-to-stdout --print-errors "$2"`
+ fi
+ # Put the nasty error message in config.log where it belongs
+ echo "$$1[]_PKG_ERRORS" >&AS_MESSAGE_LOG_FD
+
+ ifelse([$4], , [AC_MSG_ERROR(dnl
+[Package requirements ($2) were not met:
+
+$$1_PKG_ERRORS
+
+Consider adjusting the PKG_CONFIG_PATH environment variable if you
+installed software in a non-standard prefix.
+
+_PKG_TEXT
+])],
+ [$4])
+ AC_MSG_RESULT([no])
+elif test $pkg_failed = untried; then
+ ifelse([$4], , [AC_MSG_FAILURE(dnl
+[The pkg-config script could not be found or is too old. Make sure it
+is in your PATH or set the PKG_CONFIG environment variable to the full
+path to pkg-config.
+
+_PKG_TEXT
+
+To get pkg-config, see <http://www.freedesktop.org/software/pkgconfig>.])],
+ [$4])
+ AC_MSG_RESULT([no])
+else
+ $1[]_CFLAGS=$pkg_cv_[]$1[]_CFLAGS
+ $1[]_LIBS=$pkg_cv_[]$1[]_LIBS
+ AC_MSG_RESULT([yes])
+ ifelse([$3], , :, [$3])
+fi[]dnl
+])# PKG_CHECK_MODULES
diff --git a/src/.gitignore b/src/.gitignore
new file mode 100644
index 0000000..0c0f75f
--- /dev/null
+++ b/src/.gitignore
@@ -0,0 +1,5 @@
+/config.h.in
+/config.h.in~
+/config.h
+/stamp-h1
+/libdb.la
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..06a92ff
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,11 @@
+MAINTAINERCLEANFILES = Makefile.in
+
+AM_CPPFLAGS = @DEFINES@
+
+noinst_LTLIBRARIES = libdb.la
+
+libdb_la_SOURCES = db.hh common.hh db.cc sqlite3_db.hh sqlite3_db.cc
+libdb_la_CPPFLAGS = $(AM_CPPFLAGS) @SQLITE3_CFLAGS@
+libdb_la_LIBADD = @SQLITE3_LIBS@
+
+
diff --git a/src/common.hh b/src/common.hh
new file mode 100644
index 0000000..3b69aee
--- /dev/null
+++ b/src/common.hh
@@ -0,0 +1,6 @@
+#ifndef COMMON_HH
+#define COMMON_HH
+
+#include <cassert>
+
+#endif /* COMMON_HH */
diff --git a/src/db.cc b/src/db.cc
new file mode 100644
index 0000000..b1efc21
--- /dev/null
+++ b/src/db.cc
@@ -0,0 +1,60 @@
+#include "common.hh"
+
+#include "db.hh"
+
+namespace stuff {
+
+DB::Value::Value(const std::string& value)
+ : type_(DB::Type::STRING), string_(value) {
+}
+
+DB::Value::Value(int32_t value)
+ : type_(DB::Type::INT32) {
+ data_.i32 = value;
+}
+
+DB::Value::Value(int64_t value)
+ : type_(DB::Type::INT64) {
+ data_.i64 = value;
+}
+
+DB::Value::Value(bool value)
+ : type_(DB::Type::BOOL) {
+ data_.b = value;
+}
+
+DB::Value::Value(double value)
+ : type_(DB::Type::DOUBLE) {
+ data_.d = value;
+}
+
+DB::Value::Value(std::nullptr_t)
+ : type_(DB::Type::RAW) {
+}
+
+const std::string& DB::Value::string() const {
+ assert(type_ == DB::Type::STRING);
+ return string_;
+}
+
+int32_t DB::Value::i32() const {
+ assert(type_ == DB::Type::INT32);
+ return data_.i32;
+}
+
+int64_t DB::Value::i64() const {
+ assert(type_ == DB::Type::INT64);
+ return data_.i64;
+}
+
+bool DB::Value::b() const {
+ assert(type_ == DB::Type::BOOL);
+ return data_.b;
+}
+
+double DB::Value::d() const {
+ assert(type_ == DB::Type::DOUBLE);
+ return data_.d;
+}
+
+} // namespace stuff
diff --git a/src/db.hh b/src/db.hh
new file mode 100644
index 0000000..a4f65e4
--- /dev/null
+++ b/src/db.hh
@@ -0,0 +1,426 @@
+#ifndef DB_HH
+#define DB_HH
+
+#include <functional>
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace stuff {
+
+class DB {
+public:
+ virtual ~DB() {}
+
+ enum class Type {
+ STRING,
+ BOOL,
+ DOUBLE,
+ INT32,
+ INT64,
+ RAW
+ };
+
+ class Constraint {
+ public:
+ Constraint(Type type)
+ : Constraint(type, false, false, false) {
+ }
+
+ Type type() const {
+ return type_;
+ }
+
+ bool primary_key() const {
+ return primary_;
+ }
+
+ bool unique() const {
+ return primary_ || unique_;
+ }
+
+ bool not_null() const {
+ return primary_ || not_null_;
+ }
+
+ protected:
+ Constraint(Type type, bool primary, bool unique, bool not_null)
+ : type_(type), primary_(primary), unique_(unique),
+ not_null_(not_null) {
+ }
+
+ private:
+ Type type_;
+ bool primary_;
+ bool unique_;
+ bool not_null_;
+ };
+
+ class PrimaryKey : public Constraint {
+ public:
+ explicit PrimaryKey(Type type)
+ : Constraint(type, true, true, true) {
+ }
+ };
+
+ class Unique : public Constraint {
+ public:
+ explicit Unique(Type type)
+ : Constraint(type, false, true, false) {
+ }
+ };
+
+ class NotNull : public Constraint {
+ public:
+ explicit NotNull(Type type)
+ : Constraint(type, false, false, true) {
+ }
+ };
+
+ typedef std::vector<std::pair<std::string,Constraint>> Declaration;
+
+ class Editor {
+ public:
+ virtual ~Editor() {}
+
+ // Set the column matching name to value
+ virtual void set(const std::string& name, const std::string& value) = 0;
+ virtual void set(const std::string& name, const char* value) {
+ set(name, std::string(value));
+ }
+ virtual void set(const std::string& name, bool value) = 0;
+ virtual void set(const std::string& name, double value) = 0;
+ virtual void set(const std::string& name, int32_t value) = 0;
+ virtual void set(const std::string& name, int64_t value) = 0;
+ virtual void set_null(const std::string& name) = 0;
+ virtual void set(const std::string& name, const void* data,
+ size_t size) = 0;
+
+ // Return true if the insert/update succeeded, false in case of error
+ // After calling commit, release the object, it's no longer usable
+ virtual bool commit() = 0;
+
+ // Return the latest inserted rowid
+ virtual int64_t last_insert_rowid() = 0;
+
+ protected:
+ Editor() {}
+
+ private:
+ Editor(const Editor&) = delete;
+ Editor& operator=(const Editor&) = delete;
+ };
+
+ class Snapshot {
+ public:
+ virtual ~Snapshot() {}
+
+ // Get the value for the column matching name, returns false if no
+ // column matching that name exists or if the type doesn't match
+ virtual bool get(const std::string& name, std::string* value) = 0;
+ virtual bool get(const std::string& name, bool* value) = 0;
+ virtual bool get(const std::string& name, double* value) = 0;
+ virtual bool get(const std::string& name, int32_t* value) = 0;
+ virtual bool get(const std::string& name, int64_t* value) = 0;
+ virtual bool get(const std::string& name,
+ std::vector<uint8_t>* value) = 0;
+ virtual bool is_null(const std::string& name, bool* value) = 0;
+
+ // Set the column to value, returns false if no column with that index
+ // exists or if the type doesn't match
+ virtual bool get(uint32_t column, std::string* value) = 0;
+ virtual bool get(uint32_t column, bool* value) = 0;
+ virtual bool get(uint32_t column, double* value) = 0;
+ virtual bool get(uint32_t column, int32_t* value) = 0;
+ virtual bool get(uint32_t column, int64_t* value) = 0;
+ virtual bool get(uint32_t column, std::vector<uint8_t>* value) = 0;
+ virtual bool is_null(uint32_t column, bool* value) = 0;
+
+ // Go to the next row in snapshot, returns false if this was the last
+ // or an error occurred
+ virtual bool next() = 0;
+
+ // Returns true if the snapshot had an error
+ virtual bool bad() = 0;
+
+ protected:
+ Snapshot() {}
+
+ private:
+ Snapshot(const Snapshot&) = delete;
+ Snapshot& operator=(const Snapshot&) = delete;
+ };
+
+ class Condition;
+
+ class Value {
+ public:
+ explicit Value(const std::string& value);
+ Value(int32_t value);
+ Value(int64_t value);
+ Value(bool value);
+ Value(double value);
+ Value(std::nullptr_t);
+
+ Type type() const {
+ return type_;
+ }
+
+ const std::string& string() const;
+
+ int32_t i32() const;
+ int64_t i64() const;
+ bool b() const;
+ double d() const;
+
+ private:
+ friend class Condition;
+ Value() {}
+
+ Type type_;
+ std::string string_;
+ union {
+ int32_t i32;
+ int64_t i64;
+ bool b;
+ double d;
+ } data_;
+ };
+
+ class Column {
+ public:
+ explicit Column(const std::string& name)
+ : name_(name) {
+ }
+
+ const std::string& name() const {
+ return name_;
+ }
+
+ private:
+ friend class Condition;
+ Column() {}
+
+ std::string name_;
+ };
+
+ class Condition {
+ public:
+ enum BinaryBooleanOperator {
+ AND,
+ OR
+ };
+ enum UnaryBooleanOperator {
+ NOT
+ };
+ enum BinaryOperator {
+ EQUAL,
+ NOT_EQUAL,
+ GREATER_THAN,
+ LESS_THAN,
+ GREATER_EQUAL,
+ LESS_EQUAL,
+ };
+ enum UnaryOperator {
+ NEGATIVE,
+ IS_NULL,
+ };
+ enum Mode {
+ NOOP,
+ BOOL_BINARY,
+ BOOL_UNARY,
+ COMP_BINARY,
+ COMP_UNARY
+ };
+
+ Condition()
+ : mode_(NOOP) {
+ }
+ Condition(const Condition& c1, BinaryBooleanOperator op,
+ const Condition& c2)
+ : mode_(BOOL_BINARY), c1_(new Condition(c1)),
+ c2_(new Condition(c2)) {
+ assert(!c1_->empty() && !c2_->empty());
+ op_.bool_binary = op;
+ }
+ Condition(UnaryBooleanOperator op, const Condition& c)
+ : mode_(BOOL_UNARY), c1_(new Condition(c)) {
+ assert(!c1_->empty());
+ op_.bool_unary = op;
+ }
+ Condition(const Column& c, BinaryOperator op, const Value& v)
+ : mode_(COMP_BINARY), c_(c), v_(v) {
+ op_.comp_binary = op;
+ }
+ Condition(UnaryOperator op, const Column& c)
+ : mode_(COMP_UNARY), c_(c) {
+ op_.comp_unary = op;
+ }
+
+ Mode mode() const {
+ return mode_;
+ }
+
+ bool empty() const {
+ return mode_ == NOOP;
+ }
+
+ const Condition& c1() const {
+ assert(mode_ == BOOL_BINARY || mode_ == BOOL_UNARY);
+ return *c1_;
+ }
+
+ BinaryBooleanOperator bool_binary_op() const {
+ assert(mode_ == BOOL_BINARY);
+ return op_.bool_binary;
+ }
+
+ UnaryBooleanOperator bool_unary_op() const {
+ assert(mode_ == BOOL_UNARY);
+ return op_.bool_unary;
+ }
+
+ const Condition& c2() const {
+ assert(mode_ == BOOL_BINARY);
+ return *c2_;
+ }
+
+ BinaryOperator binary_op() const {
+ assert(mode_ == COMP_BINARY);
+ return op_.comp_binary;
+ }
+
+ UnaryOperator unary_op() const {
+ assert(mode_ == COMP_UNARY);
+ return op_.comp_unary;
+ }
+
+ const Column& column() const {
+ assert(mode_ == COMP_BINARY || mode_ == COMP_UNARY);
+ return c_;
+ }
+
+ const Value& value() const {
+ assert(mode_ == COMP_BINARY);
+ return v_;
+ }
+
+ private:
+ const Mode mode_;
+ const std::shared_ptr<Condition> c1_, c2_;
+ const Column c_;
+ const Value v_;
+ union {
+ BinaryBooleanOperator bool_binary;
+ UnaryBooleanOperator bool_unary;
+ BinaryOperator comp_binary;
+ UnaryOperator comp_unary;
+ } op_;
+ };
+
+ class OrderBy {
+ public:
+ OrderBy(const std::string& name, bool ascending = true)
+ : name_(name), ascending_(ascending) {
+ }
+ OrderBy(const Column& column, bool ascending = true)
+ : name_(column.name()), ascending_(ascending) {
+ }
+
+ const std::string& name() const {
+ return name_;
+ }
+
+ bool ascending() const {
+ return ascending_;
+ }
+
+ private:
+ std::string name_;
+ bool ascending_;
+ };
+
+ // Create a table with the given declarations.
+ // If a table with that name already exists nothing happens.
+ // Returns false in case of error
+ virtual bool insert_table(const std::string& table,
+ const Declaration& declaration) = 0;
+ // Returns false in case of error. The table not existing is not an error.
+ virtual bool remove_table(const std::string& table) = 0;
+ // Create an editor for inserting an row in the table.
+ // Always succeeds and doesn't do anything until commit is called on the
+ // Editor.
+ virtual std::shared_ptr<Editor> insert(const std::string& table) = 0;
+ // Create an editor for updating an row in the table.
+ // Always succeeds and doesn't do anything until commit is called on the
+ // Editor.
+ virtual std::shared_ptr<Editor> update(const std::string& table,
+ const Condition& condition =
+ Condition()) = 0;
+ // Return a snapshot for rows in table matching condition.
+ // Check bad() in snapshot for errors
+ virtual std::shared_ptr<Snapshot> select(
+ const std::string& table, const Condition& condition = Condition(),
+ const std::vector<OrderBy>& order_by = std::vector<OrderBy>()) = 0;
+ // Remove all rows matching condition in table. Returns number of rows
+ // removed or -1 in case of error
+ virtual int64_t remove(const std::string& table,
+ const Condition& condition = Condition()) = 0;
+
+ virtual bool start_transaction() = 0;
+ virtual bool commit_transaction() = 0;
+ virtual bool rollback_transaction() = 0;
+
+ // Returns true if a fatal database error has occurred
+ virtual bool bad() = 0;
+
+ // Returns a description of the last error returned by an earlier method
+ virtual std::string last_error() = 0;
+
+protected:
+ DB() {}
+
+private:
+ DB(const DB&) = delete;
+ DB& operator=(const DB&) = delete;
+};
+
+inline DB::Condition operator&&(const DB::Condition& c1,
+ const DB::Condition& c2) {
+ return DB::Condition(c1, DB::Condition::AND, c2);
+}
+inline DB::Condition operator||(const DB::Condition& c1,
+ const DB::Condition& c2) {
+ return DB::Condition(c1, DB::Condition::OR, c2);
+}
+inline DB::Condition operator!(const DB::Condition& c) {
+ return DB::Condition(DB::Condition::NOT, c);
+}
+inline DB::Condition operator==(const DB::Column& c, const DB::Value& v) {
+ return DB::Condition(c, DB::Condition::EQUAL, v);
+}
+inline DB::Condition operator!=(const DB::Column& c, const DB::Value& v) {
+ return DB::Condition(c, DB::Condition::NOT_EQUAL, v);
+}
+inline DB::Condition operator<(const DB::Column& c, const DB::Value& v) {
+ return DB::Condition(c, DB::Condition::LESS_THAN, v);
+}
+inline DB::Condition operator>(const DB::Column& c, const DB::Value& v) {
+ return DB::Condition(c, DB::Condition::GREATER_THAN, v);
+}
+inline DB::Condition operator<=(const DB::Column& c, const DB::Value& v) {
+ return DB::Condition(c, DB::Condition::LESS_EQUAL, v);
+}
+inline DB::Condition operator>=(const DB::Column& c, const DB::Value& v) {
+ return DB::Condition(c, DB::Condition::GREATER_EQUAL, v);
+}
+inline DB::Condition operator-(const DB::Column& c) {
+ return DB::Condition(DB::Condition::NEGATIVE, c);
+}
+inline DB::Condition is_null(const DB::Column& c) {
+ return DB::Condition(DB::Condition::IS_NULL, c);
+}
+
+} // namespace stuff
+
+#endif /* DB_HH */
diff --git a/src/sqlite3_db.cc b/src/sqlite3_db.cc
new file mode 100644
index 0000000..0333bda
--- /dev/null
+++ b/src/sqlite3_db.cc
@@ -0,0 +1,716 @@
+#include "common.hh"
+
+#include "sqlite3_db.hh"
+
+#include <map>
+#include <sqlite3.h>
+
+namespace stuff {
+
+namespace {
+
+class DeleteStmt {
+public:
+ void operator()(sqlite3_stmt* stmt) const {
+ sqlite3_finalize(stmt);
+ }
+};
+
+typedef std::unique_ptr<sqlite3_stmt,DeleteStmt> unique_stmt;
+
+class DBImpl : public DB {
+public:
+ DBImpl()
+ : db_(nullptr), bad_(true) {
+ }
+
+ ~DBImpl() {
+ close();
+ }
+
+ void open(const std::string& path) {
+ close();
+ int err = sqlite3_open(path.c_str(), &db_);
+ if (err == SQLITE_OK) {
+ bad_ = false;
+ prepare();
+ } else {
+ bad_ = true;
+ }
+ }
+
+ void close() {
+ if (!db_) return;
+ unprepare();
+ sqlite3_close(db_);
+ db_ = nullptr;
+ }
+
+ bool insert_table(const std::string& table,
+ const Declaration& declaration) override {
+ if (!db_) return false;
+ std::string sql = "CREATE TABLE IF NOT EXISTS " + safe(table) + " (";
+ std::string primary_key;
+ bool first = true, pk_first = true;
+ for (const auto& pair : declaration) {
+ if (!first) sql += ','; else first = false;
+ sql += safe(pair.first);
+ if (pair.second.primary_key()) {
+ if (!pk_first) primary_key += ','; else pk_first = false;
+ primary_key += safe(pair.first);
+ }
+ switch (pair.second.type()) {
+ case Type::STRING:
+ sql += " TEXT";
+ break;
+ case Type::INT32:
+ case Type::INT64:
+ case Type::BOOL:
+ sql += " INTEGER";
+ break;
+ case Type::DOUBLE:
+ sql += " REAL";
+ break;
+ case Type::RAW:
+ sql += " BLOB";
+ break;
+ }
+ if (!pair.second.primary_key()) {
+ if (pair.second.unique()) {
+ sql += " UNIQUE";
+ }
+ if (pair.second.not_null()) {
+ sql += " NOT NULL";
+ }
+ }
+ }
+ if (!primary_key.empty()) {
+ sql += ", PRIMARY KEY (" + primary_key + ")";
+ }
+ sql += ")";
+ unique_stmt stmt;
+ if (!prepare(sql, &stmt)) return false;
+ return exec(stmt);
+ }
+
+ bool remove_table(const std::string& table) override {
+ if (!db_) return false;
+ std::string sql = "DROP TABLE IF EXISTS " + safe(table);
+ unique_stmt stmt;
+ if (!prepare(sql, &stmt)) return false;
+ return exec(stmt);
+ }
+
+ std::shared_ptr<Editor> insert(const std::string& table) override {
+ return std::shared_ptr<Editor>(new InsertEditorImpl(this, table));
+ }
+
+ std::shared_ptr<Editor> update(const std::string& table,
+ const Condition& condition) override {
+ return std::shared_ptr<Editor>(
+ new UpdateEditorImpl(this, table, condition));
+ }
+
+ std::shared_ptr<Snapshot> select(
+ const std::string& table, const Condition& condition,
+ const std::vector<OrderBy>& order_by) override {
+ std::string sql = "SELECT * FROM " + safe(table);
+ sql += compile(condition);
+ sql += compile(order_by);
+ unique_stmt stmt;
+ if (!prepare(sql, &stmt)) return nullptr;
+ int index = 1;
+ if (!bind(stmt, condition, &index)) return nullptr;
+ std::shared_ptr<Snapshot> ret(new SnapshotImpl(stmt));
+ if (!ret->next()) return nullptr;
+ return ret;
+ }
+
+ int64_t remove(const std::string& table,
+ const Condition& condition) override {
+ std::string sql = "DELETE FROM " + safe(table) + compile(condition);
+ unique_stmt stmt;
+ if (!prepare(sql, &stmt)) return -1;
+ int index = 1;
+ if (!bind(stmt, condition, &index)) return -1;
+ if (!exec(stmt)) return -1;
+ return sqlite3_changes(db_);
+ }
+
+ bool start_transaction() override {
+ return exec(stmt_begin_);
+ }
+
+ bool commit_transaction() override {
+ return exec(stmt_commit_);
+ }
+
+ bool rollback_transaction() override {
+ return exec(stmt_rollback_);
+ }
+
+ bool bad() override {
+ return bad_;
+ }
+
+ std::string last_error() override {
+ if (!db_) return "Out of memory";
+ return sqlite3_errmsg(db_);
+ }
+
+ class EditorImpl : public Editor {
+ public:
+ EditorImpl(DBImpl* db, const std::string& table)
+ : db_(db), table_(table), new_names_(true) {
+ }
+
+ void set(const std::string& name, const std::string& value) override {
+ do_set(name, Value(value));
+ }
+
+ void set(const std::string& name, bool value) override {
+ do_set(name, value);
+ }
+
+ void set(const std::string& name, double value) override {
+ do_set(name, value);
+ }
+
+ void set(const std::string& name, int32_t value) override {
+ do_set(name, value);
+ }
+
+ void set(const std::string& name, int64_t value) override {
+ do_set(name, value);
+ }
+
+ void set_null(const std::string& name) override {
+ do_set(name, nullptr);
+ }
+
+ void set(const std::string& name, const void* data,
+ size_t size) override {
+ if (!data) {
+ set(name, nullptr);
+ blob_.erase(name);
+ } else {
+ auto ptr = reinterpret_cast<const char*>(data);
+ auto vector = std::vector<char>(ptr, ptr + size);
+ auto it = blob_.emplace(name, vector);
+ if (!it.second) {
+ it.first->second.swap(vector);
+ } else {
+ if (data_.erase(name) == 0) {
+ new_names_ = true;
+ }
+ }
+ }
+ }
+
+ protected:
+ void do_set(const std::string& name, const Value&& value) {
+ auto it = data_.emplace(name, value);
+ if (!it.second) {
+ it.first->second = value;
+ } else {
+ new_names_ = true;
+ }
+ }
+
+ DBImpl* const db_;
+ const std::string table_;
+ std::map<std::string,Value> data_;
+ std::map<std::string,std::vector<char>> blob_;
+ bool new_names_;
+ };
+
+ class InsertEditorImpl : public EditorImpl {
+ public:
+ InsertEditorImpl(DBImpl* db, const std::string& table)
+ : EditorImpl(db, table) {
+ }
+
+ bool commit() override {
+ if (!stmt_ || new_names_) {
+ if (!prepare()) return false;
+ new_names_ = false;
+ }
+ int index = 1;
+ for (const auto& pair : data_) {
+ if (!db_->bind(stmt_, index++, pair.second)) return false;
+ }
+ for (const auto& pair : blob_) {
+ if (!db_->bind(stmt_, index++, pair.second)) return false;
+ }
+ return db_->exec(stmt_);
+ }
+
+ int64_t last_insert_rowid() override {
+ return sqlite3_last_insert_rowid(db_->db_);
+ }
+
+ private:
+ bool prepare() {
+ if (data_.empty() && blob_.empty()) return false;
+ std::string sql = "INSERT INTO " + db_->safe(table_) + " (";
+ for (const auto& pair : data_) {
+ sql += db_->safe(pair.first) + ",";
+ }
+ for (const auto& pair : blob_) {
+ sql += db_->safe(pair.first) + ",";
+ }
+ sql.pop_back();
+ sql += ") VALUES (?";
+ auto count = data_.size() + blob_.size() - 1;
+ while (count--) {
+ sql += ",?";
+ }
+ sql += ")";
+ return db_->prepare(sql, &stmt_);
+ }
+
+ unique_stmt stmt_;
+ };
+
+ class UpdateEditorImpl : public EditorImpl {
+ public:
+ UpdateEditorImpl(DBImpl* db, const std::string& table,
+ const Condition& condition)
+ : EditorImpl(db, table), condition_(condition) {
+ }
+
+ bool commit() override {
+ if (!stmt_ || new_names_) {
+ if (!prepare()) return false;
+ new_names_ = false;
+ }
+ int index = 1;
+ for (const auto& pair : data_) {
+ if (!db_->bind(stmt_, index++, pair.second)) return false;
+ }
+ for (const auto& pair : blob_) {
+ if (!db_->bind(stmt_, index++, pair.second)) return false;
+ }
+ if (!db_->bind(stmt_, condition_, &index)) return false;
+ return db_->exec(stmt_);
+ }
+
+ int64_t last_insert_rowid() override {
+ assert(false);
+ return 0;
+ }
+
+ private:
+ bool prepare() {
+ if (data_.empty() && blob_.empty()) return false;
+ std::string sql = "UPDATE " + db_->safe(table_) + " SET ";
+ for (const auto& pair : data_) {
+ sql += safe(pair.first) + "=?,";
+ }
+ for (const auto& pair : blob_) {
+ sql += safe(pair.first) + "=?,";
+ }
+ sql.pop_back();
+ sql += db_->compile(condition_);
+ return db_->prepare(sql, &stmt_);
+ }
+
+ const Condition condition_;
+ unique_stmt stmt_;
+ };
+
+ class SnapshotImpl : public Snapshot {
+ public:
+ SnapshotImpl(unique_stmt& stmt) {
+ stmt_.swap(stmt);
+ }
+ bool get(const std::string& name, std::string* value) override {
+ return get(find_column(name), value);
+ }
+ bool get(const std::string& name, bool* value) override {
+ return get(find_column(name), value);
+ }
+ bool get(const std::string& name, double* value) override {
+ return get(find_column(name), value);
+ }
+ bool get(const std::string& name, int32_t* value) override {
+ return get(find_column(name), value);
+ }
+ bool get(const std::string& name, int64_t* value) override {
+ return get(find_column(name), value);
+ }
+ bool get(const std::string& name,
+ std::vector<uint8_t>* value) override {
+ return get(find_column(name), value);
+ }
+ bool is_null(const std::string& name, bool* value) override {
+ return is_null(find_column(name), value);
+ }
+
+ bool get(uint32_t column, std::string* value) override {
+ if (!value) { assert(false); return false; }
+ if (!stmt_) return false;
+ if (column >= static_cast<uint32_t>(
+ sqlite3_column_count(stmt_.get()))) return false;
+ if (sqlite3_column_type(stmt_.get(), column) != SQLITE_TEXT) {
+ return false;
+ }
+ value->assign(reinterpret_cast<const char*>(
+ sqlite3_column_blob(stmt_.get(), column)),
+ sqlite3_column_bytes(stmt_.get(), column));
+ return true;
+ }
+ bool get(uint32_t column, bool* value) override {
+ if (!value) { assert(false); return false; }
+ int32_t tmp;
+ if (!get(column, &tmp)) return false;
+ *value = tmp != 0;
+ return true;
+ }
+ bool get(uint32_t column, double* value) override {
+ if (!value) { assert(false); return false; }
+ if (!stmt_) return false;
+ if (column >= static_cast<uint32_t>(
+ sqlite3_column_count(stmt_.get()))) return false;
+ if (sqlite3_column_type(stmt_.get(), column) != SQLITE_FLOAT) {
+ return false;
+ }
+ *value = sqlite3_column_double(stmt_.get(), column);
+ return true;
+ }
+ bool get(uint32_t column, int32_t* value) override {
+ if (!value) { assert(false); return false; }
+ if (!stmt_) return false;
+ if (column >= static_cast<uint32_t>(
+ sqlite3_column_count(stmt_.get()))) return false;
+ if (sqlite3_column_type(stmt_.get(), column) != SQLITE_INTEGER) {
+ return false;
+ }
+ if (sizeof(int) >= sizeof(int32_t)) {
+ *value = sqlite3_column_int(stmt_.get(), column);
+ } else {
+ *value = sqlite3_column_int64(stmt_.get(), column);
+ }
+ return true;
+ }
+ bool get(uint32_t column, int64_t* value) override {
+ if (!value) { assert(false); return false; }
+ if (!stmt_) return false;
+ if (column >= static_cast<uint32_t>(
+ sqlite3_column_count(stmt_.get()))) return false;
+ if (sqlite3_column_type(stmt_.get(), column) != SQLITE_INTEGER) {
+ return false;
+ }
+ *value = sqlite3_column_int64(stmt_.get(), column);
+ return true;
+ }
+ bool get(uint32_t column, std::vector<uint8_t>* value) override {
+ if (!value) { assert(false); return false; }
+ if (!stmt_) return false;
+ if (column >= static_cast<uint32_t>(
+ sqlite3_column_count(stmt_.get()))) return false;
+ if (sqlite3_column_type(stmt_.get(), column) != SQLITE_BLOB) {
+ return false;
+ }
+ auto data = reinterpret_cast<const char*>(
+ sqlite3_column_blob(stmt_.get(), column));
+ value->assign(data, data +
+ sqlite3_column_bytes(stmt_.get(), column));
+ return true;
+ }
+ bool is_null(uint32_t column, bool* value) override {
+ if (!value) { assert(false); return false; }
+ if (!stmt_) return false;
+ if (column >= static_cast<uint32_t>(
+ sqlite3_column_count(stmt_.get()))) return false;
+ return sqlite3_column_type(stmt_.get(), column) == SQLITE_NULL;
+ }
+
+ bool next() override {
+ if (!stmt_) return false;
+ while (true) {
+ switch (sqlite3_step(stmt_.get())) {
+ case SQLITE_BUSY:
+ continue;
+ case SQLITE_DONE:
+ stmt_.reset();
+ return false;
+ case SQLITE_ROW:
+ return true;
+ default:
+ stmt_.reset();
+ return false;
+ }
+ }
+ }
+
+ bool bad() override {
+ return !stmt_;
+ }
+
+ private:
+ uint32_t find_column(const std::string& name) {
+#if SQLITE_ENABLE_COLUMN_METADATA
+ auto ret = column_cache_.insert(std::make_pair(name, 0xfffffffful));
+ if (!ret.second) return ret.first->second;
+ const int count = sqlite3_column_count(stmt_.get());
+ for (int i = 0; i < count; i++) {
+ if (name.compare(sqlite3_column_origin_name(stmt_.get(),
+ i)) == 0) {
+ return ret.first->second = i;
+ }
+ }
+#endif
+ return 0xfffffffful;
+ }
+
+#if SQLITE_ENABLE_COLUMN_METADATA
+ std::map<std::string,int> column_cache_;
+#endif
+
+ unique_stmt stmt_;
+ };
+
+ std::string compile(const Condition& condition) {
+ if (condition.empty()) return "";
+ std::string sql = " WHERE ";
+ compile(sql, condition);
+ return sql;
+ }
+
+ std::string compile(const std::vector<OrderBy>& order_by) {
+ if (order_by.empty()) return "";
+ std::string sql = " ORDER BY ";
+ compile(sql, order_by);
+ return sql;
+ }
+
+ bool bind(unique_stmt& stmt, int index, const std::string& text) {
+ return sqlite3_bind_text(stmt.get(), index, text.data(), text.size(),
+ SQLITE_STATIC) == SQLITE_OK;
+ }
+
+ bool bind(unique_stmt& stmt, int index, int32_t value) {
+ if (sizeof(int) >= sizeof(int32_t)) {
+ return sqlite3_bind_int(stmt.get(), index, value) == SQLITE_OK;
+ } else {
+ return sqlite3_bind_int64(stmt.get(), index, value) == SQLITE_OK;
+ }
+ }
+
+ bool bind(unique_stmt& stmt, int index, int64_t value) {
+ return sqlite3_bind_int64(stmt.get(), index, value) == SQLITE_OK;
+ }
+
+ bool bind(unique_stmt& stmt, int index, bool value) {
+ return sqlite3_bind_int(stmt.get(), index, value ? 1 : 0) == SQLITE_OK;
+ }
+
+ bool bind(unique_stmt& stmt, int index, double value) {
+ return sqlite3_bind_double(stmt.get(), index, value) == SQLITE_OK;
+ }
+
+ bool bind(unique_stmt& stmt, int index, const Value& value) {
+ switch (value.type()) {
+ case Type::STRING:
+ return bind(stmt, index, value.string());
+ case Type::INT32:
+ return bind(stmt, index, value.i32());
+ case Type::INT64:
+ return bind(stmt, index, value.i64());
+ case Type::BOOL:
+ return bind(stmt, index, value.b());
+ case Type::DOUBLE:
+ return bind(stmt, index, value.d());
+ case Type::RAW:
+ return bind(stmt, index, nullptr);
+ }
+ assert(false);
+ return false;
+ }
+
+ bool bind(unique_stmt& stmt, int index, std::nullptr_t) {
+ return sqlite3_bind_null(stmt.get(), index) == SQLITE_OK;
+ }
+
+ bool bind(unique_stmt& stmt, int index, const std::vector<char>& blob) {
+ return sqlite3_bind_blob64(stmt.get(), index, blob.data(),
+ blob.size(), SQLITE_STATIC) == SQLITE_OK;
+ }
+
+ bool bind(unique_stmt& stmt, const Condition& condition, int* index) {
+ switch (condition.mode()) {
+ case Condition::NOOP:
+ return true;
+ case Condition::BOOL_BINARY:
+ return bind(stmt, condition.c1(), index) &&
+ bind(stmt, condition.c2(), index);
+ case Condition::BOOL_UNARY:
+ return bind(stmt, condition.c1(), index);
+ case Condition::COMP_BINARY:
+ return bind(stmt, (*index)++, condition.value());
+ case Condition::COMP_UNARY:
+ return true;
+ }
+ assert(false);
+ return false;
+ }
+
+ void compile(std::string& sql, const Condition& condition) {
+ switch (condition.mode()) {
+ case Condition::NOOP:
+ break;
+ case Condition::BOOL_BINARY:
+ sql += '(';
+ compile(sql, condition.c1());
+ switch (condition.bool_binary_op()) {
+ case Condition::AND:
+ sql += " && ";
+ break;
+ case Condition::OR:
+ sql += " || ";
+ break;
+ }
+ compile(sql, condition.c2());
+ sql += ')';
+ break;
+ case Condition::BOOL_UNARY:
+ switch (condition.bool_unary_op()) {
+ case Condition::NOT:
+ sql += " NOT ";
+ break;
+ }
+ compile(sql, condition.c1());
+ break;
+ case Condition::COMP_BINARY:
+ sql += "(" + safe(condition.column().name());
+ switch (condition.binary_op()) {
+ case Condition::EQUAL:
+ sql += " == ";
+ break;
+ case Condition::NOT_EQUAL:
+ sql += " != ";
+ break;
+ case Condition::LESS_THAN:
+ sql += " < ";
+ break;
+ case Condition::GREATER_THAN:
+ sql += " < ";
+ break;
+ case Condition::LESS_EQUAL:
+ sql += " <= ";
+ break;
+ case Condition::GREATER_EQUAL:
+ sql += " >= ";
+ break;
+ }
+ sql += "?)";
+ break;
+ case Condition::COMP_UNARY:
+ switch (condition.unary_op()) {
+ case Condition::NEGATIVE:
+ sql += "-" + safe(condition.column().name());
+ break;
+ case Condition::IS_NULL:
+ sql += safe(condition.column().name()) + " ISNULL";
+ break;
+ }
+ break;
+ }
+ }
+
+ void compile(std::string& sql, const std::vector<OrderBy>& order_by) {
+ bool first = true;
+ for (const auto& entry : order_by) {
+ if (!first) {
+ sql += ',';
+ } else {
+ first = false;
+ }
+ sql += safe(entry.name());
+ if (!entry.ascending()) {
+ sql += " DESC";
+ }
+ }
+ }
+
+ bool exec(const unique_stmt& stmt) {
+ if (!db_ || !stmt) return false;
+ while (true) {
+ switch (sqlite3_step(stmt.get())) {
+ case SQLITE_BUSY:
+ // Retry
+ continue;
+ case SQLITE_DONE:
+ sqlite3_reset(stmt.get());
+ return true;
+ case SQLITE_ERROR:
+ default:
+ // Error
+ sqlite3_reset(stmt.get());
+ return false;
+ }
+ }
+ }
+
+ void prepare() {
+ static const char* const statements = "BEGIN;COMMIT;ROLLBACK";
+ const char* ptr = statements;
+ if (!prepare(ptr, &stmt_begin_, &ptr) ||
+ !prepare(ptr, &stmt_commit_, &ptr) ||
+ !prepare(ptr, &stmt_rollback_, &ptr)) {
+ bad_ = true;
+ return;
+ }
+ }
+
+ void unprepare() {
+ stmt_begin_.reset();
+ stmt_commit_.reset();
+ stmt_rollback_.reset();
+ }
+
+ bool prepare(const char* str, unique_stmt* stmt, const char** tail) {
+ assert(db_);
+ sqlite3_stmt* ptr;
+ if (sqlite3_prepare_v2(db_, str, -1, &ptr, tail) != SQLITE_OK) {
+ stmt->reset();
+ return false;
+ }
+ stmt->reset(ptr);
+ return true;
+ }
+
+ bool prepare(const std::string& str, unique_stmt* stmt) {
+ assert(db_);
+ sqlite3_stmt* ptr;
+ if (sqlite3_prepare_v2(db_, str.data(), str.size(), &ptr, nullptr) !=
+ SQLITE_OK) {
+ stmt->reset();
+ return false;
+ }
+ stmt->reset(ptr);
+ return true;
+ }
+
+ static const std::string& safe(const std::string& str) {
+ return str;
+ }
+
+ sqlite3 *db_;
+ bool bad_;
+ unique_stmt stmt_begin_;
+ unique_stmt stmt_commit_;
+ unique_stmt stmt_rollback_;
+};
+
+} // namespace
+
+// static
+DB* SQLite3::open(const std::string& path) {
+ std::unique_ptr<DBImpl> db(new DBImpl());
+ db->open(path);
+ return db.release();
+}
+
+} // namespace stuff
diff --git a/src/sqlite3_db.hh b/src/sqlite3_db.hh
new file mode 100644
index 0000000..6e266e9
--- /dev/null
+++ b/src/sqlite3_db.hh
@@ -0,0 +1,17 @@
+#ifndef SQLITE3_DB_HH
+#define SQLITE3_DB_HH
+
+#include "db.hh"
+
+#include <string>
+
+namespace stuff {
+
+class SQLite3 {
+public:
+ static DB* open(const std::string& path);
+};
+
+} // namespace stuff
+
+#endif /* SQLITE3_DB_HH */