diff options
| -rw-r--r-- | .gitignore | 22 | ||||
| -rw-r--r-- | Makefile.am | 7 | ||||
| -rw-r--r-- | configure.ac | 65 | ||||
| -rw-r--r-- | m4/.gitignore | 5 | ||||
| -rw-r--r-- | m4/ax_append_compile_flags.m4 | 65 | ||||
| -rw-r--r-- | m4/ax_append_flag.m4 | 69 | ||||
| -rw-r--r-- | m4/ax_cflags_warn_all.m4 | 122 | ||||
| -rw-r--r-- | m4/ax_check_compile_flag.m4 | 74 | ||||
| -rw-r--r-- | m4/pkg.m4 | 158 | ||||
| -rw-r--r-- | src/.gitignore | 5 | ||||
| -rw-r--r-- | src/Makefile.am | 11 | ||||
| -rw-r--r-- | src/common.hh | 6 | ||||
| -rw-r--r-- | src/db.cc | 60 | ||||
| -rw-r--r-- | src/db.hh | 426 | ||||
| -rw-r--r-- | src/sqlite3_db.cc | 716 | ||||
| -rw-r--r-- | src/sqlite3_db.hh | 17 |
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 */ |
