pax_global_header00006660000000000000000000000064141634277130014522gustar00rootroot0000000000000052 comment=1fa277e344591529c80c56ba47628768f260b2d8 dqlite-1.9.1/000077500000000000000000000000001416342771300130145ustar00rootroot00000000000000dqlite-1.9.1/.clang-format000066400000000000000000000003461416342771300153720ustar00rootroot00000000000000BasedOnStyle: Chromium BreakBeforeBraces: Custom BraceWrapping: AfterFunction: true AfterStruct: true IndentWidth: 8 UseTab: ForContinuationAndIndentation PointerAlignment: Right AllowAllParametersOfDeclarationOnNextLine: false dqlite-1.9.1/.dir-locals.el000066400000000000000000000002661416342771300154510ustar00rootroot00000000000000((nil . ((fill-column . 80))) (c-mode . ((c-file-style . "linux-tabs-only") (flycheck-gcc-definitions . ("_GNU_SOURCE")) (flycheck-clang-definitions . ("_GNU_SOURCE"))))) dqlite-1.9.1/.github/000077500000000000000000000000001416342771300143545ustar00rootroot00000000000000dqlite-1.9.1/.github/workflows/000077500000000000000000000000001416342771300164115ustar00rootroot00000000000000dqlite-1.9.1/.github/workflows/build-and-test.yml000066400000000000000000000023431416342771300217520ustar00rootroot00000000000000name: CI Tests on: - push - pull_request jobs: build-and-test: strategy: fail-fast: false matrix: os: - ubuntu-18.04 - ubuntu-20.04 compiler: - gcc - clang tracing: - LIBDQLITE_TRACE=1 - NOLIBDQLITE_TRACE=1 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v2 - name: Setup dependencies run: | sudo add-apt-repository -y ppa:dqlite/dev sudo apt update sudo apt install -y clang lcov libsqlite3-dev liblz4-dev libuv1-dev libraft-dev - name: Build env: CC: ${{ matrix.compiler }} run: | autoreconf -i ./configure --enable-debug --enable-code-coverage --enable-sanitize make CFLAGS=-O0 -j2 - name: Test env: CC: ${{ matrix.compiler }} run: | export ${{ matrix.tracing }} make CFLAGS=-O0 -j2 check || (cat ./test-suite.log && false) - name: Coverage env: CC: ${{ matrix.compiler }} run: if [ "${CC}" = "gcc" ]; then make code-coverage-capture; fi - name: Upload coverage to Codecov uses: codecov/codecov-action@v1 with: verbose: true dqlite-1.9.1/.github/workflows/cla-check.yml000066400000000000000000000002711416342771300207460ustar00rootroot00000000000000name: Canonical CLA on: - pull_request jobs: cla-check: runs-on: ubuntu-20.04 steps: - name: Check if CLA signed uses: canonical/has-signed-canonical-cla@v1 dqlite-1.9.1/.github/workflows/coverity.yml000066400000000000000000000030161416342771300210000ustar00rootroot00000000000000name: Coverity on: push: branches: - master jobs: test: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v2 - name: Download Coverity Build Tool run: | wget -q https://scan.coverity.com/download/cxx/linux64 --post-data "token=$TOKEN&project=canonical/dqlite" -O cov-analysis-linux64.tar.gz mkdir cov-analysis-linux64 tar xzf cov-analysis-linux64.tar.gz --strip 1 -C cov-analysis-linux64 env: TOKEN: ${{ secrets.COVERITY_SCAN_TOKEN }} - name: Install dependencies run: | sudo add-apt-repository -y ppa:dqlite/dev sudo apt-get update -qq sudo apt-get install -qq gcc libsqlite3-dev liblz4-dev libuv1-dev libraft-dev - name: Run coverity run: | export PATH="$(pwd)/cov-analysis-linux64/bin:${PATH}" # Configure autoreconf -i mkdir build cd build ../configure # Build cov-build --dir cov-int make -j4 tar czvf dqlite.tgz cov-int # Submit the results curl \ --form project=canonical/dqlite \ --form token=${TOKEN} \ --form email=mathieu.bordere@canonical.com \ --form file=@dqlite.tgz \ --form version=master \ --form description="${GITHUB_SHA}" \ https://scan.coverity.com/builds?project=canonical/dqlite env: TOKEN: ${{ secrets.COVERITY_SCAN_TOKEN }} dqlite-1.9.1/.github/workflows/packages.yml000066400000000000000000000024611416342771300207150ustar00rootroot00000000000000name: Build PPA source packages on: - push jobs: build: strategy: fail-fast: false matrix: target: - bionic - focal - impish runs-on: ubuntu-20.04 steps: - name: Clone the repositories run: | git clone https://github.com/canonical/dqlite git clone https://github.com/canonical/dqlite-ppa -b dqlite --depth 1 - name: Setup dependencies run: | sudo apt-get update -qq sudo apt-get install -qq debhelper devscripts - name: Build source package env: DEBFULLNAME: "Github Actions" DEBEMAIL: "noreply@linuxcontainers.org" TARGET: ${{ matrix.target }} run: | cp -R dqlite-ppa/debian dqlite/ cd dqlite/ VERSION="$(git describe --tags | sed -e "s/^v//" -e "s/-/+git/")" dch --create \ --distribution ${TARGET} \ --package dqlite \ --newversion ${VERSION}~${TARGET}1 \ "Automatic build from Github" debuild -S -sa -us -uc -d - name: Upload artifacts uses: actions/upload-artifact@v2 with: name: debian-${{ matrix.target }} if-no-files-found: error path: | *.buildinfo *.changes *.dsc *.tar.* dqlite-1.9.1/.gitignore000066400000000000000000000003741416342771300150100ustar00rootroot00000000000000*.a *.gcda *.gcno *.la *.lo *.log *.o *.so *.trs .deps .dirstamp .libs Makefile Makefile.in aclocal.m4 aminclude_static.am autom4te*.cache confdefs.h config.status configure coverage/ coverage.info unit-test integration-test dqlite.pc libtool stamp-h* dqlite-1.9.1/AUTHORS000066400000000000000000000003601416342771300140630ustar00rootroot00000000000000Unless mentioned otherwise in a specific file's header, all code in this project is released under the LGPL v3 license. The list of authors and contributors can be retrieved from the git commit history and in some cases, the file headers. dqlite-1.9.1/Dockerfile000066400000000000000000000027541416342771300150160ustar00rootroot00000000000000# FROM debian:buster-slim as dqlite-lib-builder FROM ubuntu as dqlite-lib-builder ARG DEBIAN_FRONTEND="noninteractive" ENV TZ=Europe/London ENV LD_LIBRARY_PATH=/usr/local/lib ENV GOROOT=/usr/local/go ENV GOPATH=/go ENV PATH=$GOPATH/bin:$GOROOT/bin:$PATH RUN apt-get update && apt-get install -y git build-essential dh-autoreconf pkg-config libuv1-dev libsqlite3-dev tcl8.6 wget WORKDIR /opt RUN git clone https://github.com/canonical/raft.git && \ git clone https://github.com/canonical/go-dqlite.git && \ wget -c https://golang.org/dl/go1.15.2.linux-amd64.tar.gz -O - | tar -xzf - -C /usr/local WORKDIR /opt/raft RUN autoreconf -i && ./configure && make && make install WORKDIR /opt/dqlite COPY . . RUN autoreconf -i && ./configure && make && make install WORKDIR /opt/go-dqlite RUN go get -d -v ./... && \ go install -tags libsqlite3 ./cmd/dqlite-demo && \ go install -tags libsqlite3 ./cmd/dqlite # FROM debian:buster-slim FROM ubuntu ARG DEBIAN_FRONTEND="noninteractive" ENV TZ=Europe/London ENV LD_LIBRARY_PATH=/usr/local/lib ENV PATH=/opt:$PATH COPY --from=dqlite-lib-builder /go/bin /opt/ COPY --from=dqlite-lib-builder /usr/local/lib /usr/local/lib COPY --from=dqlite-lib-builder \ /usr/lib/x86_64-linux-gnu/libuv.so \ /usr/lib/x86_64-linux-gnu/libuv.so.1\ /usr/lib/x86_64-linux-gnu/libuv.so.1.0.0\ /usr/lib/ COPY --from=dqlite-lib-builder \ /lib/x86_64-linux-gnu/libsqlite3.so \ /lib/x86_64-linux-gnu/libsqlite3.so.0 \ /usr/lib/x86_64-linux-gnu/ dqlite-1.9.1/LICENSE000066400000000000000000000215061416342771300140250ustar00rootroot00000000000000All files in this repository are licensed as follows. If you contribute to this repository, it is assumed that you license your contribution under the same license unless you state otherwise. All files Copyright (C) 2017-2019 Canonical Ltd. unless otherwise specified in the file. This software is licensed under the LGPLv3, included below. As a special exception to the GNU Lesser General Public License version 3 ("LGPL3"), the copyright holders of this Library give you permission to convey to a third party a Combined Work that links statically or dynamically to this Library without providing any Minimal Corresponding Source or Minimal Application Code as set out in 4d or providing the installation information set out in section 4e, provided that you comply with the other provisions of LGPL3 and provided that you meet, for the Application the terms and conditions of the license(s) which apply to the Application. Except as stated in this special exception, the provisions of LGPL3 will continue to comply in full to this Library. If you modify this Library, you may apply this exception to your version of this Library, but you are not obliged to do so. If you do not wish to do so, delete this exception statement from your version. This exception does not (and cannot) modify any license terms which apply to the Application, with which you must still comply. GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. dqlite-1.9.1/Makefile.am000066400000000000000000000057561416342771300150650ustar00rootroot00000000000000ACLOCAL_AMFLAGS = -I m4 AM_CFLAGS += $(CODE_COVERAGE_CFLAGS) AM_CFLAGS += $(SQLITE_CFLAGS) $(UV_CFLAGS) $(RAFT_CFLAGS) $(PTHREAD_CFLAGS) AM_LDFLAGS = $(SQLITE_LIBS) $(UV_LIBS) $(RAFT_LIBS) $(PTHREAD_LIBS) include_HEADERS = include/dqlite.h lib_LTLIBRARIES = libdqlite.la libdqlite_la_LDFLAGS = $(AM_LDFLAGS) -version-info 0:1:0 libdqlite_la_SOURCES = \ src/bind.c \ src/client.c \ src/command.c \ src/conn.c \ src/db.c \ src/dqlite.c \ src/error.c \ src/format.c \ src/fsm.c \ src/gateway.c \ src/leader.c \ src/lib/buffer.c \ src/lib/transport.c \ src/logger.c \ src/message.c \ src/metrics.c \ src/config.c \ src/query.c \ src/registry.c \ src/request.c \ src/response.c \ src/server.c \ src/stmt.c \ src/tracing.c \ src/transport.c \ src/translate.c \ src/tuple.c \ src/vfs.c check_PROGRAMS = \ unit-test \ integration-test TESTS = unit-test integration-test check_LTLIBRARIES = libtest.la libtest_la_CFLAGS = $(AM_CFLAGS) -DMUNIT_TEST_NAME_LEN=60 -Wno-unused-result -Wno-conversion -Wno-maybe-uninitialized -Wno-strict-prototypes -Wno-old-style-definition libtest_la_SOURCES = \ test/lib/endpoint.c \ test/lib/fault.c \ test/lib/fs.c \ test/lib/heap.c \ test/lib/logger.c \ test/lib/munit.c \ test/lib/server.c \ test/lib/sqlite.c \ test/lib/uv.c unit_test_SOURCES = $(libdqlite_la_SOURCES) unit_test_SOURCES += \ test/test_error.c \ test/test_integration.c \ test/unit/ext/test_uv.c \ test/unit/lib/test_buffer.c \ test/unit/lib/test_registry.c \ test/unit/lib/test_serialize.c \ test/unit/lib/test_transport.c \ test/unit/test_command.c \ test/unit/test_conn.c \ test/unit/test_gateway.c \ test/unit/test_concurrency.c \ test/unit/test_registry.c \ test/unit/test_replication.c \ test/unit/test_request.c \ test/unit/test_tuple.c \ test/unit/test_vfs.c \ test/unit/main.c unit_test_CFLAGS = $(AM_CFLAGS) -Wno-maybe-uninitialized -Wno-float-equal -Wno-conversion unit_test_LDFLAGS = $(AM_LDFLAGS) unit_test_LDADD = libtest.la integration_test_SOURCES = \ test/integration/test_client.c \ test/integration/test_membership.c \ test/integration/test_node.c \ test/integration/test_vfs.c \ test/integration/main.c integration_test_CFLAGS = $(AM_CFLAGS) -Wno-conversion integration_test_LDFLAGS = $(AM_LDFLAGS) -no-install integration_test_LDADD = libtest.la libdqlite.la if DEBUG_ENABLED AM_CFLAGS += -g else AM_CFLAGS += -O2 endif if SANITIZE_ENABLED AM_CFLAGS += -fsanitize=address endif if CODE_COVERAGE_ENABLED include $(top_srcdir)/aminclude_static.am CODE_COVERAGE_DIRECTORY=./src CODE_COVERAGE_OUTPUT_DIRECTORY=coverage CODE_COVERAGE_OUTPUT_FILE=coverage.info CODE_COVERAGE_IGNORE_PATTERN="/usr/include/*" CODE_COVERAGE_BRANCH_COVERAGE=1 CODE_COVERAGE_LCOV_OPTIONS=$(CODE_COVERAGE_LCOV_OPTIONS_DEFAULT) --rc lcov_excl_br_line="assert\(" clean-local: code-coverage-clean distclean-local: code-coverage-dist-clean endif # CODE_COVERAGE_ENABLED pkgconfigdir = $(libdir)/pkgconfig pkgconfig_DATA = dqlite.pc dqlite-1.9.1/README.md000066400000000000000000000056431416342771300143030ustar00rootroot00000000000000dqlite [![Build Status](https://travis-ci.org/canonical/dqlite.png)](https://travis-ci.org/canonical/dqlite) [![codecov](https://codecov.io/gh/canonical/dqlite/branch/master/graph/badge.svg)](https://codecov.io/gh/canonical/dqlite) ====== [dqlite](https://dqlite.io) is a C library that implements an embeddable and replicated SQL database engine with high-availability and automatic failover. The acronym "dqlite" stands for "distributed SQLite", meaning that dqlite extends [SQLite](https://sqlite.org/) with a network protocol that can connect together various instances of your application and have them act as a highly-available cluster, with no dependency on external databases. Design highlights ---------------- * Asynchronous single-threaded implementation using [libuv](https://libuv.org/) as event loop. * Custom wire protocol optimized for SQLite primitives and data types. * Data replication based on the [Raft](https://raft.github.io/) algorithm and its efficient [C-raft](https://github.com/canonical/raft) implementation. License ------- The dqlite library is released under a slightly modified version of LGPLv3, that includes a copyright exception allowing users to statically link the library code in their project and release the final work under their own terms. See the full [license](https://github.com/canonical/dqlite/blob/master/LICENSE) text. Try it ------- The simplest way to see dqlite in action is to use the demo program that comes with the Go dqlite bindings. Please see the [relevant documentation](https://github.com/canonical/go-dqlite#demo) in that project. Media ----- A talk about dqlite was given at FOSDEM 2020, you can watch it [here](https://fosdem.org/2020/schedule/event/dqlite/). Wire protocol ------------- If you wish to write a client, please refer to the [wire protocol](https://dqlite.io/docs/protocol) documentation. Install ------- If you are on a Debian-based system, you can get the latest development release from dqlite's [dev PPA](https://launchpad.net/~dqlite/+archive/ubuntu/dev): ``` sudo add-apt-repository ppa:dqlite/dev sudo apt-get update sudo apt-get install libdqlite-dev ``` Build ----- To build ``libdqlite`` from source you'll need: * A reasonably recent version of [libuv](http://libuv.org/) (v1.8.0 or beyond). * A reasonably recent version of sqlite3-dev * A build of the [C-raft](https://github.com/canonical/raft) Raft library. Your distribution should already provide you with a pre-built libuv shared library and libsqlite3-dev. To build the raft library: ``` git clone https://github.com/canonical/raft.git cd raft autoreconf -i ./configure make sudo make install cd .. ``` Once all the required libraries are installed, in order to build the dqlite shared library itself, you can run: ``` autoreconf -i ./configure make sudo make install ``` Usage Notes ----------- Detailed tracing will be enabled when the environment variable `LIBDQLITE_TRACE` is set before startup. dqlite-1.9.1/VERSION000066400000000000000000000000051416342771300140570ustar00rootroot000000000000000.1.0dqlite-1.9.1/ac/000077500000000000000000000000001416342771300133775ustar00rootroot00000000000000dqlite-1.9.1/ac/.gitignore000066400000000000000000000000161416342771300153640ustar00rootroot00000000000000* !.gitignore dqlite-1.9.1/configure.ac000066400000000000000000000045701416342771300153100ustar00rootroot00000000000000AC_PREREQ(2.60) AC_INIT([libdqlite], [1.9.0], [https://github.com/canonical/dqlite]) AC_CONFIG_MACRO_DIR([m4]) AC_CONFIG_AUX_DIR([ac]) AM_INIT_AUTOMAKE([subdir-objects -Wall -Werror -Wno-portability foreign]) AM_SILENT_RULES([yes]) AC_PROG_CC_STDC AC_USE_SYSTEM_EXTENSIONS AX_PTHREAD LT_INIT # TODO: eventually enable this # AX_CHECK_COMPILE_FLAG([-Weverything], AM_CFLAGS+=" -Weverything") # Whether to enable debugging code. AC_ARG_ENABLE(debug, AS_HELP_STRING([--enable-debug[=ARG]], [enable debugging [default=no]])) AM_CONDITIONAL(DEBUG_ENABLED, test "x$enable_debug" = "xyes") # Whether to enable memory sanitizer. AC_ARG_ENABLE(sanitize, AS_HELP_STRING([--enable-sanitize[=ARG]], [enable code sanitizers [default=no]])) AM_CONDITIONAL(SANITIZE_ENABLED, test x"$enable_sanitize" = x"yes") AM_COND_IF(SANITIZE_ENABLED, AX_CHECK_COMPILE_FLAG([-fsanitize=address], [true], [AC_MSG_ERROR([address sanitizer not supported])])) # Whether to enable code coverage. AX_CODE_COVERAGE # Checks for header files. AC_CHECK_HEADERS([arpa/inet.h fcntl.h stdint.h stdlib.h string.h sys/socket.h unistd.h]) # Checks for typedefs, structures, and compiler characteristics. AC_TYPE_SIZE_T AC_TYPE_SSIZE_T AC_TYPE_UINT16_T AC_TYPE_UINT32_T AC_TYPE_UINT8_T # Enable large file support. This is mandatory in order to interoperate with # libuv, which enables large file support by default, making the size of 'off_t' # on 32-bit architecture be 8 bytes instead of the normal 4. AC_SYS_LARGEFILE # Checks for libraries PKG_CHECK_MODULES(SQLITE, [sqlite3 >= 3.22.0], [], []) PKG_CHECK_MODULES(UV, [libuv >= 1.8.0], [], []) PKG_CHECK_MODULES(RAFT, [raft], [], []) CC_CHECK_FLAGS_APPEND([AM_CFLAGS],[CFLAGS],[ \ -std=c11 \ -g \ --mcet \ -fcf-protection \ --param=ssp-buffer-size=4 \ -pipe \ -fno-strict-aliasing \ -fdiagnostics-color \ -fexceptions \ -fstack-clash-protection \ -fstack-protector-strong \ -fasynchronous-unwind-tables \ -fdiagnostics-show-option \ -Wall \ -Wextra \ -Wimplicit-fallthrough=5 \ -Wcast-align \ -Wstrict-prototypes \ -Wlogical-op \ -Wmissing-include-dirs \ -Wold-style-definition \ -Winit-self \ -Wfloat-equal \ -Wsuggest-attribute=noreturn \ -Wformat=2 \ -Wshadow \ -Wendif-labels \ -Wdate-time \ -Wnested-externs \ -Wconversion \ ]) # To enable: # # -Wpedantic \ AC_SUBST(AM_CFLAGS) AC_CONFIG_FILES([dqlite.pc Makefile]) AC_OUTPUT dqlite-1.9.1/doc/000077500000000000000000000000001416342771300135615ustar00rootroot00000000000000dqlite-1.9.1/doc/faq.md000066400000000000000000000000741416342771300146530ustar00rootroot00000000000000Moved to the [website project](https://dqlite.io/docs/faq). dqlite-1.9.1/doc/index.md000066400000000000000000000000701416342771300152070ustar00rootroot00000000000000Moved to the [website project](https://dqlite.io/docs). dqlite-1.9.1/doc/protocol.md000066400000000000000000000001011416342771300157340ustar00rootroot00000000000000Moved to the [website project](https://dqlite.io/docs/protocol). dqlite-1.9.1/dqlite.pc.in000066400000000000000000000004101416342771300152220ustar00rootroot00000000000000prefix=@prefix@ exec_prefix=@exec_prefix@ libdir=@libdir@ includedir=@includedir@ Name: dqlite Description: Distributed SQLite engine Version: @PACKAGE_VERSION@ Libs: -L${libdir} -ldqlite Libs.private: @SQLITE_LIBS@ @UV_LIBS@ @RAFT_LIBS@ Cflags: -I${includedir} dqlite-1.9.1/include/000077500000000000000000000000001416342771300144375ustar00rootroot00000000000000dqlite-1.9.1/include/dqlite.h000066400000000000000000000310211416342771300160670ustar00rootroot00000000000000#ifndef DQLITE_H #define DQLITE_H #include #include #include /** * Version. */ #define DQLITE_VERSION_MAJOR 1 #define DQLITE_VERSION_MINOR 9 #define DQLITE_VERSION_RELEASE 0 #define DQLITE_VERSION_NUMBER (DQLITE_VERSION_MAJOR *100*100 + DQLITE_VERSION_MINOR *100 + DQLITE_VERSION_RELEASE) int dqlite_version_number (void); /** * Error codes. */ #define DQLITE_ERROR 1 /* Generic error */ #define DQLITE_MISUSE 2 /* Library used incorrectly */ #define DQLITE_NOMEM 3 /* A malloc() failed */ /** * Dqlite node handle. * * Opaque handle to a single dqlite node that can serve database requests from * connected clients and exchanges data replication messages with other dqlite * nodes. */ typedef struct dqlite_node dqlite_node; /** * Hold the value of a dqlite node ID. Guaranteed to be at least 64-bit long. */ typedef unsigned long long dqlite_node_id; /** * Create a new dqlite node object. * * The @id argument a is positive number that identifies this particular dqlite * node in the cluster. Each dqlite node part of the same cluster must be * created with a different ID. The very first node, used to bootstrap a new * cluster, must have ID #1. Every time a node is started again, it must be * passed the same ID. * * The @address argument is the network address that clients or other nodes in * the cluster must use to connect to this dqlite node. If no custom connect * function is going to be set using dqlite_node_set_connect_func(), then the * format of the string must be ":", where is an IPv4/IPv6 * address or a DNS name, and is a port number. Otherwise if a custom * connect function is used, then the format of the string must by whatever the * custom connect function accepts. * * The @data_dir argument the file system path where the node should store its * durable data, such as Raft log entries containing WAL frames of the SQLite * databases being replicated. * * No reference to the memory pointed to by @address and @data_dir is kept by * the dqlite library, so any memory associated with them can be released after * the function returns. */ int dqlite_node_create(dqlite_node_id id, const char *address, const char *data_dir, dqlite_node **n); /** * Destroy a dqlite node object. * * This will release all memory that was allocated by the node. If * dqlite_node_start() was successfully invoked, then dqlite_node_stop() must be * invoked before destroying the node. */ void dqlite_node_destroy(dqlite_node *n); /** * Instruct the dqlite node to bind a network address when starting, and * listening for incoming client connections. * * The given address might match the one passed to @dqlite_node_create or be a * different one (for example if the application wants to proxy it). * * The format of the @address argument must be either ":", where * is an IPv4/IPv6 address or a DNS name and is a port number, or * "@", where is an abstract Unix socket path. The special string * "@" can be used to automatically select an available abstract Unix socket * path, which can then be retrieved with dqlite_node_get_bind_address(). * If an abstract Unix socket is used the dqlite node will accept only * connections originating from the same process. * * No reference to the memory pointed to by @address is kept, so any memory * associated with them can be released after the function returns. * * This function must be called before calling dqlite_node_start(). */ int dqlite_node_set_bind_address(dqlite_node *n, const char *address); /** * Get the network address that the dqlite node is using to accept incoming * connections. */ const char *dqlite_node_get_bind_address(dqlite_node *n); /** * Set a custom connect function. * * The function should block until a network connection with the dqlite node at * the given @address is established, or an error occurs. * * In case of success, the file descriptor of the connected socket must be saved * into the location pointed by the @fd argument. The socket must be either a * TCP or a Unix socket. * * This function must be called before calling dqlite_node_start(). */ int dqlite_node_set_connect_func(dqlite_node *n, int (*f)(void *arg, const char *address, int *fd), void *arg); /** * DEPRECATED - USE `dqlite_node_set_network_latency_ms` * Set the average one-way network latency, expressed in nanoseconds. * * This value is used internally by dqlite to decide how frequently the leader * node should send heartbeats to other nodes in order to maintain its * leadership, and how long other nodes should wait before deciding that the * leader has died and initiate a failover. * * This function must be called before calling dqlite_node_start(). */ int dqlite_node_set_network_latency(dqlite_node *n, unsigned long long nanoseconds); /** * Set the average one-way network latency, expressed in milliseconds. * * This value is used internally by dqlite to decide how frequently the leader * node should send heartbeats to other nodes in order to maintain its * leadership, and how long other nodes should wait before deciding that the * leader has died and initiate a failover. * * This function must be called before calling dqlite_node_start(). * * Latency should not be 0 or larger than 3600000 milliseconds. */ int dqlite_node_set_network_latency_ms(dqlite_node *t, unsigned milliseconds); /** * Set the failure domain associated with this node. * * This is effectively a tag applied to the node and that can be inspected later * with the "Describe node" client request. */ int dqlite_node_set_failure_domain(dqlite_node *n, unsigned long long code); /** * Set the snapshot parameters for this node. * * This function determines how frequently a node will snapshot the state * of the database and how many raft log entries will be kept around after * a snapshot has been taken. * * `snapshot_threshold` : Determines the frequency of taking a snapshot, the * lower the number, the higher the frequency. * * `snapshot_trailing` : Determines the amount of log entries kept around after * taking a snapshot. Lowering this number decreases disk and memory footprint * but increases the chance of having to send a full snapshot (instead of a * number of log entries to a node that has fallen behind. * * This function must be called before calling dqlite_node_start(). */ int dqlite_node_set_snapshot_params(dqlite_node *n, unsigned snapshot_threshold, unsigned snapshot_trailing); /** * Start a dqlite node. * * A background thread will be spawned which will run the node's main loop. If * this function returns successfully, the dqlite node is ready to accept new * connections. */ int dqlite_node_start(dqlite_node *n); /** * Stop a dqlite node. * * The background thread running the main loop will be notified and the node * will not accept any new client connections. Once inflight requests are * completed, open client connections get closed and then the thread exits. */ int dqlite_node_stop(dqlite_node *n); struct dqlite_node_info { dqlite_node_id id; const char *address; }; typedef struct dqlite_node_info dqlite_node_info; /* Defined to be an extensible struct, future additions to this struct should be * 64-bits wide and 0 should not be used as a valid value. */ struct dqlite_node_info_ext { uint64_t size; /* The size of this struct */ uint64_t id; /* dqlite_node_id */ uint64_t address; uint64_t dqlite_role; }; typedef struct dqlite_node_info_ext dqlite_node_info_ext; #define DQLITE_NODE_INFO_EXT_SZ_ORIG 32U /* (4 * 64) / 8 */ /** * !!! Deprecated, use `dqlite_node_recover_ext` instead which also includes * dqlite roles. !!! * * Force recovering a dqlite node which is part of a cluster whose majority of * nodes have died, and therefore has become unavailable. * * In order for this operation to be safe you must follow these steps: * * 1. Make sure no dqlite node in the cluster is running. * * 2. Identify all dqlite nodes that have survived and that you want to be part * of the recovered cluster. * * 3. Among the survived dqlite nodes, find the one with the most up-to-date * raft term and log. * * 4. Invoke @dqlite_node_recover exactly one time, on the node you found in * step 3, and pass it an array of #dqlite_node_info filled with the IDs and * addresses of the survived nodes, including the one being recovered. * * 5. Copy the data directory of the node you ran @dqlite_node_recover on to all * other non-dead nodes in the cluster, replacing their current data * directory. * * 6. Restart all nodes. */ int dqlite_node_recover(dqlite_node *n, dqlite_node_info infos[], int n_info); /** * Force recovering a dqlite node which is part of a cluster whose majority of * nodes have died, and therefore has become unavailable. * * In order for this operation to be safe you must follow these steps: * * 1. Make sure no dqlite node in the cluster is running. * * 2. Identify all dqlite nodes that have survived and that you want to be part * of the recovered cluster. * * 3. Among the survived dqlite nodes, find the one with the most up-to-date * raft term and log. * * 4. Invoke @dqlite_node_recover_ext exactly one time, on the node you found in * step 3, and pass it an array of #dqlite_node_info filled with the IDs, * addresses and roles of the survived nodes, including the one being recovered. * * 5. Copy the data directory of the node you ran @dqlite_node_recover_ext on to all * other non-dead nodes in the cluster, replacing their current data * directory. * * 6. Restart all nodes. */ int dqlite_node_recover_ext(dqlite_node *n, dqlite_node_info_ext infos[], int n_info); /** * Return a human-readable description of the last error occurred. */ const char *dqlite_node_errmsg(dqlite_node *n); /** * Generate a unique ID for the given address. */ dqlite_node_id dqlite_generate_node_id(const char *address); /** * Initialize the given SQLite VFS interface object with dqlite's custom * implementation, which can be used for replication. */ int dqlite_vfs_init(sqlite3_vfs *vfs, const char *name); /** * Release all memory used internally by a SQLite VFS object that was * initialized using @qlite_vfs_init. */ void dqlite_vfs_close(sqlite3_vfs *vfs); /** * A single WAL frame to be replicated. */ struct dqlite_vfs_frame { unsigned long page_number; /* Database page number. */ void *data; /* Content of the database page. */ }; typedef struct dqlite_vfs_frame dqlite_vfs_frame; /** * Check if the last call to sqlite3_step() has triggered a write transaction on * the database with the given filename. In that case acquire a WAL write lock * to prevent further write transactions, and return all new WAL frames * generated by the transaction. These frames are meant to be replicated across * nodes and then actually added to the WAL with dqlite_vfs_apply() once a * quorum is reached. If a quorum is not reached within a given time, then * dqlite_vfs_abort() can be used to abort and release the WAL write lock. */ int dqlite_vfs_poll(sqlite3_vfs *vfs, const char *filename, dqlite_vfs_frame **frames, unsigned *n); /** * Add to the WAL all frames that were generated by a write transaction * triggered by sqlite3_step() and that were obtained via dqlite_vfs_poll(). * * This interface is designed to match the typical use case of a node receiving * the frames by sequentially reading a byte stream from a network socket and * passing the data to this routine directly without any copy or futher * allocation, possibly except for integer encoding/decoding. */ int dqlite_vfs_apply(sqlite3_vfs *vfs, const char *filename, unsigned n, unsigned long *page_numbers, void *frames); /** * Abort a pending write transaction that was triggered by sqlite3_step() and * whose frames were obtained via dqlite_vfs_poll(). * * This should be called if the transaction could not be safely replicated. In * particular it will release the write lock acquired by dqlite_vfs_poll(). */ int dqlite_vfs_abort(sqlite3_vfs *vfs, const char *filename); /** * Return a snapshot of the main database file and of the WAL file. */ int dqlite_vfs_snapshot(sqlite3_vfs *vfs, const char *filename, void **data, size_t *n); /** * Restore a snapshot of the main database file and of the WAL file. */ int dqlite_vfs_restore(sqlite3_vfs *vfs, const char *filename, const void *data, size_t n); #endif /* DQLITE_H */ dqlite-1.9.1/m4/000077500000000000000000000000001416342771300133345ustar00rootroot00000000000000dqlite-1.9.1/m4/.gitignore000066400000000000000000000003771416342771300153330ustar00rootroot00000000000000*.m4 !attributes.m4 !ax_ac_append_to_file.m4 !ax_ac_print_to_file.m4 !ax_add_am_macro_static.m4 !ax_am_macros_static.m4 !ax_check_compile_flag.m4 !ax_check_gnu_make.m4 !ax_code_coverage.m4 !ax_compare_version.m4 !ax_file_escapes.m4 !ax_pthread.m4 !pkg.m4 dqlite-1.9.1/m4/attributes.m4000066400000000000000000000240211416342771300157630ustar00rootroot00000000000000dnl Macros to check the presence of generic (non-typed) symbols. dnl Copyright (c) 2006-2008 Diego Pettenò dnl Copyright (c) 2006-2008 xine project dnl Copyright (c) 2012 Lucas De Marchi dnl dnl This program is free software; you can redistribute it and/or modify dnl it under the terms of the GNU General Public License as published by dnl the Free Software Foundation; either version 2, or (at your option) dnl any later version. dnl dnl This program is distributed in the hope that it will be useful, dnl but WITHOUT ANY WARRANTY; without even the implied warranty of dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the dnl GNU General Public License for more details. dnl dnl You should have received a copy of the GNU General Public License dnl along with this program; if not, write to the Free Software dnl Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA dnl 02110-1301, USA. dnl dnl As a special exception, the copyright owners of the dnl macro gives unlimited permission to copy, distribute and modify the dnl configure scripts that are the output of Autoconf when processing the dnl Macro. You need not follow the terms of the GNU General Public dnl License when using or distributing such scripts, even though portions dnl of the text of the Macro appear in them. The GNU General Public dnl License (GPL) does govern all other use of the material that dnl constitutes the Autoconf Macro. dnl dnl This special exception to the GPL applies to versions of the dnl Autoconf Macro released by this project. When you make and dnl distribute a modified version of the Autoconf Macro, you may extend dnl this special exception to the GPL to apply to your modified version as dnl well. dnl Check if FLAG in ENV-VAR is supported by compiler and append it dnl to WHERE-TO-APPEND variable. Note that we invert -Wno-* checks to dnl -W* as gcc cannot test for negated warnings. If a C snippet is passed, dnl use it, otherwise use a simple main() definition that just returns 0. dnl CC_CHECK_FLAG_APPEND([WHERE-TO-APPEND], [ENV-VAR], [FLAG], [C-SNIPPET]) AC_DEFUN([CC_CHECK_FLAG_APPEND], [ AC_CACHE_CHECK([if $CC supports flag $3 in envvar $2], AS_TR_SH([cc_cv_$2_$3]), [eval "AS_TR_SH([cc_save_$2])='${$2}'" eval "AS_TR_SH([$2])='${cc_save_$2} -Werror `echo "$3" | sed 's/^-Wno-/-W/'`'" AC_LINK_IFELSE([AC_LANG_SOURCE(ifelse([$4], [], [int main(void) { return 0; } ], [$4]))], [eval "AS_TR_SH([cc_cv_$2_$3])='yes'"], [eval "AS_TR_SH([cc_cv_$2_$3])='no'"]) eval "AS_TR_SH([$2])='$cc_save_$2'"]) AS_IF([eval test x$]AS_TR_SH([cc_cv_$2_$3])[ = xyes], [eval "$1='${$1} $3'"]) ]) dnl CC_CHECK_FLAGS_APPEND([WHERE-TO-APPEND], [ENV-VAR], [FLAG1 FLAG2], [C-SNIPPET]) AC_DEFUN([CC_CHECK_FLAGS_APPEND], [ for flag in [$3]; do CC_CHECK_FLAG_APPEND([$1], [$2], $flag, [$4]) done ]) dnl Check if the flag is supported by linker (cacheable) dnl CC_CHECK_LDFLAGS([FLAG], [ACTION-IF-FOUND],[ACTION-IF-NOT-FOUND]) AC_DEFUN([CC_CHECK_LDFLAGS], [ AC_CACHE_CHECK([if $CC supports $1 flag], AS_TR_SH([cc_cv_ldflags_$1]), [ac_save_LDFLAGS="$LDFLAGS" LDFLAGS="$LDFLAGS $1" AC_LINK_IFELSE([int main() { return 1; }], [eval "AS_TR_SH([cc_cv_ldflags_$1])='yes'"], [eval "AS_TR_SH([cc_cv_ldflags_$1])="]) LDFLAGS="$ac_save_LDFLAGS" ]) AS_IF([eval test x$]AS_TR_SH([cc_cv_ldflags_$1])[ = xyes], [$2], [$3]) ]) dnl define the LDFLAGS_NOUNDEFINED variable with the correct value for dnl the current linker to avoid undefined references in a shared object. AC_DEFUN([CC_NOUNDEFINED], [ dnl We check $host for which systems to enable this for. AC_REQUIRE([AC_CANONICAL_HOST]) case $host in dnl FreeBSD (et al.) does not complete linking for shared objects when pthreads dnl are requested, as different implementations are present; to avoid problems dnl use -Wl,-z,defs only for those platform not behaving this way. *-freebsd* | *-openbsd*) ;; *) dnl First of all check for the --no-undefined variant of GNU ld. This allows dnl for a much more readable command line, so that people can understand what dnl it does without going to look for what the heck -z defs does. for possible_flags in "-Wl,--no-undefined" "-Wl,-z,defs"; do CC_CHECK_LDFLAGS([$possible_flags], [LDFLAGS_NOUNDEFINED="$possible_flags"]) break done ;; esac AC_SUBST([LDFLAGS_NOUNDEFINED]) ]) dnl Check for a -Werror flag or equivalent. -Werror is the GCC dnl and ICC flag that tells the compiler to treat all the warnings dnl as fatal. We usually need this option to make sure that some dnl constructs (like attributes) are not simply ignored. dnl dnl Other compilers don't support -Werror per se, but they support dnl an equivalent flag: dnl - Sun Studio compiler supports -errwarn=%all AC_DEFUN([CC_CHECK_WERROR], [ AC_CACHE_CHECK( [for $CC way to treat warnings as errors], [cc_cv_werror], [CC_CHECK_CFLAGS_SILENT([-Werror], [cc_cv_werror=-Werror], [CC_CHECK_CFLAGS_SILENT([-errwarn=%all], [cc_cv_werror=-errwarn=%all])]) ]) ]) AC_DEFUN([CC_CHECK_ATTRIBUTE], [ AC_REQUIRE([CC_CHECK_WERROR]) AC_CACHE_CHECK([if $CC supports __attribute__(( ifelse([$2], , [$1], [$2]) ))], AS_TR_SH([cc_cv_attribute_$1]), [ac_save_CFLAGS="$CFLAGS" CFLAGS="$CFLAGS $cc_cv_werror" AC_COMPILE_IFELSE([AC_LANG_SOURCE([$3])], [eval "AS_TR_SH([cc_cv_attribute_$1])='yes'"], [eval "AS_TR_SH([cc_cv_attribute_$1])='no'"]) CFLAGS="$ac_save_CFLAGS" ]) AS_IF([eval test x$]AS_TR_SH([cc_cv_attribute_$1])[ = xyes], [AC_DEFINE( AS_TR_CPP([SUPPORT_ATTRIBUTE_$1]), 1, [Define this if the compiler supports __attribute__(( ifelse([$2], , [$1], [$2]) ))] ) $4], [$5]) ]) AC_DEFUN([CC_ATTRIBUTE_CONSTRUCTOR], [ CC_CHECK_ATTRIBUTE( [constructor],, [void __attribute__((constructor)) ctor() { int a; }], [$1], [$2]) ]) AC_DEFUN([CC_ATTRIBUTE_FORMAT], [ CC_CHECK_ATTRIBUTE( [format], [format(printf, n, n)], [void __attribute__((format(printf, 1, 2))) printflike(const char *fmt, ...) { fmt = (void *)0; }], [$1], [$2]) ]) AC_DEFUN([CC_ATTRIBUTE_FORMAT_ARG], [ CC_CHECK_ATTRIBUTE( [format_arg], [format_arg(printf)], [char *__attribute__((format_arg(1))) gettextlike(const char *fmt) { fmt = (void *)0; }], [$1], [$2]) ]) AC_DEFUN([CC_ATTRIBUTE_VISIBILITY], [ CC_CHECK_ATTRIBUTE( [visibility_$1], [visibility("$1")], [void __attribute__((visibility("$1"))) $1_function() { }], [$2], [$3]) ]) AC_DEFUN([CC_ATTRIBUTE_NONNULL], [ CC_CHECK_ATTRIBUTE( [nonnull], [nonnull()], [void __attribute__((nonnull())) some_function(void *foo, void *bar) { foo = (void*)0; bar = (void*)0; }], [$1], [$2]) ]) AC_DEFUN([CC_ATTRIBUTE_UNUSED], [ CC_CHECK_ATTRIBUTE( [unused], , [void some_function(void *foo, __attribute__((unused)) void *bar);], [$1], [$2]) ]) AC_DEFUN([CC_ATTRIBUTE_SENTINEL], [ CC_CHECK_ATTRIBUTE( [sentinel], , [void some_function(void *foo, ...) __attribute__((sentinel));], [$1], [$2]) ]) AC_DEFUN([CC_ATTRIBUTE_DEPRECATED], [ CC_CHECK_ATTRIBUTE( [deprecated], , [void some_function(void *foo, ...) __attribute__((deprecated));], [$1], [$2]) ]) AC_DEFUN([CC_ATTRIBUTE_ALIAS], [ CC_CHECK_ATTRIBUTE( [alias], [weak, alias], [void other_function(void *foo) { } void some_function(void *foo) __attribute__((weak, alias("other_function")));], [$1], [$2]) ]) AC_DEFUN([CC_ATTRIBUTE_MALLOC], [ CC_CHECK_ATTRIBUTE( [malloc], , [void * __attribute__((malloc)) my_alloc(int n);], [$1], [$2]) ]) AC_DEFUN([CC_ATTRIBUTE_PACKED], [ CC_CHECK_ATTRIBUTE( [packed], , [struct astructure { char a; int b; long c; void *d; } __attribute__((packed));], [$1], [$2]) ]) AC_DEFUN([CC_ATTRIBUTE_CONST], [ CC_CHECK_ATTRIBUTE( [const], , [int __attribute__((const)) twopow(int n) { return 1 << n; } ], [$1], [$2]) ]) AC_DEFUN([CC_FLAG_VISIBILITY], [ AC_REQUIRE([CC_CHECK_WERROR]) AC_CACHE_CHECK([if $CC supports -fvisibility=hidden], [cc_cv_flag_visibility], [cc_flag_visibility_save_CFLAGS="$CFLAGS" CFLAGS="$CFLAGS $cc_cv_werror" CC_CHECK_CFLAGS_SILENT([-fvisibility=hidden], cc_cv_flag_visibility='yes', cc_cv_flag_visibility='no') CFLAGS="$cc_flag_visibility_save_CFLAGS"]) AS_IF([test "x$cc_cv_flag_visibility" = "xyes"], [AC_DEFINE([SUPPORT_FLAG_VISIBILITY], 1, [Define this if the compiler supports the -fvisibility flag]) $1], [$2]) ]) AC_DEFUN([CC_FUNC_EXPECT], [ AC_REQUIRE([CC_CHECK_WERROR]) AC_CACHE_CHECK([if compiler has __builtin_expect function], [cc_cv_func_expect], [ac_save_CFLAGS="$CFLAGS" CFLAGS="$CFLAGS $cc_cv_werror" AC_COMPILE_IFELSE([AC_LANG_SOURCE( [int some_function() { int a = 3; return (int)__builtin_expect(a, 3); }])], [cc_cv_func_expect=yes], [cc_cv_func_expect=no]) CFLAGS="$ac_save_CFLAGS" ]) AS_IF([test "x$cc_cv_func_expect" = "xyes"], [AC_DEFINE([SUPPORT__BUILTIN_EXPECT], 1, [Define this if the compiler supports __builtin_expect() function]) $1], [$2]) ]) AC_DEFUN([CC_ATTRIBUTE_ALIGNED], [ AC_REQUIRE([CC_CHECK_WERROR]) AC_CACHE_CHECK([highest __attribute__ ((aligned ())) supported], [cc_cv_attribute_aligned], [ac_save_CFLAGS="$CFLAGS" CFLAGS="$CFLAGS $cc_cv_werror" for cc_attribute_align_try in 64 32 16 8 4 2; do AC_COMPILE_IFELSE([AC_LANG_SOURCE([ int main() { static char c __attribute__ ((aligned($cc_attribute_align_try))) = 0; return c; }])], [cc_cv_attribute_aligned=$cc_attribute_align_try; break]) done CFLAGS="$ac_save_CFLAGS" ]) if test "x$cc_cv_attribute_aligned" != "x"; then AC_DEFINE_UNQUOTED([ATTRIBUTE_ALIGNED_MAX], [$cc_cv_attribute_aligned], [Define the highest alignment supported]) fi ]) dqlite-1.9.1/m4/ax_ac_append_to_file.m4000066400000000000000000000016221416342771300177020ustar00rootroot00000000000000# =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_ac_append_to_file.html # =========================================================================== # # SYNOPSIS # # AX_AC_APPEND_TO_FILE([FILE],[DATA]) # # DESCRIPTION # # Appends the specified data to the specified Autoconf is run. If you want # to append to a file when configure is run use AX_APPEND_TO_FILE instead. # # LICENSE # # Copyright (c) 2009 Allan Caffee # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 10 AC_DEFUN([AX_AC_APPEND_TO_FILE],[ AC_REQUIRE([AX_FILE_ESCAPES]) m4_esyscmd( AX_FILE_ESCAPES [ printf "%s" "$2" >> "$1" ]) ]) dqlite-1.9.1/m4/ax_ac_print_to_file.m4000066400000000000000000000016111416342771300175650ustar00rootroot00000000000000# =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_ac_print_to_file.html # =========================================================================== # # SYNOPSIS # # AX_AC_PRINT_TO_FILE([FILE],[DATA]) # # DESCRIPTION # # Writes the specified data to the specified file when Autoconf is run. If # you want to print to a file when configure is run use AX_PRINT_TO_FILE # instead. # # LICENSE # # Copyright (c) 2009 Allan Caffee # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 10 AC_DEFUN([AX_AC_PRINT_TO_FILE],[ m4_esyscmd( AC_REQUIRE([AX_FILE_ESCAPES]) [ printf "%s" "$2" > "$1" ]) ]) dqlite-1.9.1/m4/ax_add_am_macro_static.m4000066400000000000000000000015251416342771300202260ustar00rootroot00000000000000# =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_add_am_macro_static.html # =========================================================================== # # SYNOPSIS # # AX_ADD_AM_MACRO_STATIC([RULE]) # # DESCRIPTION # # Adds the specified rule to $AMINCLUDE. # # LICENSE # # Copyright (c) 2009 Tom Howard # Copyright (c) 2009 Allan Caffee # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 8 AC_DEFUN([AX_ADD_AM_MACRO_STATIC],[ AC_REQUIRE([AX_AM_MACROS_STATIC]) AX_AC_APPEND_TO_FILE(AMINCLUDE_STATIC,[$1]) ]) dqlite-1.9.1/m4/ax_am_macros_static.m4000066400000000000000000000021251416342771300175760ustar00rootroot00000000000000# =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_am_macros_static.html # =========================================================================== # # SYNOPSIS # # AX_AM_MACROS_STATIC # # DESCRIPTION # # Adds support for macros that create Automake rules. You must manually # add the following line # # include $(top_srcdir)/aminclude_static.am # # to your Makefile.am files. # # LICENSE # # Copyright (c) 2009 Tom Howard # Copyright (c) 2009 Allan Caffee # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 11 AC_DEFUN([AMINCLUDE_STATIC],[aminclude_static.am]) AC_DEFUN([AX_AM_MACROS_STATIC], [ AX_AC_PRINT_TO_FILE(AMINCLUDE_STATIC,[ # ]AMINCLUDE_STATIC[ generated automatically by Autoconf # from AX_AM_MACROS_STATIC on ]m4_esyscmd([LC_ALL=C date])[ ]) ]) dqlite-1.9.1/m4/ax_check_compile_flag.m4000066400000000000000000000040701416342771300200450ustar00rootroot00000000000000# =========================================================================== # https://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 # Copyright (c) 2011 Maarten Bosmans # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 6 AC_DEFUN([AX_CHECK_COMPILE_FLAG], [AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_IF 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_VAR_IF(CACHEVAR,yes, [m4_default([$2], :)], [m4_default([$3], :)]) AS_VAR_POPDEF([CACHEVAR])dnl ])dnl AX_CHECK_COMPILE_FLAGS dqlite-1.9.1/m4/ax_check_gnu_make.m4000066400000000000000000000077271416342771300172260ustar00rootroot00000000000000# =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_check_gnu_make.html # =========================================================================== # # SYNOPSIS # # AX_CHECK_GNU_MAKE([run-if-true],[run-if-false]) # # DESCRIPTION # # This macro searches for a GNU version of make. If a match is found: # # * The makefile variable `ifGNUmake' is set to the empty string, otherwise # it is set to "#". This is useful for including a special features in a # Makefile, which cannot be handled by other versions of make. # * The makefile variable `ifnGNUmake' is set to #, otherwise # it is set to the empty string. This is useful for including a special # features in a Makefile, which can be handled # by other versions of make or to specify else like clause. # * The variable `_cv_gnu_make_command` is set to the command to invoke # GNU make if it exists, the empty string otherwise. # * The variable `ax_cv_gnu_make_command` is set to the command to invoke # GNU make by copying `_cv_gnu_make_command`, otherwise it is unset. # * If GNU Make is found, its version is extracted from the output of # `make --version` as the last field of a record of space-separated # columns and saved into the variable `ax_check_gnu_make_version`. # * Additionally if GNU Make is found, run shell code run-if-true # else run shell code run-if-false. # # Here is an example of its use: # # Makefile.in might contain: # # # A failsafe way of putting a dependency rule into a makefile # $(DEPEND): # $(CC) -MM $(srcdir)/*.c > $(DEPEND) # # @ifGNUmake@ ifeq ($(DEPEND),$(wildcard $(DEPEND))) # @ifGNUmake@ include $(DEPEND) # @ifGNUmake@ else # fallback code # @ifGNUmake@ endif # # Then configure.in would normally contain: # # AX_CHECK_GNU_MAKE() # AC_OUTPUT(Makefile) # # Then perhaps to cause gnu make to override any other make, we could do # something like this (note that GNU make always looks for GNUmakefile # first): # # if ! test x$_cv_gnu_make_command = x ; then # mv Makefile GNUmakefile # echo .DEFAULT: > Makefile ; # echo \ $_cv_gnu_make_command \$@ >> Makefile; # fi # # Then, if any (well almost any) other make is called, and GNU make also # exists, then the other make wraps the GNU make. # # LICENSE # # Copyright (c) 2008 John Darrington # Copyright (c) 2015 Enrico M. Crisostomo # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 12 AC_DEFUN([AX_CHECK_GNU_MAKE],dnl [AC_PROG_AWK AC_CACHE_CHECK([for GNU make],[_cv_gnu_make_command],[dnl _cv_gnu_make_command="" ; dnl Search all the common names for GNU make for a in "$MAKE" make gmake gnumake ; do if test -z "$a" ; then continue ; fi ; if "$a" --version 2> /dev/null | grep GNU 2>&1 > /dev/null ; then _cv_gnu_make_command=$a ; AX_CHECK_GNU_MAKE_HEADLINE=$("$a" --version 2> /dev/null | grep "GNU Make") ax_check_gnu_make_version=$(echo ${AX_CHECK_GNU_MAKE_HEADLINE} | ${AWK} -F " " '{ print $(NF); }') break ; fi done ;]) dnl If there was a GNU version, then set @ifGNUmake@ to the empty string, '#' otherwise AS_VAR_IF([_cv_gnu_make_command], [""], [AS_VAR_SET([ifGNUmake], ["#"])], [AS_VAR_SET([ifGNUmake], [""])]) AS_VAR_IF([_cv_gnu_make_command], [""], [AS_VAR_SET([ifnGNUmake], [""])], [AS_VAR_SET([ifnGNUmake], ["#"])]) AS_VAR_IF([_cv_gnu_make_command], [""], [AS_UNSET(ax_cv_gnu_make_command)], [AS_VAR_SET([ax_cv_gnu_make_command], [${_cv_gnu_make_command}])]) AS_VAR_IF([_cv_gnu_make_command], [""],[$2],[$1]) AC_SUBST([ifGNUmake]) AC_SUBST([ifnGNUmake]) ]) dqlite-1.9.1/m4/ax_code_coverage.m4000066400000000000000000000276161416342771300170670ustar00rootroot00000000000000# =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_code_coverage.html # =========================================================================== # # SYNOPSIS # # AX_CODE_COVERAGE() # # DESCRIPTION # # Defines CODE_COVERAGE_CPPFLAGS, CODE_COVERAGE_CFLAGS, # CODE_COVERAGE_CXXFLAGS and CODE_COVERAGE_LIBS which should be included # in the CPPFLAGS, CFLAGS CXXFLAGS and LIBS/LIBADD variables of every # build target (program or library) which should be built with code # coverage support. Also add rules using AX_ADD_AM_MACRO_STATIC; and # $enable_code_coverage which can be used in subsequent configure output. # CODE_COVERAGE_ENABLED is defined and substituted, and corresponds to the # value of the --enable-code-coverage option, which defaults to being # disabled. # # Test also for gcov program and create GCOV variable that could be # substituted. # # Note that all optimization flags in CFLAGS must be disabled when code # coverage is enabled. # # Usage example: # # configure.ac: # # AX_CODE_COVERAGE # # Makefile.am: # # include $(top_srcdir)/aminclude_static.am # # my_program_LIBS = ... $(CODE_COVERAGE_LIBS) ... # my_program_CPPFLAGS = ... $(CODE_COVERAGE_CPPFLAGS) ... # my_program_CFLAGS = ... $(CODE_COVERAGE_CFLAGS) ... # my_program_CXXFLAGS = ... $(CODE_COVERAGE_CXXFLAGS) ... # # clean-local: code-coverage-clean # distclean-local: code-coverage-dist-clean # # This results in a "check-code-coverage" rule being added to any # Makefile.am which do "include $(top_srcdir)/aminclude_static.am" # (assuming the module has been configured with --enable-code-coverage). # Running `make check-code-coverage` in that directory will run the # module's test suite (`make check`) and build a code coverage report # detailing the code which was touched, then print the URI for the report. # # This code was derived from Makefile.decl in GLib, originally licensed # under LGPLv2.1+. # # LICENSE # # Copyright (c) 2012, 2016 Philip Withnall # Copyright (c) 2012 Xan Lopez # Copyright (c) 2012 Christian Persch # Copyright (c) 2012 Paolo Borelli # Copyright (c) 2012 Dan Winship # Copyright (c) 2015,2018 Bastien ROUCARIES # # This library is free software; you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation; either version 2.1 of the License, or (at # your option) any later version. # # This library 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 Lesser # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . #serial 34 m4_define(_AX_CODE_COVERAGE_RULES,[ AX_ADD_AM_MACRO_STATIC([ # Code coverage # # Optional: # - CODE_COVERAGE_DIRECTORY: Top-level directory for code coverage reporting. # Multiple directories may be specified, separated by whitespace. # (Default: \$(top_builddir)) # - CODE_COVERAGE_OUTPUT_FILE: Filename and path for the .info file generated # by lcov for code coverage. (Default: # \$(PACKAGE_NAME)-\$(PACKAGE_VERSION)-coverage.info) # - CODE_COVERAGE_OUTPUT_DIRECTORY: Directory for generated code coverage # reports to be created. (Default: # \$(PACKAGE_NAME)-\$(PACKAGE_VERSION)-coverage) # - CODE_COVERAGE_BRANCH_COVERAGE: Set to 1 to enforce branch coverage, # set to 0 to disable it and leave empty to stay with the default. # (Default: empty) # - CODE_COVERAGE_LCOV_SHOPTS_DEFAULT: Extra options shared between both lcov # instances. (Default: based on $CODE_COVERAGE_BRANCH_COVERAGE) # - CODE_COVERAGE_LCOV_SHOPTS: Extra options to shared between both lcov # instances. (Default: $CODE_COVERAGE_LCOV_SHOPTS_DEFAULT) # - CODE_COVERAGE_LCOV_OPTIONS_GCOVPATH: --gcov-tool pathtogcov # - CODE_COVERAGE_LCOV_OPTIONS_DEFAULT: Extra options to pass to the # collecting lcov instance. (Default: $CODE_COVERAGE_LCOV_OPTIONS_GCOVPATH) # - CODE_COVERAGE_LCOV_OPTIONS: Extra options to pass to the collecting lcov # instance. (Default: $CODE_COVERAGE_LCOV_OPTIONS_DEFAULT) # - CODE_COVERAGE_LCOV_RMOPTS_DEFAULT: Extra options to pass to the filtering # lcov instance. (Default: empty) # - CODE_COVERAGE_LCOV_RMOPTS: Extra options to pass to the filtering lcov # instance. (Default: $CODE_COVERAGE_LCOV_RMOPTS_DEFAULT) # - CODE_COVERAGE_GENHTML_OPTIONS_DEFAULT: Extra options to pass to the # genhtml instance. (Default: based on $CODE_COVERAGE_BRANCH_COVERAGE) # - CODE_COVERAGE_GENHTML_OPTIONS: Extra options to pass to the genhtml # instance. (Default: $CODE_COVERAGE_GENHTML_OPTIONS_DEFAULT) # - CODE_COVERAGE_IGNORE_PATTERN: Extra glob pattern of files to ignore # # The generated report will be titled using the \$(PACKAGE_NAME) and # \$(PACKAGE_VERSION). In order to add the current git hash to the title, # use the git-version-gen script, available online. # Optional variables # run only on top dir if CODE_COVERAGE_ENABLED ifeq (\$(abs_builddir), \$(abs_top_builddir)) CODE_COVERAGE_DIRECTORY ?= \$(top_builddir) CODE_COVERAGE_OUTPUT_FILE ?= \$(PACKAGE_NAME)-\$(PACKAGE_VERSION)-coverage.info CODE_COVERAGE_OUTPUT_DIRECTORY ?= \$(PACKAGE_NAME)-\$(PACKAGE_VERSION)-coverage CODE_COVERAGE_BRANCH_COVERAGE ?= CODE_COVERAGE_LCOV_SHOPTS_DEFAULT ?= \$(if \$(CODE_COVERAGE_BRANCH_COVERAGE),\ --rc lcov_branch_coverage=\$(CODE_COVERAGE_BRANCH_COVERAGE)) CODE_COVERAGE_LCOV_SHOPTS ?= \$(CODE_COVERAGE_LCOV_SHOPTS_DEFAULT) CODE_COVERAGE_LCOV_OPTIONS_GCOVPATH ?= --gcov-tool \"\$(GCOV)\" CODE_COVERAGE_LCOV_OPTIONS_DEFAULT ?= \$(CODE_COVERAGE_LCOV_OPTIONS_GCOVPATH) CODE_COVERAGE_LCOV_OPTIONS ?= \$(CODE_COVERAGE_LCOV_OPTIONS_DEFAULT) CODE_COVERAGE_LCOV_RMOPTS_DEFAULT ?= CODE_COVERAGE_LCOV_RMOPTS ?= \$(CODE_COVERAGE_LCOV_RMOPTS_DEFAULT) CODE_COVERAGE_GENHTML_OPTIONS_DEFAULT ?=\ \$(if \$(CODE_COVERAGE_BRANCH_COVERAGE),\ --rc genhtml_branch_coverage=\$(CODE_COVERAGE_BRANCH_COVERAGE)) CODE_COVERAGE_GENHTML_OPTIONS ?= \$(CODE_COVERAGE_GENHTML_OPTIONS_DEFAULT) CODE_COVERAGE_IGNORE_PATTERN ?= GITIGNOREFILES := \$(GITIGNOREFILES) \$(CODE_COVERAGE_OUTPUT_FILE) \$(CODE_COVERAGE_OUTPUT_DIRECTORY) code_coverage_v_lcov_cap = \$(code_coverage_v_lcov_cap_\$(V)) code_coverage_v_lcov_cap_ = \$(code_coverage_v_lcov_cap_\$(AM_DEFAULT_VERBOSITY)) code_coverage_v_lcov_cap_0 = @echo \" LCOV --capture\" \$(CODE_COVERAGE_OUTPUT_FILE); code_coverage_v_lcov_ign = \$(code_coverage_v_lcov_ign_\$(V)) code_coverage_v_lcov_ign_ = \$(code_coverage_v_lcov_ign_\$(AM_DEFAULT_VERBOSITY)) code_coverage_v_lcov_ign_0 = @echo \" LCOV --remove /tmp/*\" \$(CODE_COVERAGE_IGNORE_PATTERN); code_coverage_v_genhtml = \$(code_coverage_v_genhtml_\$(V)) code_coverage_v_genhtml_ = \$(code_coverage_v_genhtml_\$(AM_DEFAULT_VERBOSITY)) code_coverage_v_genhtml_0 = @echo \" GEN \" \"\$(CODE_COVERAGE_OUTPUT_DIRECTORY)\"; code_coverage_quiet = \$(code_coverage_quiet_\$(V)) code_coverage_quiet_ = \$(code_coverage_quiet_\$(AM_DEFAULT_VERBOSITY)) code_coverage_quiet_0 = --quiet # sanitizes the test-name: replaces with underscores: dashes and dots code_coverage_sanitize = \$(subst -,_,\$(subst .,_,\$(1))) # Use recursive makes in order to ignore errors during check check-code-coverage: -\$(AM_V_at)\$(MAKE) \$(AM_MAKEFLAGS) -k check \$(AM_V_at)\$(MAKE) \$(AM_MAKEFLAGS) code-coverage-capture # Capture code coverage data code-coverage-capture: code-coverage-capture-hook \$(code_coverage_v_lcov_cap)\$(LCOV) \$(code_coverage_quiet) \$(addprefix --directory ,\$(CODE_COVERAGE_DIRECTORY)) --capture --output-file \"\$(CODE_COVERAGE_OUTPUT_FILE).tmp\" --test-name \"\$(call code_coverage_sanitize,\$(PACKAGE_NAME)-\$(PACKAGE_VERSION))\" --no-checksum --compat-libtool \$(CODE_COVERAGE_LCOV_SHOPTS) \$(CODE_COVERAGE_LCOV_OPTIONS) \$(code_coverage_v_lcov_ign)\$(LCOV) \$(code_coverage_quiet) \$(addprefix --directory ,\$(CODE_COVERAGE_DIRECTORY)) --remove \"\$(CODE_COVERAGE_OUTPUT_FILE).tmp\" \"/tmp/*\" \$(CODE_COVERAGE_IGNORE_PATTERN) --output-file \"\$(CODE_COVERAGE_OUTPUT_FILE)\" \$(CODE_COVERAGE_LCOV_SHOPTS) \$(CODE_COVERAGE_LCOV_RMOPTS) -@rm -f \"\$(CODE_COVERAGE_OUTPUT_FILE).tmp\" \$(code_coverage_v_genhtml)LANG=C \$(GENHTML) \$(code_coverage_quiet) \$(addprefix --prefix ,\$(CODE_COVERAGE_DIRECTORY)) --output-directory \"\$(CODE_COVERAGE_OUTPUT_DIRECTORY)\" --title \"\$(PACKAGE_NAME)-\$(PACKAGE_VERSION) Code Coverage\" --legend --show-details \"\$(CODE_COVERAGE_OUTPUT_FILE)\" \$(CODE_COVERAGE_GENHTML_OPTIONS) @echo \"file://\$(abs_builddir)/\$(CODE_COVERAGE_OUTPUT_DIRECTORY)/index.html\" code-coverage-clean: -\$(LCOV) --directory \$(top_builddir) -z -rm -rf \"\$(CODE_COVERAGE_OUTPUT_FILE)\" \"\$(CODE_COVERAGE_OUTPUT_FILE).tmp\" \"\$(CODE_COVERAGE_OUTPUT_DIRECTORY)\" -find . \\( -name \"*.gcda\" -o -name \"*.gcno\" -o -name \"*.gcov\" \\) -delete code-coverage-dist-clean: A][M_DISTCHECK_CONFIGURE_FLAGS := \$(A][M_DISTCHECK_CONFIGURE_FLAGS) --disable-code-coverage else # ifneq (\$(abs_builddir), \$(abs_top_builddir)) check-code-coverage: code-coverage-capture: code-coverage-capture-hook code-coverage-clean: code-coverage-dist-clean: endif # ifeq (\$(abs_builddir), \$(abs_top_builddir)) else #! CODE_COVERAGE_ENABLED # Use recursive makes in order to ignore errors during check check-code-coverage: @echo \"Need to reconfigure with --enable-code-coverage\" # Capture code coverage data code-coverage-capture: code-coverage-capture-hook @echo \"Need to reconfigure with --enable-code-coverage\" code-coverage-clean: code-coverage-dist-clean: endif #CODE_COVERAGE_ENABLED # Hook rule executed before code-coverage-capture, overridable by the user code-coverage-capture-hook: .PHONY: check-code-coverage code-coverage-capture code-coverage-dist-clean code-coverage-clean code-coverage-capture-hook ]) ]) AC_DEFUN([_AX_CODE_COVERAGE_ENABLED],[ AX_CHECK_GNU_MAKE([],[AC_MSG_ERROR([not using GNU make that is needed for coverage])]) AC_REQUIRE([AX_ADD_AM_MACRO_STATIC]) # check for gcov AC_CHECK_TOOL([GCOV], [$_AX_CODE_COVERAGE_GCOV_PROG_WITH], [:]) AS_IF([test "X$GCOV" = "X:"], [AC_MSG_ERROR([gcov is needed to do coverage])]) AC_SUBST([GCOV]) dnl Check if gcc is being used AS_IF([ test "$GCC" = "no" ], [ AC_MSG_ERROR([not compiling with gcc, which is required for gcov code coverage]) ]) AC_CHECK_PROG([LCOV], [lcov], [lcov]) AC_CHECK_PROG([GENHTML], [genhtml], [genhtml]) AS_IF([ test x"$LCOV" = x ], [ AC_MSG_ERROR([To enable code coverage reporting you must have lcov installed]) ]) AS_IF([ test x"$GENHTML" = x ], [ AC_MSG_ERROR([Could not find genhtml from the lcov package]) ]) dnl Build the code coverage flags dnl Define CODE_COVERAGE_LDFLAGS for backwards compatibility CODE_COVERAGE_CPPFLAGS="-DNDEBUG" CODE_COVERAGE_CFLAGS="-O0 -g -fprofile-arcs -ftest-coverage" CODE_COVERAGE_CXXFLAGS="-O0 -g -fprofile-arcs -ftest-coverage" CODE_COVERAGE_LIBS="-lgcov" AC_SUBST([CODE_COVERAGE_CPPFLAGS]) AC_SUBST([CODE_COVERAGE_CFLAGS]) AC_SUBST([CODE_COVERAGE_CXXFLAGS]) AC_SUBST([CODE_COVERAGE_LIBS]) ]) AC_DEFUN([AX_CODE_COVERAGE],[ dnl Check for --enable-code-coverage # allow to override gcov location AC_ARG_WITH([gcov], [AS_HELP_STRING([--with-gcov[=GCOV]], [use given GCOV for coverage (GCOV=gcov).])], [_AX_CODE_COVERAGE_GCOV_PROG_WITH=$with_gcov], [_AX_CODE_COVERAGE_GCOV_PROG_WITH=gcov]) AC_MSG_CHECKING([whether to build with code coverage support]) AC_ARG_ENABLE([code-coverage], AS_HELP_STRING([--enable-code-coverage], [Whether to enable code coverage support]),, enable_code_coverage=no) AM_CONDITIONAL([CODE_COVERAGE_ENABLED], [test "x$enable_code_coverage" = xyes]) AC_SUBST([CODE_COVERAGE_ENABLED], [$enable_code_coverage]) AC_MSG_RESULT($enable_code_coverage) AS_IF([ test "x$enable_code_coverage" = xyes ], [ _AX_CODE_COVERAGE_ENABLED ]) _AX_CODE_COVERAGE_RULES ]) dqlite-1.9.1/m4/ax_compare_version.m4000066400000000000000000000146531416342771300174720ustar00rootroot00000000000000# =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_compare_version.html # =========================================================================== # # SYNOPSIS # # AX_COMPARE_VERSION(VERSION_A, OP, VERSION_B, [ACTION-IF-TRUE], [ACTION-IF-FALSE]) # # DESCRIPTION # # This macro compares two version strings. Due to the various number of # minor-version numbers that can exist, and the fact that string # comparisons are not compatible with numeric comparisons, this is not # necessarily trivial to do in a autoconf script. This macro makes doing # these comparisons easy. # # The six basic comparisons are available, as well as checking equality # limited to a certain number of minor-version levels. # # The operator OP determines what type of comparison to do, and can be one # of: # # eq - equal (test A == B) # ne - not equal (test A != B) # le - less than or equal (test A <= B) # ge - greater than or equal (test A >= B) # lt - less than (test A < B) # gt - greater than (test A > B) # # Additionally, the eq and ne operator can have a number after it to limit # the test to that number of minor versions. # # eq0 - equal up to the length of the shorter version # ne0 - not equal up to the length of the shorter version # eqN - equal up to N sub-version levels # neN - not equal up to N sub-version levels # # When the condition is true, shell commands ACTION-IF-TRUE are run, # otherwise shell commands ACTION-IF-FALSE are run. The environment # variable 'ax_compare_version' is always set to either 'true' or 'false' # as well. # # Examples: # # AX_COMPARE_VERSION([3.15.7],[lt],[3.15.8]) # AX_COMPARE_VERSION([3.15],[lt],[3.15.8]) # # would both be true. # # AX_COMPARE_VERSION([3.15.7],[eq],[3.15.8]) # AX_COMPARE_VERSION([3.15],[gt],[3.15.8]) # # would both be false. # # AX_COMPARE_VERSION([3.15.7],[eq2],[3.15.8]) # # would be true because it is only comparing two minor versions. # # AX_COMPARE_VERSION([3.15.7],[eq0],[3.15]) # # would be true because it is only comparing the lesser number of minor # versions of the two values. # # Note: The characters that separate the version numbers do not matter. An # empty string is the same as version 0. OP is evaluated by autoconf, not # configure, so must be a string, not a variable. # # The author would like to acknowledge Guido Draheim whose advice about # the m4_case and m4_ifvaln functions make this macro only include the # portions necessary to perform the specific comparison specified by the # OP argument in the final configure script. # # LICENSE # # Copyright (c) 2008 Tim Toolan # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 13 dnl ######################################################################### AC_DEFUN([AX_COMPARE_VERSION], [ AC_REQUIRE([AC_PROG_AWK]) # Used to indicate true or false condition ax_compare_version=false # Convert the two version strings to be compared into a format that # allows a simple string comparison. The end result is that a version # string of the form 1.12.5-r617 will be converted to the form # 0001001200050617. In other words, each number is zero padded to four # digits, and non digits are removed. AS_VAR_PUSHDEF([A],[ax_compare_version_A]) A=`echo "$1" | sed -e 's/\([[0-9]]*\)/Z\1Z/g' \ -e 's/Z\([[0-9]]\)Z/Z0\1Z/g' \ -e 's/Z\([[0-9]][[0-9]]\)Z/Z0\1Z/g' \ -e 's/Z\([[0-9]][[0-9]][[0-9]]\)Z/Z0\1Z/g' \ -e 's/[[^0-9]]//g'` AS_VAR_PUSHDEF([B],[ax_compare_version_B]) B=`echo "$3" | sed -e 's/\([[0-9]]*\)/Z\1Z/g' \ -e 's/Z\([[0-9]]\)Z/Z0\1Z/g' \ -e 's/Z\([[0-9]][[0-9]]\)Z/Z0\1Z/g' \ -e 's/Z\([[0-9]][[0-9]][[0-9]]\)Z/Z0\1Z/g' \ -e 's/[[^0-9]]//g'` dnl # In the case of le, ge, lt, and gt, the strings are sorted as necessary dnl # then the first line is used to determine if the condition is true. dnl # The sed right after the echo is to remove any indented white space. m4_case(m4_tolower($2), [lt],[ ax_compare_version=`echo "x$A x$B" | sed 's/^ *//' | sort -r | sed "s/x${A}/false/;s/x${B}/true/;1q"` ], [gt],[ ax_compare_version=`echo "x$A x$B" | sed 's/^ *//' | sort | sed "s/x${A}/false/;s/x${B}/true/;1q"` ], [le],[ ax_compare_version=`echo "x$A x$B" | sed 's/^ *//' | sort | sed "s/x${A}/true/;s/x${B}/false/;1q"` ], [ge],[ ax_compare_version=`echo "x$A x$B" | sed 's/^ *//' | sort -r | sed "s/x${A}/true/;s/x${B}/false/;1q"` ],[ dnl Split the operator from the subversion count if present. m4_bmatch(m4_substr($2,2), [0],[ # A count of zero means use the length of the shorter version. # Determine the number of characters in A and B. ax_compare_version_len_A=`echo "$A" | $AWK '{print(length)}'` ax_compare_version_len_B=`echo "$B" | $AWK '{print(length)}'` # Set A to no more than B's length and B to no more than A's length. A=`echo "$A" | sed "s/\(.\{$ax_compare_version_len_B\}\).*/\1/"` B=`echo "$B" | sed "s/\(.\{$ax_compare_version_len_A\}\).*/\1/"` ], [[0-9]+],[ # A count greater than zero means use only that many subversions A=`echo "$A" | sed "s/\(\([[0-9]]\{4\}\)\{m4_substr($2,2)\}\).*/\1/"` B=`echo "$B" | sed "s/\(\([[0-9]]\{4\}\)\{m4_substr($2,2)\}\).*/\1/"` ], [.+],[ AC_WARNING( [invalid OP numeric parameter: $2]) ],[]) # Pad zeros at end of numbers to make same length. ax_compare_version_tmp_A="$A`echo $B | sed 's/./0/g'`" B="$B`echo $A | sed 's/./0/g'`" A="$ax_compare_version_tmp_A" # Check for equality or inequality as necessary. m4_case(m4_tolower(m4_substr($2,0,2)), [eq],[ test "x$A" = "x$B" && ax_compare_version=true ], [ne],[ test "x$A" != "x$B" && ax_compare_version=true ],[ AC_WARNING([invalid OP parameter: $2]) ]) ]) AS_VAR_POPDEF([A])dnl AS_VAR_POPDEF([B])dnl dnl # Execute ACTION-IF-TRUE / ACTION-IF-FALSE. if test "$ax_compare_version" = "true" ; then m4_ifvaln([$4],[$4],[:])dnl m4_ifvaln([$5],[else $5])dnl fi ]) dnl AX_COMPARE_VERSION dqlite-1.9.1/m4/ax_file_escapes.m4000066400000000000000000000013731416342771300167140ustar00rootroot00000000000000# =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_file_escapes.html # =========================================================================== # # SYNOPSIS # # AX_FILE_ESCAPES # # DESCRIPTION # # Writes the specified data to the specified file. # # LICENSE # # Copyright (c) 2008 Tom Howard # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 8 AC_DEFUN([AX_FILE_ESCAPES],[ AX_DOLLAR="\$" AX_SRB="\\135" AX_SLB="\\133" AX_BS="\\\\" AX_DQ="\"" ]) dqlite-1.9.1/m4/ax_pthread.m4000066400000000000000000000527221416342771300157250ustar00rootroot00000000000000# =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_pthread.html # =========================================================================== # # SYNOPSIS # # AX_PTHREAD([ACTION-IF-FOUND[, ACTION-IF-NOT-FOUND]]) # # DESCRIPTION # # This macro figures out how to build C programs using POSIX threads. It # sets the PTHREAD_LIBS output variable to the threads library and linker # flags, and the PTHREAD_CFLAGS output variable to any special C compiler # flags that are needed. (The user can also force certain compiler # flags/libs to be tested by setting these environment variables.) # # Also sets PTHREAD_CC to any special C compiler that is needed for # multi-threaded programs (defaults to the value of CC otherwise). (This # is necessary on AIX to use the special cc_r compiler alias.) # # NOTE: You are assumed to not only compile your program with these flags, # but also to link with them as well. For example, you might link with # $PTHREAD_CC $CFLAGS $PTHREAD_CFLAGS $LDFLAGS ... $PTHREAD_LIBS $LIBS # # If you are only building threaded programs, you may wish to use these # variables in your default LIBS, CFLAGS, and CC: # # LIBS="$PTHREAD_LIBS $LIBS" # CFLAGS="$CFLAGS $PTHREAD_CFLAGS" # CC="$PTHREAD_CC" # # In addition, if the PTHREAD_CREATE_JOINABLE thread-attribute constant # has a nonstandard name, this macro defines PTHREAD_CREATE_JOINABLE to # that name (e.g. PTHREAD_CREATE_UNDETACHED on AIX). # # Also HAVE_PTHREAD_PRIO_INHERIT is defined if pthread is found and the # PTHREAD_PRIO_INHERIT symbol is defined when compiling with # PTHREAD_CFLAGS. # # ACTION-IF-FOUND is a list of shell commands to run if a threads library # is found, and ACTION-IF-NOT-FOUND is a list of commands to run it if it # is not found. If ACTION-IF-FOUND is not specified, the default action # will define HAVE_PTHREAD. # # Please let the authors know if this macro fails on any platform, or if # you have any other suggestions or comments. This macro was based on work # by SGJ on autoconf scripts for FFTW (http://www.fftw.org/) (with help # from M. Frigo), as well as ac_pthread and hb_pthread macros posted by # Alejandro Forero Cuervo to the autoconf macro repository. We are also # grateful for the helpful feedback of numerous users. # # Updated for Autoconf 2.68 by Daniel Richard G. # # LICENSE # # Copyright (c) 2008 Steven G. Johnson # Copyright (c) 2011 Daniel Richard G. # Copyright (c) 2019 Marc Stevens # # 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 . # # 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 27 AU_ALIAS([ACX_PTHREAD], [AX_PTHREAD]) AC_DEFUN([AX_PTHREAD], [ AC_REQUIRE([AC_CANONICAL_HOST]) AC_REQUIRE([AC_PROG_CC]) AC_REQUIRE([AC_PROG_SED]) AC_LANG_PUSH([C]) ax_pthread_ok=no # We used to check for pthread.h first, but this fails if pthread.h # requires special compiler flags (e.g. on Tru64 or Sequent). # It gets checked for in the link test anyway. # First of all, check if the user has set any of the PTHREAD_LIBS, # etcetera environment variables, and if threads linking works using # them: if test "x$PTHREAD_CFLAGS$PTHREAD_LIBS" != "x"; then ax_pthread_save_CC="$CC" ax_pthread_save_CFLAGS="$CFLAGS" ax_pthread_save_LIBS="$LIBS" AS_IF([test "x$PTHREAD_CC" != "x"], [CC="$PTHREAD_CC"]) CFLAGS="$CFLAGS $PTHREAD_CFLAGS" LIBS="$PTHREAD_LIBS $LIBS" AC_MSG_CHECKING([for pthread_join using $CC $PTHREAD_CFLAGS $PTHREAD_LIBS]) AC_LINK_IFELSE([AC_LANG_CALL([], [pthread_join])], [ax_pthread_ok=yes]) AC_MSG_RESULT([$ax_pthread_ok]) if test "x$ax_pthread_ok" = "xno"; then PTHREAD_LIBS="" PTHREAD_CFLAGS="" fi CC="$ax_pthread_save_CC" CFLAGS="$ax_pthread_save_CFLAGS" LIBS="$ax_pthread_save_LIBS" fi # We must check for the threads library under a number of different # names; the ordering is very important because some systems # (e.g. DEC) have both -lpthread and -lpthreads, where one of the # libraries is broken (non-POSIX). # Create a list of thread flags to try. Items with a "," contain both # C compiler flags (before ",") and linker flags (after ","). Other items # starting with a "-" are C compiler flags, and remaining items are # library names, except for "none" which indicates that we try without # any flags at all, and "pthread-config" which is a program returning # the flags for the Pth emulation library. ax_pthread_flags="pthreads none -Kthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config" # The ordering *is* (sometimes) important. Some notes on the # individual items follow: # pthreads: AIX (must check this before -lpthread) # none: in case threads are in libc; should be tried before -Kthread and # other compiler flags to prevent continual compiler warnings # -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h) # -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads), Tru64 # (Note: HP C rejects this with "bad form for `-t' option") # -pthreads: Solaris/gcc (Note: HP C also rejects) # -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it # doesn't hurt to check since this sometimes defines pthreads and # -D_REENTRANT too), HP C (must be checked before -lpthread, which # is present but should not be used directly; and before -mthreads, # because the compiler interprets this as "-mt" + "-hreads") # -mthreads: Mingw32/gcc, Lynx/gcc # pthread: Linux, etcetera # --thread-safe: KAI C++ # pthread-config: use pthread-config program (for GNU Pth library) case $host_os in freebsd*) # -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able) # lthread: LinuxThreads port on FreeBSD (also preferred to -pthread) ax_pthread_flags="-kthread lthread $ax_pthread_flags" ;; hpux*) # From the cc(1) man page: "[-mt] Sets various -D flags to enable # multi-threading and also sets -lpthread." ax_pthread_flags="-mt -pthread pthread $ax_pthread_flags" ;; openedition*) # IBM z/OS requires a feature-test macro to be defined in order to # enable POSIX threads at all, so give the user a hint if this is # not set. (We don't define these ourselves, as they can affect # other portions of the system API in unpredictable ways.) AC_EGREP_CPP([AX_PTHREAD_ZOS_MISSING], [ # if !defined(_OPEN_THREADS) && !defined(_UNIX03_THREADS) AX_PTHREAD_ZOS_MISSING # endif ], [AC_MSG_WARN([IBM z/OS requires -D_OPEN_THREADS or -D_UNIX03_THREADS to enable pthreads support.])]) ;; solaris*) # On Solaris (at least, for some versions), libc contains stubbed # (non-functional) versions of the pthreads routines, so link-based # tests will erroneously succeed. (N.B.: The stubs are missing # pthread_cleanup_push, or rather a function called by this macro, # so we could check for that, but who knows whether they'll stub # that too in a future libc.) So we'll check first for the # standard Solaris way of linking pthreads (-mt -lpthread). ax_pthread_flags="-mt,-lpthread pthread $ax_pthread_flags" ;; esac # Are we compiling with Clang? AC_CACHE_CHECK([whether $CC is Clang], [ax_cv_PTHREAD_CLANG], [ax_cv_PTHREAD_CLANG=no # Note that Autoconf sets GCC=yes for Clang as well as GCC if test "x$GCC" = "xyes"; then AC_EGREP_CPP([AX_PTHREAD_CC_IS_CLANG], [/* Note: Clang 2.7 lacks __clang_[a-z]+__ */ # if defined(__clang__) && defined(__llvm__) AX_PTHREAD_CC_IS_CLANG # endif ], [ax_cv_PTHREAD_CLANG=yes]) fi ]) ax_pthread_clang="$ax_cv_PTHREAD_CLANG" # GCC generally uses -pthread, or -pthreads on some platforms (e.g. SPARC) # Note that for GCC and Clang -pthread generally implies -lpthread, # except when -nostdlib is passed. # This is problematic using libtool to build C++ shared libraries with pthread: # [1] https://gcc.gnu.org/bugzilla/show_bug.cgi?id=25460 # [2] https://bugzilla.redhat.com/show_bug.cgi?id=661333 # [3] https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=468555 # To solve this, first try -pthread together with -lpthread for GCC AS_IF([test "x$GCC" = "xyes"], [ax_pthread_flags="-pthread,-lpthread -pthread -pthreads $ax_pthread_flags"]) # Clang takes -pthread (never supported any other flag), but we'll try with -lpthread first AS_IF([test "x$ax_pthread_clang" = "xyes"], [ax_pthread_flags="-pthread,-lpthread -pthread"]) # The presence of a feature test macro requesting re-entrant function # definitions is, on some systems, a strong hint that pthreads support is # correctly enabled case $host_os in darwin* | hpux* | linux* | osf* | solaris*) ax_pthread_check_macro="_REENTRANT" ;; aix*) ax_pthread_check_macro="_THREAD_SAFE" ;; *) ax_pthread_check_macro="--" ;; esac AS_IF([test "x$ax_pthread_check_macro" = "x--"], [ax_pthread_check_cond=0], [ax_pthread_check_cond="!defined($ax_pthread_check_macro)"]) if test "x$ax_pthread_ok" = "xno"; then for ax_pthread_try_flag in $ax_pthread_flags; do case $ax_pthread_try_flag in none) AC_MSG_CHECKING([whether pthreads work without any flags]) ;; *,*) PTHREAD_CFLAGS=`echo $ax_pthread_try_flag | sed "s/^\(.*\),\(.*\)$/\1/"` PTHREAD_LIBS=`echo $ax_pthread_try_flag | sed "s/^\(.*\),\(.*\)$/\2/"` AC_MSG_CHECKING([whether pthreads work with "$PTHREAD_CFLAGS" and "$PTHREAD_LIBS"]) ;; -*) AC_MSG_CHECKING([whether pthreads work with $ax_pthread_try_flag]) PTHREAD_CFLAGS="$ax_pthread_try_flag" ;; pthread-config) AC_CHECK_PROG([ax_pthread_config], [pthread-config], [yes], [no]) AS_IF([test "x$ax_pthread_config" = "xno"], [continue]) PTHREAD_CFLAGS="`pthread-config --cflags`" PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`" ;; *) AC_MSG_CHECKING([for the pthreads library -l$ax_pthread_try_flag]) PTHREAD_LIBS="-l$ax_pthread_try_flag" ;; esac ax_pthread_save_CFLAGS="$CFLAGS" ax_pthread_save_LIBS="$LIBS" CFLAGS="$CFLAGS $PTHREAD_CFLAGS" LIBS="$PTHREAD_LIBS $LIBS" # Check for various functions. We must include pthread.h, # since some functions may be macros. (On the Sequent, we # need a special flag -Kthread to make this header compile.) # We check for pthread_join because it is in -lpthread on IRIX # while pthread_create is in libc. We check for pthread_attr_init # due to DEC craziness with -lpthreads. We check for # pthread_cleanup_push because it is one of the few pthread # functions on Solaris that doesn't have a non-functional libc stub. # We try pthread_create on general principles. AC_LINK_IFELSE([AC_LANG_PROGRAM([#include # if $ax_pthread_check_cond # error "$ax_pthread_check_macro must be defined" # endif static void *some_global = NULL; static void routine(void *a) { /* To avoid any unused-parameter or unused-but-set-parameter warning. */ some_global = a; } static void *start_routine(void *a) { return a; }], [pthread_t th; pthread_attr_t attr; pthread_create(&th, 0, start_routine, 0); pthread_join(th, 0); pthread_attr_init(&attr); pthread_cleanup_push(routine, 0); pthread_cleanup_pop(0) /* ; */])], [ax_pthread_ok=yes], []) CFLAGS="$ax_pthread_save_CFLAGS" LIBS="$ax_pthread_save_LIBS" AC_MSG_RESULT([$ax_pthread_ok]) AS_IF([test "x$ax_pthread_ok" = "xyes"], [break]) PTHREAD_LIBS="" PTHREAD_CFLAGS="" done fi # Clang needs special handling, because older versions handle the -pthread # option in a rather... idiosyncratic way if test "x$ax_pthread_clang" = "xyes"; then # Clang takes -pthread; it has never supported any other flag # (Note 1: This will need to be revisited if a system that Clang # supports has POSIX threads in a separate library. This tends not # to be the way of modern systems, but it's conceivable.) # (Note 2: On some systems, notably Darwin, -pthread is not needed # to get POSIX threads support; the API is always present and # active. We could reasonably leave PTHREAD_CFLAGS empty. But # -pthread does define _REENTRANT, and while the Darwin headers # ignore this macro, third-party headers might not.) # However, older versions of Clang make a point of warning the user # that, in an invocation where only linking and no compilation is # taking place, the -pthread option has no effect ("argument unused # during compilation"). They expect -pthread to be passed in only # when source code is being compiled. # # Problem is, this is at odds with the way Automake and most other # C build frameworks function, which is that the same flags used in # compilation (CFLAGS) are also used in linking. Many systems # supported by AX_PTHREAD require exactly this for POSIX threads # support, and in fact it is often not straightforward to specify a # flag that is used only in the compilation phase and not in # linking. Such a scenario is extremely rare in practice. # # Even though use of the -pthread flag in linking would only print # a warning, this can be a nuisance for well-run software projects # that build with -Werror. So if the active version of Clang has # this misfeature, we search for an option to squash it. AC_CACHE_CHECK([whether Clang needs flag to prevent "argument unused" warning when linking with -pthread], [ax_cv_PTHREAD_CLANG_NO_WARN_FLAG], [ax_cv_PTHREAD_CLANG_NO_WARN_FLAG=unknown # Create an alternate version of $ac_link that compiles and # links in two steps (.c -> .o, .o -> exe) instead of one # (.c -> exe), because the warning occurs only in the second # step ax_pthread_save_ac_link="$ac_link" ax_pthread_sed='s/conftest\.\$ac_ext/conftest.$ac_objext/g' ax_pthread_link_step=`$as_echo "$ac_link" | sed "$ax_pthread_sed"` ax_pthread_2step_ac_link="($ac_compile) && (echo ==== >&5) && ($ax_pthread_link_step)" ax_pthread_save_CFLAGS="$CFLAGS" for ax_pthread_try in '' -Qunused-arguments -Wno-unused-command-line-argument unknown; do AS_IF([test "x$ax_pthread_try" = "xunknown"], [break]) CFLAGS="-Werror -Wunknown-warning-option $ax_pthread_try -pthread $ax_pthread_save_CFLAGS" ac_link="$ax_pthread_save_ac_link" AC_LINK_IFELSE([AC_LANG_SOURCE([[int main(void){return 0;}]])], [ac_link="$ax_pthread_2step_ac_link" AC_LINK_IFELSE([AC_LANG_SOURCE([[int main(void){return 0;}]])], [break]) ]) done ac_link="$ax_pthread_save_ac_link" CFLAGS="$ax_pthread_save_CFLAGS" AS_IF([test "x$ax_pthread_try" = "x"], [ax_pthread_try=no]) ax_cv_PTHREAD_CLANG_NO_WARN_FLAG="$ax_pthread_try" ]) case "$ax_cv_PTHREAD_CLANG_NO_WARN_FLAG" in no | unknown) ;; *) PTHREAD_CFLAGS="$ax_cv_PTHREAD_CLANG_NO_WARN_FLAG $PTHREAD_CFLAGS" ;; esac fi # $ax_pthread_clang = yes # Various other checks: if test "x$ax_pthread_ok" = "xyes"; then ax_pthread_save_CFLAGS="$CFLAGS" ax_pthread_save_LIBS="$LIBS" CFLAGS="$CFLAGS $PTHREAD_CFLAGS" LIBS="$PTHREAD_LIBS $LIBS" # Detect AIX lossage: JOINABLE attribute is called UNDETACHED. AC_CACHE_CHECK([for joinable pthread attribute], [ax_cv_PTHREAD_JOINABLE_ATTR], [ax_cv_PTHREAD_JOINABLE_ATTR=unknown for ax_pthread_attr in PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_UNDETACHED; do AC_LINK_IFELSE([AC_LANG_PROGRAM([#include ], [int attr = $ax_pthread_attr; return attr /* ; */])], [ax_cv_PTHREAD_JOINABLE_ATTR=$ax_pthread_attr; break], []) done ]) AS_IF([test "x$ax_cv_PTHREAD_JOINABLE_ATTR" != "xunknown" && \ test "x$ax_cv_PTHREAD_JOINABLE_ATTR" != "xPTHREAD_CREATE_JOINABLE" && \ test "x$ax_pthread_joinable_attr_defined" != "xyes"], [AC_DEFINE_UNQUOTED([PTHREAD_CREATE_JOINABLE], [$ax_cv_PTHREAD_JOINABLE_ATTR], [Define to necessary symbol if this constant uses a non-standard name on your system.]) ax_pthread_joinable_attr_defined=yes ]) AC_CACHE_CHECK([whether more special flags are required for pthreads], [ax_cv_PTHREAD_SPECIAL_FLAGS], [ax_cv_PTHREAD_SPECIAL_FLAGS=no case $host_os in solaris*) ax_cv_PTHREAD_SPECIAL_FLAGS="-D_POSIX_PTHREAD_SEMANTICS" ;; esac ]) AS_IF([test "x$ax_cv_PTHREAD_SPECIAL_FLAGS" != "xno" && \ test "x$ax_pthread_special_flags_added" != "xyes"], [PTHREAD_CFLAGS="$ax_cv_PTHREAD_SPECIAL_FLAGS $PTHREAD_CFLAGS" ax_pthread_special_flags_added=yes]) AC_CACHE_CHECK([for PTHREAD_PRIO_INHERIT], [ax_cv_PTHREAD_PRIO_INHERIT], [AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include ]], [[int i = PTHREAD_PRIO_INHERIT; return i;]])], [ax_cv_PTHREAD_PRIO_INHERIT=yes], [ax_cv_PTHREAD_PRIO_INHERIT=no]) ]) AS_IF([test "x$ax_cv_PTHREAD_PRIO_INHERIT" = "xyes" && \ test "x$ax_pthread_prio_inherit_defined" != "xyes"], [AC_DEFINE([HAVE_PTHREAD_PRIO_INHERIT], [1], [Have PTHREAD_PRIO_INHERIT.]) ax_pthread_prio_inherit_defined=yes ]) CFLAGS="$ax_pthread_save_CFLAGS" LIBS="$ax_pthread_save_LIBS" # More AIX lossage: compile with *_r variant if test "x$GCC" != "xyes"; then case $host_os in aix*) AS_CASE(["x/$CC"], [x*/c89|x*/c89_128|x*/c99|x*/c99_128|x*/cc|x*/cc128|x*/xlc|x*/xlc_v6|x*/xlc128|x*/xlc128_v6], [#handle absolute path differently from PATH based program lookup AS_CASE(["x$CC"], [x/*], [AS_IF([AS_EXECUTABLE_P([${CC}_r])],[PTHREAD_CC="${CC}_r"])], [AC_CHECK_PROGS([PTHREAD_CC],[${CC}_r],[$CC])])]) ;; esac fi fi test -n "$PTHREAD_CC" || PTHREAD_CC="$CC" AC_SUBST([PTHREAD_LIBS]) AC_SUBST([PTHREAD_CFLAGS]) AC_SUBST([PTHREAD_CC]) # Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND: if test "x$ax_pthread_ok" = "xyes"; then ifelse([$1],,[AC_DEFINE([HAVE_PTHREAD],[1],[Define if you have POSIX threads libraries and header files.])],[$1]) : else ax_pthread_ok=no $2 fi AC_LANG_POP ])dnl AX_PTHREAD dqlite-1.9.1/m4/pkg.m4000066400000000000000000000240111416342771300143550ustar00rootroot00000000000000dnl pkg.m4 - Macros to locate and utilise pkg-config. -*- Autoconf -*- dnl serial 11 (pkg-config-0.29.1) dnl dnl Copyright © 2004 Scott James Remnant . dnl Copyright © 2012-2015 Dan Nicholson dnl dnl This program is free software; you can redistribute it and/or modify dnl it under the terms of the GNU General Public License as published by dnl the Free Software Foundation; either version 2 of the License, or dnl (at your option) any later version. dnl dnl This program is distributed in the hope that it will be useful, but dnl WITHOUT ANY WARRANTY; without even the implied warranty of dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU dnl General Public License for more details. dnl dnl You should have received a copy of the GNU General Public License dnl along with this program; if not, write to the Free Software dnl Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA dnl 02111-1307, USA. dnl dnl As a special exception to the GNU General Public License, if you dnl distribute this file as part of a program that contains a dnl configuration script generated by Autoconf, you may include it under dnl the same distribution terms that you use for the rest of that dnl program. dnl PKG_PREREQ(MIN-VERSION) dnl ----------------------- dnl Since: 0.29 dnl dnl Verify that the version of the pkg-config macros are at least dnl MIN-VERSION. Unlike PKG_PROG_PKG_CONFIG, which checks the user's dnl installed version of pkg-config, this checks the developer's version dnl of pkg.m4 when generating configure. dnl dnl To ensure that this macro is defined, also add: dnl m4_ifndef([PKG_PREREQ], dnl [m4_fatal([must install pkg-config 0.29 or later before running autoconf/autogen])]) dnl dnl See the "Since" comment for each macro you use to see what version dnl of the macros you require. m4_defun([PKG_PREREQ], [m4_define([PKG_MACROS_VERSION], [0.29.1]) m4_if(m4_version_compare(PKG_MACROS_VERSION, [$1]), -1, [m4_fatal([pkg.m4 version $1 or higher is required but ]PKG_MACROS_VERSION[ found])]) ])dnl PKG_PREREQ dnl PKG_PROG_PKG_CONFIG([MIN-VERSION]) dnl ---------------------------------- dnl Since: 0.16 dnl dnl Search for the pkg-config tool and set the PKG_CONFIG variable to dnl first found in the path. Checks that the version of pkg-config found dnl is at least MIN-VERSION. If MIN-VERSION is not specified, 0.9.0 is dnl used since that's the first version where most current features of dnl pkg-config existed. AC_DEFUN([PKG_PROG_PKG_CONFIG], [m4_pattern_forbid([^_?PKG_[A-Z_]+$]) m4_pattern_allow([^PKG_CONFIG(_(PATH|LIBDIR|SYSROOT_DIR|ALLOW_SYSTEM_(CFLAGS|LIBS)))?$]) m4_pattern_allow([^PKG_CONFIG_(DISABLE_UNINSTALLED|TOP_BUILD_DIR|DEBUG_SPEW)$]) AC_ARG_VAR([PKG_CONFIG], [path to pkg-config utility]) AC_ARG_VAR([PKG_CONFIG_PATH], [directories to add to pkg-config's search path]) AC_ARG_VAR([PKG_CONFIG_LIBDIR], [path overriding pkg-config's built-in search path]) 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 ])dnl PKG_PROG_PKG_CONFIG dnl PKG_CHECK_EXISTS(MODULES, [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) dnl ------------------------------------------------------------------- dnl Since: 0.18 dnl dnl Check to see whether a particular set of modules exists. Similar to dnl PKG_CHECK_MODULES(), but does not set variables or print errors. dnl dnl Please remember that m4 expands AC_REQUIRE([PKG_PROG_PKG_CONFIG]) dnl only at the first occurence in configure.ac, so if the first place dnl it's called might be skipped (such as if it is within an "if", you dnl have 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_default([$2], [:]) m4_ifvaln([$3], [else $3])dnl fi]) dnl _PKG_CONFIG([VARIABLE], [COMMAND], [MODULES]) dnl --------------------------------------------- dnl Internal wrapper calling pkg-config via PKG_CONFIG and setting dnl pkg_failed based on the result. m4_define([_PKG_CONFIG], [if test -n "$$1"; then pkg_cv_[]$1="$$1" elif test -n "$PKG_CONFIG"; then PKG_CHECK_EXISTS([$3], [pkg_cv_[]$1=`$PKG_CONFIG --[]$2 "$3" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes ], [pkg_failed=yes]) else pkg_failed=untried fi[]dnl ])dnl _PKG_CONFIG dnl _PKG_SHORT_ERRORS_SUPPORTED dnl --------------------------- dnl Internal check to see if pkg-config supports short errors. 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 ])dnl _PKG_SHORT_ERRORS_SUPPORTED dnl PKG_CHECK_MODULES(VARIABLE-PREFIX, MODULES, [ACTION-IF-FOUND], dnl [ACTION-IF-NOT-FOUND]) dnl -------------------------------------------------------------- dnl Since: 0.4.0 dnl dnl Note that if there is a possibility the first call to dnl PKG_CHECK_MODULES might not happen, you should be sure to include an dnl 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 $1]) _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 AC_MSG_RESULT([no]) _PKG_SHORT_ERRORS_SUPPORTED if test $_pkg_short_errors_supported = yes; then $1[]_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "$2" 2>&1` else $1[]_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "$2" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$$1[]_PKG_ERRORS" >&AS_MESSAGE_LOG_FD m4_default([$4], [AC_MSG_ERROR( [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])[]dnl ]) elif test $pkg_failed = untried; then AC_MSG_RESULT([no]) m4_default([$4], [AC_MSG_FAILURE( [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 .])[]dnl ]) else $1[]_CFLAGS=$pkg_cv_[]$1[]_CFLAGS $1[]_LIBS=$pkg_cv_[]$1[]_LIBS AC_MSG_RESULT([yes]) $3 fi[]dnl ])dnl PKG_CHECK_MODULES dnl PKG_CHECK_MODULES_STATIC(VARIABLE-PREFIX, MODULES, [ACTION-IF-FOUND], dnl [ACTION-IF-NOT-FOUND]) dnl --------------------------------------------------------------------- dnl Since: 0.29 dnl dnl Checks for existence of MODULES and gathers its build flags with dnl static libraries enabled. Sets VARIABLE-PREFIX_CFLAGS from --cflags dnl and VARIABLE-PREFIX_LIBS from --libs. dnl dnl Note that if there is a possibility the first call to dnl PKG_CHECK_MODULES_STATIC might not happen, you should be sure to dnl include an explicit call to PKG_PROG_PKG_CONFIG in your dnl configure.ac. AC_DEFUN([PKG_CHECK_MODULES_STATIC], [AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl _save_PKG_CONFIG=$PKG_CONFIG PKG_CONFIG="$PKG_CONFIG --static" PKG_CHECK_MODULES($@) PKG_CONFIG=$_save_PKG_CONFIG[]dnl ])dnl PKG_CHECK_MODULES_STATIC dnl PKG_INSTALLDIR([DIRECTORY]) dnl ------------------------- dnl Since: 0.27 dnl dnl Substitutes the variable pkgconfigdir as the location where a module dnl should install pkg-config .pc files. By default the directory is dnl $libdir/pkgconfig, but the default can be changed by passing dnl DIRECTORY. The user can override through the --with-pkgconfigdir dnl parameter. AC_DEFUN([PKG_INSTALLDIR], [m4_pushdef([pkg_default], [m4_default([$1], ['${libdir}/pkgconfig'])]) m4_pushdef([pkg_description], [pkg-config installation directory @<:@]pkg_default[@:>@]) AC_ARG_WITH([pkgconfigdir], [AS_HELP_STRING([--with-pkgconfigdir], pkg_description)],, [with_pkgconfigdir=]pkg_default) AC_SUBST([pkgconfigdir], [$with_pkgconfigdir]) m4_popdef([pkg_default]) m4_popdef([pkg_description]) ])dnl PKG_INSTALLDIR dnl PKG_NOARCH_INSTALLDIR([DIRECTORY]) dnl -------------------------------- dnl Since: 0.27 dnl dnl Substitutes the variable noarch_pkgconfigdir as the location where a dnl module should install arch-independent pkg-config .pc files. By dnl default the directory is $datadir/pkgconfig, but the default can be dnl changed by passing DIRECTORY. The user can override through the dnl --with-noarch-pkgconfigdir parameter. AC_DEFUN([PKG_NOARCH_INSTALLDIR], [m4_pushdef([pkg_default], [m4_default([$1], ['${datadir}/pkgconfig'])]) m4_pushdef([pkg_description], [pkg-config arch-independent installation directory @<:@]pkg_default[@:>@]) AC_ARG_WITH([noarch-pkgconfigdir], [AS_HELP_STRING([--with-noarch-pkgconfigdir], pkg_description)],, [with_noarch_pkgconfigdir=]pkg_default) AC_SUBST([noarch_pkgconfigdir], [$with_noarch_pkgconfigdir]) m4_popdef([pkg_default]) m4_popdef([pkg_description]) ])dnl PKG_NOARCH_INSTALLDIR dnl PKG_CHECK_VAR(VARIABLE, MODULE, CONFIG-VARIABLE, dnl [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) dnl ------------------------------------------- dnl Since: 0.28 dnl dnl Retrieves the value of the pkg-config variable for the given module. AC_DEFUN([PKG_CHECK_VAR], [AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl AC_ARG_VAR([$1], [value of $3 for $2, overriding pkg-config])dnl _PKG_CONFIG([$1], [variable="][$3]["], [$2]) AS_VAR_COPY([$1], [pkg_cv_][$1]) AS_VAR_IF([$1], [""], [$5], [$4])dnl ])dnl PKG_CHECK_VAR dqlite-1.9.1/src/000077500000000000000000000000001416342771300136035ustar00rootroot00000000000000dqlite-1.9.1/src/bind.c000066400000000000000000000037211416342771300146660ustar00rootroot00000000000000#include "bind.h" #include "tuple.h" /* Bind a single parameter. */ static int bind_one(sqlite3_stmt *stmt, int n, struct value *value) { int rc; /* TODO: the binding calls below currently use SQLITE_TRANSIENT when * passing pointers to data (for TEXT or BLOB datatypes). This way * SQLite makes its private copy of the data before the bind call * returns, and we can reuse the message body buffer. The overhead of * the copy is typically low, but if it becomes a concern, this could be * optimized to make no copy and instead prevent the message body from * being reused. */ switch (value->type) { case SQLITE_INTEGER: rc = sqlite3_bind_int64(stmt, n, value->integer); break; case SQLITE_FLOAT: rc = sqlite3_bind_double(stmt, n, value->float_); break; case SQLITE_BLOB: rc = sqlite3_bind_blob(stmt, n, value->blob.base, (int)value->blob.len, SQLITE_TRANSIENT); break; case SQLITE_NULL: rc = sqlite3_bind_null(stmt, n); break; case SQLITE_TEXT: rc = sqlite3_bind_text(stmt, n, value->text, -1, SQLITE_TRANSIENT); break; case DQLITE_ISO8601: rc = sqlite3_bind_text(stmt, n, value->text, -1, SQLITE_TRANSIENT); break; case DQLITE_BOOLEAN: rc = sqlite3_bind_int64(stmt, n, value->boolean == 0 ? 0 : 1); break; default: rc = DQLITE_PROTO; break; } return rc; } int bind__params(sqlite3_stmt *stmt, struct cursor *cursor) { struct tuple_decoder decoder; unsigned i; int rc; sqlite3_reset(stmt); /* If the payload has been fully consumed, it means there are no * parameters to bind. */ if (cursor->cap == 0) { return 0; } rc = tuple_decoder__init(&decoder, 0, cursor); if (rc != 0) { return rc; } for (i = 0; i < tuple_decoder__n(&decoder); i++) { struct value value; rc = tuple_decoder__next(&decoder, &value); if (rc != 0) { return rc; } rc = bind_one(stmt, (int)(i + 1), &value); if (rc != 0) { return rc; } } return 0; } dqlite-1.9.1/src/bind.h000066400000000000000000000005151416342771300146710ustar00rootroot00000000000000/** * Bind statement parameters decoding them from a client request payload. */ #ifndef BIND_H_ #define BIND_H_ #include #include "lib/serialize.h" /** * Bind the parameters of the given statement by decoding the given payload. */ int bind__params(sqlite3_stmt *stmt, struct cursor *cursor); #endif /* BIND_H_*/ dqlite-1.9.1/src/client.c000066400000000000000000000252161416342771300152330ustar00rootroot00000000000000#include #include #include #include "lib/assert.h" #include "client.h" #include "message.h" #include "protocol.h" #include "request.h" #include "response.h" #include "tracing.h" #include "tuple.h" int clientInit(struct client *c, int fd) { tracef("init client fd %d", fd); int rv; c->fd = fd; rv = buffer__init(&c->read); if (rv != 0) { tracef("init client read buffer init failed"); goto err; } rv = buffer__init(&c->write); if (rv != 0) { tracef("init client write buffer init failed"); goto err_after_read_buffer_init; } return 0; err_after_read_buffer_init: buffer__close(&c->read); err: return rv; } void clientClose(struct client *c) { tracef("client close fd %d", c->fd); buffer__close(&c->write); buffer__close(&c->read); } int clientSendHandshake(struct client *c) { uint64_t protocol; ssize_t rv; tracef("client send handshake fd %d", c->fd); /* TODO: update to version 1 */ protocol = byte__flip64(DQLITE_PROTOCOL_VERSION_LEGACY); rv = write(c->fd, &protocol, sizeof protocol); if (rv < 0) { tracef("client send handshake failed %zd", rv); return DQLITE_ERROR; } return 0; } /* Write out a request. */ #define REQUEST(LOWER, UPPER) \ { \ struct message message = {0}; \ size_t n; \ size_t n1; \ size_t n2; \ void *cursor; \ ssize_t rv; \ n1 = message__sizeof(&message); \ n2 = request_##LOWER##__sizeof(&request); \ n = n1 + n2; \ buffer__reset(&c->write); \ cursor = buffer__advance(&c->write, n); \ if (cursor == NULL) { \ return DQLITE_NOMEM; \ } \ assert(n2 % 8 == 0); \ message.type = DQLITE_REQUEST_##UPPER; \ message.words = (uint32_t)(n2 / 8); \ message__encode(&message, &cursor); \ request_##LOWER##__encode(&request, &cursor); \ rv = write(c->fd, buffer__cursor(&c->write, 0), n); \ if (rv != (int)n) { \ tracef("request write failed rv %zd", rv); \ return DQLITE_ERROR; \ } \ } /* Read a response without decoding it. */ #define READ(LOWER, UPPER) \ { \ struct message _message = {0}; \ struct cursor _cursor; \ size_t _n; \ void *_p; \ ssize_t _rv; \ _n = message__sizeof(&_message); \ buffer__reset(&c->read); \ _p = buffer__advance(&c->read, _n); \ assert(_p != NULL); \ _rv = read(c->fd, _p, _n); \ if (_rv != (int)_n) { \ tracef("read failed rv %zd)", _rv); \ return DQLITE_ERROR; \ } \ _cursor.p = _p; \ _cursor.cap = _n; \ _rv = message__decode(&_cursor, &_message); \ assert(_rv == 0); \ if (_message.type != DQLITE_RESPONSE_##UPPER) { \ tracef("read decode failed rv %zd)", _rv); \ return DQLITE_ERROR; \ } \ buffer__reset(&c->read); \ _n = _message.words * 8; \ _p = buffer__advance(&c->read, _n); \ if (_p == NULL) { \ tracef("read buf adv failed rv %zd)", _rv); \ return DQLITE_ERROR; \ } \ _rv = read(c->fd, _p, _n); \ if (_rv != (int)_n) { \ tracef("read failed rv %zd)", _rv); \ return DQLITE_ERROR; \ } \ } /* Decode a response. */ #define DECODE(LOWER) \ { \ int rv; \ struct cursor cursor; \ cursor.p = buffer__cursor(&c->read, 0); \ cursor.cap = buffer__offset(&c->read); \ rv = response_##LOWER##__decode(&cursor, &response); \ if (rv != 0) { \ tracef("decode failed rv %d)", rv); \ return DQLITE_ERROR; \ } \ } /* Read and decode a response. */ #define RESPONSE(LOWER, UPPER) \ READ(LOWER, UPPER); \ DECODE(LOWER) int clientSendOpen(struct client *c, const char *name) { tracef("client send open fd %d name %s", c->fd, name); struct request_open request; request.filename = name; request.flags = 0; /* TODO: this is unused, should we drop it? */ request.vfs = "test"; /* TODO: this is unused, should we drop it? */ REQUEST(open, OPEN); return 0; } int clientRecvDb(struct client *c) { tracef("client recvdb fd %d", c->fd); struct response_db response; RESPONSE(db, DB); c->db_id = response.id; return 0; } int clientSendPrepare(struct client *c, const char *sql) { tracef("client send prepare fd %d", c->fd); struct request_prepare request; request.db_id = c->db_id; request.sql = sql; REQUEST(prepare, PREPARE); return 0; } int clientRecvStmt(struct client *c, unsigned *stmt_id) { struct response_stmt response; RESPONSE(stmt, STMT); *stmt_id = response.id; tracef("client recv stmt fd %d stmt_id %u", c->fd, *stmt_id); return 0; } int clientSendExec(struct client *c, unsigned stmt_id) { tracef("client send exec fd %d id %u", c->fd, stmt_id); struct request_exec request; request.db_id = c->db_id; request.stmt_id = stmt_id; REQUEST(exec, EXEC); return 0; } int clientSendExecSQL(struct client *c, const char *sql) { tracef("client send exec sql fd %d", c->fd); struct request_exec_sql request; request.db_id = c->db_id; request.sql = sql; REQUEST(exec_sql, EXEC_SQL); return 0; } int clientRecvResult(struct client *c, unsigned *last_insert_id, unsigned *rows_affected) { struct response_result response; RESPONSE(result, RESULT); *last_insert_id = (unsigned)response.last_insert_id; *rows_affected = (unsigned)response.rows_affected; tracef("client recv result fd %d last_insert_id %u rows_affected %u", c->fd, *last_insert_id, *rows_affected); return 0; } int clientSendQuery(struct client *c, unsigned stmt_id) { tracef("client send query fd %d stmt_id %u", c->fd, stmt_id); struct request_query request; request.db_id = c->db_id; request.stmt_id = stmt_id; REQUEST(query, QUERY); return 0; } int clientSendQuerySql(struct client *c, const char *sql) { tracef("client send query sql fd %d sql %s", c->fd, sql); struct request_query_sql request; request.db_id = c->db_id; request.sql = sql; REQUEST(query_sql, QUERY_SQL); return 0; } int clientRecvRows(struct client *c, struct rows *rows) { tracef("client recv rows fd %d", c->fd); struct cursor cursor; struct tuple_decoder decoder; uint64_t column_count; struct row *last; unsigned i; int rv; READ(rows, ROWS); cursor.p = buffer__cursor(&c->read, 0); cursor.cap = buffer__offset(&c->read); rv = uint64__decode(&cursor, &column_count); if (rv != 0) { tracef("client recv rows fd %d decode failed %d", c->fd, rv); return DQLITE_ERROR; } rows->column_count = (unsigned)column_count; for (i = 0; i < rows->column_count; i++) { rows->column_names = sqlite3_malloc( (int)(column_count * sizeof *rows->column_names)); if (rows->column_names == NULL) { return DQLITE_ERROR; } rv = text__decode(&cursor, &rows->column_names[i]); if (rv != 0) { return DQLITE_ERROR; } } last = NULL; while (1) { uint64_t eof; struct row *row; if (cursor.cap < 8) { /* No EOF marker fond */ return DQLITE_ERROR; } eof = byte__flip64(*(uint64_t *)cursor.p); if (eof == DQLITE_RESPONSE_ROWS_DONE || eof == DQLITE_RESPONSE_ROWS_PART) { break; } row = sqlite3_malloc(sizeof *row); if (row == NULL) { tracef("malloc"); return DQLITE_NOMEM; } row->values = sqlite3_malloc((int)(column_count * sizeof *row->values)); if (row->values == NULL) { tracef("malloc"); sqlite3_free(row); return DQLITE_NOMEM; } row->next = NULL; rv = tuple_decoder__init(&decoder, (unsigned)column_count, &cursor); if (rv != 0) { tracef("decode init error %d", rv); sqlite3_free(row->values); sqlite3_free(row); return DQLITE_ERROR; } for (i = 0; i < rows->column_count; i++) { rv = tuple_decoder__next(&decoder, &row->values[i]); if (rv != 0) { tracef("decode error %d", rv); sqlite3_free(row->values); sqlite3_free(row); return DQLITE_ERROR; } } if (last == NULL) { rows->next = row; } else { last->next = row; } last = row; } return 0; } void clientCloseRows(struct rows *rows) { struct row *row = rows->next; while (row != NULL) { struct row *next; next = row->next; sqlite3_free(row->values); sqlite3_free(row); row = next; } sqlite3_free(rows->column_names); } int clientSendAdd(struct client *c, unsigned id, const char *address) { tracef("client send add fd %d id %u address %s", c->fd, id, address); struct request_add request; request.id = id; request.address = address; REQUEST(add, ADD); return 0; } int clientSendAssign(struct client *c, unsigned id, int role) { tracef("client send assign fd %d id %u role %d", c->fd, id, role); struct request_assign request; (void)role; /* TODO: actually send an assign request, not a legacy promote one. */ request.id = id; REQUEST(assign, ASSIGN); return 0; } int clientSendRemove(struct client *c, unsigned id) { tracef("client send remove fd %d id %u", c->fd, id); struct request_remove request; request.id = id; REQUEST(remove, REMOVE); return 0; } int clientRecvEmpty(struct client *c) { tracef("client recv empty fd %d", c->fd); struct response_empty response; RESPONSE(empty, EMPTY); return 0; } dqlite-1.9.1/src/client.h000066400000000000000000000045041416342771300152350ustar00rootroot00000000000000/* Core dqlite client logic for encoding requests and decoding responses. */ #ifndef CLIENT_H_ #define CLIENT_H_ #include "lib/buffer.h" #include "tuple.h" struct client { int fd; /* Connected socket */ unsigned db_id; /* Database ID provided by the server */ struct buffer read; /* Read buffer */ struct buffer write; /* Write buffer */ }; struct row { struct value *values; struct row *next; }; struct rows { unsigned column_count; const char **column_names; struct row *next; }; /* Initialize a new client, writing requests to fd. */ int clientInit(struct client *c, int fd); /* Release all memory used by the client, and close the client socket. */ void clientClose(struct client *c); /* Initialize the connection by writing the protocol version. This must be * called before using any other API. */ int clientSendHandshake(struct client *c); /* Send a request to open a database */ int clientSendOpen(struct client *c, const char *name); /* Receive the response to an open request. */ int clientRecvDb(struct client *c); /* Send a request to prepare a statement. */ int clientSendPrepare(struct client *c, const char *sql); /* Receive the response to a prepare request. */ int clientRecvStmt(struct client *c, unsigned *stmt_id); /* Send a request to execute a statement. */ int clientSendExec(struct client *c, unsigned stmt_id); /* Send a request to execute a non-prepared statement. */ int clientSendExecSQL(struct client *c, const char *sql); /* Receive the response to an exec request. */ int clientRecvResult(struct client *c, unsigned *last_insert_id, unsigned *rows_affected); /* Send a request to perform a query. */ int clientSendQuery(struct client *c, unsigned stmt_id); /* Receive the response of a query request. */ int clientRecvRows(struct client *c, struct rows *rows); /* Release all memory used in the given rows object. */ void clientCloseRows(struct rows *rows); /* Send a request to add a dqlite node. */ int clientSendAdd(struct client *c, unsigned id, const char *address); /* Send a request to assign a role to a node. */ int clientSendAssign(struct client *c, unsigned id, int role); /* Send a request to remove a server from the cluster. */ int clientSendRemove(struct client *c, unsigned id); /* Receive an empty response. */ int clientRecvEmpty(struct client *c); #endif /* CLIENT_H_*/ dqlite-1.9.1/src/command.c000066400000000000000000000103001416342771300153570ustar00rootroot00000000000000#include #include "../include/dqlite.h" #include "lib/serialize.h" #include "command.h" #include "protocol.h" #define FORMAT 1 /* Format version */ #define HEADER(X, ...) \ X(uint8, format, ##__VA_ARGS__) \ X(uint8, type, ##__VA_ARGS__) \ X(uint8, _unused1, ##__VA_ARGS__) \ X(uint8, _unused2, ##__VA_ARGS__) \ X(uint32, _unused3, ##__VA_ARGS__) SERIALIZE__DEFINE(header, HEADER); SERIALIZE__IMPLEMENT(header, HEADER); static size_t frames__sizeof(const frames_t *frames) { size_t s = uint32__sizeof(&frames->n_pages) + uint16__sizeof(&frames->page_size) + uint16__sizeof(&frames->__unused__) + sizeof(uint64_t) * frames->n_pages + /* Page numbers */ frames->page_size * frames->n_pages; /* Page data */ return s; } static void frames__encode(const frames_t *frames, void **cursor) { const dqlite_vfs_frame *list; unsigned i; uint32__encode(&frames->n_pages, cursor); uint16__encode(&frames->page_size, cursor); uint16__encode(&frames->__unused__, cursor); list = frames->data; for (i = 0; i < frames->n_pages; i++) { uint64_t pgno = list[i].page_number; uint64__encode(&pgno, cursor); } for (i = 0; i < frames->n_pages; i++) { memcpy(*cursor, list[i].data, frames->page_size); *cursor += frames->page_size; } } static int frames__decode(struct cursor *cursor, frames_t *frames) { int rc; rc = uint32__decode(cursor, &frames->n_pages); if (rc != 0) { return rc; } rc = uint16__decode(cursor, &frames->page_size); if (rc != 0) { return rc; } rc = uint16__decode(cursor, &frames->__unused__); if (rc != 0) { return rc; } frames->data = cursor->p; return 0; } #define COMMAND__IMPLEMENT(LOWER, UPPER, _) \ SERIALIZE__IMPLEMENT(command_##LOWER, COMMAND__##UPPER); COMMAND__TYPES(COMMAND__IMPLEMENT, ); #define ENCODE(LOWER, UPPER, _) \ case COMMAND_##UPPER: \ h.type = COMMAND_##UPPER; \ buf->len = header__sizeof(&h); \ buf->len += command_##LOWER##__sizeof(command); \ buf->base = raft_malloc(buf->len); \ if (buf->base == NULL) { \ return DQLITE_NOMEM; \ } \ cursor = buf->base; \ header__encode(&h, &cursor); \ command_##LOWER##__encode(command, &cursor); \ break; int command__encode(int type, const void *command, struct raft_buffer *buf) { struct header h = {0}; void *cursor; int rc = 0; h.format = FORMAT; switch (type) { COMMAND__TYPES(ENCODE, ) }; return rc; } #define DECODE(LOWER, UPPER, _) \ case COMMAND_##UPPER: \ *command = raft_malloc(sizeof(struct command_##LOWER)); \ if (*command == NULL) { \ return DQLITE_NOMEM; \ } \ rc = command_##LOWER##__decode(&cursor, *command); \ break; int command__decode(const struct raft_buffer *buf, int *type, void **command) { struct header h; struct cursor cursor; int rc; cursor.p = buf->base; cursor.cap = buf->len; rc = header__decode(&cursor, &h); if (rc != 0) { return rc; } if (h.format != FORMAT) { return DQLITE_PROTO; } switch (h.type) { COMMAND__TYPES(DECODE, ) default: rc = DQLITE_PROTO; break; }; if (rc != 0) { return rc; } *type = h.type; return 0; } int command_frames__page_numbers(const struct command_frames *c, unsigned long *page_numbers[]) { unsigned i; struct cursor cursor; cursor.p = c->frames.data; cursor.cap = sizeof(uint64_t) * c->frames.n_pages; *page_numbers = sqlite3_malloc64(sizeof **page_numbers * c->frames.n_pages); if (*page_numbers == NULL) { return DQLITE_NOMEM; } for (i = 0; i < c->frames.n_pages; i++) { uint64_t pgno; int r = uint64__decode(&cursor, &pgno); if (r != 0) { return r; } (*page_numbers)[i] = (unsigned long)pgno; } return 0; } void command_frames__pages(const struct command_frames *c, void **pages) { *pages = (void *)(c->frames.data + (sizeof(uint64_t) * c->frames.n_pages)); } dqlite-1.9.1/src/command.h000066400000000000000000000040171416342771300153740ustar00rootroot00000000000000/** * Encode and decode dqlite Raft FSM commands. */ #ifndef COMMAND_H_ #define COMMAND_H_ #include #include "lib/serialize.h" /* Command type codes */ enum { COMMAND_OPEN = 1, COMMAND_FRAMES, COMMAND_UNDO, COMMAND_CHECKPOINT }; /* Hold information about an array of WAL frames. */ struct frames { uint32_t n_pages; uint16_t page_size; uint16_t __unused__; /* TODO: because the sqlite3 replication APIs are asymmetrics, the * format differs between encode and decode. When encoding data is * expected to be a sqlite3_wal_replication_frame* array, and when * decoding it will be a pointer to raw memory which can be further * decoded with the command_frames__page_numbers() and * command_frames__pages() helpers. */ const void *data; }; typedef struct frames frames_t; /* Serialization definitions for a raft FSM command. */ #define COMMAND__DEFINE(LOWER, UPPER, _) \ SERIALIZE__DEFINE_STRUCT(command_##LOWER, COMMAND__##UPPER); #define COMMAND__OPEN(X, ...) X(text, filename, ##__VA_ARGS__) #define COMMAND__FRAMES(X, ...) \ X(text, filename, ##__VA_ARGS__) \ X(uint64, tx_id, ##__VA_ARGS__) \ X(uint32, truncate, ##__VA_ARGS__) \ X(uint8, is_commit, ##__VA_ARGS__) \ X(uint8, __unused1__, ##__VA_ARGS__) \ X(uint16, __unused2__, ##__VA_ARGS__) \ X(frames, frames, ##__VA_ARGS__) #define COMMAND__UNDO(X, ...) X(uint64, tx_id, ##__VA_ARGS__) #define COMMAND__CHECKPOINT(X, ...) X(text, filename, ##__VA_ARGS__) #define COMMAND__TYPES(X, ...) \ X(open, OPEN, __VA_ARGS__) \ X(frames, FRAMES, __VA_ARGS__) \ X(undo, UNDO, __VA_ARGS__) \ X(checkpoint, CHECKPOINT, __VA_ARGS__) COMMAND__TYPES(COMMAND__DEFINE); int command__encode(int type, const void *command, struct raft_buffer *buf); int command__decode(const struct raft_buffer *buf, int *type, void **command); int command_frames__page_numbers(const struct command_frames *c, unsigned long *page_numbers[]); void command_frames__pages(const struct command_frames *c, void **pages); #endif /* COMMAND_H_*/ dqlite-1.9.1/src/config.c000066400000000000000000000025451416342771300152220ustar00rootroot00000000000000#include #include #include #include "../include/dqlite.h" #include "./lib/assert.h" #include "config.h" #include "logger.h" /* Default heartbeat timeout in milliseconds. * * Clients will be disconnected if the server does not receive a heartbeat * message within this time. */ #define DEFAULT_HEARTBEAT_TIMEOUT 15000 /* Default database page size in bytes. */ #define DEFAULT_PAGE_SIZE 4096 /* Number of outstanding WAL frames after which a checkpoint is triggered as * soon as possible. */ #define DEFAULT_CHECKPOINT_THRESHOLD 1000 /* For generating unique replication/VFS registration names. * * TODO: make this thread safe. */ static unsigned serial = 1; int config__init(struct config *c, dqlite_node_id id, const char *address) { int rv; c->id = id; c->address = sqlite3_malloc((int)strlen(address) + 1); if (c->address == NULL) { return DQLITE_NOMEM; } strcpy(c->address, address); c->heartbeat_timeout = DEFAULT_HEARTBEAT_TIMEOUT; c->page_size = DEFAULT_PAGE_SIZE; c->checkpoint_threshold = DEFAULT_CHECKPOINT_THRESHOLD; rv = snprintf(c->name, sizeof c->name, "dqlite-%u", serial); assert(rv < (int)(sizeof c->name)); c->logger.data = NULL; c->logger.emit = loggerDefaultEmit; c->failure_domain = 0; c->weight = 0; serial++; return 0; } void config__close(struct config *c) { sqlite3_free(c->address); } dqlite-1.9.1/src/config.h000066400000000000000000000020031416342771300152140ustar00rootroot00000000000000#ifndef CONFIG_H_ #define CONFIG_H_ #include "logger.h" /** * Value object holding dqlite configuration. */ struct config { dqlite_node_id id; /* Unique instance ID */ char *address; /* Instance address */ unsigned heartbeat_timeout; /* In milliseconds */ unsigned page_size; /* Database page size */ unsigned checkpoint_threshold; /* In outstanding WAL frames */ struct logger logger; /* Custom logger */ char name[256]; /* VFS/replication registriatio name */ unsigned long long failure_domain; /* User-provided failure domain */ unsigned long long int weight; /* User-provided node weight */ }; /** * Initialize the config object with required values and set the rest to sane * defaults. A copy will be made of the given @address. */ int config__init(struct config *c, dqlite_node_id id, const char *address); /** * Release any memory held by the config object. */ void config__close(struct config *c); #endif /* DQLITE_OPTIONS_H */ dqlite-1.9.1/src/conn.c000066400000000000000000000167651416342771300147230ustar00rootroot00000000000000#include "conn.h" #include "message.h" #include "request.h" #include "tracing.h" #include "transport.h" #include "protocol.h" /* Initialize the given buffer for reading, ensure it has the given size. */ static int init_read(struct conn *c, uv_buf_t *buf, size_t size) { buffer__reset(&c->read); buf->base = buffer__advance(&c->read, size); if (buf->base == NULL) { return DQLITE_NOMEM; } buf->len = size; return 0; } static int read_message(struct conn *c); static void write_cb(struct transport *transport, int status) { struct conn *c = transport->data; bool finished; int rv; if (status != 0) { tracef("write cb status %d", status); goto abort; } buffer__reset(&c->write); buffer__advance(&c->write, message__sizeof(&c->response)); /* Header */ rv = gateway__resume(&c->gateway, &finished); if (rv != 0) { goto abort; } if (!finished) { return; } /* Start reading the next request */ rv = read_message(c); if (rv != 0) { goto abort; } return; abort: conn__stop(c); } static void gateway_handle_cb(struct handle *req, int status, int type) { struct conn *c = req->data; size_t n; void *cursor; uv_buf_t buf; int rv; /* Ignore results firing after we started closing. TODO: instead, we * should make gateway__close() asynchronous. */ if (c->closed) { tracef("gateway handle cb closed"); return; } if (status != 0) { tracef("gateway handle cb closed status %d", status); goto abort; } n = buffer__offset(&c->write) - message__sizeof(&c->response); assert(n % 8 == 0); c->response.type = (uint8_t)type; c->response.words = (uint32_t)(n / 8); c->response.flags = 0; c->response.extra = 0; cursor = buffer__cursor(&c->write, 0); message__encode(&c->response, &cursor); buf.base = buffer__cursor(&c->write, 0); buf.len = buffer__offset(&c->write); rv = transport__write(&c->transport, &buf, write_cb); if (rv != 0) { tracef("transport write failed %d", rv); goto abort; } return; abort: conn__stop(c); } static void closeCb(struct transport *transport) { struct conn *c = transport->data; buffer__close(&c->write); buffer__close(&c->read); if (c->close_cb != NULL) { c->close_cb(c); } } static void raft_connect(struct conn *c, struct cursor *cursor) { struct request_connect request; int rv; tracef("raft_connect"); rv = request_connect__decode(cursor, &request); if (rv != 0) { tracef("request connect decode failed %d", rv); conn__stop(c); return; } raftProxyAccept(c->uv_transport, request.id, request.address, c->transport.stream); /* Close the connection without actually closing the transport, since * the stream will be used by raft */ c->closed = true; closeCb(&c->transport); } static void read_request_cb(struct transport *transport, int status) { struct conn *c = transport->data; struct cursor cursor; int rv; if (status != 0) { tracef("read error %d", status); //errorf(c->logger, "read error"); conn__stop(c); return; } cursor.p = buffer__cursor(&c->read, 0); cursor.cap = buffer__offset(&c->read); buffer__reset(&c->write); buffer__advance(&c->write, message__sizeof(&c->response)); /* Header */ switch (c->request.type) { case DQLITE_REQUEST_CONNECT: raft_connect(c, &cursor); return; } rv = gateway__handle(&c->gateway, &c->handle, c->request.type, &cursor, &c->write, gateway_handle_cb); if (rv != 0) { tracef("read gateway handle error %d", rv); conn__stop(c); } } /* Start reading the body of the next request */ static int read_request(struct conn *c) { uv_buf_t buf; int rv; rv = init_read(c, &buf, c->request.words * 8); if (rv != 0) { tracef("init read failed %d", rv); return rv; } rv = transport__read(&c->transport, &buf, read_request_cb); if (rv != 0) { tracef("transport read failed %d", rv); return rv; } return 0; } static void read_message_cb(struct transport *transport, int status) { struct conn *c = transport->data; struct cursor cursor; int rv; if (status != 0) { // errorf(c->logger, "read error"); tracef("read error %d", status); conn__stop(c); return; } cursor.p = buffer__cursor(&c->read, 0); cursor.cap = buffer__offset(&c->read); rv = message__decode(&cursor, &c->request); assert(rv == 0); /* Can't fail, we know we have enough bytes */ rv = read_request(c); if (rv != 0) { tracef("read request error %d", rv); conn__stop(c); return; } } /* Start reading metadata about the next message */ static int read_message(struct conn *c) { uv_buf_t buf; int rv; rv = init_read(c, &buf, message__sizeof(&c->request)); if (rv != 0) { tracef("init read failed %d", rv); return rv; } rv = transport__read(&c->transport, &buf, read_message_cb); if (rv != 0) { tracef("transport read failed %d", rv); return rv; } return 0; } static void read_protocol_cb(struct transport *transport, int status) { struct conn *c = transport->data; struct cursor cursor; int rv; if (status != 0) { // errorf(c->logger, "read error"); tracef("read error %d", status); goto abort; } cursor.p = buffer__cursor(&c->read, 0); cursor.cap = buffer__offset(&c->read); rv = uint64__decode(&cursor, &c->protocol); assert(rv == 0); /* Can't fail, we know we have enough bytes */ if (c->protocol != DQLITE_PROTOCOL_VERSION && c->protocol != DQLITE_PROTOCOL_VERSION_LEGACY) { /* errorf(c->logger, "unknown protocol version: %lx", */ /* c->protocol); */ /* TODO: instead of closing the connection we should return * error messages */ tracef("unknown protocol version %" PRIu64, c->protocol); goto abort; } c->gateway.protocol = c->protocol; rv = read_message(c); if (rv != 0) { goto abort; } return; abort: conn__stop(c); } /* Start reading the protocol format version */ static int read_protocol(struct conn *c) { uv_buf_t buf; int rv; rv = init_read(c, &buf, sizeof c->protocol); if (rv != 0) { tracef("init read failed %d", rv); return rv; } rv = transport__read(&c->transport, &buf, read_protocol_cb); if (rv != 0) { tracef("transport read failed %d", rv); return rv; } return 0; } int conn__start(struct conn *c, struct config *config, struct uv_loop_s *loop, struct registry *registry, struct raft *raft, struct uv_stream_s *stream, struct raft_uv_transport *uv_transport, conn_close_cb close_cb) { int rv; (void)loop; tracef("conn start"); rv = transport__init(&c->transport, stream); if (rv != 0) { tracef("conn start - transport init failed %d", rv); goto err; } c->config = config; c->transport.data = c; c->uv_transport = uv_transport; c->close_cb = close_cb; gateway__init(&c->gateway, config, registry, raft); rv = buffer__init(&c->read); if (rv != 0) { goto err_after_transport_init; } rv = buffer__init(&c->write); if (rv != 0) { goto err_after_read_buffer_init; } c->handle.data = c; c->closed = false; /* First, we expect the client to send us the protocol version. */ rv = read_protocol(c); if (rv != 0) { goto err_after_write_buffer_init; } return 0; err_after_write_buffer_init: buffer__close(&c->write); err_after_read_buffer_init: buffer__close(&c->read); err_after_transport_init: transport__close(&c->transport, NULL); err: return rv; } void conn__stop(struct conn *c) { tracef("conn stop"); if (c->closed) { return; } c->closed = true; gateway__close(&c->gateway); transport__close(&c->transport, closeCb); } dqlite-1.9.1/src/conn.h000066400000000000000000000031521416342771300147120ustar00rootroot00000000000000/** * Handle a single client connection. */ #ifndef DQLITE_CONN_H_ #define DQLITE_CONN_H_ #include #include "lib/buffer.h" #include "lib/queue.h" #include "lib/transport.h" #include "gateway.h" #include "message.h" /** * Callbacks. */ struct conn; typedef void (*conn_close_cb)(struct conn *c); struct conn { struct config *config; struct raft_uv_transport *uv_transport; /* Raft transport */ conn_close_cb close_cb; /* Close callback */ struct transport transport; /* Async network read/write */ struct gateway gateway; /* Request handler */ struct buffer read; /* Read buffer */ struct buffer write; /* Write buffer */ uint64_t protocol; /* Protocol format version */ struct message request; /* Request message meta data */ struct message response; /* Response message meta data */ struct handle handle; bool closed; queue queue; }; /** * Initialize and start a connection. * * If no error is returned, the connection should be considered started. Any * error occurring after this point will trigger the @close_cb callback. */ int conn__start(struct conn *c, struct config *config, struct uv_loop_s *loop, struct registry *registry, struct raft *raft, struct uv_stream_s *stream, struct raft_uv_transport *uv_transport, conn_close_cb close_cb); /** * Force closing the connection. The close callback will be invoked when it's * safe to release the memory of the connection object. */ void conn__stop(struct conn *c); #endif /* DQLITE_CONN_H_ */ dqlite-1.9.1/src/db.c000066400000000000000000000043731416342771300143430ustar00rootroot00000000000000#include #include "../include/dqlite.h" #include "./lib/assert.h" #include "db.h" #include "tracing.h" /* Open a SQLite connection and set it to follower mode. */ static int open_follower_conn(const char *filename, const char *vfs, unsigned page_size, sqlite3 **conn); void db__init(struct db *db, struct config *config, const char *filename) { tracef("db init %s", filename); db->config = config; db->filename = sqlite3_malloc((int)(strlen(filename) + 1)); assert(db->filename != NULL); /* TODO: return an error instead */ strcpy(db->filename, filename); db->follower = NULL; db->tx_id = 0; QUEUE__INIT(&db->leaders); } void db__close(struct db *db) { assert(QUEUE__IS_EMPTY(&db->leaders)); if (db->follower != NULL) { int rc; rc = sqlite3_close(db->follower); assert(rc == SQLITE_OK); } sqlite3_free(db->filename); } int db__open_follower(struct db *db) { int rc; assert(db->follower == NULL); rc = open_follower_conn(db->filename, db->config->name, db->config->page_size, &db->follower); if (rc != 0) { return rc; } return 0; } static int open_follower_conn(const char *filename, const char *vfs, unsigned page_size, sqlite3 **conn) { char pragma[255]; int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE; char *msg = NULL; int rc; rc = sqlite3_open_v2(filename, conn, flags, vfs); if (rc != SQLITE_OK) { goto err; } /* Enable extended result codes */ rc = sqlite3_extended_result_codes(*conn, 1); if (rc != SQLITE_OK) { goto err_after_open; } /* Set the page size. */ sprintf(pragma, "PRAGMA page_size=%d", page_size); rc = sqlite3_exec(*conn, pragma, NULL, NULL, &msg); if (rc != SQLITE_OK) { goto err_after_open; } /* Disable syncs. */ rc = sqlite3_exec(*conn, "PRAGMA synchronous=OFF", NULL, NULL, &msg); if (rc != SQLITE_OK) { goto err_after_open; } /* Set WAL journaling. */ rc = sqlite3_exec(*conn, "PRAGMA journal_mode=WAL", NULL, NULL, &msg); if (rc != SQLITE_OK) { goto err_after_open; } rc = sqlite3_db_config(*conn, SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE, 1, NULL); if (rc != SQLITE_OK) { goto err_after_open; } return 0; err_after_open: sqlite3_close(*conn); err: if (msg != NULL) { sqlite3_free(msg); } return rc; } dqlite-1.9.1/src/db.h000066400000000000000000000016311416342771300143420ustar00rootroot00000000000000/** * State of a single database. */ #ifndef DB_H_ #define DB_H_ #include "lib/queue.h" #include "config.h" struct db { struct config *config; /* Dqlite configuration */ char *filename; /* Database filename */ sqlite3 *follower; /* Follower connection */ queue leaders; /* Open leader connections */ unsigned tx_id; /* Current ongoing transaction ID, if any */ queue queue; /* Prev/next database, used by the registry */ }; /** * Initialize a database object. * * The given @filename will be copied. */ void db__init(struct db *db, struct config *config, const char *filename); /** * Release all memory associated with a database object. * * If the follower connection was opened, it will be closed. */ void db__close(struct db *db); /** * Open the follower connection associated with this database. */ int db__open_follower(struct db *db); #endif /* DB_H_*/ dqlite-1.9.1/src/dqlite.c000066400000000000000000000020451416342771300152320ustar00rootroot00000000000000#include "../include/dqlite.h" #include "vfs.h" int dqlite_version_number (void) { return DQLITE_VERSION_NUMBER; } int dqlite_vfs_init(sqlite3_vfs *vfs, const char *name) { return VfsInit(vfs, name); } void dqlite_vfs_close(sqlite3_vfs *vfs) { VfsClose(vfs); } int dqlite_vfs_poll(sqlite3_vfs *vfs, const char *filename, dqlite_vfs_frame **frames, unsigned *n) { return VfsPoll(vfs, filename, frames, n); } int dqlite_vfs_apply(sqlite3_vfs *vfs, const char *filename, unsigned n, unsigned long *page_numbers, void *frames) { return VfsApply(vfs, filename, n, page_numbers, frames); } int dqlite_vfs_abort(sqlite3_vfs *vfs, const char *filename) { return VfsAbort(vfs, filename); } int dqlite_vfs_snapshot(sqlite3_vfs *vfs, const char *filename, void **data, size_t *n) { return VfsSnapshot(vfs, filename, data, n); } int dqlite_vfs_restore(sqlite3_vfs *vfs, const char *filename, const void *data, size_t n) { return VfsRestore(vfs, filename, data, n); } dqlite-1.9.1/src/error.c000066400000000000000000000071571416342771300151120ustar00rootroot00000000000000#include #include #include #include #include #include #include "../include/dqlite.h" #include "./lib/assert.h" #include "error.h" /* Fallback message returned when failing to allocate the error message * itself. */ static char *dqlite__error_oom_msg = "error message unavailable (out of memory)"; void dqlite__error_init(dqlite__error *e) { *e = NULL; } void dqlite__error_close(dqlite__error *e) { if (*e != NULL && *e != dqlite__error_oom_msg) { sqlite3_free(*e); } } /* Set an error message by rendering the given format against the given * parameters. * * Any previously set error message will be cleared. */ static void dqlite__error_vprintf(dqlite__error *e, const char * fmt, va_list args) { assert(fmt != NULL); /* If a previous error was set (other than the hard-coded OOM fallback * fallback), let's free it. */ if (*e != NULL && *e != dqlite__error_oom_msg) { sqlite3_free(*e); } /* Render the message. In case of error we fallback to the hard-coded * OOM fallback message. */ *e = sqlite3_vmprintf(fmt, args); if (*e == NULL) { *e = dqlite__error_oom_msg; } } void dqlite__error_printf(dqlite__error *e, const char *fmt, ...) { va_list args; va_start(args, fmt); dqlite__error_vprintf(e, fmt, args); va_end(args); } static void dqlite__error_vwrapf(dqlite__error *e, const char * cause, const char * fmt, va_list args) { dqlite__error tmp; char * msg; /* First, print the format and arguments, using a temporary error. */ dqlite__error_init(&tmp); dqlite__error_vprintf(&tmp, fmt, args); if (cause == NULL) { /* Special case the cause error being empty. */ dqlite__error_printf(e, "%s: (null)", tmp); } else if (cause == *e) { /* When the error is wrapping itself, we need to make a copy */ dqlite__error_copy(e, &msg); dqlite__error_printf(e, "%s: %s", tmp, msg); sqlite3_free(msg); } else { dqlite__error_printf(e, "%s: %s", tmp, cause); } dqlite__error_close(&tmp); } void dqlite__error_wrapf(dqlite__error * e, const dqlite__error *cause, const char * fmt, ...) { va_list args; va_start(args, fmt); dqlite__error_vwrapf(e, (const char *)(*cause), fmt, args); va_end(args); } void dqlite__error_oom(dqlite__error *e, const char *msg, ...) { va_list args; va_start(args, msg); dqlite__error_vwrapf(e, "out of memory", msg, args); va_end(args); } void dqlite__error_sys(dqlite__error *e, const char *msg) { dqlite__error_printf(e, "%s: %s", msg, strerror(errno)); } void dqlite__error_uv(dqlite__error *e, int err, const char *msg) { dqlite__error_printf( e, "%s: %s (%s)", msg, uv_strerror(err), uv_err_name(err)); } int dqlite__error_copy(dqlite__error *e, char **msg) { char * copy; size_t len; assert(e != NULL); assert(msg != NULL); /* Trying to copy an empty error message is an error. */ if (*e == NULL) { *msg = NULL; return DQLITE_ERROR; } len = strlen(*e) + 1; copy = sqlite3_malloc((int)(len * sizeof *copy)); if (copy == NULL) { *msg = NULL; return DQLITE_NOMEM; } memcpy(copy, *e, len); *msg = copy; return 0; } int dqlite__error_is_null(dqlite__error *e) { return *e == NULL; } int dqlite__error_is_disconnect(dqlite__error *e) { if (*e == NULL) return 0; if (strstr(*e, uv_err_name(UV_EOF)) != NULL) return 1; if (strstr(*e, uv_err_name(UV_ECONNRESET)) != NULL) return 1; return 0; } dqlite-1.9.1/src/error.h000066400000000000000000000025641416342771300151140ustar00rootroot00000000000000#ifndef DQLITE_ERROR_H #define DQLITE_ERROR_H #include #include /* A message describing the last error occurred on an object */ typedef char *dqlite__error; /* Initialize the error with an empty message */ void dqlite__error_init(dqlite__error *e); /* Release the memory of the error message, if any is set */ void dqlite__error_close(dqlite__error *e); /* Set the error message */ void dqlite__error_printf(dqlite__error *e, const char *fmt, ...); /* Wrap an error with an additional message */ void dqlite__error_wrapf(dqlite__error * e, const dqlite__error *cause, const char * fmt, ...); /* Out of memory error */ void dqlite__error_oom(dqlite__error *e, const char *msg, ...); /* Wrap a system error */ void dqlite__error_sys(dqlite__error *e, const char *msg); /* Wrap an error from libuv */ void dqlite__error_uv(dqlite__error *e, int err, const char *msg); /* Copy the underlying error message. * * Client code is responsible of invoking sqlite3_free to deallocate the * returned string. */ int dqlite__error_copy(dqlite__error *e, char **msg); /* Whether the error is not set */ int dqlite__error_is_null(dqlite__error *e); /* Whether the error is due to client disconnection */ int dqlite__error_is_disconnect(dqlite__error *e); #endif /* DQLITE_ERROR_H */ dqlite-1.9.1/src/format.c000066400000000000000000000065061416342771300152460ustar00rootroot00000000000000#include #include #include #include #include "./lib/assert.h" #include "format.h" /* WAL magic value. Either this value, or the same value with the least * significant bit also set (FORMAT__WAL_MAGIC | 0x00000001) is stored in 32-bit * big-endian format in the first 4 bytes of a WAL file. * * If the LSB is set, then the checksums for each frame within the WAL file are * calculated by treating all data as an array of 32-bit big-endian * words. Otherwise, they are calculated by interpreting all data as 32-bit * little-endian words. */ #define FORMAT__WAL_MAGIC 0x377f0682 #define FORMAT__WAL_MAX_VERSION 3007000 static void formatGet32(const uint8_t buf[4], uint32_t *v) { *v = 0; *v += (uint32_t)(buf[0] << 24); *v += (uint32_t)(buf[1] << 16); *v += (uint32_t)(buf[2] << 8); *v += (uint32_t)(buf[3]); } /* Encode a 32-bit number to big endian format */ static void formatPut32(uint32_t v, uint8_t *buf) { buf[0] = (uint8_t)(v >> 24); buf[1] = (uint8_t)(v >> 16); buf[2] = (uint8_t)(v >> 8); buf[3] = (uint8_t)v; } /* * Generate or extend an 8 byte checksum based on the data in array data[] and * the initial values of in[0] and in[1] (or initial values of 0 and 0 if * in==NULL). * * The checksum is written back into out[] before returning. * * n must be a positive multiple of 8. */ static void formatWalChecksumBytes( bool native, /* True for native byte-order, false for non-native */ uint8_t *data, /* Content to be checksummed */ unsigned n, /* Bytes of content in a[]. Must be a multiple of 8. */ const uint32_t in[2], /* Initial checksum value input */ uint32_t out[2] /* OUT: Final checksum value output */ ) { uint32_t s1, s2; /* `data` is an alias for the `hdr` member of a `struct vfsWal`. `hdr` * is the first member of this struct. Because `struct vfsWal` contains * pointer members, the struct itself will have the alignment of the pointer * members. As `hdr` is the first member, it will have this alignment too. * Therefore it is safe to assume pointer alignment (and silence the compiler). * more info -> http://www.catb.org/esr/structure-packing/ */ uint32_t *cur = (uint32_t *) __builtin_assume_aligned(data, sizeof(void*)); uint32_t *end = (uint32_t *) __builtin_assume_aligned(&data[n], sizeof(void*)); if (in) { s1 = in[0]; s2 = in[1]; } else { s1 = s2 = 0; } assert(n >= 8); assert((n & 0x00000007) == 0); assert(n <= 65536); if (native) { do { s1 += *cur++ + s2; s2 += *cur++ + s1; } while (cur < end); } else { do { uint32_t d; formatPut32(cur[0], (uint8_t *)&d); s1 += d + s2; formatPut32(cur[1], (uint8_t *)&d); s2 += d + s1; cur += 2; } while (cur < end); } out[0] = s1; out[1] = s2; } void formatWalRestartHeader(uint8_t *header) { uint32_t checksum[2] = {0, 0}; uint32_t checkpoint; uint32_t salt1; /* Increase the checkpoint sequence. */ formatGet32(&header[12], &checkpoint); checkpoint++; formatPut32(checkpoint, &header[12]); /* Increase salt1. */ formatGet32(&header[16], &salt1); salt1++; formatPut32(salt1, &header[16]); /* Generate a random salt2. */ sqlite3_randomness(4, &header[20]); /* Update the checksum. */ formatWalChecksumBytes(true, header, 24, checksum, checksum); formatPut32(checksum[0], header + 24); formatPut32(checksum[1], header + 28); } dqlite-1.9.1/src/format.h000066400000000000000000000024361416342771300152510ustar00rootroot00000000000000/* Utilities around SQLite file formats. * * See https://sqlite.org/fileformat.html. */ #ifndef FORMAT_H_ #define FORMAT_H_ #include #include /* Minumum and maximum page size. */ #define FORMAT__PAGE_SIZE_MIN 512 #define FORMAT__PAGE_SIZE_MAX 65536 /* Database header size. */ #define FORMAT__DB_HDR_SIZE 100 /* Write ahead log header size. */ #define FORMAT__WAL_HDR_SIZE 32 /* Write ahead log frame header size. */ #define FORMAT__WAL_FRAME_HDR_SIZE 24 /* Number of reader marks in the wal index header. */ #define FORMAT__WAL_NREADER 5 /* Given the page size, calculate the size of a full WAL frame (frame header * plus page data). */ #define formatWalCalcFrameSize(PAGE_SIZE) \ (FORMAT__WAL_FRAME_HDR_SIZE + PAGE_SIZE) /* Given the page size and the WAL file size, calculate the number of frames it * has. */ #define formatWalCalcFramesNumber(PAGE_SIZE, SIZE) \ ((SIZE - FORMAT__WAL_HDR_SIZE) / formatWalCalcFrameSize(PAGE_SIZE)) /* Given the page size, calculate the WAL page number of the frame starting at * the given offset. */ #define formatWalCalcFrameIndex(PAGE_SIZE, OFFSET) \ (formatWalCalcFramesNumber(PAGE_SIZE, OFFSET) + 1) /* Restart the header of a WAL file after a checkpoint. */ void formatWalRestartHeader(uint8_t *header); #endif /* FORMAT_H */ dqlite-1.9.1/src/fsm.c000066400000000000000000000266541416342771300145510ustar00rootroot00000000000000#include #include "lib/assert.h" #include "lib/serialize.h" #include "command.h" #include "fsm.h" #include "tracing.h" #include "vfs.h" struct fsm { struct logger *logger; struct registry *registry; struct { unsigned n_pages; unsigned long *page_numbers; uint8_t *pages; } pending; /* For upgrades from V1 */ }; static int apply_open(struct fsm *f, const struct command_open *c) { tracef("fsm apply open"); (void)f; (void)c; return 0; } static int add_pending_pages(struct fsm *f, unsigned long *page_numbers, uint8_t *pages, unsigned n_pages, unsigned page_size) { unsigned n = f->pending.n_pages + n_pages; unsigned i; f->pending.page_numbers = sqlite3_realloc64( f->pending.page_numbers, n * sizeof *f->pending.page_numbers); if (f->pending.page_numbers == NULL) { return DQLITE_NOMEM; } f->pending.pages = sqlite3_realloc64(f->pending.pages, n * page_size); if (f->pending.pages == NULL) { return DQLITE_NOMEM; } for (i = 0; i < n_pages; i++) { unsigned j = f->pending.n_pages + i; f->pending.page_numbers[j] = page_numbers[i]; memcpy(f->pending.pages + j * page_size, (uint8_t *)pages + i * page_size, page_size); } f->pending.n_pages = n; return 0; } static int apply_frames(struct fsm *f, const struct command_frames *c) { tracef("fsm apply frames"); struct db *db; sqlite3_vfs *vfs; unsigned long *page_numbers = NULL; void *pages; int exists; int rv; rv = registry__db_get(f->registry, c->filename, &db); if (rv != 0) { tracef("db get failed %d", rv); return rv; } vfs = sqlite3_vfs_find(db->config->name); /* Check if the database file exists, and create it by opening a * connection if it doesn't. */ rv = vfs->xAccess(vfs, c->filename, 0, &exists); assert(rv == 0); if (!exists) { rv = db__open_follower(db); if (rv != 0) { tracef("open follower failed %d", rv); return rv; } sqlite3_close(db->follower); db->follower = NULL; } rv = command_frames__page_numbers(c, &page_numbers); if (rv != 0) { if (page_numbers != NULL) { sqlite3_free(page_numbers); } tracef("page numbers failed %d", rv); return rv; } command_frames__pages(c, &pages); /* If the commit marker is set, we apply the changes directly to the * VFS. Otherwise, if the commit marker is not set, this must be an * upgrade from V1, we accumulate uncommitted frames in memory until the * final commit or a rollback. */ if (c->is_commit) { if (f->pending.n_pages > 0) { rv = add_pending_pages(f, page_numbers, pages, c->frames.n_pages, db->config->page_size); if (rv != 0) { tracef("malloc"); sqlite3_free(page_numbers); return DQLITE_NOMEM; } rv = VfsApply(vfs, db->filename, f->pending.n_pages, f->pending.page_numbers, f->pending.pages); if (rv != 0) { tracef("VfsApply failed %d", rv); sqlite3_free(page_numbers); return rv; } sqlite3_free(f->pending.page_numbers); sqlite3_free(f->pending.pages); f->pending.n_pages = 0; f->pending.page_numbers = NULL; f->pending.pages = NULL; } else { rv = VfsApply(vfs, db->filename, c->frames.n_pages, page_numbers, pages); if (rv != 0) { tracef("VfsApply failed %d", rv); sqlite3_free(page_numbers); return rv; } } } else { rv = add_pending_pages(f, page_numbers, pages, c->frames.n_pages, db->config->page_size); if (rv != 0) { tracef("add pending pages failed %d", rv); sqlite3_free(page_numbers); return DQLITE_NOMEM; } } sqlite3_free(page_numbers); return 0; } static int apply_undo(struct fsm *f, const struct command_undo *c) { tracef("apply undo %" PRIu64, c->tx_id); (void)c; if (f->pending.n_pages == 0) { return 0; } sqlite3_free(f->pending.page_numbers); sqlite3_free(f->pending.pages); f->pending.n_pages = 0; f->pending.page_numbers = NULL; f->pending.pages = NULL; return 0; } static int apply_checkpoint(struct fsm *f, const struct command_checkpoint *c) { tracef("apply checkpoint"); struct db *db; struct sqlite3_file *file; int size; int ckpt; int rv; rv = registry__db_get(f->registry, c->filename, &db); assert(rv == 0); /* We have registered this filename before. */ /* Use a new connection to force re-opening the WAL. */ rv = db__open_follower(db); if (rv != 0) { tracef("open failed %d", rv); return rv; } rv = sqlite3_file_control(db->follower, "main", SQLITE_FCNTL_FILE_POINTER, &file); assert(rv == SQLITE_OK); /* Should never fail */ /* If there's a checkpoint lock in place, we must be the node that * originated the checkpoint command in the first place, let's release * it. */ file->pMethods->xShmLock(file, 1 /* checkpoint lock */, 1, SQLITE_SHM_UNLOCK | SQLITE_SHM_EXCLUSIVE); rv = sqlite3_wal_checkpoint_v2( db->follower, "main", SQLITE_CHECKPOINT_TRUNCATE, &size, &ckpt); if (rv != 0) { tracef("sqlite wal checkpoint failed %d", rv); return rv; } sqlite3_close(db->follower); db->follower = NULL; /* Since no reader transaction is in progress, we must be able to * checkpoint the entire WAL */ assert(size == 0); assert(ckpt == 0); return 0; } static int fsm__apply(struct raft_fsm *fsm, const struct raft_buffer *buf, void **result) { tracef("fsm apply"); struct fsm *f = fsm->data; int type; void *command; int rc; rc = command__decode(buf, &type, &command); if (rc != 0) { // errorf(f->logger, "fsm: decode command: %d", rc); goto err; } switch (type) { case COMMAND_OPEN: rc = apply_open(f, command); break; case COMMAND_FRAMES: rc = apply_frames(f, command); break; case COMMAND_UNDO: rc = apply_undo(f, command); break; case COMMAND_CHECKPOINT: rc = apply_checkpoint(f, command); break; default: rc = RAFT_MALFORMED; goto err_after_command_decode; } raft_free(command); *result = NULL; return 0; err_after_command_decode: raft_free(command); err: return rc; } #define SNAPSHOT_FORMAT 1 #define SNAPSHOT_HEADER(X, ...) \ X(uint64, format, ##__VA_ARGS__) \ X(uint64, n, ##__VA_ARGS__) SERIALIZE__DEFINE(snapshotHeader, SNAPSHOT_HEADER); SERIALIZE__IMPLEMENT(snapshotHeader, SNAPSHOT_HEADER); #define SNAPSHOT_DATABASE(X, ...) \ X(text, filename, ##__VA_ARGS__) \ X(uint64, main_size, ##__VA_ARGS__) \ X(uint64, wal_size, ##__VA_ARGS__) SERIALIZE__DEFINE(snapshotDatabase, SNAPSHOT_DATABASE); SERIALIZE__IMPLEMENT(snapshotDatabase, SNAPSHOT_DATABASE); /* Encode the global snapshot header. */ static int encodeSnapshotHeader(unsigned n, struct raft_buffer *buf) { struct snapshotHeader header; void *cursor; header.format = SNAPSHOT_FORMAT; header.n = n; buf->len = snapshotHeader__sizeof(&header); buf->base = raft_malloc(buf->len); if (buf->base == NULL) { return RAFT_NOMEM; } cursor = buf->base; snapshotHeader__encode(&header, &cursor); return 0; } /* Encode the given database. */ static int encodeDatabase(struct db *db, struct raft_buffer bufs[2]) { struct snapshotDatabase header; sqlite3_vfs *vfs; uint32_t database_size = 0; uint8_t *page; void *cursor; int rv; header.filename = db->filename; vfs = sqlite3_vfs_find(db->config->name); rv = VfsSnapshot(vfs, db->filename, &bufs[1].base, &bufs[1].len); if (rv != 0) { goto err; } /* Extract the database size from the first page. */ page = bufs[1].base; database_size += (uint32_t)(page[28] << 24); database_size += (uint32_t)(page[29] << 16); database_size += (uint32_t)(page[30] << 8); database_size += (uint32_t)(page[31]); header.main_size = database_size * db->config->page_size; header.wal_size = bufs[1].len - header.main_size; /* Database header. */ bufs[0].len = snapshotDatabase__sizeof(&header); bufs[0].base = raft_malloc(bufs[0].len); if (bufs[0].base == NULL) { rv = RAFT_NOMEM; goto err_after_snapshot; } cursor = bufs[0].base; snapshotDatabase__encode(&header, &cursor); return 0; err_after_snapshot: raft_free(bufs[1].base); err: assert(rv != 0); return rv; } /* Decode the database contained in a snapshot. */ static int decodeDatabase(struct fsm *f, struct cursor *cursor) { struct snapshotDatabase header; struct db *db; sqlite3_vfs *vfs; size_t n; int exists; int rv; rv = snapshotDatabase__decode(cursor, &header); if (rv != 0) { return rv; } rv = registry__db_get(f->registry, header.filename, &db); if (rv != 0) { return rv; } vfs = sqlite3_vfs_find(db->config->name); /* Check if the database file exists, and create it by opening a * connection if it doesn't. */ rv = vfs->xAccess(vfs, header.filename, 0, &exists); assert(rv == 0); if (!exists) { rv = db__open_follower(db); if (rv != 0) { return rv; } sqlite3_close(db->follower); db->follower = NULL; } if (header.main_size + header.wal_size > SIZE_MAX) { tracef("main_size + wal_size would overflow max DB size"); return -1; } /* Due to the check above, this cast is safe. */ n = (size_t)(header.main_size + header.wal_size); rv = VfsRestore(vfs, db->filename, cursor->p, n); if (rv != 0) { return rv; } cursor->p += n; return 0; } static int fsm__snapshot(struct raft_fsm *fsm, struct raft_buffer *bufs[], unsigned *n_bufs) { struct fsm *f = fsm->data; queue *head; struct db *db; unsigned n = 0; unsigned i; int rv; /* First count how many databases we have and check that no transaction * is in progress. */ QUEUE__FOREACH(head, &f->registry->dbs) { db = QUEUE__DATA(head, struct db, queue); if (db->tx_id != 0) { return RAFT_BUSY; } n++; } *n_bufs = 1; /* Snapshot header */ *n_bufs += n * 2; /* Database header an content */ *bufs = raft_malloc(*n_bufs * sizeof **bufs); if (*bufs == NULL) { rv = RAFT_NOMEM; goto err; } rv = encodeSnapshotHeader(n, &(*bufs)[0]); if (rv != 0) { goto err_after_bufs_alloc; } /* Encode individual databases. */ i = 1; QUEUE__FOREACH(head, &f->registry->dbs) { db = QUEUE__DATA(head, struct db, queue); rv = encodeDatabase(db, &(*bufs)[i]); if (rv != 0) { goto err_after_encode_header; } i += 2; } return 0; err_after_encode_header: do { raft_free((*bufs)[i].base); i--; } while (i > 0); err_after_bufs_alloc: raft_free(*bufs); err: assert(rv != 0); return rv; } static int fsm__restore(struct raft_fsm *fsm, struct raft_buffer *buf) { tracef("fsm restore"); struct fsm *f = fsm->data; struct cursor cursor = {buf->base, buf->len}; struct snapshotHeader header; unsigned i; int rv; rv = snapshotHeader__decode(&cursor, &header); if (rv != 0) { tracef("decode failed %d", rv); return rv; } if (header.format != SNAPSHOT_FORMAT) { tracef("bad format"); return RAFT_MALFORMED; } for (i = 0; i < header.n; i++) { rv = decodeDatabase(f, &cursor); if (rv != 0) { tracef("decode failed"); return rv; } } raft_free(buf->base); return 0; } int fsm__init(struct raft_fsm *fsm, struct config *config, struct registry *registry) { tracef("fsm init"); struct fsm *f = raft_malloc(sizeof *f); if (f == NULL) { return DQLITE_NOMEM; } f->logger = &config->logger; f->registry = registry; f->pending.n_pages = 0; f->pending.page_numbers = NULL; f->pending.pages = NULL; fsm->version = 1; fsm->data = f; fsm->apply = fsm__apply; fsm->snapshot = fsm__snapshot; fsm->restore = fsm__restore; return 0; } void fsm__close(struct raft_fsm *fsm) { tracef("fsm close"); struct fsm *f = fsm->data; raft_free(f); } dqlite-1.9.1/src/fsm.h000066400000000000000000000006511416342771300145430ustar00rootroot00000000000000/** * Dqlite Raft FSM */ #ifndef DQLITE_FSM_H_ #define DQLITE_FSM_H_ #include #include "registry.h" #include "config.h" /** * Initialize the given SQLite replication interface with dqlite's raft based * implementation. */ int fsm__init(struct raft_fsm *fsm, struct config *config, struct registry *registry); void fsm__close(struct raft_fsm *fsm); #endif /* DQLITE_REPLICATION_METHODS_H_ */ dqlite-1.9.1/src/gateway.c000066400000000000000000000657301416342771300154230ustar00rootroot00000000000000#include "gateway.h" #include "bind.h" #include "protocol.h" #include "query.h" #include "request.h" #include "response.h" #include "tracing.h" #include "translate.h" #include "vfs.h" void gateway__init(struct gateway *g, struct config *config, struct registry *registry, struct raft *raft) { tracef("gateway init"); g->config = config; g->registry = registry; g->raft = raft; g->leader = NULL; g->req = NULL; g->stmt = NULL; g->stmt_finalize = false; g->exec.data = g; g->sql = NULL; stmt__registry_init(&g->stmts); g->barrier.data = g; g->barrier.cb = NULL; g->barrier.leader = NULL; g->protocol = DQLITE_PROTOCOL_VERSION; } void gateway__close(struct gateway *g) { tracef("gateway close"); if (g->leader == NULL) { stmt__registry_close(&g->stmts); return; } if (g->stmt != NULL) { if (g->leader->inflight != NULL) { tracef("finish inflight apply request"); struct raft_apply *req = &g->leader->inflight->req; req->cb(req, RAFT_SHUTDOWN, NULL); assert(g->req == NULL); assert(g->stmt == NULL); } else if (g->barrier.cb != NULL) { tracef("finish inflight query barrier"); /* This is not a typo, g->barrier.req.cb is a wrapper * around g->barrier.cb and when called, will set g->barrier.cb to NULL. * */ struct raft_barrier *b = &g->barrier.req; b->cb(b, RAFT_SHUTDOWN); assert(g->barrier.cb == NULL); } else if (g->leader->exec != NULL && g->leader->exec->barrier.cb != NULL) { tracef("finish inflight exec barrier"); struct raft_barrier *b = &g->leader->exec->barrier.req; b->cb(b, RAFT_SHUTDOWN); assert(g->leader->exec == NULL); } else if (g->req != NULL && g->req->type != DQLITE_REQUEST_QUERY && g->req->type != DQLITE_REQUEST_EXEC) { /* Regular exec and query stmt's will be closed when the * registry is closed below. */ tracef("finalize exec_sql or query_sql type:%d", g->req->type); sqlite3_finalize(g->stmt); g->stmt = NULL; } } stmt__registry_close(&g->stmts); leader__close(g->leader); sqlite3_free(g->leader); } /* Declare a request struct and a response struct of the appropriate types and * decode the request. */ #define START(REQ, RES) \ struct request_##REQ request; \ struct response_##RES response; \ { \ int rv_; \ rv_ = request_##REQ##__decode(cursor, &request); \ if (rv_ != 0) { \ return rv_; \ } \ } #define CHECK_LEADER(REQ) \ if (raft_state(req->gateway->raft) != RAFT_LEADER) { \ failure(req, SQLITE_IOERR_NOT_LEADER, "not leader"); \ return 0; \ } /* Encode the given success response and invoke the request callback */ #define SUCCESS(LOWER, UPPER) \ { \ size_t _n = response_##LOWER##__sizeof(&response); \ void *_cursor; \ assert(_n % 8 == 0); \ _cursor = buffer__advance(req->buffer, _n); \ /* Since responses are small and the buffer it's at least 4096 \ * bytes, this can't fail. */ \ assert(_cursor != NULL); \ response_##LOWER##__encode(&response, &_cursor); \ req->cb(req, 0, DQLITE_RESPONSE_##UPPER); \ } /* Lookup the database with the given ID. * * TODO: support more than one database per connection? */ #define LOOKUP_DB(ID) \ if (ID != 0 || req->gateway->leader == NULL) { \ failure(req, SQLITE_NOTFOUND, "no database opened"); \ return 0; \ } /* Lookup the statement with the given ID. */ #define LOOKUP_STMT(ID) \ stmt = stmt__registry_get(&req->gateway->stmts, ID); \ if (stmt == NULL) { \ failure(req, SQLITE_NOTFOUND, \ "no statement with the given id"); \ return 0; \ } #define FAIL_IF_CHECKPOINTING \ { \ struct sqlite3_file *_file; \ int _rv; \ _rv = sqlite3_file_control(g->leader->conn, "main", \ SQLITE_FCNTL_FILE_POINTER, &_file); \ assert(_rv == SQLITE_OK); /* Should never fail */ \ \ _rv = _file->pMethods->xShmLock( \ _file, 1 /* checkpoint lock */, 1, \ SQLITE_SHM_LOCK | SQLITE_SHM_EXCLUSIVE); \ if (_rv != 0) { \ assert(_rv == SQLITE_BUSY); \ failure(req, SQLITE_BUSY, "checkpoint in progress"); \ return 0; \ } \ _file->pMethods->xShmLock( \ _file, 1 /* checkpoint lock */, 1, \ SQLITE_SHM_UNLOCK | SQLITE_SHM_EXCLUSIVE); \ } /* Encode fa failure response and invoke the request callback */ static void failure(struct handle *req, int code, const char *message) { struct response_failure failure; size_t n; void *cursor; failure.code = (uint64_t)code; failure.message = message; n = response_failure__sizeof(&failure); assert(n % 8 == 0); cursor = buffer__advance(req->buffer, n); /* The buffer has at least 4096 bytes, and error messages are shorter * than that. So this can't fail. */ assert(cursor != NULL); response_failure__encode(&failure, &cursor); req->cb(req, 0, DQLITE_RESPONSE_FAILURE); } static int handle_leader_legacy(struct handle *req, struct cursor *cursor) { tracef("handle leader legacy"); START(leader, server_legacy); raft_id id; raft_leader(req->gateway->raft, &id, &response.address); if (response.address == NULL) { response.address = ""; } SUCCESS(server_legacy, SERVER_LEGACY); return 0; } static int handle_leader(struct handle *req, struct cursor *cursor) { tracef("handle leader"); struct gateway *g = req->gateway; raft_id id = 0; const char *address = NULL; unsigned i; if (g->protocol == DQLITE_PROTOCOL_VERSION_LEGACY) { return handle_leader_legacy(req, cursor); } START(leader, server); /* Only voters might now who the leader is. */ for (i = 0; i < g->raft->configuration.n; i++) { struct raft_server *server = &g->raft->configuration.servers[i]; if (server->id == g->raft->id && server->role == RAFT_VOTER) { tracef("handle leader - dispatch to %llu", server->id); raft_leader(req->gateway->raft, &id, &address); break; } } response.id = id; response.address = address; if (response.address == NULL) { response.address = ""; } SUCCESS(server, SERVER); return 0; } static int handle_client(struct handle *req, struct cursor *cursor) { tracef("handle client"); START(client, welcome); response.heartbeat_timeout = req->gateway->config->heartbeat_timeout; SUCCESS(welcome, WELCOME); return 0; } static int handle_open(struct handle *req, struct cursor *cursor) { tracef("handle open"); struct gateway *g = req->gateway; struct db *db; int rc; START(open, db); if (g->leader != NULL) { tracef("already open"); failure(req, SQLITE_BUSY, "a database for this connection is already open"); return 0; } rc = registry__db_get(g->registry, request.filename, &db); if (rc != 0) { tracef("registry db get failed %d", rc); return rc; } g->leader = sqlite3_malloc(sizeof *g->leader); if (g->leader == NULL) { tracef("malloc failed"); return DQLITE_NOMEM; } rc = leader__init(g->leader, db, g->raft); if (rc != 0) { tracef("leader init failed %d", rc); sqlite3_free(g->leader); g->leader = NULL; return rc; } response.id = 0; SUCCESS(db, DB); return 0; } static int handle_prepare(struct handle *req, struct cursor *cursor) { tracef("handle prepare"); struct gateway *g = req->gateway; struct stmt *stmt; const char *tail; int rc; START(prepare, stmt); LOOKUP_DB(request.db_id); rc = stmt__registry_add(&g->stmts, &stmt); if (rc != 0) { tracef("handle prepare registry add failed %d", rc); return rc; } assert(stmt != NULL); rc = sqlite3_prepare_v2(g->leader->conn, request.sql, -1, &stmt->stmt, &tail); if (rc != SQLITE_OK) { tracef("handle prepare sqlite prepare failed %d", rc); failure(req, rc, sqlite3_errmsg(g->leader->conn)); return 0; } response.db_id = (uint32_t)request.db_id; response.id = (uint32_t)stmt->id; response.params = (uint64_t)sqlite3_bind_parameter_count(stmt->stmt); SUCCESS(stmt, STMT); return 0; } /* Fill a result response with the last inserted ID and number of rows * affected. */ static void fill_result(struct gateway *g, struct response_result *response) { response->last_insert_id = (uint64_t)sqlite3_last_insert_rowid(g->leader->conn); response->rows_affected = (uint64_t)sqlite3_changes(g->leader->conn); } static const char *error_message(sqlite3 *db, int rc) { switch (rc) { case SQLITE_IOERR_LEADERSHIP_LOST: return "disk I/O error"; case SQLITE_IOERR_WRITE: return "disk I/O error"; case SQLITE_ABORT: return "abort"; } return sqlite3_errmsg(db); } static void leader_exec_cb(struct exec *exec, int status) { struct gateway *g = exec->data; struct handle *req = g->req; sqlite3_stmt *stmt = g->stmt; struct response_result response; g->req = NULL; g->stmt = NULL; if (status == SQLITE_DONE) { fill_result(g, &response); SUCCESS(result, RESULT); } else { failure(req, status, error_message(g->leader->conn, status)); sqlite3_reset(stmt); } } static int handle_exec(struct handle *req, struct cursor *cursor) { tracef("handle exec"); struct gateway *g = req->gateway; struct stmt *stmt; int rv; START(exec, result); CHECK_LEADER(req); LOOKUP_DB(request.db_id); LOOKUP_STMT(request.stmt_id); FAIL_IF_CHECKPOINTING; (void)response; rv = bind__params(stmt->stmt, cursor); if (rv != 0) { tracef("handle exec bind failed %d", rv); failure(req, rv, "bind parameters"); return 0; } g->req = req; g->stmt = stmt->stmt; rv = leader__exec(g->leader, &g->exec, stmt->stmt, leader_exec_cb); if (rv != 0) { tracef("handle exec leader exec failed %d", rv); return rv; } return 0; } /* Step through the given statement and populate the response buffer of the * given request with a single batch of rows. * * A single batch of rows is typically about the size of a memory page. */ static void query_batch(sqlite3_stmt *stmt, struct handle *req) { struct gateway *g = req->gateway; struct response_rows response; int rc; rc = query__batch(stmt, req->buffer); if (rc != SQLITE_ROW && rc != SQLITE_DONE) { sqlite3_reset(stmt); failure(req, rc, sqlite3_errmsg(g->leader->conn)); goto done; } if (rc == SQLITE_ROW) { response.eof = DQLITE_RESPONSE_ROWS_PART; g->req = req; g->stmt = stmt; SUCCESS(rows, ROWS); return; } else { response.eof = DQLITE_RESPONSE_ROWS_DONE; SUCCESS(rows, ROWS); } done: if (g->stmt_finalize) { /* TODO: do we care about errors? */ sqlite3_finalize(stmt); g->stmt_finalize = false; } g->stmt = NULL; g->req = NULL; } static void query_barrier_cb(struct barrier *barrier, int status) { tracef("query barrier cb status:%d", status); struct gateway *g = barrier->data; struct handle *handle = g->req; sqlite3_stmt *stmt = g->stmt; assert(handle != NULL); assert(stmt != NULL); g->stmt = NULL; g->req = NULL; if (status != 0) { if (g->stmt_finalize) { sqlite3_finalize(stmt); g->stmt_finalize = false; } failure(handle, status, "barrier error"); return; } query_batch(stmt, handle); } static int handle_query(struct handle *req, struct cursor *cursor) { tracef("handle query"); struct gateway *g = req->gateway; struct stmt *stmt; int rv; START(query, rows); CHECK_LEADER(req); LOOKUP_DB(request.db_id); LOOKUP_STMT(request.stmt_id); FAIL_IF_CHECKPOINTING; (void)response; rv = bind__params(stmt->stmt, cursor); if (rv != 0) { tracef("handle query bind failed %d", rv); failure(req, rv, sqlite3_errmsg(g->leader->conn)); return 0; } g->req = req; g->stmt = stmt->stmt; rv = leader__barrier(g->leader, &g->barrier, query_barrier_cb); if (rv != 0) { tracef("handle query leader barrier failed %d", rv); g->req = NULL; g->stmt = NULL; return rv; } return 0; } static int handle_finalize(struct handle *req, struct cursor *cursor) { tracef("handle finalize"); struct stmt *stmt; int rv; START(finalize, empty); LOOKUP_DB(request.db_id); LOOKUP_STMT(request.stmt_id); rv = stmt__registry_del(&req->gateway->stmts, stmt); if (rv != 0) { tracef("handle finalize registry del failed %d", rv); failure(req, rv, "finalize statement"); return 0; } SUCCESS(empty, EMPTY); return 0; } static void handle_exec_sql_next(struct handle *req, struct cursor *cursor); static void handle_exec_sql_cb(struct exec *exec, int status) { tracef("handle exec sql cb status %d", status); struct gateway *g = exec->data; struct handle *req = g->req; if (status == SQLITE_DONE) { handle_exec_sql_next(req, NULL); } else { failure(req, status, error_message(g->leader->conn, status)); sqlite3_reset(g->stmt); sqlite3_finalize(g->stmt); g->req = NULL; g->stmt = NULL; g->sql = NULL; } } static void handle_exec_sql_next(struct handle *req, struct cursor *cursor) { tracef("handle exec sql next"); struct gateway *g = req->gateway; struct response_result response; const char *tail; int rv; if (g->sql == NULL || strcmp(g->sql, "") == 0) { goto success; } if (g->stmt != NULL) { sqlite3_finalize(g->stmt); g->stmt = NULL; } /* g->stmt will be set to NULL by sqlite when an error occurs. */ rv = sqlite3_prepare_v2(g->leader->conn, g->sql, -1, &g->stmt, &tail); if (rv != SQLITE_OK) { failure(req, rv, sqlite3_errmsg(g->leader->conn)); goto done; } if (g->stmt == NULL) { goto success; } /* TODO: what about bindings for multi-statement SQL text? */ if (cursor != NULL) { rv = bind__params(g->stmt, cursor); if (rv != SQLITE_OK) { failure(req, rv, sqlite3_errmsg(g->leader->conn)); goto done_after_prepare; } } g->sql = tail; g->req = req; rv = leader__exec(g->leader, &g->exec, g->stmt, handle_exec_sql_cb); if (rv != SQLITE_OK) { failure(req, rv, sqlite3_errmsg(g->leader->conn)); goto done_after_prepare; } return; success: tracef("handle exec sql next success"); if (g->stmt != NULL) { fill_result(g, &response); } SUCCESS(result, RESULT); done_after_prepare: if (g->stmt != NULL) { sqlite3_finalize(g->stmt); } done: g->req = NULL; g->stmt = NULL; g->sql = NULL; } static int handle_exec_sql(struct handle *req, struct cursor *cursor) { tracef("handle exec sql"); struct gateway *g = req->gateway; START(exec_sql, result); CHECK_LEADER(req); LOOKUP_DB(request.db_id); FAIL_IF_CHECKPOINTING; (void)response; assert(g->req == NULL); assert(g->sql == NULL); assert(g->stmt == NULL); req->gateway->sql = request.sql; handle_exec_sql_next(req, cursor); return 0; } static int handle_query_sql(struct handle *req, struct cursor *cursor) { tracef("handle query sql"); struct gateway *g = req->gateway; const char *tail; int rv; START(query_sql, rows); CHECK_LEADER(req); LOOKUP_DB(request.db_id); FAIL_IF_CHECKPOINTING; (void)response; rv = sqlite3_prepare_v2(g->leader->conn, request.sql, -1, &g->stmt, &tail); if (rv != SQLITE_OK) { tracef("handle query sql prepare failed %d", rv); failure(req, rv, sqlite3_errmsg(g->leader->conn)); return 0; } rv = bind__params(g->stmt, cursor); if (rv != 0) { tracef("handle query sql bind failed %d", rv); sqlite3_finalize(g->stmt); g->stmt = NULL; failure(req, rv, sqlite3_errmsg(g->leader->conn)); return 0; } g->stmt_finalize = true; g->req = req; rv = leader__barrier(g->leader, &g->barrier, query_barrier_cb); if (rv != 0) { tracef("barrier failed %d", rv); g->req = NULL; sqlite3_finalize(g->stmt); g->stmt = NULL; g->stmt_finalize = false; return rv; } return 0; } static int handle_interrupt(struct handle *req, struct cursor *cursor) { tracef("handle interrupt"); struct gateway *g = req->gateway; START(interrupt, empty); /* Take appropriate action depending on the cleanup code. */ if (g->stmt_finalize) { sqlite3_finalize(g->stmt); g->stmt_finalize = false; } g->stmt = NULL; g->req = NULL; SUCCESS(empty, EMPTY); return 0; } struct change { struct gateway *gateway; struct raft_change req; }; static void raftChangeCb(struct raft_change *change, int status) { tracef("raft change cb status %d", status); struct change *r = change->data; struct gateway *g = r->gateway; struct handle *req = g->req; struct response_empty response; g->req = NULL; sqlite3_free(r); if (status != 0) { failure(req, translateRaftErrCode(status), raft_strerror(status)); } else { SUCCESS(empty, EMPTY); } } static int handle_add(struct handle *req, struct cursor *cursor) { tracef("handle add"); struct gateway *g = req->gateway; struct change *r; int rv; START(add, empty); (void)response; CHECK_LEADER(req); r = sqlite3_malloc(sizeof *r); if (r == NULL) { return DQLITE_NOMEM; } r->gateway = g; r->req.data = r; g->req = req; rv = raft_add(g->raft, &r->req, request.id, request.address, raftChangeCb); if (rv != 0) { tracef("raft add failed %d", rv); g->req = NULL; sqlite3_free(r); failure(req, translateRaftErrCode(rv), raft_strerror(rv)); return 0; } return 0; } static int handle_assign(struct handle *req, struct cursor *cursor) { tracef("handle assign"); struct gateway *g = req->gateway; struct change *r; uint64_t role = DQLITE_VOTER; int rv; START(assign, empty); (void)response; CHECK_LEADER(req); /* Detect if this is an assign role request, instead of the former * promote request. */ if (cursor->cap > 0) { rv = uint64__decode(cursor, &role); if (rv != 0) { tracef("handle assign promote rv %d", rv); return rv; } } r = sqlite3_malloc(sizeof *r); if (r == NULL) { tracef("malloc failed"); return DQLITE_NOMEM; } r->gateway = g; r->req.data = r; g->req = req; rv = raft_assign(g->raft, &r->req, request.id, translateDqliteRole((int)role), raftChangeCb); if (rv != 0) { tracef("raft_assign failed %d", rv); g->req = NULL; sqlite3_free(r); failure(req, translateRaftErrCode(rv), raft_strerror(rv)); return 0; } return 0; } static int handle_remove(struct handle *req, struct cursor *cursor) { tracef("handle remove"); struct gateway *g = req->gateway; struct change *r; int rv; START(remove, empty); (void)response; CHECK_LEADER(req); r = sqlite3_malloc(sizeof *r); if (r == NULL) { tracef("malloc failed"); return DQLITE_NOMEM; } r->gateway = g; r->req.data = r; g->req = req; rv = raft_remove(g->raft, &r->req, request.id, raftChangeCb); if (rv != 0) { tracef("raft_remote failed %d", rv); g->req = NULL; sqlite3_free(r); failure(req, translateRaftErrCode(rv), raft_strerror(rv)); return 0; } return 0; } static int dumpFile(const char *filename, uint8_t *data, size_t n, struct buffer *buffer) { void *cur; uint64_t len = n; cur = buffer__advance(buffer, text__sizeof(&filename)); if (cur == NULL) { goto oom; } text__encode(&filename, &cur); cur = buffer__advance(buffer, uint64__sizeof(&len)); if (cur == NULL) { goto oom; } uint64__encode(&len, &cur); if (n == 0) { return 0; } assert(n % 8 == 0); assert(data != NULL); cur = buffer__advance(buffer, n); if (cur == NULL) { goto oom; } memcpy(cur, data, n); return 0; oom: return DQLITE_NOMEM; } static int handle_dump(struct handle *req, struct cursor *cursor) { tracef("handle dump"); bool err = true; struct gateway *g = req->gateway; sqlite3_vfs *vfs; void *cur; char filename[1024] = {0}; void *data; size_t n; uint8_t *page; uint32_t database_size = 0; uint8_t *database; uint8_t *wal; size_t n_database; size_t n_wal; int rv; START(dump, files); response.n = 2; cur = buffer__advance(req->buffer, response_files__sizeof(&response)); assert(cur != NULL); response_files__encode(&response, &cur); vfs = sqlite3_vfs_find(g->config->name); rv = VfsSnapshot(vfs, request.filename, &data, &n); if (rv != 0) { tracef("dump failed"); failure(req, rv, "failed to dump database"); return 0; } if (data != NULL) { /* Extract the database size from the first page. */ page = data; database_size += (uint32_t)(page[28] << 24); database_size += (uint32_t)(page[29] << 16); database_size += (uint32_t)(page[30] << 8); database_size += (uint32_t)(page[31]); n_database = database_size * g->config->page_size; n_wal = n - n_database; database = data; wal = database + n_database; } else { assert(n == 0); n_database = 0; n_wal = 0; database = NULL; wal = NULL; } rv = dumpFile(request.filename, database, n_database, req->buffer); if (rv != 0) { tracef("dump failed"); failure(req, rv, "failed to dump database"); goto out_free_data; } /* filename is zero inited and initially we allow only writing 1024 - 4 * - 1 bytes to it, so after strncpy filename will be zero-terminated * and will not have overflowed. strcat adds the 4 byte suffix and * also zero terminates the resulting string. */ const char * wal_suffix = "-wal"; strncpy(filename, request.filename, sizeof(filename) - strlen(wal_suffix) - 1); strcat(filename, wal_suffix); rv = dumpFile(filename, wal, n_wal, req->buffer); if (rv != 0) { tracef("wal dump failed"); failure(req, rv, "failed to dump wal file"); goto out_free_data; } err = false; out_free_data: if (data != NULL) { raft_free(data); } if (!err) req->cb(req, 0, DQLITE_RESPONSE_FILES); return 0; } static int encodeServer(struct gateway *g, unsigned i, struct buffer *buffer, int format) { void *cur; uint64_t id; uint64_t role; text_t address; id = g->raft->configuration.servers[i].id; address = g->raft->configuration.servers[i].address; role = (uint64_t)translateRaftRole(g->raft->configuration.servers[i].role); cur = buffer__advance(buffer, uint64__sizeof(&id)); if (cur == NULL) { return DQLITE_NOMEM; } uint64__encode(&id, &cur); cur = buffer__advance(buffer, text__sizeof(&address)); if (cur == NULL) { return DQLITE_NOMEM; } text__encode(&address, &cur); if (format == DQLITE_REQUEST_CLUSTER_FORMAT_V0) { return 0; } cur = buffer__advance(buffer, uint64__sizeof(&role)); if (cur == NULL) { return DQLITE_NOMEM; } uint64__encode(&role, &cur); return 0; } static int handle_cluster(struct handle *req, struct cursor *cursor) { tracef("handle cluster"); struct gateway *g = req->gateway; unsigned i; void *cur; int rv; START(cluster, servers); response.n = g->raft->configuration.n; cur = buffer__advance(req->buffer, response_servers__sizeof(&response)); assert(cur != NULL); response_servers__encode(&response, &cur); for (i = 0; i < response.n; i++) { rv = encodeServer(g, i, req->buffer, (int)request.format); if (rv != 0) { tracef("encode failed"); failure(req, rv, "failed to encode server"); return 0; } } req->cb(req, 0, DQLITE_RESPONSE_SERVERS); return 0; } void raftTransferCb(struct raft_transfer *r) { struct gateway *g = r->data; struct handle *req = g->req; struct response_empty response; g->req = NULL; sqlite3_free(r); if (g->raft->state == RAFT_LEADER) { tracef("transfer failed"); failure(req, DQLITE_ERROR, "leadership transfer failed"); } else { SUCCESS(empty, EMPTY); } } static int handle_transfer(struct handle *req, struct cursor *cursor) { tracef("handle transfer"); struct gateway *g = req->gateway; struct raft_transfer *r; int rv; START(transfer, empty); (void)response; CHECK_LEADER(req); r = sqlite3_malloc(sizeof *r); if (r == NULL) { tracef("malloc failed"); return DQLITE_NOMEM; } r->data = g; g->req = req; rv = raft_transfer(g->raft, r, request.id, raftTransferCb); if (rv != 0) { tracef("raft_transfer failed %d", rv); g->req = NULL; sqlite3_free(r); failure(req, translateRaftErrCode(rv), raft_strerror(rv)); return 0; } return 0; } static int handle_describe(struct handle *req, struct cursor *cursor) { tracef("handle describe"); struct gateway *g = req->gateway; START(describe, metadata); if (request.format != DQLITE_REQUEST_DESCRIBE_FORMAT_V0) { tracef("bad format"); failure(req, SQLITE_PROTOCOL, "bad format version"); } response.failure_domain = g->config->failure_domain; response.weight = g->config->weight; SUCCESS(metadata, METADATA); return 0; } static int handle_weight(struct handle *req, struct cursor *cursor) { tracef("handle weight"); struct gateway *g = req->gateway; START(weight, empty); g->config->weight = request.weight; SUCCESS(empty, EMPTY); return 0; } int gateway__handle(struct gateway *g, struct handle *req, int type, struct cursor *cursor, struct buffer *buffer, handle_cb cb) { tracef("gateway handle"); int rc = 0; /* Check if there is a request in progress. */ if (g->req != NULL && type != DQLITE_REQUEST_HEARTBEAT) { if (g->req->type == DQLITE_REQUEST_QUERY || g->req->type == DQLITE_REQUEST_QUERY_SQL) { /* TODO: handle interrupt requests */ assert(type == DQLITE_REQUEST_INTERRUPT); goto handle; } if (g->req->type == DQLITE_REQUEST_EXEC || g->req->type == DQLITE_REQUEST_EXEC_SQL) { tracef("gateway handle - BUSY"); return SQLITE_BUSY; } assert(0); } handle: req->type = type; req->gateway = g; req->cb = cb; req->buffer = buffer; switch (type) { #define DISPATCH(LOWER, UPPER, _) \ case DQLITE_REQUEST_##UPPER: \ rc = handle_##LOWER(req, cursor); \ break; REQUEST__TYPES(DISPATCH); } return rc; } int gateway__resume(struct gateway *g, bool *finished) { if (g->req == NULL || (g->req->type != DQLITE_REQUEST_QUERY && g->req->type != DQLITE_REQUEST_QUERY_SQL)) { tracef("gateway resume - finished"); *finished = true; return 0; } assert(g->stmt != NULL); tracef("gateway resume - not finished"); *finished = false; query_batch(g->stmt, g->req); return 0; } dqlite-1.9.1/src/gateway.h000066400000000000000000000050061416342771300154160ustar00rootroot00000000000000/** * Core dqlite server engine, calling out SQLite for serving client requests. */ #ifndef DQLITE_GATEWAY_H_ #define DQLITE_GATEWAY_H_ #include #include "../include/dqlite.h" #include "lib/buffer.h" #include "lib/serialize.h" #include "config.h" #include "leader.h" #include "registry.h" #include "stmt.h" struct handle; /** * Handle requests from a single connected client and forward them to * SQLite. */ struct gateway { struct config *config; /* Configuration */ struct registry *registry; /* Register of existing databases */ struct raft *raft; /* Raft instance */ struct leader *leader; /* Leader connection to the database */ struct handle *req; /* Asynchronous request being handled */ sqlite3_stmt *stmt; /* Statement being processed */ bool stmt_finalize; /* Whether to finalize the statement */ struct exec exec; /* Low-level exec async request */ const char *sql; /* SQL query for exec_sql requests */ struct stmt__registry stmts; /* Registry of prepared statements */ struct barrier barrier; /* Barrier for query requests */ uint64_t protocol; /* Protocol format version */ }; void gateway__init(struct gateway *g, struct config *config, struct registry *registry, struct raft *raft); void gateway__close(struct gateway *g); /** * Asynchronous request to handle a client command. */ typedef void (*handle_cb)(struct handle *req, int status, int type); struct handle { void *data; /* User data */ int type; /* Request type */ struct gateway *gateway; struct buffer *buffer; handle_cb cb; }; /** * Start handling a new client request. * * At most one request can be outstanding at any given time. This function will * return an error if user code calls it and there's already a request in * progress. * * The @type parameter holds the request type code (e.g. #REQUEST_LEADER), the * @cursor parameter holds a cursor for reading the request payload, and the * @buffer parameter is a buffer for writing the response. */ int gateway__handle(struct gateway *g, struct handle *req, int type, struct cursor *cursor, struct buffer *buffer, handle_cb cb); /** * Resume execution of a query that was yielding a lot of rows and has been * interrupted in order to start sending a first batch of rows. The response * write buffer associated with the request must have been reset. */ int gateway__resume(struct gateway *g, bool *finished); #endif /* DQLITE_GATEWAY_H_ */ dqlite-1.9.1/src/leader.c000066400000000000000000000271031416342771300152060ustar00rootroot00000000000000#include #include "../include/dqlite.h" #include "./lib/assert.h" #include "command.h" #include "leader.h" #include "tracing.h" #include "vfs.h" static void maybeExecDone(struct exec *req) { if (!req->done) { return; } req->leader->exec = NULL; if (req->cb != NULL) { req->cb(req, req->status); } } /* Open a SQLite connection and set it to leader replication mode. */ static int openConnection(const char *filename, const char *vfs, unsigned page_size, sqlite3 **conn) { tracef("open connection filename %s", filename); char pragma[255]; int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE; char *msg = NULL; int rc; rc = sqlite3_open_v2(filename, conn, flags, vfs); if (rc != SQLITE_OK) { tracef("open failed %d", rc); goto err; } /* Enable extended result codes */ rc = sqlite3_extended_result_codes(*conn, 1); if (rc != SQLITE_OK) { tracef("extended codes failed %d", rc); goto err_after_open; } /* Set the page size. */ sprintf(pragma, "PRAGMA page_size=%d", page_size); rc = sqlite3_exec(*conn, pragma, NULL, NULL, &msg); if (rc != SQLITE_OK) { tracef("page size set failed %d page size %u", rc, page_size); goto err_after_open; } /* Disable syncs. */ rc = sqlite3_exec(*conn, "PRAGMA synchronous=OFF", NULL, NULL, &msg); if (rc != SQLITE_OK) { tracef("sync off failed %d", rc); goto err_after_open; } /* Set WAL journaling. */ rc = sqlite3_exec(*conn, "PRAGMA journal_mode=WAL", NULL, NULL, &msg); if (rc != SQLITE_OK) { tracef("wal on failed %d", rc); goto err_after_open; } rc = sqlite3_exec(*conn, "PRAGMA wal_autocheckpoint=0", NULL, NULL, &msg); if (rc != SQLITE_OK) { tracef("wal autocheckpoint off failed %d", rc); goto err_after_open; } rc = sqlite3_db_config(*conn, SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE, 1, NULL); if (rc != SQLITE_OK) { tracef("db config failed %d", rc); goto err_after_open; } /* TODO: make setting foreign keys optional. */ rc = sqlite3_exec(*conn, "PRAGMA foreign_keys=1", NULL, NULL, &msg); if (rc != SQLITE_OK) { tracef("enable foreign keys failed %d", rc); goto err_after_open; } return 0; err_after_open: sqlite3_close(*conn); err: if (msg != NULL) { sqlite3_free(msg); } return rc; } /* Whether we need to submit a barrier request because there is no transaction * in progress in the underlying database and the FSM is behind the last log * index. */ static bool needsBarrier(struct leader *l) { return l->db->tx_id == 0 && raft_last_applied(l->raft) < raft_last_index(l->raft); } int leader__init(struct leader *l, struct db *db, struct raft *raft) { tracef("leader init"); int rc; l->db = db; l->raft = raft; rc = openConnection(db->filename, db->config->name, db->config->page_size, &l->conn); if (rc != 0) { tracef("open failed %d", rc); return rc; } l->exec = NULL; l->apply.data = l; l->inflight = NULL; QUEUE__PUSH(&db->leaders, &l->queue); return 0; } void leader__close(struct leader *l) { tracef("leader close"); int rc; /* TODO: there shouldn't be any ongoing exec request. */ if (l->exec != NULL) { assert(l->inflight == NULL); l->exec->done = true; l->exec->status = SQLITE_ERROR; maybeExecDone(l->exec); } rc = sqlite3_close(l->conn); assert(rc == 0); QUEUE__REMOVE(&l->queue); } static void leaderCheckpointApplyCb(struct raft_apply *req, int status, void *result) { struct leader *l = req->data; (void)result; /* In case of failure, release the chekcpoint lock. */ if (status != 0) { tracef("checkpoint apply failed %d", status); struct sqlite3_file *file; sqlite3_file_control(l->conn, "main", SQLITE_FCNTL_FILE_POINTER, &file); file->pMethods->xShmLock( file, 1 /* checkpoint lock */, 1, SQLITE_SHM_UNLOCK | SQLITE_SHM_EXCLUSIVE); } l->inflight = NULL; l->db->tx_id = 0; l->exec->done = true; maybeExecDone(l->exec); } /* Attempt to perform a checkpoint if possible. */ static bool leaderMaybeCheckpoint(struct leader *l) { tracef("leader maybe checkpoint"); struct sqlite3_file *main_f; struct sqlite3_file *wal; struct raft_buffer buf; struct command_checkpoint command; volatile void *region; sqlite3_int64 size; unsigned page_size = l->db->config->page_size; unsigned pages; int i; int rv; /* Get the database file associated with this connection */ rv = sqlite3_file_control(l->conn, "main", SQLITE_FCNTL_JOURNAL_POINTER, &wal); assert(rv == SQLITE_OK); /* Should never fail */ rv = wal->pMethods->xFileSize(wal, &size); assert(rv == SQLITE_OK); /* Should never fail */ /* Calculate the number of frames. */ pages = ((unsigned)size - 32) / (24 + page_size); /* Check if the size of the WAL is beyond the threshold. */ if (pages < l->db->config->checkpoint_threshold) { tracef("wal size (%u) < threshold (%u)", pages, l->db->config->checkpoint_threshold); return false; } /* Get the database file associated with this connection */ rv = sqlite3_file_control(l->conn, "main", SQLITE_FCNTL_FILE_POINTER, &main_f); assert(rv == SQLITE_OK); /* Should never fail */ /* Get the first SHM region, which contains the WAL header. */ rv = main_f->pMethods->xShmMap(main_f, 0, 0, 0, ®ion); assert(rv == SQLITE_OK); /* Should never fail */ rv = main_f->pMethods->xShmUnmap(main_f, 0); assert(rv == SQLITE_OK); /* Should never fail */ /* Try to acquire all locks. */ for (i = 0; i < SQLITE_SHM_NLOCK; i++) { int flags = SQLITE_SHM_LOCK | SQLITE_SHM_EXCLUSIVE; rv = main_f->pMethods->xShmLock(main_f, i, 1, flags); if (rv == SQLITE_BUSY) { /* There's a reader. Let's postpone the checkpoint * for now. */ tracef("busy reader"); return false; } /* Not locked. Let's release the lock we just * acquired. */ flags = SQLITE_SHM_UNLOCK | SQLITE_SHM_EXCLUSIVE; main_f->pMethods->xShmLock(main_f, i, 1, flags); } /* Attempt to perfom a checkpoint across all nodes. * * TODO: reason about if it's indeed fine to ignore all kind of * errors. */ command.filename = l->db->filename; rv = command__encode(COMMAND_CHECKPOINT, &command, &buf); if (rv != 0) { tracef("encode failed %d", rv); goto abort; } rv = raft_apply(l->raft, &l->apply, &buf, 1, leaderCheckpointApplyCb); if (rv != 0) { tracef("raft_apply failed %d", rv); goto abort_after_command_encode; } rv = main_f->pMethods->xShmLock(main_f, 1 /* checkpoint lock */, 1, SQLITE_SHM_LOCK | SQLITE_SHM_EXCLUSIVE); assert(rv == 0); return true; abort_after_command_encode: raft_free(buf.base); abort: assert(rv != 0); return false; } static void leaderApplyFramesCb(struct raft_apply *req, int status, void *result) { tracef("apply frames cb"); struct apply *apply = req->data; struct leader *l = apply->leader; if (l == NULL) { raft_free(apply); return; } (void)result; if (status != 0) { tracef("apply frames cb failed status %d", status); sqlite3_vfs *vfs = sqlite3_vfs_find(l->db->config->name); switch (status) { case RAFT_LEADERSHIPLOST: l->exec->status = SQLITE_IOERR_LEADERSHIP_LOST; break; case RAFT_NOSPACE: l->exec->status = SQLITE_IOERR_WRITE; break; case RAFT_SHUTDOWN: /* If we got here it means we have manually * fired the apply callback from * gateway__close(). In this case we don't * free() the apply object, since it will be * freed when the callback is fired again by * raft. * * TODO: we should instead make gatewa__close() * itself asynchronous. */ apply->leader = NULL; l->exec->status = SQLITE_ABORT; goto finish; break; default: l->exec->status = SQLITE_IOERR; break; } VfsAbort(vfs, l->db->filename); } raft_free(apply); if (status == 0 && leaderMaybeCheckpoint(l)) { /* Wait for the checkpoint to finish. */ return; } finish: l->inflight = NULL; l->db->tx_id = 0; l->exec->done = true; maybeExecDone(l->exec); } static int leaderApplyFrames(struct exec *req, dqlite_vfs_frame *frames, unsigned n) { struct leader *l = req->leader; struct db *db = l->db; struct command_frames c; struct raft_buffer buf; struct apply *apply; int rv; c.filename = db->filename; c.tx_id = 0; c.truncate = 0; c.is_commit = 1; c.frames.n_pages = (uint32_t)n; c.frames.page_size = (uint16_t)db->config->page_size; c.frames.data = frames; apply = raft_malloc(sizeof *req); if (apply == NULL) { tracef("malloc"); rv = DQLITE_NOMEM; goto err; } rv = command__encode(COMMAND_FRAMES, &c, &buf); if (rv != 0) { tracef("encode %d", rv); goto err_after_apply_alloc; } apply->leader = req->leader; apply->req.data = apply; apply->type = COMMAND_FRAMES; rv = raft_apply(l->raft, &apply->req, &buf, 1, leaderApplyFramesCb); if (rv != 0) { tracef("raft apply failed %d", rv); goto err_after_command_encode; } db->tx_id = 1; l->inflight = apply; return 0; err_after_command_encode: raft_free(buf.base); err_after_apply_alloc: raft_free(apply); err: assert(rv != 0); return rv; } static void leaderExecV2(struct exec *req) { struct leader *l = req->leader; struct db *db = l->db; sqlite3_vfs *vfs = sqlite3_vfs_find(db->config->name); dqlite_vfs_frame *frames; unsigned n; unsigned i; int rv; req->status = sqlite3_step(req->stmt); rv = VfsPoll(vfs, l->db->filename, &frames, &n); if (rv != 0 || n == 0) { tracef("vfs poll"); goto finish; } rv = leaderApplyFrames(req, frames, n); for (i = 0; i < n; i++) { sqlite3_free(frames[i].data); } sqlite3_free(frames); if (rv != 0) { VfsAbort(vfs, l->db->filename); goto finish; } return; finish: l->exec->done = true; if (rv != 0) { tracef("exec v2 failed %d", rv); l->exec->status = rv; } maybeExecDone(l->exec); } static void execBarrierCb(struct barrier *barrier, int status) { tracef("exec barrier cb status %d", status); struct exec *req = barrier->data; struct leader *l = req->leader; if (status != 0) { l->exec->done = true; l->exec->status = status; maybeExecDone(l->exec); return; } leaderExecV2(req); } int leader__exec(struct leader *l, struct exec *req, sqlite3_stmt *stmt, exec_cb cb) { tracef("leader exec"); int rv; if (l->exec != NULL) { tracef("busy"); return SQLITE_BUSY; } l->exec = req; req->leader = l; req->stmt = stmt; req->cb = cb; req->done = false; req->barrier.data = req; req->barrier.cb = NULL; rv = leader__barrier(l, &req->barrier, execBarrierCb); if (rv != 0) { return rv; } return 0; } static void raftBarrierCb(struct raft_barrier *req, int status) { tracef("raft barrier cb status %d", status); struct barrier *barrier = req->data; int rv = 0; if (status != 0) { if (status == RAFT_LEADERSHIPLOST) { rv = SQLITE_IOERR_LEADERSHIP_LOST; } else { rv = SQLITE_ERROR; } } barrier_cb cb = barrier->cb; if (cb == NULL) { tracef("barrier cb already fired"); return; } barrier->cb = NULL; cb(barrier, rv); } int leader__barrier(struct leader *l, struct barrier *barrier, barrier_cb cb) { tracef("leader barrier"); int rv; if (!needsBarrier(l)) { tracef("not needed"); cb(barrier, 0); return 0; } barrier->cb = cb; barrier->leader = l; barrier->req.data = barrier; rv = raft_barrier(l->raft, &barrier->req, raftBarrierCb); if (rv != 0) { tracef("raft barrier failed %d", rv); barrier->req.data = NULL; barrier->leader = NULL; barrier->cb = NULL; return rv; } return 0; } dqlite-1.9.1/src/leader.h000066400000000000000000000063751416342771300152230ustar00rootroot00000000000000/** * Track the state of leader connection and execute statements asynchronously. */ #ifndef LEADER_H_ #define LEADER_H_ #include #include #include #include "./lib/queue.h" #include "db.h" #define SQLITE_IOERR_NOT_LEADER (SQLITE_IOERR | (40<<8)) #define SQLITE_IOERR_LEADERSHIP_LOST (SQLITE_IOERR | (41<<8)) struct exec; struct barrier; struct leader; typedef void (*exec_cb)(struct exec *req, int status); typedef void (*barrier_cb)(struct barrier *req, int status); /* Wrapper around raft_apply, saving context information. */ struct apply { struct raft_apply req; /* Raft apply request */ int status; /* Raft apply result */ struct leader *leader; /* Leader connection that triggered the hook */ int type; /* Command type */ union { /* Command-specific data */ struct { bool is_commit; } frames; }; }; struct leader { struct db *db; /* Database the connection. */ sqlite3 *conn; /* Underlying SQLite connection. */ struct raft *raft; /* Raft instance. */ struct exec *exec; /* Exec request in progress, if any. */ struct raft_apply apply; /* To apply checkpoint commands */ queue queue; /* Prev/next leader, used by struct db. */ struct apply *inflight; /* TODO: make leader__close async */ }; struct barrier { void *data; struct leader *leader; struct raft_barrier req; barrier_cb cb; }; /** * Asynchronous request to execute a statement. */ struct exec { void *data; struct leader *leader; struct barrier barrier; sqlite3_stmt *stmt; bool done; int status; queue queue; exec_cb cb; }; /** * Initialize a new leader connection. * * This function will start the leader loop coroutine and pause it immediately, * transfering control back to main coroutine and then opening a new leader * connection against the given database. */ int leader__init(struct leader *l, struct db *db, struct raft *raft); void leader__close(struct leader *l); /** * Submit a request to step a SQLite statement. * * The request will be dispatched to the leader loop coroutine, which will be * resumed and will invoke sqlite_step(). If the statement triggers the * replication hooks and one or more new Raft log entries need to be appended, * then the loop coroutine will be paused and control will be transferred back * to the main coroutine. In this state the leader loop coroutine call stack * will be "blocked" on the xFrames() replication hook call triggered by the top * sqlite_step() call. The leader loop coroutine will be resumed once the Raft * append request completes (either successfully or not) and at that point the * stack will rewind back to the @sqlite_step() call, returning to the leader * loop which will then have completed the request and transfer control back to * the main coroutine, pausing until the next request. */ int leader__exec(struct leader *l, struct exec *req, sqlite3_stmt *stmt, exec_cb cb); /** * Submit a raft barrier request if there is no transaction in progress in the * underlying database and the FSM is behind the last log index. * * Otherwise, just invoke the given @cb immediately. */ int leader__barrier(struct leader *l, struct barrier *barrier, barrier_cb cb); #endif /* LEADER_H_*/ dqlite-1.9.1/src/lib/000077500000000000000000000000001416342771300143515ustar00rootroot00000000000000dqlite-1.9.1/src/lib/assert.h000066400000000000000000000004461416342771300160270ustar00rootroot00000000000000/** * Define the assert() macro, either as the standard one or the test one. */ #ifndef LIB_ASSERT_H_ #define LIB_ASSERT_H_ #if defined(DQLITE_TEST) #include "../../test/lib/munit.h" #define assert(expr) munit_assert(expr) #else #include #endif #endif /* LIB_ASSERT_H_ */ dqlite-1.9.1/src/lib/buffer.c000066400000000000000000000024271416342771300157730ustar00rootroot00000000000000#include #include #include #include "buffer.h" #include "../../include/dqlite.h" /* How large is the buffer currently */ #define SIZE(B) (B->n_pages * B->page_size) /* How many remaining bytes the buffer currently */ #define CAP(B) (SIZE(B) - B->offset) int buffer__init(struct buffer *b) { b->page_size = (unsigned)sysconf(_SC_PAGESIZE); b->n_pages = 1; b->data = malloc(SIZE(b)); if (b->data == NULL) { return DQLITE_NOMEM; } b->offset = 0; return 0; } void buffer__close(struct buffer *b) { free(b->data); } /* Ensure that the buffer as at least @size spare bytes */ static bool ensure(struct buffer *b, size_t size) { /* Double the buffer until we have enough capacity */ while (size > CAP(b)) { void *data; b->n_pages *= 2; data = realloc(b->data, SIZE(b)); if (data == NULL) { return false; } b->data = data; } return true; } void *buffer__advance(struct buffer *b, size_t size) { void *cursor; if (!ensure(b, size)) { return NULL; } cursor = buffer__cursor(b, b->offset); b->offset += size; return cursor; } size_t buffer__offset(struct buffer *b) { return b->offset; } void *buffer__cursor(struct buffer *b, size_t offset) { return b->data + offset; } void buffer__reset(struct buffer *b) { b->offset = 0; } dqlite-1.9.1/src/lib/buffer.h000066400000000000000000000025311416342771300157740ustar00rootroot00000000000000/** * A dynamic buffer which can grow as needed when writing to it. * * The buffer size is always a multiple of the OS virtual memory page size, so * resizing the buffer *should* not incur in memory being copied. * * See https://stackoverflow.com/questions/16765389 * * TODO: consider using mremap. */ #ifndef LIB_BUFFER_H_ #define LIB_BUFFER_H_ #include struct buffer { void *data; /* Allocated buffer */ unsigned page_size; /* Size of an OS page */ unsigned n_pages; /* Number of pages allocated */ size_t offset; /* Next byte to write in the buffer */ }; /** * Initialize the buffer. It will initially have 1 memory page. */ int buffer__init(struct buffer *b); /** * Release the memory of the buffer. */ void buffer__close(struct buffer *b); /** * Return a write cursor pointing to the next byte to write, ensuring that the * buffer has at least @size spare bytes. * * Return #NULL in case of out-of-memory errors. */ void *buffer__advance(struct buffer *b, size_t size); /** * Return the offset of next byte to write. */ size_t buffer__offset(struct buffer *b); /** * Return a write cursor pointing to the @offset'th byte of the buffer. */ void *buffer__cursor(struct buffer *b, size_t offset); /** * Reset the write offset of the buffer. */ void buffer__reset(struct buffer *b); #endif /* LIB_BUFFER_H_ */ dqlite-1.9.1/src/lib/byte.h000066400000000000000000000042221416342771300154650ustar00rootroot00000000000000#ifndef LIB_BYTE_H_ #define LIB_BYTE_H_ #include #include #include #if defined(__cplusplus) #define DQLITE_INLINE inline #else #define DQLITE_INLINE static inline #endif /* Flip a 16-bit number to network byte order (little endian) */ DQLITE_INLINE uint16_t byte__flip16(uint16_t v) { #if defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __LITTLE_ENDIAN__) return v; #elif defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __BIG_ENDIAN__) && \ defined(__GNUC__) && __GNUC__ >= 4 && __GNUC_MINOR__ >= 8 return __builtin_bswap16(v); #else union { uint16_t u; uint8_t v[2]; } s; s.v[0] = (uint8_t)v; s.v[1] = (uint8_t)(v >> 8); return s.u; #endif } /* Flip a 32-bit number to network byte order (little endian) */ DQLITE_INLINE uint32_t byte__flip32(uint32_t v) { #if defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __LITTLE_ENDIAN__) return v; #elif defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __BIG_ENDIAN__) && \ defined(__GNUC__) && __GNUC__ >= 4 && __GNUC_MINOR__ >= 8 return __builtin_bswap32(v); #else union { uint32_t u; uint8_t v[4]; } s; s.v[0] = (uint8_t)v; s.v[1] = (uint8_t)(v >> 8); s.v[2] = (uint8_t)(v >> 16); s.v[3] = (uint8_t)(v >> 24); return s.u; #endif } /* Flip a 64-bit number to network byte order (little endian) */ DQLITE_INLINE uint64_t byte__flip64(uint64_t v) { #if defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __LITTLE_ENDIAN__) return v; #elif defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __BIG_ENDIAN__) && \ defined(__GNUC__) && __GNUC__ >= 4 && __GNUC_MINOR__ >= 8 return __builtin_bswap64(v); #else union { uint64_t u; uint8_t v[8]; } s; s.v[0] = (uint8_t)v; s.v[1] = (uint8_t)(v >> 8); s.v[2] = (uint8_t)(v >> 16); s.v[3] = (uint8_t)(v >> 24); s.v[4] = (uint8_t)(v >> 32); s.v[5] = (uint8_t)(v >> 40); s.v[6] = (uint8_t)(v >> 48); s.v[7] = (uint8_t)(v >> 56); return s.u; #endif } /** * Add padding to size if it's not a multiple of 8. E.g. if 11 is passed, 16 is * returned. */ DQLITE_INLINE size_t byte__pad64(size_t size) { size_t rest = size % sizeof(uint64_t); if (rest != 0) { size += sizeof(uint64_t) - rest; } return size; } #endif /* LIB_BYTE_H_ */ dqlite-1.9.1/src/lib/queue.h000066400000000000000000000034171416342771300156530ustar00rootroot00000000000000#ifndef LIB_QUEUE_H_ #define LIB_QUEUE_H_ #include typedef void *queue[2]; /* Private macros. */ #define QUEUE__NEXT(q) (*(queue **)&((*(q))[0])) #define QUEUE__PREV(q) (*(queue **)&((*(q))[1])) #define QUEUE__PREV_NEXT(q) (QUEUE__NEXT(QUEUE__PREV(q))) #define QUEUE__NEXT_PREV(q) (QUEUE__PREV(QUEUE__NEXT(q))) /** * Initialize an empty queue. */ #define QUEUE__INIT(q) \ { \ QUEUE__NEXT(q) = (q); \ QUEUE__PREV(q) = (q); \ } /** * Return true if the queue has no element. */ #define QUEUE__IS_EMPTY(q) ((const queue *)(q) == (const queue *)QUEUE__NEXT(q)) /** * Insert an element at the back of a queue. */ #define QUEUE__PUSH(q, e) \ { \ QUEUE__NEXT(e) = (q); \ QUEUE__PREV(e) = QUEUE__PREV(q); \ QUEUE__PREV_NEXT(e) = (e); \ QUEUE__PREV(q) = (e); \ } /** * Remove the given element from the queue. Any element can be removed at any * time. */ #define QUEUE__REMOVE(e) \ { \ QUEUE__PREV_NEXT(e) = QUEUE__NEXT(e); \ QUEUE__NEXT_PREV(e) = QUEUE__PREV(e); \ } /** * Return the element at the front of the queue. */ #define QUEUE__HEAD(q) (QUEUE__NEXT(q)) /** * Return the element at the back of the queue. */ #define QUEUE__TAIL(q) (QUEUE__PREV(q)) /** * Iternate over the element of a queue. * * Mutating the queue while iterating results in undefined behavior. */ #define QUEUE__FOREACH(q, e) \ for ((q) = QUEUE__NEXT(e); (q) != (e); (q) = QUEUE__NEXT(q)) /** * Return the structure holding the given element. */ #define QUEUE__DATA(e, type, field) \ ((type *)(uintptr_t)((char *)(e)-offsetof(type, field))) #endif /* LIB_QUEUE_H_*/ dqlite-1.9.1/src/lib/registry.h000066400000000000000000000352221416342771300163760ustar00rootroot00000000000000#ifndef LIB_REGISTRY_H_ #define LIB_REGISTRY_H_ #include #include #include #include #include "../../include/dqlite.h" #include "assert.h" #define DQLITE_NOTFOUND 1002 /** * Define a type-safe registry able to allocate and lookup items of a given * type. * * The item TYPE is required to implement three methods: TYPE##_init, * TYPE##_close and TYPE##_hash. */ #define REGISTRY(NAME, TYPE) \ \ struct NAME { \ struct TYPE **buf; /* Array of registry item slots */ \ size_t len; /* Index of the highest used slot */ \ size_t cap; /* Total number of slots */ \ }; \ \ /* Initialize the registry. */ \ void NAME##_init(struct NAME *r); \ \ /* Close the registry. */ \ void NAME##_close(struct NAME *r); \ \ /* Add an item to the registry. \ * \ * Return a pointer to a newly allocated an initialized item. \ * The "id" field of the item will be set to a unique value \ * identifying the item in the registry. */ \ int NAME##_add(struct NAME *r, struct TYPE **item); \ \ /* Given its ID, retrieve an item previously added to the \ * registry. */ \ struct TYPE *NAME##_get(struct NAME *r, size_t id); \ \ /* Get the index of the first item matching the given hash key. Return \ * 0 on success and DQLITE_NOTFOUND otherwise. */ \ int NAME##_idx(struct NAME *r, const char *key, size_t *i); \ \ /* Delete a previously added item. */ \ int NAME##_del(struct NAME *r, struct TYPE *item) /** * Define the methods of a registry */ #define REGISTRY_METHODS(NAME, TYPE) \ void NAME##_init(struct NAME *r) { \ assert(r != NULL); \ \ r->buf = NULL; \ r->len = 0; \ r->cap = 0; \ } \ \ void NAME##_close(struct NAME *r) { \ size_t i; \ struct TYPE *item; \ \ assert(r != NULL); \ \ /* Loop through all items currently in the registry, \ * and close them. */ \ for (i = 0; i < r->len; i++) { \ item = *(r->buf + i); \ /* Some slots may have been deleted, so we need \ * to check if the slot is actually used. */ \ if (item != NULL) { \ TYPE##_close(item); \ sqlite3_free(item); \ } \ } \ \ if (r->buf != NULL) { \ sqlite3_free(r->buf); \ } \ } \ \ int NAME##_add(struct NAME *r, struct TYPE **item) { \ struct TYPE **buf; \ size_t cap; \ size_t i; \ \ assert(r != NULL); \ assert(item != NULL); \ \ /* Check if there is an unllocated slot. */ \ for (i = 0; i < r->len; i++) { \ if (*(r->buf + i) == NULL) { \ goto ok_slot; \ } \ } \ \ /* There are no unallocated slots. */ \ assert(i == r->len); \ \ /* If we are full, then double the capacity. */ \ if (r->len + 1 > r->cap) { \ cap = (r->cap == 0) ? 1 : r->cap * 2; \ buf = sqlite3_realloc(r->buf, (int)(cap * sizeof(*r->buf))); \ if (buf == NULL) { \ return DQLITE_NOMEM; \ } \ r->buf = buf; \ r->cap = cap; \ } \ r->len++; \ \ ok_slot: \ assert(i < r->len); \ \ /* Allocate and initialize the new item */ \ *item = sqlite3_malloc(sizeof **item); \ if (*item == NULL) \ return DQLITE_NOMEM; \ \ (*item)->id = i; \ \ TYPE##_init(*item); \ \ /* Save the item in its registry slot */ \ *(r->buf + i) = *item; \ \ return 0; \ } \ \ struct TYPE *NAME##_get(struct NAME *r, size_t id) { \ struct TYPE *item; \ size_t i = id; \ \ assert(r != NULL); \ \ if (i >= r->len) { \ return NULL; \ } \ \ item = *(r->buf + i); \ \ assert(item->id == id); \ \ return item; \ } \ \ int NAME##_idx(struct NAME *r, const char *key, size_t *i) { \ struct TYPE *item; \ \ assert(r != NULL); \ assert(key != NULL); \ assert(i != NULL); \ \ for (*i = 0; *i < r->len; (*i)++) { \ const char *hash; \ \ item = *(r->buf + *i); \ \ if (item == NULL) { \ continue; \ } \ \ hash = TYPE##_hash(item); \ \ if (hash != NULL && strcmp(hash, key) == 0) { \ return 0; \ } \ } \ \ return DQLITE_NOTFOUND; \ } \ \ int NAME##_del(struct NAME *r, struct TYPE *item) { \ struct TYPE **buf; \ size_t cap; \ size_t i = item->id; \ \ assert(r != NULL); \ \ if (i >= r->len) { \ return DQLITE_NOTFOUND; \ } \ \ /* Check that the item address actually matches the one \ * we have in the registry */ \ if (*(r->buf + i) != item) { \ return DQLITE_NOTFOUND; \ } \ \ TYPE##_close(item); \ sqlite3_free(item); \ \ *(r->buf + i) = NULL; \ \ /* If this was the last item in the registry buffer, \ * decrease the length. */ \ if (i == r->len - 1) { \ r->len--; \ } \ \ /* If the new length is less than half of the capacity, \ * try to shrink the registry. */ \ if (r->len < (r->cap / 2)) { \ cap = r->cap / 2; \ buf = sqlite3_realloc(r->buf, (int)(cap * sizeof *r->buf)); \ if (buf != NULL) { \ r->buf = buf; \ r->cap = cap; \ } \ } \ \ return 0; \ } #endif /* LIB_REGISTRY_H_ */ dqlite-1.9.1/src/lib/serialize.h000066400000000000000000000175771416342771300165320ustar00rootroot00000000000000#ifndef LIB_SERIALIZE_H_ #define LIB_SERIALIZE_H_ #include #include #include #include "../../include/dqlite.h" #include "assert.h" #include "byte.h" #define DQLITE_PARSE 1005 /** * The size in bytes of a single serialized word. */ #define SERIALIZE__WORD_SIZE 8 /* We rely on the size of double to be 64 bit, since that's what is sent over * the wire. * * See https://stackoverflow.com/questions/752309/ensuring-c-doubles-are-64-bits */ #ifndef __STDC_IEC_559__ #if __SIZEOF_DOUBLE__ != 8 #error "Requires IEEE 754 floating point!" #endif #endif #ifdef static_assert static_assert(sizeof(double) == sizeof(uint64_t), "Size of 'double' is not 64 bits"); #endif /** * Basic type aliases to used by macro-based processing. */ typedef const char *text_t; typedef double float_t; typedef uv_buf_t blob_t; /** * Cursor to progressively read a buffer. */ struct cursor { const void *p; /* Next byte to read */ size_t cap; /* Number of bytes left in the buffer */ }; /** * Define a serializable struct. * * NAME: Name of the structure which will be defined. * FIELDS: List of X-based macros defining the fields in the schema, in the form * of X(KIND, NAME, ##__VA_ARGS__). E.g. X(uint64, id, ##__VA_ARGS__). * * A new struct called NAME will be defined, along with sizeof, encode and * decode functions. */ #define SERIALIZE__DEFINE(NAME, FIELDS) \ SERIALIZE__DEFINE_STRUCT(NAME, FIELDS); \ SERIALIZE__DEFINE_METHODS(NAME, FIELDS) #define SERIALIZE__DEFINE_STRUCT(NAME, FIELDS) \ struct NAME \ { \ FIELDS(SERIALIZE__DEFINE_FIELD) \ } #define SERIALIZE__DEFINE_METHODS(NAME, FIELDS) \ size_t NAME##__sizeof(const struct NAME *p); \ void NAME##__encode(const struct NAME *p, void **cursor); \ int NAME##__decode(struct cursor *cursor, struct NAME *p) /* Define a single field in serializable struct. * * KIND: Type code (e.g. uint64, text, etc). * MEMBER: Field name. */ #define SERIALIZE__DEFINE_FIELD(KIND, MEMBER) KIND##_t MEMBER; /** * Implement the sizeof, encode and decode function of a serializable struct. */ #define SERIALIZE__IMPLEMENT(NAME, FIELDS) \ size_t NAME##__sizeof(const struct NAME *p) \ { \ size_t size = 0; \ FIELDS(SERIALIZE__SIZEOF_FIELD, p); \ return size; \ } \ void NAME##__encode(const struct NAME *p, void **cursor) \ { \ FIELDS(SERIALIZE__ENCODE_FIELD, p, cursor); \ } \ int NAME##__decode(struct cursor *cursor, struct NAME *p) \ { \ int rc; \ FIELDS(SERIALIZE__DECODE_FIELD, p, cursor); \ return 0; \ } #define SERIALIZE__SIZEOF_FIELD(KIND, MEMBER, P) \ size += KIND##__sizeof(&((P)->MEMBER)); #define SERIALIZE__ENCODE_FIELD(KIND, MEMBER, P, CURSOR) \ KIND##__encode(&((P)->MEMBER), CURSOR); #define SERIALIZE__DECODE_FIELD(KIND, MEMBER, P, CURSOR) \ rc = KIND##__decode(CURSOR, &((P)->MEMBER)); \ if (rc != 0) { \ return rc; \ } DQLITE_INLINE size_t uint8__sizeof(const uint8_t *value) { (void)value; return sizeof(uint8_t); } DQLITE_INLINE size_t uint16__sizeof(const uint16_t *value) { (void)value; return sizeof(uint16_t); } DQLITE_INLINE size_t uint32__sizeof(const uint32_t *value) { (void)value; return sizeof(uint32_t); } DQLITE_INLINE size_t uint64__sizeof(const uint64_t *value) { (void)value; return sizeof(uint64_t); } DQLITE_INLINE size_t int64__sizeof(const int64_t *value) { (void)value; return sizeof(int64_t); } DQLITE_INLINE size_t float__sizeof(const float_t *value) { (void)value; return sizeof(double); } DQLITE_INLINE size_t text__sizeof(const text_t *value) { return byte__pad64(strlen(*value) + 1); } DQLITE_INLINE size_t blob__sizeof(const blob_t *value) { return sizeof(uint64_t) /* length */ + byte__pad64(value->len) /* data */; } DQLITE_INLINE void uint8__encode(const uint8_t *value, void **cursor) { *(uint8_t *)(*cursor) = *value; *cursor += sizeof(uint8_t); } DQLITE_INLINE void uint16__encode(const uint16_t *value, void **cursor) { *(uint16_t *)(*cursor) = byte__flip16(*value); *cursor += sizeof(uint16_t); } DQLITE_INLINE void uint32__encode(const uint32_t *value, void **cursor) { *(uint32_t *)(*cursor) = byte__flip32(*value); *cursor += sizeof(uint32_t); } DQLITE_INLINE void uint64__encode(const uint64_t *value, void **cursor) { *(uint64_t *)(*cursor) = byte__flip64(*value); *cursor += sizeof(uint64_t); } DQLITE_INLINE void int64__encode(const int64_t *value, void **cursor) { *(int64_t *)(*cursor) = (int64_t)byte__flip64((uint64_t)*value); *cursor += sizeof(int64_t); } DQLITE_INLINE void float__encode(const float_t *value, void **cursor) { *(uint64_t *)(*cursor) = byte__flip64(*(uint64_t *)value); *cursor += sizeof(uint64_t); } DQLITE_INLINE void text__encode(const text_t *value, void **cursor) { size_t len = byte__pad64(strlen(*value) + 1); memset(*cursor, 0, len); strcpy(*cursor, *value); *cursor += len; } DQLITE_INLINE void blob__encode(const blob_t *value, void **cursor) { size_t len = byte__pad64(value->len); uint64_t value_len = value->len; uint64__encode(&value_len, cursor); memcpy(*cursor, value->base, value->len); *cursor += len; } DQLITE_INLINE int uint8__decode(struct cursor *cursor, uint8_t *value) { size_t n = sizeof(uint8_t); if (n > cursor->cap) { return DQLITE_PARSE; } *value = *(uint8_t *)cursor->p; cursor->p += n; cursor->cap -= n; return 0; } DQLITE_INLINE int uint16__decode(struct cursor *cursor, uint16_t *value) { size_t n = sizeof(uint16_t); if (n > cursor->cap) { return DQLITE_PARSE; } *value = byte__flip16(*(uint16_t *)cursor->p); cursor->p += n; cursor->cap -= n; return 0; } DQLITE_INLINE int uint32__decode(struct cursor *cursor, uint32_t *value) { size_t n = sizeof(uint32_t); if (n > cursor->cap) { return DQLITE_PARSE; } *value = byte__flip32(*(uint32_t *)cursor->p); cursor->p += n; cursor->cap -= n; return 0; } DQLITE_INLINE int uint64__decode(struct cursor *cursor, uint64_t *value) { size_t n = sizeof(uint64_t); if (n > cursor->cap) { return DQLITE_PARSE; } *value = byte__flip64(*(uint64_t *)cursor->p); cursor->p += n; cursor->cap -= n; return 0; } DQLITE_INLINE int int64__decode(struct cursor *cursor, int64_t *value) { size_t n = sizeof(int64_t); if (n > cursor->cap) { return DQLITE_PARSE; } *value = (int64_t)byte__flip64((uint64_t)*(int64_t *)cursor->p); cursor->p += n; cursor->cap -= n; return 0; } DQLITE_INLINE int float__decode(struct cursor *cursor, float_t *value) { size_t n = sizeof(double); if (n > cursor->cap) { return DQLITE_PARSE; } *(uint64_t *)value = byte__flip64(*(uint64_t *)cursor->p); cursor->p += n; cursor->cap -= n; return 0; } DQLITE_INLINE int text__decode(struct cursor *cursor, text_t *value) { /* Find the terminating null byte of the next string, if any. */ size_t len = strnlen(cursor->p, cursor->cap); size_t n; if (len == cursor->cap) { return DQLITE_PARSE; } *value = cursor->p; n = byte__pad64(strlen(*value) + 1); cursor->p += n; cursor->cap -= n; return 0; } DQLITE_INLINE int blob__decode(struct cursor *cursor, blob_t *value) { uint64_t len; size_t n; int rv; rv = uint64__decode(cursor, &len); if (rv != 0) { return rv; } n = byte__pad64((size_t)len); if (n > cursor->cap) { return DQLITE_PARSE; } value->base = (char *)cursor->p; value->len = (size_t)len; cursor->p += n; cursor->cap -= n; return 0; } #endif /* LIB_SERIALIZE_H_ */ dqlite-1.9.1/src/lib/transport.c000066400000000000000000000070041416342771300165520ustar00rootroot00000000000000#include #include "../../include/dqlite.h" #include "assert.h" #include "transport.h" /* Called to allocate a buffer for the next stream read. */ static void alloc_cb(uv_handle_t *stream, size_t suggested_size, uv_buf_t *buf) { struct transport *t; (void)suggested_size; t = stream->data; assert(t->read.base != NULL); assert(t->read.len > 0); *buf = t->read; } /* Invoke the read callback. */ static void read_done(struct transport *t, ssize_t status) { transport_read_cb cb; int rv; rv = uv_read_stop(t->stream); assert(rv == 0); cb = t->read_cb; assert(cb != NULL); t->read_cb = NULL; t->read.base = NULL; t->read.len = 0; cb(t, (int)status); } static void read_cb(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) { struct transport *t; (void)buf; t = stream->data; if (nread > 0) { size_t n = (size_t)nread; /* We shouldn't have read more data than the pending amount. */ assert(n <= t->read.len); /* Advance the read window */ t->read.base += n; t->read.len -= n; /* If there's more data to read in order to fill the current * read buffer, just return, we'll be invoked again. */ if (t->read.len > 0) { return; } /* Read completed, invoke the callback. */ read_done(t, 0); return; } assert(nread <= 0); if (nread == 0) { /* Empty read */ return; } assert(nread < 0); /* Failure. */ read_done(t, nread); } int transport__stream(struct uv_loop_s *loop, int fd, struct uv_stream_s **stream) { struct uv_pipe_s *pipe; struct uv_tcp_s *tcp; int rv; switch (uv_guess_handle(fd)) { case UV_TCP: tcp = raft_malloc(sizeof *tcp); if (tcp == NULL) { return DQLITE_NOMEM; } rv = uv_tcp_init(loop, tcp); assert(rv == 0); rv = uv_tcp_open(tcp, fd); if (rv != 0) { raft_free(tcp); return TRANSPORT__BADSOCKET; } *stream = (struct uv_stream_s *)tcp; break; case UV_NAMED_PIPE: pipe = raft_malloc(sizeof *pipe); if (pipe == NULL) { return DQLITE_NOMEM; } rv = uv_pipe_init(loop, pipe, 0); assert(rv == 0); rv = uv_pipe_open(pipe, fd); if (rv != 0) { raft_free(pipe); return TRANSPORT__BADSOCKET; } *stream = (struct uv_stream_s *)pipe; break; default: return TRANSPORT__BADSOCKET; }; return 0; } int transport__init(struct transport *t, struct uv_stream_s *stream) { t->stream = stream; t->stream->data = t; t->read.base = NULL; t->read.len = 0; t->write.data = t; t->read_cb = NULL; t->write_cb = NULL; t->close_cb = NULL; return 0; } static void close_cb(uv_handle_t *handle) { struct transport *t = handle->data; raft_free(t->stream); if (t->close_cb != NULL) { t->close_cb(t); } } void transport__close(struct transport *t, transport_close_cb cb) { assert(t->close_cb == NULL); t->close_cb = cb; uv_close((uv_handle_t *)t->stream, close_cb); } int transport__read(struct transport *t, uv_buf_t *buf, transport_read_cb cb) { int rv; assert(t->read.base == NULL); assert(t->read.len == 0); t->read = *buf; t->read_cb = cb; rv = uv_read_start(t->stream, alloc_cb, read_cb); if (rv != 0) { return DQLITE_ERROR; } return 0; } static void write_cb(uv_write_t *req, int status) { struct transport *t = req->data; transport_write_cb cb = t->write_cb; assert(cb != NULL); t->write_cb = NULL; cb(t, status); } int transport__write(struct transport *t, uv_buf_t *buf, transport_write_cb cb) { int rv; assert(t->write_cb == NULL); t->write_cb = cb; rv = uv_write(&t->write, t->stream, buf, 1, write_cb); if (rv != 0) { return rv; } return 0; } dqlite-1.9.1/src/lib/transport.h000066400000000000000000000031511416342771300165560ustar00rootroot00000000000000/** * Asynchronously read and write buffer from and to the network. */ #ifndef LIB_TRANSPORT_H_ #define LIB_TRANSPORT_H_ #include #define TRANSPORT__BADSOCKET 1000 /** * Callbacks. */ struct transport; typedef void (*transport_read_cb)(struct transport *t, int status); typedef void (*transport_write_cb)(struct transport *t, int status); typedef void (*transport_close_cb)(struct transport *t); /** * Light wrapper around a libuv stream handle, providing a more convenient way * to read a certain amount of bytes. */ struct transport { void *data; /* User defined */ struct uv_stream_s *stream; /* Data stream */ uv_buf_t read; /* Read buffer */ uv_write_t write; /* Write request */ transport_read_cb read_cb; /* Read callback */ transport_write_cb write_cb; /* Write callback */ transport_close_cb close_cb; /* Close callback */ }; /** * Initialize a transport of the appropriate type (TCP or PIPE) attached to the * given file descriptor. */ int transport__init(struct transport *t, struct uv_stream_s *stream); /** * Start closing by the transport. */ void transport__close(struct transport *t, transport_close_cb cb); /** * Read from the transport file descriptor until the given buffer is full. */ int transport__read(struct transport *t, uv_buf_t *buf, transport_read_cb cb); /** * Write the given buffer to the transport. */ int transport__write(struct transport *t, uv_buf_t *buf, transport_write_cb cb); /* Create an UV stream object from the given fd. */ int transport__stream(struct uv_loop_s *loop, int fd, struct uv_stream_s **stream); #endif /* LIB_TRANSPORT_H_ */ dqlite-1.9.1/src/logger.c000066400000000000000000000014451416342771300152320ustar00rootroot00000000000000#include #include #include "logger.h" #define EMIT_BUF_LEN 1024 void loggerDefaultEmit(void *data, int level, const char *fmt, va_list args) { char buf[EMIT_BUF_LEN]; char *cursor = buf; size_t n; (void)data; /* First, render the logging level. */ switch (level) { case DQLITE_DEBUG: sprintf(cursor, "[DEBUG]: "); break; case DQLITE_INFO: sprintf(cursor, "[INFO ]: "); break; case DQLITE_WARN: sprintf(cursor, "[WARN ]: "); break; case DQLITE_LOG_ERROR: sprintf(cursor, "[ERROR]: "); break; default: sprintf(cursor, "[ ]: "); break; }; cursor = buf + strlen(buf); /* Then render the message, possibly truncating it. */ n = EMIT_BUF_LEN - strlen(buf) - 1; vsnprintf(cursor, n, fmt, args); fprintf(stderr, "%s\n", buf); } dqlite-1.9.1/src/logger.h000066400000000000000000000015171416342771300152370ustar00rootroot00000000000000#ifndef LOGGER_H_ #define LOGGER_H_ #include #include "../include/dqlite.h" /* Log levels */ enum { DQLITE_DEBUG = 0, DQLITE_INFO, DQLITE_WARN, DQLITE_LOG_ERROR }; /* Function to emit log messages. */ typedef void (*dqlite_emit)(void *data, int level, const char *fmt, va_list args); struct logger { void *data; dqlite_emit emit; }; /* Default implementation of dqlite_emit, using stderr. */ void loggerDefaultEmit(void *data, int level, const char *fmt, va_list args); /* Emit a log message with a certain level. */ /* #define debugf(L, FORMAT, ...) \ */ /* logger__emit(L, DQLITE_DEBUG, FORMAT, ##__VA_ARGS__) */ #define debugf(C, FORMAT, ...) \ C->gateway.raft->io->emit(C->gateway.raft->io, RAFT_DEBUG, FORMAT, \ ##__VA_ARGS__) #endif /* LOGGER_H_ */ dqlite-1.9.1/src/message.c000066400000000000000000000000761416342771300153760ustar00rootroot00000000000000#include "message.h" SERIALIZE__IMPLEMENT(message, MESSAGE); dqlite-1.9.1/src/message.h000066400000000000000000000005571416342771300154070ustar00rootroot00000000000000#ifndef MESSAGE_H_ #define MESSAGE_H_ #include "lib/serialize.h" /** * Metadata about an incoming or outgoing RPC message. */ #define MESSAGE(X, ...) \ X(uint32, words, ##__VA_ARGS__) \ X(uint8, type, ##__VA_ARGS__) \ X(uint8, flags, ##__VA_ARGS__) \ X(uint16, extra, ##__VA_ARGS__) SERIALIZE__DEFINE(message, MESSAGE); #endif /* MESSAGE_H_x */ dqlite-1.9.1/src/metrics.c000066400000000000000000000002701416342771300154140ustar00rootroot00000000000000#include #include "./lib/assert.h" #include "metrics.h" void dqlite__metrics_init(struct dqlite__metrics *m) { assert(m != NULL); m->requests = 0; m->duration = 0; } dqlite-1.9.1/src/metrics.h000066400000000000000000000010031416342771300154140ustar00rootroot00000000000000/****************************************************************************** * * Collect various performance metrics. * *****************************************************************************/ #ifndef DQLITE_METRICS_H #define DQLITE_METRICS_H #include struct dqlite__metrics { uint64_t requests; /* Total number of requests served. */ uint64_t duration; /* Total time spent to server requests. */ }; void dqlite__metrics_init(struct dqlite__metrics *m); #endif /* DQLITE_METRICS_H */ dqlite-1.9.1/src/protocol.h000066400000000000000000000040021416342771300156110ustar00rootroot00000000000000#ifndef DQLITE_PROTOCOL_H_ #define DQLITE_PROTOCOL_H_ /* Special datatypes */ #define DQLITE_UNIXTIME 9 #define DQLITE_ISO8601 10 #define DQLITE_BOOLEAN 11 #define DQLITE_PROTO 1001 /* Protocol error */ /* Role codes */ #define DQLITE_VOTER 0 #define DQLITE_STANDBY 1 #define DQLITE_SPARE 2 /* Current protocol version */ #define DQLITE_PROTOCOL_VERSION 1 /* Legacly pre-1.0 version. */ #define DQLITE_PROTOCOL_VERSION_LEGACY 0x86104dd760433fe5 /* Special value indicating that a batch of rows is over, but there are more. */ #define DQLITE_RESPONSE_ROWS_PART 0xeeeeeeeeeeeeeeee /* Special value indicating that the result set is complete. */ #define DQLITE_RESPONSE_ROWS_DONE 0xffffffffffffffff /* Request types */ #define DQLITE_REQUEST_LEADER 0 #define DQLITE_REQUEST_CLIENT 1 #define DQLITE_REQUEST_HEARTBEAT 2 #define DQLITE_REQUEST_OPEN 3 #define DQLITE_REQUEST_PREPARE 4 #define DQLITE_REQUEST_EXEC 5 #define DQLITE_REQUEST_QUERY 6 #define DQLITE_REQUEST_FINALIZE 7 #define DQLITE_REQUEST_EXEC_SQL 8 #define DQLITE_REQUEST_QUERY_SQL 9 #define DQLITE_REQUEST_INTERRUPT 10 #define DQLITE_REQUEST_CONNECT 11 #define DQLITE_REQUEST_ADD 12 #define DQLITE_REQUEST_ASSIGN 13 #define DQLITE_REQUEST_REMOVE 14 #define DQLITE_REQUEST_DUMP 15 #define DQLITE_REQUEST_CLUSTER 16 #define DQLITE_REQUEST_TRANSFER 17 #define DQLITE_REQUEST_DESCRIBE 18 #define DQLITE_REQUEST_WEIGHT 19 #define DQLITE_REQUEST_CLUSTER_FORMAT_V0 0 /* ID and address */ #define DQLITE_REQUEST_CLUSTER_FORMAT_V1 1 /* ID, address and role */ #define DQLITE_REQUEST_DESCRIBE_FORMAT_V0 0 /* Failure domain and weight */ /* Response types */ #define DQLITE_RESPONSE_FAILURE 0 #define DQLITE_RESPONSE_SERVER 1 #define DQLITE_RESPONSE_SERVER_LEGACY 1 #define DQLITE_RESPONSE_WELCOME 2 #define DQLITE_RESPONSE_SERVERS 3 #define DQLITE_RESPONSE_DB 4 #define DQLITE_RESPONSE_STMT 5 #define DQLITE_RESPONSE_RESULT 6 #define DQLITE_RESPONSE_ROWS 7 #define DQLITE_RESPONSE_EMPTY 8 #define DQLITE_RESPONSE_FILES 9 #define DQLITE_RESPONSE_METADATA 10 #endif /* DQLITE_PROTOCOL_H_ */ dqlite-1.9.1/src/query.c000066400000000000000000000065021416342771300151170ustar00rootroot00000000000000#include "query.h" #include "tuple.h" /* Return the type code of the i'th column value. * * TODO: find a better way to handle time types. */ static int value_type(sqlite3_stmt *stmt, int i) { int type = sqlite3_column_type(stmt, i); const char *column_type_name = sqlite3_column_decltype(stmt, i); if (column_type_name != NULL) { if ((strcasecmp(column_type_name, "DATETIME") == 0) || (strcasecmp(column_type_name, "DATE") == 0) || (strcasecmp(column_type_name, "TIMESTAMP") == 0)) { if (type == SQLITE_INTEGER) { type = DQLITE_UNIXTIME; } else { assert(type == SQLITE_TEXT || type == SQLITE_NULL); type = DQLITE_ISO8601; } } else if (strcasecmp(column_type_name, "BOOLEAN") == 0) { assert(type == SQLITE_INTEGER || type == SQLITE_NULL); type = DQLITE_BOOLEAN; } } assert(type < 16); return type; } /* Append a single row to the message. */ static int encode_row(sqlite3_stmt *stmt, struct buffer *buffer, int n) { struct tuple_encoder encoder; int rc; int i; rc = tuple_encoder__init(&encoder, (unsigned)n, TUPLE__ROW, buffer); if (rc != 0) { return SQLITE_ERROR; } /* Encode the row values */ for (i = 0; i < n; i++) { /* Figure the type */ struct value value; value.type = value_type(stmt, i); switch (value.type) { case SQLITE_INTEGER: value.integer = sqlite3_column_int64(stmt, i); break; case SQLITE_FLOAT: value.float_ = sqlite3_column_double(stmt, i); break; case SQLITE_BLOB: value.blob.base = (char*)sqlite3_column_blob(stmt, i); value.blob.len = (size_t)sqlite3_column_bytes(stmt, i); break; case SQLITE_NULL: /* TODO: allow null to be encoded with 0 bytes */ value.null = 0; break; case SQLITE_TEXT: value.text = (text_t)sqlite3_column_text(stmt, i); break; case DQLITE_UNIXTIME: value.integer = sqlite3_column_int64(stmt, i); break; case DQLITE_ISO8601: value.text = (text_t)sqlite3_column_text(stmt, i); if (value.text == NULL) { value.text = ""; } break; case DQLITE_BOOLEAN: value.integer = sqlite3_column_int64(stmt, i); break; default: return SQLITE_ERROR; } rc = tuple_encoder__next(&encoder, &value); if (rc != 0) { return rc; } } return SQLITE_OK; } int query__batch(sqlite3_stmt *stmt, struct buffer *buffer) { int n; /* Column count */ int i; uint64_t n64; void *cursor; int rc; n = sqlite3_column_count(stmt); if (n <= 0) { return SQLITE_ERROR; } n64 = (uint64_t)n; /* Insert the column count */ cursor = buffer__advance(buffer, sizeof(uint64_t)); assert(cursor != NULL); uint64__encode(&n64, &cursor); /* Insert the column names */ for (i = 0; i < n; i++) { const char *name = sqlite3_column_name(stmt, i); cursor = buffer__advance(buffer, text__sizeof(&name)); if (cursor == NULL) { return SQLITE_NOMEM; } text__encode(&name, &cursor); } /* Insert the rows. */ do { if (buffer__offset(buffer) >= buffer->page_size) { /* If we are already filled a memory page, let's break * for now, we'll send more rows in a separate * response. */ rc = SQLITE_ROW; break; } rc = sqlite3_step(stmt); if (rc != SQLITE_ROW) { break; } rc = encode_row(stmt, buffer, n); if (rc != SQLITE_OK) { break; } } while (1); return rc; } dqlite-1.9.1/src/query.h000066400000000000000000000007051416342771300151230ustar00rootroot00000000000000/** * Step through a query progressively encoding a the row tuples. */ #ifndef QUERY_H_ #define QUERY_H_ #include #include "lib/serialize.h" #include "lib/buffer.h" /** * Step through the given query statement progressively encoding the yielded row * tuples, either until #SQLITE_DONE is returned or a full page of the given * buffer is filled. */ int query__batch(sqlite3_stmt *stmt, struct buffer *buffer); #endif /* QUERY_H_*/ dqlite-1.9.1/src/registry.c000066400000000000000000000015561416342771300156260ustar00rootroot00000000000000#include #include "../include/dqlite.h" #include "lib/assert.h" #include "registry.h" void registry__init(struct registry *r, struct config *config) { r->config = config; QUEUE__INIT(&r->dbs); } void registry__close(struct registry *r) { while (!QUEUE__IS_EMPTY(&r->dbs)) { struct db *db; queue *head; head = QUEUE__HEAD(&r->dbs); QUEUE__REMOVE(head); db = QUEUE__DATA(head, struct db, queue); db__close(db); sqlite3_free(db); } } int registry__db_get(struct registry *r, const char *filename, struct db **db) { queue *head; QUEUE__FOREACH(head, &r->dbs) { *db = QUEUE__DATA(head, struct db, queue); if (strcmp((*db)->filename, filename) == 0) { return 0; } } *db = sqlite3_malloc(sizeof **db); if (*db == NULL) { return DQLITE_NOMEM; } db__init(*db, r->config, filename); QUEUE__PUSH(&r->dbs, &(*db)->queue); return 0; } dqlite-1.9.1/src/registry.h000066400000000000000000000007401416342771300156250ustar00rootroot00000000000000#ifndef REGISTRY_H_ #define REGISTRY_H_ #include #include #include "lib/queue.h" #include "db.h" struct registry { struct config *config; queue dbs; }; void registry__init(struct registry *r, struct config *config); void registry__close(struct registry *r); /** * Get the db with the given filename. If no one is registered, create one. */ int registry__db_get(struct registry *r, const char *filename, struct db **db); #endif /* REGISTRY_H_*/ dqlite-1.9.1/src/request.c000066400000000000000000000003351416342771300154400ustar00rootroot00000000000000#include "request.h" #define REQUEST__IMPLEMENT(LOWER, UPPER, _) \ SERIALIZE__IMPLEMENT(request_##LOWER, REQUEST_##UPPER); REQUEST__TYPES(REQUEST__IMPLEMENT, ); SERIALIZE__IMPLEMENT(request_connect, REQUEST_CONNECT); dqlite-1.9.1/src/request.h000066400000000000000000000052211416342771300154440ustar00rootroot00000000000000#ifndef REQUEST_H_ #define REQUEST_H_ #include "lib/serialize.h" /** * Request types. */ #define REQUEST_LEADER(X, ...) X(uint64, __unused__, ##__VA_ARGS__) #define REQUEST_CLIENT(X, ...) X(uint64, id, ##__VA_ARGS__) #define REQUEST_OPEN(X, ...) \ X(text, filename, ##__VA_ARGS__) \ X(uint64, flags, ##__VA_ARGS__) \ X(text, vfs, ##__VA_ARGS__) #define REQUEST_PREPARE(X, ...) \ X(uint64, db_id, ##__VA_ARGS__) \ X(text, sql, ##__VA_ARGS__) #define REQUEST_EXEC(X, ...) \ X(uint32, db_id, ##__VA_ARGS__) \ X(uint32, stmt_id, ##__VA_ARGS__) #define REQUEST_QUERY(X, ...) \ X(uint32, db_id, ##__VA_ARGS__) \ X(uint32, stmt_id, ##__VA_ARGS__) #define REQUEST_FINALIZE(X, ...) \ X(uint32, db_id, ##__VA_ARGS__) \ X(uint32, stmt_id, ##__VA_ARGS__) #define REQUEST_EXEC_SQL(X, ...) \ X(uint64, db_id, ##__VA_ARGS__) \ X(text, sql, ##__VA_ARGS__) #define REQUEST_QUERY_SQL(X, ...) \ X(uint64, db_id, ##__VA_ARGS__) \ X(text, sql, ##__VA_ARGS__) #define REQUEST_INTERRUPT(X, ...) X(uint64, db_id, ##__VA_ARGS__) #define REQUEST_ADD(X, ...) \ X(uint64, id, ##__VA_ARGS__) \ X(text, address, ##__VA_ARGS__) #define REQUEST_ASSIGN(X, ...) X(uint64, id, ##__VA_ARGS__) #define REQUEST_REMOVE(X, ...) X(uint64, id, ##__VA_ARGS__) #define REQUEST_DUMP(X, ...) X(text, filename, ##__VA_ARGS__) #define REQUEST_CLUSTER(X, ...) X(uint64, format, ##__VA_ARGS__) #define REQUEST_TRANSFER(X, ...) X(uint64, id, ##__VA_ARGS__) #define REQUEST_DESCRIBE(X, ...) X(uint64, format, ##__VA_ARGS__) #define REQUEST_WEIGHT(X, ...) X(uint64, weight, ##__VA_ARGS__) #define REQUEST__DEFINE(LOWER, UPPER, _) \ SERIALIZE__DEFINE(request_##LOWER, REQUEST_##UPPER); #define REQUEST__TYPES(X, ...) \ X(leader, LEADER, __VA_ARGS__) \ X(client, CLIENT, __VA_ARGS__) \ X(open, OPEN, __VA_ARGS__) \ X(prepare, PREPARE, __VA_ARGS__) \ X(exec, EXEC, __VA_ARGS__) \ X(query, QUERY, __VA_ARGS__) \ X(finalize, FINALIZE, __VA_ARGS__) \ X(exec_sql, EXEC_SQL, __VA_ARGS__) \ X(query_sql, QUERY_SQL, __VA_ARGS__) \ X(interrupt, INTERRUPT, __VA_ARGS__) \ X(add, ADD, __VA_ARGS__) \ X(assign, ASSIGN, __VA_ARGS__) \ X(remove, REMOVE, __VA_ARGS__) \ X(dump, DUMP, __VA_ARGS__) \ X(cluster, CLUSTER, __VA_ARGS__) \ X(transfer, TRANSFER, __VA_ARGS__) \ X(describe, DESCRIBE, __VA_ARGS__) \ X(weight, WEIGHT, __VA_ARGS__) REQUEST__TYPES(REQUEST__DEFINE); #define REQUEST_CONNECT(X, ...) \ X(uint64, id, ##__VA_ARGS__) \ X(text, address, ##__VA_ARGS__) SERIALIZE__DEFINE(request_connect, REQUEST_CONNECT); #endif /* REQUEST_H_ */ dqlite-1.9.1/src/response.c000066400000000000000000000002521416342771300156040ustar00rootroot00000000000000#include "response.h" #define RESPONSE__IMPLEMENT(LOWER, UPPER, _) \ SERIALIZE__IMPLEMENT(response_##LOWER, RESPONSE_##UPPER); RESPONSE__TYPES(RESPONSE__IMPLEMENT, ); dqlite-1.9.1/src/response.h000066400000000000000000000037251416342771300156210ustar00rootroot00000000000000#ifndef RESPONSE_H_ #define RESPONSE_H_ #include "lib/serialize.h" /** * Response types. */ #define RESPONSE_SERVER(X, ...) \ X(uint64, id, ##__VA_ARGS__) \ X(text, address, ##__VA_ARGS__) #define RESPONSE_SERVER_LEGACY(X, ...) X(text, address, ##__VA_ARGS__) #define RESPONSE_WELCOME(X, ...) X(uint64, heartbeat_timeout, ##__VA_ARGS__) #define RESPONSE_FAILURE(X, ...) \ X(uint64, code, ##__VA_ARGS__) \ X(text, message, ##__VA_ARGS__) #define RESPONSE_DB(X, ...) \ X(uint32, id, ##__VA_ARGS__) \ X(uint32, __pad__, ##__VA_ARGS__) #define RESPONSE_STMT(X, ...) \ X(uint32, db_id, ##__VA_ARGS__) \ X(uint32, id, ##__VA_ARGS__) \ X(uint64, params, ##__VA_ARGS__) #define RESPONSE_RESULT(X, ...) \ X(uint64, last_insert_id, ##__VA_ARGS__) \ X(uint64, rows_affected, ##__VA_ARGS__) #define RESPONSE_ROWS(X, ...) X(uint64, eof, ##__VA_ARGS__) #define RESPONSE_EMPTY(X, ...) X(uint64, __unused__, ##__VA_ARGS__) #define RESPONSE_FILES(X, ...) X(uint64, n, ##__VA_ARGS__) #define RESPONSE_SERVERS(X, ...) X(uint64, n, ##__VA_ARGS__) #define RESPONSE_METADATA(X, ...) \ X(uint64, failure_domain, ##__VA_ARGS__) \ X(uint64, weight, ##__VA_ARGS__) #define RESPONSE__DEFINE(LOWER, UPPER, _) \ SERIALIZE__DEFINE(response_##LOWER, RESPONSE_##UPPER); #define RESPONSE__TYPES(X, ...) \ X(server, SERVER, __VA_ARGS__) \ X(server_legacy, SERVER_LEGACY, __VA_ARGS__) \ X(welcome, WELCOME, __VA_ARGS__) \ X(failure, FAILURE, __VA_ARGS__) \ X(db, DB, __VA_ARGS__) \ X(stmt, STMT, __VA_ARGS__) \ X(result, RESULT, __VA_ARGS__) \ X(rows, ROWS, __VA_ARGS__) \ X(empty, EMPTY, __VA_ARGS__) \ X(files, FILES, __VA_ARGS__) \ X(servers, SERVERS, __VA_ARGS__) \ X(metadata, METADATA, __VA_ARGS__) RESPONSE__TYPES(RESPONSE__DEFINE); #endif /* RESPONSE_H_ */ dqlite-1.9.1/src/server.c000066400000000000000000000472251416342771300152670ustar00rootroot00000000000000#include "server.h" #include #include #include #include "../include/dqlite.h" #include "conn.h" #include "fsm.h" #include "lib/assert.h" #include "logger.h" #include "protocol.h" #include "tracing.h" #include "translate.h" #include "transport.h" #include "utils.h" #include "vfs.h" /* Special ID for the bootstrap node. Equals to raft_digest("1", 0). */ #define BOOTSTRAP_ID 0x2dc171858c3155be int dqlite__init(struct dqlite_node *d, dqlite_node_id id, const char *address, const char *dir) { int rv; memset(d->errmsg, 0, sizeof d->errmsg); rv = config__init(&d->config, id, address); if (rv != 0) { goto err; } rv = VfsInit(&d->vfs, d->config.name); sqlite3_vfs_register(&d->vfs, 0); if (rv != 0) { goto err_after_config_init; } registry__init(&d->registry, &d->config); rv = uv_loop_init(&d->loop); if (rv != 0) { /* TODO: better error reporting */ rv = DQLITE_ERROR; goto err_after_vfs_init; } rv = raftProxyInit(&d->raft_transport, &d->loop); if (rv != 0) { goto err_after_loop_init; } rv = raft_uv_init(&d->raft_io, &d->loop, dir, &d->raft_transport); if (rv != 0) { /* TODO: better error reporting */ rv = DQLITE_ERROR; goto err_after_raft_transport_init; } rv = fsm__init(&d->raft_fsm, &d->config, &d->registry); if (rv != 0) { goto err_after_raft_io_init; } /* TODO: properly handle closing the dqlite server without running it */ rv = raft_init(&d->raft, &d->raft_io, &d->raft_fsm, d->config.id, d->config.address); if (rv != 0) { snprintf(d->errmsg, RAFT_ERRMSG_BUF_SIZE, "raft_init(): %s", raft_errmsg(&d->raft)); return rv; } /* TODO: expose these values through some API */ raft_set_election_timeout(&d->raft, 3000); raft_set_heartbeat_timeout(&d->raft, 500); raft_set_snapshot_threshold(&d->raft, 1024); raft_set_snapshot_trailing(&d->raft, 8192); raft_set_pre_vote(&d->raft, true); raft_set_max_catch_up_rounds(&d->raft, 100); raft_set_max_catch_up_round_duration(&d->raft, 50 * 1000); /* 50 secs */ #ifdef __APPLE__ d->ready = dispatch_semaphore_create(0); d->stopped = dispatch_semaphore_create(0); #else rv = sem_init(&d->ready, 0, 0); if (rv != 0) { /* TODO: better error reporting */ rv = DQLITE_ERROR; goto err_after_raft_fsm_init; } rv = sem_init(&d->stopped, 0, 0); if (rv != 0) { /* TODO: better error reporting */ rv = DQLITE_ERROR; goto err_after_ready_init; } #endif rv = pthread_mutex_init(&d->mutex, NULL); assert(rv == 0); /* Docs say that pthread_mutex_init can't fail */ QUEUE__INIT(&d->queue); QUEUE__INIT(&d->conns); d->raft_state = RAFT_UNAVAILABLE; d->running = false; d->listener = NULL; d->bind_address = NULL; return 0; err_after_ready_init: #ifdef __APPLE__ dispatch_release(d->ready); #else sem_destroy(&d->ready); #endif err_after_raft_fsm_init: fsm__close(&d->raft_fsm); err_after_raft_io_init: raft_uv_close(&d->raft_io); err_after_raft_transport_init: raftProxyClose(&d->raft_transport); err_after_loop_init: uv_loop_close(&d->loop); err_after_vfs_init: VfsClose(&d->vfs); err_after_config_init: config__close(&d->config); err: return rv; } void dqlite__close(struct dqlite_node *d) { int rv; raft_free(d->listener); rv = pthread_mutex_destroy(&d->mutex); /* This is a no-op on Linux . */ assert(rv == 0); #ifdef __APPLE__ dispatch_release(d->stopped); dispatch_release(d->ready); #else rv = sem_destroy(&d->stopped); assert(rv == 0); /* Fails only if sem object is not valid */ rv = sem_destroy(&d->ready); assert(rv == 0); /* Fails only if sem object is not valid */ #endif fsm__close(&d->raft_fsm); uv_loop_close(&d->loop); raftProxyClose(&d->raft_transport); registry__close(&d->registry); sqlite3_vfs_unregister(&d->vfs); VfsClose(&d->vfs); config__close(&d->config); if (d->bind_address != NULL) { sqlite3_free(d->bind_address); } } int dqlite_node_create(dqlite_node_id id, const char *address, const char *data_dir, dqlite_node **t) { int rv; *t = sqlite3_malloc(sizeof **t); if (*t == NULL) { return DQLITE_NOMEM; } rv = dqlite__init(*t, id, address, data_dir); if (rv != 0) { sqlite3_free(*t); *t = NULL; return rv; } return 0; } static int ipParse(const char *address, struct sockaddr_in *addr) { char buf[256]; char *host; char *port; char *colon = ":"; int rv; /* TODO: turn this poor man parsing into proper one */ strncpy(buf, address, sizeof(buf)-1); buf[sizeof(buf)-1] = '\0'; host = strtok(buf, colon); port = strtok(NULL, ":"); if (port == NULL) { port = "8080"; } rv = uv_ip4_addr(host, atoi(port), addr); if (rv != 0) { return RAFT_NOCONNECTION; } return 0; } int dqlite_node_set_bind_address(dqlite_node *t, const char *address) { struct sockaddr_un addr_un; struct sockaddr_in addr_in; struct sockaddr *addr; size_t len; int fd; int rv; int domain = address[0] == '@' ? AF_UNIX : AF_INET; if (t->running) { return DQLITE_MISUSE; } if (domain == AF_INET) { memset(&addr_in, 0, sizeof addr_in); rv = ipParse(address, &addr_in); if (rv != 0) { return DQLITE_MISUSE; } len = sizeof addr_in; addr = (struct sockaddr *)&addr_in; } else { memset(&addr_un, 0, sizeof addr_un); addr_un.sun_family = AF_UNIX; len = strlen(address); if (len == 1) { /* Auto bind */ len = 0; } else { size_t n = sizeof(addr_un.sun_path); #if defined(__linux__) /* Linux abstract socket requires \0 in sun_path[0]. * Copy at most n-2 bytes because we start writing at * byte 1 and want to leave room to '\0' terminate */ strncpy(addr_un.sun_path + 1, address + 1, n - 2); addr_un.sun_path[n-1] = '\0'; #else /* MacOS do not support abstract sockets */ strncpy(addr_un.sun_path, address + 1, n - 1); addr_un.sun_path[n-1] = '\0'; (void)unlink(addr_un.sun_path); #endif } len += sizeof(sa_family_t); addr = (struct sockaddr *)&addr_un; } fd = socket(domain, SOCK_STREAM, 0); if (fd == -1) { return DQLITE_ERROR; } rv = fcntl(fd, FD_CLOEXEC); if (rv != 0) { close(fd); return DQLITE_ERROR; } if (domain == AF_INET) { int reuse = 1; rv = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const char *)&reuse, sizeof(reuse)); if (rv != 0) { close(fd); return DQLITE_ERROR; } } rv = bind(fd, addr, (socklen_t)len); if (rv != 0) { close(fd); return DQLITE_ERROR; } rv = transport__stream(&t->loop, fd, &t->listener); if (rv != 0) { close(fd); return DQLITE_ERROR; } if (domain == AF_INET) { int sz = ((int)strlen(address)) + 1; /* Room for '\0' */ t->bind_address = sqlite3_malloc(sz); if (t->bind_address == NULL) { close(fd); return DQLITE_NOMEM; } strcpy(t->bind_address, address); } else { len = sizeof addr_un.sun_path; t->bind_address = sqlite3_malloc((int)len); if (t->bind_address == NULL) { close(fd); return DQLITE_NOMEM; } memset(t->bind_address, 0, len); rv = uv_pipe_getsockname((struct uv_pipe_s *)t->listener, t->bind_address, &len); if (rv != 0) { close(fd); sqlite3_free(t->bind_address); t->bind_address = NULL; return DQLITE_ERROR; } t->bind_address[0] = '@'; } return 0; } const char *dqlite_node_get_bind_address(dqlite_node *t) { return t->bind_address; } int dqlite_node_set_connect_func(dqlite_node *t, int (*f)(void *arg, const char *address, int *fd), void *arg) { if (t->running) { return DQLITE_MISUSE; } raftProxySetConnectFunc(&t->raft_transport, f, arg); return 0; } int dqlite_node_set_network_latency(dqlite_node *t, unsigned long long nanoseconds) { unsigned milliseconds; if (t->running) { return DQLITE_MISUSE; } /* 1 hour latency should be more than sufficient, also avoids overflow * issues when converting to unsigned milliseconds later on */ if (nanoseconds > 3600000000000ULL) { return DQLITE_MISUSE; } milliseconds = (unsigned)(nanoseconds / (1000000ULL)); return dqlite_node_set_network_latency_ms(t, milliseconds); } int dqlite_node_set_network_latency_ms(dqlite_node *t, unsigned milliseconds) { if (t->running) { return DQLITE_MISUSE; } /* Currently we accept at least 1 millisecond latency and maximum 3600 s * of latency */ if (milliseconds == 0 || milliseconds > 3600U * 1000U) { return DQLITE_MISUSE; } raft_set_heartbeat_timeout(&t->raft, (milliseconds * 15) / 10); raft_set_election_timeout(&t->raft, milliseconds * 15); return 0; } int dqlite_node_set_failure_domain(dqlite_node *n, unsigned long long code) { n->config.failure_domain = code; return 0; } int dqlite_node_set_snapshot_params(dqlite_node *n, unsigned snapshot_threshold, unsigned snapshot_trailing) { if (n->running) { return DQLITE_MISUSE; } if (snapshot_trailing < 1024) { return DQLITE_MISUSE; } /* This is a safety precaution and allows to recover data from the second * last raft snapshot and segment files in case the last raft snapshot is * unusable. */ if (snapshot_trailing < snapshot_threshold) { return DQLITE_MISUSE; } raft_set_snapshot_threshold(&n->raft, snapshot_threshold); raft_set_snapshot_trailing(&n->raft, snapshot_trailing); return 0; } static int maybeBootstrap(dqlite_node *d, dqlite_node_id id, const char *address) { struct raft_configuration configuration; int rv; if (id != 1 && id != BOOTSTRAP_ID) { return 0; } raft_configuration_init(&configuration); rv = raft_configuration_add(&configuration, id, address, true); if (rv != 0) { assert(rv == RAFT_NOMEM); rv = DQLITE_NOMEM; goto out; }; rv = raft_bootstrap(&d->raft, &configuration); if (rv != 0) { if (rv == RAFT_CANTBOOTSTRAP) { rv = 0; } else { snprintf(d->errmsg, RAFT_ERRMSG_BUF_SIZE, "raft_bootstrap(): %s", raft_errmsg(&d->raft)); rv = DQLITE_ERROR; } goto out; } out: raft_configuration_close(&configuration); return rv; } /* Callback invoked when the stop async handle gets fired. * * This callback will walk through all active handles and close them. After the * last handle (which must be the 'stop' async handle) is closed, the loop gets * stopped. */ static void raftCloseCb(struct raft *raft) { struct dqlite_node *s = raft->data; raft_uv_close(&s->raft_io); uv_close((struct uv_handle_s *)&s->stop, NULL); uv_close((struct uv_handle_s *)&s->startup, NULL); uv_close((struct uv_handle_s *)&s->monitor, NULL); uv_close((struct uv_handle_s *)s->listener, NULL); } static void destroy_conn(struct conn *conn) { QUEUE__REMOVE(&conn->queue); sqlite3_free(conn); } static void stop_cb(uv_async_t *stop) { struct dqlite_node *d = stop->data; queue *head; struct conn *conn; /* We expect that we're being executed after dqlite__stop and so the * running flag is off. */ assert(!d->running); QUEUE__FOREACH(head, &d->conns) { conn = QUEUE__DATA(head, struct conn, queue); conn__stop(conn); } raft_close(&d->raft, raftCloseCb); } /* Callback invoked as soon as the loop as started. * * It unblocks the s->ready semaphore. */ static void startup_cb(uv_timer_t *startup) { struct dqlite_node *d = startup->data; int rv; d->running = true; #ifdef __APPLE__ dispatch_semaphore_signal(d->ready); #else rv = sem_post(&d->ready); assert(rv == 0); /* No reason for which posting should fail */ #endif } static void listenCb(uv_stream_t *listener, int status) { struct dqlite_node *t = listener->data; struct uv_stream_s *stream; struct conn *conn; int rv; if (status != 0) { /* TODO: log the error. */ return; } switch (listener->type) { case UV_TCP: stream = raft_malloc(sizeof(struct uv_tcp_s)); if (stream == NULL) { return; } rv = uv_tcp_init(&t->loop, (struct uv_tcp_s *)stream); assert(rv == 0); break; case UV_NAMED_PIPE: stream = raft_malloc(sizeof(struct uv_pipe_s)); if (stream == NULL) { return; } rv = uv_pipe_init(&t->loop, (struct uv_pipe_s *)stream, 0); assert(rv == 0); break; default: assert(0); } rv = uv_accept(listener, stream); if (rv != 0) { goto err; } /* We accept unix socket connections only from the same process. */ if (listener->type == UV_NAMED_PIPE) { int fd = stream->io_watcher.fd; #if defined(SO_PEERCRED) // Linux struct ucred cred; socklen_t len = sizeof(cred); rv = getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cred, &len); if (rv != 0) { goto err; } if (cred.pid != getpid()) { goto err; } #elif defined(LOCAL_PEERPID) // BSD pid_t pid = -1; socklen_t len = sizeof(pid); rv = getsockopt(fd, SOL_LOCAL, LOCAL_PEERPID, &pid, &len); if (rv != 0) { goto err; } if (pid != getpid()) { goto err; } #else // The unix socket connection can't be verified and from // security perspective it's better to block it entirely goto err; #endif } conn = sqlite3_malloc(sizeof *conn); if (conn == NULL) { goto err; } rv = conn__start(conn, &t->config, &t->loop, &t->registry, &t->raft, stream, &t->raft_transport, destroy_conn); if (rv != 0) { goto err_after_conn_alloc; } QUEUE__PUSH(&t->conns, &conn->queue); return; err_after_conn_alloc: sqlite3_free(conn); err: uv_close((struct uv_handle_s *)stream, (uv_close_cb)raft_free); } static void monitor_cb(uv_prepare_t *monitor) { struct dqlite_node *d = monitor->data; int state = raft_state(&d->raft); /* queue *head; struct conn *conn; */ if (state == RAFT_UNAVAILABLE) { return; } /* TODO: we should shutdown clients that are performing SQL requests, * but not the ones which are doing management-requests, such as * transfer leadership. */ /* if (d->raft_state == RAFT_LEADER && state != RAFT_LEADER) { QUEUE__FOREACH(head, &d->conns) { conn = QUEUE__DATA(head, struct conn, queue); conn__stop(conn); } } */ d->raft_state = state; } static int taskRun(struct dqlite_node *d) { int rv; /* TODO: implement proper cleanup upon error by spinning the loop a few * times. */ assert(d->listener != NULL); rv = uv_listen(d->listener, 128, listenCb); if (rv != 0) { return rv; } d->listener->data = d; /* Initialize notification handles. */ d->stop.data = d; rv = uv_async_init(&d->loop, &d->stop, stop_cb); assert(rv == 0); /* Schedule startup_cb to be fired as soon as the loop starts. It will * unblock clients of taskReady. */ d->startup.data = d; rv = uv_timer_init(&d->loop, &d->startup); assert(rv == 0); rv = uv_timer_start(&d->startup, startup_cb, 0, 0); assert(rv == 0); /* Schedule raft state change monitor. */ d->monitor.data = d; rv = uv_prepare_init(&d->loop, &d->monitor); assert(rv == 0); rv = uv_prepare_start(&d->monitor, monitor_cb); assert(rv == 0); d->raft.data = d; rv = raft_start(&d->raft); if (rv != 0) { snprintf(d->errmsg, RAFT_ERRMSG_BUF_SIZE, "raft_start(): %s", raft_errmsg(&d->raft)); /* Unblock any client of taskReady */ #ifdef __APPLE__ dispatch_semaphore_signal(d->ready); #else sem_post(&d->ready); #endif return rv; } rv = uv_run(&d->loop, UV_RUN_DEFAULT); assert(rv == 0); /* Unblock any client of taskReady */ #ifdef __APPLE__ dispatch_semaphore_signal(d->ready); #else rv = sem_post(&d->ready); assert(rv == 0); /* no reason for which posting should fail */ #endif return 0; } const char *dqlite_node_errmsg(dqlite_node *n) { return n->errmsg; } static void *taskStart(void *arg) { struct dqlite_node *t = arg; int rv; rv = taskRun(t); if (rv != 0) { uintptr_t result = (uintptr_t)rv; return (void *)result; } return NULL; } void dqlite_node_destroy(dqlite_node *d) { dqlite__close(d); sqlite3_free(d); } /* Wait until a dqlite server is ready and can handle connections. ** ** Returns true if the server has been successfully started, false otherwise. ** ** This is a thread-safe API, but must be invoked before any call to ** dqlite_stop or dqlite_handle. */ static bool taskReady(struct dqlite_node *d) { /* Wait for the ready semaphore */ #ifdef __APPLE__ dispatch_semaphore_wait(d->ready, DISPATCH_TIME_FOREVER); #else sem_wait(&d->ready); #endif return d->running; } int dqlite_node_start(dqlite_node *t) { int rv; dqliteTracingMaybeEnable(true); tracef("dqlite node start"); rv = maybeBootstrap(t, t->config.id, t->config.address); if (rv != 0) { tracef("bootstrap failed %d", rv); goto err; } rv = pthread_create(&t->thread, 0, &taskStart, t); if (rv != 0) { tracef("pthread create failed %d", rv); goto err; } if (!taskReady(t)) { tracef("!taskReady"); rv = DQLITE_ERROR; goto err; } return 0; err: return rv; } int dqlite_node_stop(dqlite_node *d) { tracef("dqlite node stop"); void *result; int rv; /* Grab the queue mutex, so we can be sure no new incoming request will * be enqueued from this point on. */ pthread_mutex_lock(&d->mutex); /* Turn off the running flag, so calls to dqlite_handle will fail * with DQLITE_STOPPED. This needs to happen before we send the stop * signal since the stop callback expects to see that the flag is * off. */ d->running = false; rv = uv_async_send(&d->stop); assert(rv == 0); pthread_mutex_unlock(&d->mutex); rv = pthread_join(d->thread, &result); assert(rv == 0); return (int)((uintptr_t)result); } int dqlite_node_recover(dqlite_node *n, struct dqlite_node_info infos[], int n_info) { tracef("dqlite node recover"); int i; int ret; struct dqlite_node_info_ext *infos_ext = calloc((size_t)n_info, sizeof(*infos_ext)); if (infos_ext == NULL) { return DQLITE_NOMEM; } for (i = 0; i < n_info; i++) { infos_ext[i].size = sizeof(*infos_ext); infos_ext[i].id = infos[i].id; infos_ext[i].address = PTR_TO_UINT64(infos[i].address); infos_ext[i].dqlite_role = DQLITE_VOTER; } ret = dqlite_node_recover_ext(n, infos_ext, n_info); free(infos_ext); return ret; } static bool node_info_valid(struct dqlite_node_info_ext *info) { /* Reject any size smaller than the original definition of the extensible * struct. */ if (info->size < DQLITE_NODE_INFO_EXT_SZ_ORIG) { return false; } /* Require 8 byte allignment */ if (info->size % sizeof(uint64_t)) { return false; } /* If the user uses a newer, and larger version of the struct, make sure the unknown * fields are zeroed out. */ uint64_t known_size = sizeof(struct dqlite_node_info_ext); if (info->size > known_size) { const uint64_t num_known_fields = known_size / sizeof(uint64_t); const uint64_t num_extra_fields = (info->size - known_size) / sizeof(uint64_t); const uint64_t *extra_fields = ((const uint64_t *)info) + num_known_fields; for (uint64_t i = 0; i < num_extra_fields; i++) { if (extra_fields[i] != (uint64_t)0) { return false; } } } return true; } int dqlite_node_recover_ext(dqlite_node *n, struct dqlite_node_info_ext infos[], int n_info) { tracef("dqlite node recover ext"); struct raft_configuration configuration; int i; int rv; raft_configuration_init(&configuration); for (i = 0; i < n_info; i++) { struct dqlite_node_info_ext *info = &infos[i]; if (!node_info_valid(info)) { rv = DQLITE_MISUSE; goto out; } int raft_role = translateDqliteRole((int)info->dqlite_role); const char *address = UINT64_TO_PTR(info->address, const char *); rv = raft_configuration_add(&configuration, info->id, address, raft_role); if (rv != 0) { assert(rv == RAFT_NOMEM); rv = DQLITE_NOMEM; goto out; }; } rv = raft_recover(&n->raft, &configuration); if (rv != 0) { rv = DQLITE_ERROR; goto out; } out: raft_configuration_close(&configuration); return rv; } dqlite_node_id dqlite_generate_node_id(const char *address) { tracef("generate node id"); struct timespec ts; int rv; unsigned long long n; rv = clock_gettime(CLOCK_REALTIME, &ts); assert(rv == 0); n = (unsigned long long)(ts.tv_sec * 1000 * 1000 * 1000 + ts.tv_nsec); return raft_digest(address, n); } dqlite-1.9.1/src/server.h000066400000000000000000000040651416342771300152670ustar00rootroot00000000000000#include #include #include #ifdef __APPLE__ #include #else #include #endif #include "config.h" #include "lib/assert.h" #include "logger.h" #include "registry.h" /** * A single dqlite server instance. */ struct dqlite_node { pthread_t thread; /* Main run loop thread. */ struct config config; /* Config values */ struct sqlite3_vfs vfs; /* In-memory VFS */ struct registry registry; /* Databases */ struct uv_loop_s loop; /* UV loop */ struct raft_uv_transport raft_transport; /* Raft libuv transport */ struct raft_io raft_io; /* libuv I/O */ struct raft_fsm raft_fsm; /* dqlite FSM */ #ifdef __APPLE__ dispatch_semaphore_t ready; /* Server is ready */ dispatch_semaphore_t stopped; /* Notifiy loop stopped */ #else sem_t ready; /* Server is ready */ sem_t stopped; /* Notifiy loop stopped */ #endif pthread_mutex_t mutex; /* Access incoming queue */ queue queue; /* Incoming connections */ queue conns; /* Active connections */ bool running; /* Loop is running */ struct raft raft; /* Raft instance */ struct uv_stream_s *listener; /* Listening socket */ struct uv_async_s stop; /* Trigger UV loop stop */ struct uv_timer_s startup; /* Unblock ready sem */ struct uv_prepare_s monitor; /* Raft state change monitor */ int raft_state; /* Previous raft state */ char *bind_address; /* Listen address */ char errmsg[RAFT_ERRMSG_BUF_SIZE]; /* Last error occurred */ }; int dqlite__init(struct dqlite_node *d, dqlite_node_id id, const char *address, const char *dir); void dqlite__close(struct dqlite_node *d); int dqlite__run(struct dqlite_node *d); dqlite-1.9.1/src/stmt.c000066400000000000000000000011571416342771300147420ustar00rootroot00000000000000#include #include "./lib/assert.h" #include "./tuple.h" #include "stmt.h" /* The maximum number of columns we expect (for bindings or rows) is 255, which * can fit in one byte. */ #define STMT__MAX_COLUMNS (1 << 8) - 1 void stmt__init(struct stmt *s) { s->stmt = NULL; } void stmt__close(struct stmt *s) { if (s->stmt != NULL) { /* Ignore the return code, since it will be non-zero in case the * most rececent evaluation of the statement failed. */ sqlite3_finalize(s->stmt); } } const char *stmt__hash(struct stmt *stmt) { (void)stmt; return NULL; } REGISTRY_METHODS(stmt__registry, stmt); dqlite-1.9.1/src/stmt.h000066400000000000000000000055611416342771300147520ustar00rootroot00000000000000/****************************************************************************** * * APIs to decode parameters and bind them to SQLite statement, and to fetch * rows and encode them. * * The dqlite wire format for a list of parameters to be bound to a statement * is divided in header and body. The format of the header is: * * 8 bits: Number of parameters to bind (min is 1, max is 255). * 4 bits: Type code of the 1st parameter to bind. * 4 bits: Type code of the 2nd parameter to bind, or 0. * 4 bits: Type code of the 3rn parameter to bind, or 0. * ... * * This repeats until reaching a full 64-bit word. If there are more than 14 * parameters, the header will grow additional 64-bit words as needed, following * the same pattern: a sequence of 4-bit slots with type codes of the parameters * to bind, followed by a sequence of zero bits, until word boundary is reached. * * After the parameters header follows the parameters body, which contain one * value for each parameter to bind, following the normal encoding rules. * * The dqlite wire format for a set of query rows is divided in header and * body. The format of the header is: * * 64 bits: Number of columns in the result set (min is 1). * 64 bits: Name of the first column. If the name is longer, additional words * of 64 bits can be used, like for normal string encoding. * ... If present, name of the 2nd, 3rd, ..., nth column. * * After the result set header follows the result set body, which is a sequence * of zero or more rows. Each row has the following format: * * 4 bits: Type code of the 1st column of the row. * 4 bits: Type code of the 2nd column of row, or 0. * 4 bits: Type code of the 2nd column of row, or 0. * * This repeats until reaching a full 64-bit word. If there are more than 16 row * columns, the header will grow additional 64-bit words as needed, following * the same pattern. After this row preamble, the values of all columns of the * row follow, using the normal dqlite enconding conventions. * *****************************************************************************/ #ifndef DQLITE_STMT_H #define DQLITE_STMT_H #include #include "lib/registry.h" /* Hold state for a single open SQLite database */ struct stmt { size_t id; /* Statement ID */ sqlite3_stmt *stmt; /* Underlying SQLite statement handle */ }; /* Initialize a statement state object */ void stmt__init(struct stmt *s); /* Close a statement state object, releasing all associated resources. */ void stmt__close(struct stmt *s); /* No-op hash function (hashing is not supported for stmt). This is * required by the registry interface. */ const char *stmt__hash(struct stmt *stmt); /* TODO: change registry naming pattern */ #define stmt_init stmt__init #define stmt_close stmt__close #define stmt_hash stmt__hash REGISTRY(stmt__registry, stmt); #endif /* DQLITE_STMT_H */ dqlite-1.9.1/src/tracing.c000066400000000000000000000004231416342771300153750ustar00rootroot00000000000000#include "tracing.h" #include #define LIBDQLITE_TRACE "LIBDQLITE_TRACE" bool _dqliteTracingEnabled = false; void dqliteTracingMaybeEnable(bool enable) { if (getenv(LIBDQLITE_TRACE) != NULL) { _dqliteTracingEnabled = enable; } } dqlite-1.9.1/src/tracing.h000066400000000000000000000030071416342771300154030ustar00rootroot00000000000000/* Tracing functionality for dqlite */ #ifndef DQLITE_TRACING_H_ #define DQLITE_TRACING_H_ #include #include #include #include /* This global variable is only written once at startup and is only read * from there on. Users should not manipulate the value of this variable. */ extern bool _dqliteTracingEnabled; #define tracef(...) do { \ if (_dqliteTracingEnabled) { \ static char _msg[1024]; \ snprintf(_msg, sizeof(_msg), __VA_ARGS__); \ struct timespec ts = {0}; \ /* Ignore errors */ \ clock_gettime(CLOCK_REALTIME, &ts); \ int64_t ns = ts.tv_sec * 1000000000 + ts.tv_nsec; \ fprintf(stderr, "LIBDQLITE %" PRId64 " %s:%d %s\n", ns, __func__, __LINE__, _msg); \ } \ } while(0) \ /* Enable tracing if the appropriate env variable is set, or disable tracing. */ void dqliteTracingMaybeEnable(bool enabled); #endif /* DQLITE_TRACING_H_ */ dqlite-1.9.1/src/translate.c000066400000000000000000000020501416342771300157410ustar00rootroot00000000000000#include "translate.h" #include #include "assert.h" #include "leader.h" #include "protocol.h" /* Translate a raft error to a dqlite one. */ int translateRaftErrCode(int code) { switch (code) { case RAFT_NOTLEADER: return SQLITE_IOERR_NOT_LEADER; case RAFT_LEADERSHIPLOST: return SQLITE_IOERR_LEADERSHIP_LOST; case RAFT_CANTCHANGE: return SQLITE_BUSY; default: return SQLITE_ERROR; } } /* Translate a dqlite role code to its raft equivalent. */ int translateDqliteRole(int role) { switch (role) { case DQLITE_VOTER: return RAFT_VOTER; case DQLITE_STANDBY: return RAFT_STANDBY; case DQLITE_SPARE: return RAFT_SPARE; default: /* For backward compat with clients that don't set a * role. */ return DQLITE_VOTER; } } /* Translate a raft role code to its dqlite equivalent. */ int translateRaftRole(int role) { switch (role) { case RAFT_VOTER: return DQLITE_VOTER; case RAFT_STANDBY: return DQLITE_STANDBY; case RAFT_SPARE: return DQLITE_SPARE; default: assert(0); return -1; } } dqlite-1.9.1/src/translate.h000066400000000000000000000006171416342771300157550ustar00rootroot00000000000000/* Translate to/from dqlite types */ #ifndef DQLITE_TRANSLATE_H_ #define DQLITE_TRANSLATE_H_ /* Translate a raft error to a dqlite one. */ int translateRaftErrCode(int code); /* Translate a dqlite role code to its raft equivalent. */ int translateDqliteRole(int role); /* Translate a raft role code to its dqlite equivalent. */ int translateRaftRole(int role); #endif /* DQLITE_TRANSLATE_H_ */ dqlite-1.9.1/src/transport.c000066400000000000000000000144351416342771300160120ustar00rootroot00000000000000#include "lib/transport.h" #include #include #include #include #include #include "client.h" #include "message.h" #include "request.h" #include "tracing.h" #include "transport.h" struct impl { struct uv_loop_s *loop; struct { int (*f)(void *arg, const char *address, int *fd); void *arg; } connect; raft_id id; const char *address; raft_uv_accept_cb accept_cb; }; struct connect { struct impl *impl; struct raft_uv_connect *req; struct uv_work_s work; raft_id id; const char *address; int fd; int status; }; static int impl_init(struct raft_uv_transport *transport, raft_id id, const char *address) { tracef("impl init"); struct impl *i = transport->impl; i->id = id; i->address = address; return 0; } static int impl_listen(struct raft_uv_transport *transport, raft_uv_accept_cb cb) { tracef("impl listen"); struct impl *i = transport->impl; i->accept_cb = cb; return 0; } static void connect_work_cb(uv_work_t *work) { tracef("connect work cb"); struct connect *r = work->data; struct impl *i = r->impl; struct message message; struct request_connect request; uint64_t protocol; void *buf; void *cursor; size_t n; size_t n1; size_t n2; int rv; /* Establish a connection to the other node using the provided connect * function. */ rv = i->connect.f(i->connect.arg, r->address, &r->fd); if (rv != 0) { tracef("connect failed to %llu@%s", r->id, r->address); rv = RAFT_NOCONNECTION; goto err; } /* Send the initial dqlite protocol handshake. */ protocol = byte__flip64(DQLITE_PROTOCOL_VERSION); rv = (int)write(r->fd, &protocol, sizeof protocol); if (rv != sizeof protocol) { tracef("write failed"); rv = RAFT_NOCONNECTION; goto err_after_connect; } /* Send a CONNECT dqlite protocol command, which will transfer control * to the underlying raft UV backend. */ request.id = i->id; request.address = i->address; n1 = message__sizeof(&message); n2 = request_connect__sizeof(&request); message.type = DQLITE_REQUEST_CONNECT; message.words = (uint32_t)(n2 / 8); n = n1 + n2; buf = sqlite3_malloc64(n); if (buf == NULL) { tracef("malloc failed"); rv = RAFT_NOCONNECTION; goto err_after_connect; } cursor = buf; message__encode(&message, &cursor); request_connect__encode(&request, &cursor); rv = (int)write(r->fd, buf, n); sqlite3_free(buf); if (rv != (int)n) { tracef("write failed"); rv = RAFT_NOCONNECTION; goto err_after_connect; } r->status = 0; return; err_after_connect: close(r->fd); err: r->status = rv; return; } static void connect_after_work_cb(uv_work_t *work, int status) { tracef("connect after work cb status %d", status); struct connect *r = work->data; struct impl *i = r->impl; struct uv_stream_s *stream = NULL; int rv; assert(status == 0); if (r->status != 0) { goto out; } rv = transport__stream(i->loop, r->fd, &stream); if (rv != 0) { tracef("transport stream failed %d", rv); r->status = RAFT_NOCONNECTION; close(r->fd); goto out; } out: r->req->cb(r->req, stream, r->status); sqlite3_free(r); } static int impl_connect(struct raft_uv_transport *transport, struct raft_uv_connect *req, raft_id id, const char *address, raft_uv_connect_cb cb) { tracef("impl connect id:%llu address:%s", id, address); struct impl *i = transport->impl; struct connect *r; int rv; r = sqlite3_malloc(sizeof *r); if (r == NULL) { tracef("malloc failed"); rv = DQLITE_NOMEM; goto err; } r->impl = i; r->req = req; r->work.data = r; r->id = id; r->address = address; req->cb = cb; rv = uv_queue_work(i->loop, &r->work, connect_work_cb, connect_after_work_cb); if (rv != 0) { tracef("queue work failed"); rv = RAFT_NOCONNECTION; goto err_after_connect_alloc; } return 0; err_after_connect_alloc: sqlite3_free(r); err: return rv; } static void impl_close(struct raft_uv_transport *transport, raft_uv_transport_close_cb cb) { tracef("impl close"); cb(transport); } static int parse_address(const char *address, struct sockaddr_in *addr) { char buf[256]; char *host; char *port; char *colon = ":"; int rv; /* TODO: turn this poor man parsing into proper one */ strncpy(buf, address, sizeof(buf)-1); buf[sizeof(buf)-1] = '\0'; host = strtok(buf, colon); port = strtok(NULL, ":"); if (port == NULL) { port = "8080"; } rv = uv_ip4_addr(host, atoi(port), addr); if (rv != 0) { return RAFT_NOCONNECTION; } return 0; } static int default_connect(void *arg, const char *address, int *fd) { struct sockaddr_in addr; int rv; (void)arg; rv = parse_address(address, &addr); if (rv != 0) { return RAFT_NOCONNECTION; } *fd = socket(AF_INET, SOCK_STREAM, 0); if (*fd == -1) { return RAFT_NOCONNECTION; } rv = connect(*fd, (const struct sockaddr *)&addr, sizeof addr); if (rv == -1) { close(*fd); return RAFT_NOCONNECTION; } return 0; } int raftProxyInit(struct raft_uv_transport *transport, struct uv_loop_s *loop) { tracef("raft proxy init"); struct impl *i = sqlite3_malloc(sizeof *i); if (i == NULL) { return DQLITE_NOMEM; } i->loop = loop; i->connect.f = default_connect; i->connect.arg = NULL; i->accept_cb = NULL; transport->impl = i; transport->init = impl_init; transport->listen = impl_listen; transport->connect = impl_connect; transport->close = impl_close; return 0; } void raftProxyClose(struct raft_uv_transport *transport) { tracef("raft proxy close"); struct impl *i = transport->impl; sqlite3_free(i); } void raftProxyAccept(struct raft_uv_transport *transport, raft_id id, const char *address, struct uv_stream_s *stream) { tracef("raft proxy accept"); struct impl *i = transport->impl; /* If the accept callback is NULL it means we were stopped. */ if (i->accept_cb == NULL) { tracef("raft proxy accept closed"); uv_close((struct uv_handle_s *)stream, (uv_close_cb)raft_free); } else { i->accept_cb(transport, id, address, stream); } } void raftProxySetConnectFunc(struct raft_uv_transport *transport, int (*f)(void *arg, const char *address, int *fd), void *arg) { struct impl *i = transport->impl; i->connect.f = f; i->connect.arg = arg; } dqlite-1.9.1/src/transport.h000066400000000000000000000013541416342771300160130ustar00rootroot00000000000000/* Implementation of the raft_uv_transport interface, proxied by a dqlite * connection. */ #ifndef TRANSPORT_H_ #define TRANSPORT_H_ #include #include "../include/dqlite.h" int raftProxyInit(struct raft_uv_transport *transport, struct uv_loop_s *loop); void raftProxyClose(struct raft_uv_transport *transport); /* Invoke the accept callback configured on the transport object. */ void raftProxyAccept(struct raft_uv_transport *transport, raft_id id, const char *address, struct uv_stream_s *stream); /* Set a custom connect function. */ void raftProxySetConnectFunc(struct raft_uv_transport *transport, int (*f)(void *arg, const char *address, int *fd), void *arg); #endif /* TRANSPORT_H_*/ dqlite-1.9.1/src/tuple.c000066400000000000000000000142071416342771300151040ustar00rootroot00000000000000#include #include "tuple.h" #include "assert.h" /* True if a tuple decoder or decoder is using parameter format. */ #define HAS_PARAMS_FORMAT(P) (P->format == TUPLE__PARAMS) /* True if a tuple decoder or decoder is using row format. */ #define HAS_ROW_FORMAT(P) (P->format == TUPLE__ROW) /* Return the tuple header size in bytes, for a tuple of @n values. * * If the tuple is a row, then each slot is 4 bits, otherwise if the tuple is a * sequence of parameters each slot is 8 bits. */ static size_t calc_header_size(unsigned n, int format) { size_t size; if (format == TUPLE__ROW) { size = (n / 2) * sizeof(uint8_t); if (n % 2 != 0) { size += sizeof(uint8_t); } size = byte__pad64(size); } else { assert(format == TUPLE__PARAMS); /* Include params count for the purpose of calculating possible * padding, but then exclude it as we have already read it. */ size = sizeof(uint8_t) + n * sizeof(uint8_t); size = byte__pad64(size); size -= sizeof(uint8_t); } return size; } int tuple_decoder__init(struct tuple_decoder *d, unsigned n, struct cursor *cursor) { size_t header_size; int rc; d->format = n == 0 ? TUPLE__PARAMS : TUPLE__ROW; /* When using row format the number of values is the given one, * otherwise we have to read it from the header. */ if (HAS_ROW_FORMAT(d)) { d->n = n; } else { uint8_t byte; rc = uint8__decode(cursor, &byte); if (rc != 0) { return rc; } d->n = byte; } d->i = 0; d->header = cursor->p; /* Check that there is enough room to hold n type code slots. */ header_size = calc_header_size(d->n, d->format); if (header_size > cursor->cap) { return DQLITE_PARSE; } d->cursor = cursor; d->cursor->p += header_size; d->cursor->cap -= header_size; return 0; } /* Return the number of values in the decoder's tuple. */ unsigned tuple_decoder__n(struct tuple_decoder *d) { return d->n; } /* Return the type of the i'th value of the tuple. */ static int get_type(struct tuple_decoder *d, unsigned i) { int type; /* In row format the type slot size is 4 bits, while in params format * the slot is 8 bits. */ if (HAS_ROW_FORMAT(d)) { type = d->header[i / 2]; if (i % 2 == 0) { type &= 0x0f; } else { type = type >> 4; } } else { type = d->header[i]; } return type; } int tuple_decoder__next(struct tuple_decoder *d, struct value *value) { int rc; assert(d->i < d->n); value->type = get_type(d, d->i); switch (value->type) { case SQLITE_INTEGER: rc = int64__decode(d->cursor, &value->integer); break; case SQLITE_FLOAT: rc = float__decode(d->cursor, &value->float_); break; case SQLITE_BLOB: rc = blob__decode(d->cursor, &value->blob); break; case SQLITE_NULL: /* TODO: allow null to be encoded with 0 bytes? */ rc = uint64__decode(d->cursor, &value->null); break; case SQLITE_TEXT: rc = text__decode(d->cursor, &value->text); break; case DQLITE_ISO8601: rc = text__decode(d->cursor, &value->iso8601); break; case DQLITE_BOOLEAN: rc = uint64__decode(d->cursor, &value->boolean); break; default: rc = DQLITE_PARSE; break; }; if (rc != 0) { return rc; } d->i++; return 0; } /* Return a pointer to the tuple header. */ static uint8_t *encoder__header(struct tuple_encoder *e) { return buffer__cursor(e->buffer, e->header); } int tuple_encoder__init(struct tuple_encoder *e, unsigned n, int format, struct buffer *buffer) { void *cursor; size_t n_header; e->n = n; e->format = format; e->buffer = buffer; e->i = 0; /* With params format we need to fill the first byte of the header with * the params count. */ if (HAS_PARAMS_FORMAT(e)) { uint8_t *header = buffer__advance(buffer, 1); if (header == NULL) { return DQLITE_NOMEM; } header[0] = (uint8_t)n; } e->header = buffer__offset(buffer); /* Reset the header */ n_header = calc_header_size(n, format); memset(encoder__header(e), 0, n_header); /* Advance the buffer write pointer past the tuple header. */ cursor = buffer__advance(buffer, n_header); if (cursor == NULL) { return DQLITE_NOMEM; } return 0; } /* Set the type of the i'th value of the tuple. */ static void set_type(struct tuple_encoder *e, unsigned i, int type) { uint8_t *header = encoder__header(e); /* In row format the type slot size is 4 bits, while in params format * the slot is 8 bits. */ if (HAS_ROW_FORMAT(e)) { uint8_t *slot; slot = &header[i / 2]; if (i % 2 == 0) { *slot = (uint8_t)type; } else { *slot |= (uint8_t)(type << 4); } } else { header[i] = (uint8_t)type; } } int tuple_encoder__next(struct tuple_encoder *e, struct value *value) { void *cursor; size_t size; assert(e->i < e->n); set_type(e, e->i, value->type); switch (value->type) { case SQLITE_INTEGER: size = int64__sizeof(&value->integer); break; case SQLITE_FLOAT: size = float__sizeof(&value->float_); break; case SQLITE_BLOB: size = blob__sizeof(&value->blob); break; case SQLITE_NULL: /* TODO: allow null to be encoded with 0 bytes */ size = uint64__sizeof(&value->null); break; case SQLITE_TEXT: size = text__sizeof(&value->text); break; case DQLITE_UNIXTIME: size = int64__sizeof(&value->unixtime); break; case DQLITE_ISO8601: size = text__sizeof(&value->iso8601); break; case DQLITE_BOOLEAN: size = uint64__sizeof(&value->boolean); break; default: assert(0); }; /* Advance the buffer write pointer. */ cursor = buffer__advance(e->buffer, size); if (cursor == NULL) { return DQLITE_NOMEM; } switch (value->type) { case SQLITE_INTEGER: int64__encode(&value->integer, &cursor); break; case SQLITE_FLOAT: float__encode(&value->float_, &cursor); break; case SQLITE_BLOB: blob__encode(&value->blob, &cursor); break; case SQLITE_NULL: /* TODO: allow null to be encoded with 0 bytes */ uint64__encode(&value->null, &cursor); break; case SQLITE_TEXT: text__encode(&value->text, &cursor); break; case DQLITE_UNIXTIME: int64__encode(&value->unixtime, &cursor); break; case DQLITE_ISO8601: text__encode(&value->iso8601, &cursor); break; case DQLITE_BOOLEAN: uint64__encode(&value->boolean, &cursor); break; }; e->i++; return 0; } dqlite-1.9.1/src/tuple.h000066400000000000000000000075631416342771300151200ustar00rootroot00000000000000/** * Encode and decode tuples of database values. * * A tuple is composed by a header and a body. * * The format of the header changes depending on whether the tuple is a sequence * of parameters to bind to a statement, or a sequence of values of a single row * yielded by a query. * * For a tuple of parameters the format of the header is: * * 8 bits: Number of values in the tuple. * 8 bits: Type code of the 1st value of the tuple. * 8 bits: Type code of the 2nd value of the tuple, or 0. * 8 bits: Type code of the 3rd value of the tuple, or 0. * ... * * This repeats until reaching a full 64-bit word. If there are more than 7 * parameters to bind, the header will grow additional 64-bit words as needed, * following the same pattern: a sequence of 8-bit slots with type codes of the * parameters followed by a sequence of zero bits, until word boundary is * reached. * * For a tuple of row values the format of the header is: * * 4 bits: Type code of the 1st value of the tuple. * 4 bits: Type code of the 2nd value of the tuple, or 0. * 4 bits: Type code of the 3rd value of the tuple, or 0. * ... * * This repeats until reaching a full 64-bit word. If there are more than 16 * values, the header will grow additional 64-bit words as needed, following the * same pattern: a sequence of 4-bit slots with type codes of the values * followed by a sequence of zero bits, until word boundary is reached. * * After the header the body follows immediately, which contains all parameters * or values in sequence, encoded using type-specific rules. */ #ifndef DQLITE_TUPLE_H_ #define DQLITE_TUPLE_H_ #include #include #include #include "lib/buffer.h" #include "lib/serialize.h" #include "protocol.h" enum { TUPLE__ROW = 1, TUPLE__PARAMS }; /** * Hold a single database value. */ struct value { int type; union { int64_t integer; double float_; uv_buf_t blob; uint64_t null; const char *text; const char *iso8601; /* INT8601 date string */ int64_t unixtime; /* Unix time in seconds since epoch */ uint64_t boolean; }; }; /** * Maintain state while decoding a single tuple. */ struct tuple_decoder { unsigned n; /* Number of values in the tuple */ struct cursor *cursor; /* Reading cursor */ int format; /* Tuple format (row or params) */ unsigned i; /* Index of next value to decode */ const uint8_t *header; /* Pointer to tuple header */ }; /** * Initialize the state of the decoder, before starting to decode a new * tuple. * * If @n is zero, it means that the tuple is a sequence of statement * parameters. In that case the d->n field will be read from the first byte of * @cursor. */ int tuple_decoder__init(struct tuple_decoder *d, unsigned n, struct cursor *cursor); /** * Return the number of values in the tuple being decoded. * * In row format this will be the same @n passed to the constructor. In * parameters format this is the value contained in the first byte of the tuple * header. */ unsigned tuple_decoder__n(struct tuple_decoder *d); /** * Decode the next value of the tuple. */ int tuple_decoder__next(struct tuple_decoder *d, struct value *value); /** * Maintain state while encoding a single tuple. */ struct tuple_encoder { unsigned n; /* Number of values in the tuple */ int format; /* Tuple format (row or params) */ struct buffer *buffer; /* Write buffer */ unsigned i; /* Index of next value to encode */ size_t header; /* Buffer offset of tuple header */ }; /** * Initialize the state of the encoder, before starting to encode a new * tuple. The @n parameter must always be greater than zero. */ int tuple_encoder__init(struct tuple_encoder *e, unsigned n, int format, struct buffer *buffer); /** * Encode the next value of the tuple. */ int tuple_encoder__next(struct tuple_encoder *e, struct value *value); #endif /* DQLITE_TUPLE_H_ */ dqlite-1.9.1/src/utils.h000066400000000000000000000004061416342771300151140ustar00rootroot00000000000000#ifndef DQLITE_UTILS_H_ #define DQLITE_UTILS_H_ #include /* Various utility functions and macros */ #define PTR_TO_UINT64(p) ((uint64_t)((uintptr_t)(p))) #define UINT64_TO_PTR(u, ptr_type) ((ptr_type)((uintptr_t)(u))) #endif /* DQLITE_UTILS_H_ */ dqlite-1.9.1/src/vfs.c000066400000000000000000002007041416342771300145500ustar00rootroot00000000000000#include #include #include #include #include #include #include #include "../include/dqlite.h" #include "lib/assert.h" #include "format.h" #include "tracing.h" #include "vfs.h" /* Byte order */ #if defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __LITTLE_ENDIAN__) #define VFS__BIGENDIAN 0 #elif defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __BIG_ENDIAN__) #define VFS__BIGENDIAN 1 #else const int vfsOne = 1; #define VFS__BIGENDIAN (*(char *)(&vfsOne) == 0) #endif /* Maximum pathname length supported by this VFS. */ #define VFS__MAX_PATHNAME 512 /* WAL magic value. Either this value, or the same value with the least * significant bit also set (FORMAT__WAL_MAGIC | 0x00000001) is stored in 32-bit * big-endian format in the first 4 bytes of a WAL file. * * If the LSB is set, then the checksums for each frame within the WAL file are * calculated by treating all data as an array of 32-bit big-endian * words. Otherwise, they are calculated by interpreting all data as 32-bit * little-endian words. */ #define VFS__WAL_MAGIC 0x377f0682 /* WAL format version (same for WAL index). */ #define VFS__WAL_VERSION 3007000 /* Index of the write lock in the WAL-index header locks area. */ #define VFS__WAL_WRITE_LOCK 0 /* Write ahead log header size. */ #define VFS__WAL_HEADER_SIZE 32 /* Write ahead log frame header size. */ #define VFS__FRAME_HEADER_SIZE 24 /* Size of the first part of the WAL index header. */ #define VFS__WAL_INDEX_HEADER_SIZE 48 /* Size of a single memory-mapped WAL index region. */ #define VFS__WAL_INDEX_REGION_SIZE 32768 #define vfsFrameSize(PAGE_SIZE) (VFS__FRAME_HEADER_SIZE + PAGE_SIZE) /* Hold content for a shared memory mapping. */ struct vfsShm { void **regions; /* Pointers to shared memory regions. */ unsigned n_regions; /* Number of shared memory regions. */ unsigned refcount; /* Number of outstanding mappings. */ unsigned shared[SQLITE_SHM_NLOCK]; /* Count of shared locks */ unsigned exclusive[SQLITE_SHM_NLOCK]; /* Count of exclusive locks */ }; /* Hold the content of a single WAL frame. */ struct vfsFrame { uint8_t header[VFS__FRAME_HEADER_SIZE]; uint8_t *page; /* Content of the page. */ }; /* WAL-specific content. * Watch out when changing the members of this struct, see * comment in `formatWalChecksumBytes`. */ struct vfsWal { uint8_t hdr[VFS__WAL_HEADER_SIZE]; /* Header. */ struct vfsFrame **frames; /* All frames committed. */ unsigned n_frames; /* Number of committed frames. */ struct vfsFrame **tx; /* Frames added by a transaction. */ unsigned n_tx; /* Number of added frames. */ }; /* Database-specific content */ struct vfsDatabase { char *name; /* Database name. */ void **pages; /* All database. */ unsigned n_pages; /* Number of pages. */ struct vfsShm shm; /* Shared memory. */ struct vfsWal wal; /* Associated WAL. */ }; /* Flip a 16-bit number back and forth to or from big-endian representation. */ static uint16_t vfsFlip16(uint16_t v) { #if defined(__BYTE_ORDER) && (__BYTE_ORDER == __BIG_ENDIAN) return v; #elif defined(__BYTE_ORDER) && (__BYTE_ORDER == __LITTLE_ENDIAN) && \ defined(__GNUC__) && __GNUC__ >= 4 && __GNUC_MINOR__ >= 8 return __builtin_bswap16(v); #else union { uint16_t u; uint8_t v[4]; } s; s.v[0] = (uint8_t)(v >> 8); s.v[1] = (uint8_t)v; return s.u; #endif } /* Flip a 32-bit number back and forth to or from big-endian representation. */ static uint32_t vfsFlip32(uint32_t v) { #if defined(__BYTE_ORDER) && (__BYTE_ORDER == __BIG_ENDIAN) return v; #elif defined(__BYTE_ORDER) && (__BYTE_ORDER == __LITTLE_ENDIAN) && \ defined(__GNUC__) && __GNUC__ >= 4 && __GNUC_MINOR__ >= 8 return __builtin_bswap32(v); #else union { uint32_t u; uint8_t v[4]; } s; s.v[0] = (uint8_t)(v >> 24); s.v[1] = (uint8_t)(v >> 16); s.v[2] = (uint8_t)(v >> 8); s.v[3] = (uint8_t)v; return s.u; #endif } /* Load a 16-bit number stored in big-endian representation. */ static uint32_t vfsGet16(const uint8_t *buf) { union { uint16_t u; uint8_t v[2]; } s; s.v[0] = buf[0]; s.v[1] = buf[1]; return vfsFlip16(s.u); } /* Load a 32-bit number stored in big-endian representation. */ static uint32_t vfsGet32(const uint8_t *buf) { union { uint32_t u; uint8_t v[4]; } s; s.v[0] = buf[0]; s.v[1] = buf[1]; s.v[2] = buf[2]; s.v[3] = buf[3]; return vfsFlip32(s.u); } /* Store a 32-bit number in big endian format */ static void vfsPut32(uint32_t v, uint8_t *buf) { uint32_t u = vfsFlip32(v); memcpy(buf, &u, sizeof u); } /* * Generate or extend an 8 byte checksum based on the data in array data[] and * the initial values of in[0] and in[1] (or initial values of 0 and 0 if * in==NULL). * * The checksum is written back into out[] before returning. * * n must be a positive multiple of 8. */ static void vfsChecksum( uint8_t *data, /* Content to be checksummed */ unsigned n, /* Bytes of content in a[]. Must be a multiple of 8. */ const uint32_t in[2], /* Initial checksum value input */ uint32_t out[2] /* OUT: Final checksum value output */ ) { assert((((uintptr_t)data) % sizeof(uint32_t)) == 0); uint32_t s1, s2; uint32_t *cur = (uint32_t *)__builtin_assume_aligned(data, sizeof(uint32_t)); uint32_t *end = (uint32_t *)__builtin_assume_aligned(&data[n], sizeof(uint32_t)); if (in) { s1 = in[0]; s2 = in[1]; } else { s1 = s2 = 0; } assert(n >= 8); assert((n & 0x00000007) == 0); assert(n <= 65536); do { s1 += *cur++ + s2; s2 += *cur++ + s1; } while (cur < end); out[0] = s1; out[1] = s2; } /* Create a new frame of a WAL file. */ static struct vfsFrame *vfsFrameCreate(unsigned size) { struct vfsFrame *f; assert(size > 0); f = sqlite3_malloc(sizeof *f); if (f == NULL) { goto oom; } f->page = sqlite3_malloc64(size); if (f->page == NULL) { goto oom_after_page_alloc; } memset(f->header, 0, FORMAT__WAL_FRAME_HDR_SIZE); memset(f->page, 0, (size_t)size); return f; oom_after_page_alloc: sqlite3_free(f); oom: return NULL; } /* Destroy a WAL frame */ static void vfsFrameDestroy(struct vfsFrame *f) { assert(f != NULL); assert(f->page != NULL); sqlite3_free(f->page); sqlite3_free(f); } /* Initialize the shared memory mapping of a database file. */ static void vfsShmInit(struct vfsShm *s) { int i; s->regions = NULL; s->n_regions = 0; s->refcount = 0; for (i = 0; i < SQLITE_SHM_NLOCK; i++) { s->shared[i] = 0; s->exclusive[i] = 0; } } /* Release all resources used by a shared memory mapping. */ static void vfsShmClose(struct vfsShm *s) { void *region; unsigned i; assert(s != NULL); /* Free all regions. */ for (i = 0; i < s->n_regions; i++) { region = *(s->regions + i); assert(region != NULL); sqlite3_free(region); } /* Free the shared memory region array. */ if (s->regions != NULL) { sqlite3_free(s->regions); } } /* Revert the shared mamory to its initial state. */ static void vfsShmReset(struct vfsShm *s) { vfsShmClose(s); vfsShmInit(s); } /* Initialize a new WAL object. */ static void vfsWalInit(struct vfsWal *w) { memset(w->hdr, 0, VFS__WAL_HEADER_SIZE); w->frames = NULL; w->n_frames = 0; w->tx = NULL; w->n_tx = 0; } /* Initialize a new database object. */ static void vfsDatabaseInit(struct vfsDatabase *d) { d->pages = NULL; d->n_pages = 0; vfsShmInit(&d->shm); vfsWalInit(&d->wal); } /* Release all memory used by a WAL object. */ static void vfsWalClose(struct vfsWal *w) { unsigned i; for (i = 0; i < w->n_frames; i++) { vfsFrameDestroy(w->frames[i]); } if (w->frames != NULL) { sqlite3_free(w->frames); } for (i = 0; i < w->n_tx; i++) { vfsFrameDestroy(w->tx[i]); } if (w->tx != NULL) { sqlite3_free(w->tx); } } /* Release all memory used by a database object. */ static void vfsDatabaseClose(struct vfsDatabase *d) { unsigned i; for (i = 0; i < d->n_pages; i++) { sqlite3_free(d->pages[i]); } if (d->pages != NULL) { sqlite3_free(d->pages); } vfsShmClose(&d->shm); vfsWalClose(&d->wal); } /* Destroy the content of a database object. */ static void vfsDatabaseDestroy(struct vfsDatabase *d) { assert(d != NULL); sqlite3_free(d->name); vfsDatabaseClose(d); sqlite3_free(d); } /* * Comment copied entirely for sqlite source code, it is safe to assume * the value 0x40000000 will never change. dq_sqlite_pending_byte is global * to be able to adapt it in the unittest, the value must never be changed. * * ==BEGIN COPY== * The value of the "pending" byte must be 0x40000000 (1 byte past the * 1-gibabyte boundary) in a compatible database. SQLite never uses * the database page that contains the pending byte. It never attempts * to read or write that page. The pending byte page is set aside * for use by the VFS layers as space for managing file locks. * * During testing, it is often desirable to move the pending byte to * a different position in the file. This allows code that has to * deal with the pending byte to run on files that are much smaller * than 1 GiB. The sqlite3_test_control() interface can be used to * move the pending byte. * * IMPORTANT: Changing the pending byte to any value other than * 0x40000000 results in an incompatible database file format! * Changing the pending byte during operation will result in undefined * and incorrect behavior. * ==END COPY== */ unsigned dq_sqlite_pending_byte = 0x40000000; /* Get a page from the given database, possibly creating a new one. */ static int vfsDatabaseGetPage(struct vfsDatabase *d, uint32_t page_size, unsigned pgno, void **page) { int rc; assert(d != NULL); assert(pgno > 0); /* SQLite should access pages progressively, without jumping more than * one page after the end unless one would attempt to access a page at * `sqlite_pending_byte` offset, skipping a page is permitted then. */ bool pending_byte_page_reached = (page_size * d->n_pages == dq_sqlite_pending_byte); if ((pgno > d->n_pages + 1) && !pending_byte_page_reached) { rc = SQLITE_IOERR_WRITE; goto err; } if (pgno <= d->n_pages) { /* Return the existing page. */ assert(d->pages != NULL); *page = d->pages[pgno-1]; return SQLITE_OK; } /* Create a new page, grow the page array, and append the * new page to it. */ *page = sqlite3_malloc64(page_size); if (*page == NULL) { rc = SQLITE_NOMEM; goto err; } void **pages = sqlite3_realloc64(d->pages, sizeof *pages * pgno); if (pages == NULL) { rc = SQLITE_NOMEM; goto err_after_vfs_page_create; } pages[pgno-1] = *page; /* Allocate a page to store the pending_byte */ if (pending_byte_page_reached) { void *pending_byte_page = sqlite3_malloc64(page_size); if (pending_byte_page == NULL) { rc = SQLITE_NOMEM; goto err_after_pending_byte_page; } pages[d->n_pages] = pending_byte_page; } /* Update the page array. */ d->pages = pages; d->n_pages = pgno; return SQLITE_OK; err_after_pending_byte_page: d->pages = pages; err_after_vfs_page_create: sqlite3_free(*page); err: *page = NULL; return rc; } /* Get a frame from the current transaction, possibly creating a new one. */ static int vfsWalFrameGet(struct vfsWal *w, unsigned index, uint32_t page_size, struct vfsFrame **frame) { int rv; assert(w != NULL); assert(index > 0); /* SQLite should access pages progressively, without jumping more than * one page after the end. */ if (index > w->n_frames + w->n_tx + 1) { rv = SQLITE_IOERR_WRITE; goto err; } if (index == w->n_frames + w->n_tx + 1) { /* Create a new frame, grow the transaction array, and append * the new frame to it. */ struct vfsFrame **tx; /* We assume that the page size has been set, either by * intervepting the first main database file write, or by * handling a 'PRAGMA page_size=N' command in * vfs__file_control(). This assumption is enforved in * vfsFileWrite(). */ assert(page_size > 0); *frame = vfsFrameCreate(page_size); if (*frame == NULL) { rv = SQLITE_NOMEM; goto err; } tx = sqlite3_realloc64(w->tx, sizeof *tx * w->n_tx + 1); if (tx == NULL) { rv = SQLITE_NOMEM; goto err_after_vfs_frame_create; } /* Append the new page to the new page array. */ tx[index - w->n_frames - 1] = *frame; /* Update the page array. */ w->tx = tx; w->n_tx++; } else { /* Return the existing page. */ assert(w->tx != NULL); *frame = w->tx[index - w->n_frames - 1]; } return SQLITE_OK; err_after_vfs_frame_create: vfsFrameDestroy(*frame); err: *frame = NULL; return rv; } /* Lookup a page from the given database, returning NULL if it doesn't exist. */ static void *vfsDatabasePageLookup(struct vfsDatabase *d, unsigned pgno) { void *page; assert(d != NULL); assert(pgno > 0); if (pgno > d->n_pages) { /* This page hasn't been written yet. */ return NULL; } page = d->pages[pgno - 1]; assert(page != NULL); return page; } /* Lookup a frame from the WAL, returning NULL if it doesn't exist. */ static struct vfsFrame *vfsWalFrameLookup(struct vfsWal *w, unsigned n) { struct vfsFrame *frame; assert(w != NULL); assert(n > 0); if (n > w->n_frames + w->n_tx) { /* This page hasn't been written yet. */ return NULL; } if (n <= w->n_frames) { frame = w->frames[n - 1]; } else { frame = w->tx[n - w->n_frames - 1]; } assert(frame != NULL); return frame; } /* Parse the page size ("Must be a power of two between 512 and 32768 * inclusive, or the value 1 representing a page size of 65536"). * * Return 0 if the page size is out of bound. */ static uint32_t vfsParsePageSize(uint32_t page_size) { if (page_size == 1) { page_size = FORMAT__PAGE_SIZE_MAX; } else if (page_size < FORMAT__PAGE_SIZE_MIN) { page_size = 0; } else if (page_size > (FORMAT__PAGE_SIZE_MAX / 2)) { page_size = 0; } else if (((page_size - 1) & page_size) != 0) { page_size = 0; } return page_size; } static uint32_t vfsDatabaseGetPageSize(struct vfsDatabase *d) { uint8_t *page; assert(d->n_pages > 0); page = d->pages[0]; /* The page size is stored in the 16th and 17th bytes of the first * database page (big-endian) */ return vfsParsePageSize(vfsGet16(&page[16])); } /* Truncate a database file to be exactly the given number of pages. */ static int vfsDatabaseTruncate(struct vfsDatabase *d, sqlite_int64 size) { void **cursor; uint32_t page_size; unsigned n_pages; unsigned i; if (d->n_pages == 0) { if (size > 0) { return SQLITE_IOERR_TRUNCATE; } return SQLITE_OK; } /* Since the file size is not zero, some content must * have been written and the page size must be known. */ page_size = vfsDatabaseGetPageSize(d); assert(page_size > 0); if ((size % page_size) != 0) { return SQLITE_IOERR_TRUNCATE; } n_pages = (unsigned)(size / page_size); /* We expect callers to only invoke us if some actual content has been * written already. */ assert(d->n_pages > 0); /* Truncate should always shrink a file. */ assert(n_pages <= d->n_pages); assert(d->pages != NULL); /* Destroy pages beyond pages_len. */ cursor = d->pages + n_pages; for (i = 0; i < (d->n_pages - n_pages); i++) { sqlite3_free(*cursor); cursor++; } /* Shrink the page array, possibly to 0. * * TODO: in principle realloc could fail also when shrinking. */ d->pages = sqlite3_realloc64(d->pages, sizeof *d->pages * n_pages); /* Update the page count. */ d->n_pages = n_pages; return SQLITE_OK; } /* Truncate a WAL file to zero. */ static int vfsWalTruncate(struct vfsWal *w, sqlite3_int64 size) { unsigned i; /* We expect SQLite to only truncate to zero, after a * full checkpoint. * * TODO: figure out other case where SQLite might * truncate to a different size. */ if (size != 0) { return SQLITE_PROTOCOL; } if (w->n_frames == 0) { return SQLITE_OK; } assert(w->frames != NULL); /* Restart the header. */ formatWalRestartHeader(w->hdr); /* Destroy all frames. */ for (i = 0; i < w->n_frames; i++) { vfsFrameDestroy(w->frames[i]); } sqlite3_free(w->frames); w->frames = NULL; w->n_frames = 0; return SQLITE_OK; } enum vfsFileType { VFS__DATABASE, /* Main database file */ VFS__JOURNAL, /* Default SQLite journal file */ VFS__WAL /* Write-Ahead Log */ }; /* Implementation of the abstract sqlite3_file base class. */ struct vfsFile { sqlite3_file base; /* Base class. Must be first. */ struct vfs *vfs; /* Pointer to volatile VFS data. */ enum vfsFileType type; /* Associated file (main db or WAL). */ struct vfsDatabase *database; /* Underlying database content. */ int flags; /* Flags passed to xOpen */ sqlite3_file *temp; /* For temp-files, actual VFS. */ }; /* Custom dqlite VFS. Contains pointers to all databases that were created. */ struct vfs { struct vfsDatabase **databases; /* Database objects */ unsigned n_databases; /* Number of databases */ int error; /* Last error occurred. */ }; /* Create a new vfs object. */ static struct vfs *vfsCreate(void) { struct vfs *v; v = sqlite3_malloc(sizeof *v); if (v == NULL) { return NULL; } v->databases = NULL; v->n_databases = 0; return v; } /* Release the memory used internally by the VFS object. * * All file content will be de-allocated, so dangling open FDs against * those files will be broken. */ static void vfsDestroy(struct vfs *r) { unsigned i; assert(r != NULL); for (i = 0; i < r->n_databases; i++) { struct vfsDatabase *database = r->databases[i]; vfsDatabaseDestroy(database); } if (r->databases != NULL) { sqlite3_free(r->databases); } } static bool vfsFilenameEndsWith(const char *filename, const char *suffix) { size_t n_filename = strlen(filename); size_t n_suffix = strlen(suffix); if (n_suffix > n_filename) { return false; } return strncmp(filename + n_filename - n_suffix, suffix, n_suffix) == 0; } /* Find the database object associated with the given filename. */ static struct vfsDatabase *vfsDatabaseLookup(struct vfs *v, const char *filename) { size_t n = strlen(filename); unsigned i; assert(v != NULL); assert(filename != NULL); if (vfsFilenameEndsWith(filename, "-wal")) { n -= strlen("-wal"); } if (vfsFilenameEndsWith(filename, "-journal")) { n -= strlen("-journal"); } for (i = 0; i < v->n_databases; i++) { struct vfsDatabase *database = v->databases[i]; if (strncmp(database->name, filename, n) == 0) { // Found matching file. return database; } } return NULL; } static int vfsDeleteDatabase(struct vfs *r, const char *name) { unsigned i; for (i = 0; i < r->n_databases; i++) { struct vfsDatabase *database = r->databases[i]; unsigned j; if (strcmp(database->name, name) != 0) { continue; } /* Free all memory allocated for this file. */ vfsDatabaseDestroy(database); /* Shift all other contents objects. */ for (j = i + 1; j < r->n_databases; j++) { r->databases[j - 1] = r->databases[j]; } r->n_databases--; return SQLITE_OK; } r->error = ENOENT; return SQLITE_IOERR_DELETE_NOENT; } static int vfsFileClose(sqlite3_file *file) { int rc = SQLITE_OK; struct vfsFile *f = (struct vfsFile *)file; struct vfs *v = (struct vfs *)(f->vfs); if (f->temp != NULL) { /* Close the actual temporary file. */ rc = f->temp->pMethods->xClose(f->temp); sqlite3_free(f->temp); return rc; } if (f->flags & SQLITE_OPEN_DELETEONCLOSE) { rc = vfsDeleteDatabase(v, f->database->name); } return rc; } /* Read data from the main database. */ static int vfsDatabaseRead(struct vfsDatabase *d, void *buf, int amount, sqlite_int64 offset) { unsigned page_size; unsigned pgno; void *page; if (d->n_pages == 0) { return SQLITE_IOERR_SHORT_READ; } /* If the main database file is not empty, we expect the * page size to have been set by an initial write. */ page_size = vfsDatabaseGetPageSize(d); assert(page_size > 0); if (offset < page_size) { /* Reading from page 1. We expect the read to be * at most page_size bytes. */ assert(amount <= (int)page_size); pgno = 1; } else { /* For pages greater than 1, we expect a full * page read, with an offset that starts exectly * at the page boundary. */ assert(amount == (int)page_size); assert(((unsigned)offset % page_size) == 0); pgno = ((unsigned)offset / page_size) + 1; } assert(pgno > 0); page = vfsDatabasePageLookup(d, pgno); if (pgno == 1) { /* Read the desired part of page 1. */ memcpy(buf, page + offset, (size_t)amount); } else { /* Read the full page. */ memcpy(buf, page, (size_t)amount); } return SQLITE_OK; } /* Get the page size stored in the WAL header. */ static uint32_t vfsWalGetPageSize(struct vfsWal *w) { /* The page size is stored in the 4 bytes starting at 8 * (big-endian) */ return vfsParsePageSize(vfsGet32(&w->hdr[8])); } /* Read data from the WAL. */ static int vfsWalRead(struct vfsWal *w, void *buf, int amount, sqlite_int64 offset) { uint32_t page_size; unsigned index; struct vfsFrame *frame; if (w->n_frames == 0) { return SQLITE_IOERR_SHORT_READ; } if (offset == 0) { /* Read the header. */ assert(amount == VFS__WAL_HEADER_SIZE); memcpy(buf, w->hdr, VFS__WAL_HEADER_SIZE); return SQLITE_OK; } page_size = vfsWalGetPageSize(w); assert(page_size > 0); /* For any other frame, we expect either a header read, * a checksum read, a page read or a full frame read. */ if (amount == FORMAT__WAL_FRAME_HDR_SIZE) { assert(((offset - VFS__WAL_HEADER_SIZE) % (page_size + FORMAT__WAL_FRAME_HDR_SIZE)) == 0); index = formatWalCalcFrameIndex(page_size, (unsigned)offset); } else if (amount == sizeof(uint32_t) * 2) { if (offset == FORMAT__WAL_FRAME_HDR_SIZE) { /* Read the checksum from the WAL * header. */ memcpy(buf, w->hdr + offset, (size_t)amount); return SQLITE_OK; } assert(((offset - 16 - VFS__WAL_HEADER_SIZE) % (page_size + FORMAT__WAL_FRAME_HDR_SIZE)) == 0); index = ((unsigned)offset - 16 - VFS__WAL_HEADER_SIZE) / (page_size + FORMAT__WAL_FRAME_HDR_SIZE) + 1; } else if (amount == (int)page_size) { assert(((offset - VFS__WAL_HEADER_SIZE - FORMAT__WAL_FRAME_HDR_SIZE) % (page_size + FORMAT__WAL_FRAME_HDR_SIZE)) == 0); index = formatWalCalcFrameIndex(page_size, (unsigned)offset); } else { assert(amount == (FORMAT__WAL_FRAME_HDR_SIZE + (int)page_size)); index = formatWalCalcFrameIndex(page_size, (unsigned)offset); } if (index == 0) { // This is an attempt to read a page that was // never written. memset(buf, 0, (size_t)amount); return SQLITE_IOERR_SHORT_READ; } frame = vfsWalFrameLookup(w, index); if (amount == FORMAT__WAL_FRAME_HDR_SIZE) { memcpy(buf, frame->header, (size_t)amount); } else if (amount == sizeof(uint32_t) * 2) { memcpy(buf, frame->header + 16, (size_t)amount); } else if (amount == (int)page_size) { memcpy(buf, frame->page, (size_t)amount); } else { memcpy(buf, frame->header, FORMAT__WAL_FRAME_HDR_SIZE); memcpy(buf + FORMAT__WAL_FRAME_HDR_SIZE, frame->page, page_size); } return SQLITE_OK; } static int vfsFileRead(sqlite3_file *file, void *buf, int amount, sqlite_int64 offset) { struct vfsFile *f = (struct vfsFile *)file; int rv; assert(buf != NULL); assert(amount > 0); assert(f != NULL); if (f->temp != NULL) { /* Read from the actual temporary file. */ return f->temp->pMethods->xRead(f->temp, buf, amount, offset); } switch (f->type) { case VFS__DATABASE: rv = vfsDatabaseRead(f->database, buf, amount, offset); break; case VFS__WAL: rv = vfsWalRead(&f->database->wal, buf, amount, offset); break; default: rv = SQLITE_IOERR_READ; break; } /* From SQLite docs: * * If xRead() returns SQLITE_IOERR_SHORT_READ it must also fill * in the unread portions of the buffer with zeros. A VFS that * fails to zero-fill short reads might seem to work. However, * failure to zero-fill short reads will eventually lead to * database corruption. */ if (rv == SQLITE_IOERR_SHORT_READ) { memset(buf, 0, (size_t)amount); } return rv; } static int vfsDatabaseWrite(struct vfsDatabase *d, const void *buf, int amount, sqlite_int64 offset) { unsigned pgno; uint32_t page_size; void *page; int rc; if (offset == 0) { const uint8_t *header = buf; /* This is the first database page. We expect * the data to contain at least the header. */ assert(amount >= FORMAT__DB_HDR_SIZE); /* Extract the page size from the header. */ page_size = vfsParsePageSize(vfsGet16(&header[16])); if (page_size == 0) { return SQLITE_CORRUPT; } pgno = 1; } else { page_size = vfsDatabaseGetPageSize(d); /* The header must have been written and the page size set. */ assert(page_size > 0); /* For pages beyond the first we expect offset to be a multiple * of the page size. */ assert((offset % page_size) == 0); /* We expect that SQLite writes a page at time. */ assert(amount == (int)page_size); pgno = ((unsigned)offset / page_size) + 1; } rc = vfsDatabaseGetPage(d, page_size, pgno, &page); if (rc != SQLITE_OK) { return rc; } assert(page != NULL); memcpy(page, buf, (size_t)amount); return SQLITE_OK; } static int vfsWalWrite(struct vfsWal *w, const void *buf, int amount, sqlite_int64 offset) { uint32_t page_size; unsigned index; struct vfsFrame *frame; /* WAL header. */ if (offset == 0) { /* We expect the data to contain exactly 32 * bytes. */ assert(amount == VFS__WAL_HEADER_SIZE); memcpy(w->hdr, buf, (size_t)amount); return SQLITE_OK; } page_size = vfsWalGetPageSize(w); assert(page_size > 0); /* This is a WAL frame write. We expect either a frame * header or page write. */ if (amount == FORMAT__WAL_FRAME_HDR_SIZE) { /* Frame header write. */ assert(((offset - VFS__WAL_HEADER_SIZE) % (page_size + FORMAT__WAL_FRAME_HDR_SIZE)) == 0); index = formatWalCalcFrameIndex(page_size, (unsigned)offset); vfsWalFrameGet(w, index, page_size, &frame); if (frame == NULL) { return SQLITE_NOMEM; } memcpy(frame->header, buf, (size_t)amount); } else { /* Frame page write. */ assert(amount == (int)page_size); assert(((offset - VFS__WAL_HEADER_SIZE - FORMAT__WAL_FRAME_HDR_SIZE) % (page_size + FORMAT__WAL_FRAME_HDR_SIZE)) == 0); index = formatWalCalcFrameIndex(page_size, (unsigned)offset); /* The header for the this frame must already * have been written, so the page is there. */ frame = vfsWalFrameLookup(w, index); assert(frame != NULL); memcpy(frame->page, buf, (size_t)amount); } return SQLITE_OK; } static int vfsFileWrite(sqlite3_file *file, const void *buf, int amount, sqlite_int64 offset) { struct vfsFile *f = (struct vfsFile *)file; int rv; assert(buf != NULL); assert(amount > 0); assert(f != NULL); if (f->temp != NULL) { /* Write to the actual temporary file. */ return f->temp->pMethods->xWrite(f->temp, buf, amount, offset); } switch (f->type) { case VFS__DATABASE: rv = vfsDatabaseWrite(f->database, buf, amount, offset); break; case VFS__WAL: rv = vfsWalWrite(&f->database->wal, buf, amount, offset); break; case VFS__JOURNAL: /* Silently swallow writes to the journal */ rv = SQLITE_OK; break; default: rv = SQLITE_IOERR_WRITE; break; } return rv; } static int vfsFileTruncate(sqlite3_file *file, sqlite_int64 size) { struct vfsFile *f = (struct vfsFile *)file; int rv; assert(f != NULL); switch (f->type) { case VFS__DATABASE: rv = vfsDatabaseTruncate(f->database, size); break; case VFS__WAL: rv = vfsWalTruncate(&f->database->wal, size); break; default: rv = SQLITE_IOERR_TRUNCATE; break; } return rv; } static int vfsFileSync(sqlite3_file *file, int flags) { (void)file; (void)flags; return SQLITE_IOERR_FSYNC; } /* Return the size of the database file in bytes. */ static size_t vfsDatabaseFileSize(struct vfsDatabase *d) { size_t size = 0; if (d->n_pages > 0) { size = d->n_pages * vfsDatabaseGetPageSize(d); } return size; } /* Return the size of the WAL file in bytes. */ static size_t vfsWalFileSize(struct vfsWal *w) { size_t size = 0; if (w->n_frames > 0) { uint32_t page_size; page_size = vfsWalGetPageSize(w); size += VFS__WAL_HEADER_SIZE; size += w->n_frames * (FORMAT__WAL_FRAME_HDR_SIZE + page_size); } return size; } static int vfsFileSize(sqlite3_file *file, sqlite_int64 *size) { struct vfsFile *f = (struct vfsFile *)file; size_t n; switch (f->type) { case VFS__DATABASE: n = vfsDatabaseFileSize(f->database); break; case VFS__WAL: /* TODO? here we assume that FileSize() is never invoked * between a header write and a page write. */ n = vfsWalFileSize(&f->database->wal); break; default: n = 0; break; } *size = (sqlite3_int64)n; return SQLITE_OK; } /* Locking a file is a no-op, since no other process has visibility on it. */ static int vfsFileLock(sqlite3_file *file, int lock) { (void)file; (void)lock; return SQLITE_OK; } /* Unlocking a file is a no-op, since no other process has visibility on it. */ static int vfsFileUnlock(sqlite3_file *file, int lock) { (void)file; (void)lock; return SQLITE_OK; } /* We always report that a lock is held. This routine should be used only in * journal mode, so it doesn't matter. */ static int vfsFileCheckReservedLock(sqlite3_file *file, int *result) { (void)file; *result = 1; return SQLITE_OK; } /* Handle pragma a pragma file control. See the xFileControl * docstring in sqlite.h.in for more details. */ static int vfsFileControlPragma(struct vfsFile *f, char **fnctl) { const char *left; const char *right; assert(f != NULL); assert(fnctl != NULL); left = fnctl[1]; right = fnctl[2]; assert(left != NULL); if (strcmp(left, "page_size") == 0 && right) { /* When the user executes 'PRAGMA page_size=N' we save the * size internally. * * The page size must be between 512 and 65536, and be a * power of two. The check below was copied from * sqlite3BtreeSetPageSize in btree.c. * * Invalid sizes are simply ignored, SQLite will do the same. * * It's not possible to change the size after it's set. */ int page_size = atoi(right); if (page_size >= FORMAT__PAGE_SIZE_MIN && page_size <= FORMAT__PAGE_SIZE_MAX && ((page_size - 1) & page_size) == 0) { if (f->database->n_pages > 0 && page_size != (int)vfsDatabaseGetPageSize(f->database)) { fnctl[0] = "changing page size is not supported"; return SQLITE_IOERR; } } } else if (strcmp(left, "journal_mode") == 0 && right) { /* When the user executes 'PRAGMA journal_mode=x' we ensure * that the desired mode is 'wal'. */ if (strcasecmp(right, "wal") != 0) { fnctl[0] = "only WAL mode is supported"; return SQLITE_IOERR; } } /* We're returning NOTFOUND here to tell SQLite that we wish it to go on * with its own handling as well. If we returned SQLITE_OK the page size * of the journal mode wouldn't be effectively set, as the processing of * the PRAGMA would stop here. */ return SQLITE_NOTFOUND; } /* Return the page number field stored in the header of the given frame. */ static uint32_t vfsFrameGetPageNumber(struct vfsFrame *f) { return vfsGet32(&f->header[0]); } /* Return the database size field stored in the header of the given frame. */ static uint32_t vfsFrameGetDatabaseSize(struct vfsFrame *f) { return vfsGet32(&f->header[4]); } /* Return the checksum-1 field stored in the header of the given frame. */ static uint32_t vfsFrameGetChecksum1(struct vfsFrame *f) { return vfsGet32(&f->header[16]); } /* Return the checksum-2 field stored in the header of the given frame. */ static uint32_t vfsFrameGetChecksum2(struct vfsFrame *f) { return vfsGet32(&f->header[20]); } /* Fill the header and the content of a WAL frame. The given checksum is the * rolling one of all preceeding frames and is updated by this function. */ static void vfsFrameFill(struct vfsFrame *f, uint32_t page_number, uint32_t database_size, uint32_t salt[2], uint32_t checksum[2], uint8_t *page, uint32_t page_size) { vfsPut32(page_number, &f->header[0]); vfsPut32(database_size, &f->header[4]); vfsChecksum(f->header, 8, checksum, checksum); vfsChecksum(page, page_size, checksum, checksum); memcpy(&f->header[8], &salt[0], sizeof salt[0]); memcpy(&f->header[12], &salt[1], sizeof salt[1]); vfsPut32(checksum[0], &f->header[16]); vfsPut32(checksum[1], &f->header[20]); memcpy(f->page, page, page_size); } /* This function modifies part of the WAL index header to reflect the current * content of the WAL. * * It is called in two cases. First, after a write transaction gets completed * and the SQLITE_FCNTL_COMMIT_PHASETWO file control op code is triggered, in * order to "rewind" the mxFrame and szPage fields of the WAL index header back * to when the write transaction started, effectively "shadowing" the * transaction, which will be replicated asynchronously. Second, when the * replication actually succeeds and dqlite_vfs_apply() is called on the VFS * that originated the transaction, in order to make the transaction visible. * * Note that the hash table contained in the WAL index does not get modified, * and even after a rewind following a write transaction it will still contain * entries for the frames committed by the transaction. That's safe because * mxFrame will make clients ignore those hash table entries. However it means * that in case the replication is not actually successful and * dqlite_vfs_abort() is called the WAL index must be invalidated. **/ static void vfsAmendWalIndexHeader(struct vfsDatabase *d) { struct vfsShm *shm = &d->shm; struct vfsWal *wal = &d->wal; uint8_t *index; uint32_t frame_checksum[2] = {0, 0}; uint32_t n_pages = (uint32_t)d->n_pages; uint32_t checksum[2] = {0, 0}; if (wal->n_frames > 0) { struct vfsFrame *last = wal->frames[wal->n_frames - 1]; frame_checksum[0] = vfsFrameGetChecksum1(last); frame_checksum[1] = vfsFrameGetChecksum2(last); n_pages = vfsFrameGetDatabaseSize(last); } assert(shm->n_regions > 0); index = shm->regions[0]; /* index is an alias for shm->regions[0] which is a void* that points to * memory allocated by `sqlite3_malloc64` and has the required alignment */ assert(*(uint32_t *)(__builtin_assume_aligned(&index[0], sizeof(uint32_t))) == VFS__WAL_VERSION); /* iVersion */ assert(index[12] == 1); /* isInit */ assert(index[13] == VFS__BIGENDIAN); /* bigEndCksum */ *(uint32_t *)(__builtin_assume_aligned(&index[16], sizeof(uint32_t))) = wal->n_frames; *(uint32_t *)(__builtin_assume_aligned(&index[20], sizeof(uint32_t))) = n_pages; *(uint32_t *)(__builtin_assume_aligned(&index[24], sizeof(uint32_t))) = frame_checksum[0]; *(uint32_t *)(__builtin_assume_aligned(&index[28], sizeof(uint32_t))) = frame_checksum[1]; vfsChecksum(index, 40, checksum, checksum); *(uint32_t *)__builtin_assume_aligned(&index[40], sizeof(uint32_t)) = checksum[0]; *(uint32_t *)__builtin_assume_aligned(&index[44], sizeof(uint32_t)) = checksum[1]; /* Update the second copy of the first part of the WAL index header. */ memcpy(index + VFS__WAL_INDEX_HEADER_SIZE, index, VFS__WAL_INDEX_HEADER_SIZE); } /* The SQLITE_FCNTL_COMMIT_PHASETWO file control op code is trigged by the * SQLite pager after completing a transaction. */ static int vfsFileControlCommitPhaseTwo(struct vfsFile *f) { struct vfsDatabase *database = f->database; struct vfsWal *wal = &database->wal; if (wal->n_tx > 0) { vfsAmendWalIndexHeader(database); } return 0; } static int vfsFileControl(sqlite3_file *file, int op, void *arg) { struct vfsFile *f = (struct vfsFile *)file; int rv; assert(f->type == VFS__DATABASE); switch (op) { case SQLITE_FCNTL_PRAGMA: rv = vfsFileControlPragma(f, arg); break; case SQLITE_FCNTL_COMMIT_PHASETWO: rv = vfsFileControlCommitPhaseTwo(f); break; case SQLITE_FCNTL_PERSIST_WAL: /* This prevents SQLite from deleting the WAL after the * last connection is closed. */ *(int *)(arg) = 1; rv = SQLITE_OK; break; default: rv = SQLITE_OK; break; } return rv; } static int vfsFileSectorSize(sqlite3_file *file) { (void)file; return 0; } static int vfsFileDeviceCharacteristics(sqlite3_file *file) { (void)file; return 0; } static int vfsShmMap(struct vfsShm *s, unsigned region_index, unsigned region_size, bool extend, void volatile **out) { void *region; int rv; if (s->regions != NULL && region_index < s->n_regions) { /* The region was already allocated. */ region = s->regions[region_index]; assert(region != NULL); } else { if (extend) { void **regions; /* We should grow the map one region at a time. */ assert(region_size == VFS__WAL_INDEX_REGION_SIZE); assert(region_index == s->n_regions); region = sqlite3_malloc64(region_size); if (region == NULL) { rv = SQLITE_NOMEM; goto err; } memset(region, 0, region_size); regions = sqlite3_realloc64( s->regions, sizeof *s->regions * (s->n_regions + 1)); if (regions == NULL) { rv = SQLITE_NOMEM; goto err_after_region_malloc; } s->regions = regions; s->regions[region_index] = region; s->n_regions++; } else { /* The region was not allocated and we don't have to * extend the map. */ region = NULL; } } *out = region; if (region_index == 0 && region != NULL) { s->refcount++; } return SQLITE_OK; err_after_region_malloc: sqlite3_free(region); err: assert(rv != SQLITE_OK); *out = NULL; return rv; } /* Simulate shared memory by allocating on the C heap. */ static int vfsFileShmMap(sqlite3_file *file, /* Handle open on database file */ int region_index, /* Region to retrieve */ int region_size, /* Size of regions */ int extend, /* True to extend file if necessary */ void volatile **out /* OUT: Mapped memory */ ) { struct vfsFile *f = (struct vfsFile *)file; assert(f->type == VFS__DATABASE); return vfsShmMap(&f->database->shm, (unsigned)region_index, (unsigned)region_size, extend != 0, out); } static int vfsShmLock(struct vfsShm *s, int ofst, int n, int flags) { int i; if (flags & SQLITE_SHM_EXCLUSIVE) { /* No shared or exclusive lock must be held in the region. */ for (i = ofst; i < ofst + n; i++) { if (s->shared[i] > 0 || s->exclusive[i] > 0) { tracef("EXCLUSIVE lock contention ofst:%d n:%d exclusive[%d]=%d shared[%d]=%d", ofst, n, i, s->exclusive[i], i, s->shared[i]); return SQLITE_BUSY; } } for (i = ofst; i < ofst + n; i++) { assert(s->exclusive[i] == 0); s->exclusive[i] = 1; } } else { /* No exclusive lock must be held in the region. */ for (i = ofst; i < ofst + n; i++) { if (s->exclusive[i] > 0) { tracef("SHARED lock contention ofst:%d n:%d exclusive[%d]=%d shared[%d]=%d", ofst, n, i, s->exclusive[i], i, s->shared[i]); return SQLITE_BUSY; } } for (i = ofst; i < ofst + n; i++) { s->shared[i]++; } } return SQLITE_OK; } static int vfsShmUnlock(struct vfsShm *s, int ofst, int n, int flags) { unsigned *these_locks; unsigned *other_locks; int i; if (flags & SQLITE_SHM_SHARED) { these_locks = s->shared; other_locks = s->exclusive; } else { these_locks = s->exclusive; other_locks = s->shared; } for (i = ofst; i < ofst + n; i++) { /* Coherence check that no lock of the other type is held in this * region. */ assert(other_locks[i] == 0); /* Only decrease the lock count if it's positive. In other words * releasing a never acquired lock is legal and idemponent. */ if (these_locks[i] > 0) { these_locks[i]--; } } return SQLITE_OK; } /* If there's a uncommitted transaction, roll it back. */ static void vfsWalRollbackIfUncommitted(struct vfsWal *w) { struct vfsFrame *last; uint32_t commit; unsigned i; if (w->n_tx == 0) { return; } tracef("rollback n_tx:%d", w->n_tx); last = w->tx[w->n_tx - 1]; commit = vfsFrameGetDatabaseSize(last); if (commit > 0) { tracef("rollback commit:%u", commit); return; } for (i = 0; i < w->n_tx; i++) { vfsFrameDestroy(w->tx[i]); } w->n_tx = 0; } static int vfsFileShmLock(sqlite3_file *file, int ofst, int n, int flags) { struct vfsFile *f; struct vfsShm *shm; struct vfsWal *wal; int rv; assert(file != NULL); assert(ofst >= 0); assert(n >= 0); /* Legal values for the offset and the range */ assert(ofst >= 0 && ofst + n <= SQLITE_SHM_NLOCK); assert(n >= 1); assert(n == 1 || (flags & SQLITE_SHM_EXCLUSIVE) != 0); /* Legal values for the flags. * * See https://sqlite.org/c3ref/c_shm_exclusive.html. */ assert(flags == (SQLITE_SHM_LOCK | SQLITE_SHM_SHARED) || flags == (SQLITE_SHM_LOCK | SQLITE_SHM_EXCLUSIVE) || flags == (SQLITE_SHM_UNLOCK | SQLITE_SHM_SHARED) || flags == (SQLITE_SHM_UNLOCK | SQLITE_SHM_EXCLUSIVE)); /* This is a no-op since shared-memory locking is relevant only for * inter-process concurrency. See also the unix-excl branch from * upstream (git commit cda6b3249167a54a0cf892f949d52760ee557129). */ f = (struct vfsFile *)file; assert(f->type == VFS__DATABASE); assert(f->database != NULL); shm = &f->database->shm; if (flags & SQLITE_SHM_UNLOCK) { rv = vfsShmUnlock(shm, ofst, n, flags); } else { rv = vfsShmLock(shm, ofst, n, flags); } wal = &f->database->wal; if (rv == SQLITE_OK && ofst == VFS__WAL_WRITE_LOCK) { assert(n == 1); /* When acquiring the write lock, make sure there's no * transaction that hasn't been rolled back or polled. */ if (flags == (SQLITE_SHM_LOCK | SQLITE_SHM_EXCLUSIVE)) { assert(wal->n_tx == 0); } /* When releasing the write lock, if we find a pending * uncommitted transaction then a rollback must have occurred. * In that case we delete the pending transaction. */ if (flags == (SQLITE_SHM_UNLOCK | SQLITE_SHM_EXCLUSIVE)) { vfsWalRollbackIfUncommitted(wal); } } return rv; } static void vfsFileShmBarrier(sqlite3_file *file) { (void)file; /* This is a no-op since we expect SQLite to be compiled with mutex * support (i.e. SQLITE_MUTEX_OMIT or SQLITE_MUTEX_NOOP are *not* * defined, see sqliteInt.h). */ } static void vfsShmUnmap(struct vfsShm *s) { s->refcount--; if (s->refcount == 0) { vfsShmReset(s); } } static int vfsFileShmUnmap(sqlite3_file *file, int delete_flag) { struct vfsFile *f = (struct vfsFile *)file; (void)delete_flag; vfsShmUnmap(&f->database->shm); return SQLITE_OK; } static const sqlite3_io_methods vfsFileMethods = { 2, // iVersion vfsFileClose, // xClose vfsFileRead, // xRead vfsFileWrite, // xWrite vfsFileTruncate, // xTruncate vfsFileSync, // xSync vfsFileSize, // xFileSize vfsFileLock, // xLock vfsFileUnlock, // xUnlock vfsFileCheckReservedLock, // xCheckReservedLock vfsFileControl, // xFileControl vfsFileSectorSize, // xSectorSize vfsFileDeviceCharacteristics, // xDeviceCharacteristics vfsFileShmMap, // xShmMap vfsFileShmLock, // xShmLock vfsFileShmBarrier, // xShmBarrier vfsFileShmUnmap, // xShmUnmap 0, 0, }; /* Create a database object and add it to the databases array. */ static struct vfsDatabase *vfsCreateDatabase(struct vfs *v, const char *name) { unsigned n = v->n_databases + 1; struct vfsDatabase **databases; struct vfsDatabase *d; assert(name != NULL); /* Create a new entry. */ databases = sqlite3_realloc64(v->databases, sizeof *databases * n); if (databases == NULL) { goto oom; } v->databases = databases; d = sqlite3_malloc(sizeof *d); if (d == NULL) { goto oom; } d->name = sqlite3_malloc64(strlen(name) + 1); if (d->name == NULL) { goto oom_after_database_malloc; } strcpy(d->name, name); vfsDatabaseInit(d); v->databases[n - 1] = d; v->n_databases = n; return d; oom_after_database_malloc: sqlite3_free(d); oom: return NULL; } static int vfsOpen(sqlite3_vfs *vfs, const char *filename, sqlite3_file *file, int flags, int *out_flags) { struct vfs *v; struct vfsFile *f; struct vfsDatabase *database; enum vfsFileType type; bool exists; int exclusive = flags & SQLITE_OPEN_EXCLUSIVE; int create = flags & SQLITE_OPEN_CREATE; int rc; (void)out_flags; assert(vfs != NULL); assert(vfs->pAppData != NULL); assert(file != NULL); /* From sqlite3.h.in: * * The SQLITE_OPEN_EXCLUSIVE flag is always used in conjunction with * the SQLITE_OPEN_CREATE flag, which are both directly analogous to * the O_EXCL and O_CREAT flags of the POSIX open() API. The * SQLITE_OPEN_EXCLUSIVE flag, when paired with the * SQLITE_OPEN_CREATE, is used to indicate that file should always be * created, and that it is an error if it already exists. It is not * used to indicate the file should be opened for exclusive access. */ assert(!exclusive || create); v = (struct vfs *)(vfs->pAppData); f = (struct vfsFile *)file; /* This tells SQLite to not call Close() in case we return an error. */ f->base.pMethods = 0; f->temp = NULL; /* Save the flags */ f->flags = flags; /* From SQLite documentation: * * If the zFilename parameter to xOpen is a NULL pointer then xOpen * must invent its own temporary name for the file. Whenever the * xFilename parameter is NULL it will also be the case that the * flags parameter will include SQLITE_OPEN_DELETEONCLOSE. */ if (filename == NULL) { assert(flags & SQLITE_OPEN_DELETEONCLOSE); /* Open an actual temporary file. */ vfs = sqlite3_vfs_find("unix"); assert(vfs != NULL); f->temp = sqlite3_malloc(vfs->szOsFile); if (f->temp == NULL) { v->error = ENOENT; return SQLITE_CANTOPEN; } rc = vfs->xOpen(vfs, NULL, f->temp, flags, out_flags); if (rc != SQLITE_OK) { sqlite3_free(f->temp); return rc; } f->base.pMethods = &vfsFileMethods; f->vfs = NULL; f->database = NULL; return SQLITE_OK; } /* Search if the database object exists already. */ database = vfsDatabaseLookup(v, filename); exists = database != NULL; if (flags & SQLITE_OPEN_MAIN_DB) { type = VFS__DATABASE; } else if (flags & SQLITE_OPEN_MAIN_JOURNAL) { type = VFS__JOURNAL; } else if (flags & SQLITE_OPEN_WAL) { type = VFS__WAL; } else { v->error = ENOENT; return SQLITE_CANTOPEN; } /* If file exists, and the exclusive flag is on, return an error. */ if (exists && exclusive && create && type == VFS__DATABASE) { v->error = EEXIST; rc = SQLITE_CANTOPEN; goto err; } if (!exists) { /* When opening a WAL or journal file we expect the main * database file to have already been created. */ if (type == VFS__WAL || type == VFS__JOURNAL) { v->error = ENOENT; rc = SQLITE_CANTOPEN; goto err; } assert(type == VFS__DATABASE); /* Check the create flag. */ if (!create) { v->error = ENOENT; rc = SQLITE_CANTOPEN; goto err; } database = vfsCreateDatabase(v, filename); if (database == NULL) { v->error = ENOMEM; rc = SQLITE_CANTOPEN; goto err; } } /* Populate the new file handle. */ f->base.pMethods = &vfsFileMethods; f->vfs = v; f->type = type; f->database = database; return SQLITE_OK; err: assert(rc != SQLITE_OK); return rc; } static int vfsDelete(sqlite3_vfs *vfs, const char *filename, int dir_sync) { struct vfs *v; (void)dir_sync; assert(vfs != NULL); assert(vfs->pAppData != NULL); if (vfsFilenameEndsWith(filename, "-journal")) { return SQLITE_OK; } if (vfsFilenameEndsWith(filename, "-wal")) { return SQLITE_OK; } v = (struct vfs *)(vfs->pAppData); return vfsDeleteDatabase(v, filename); } static int vfsAccess(sqlite3_vfs *vfs, const char *filename, int flags, int *result) { struct vfs *v; struct vfsDatabase *database; (void)flags; assert(vfs != NULL); assert(vfs->pAppData != NULL); v = (struct vfs *)(vfs->pAppData); /* If the database object exists, we consider all associated files as * existing and accessible. */ database = vfsDatabaseLookup(v, filename); if (database == NULL) { *result = 0; } else { *result = 1; } return SQLITE_OK; } static int vfsFullPathname(sqlite3_vfs *vfs, const char *filename, int pathname_len, char *pathname) { (void)vfs; /* Just return the path unchanged. */ sqlite3_snprintf(pathname_len, pathname, "%s", filename); return SQLITE_OK; } static void *vfsDlOpen(sqlite3_vfs *vfs, const char *filename) { (void)vfs; (void)filename; return 0; } static void vfsDlError(sqlite3_vfs *vfs, int nByte, char *zErrMsg) { (void)vfs; sqlite3_snprintf(nByte, zErrMsg, "Loadable extensions are not supported"); zErrMsg[nByte - 1] = '\0'; } static void (*vfsDlSym(sqlite3_vfs *vfs, void *pH, const char *z))(void) { (void)vfs; (void)pH; (void)z; return 0; } static void vfsDlClose(sqlite3_vfs *vfs, void *pHandle) { (void)vfs; (void)pHandle; return; } static int vfsRandomness(sqlite3_vfs *vfs, int nByte, char *zByte) { (void)vfs; (void)nByte; (void)zByte; /* TODO (is this needed?) */ return SQLITE_OK; } static int vfsSleep(sqlite3_vfs *vfs, int microseconds) { (void)vfs; /* TODO (is this needed?) */ return microseconds; } static int vfsCurrentTimeInt64(sqlite3_vfs *vfs, sqlite3_int64 *piNow) { static const sqlite3_int64 unixEpoch = 24405875 * (sqlite3_int64)8640000; struct timeval now; (void)vfs; gettimeofday(&now, 0); *piNow = unixEpoch + 1000 * (sqlite3_int64)now.tv_sec + now.tv_usec / 1000; return SQLITE_OK; } static int vfsCurrentTime(sqlite3_vfs *vfs, double *piNow) { // TODO: check if it's always safe to cast a double* to a // sqlite3_int64*. return vfsCurrentTimeInt64(vfs, (sqlite3_int64 *)piNow); } static int vfsGetLastError(sqlite3_vfs *vfs, int x, char *y) { struct vfs *v = (struct vfs *)(vfs->pAppData); int rc; (void)vfs; (void)x; (void)y; rc = v->error; return rc; } static int vfsInit(struct sqlite3_vfs *vfs, const char *name) { vfs->iVersion = 2; vfs->szOsFile = sizeof(struct vfsFile); vfs->mxPathname = VFS__MAX_PATHNAME; vfs->pNext = NULL; vfs->pAppData = vfsCreate(); if (vfs->pAppData == NULL) { return DQLITE_NOMEM; } vfs->xOpen = vfsOpen; vfs->xDelete = vfsDelete; vfs->xAccess = vfsAccess; vfs->xFullPathname = vfsFullPathname; vfs->xDlOpen = vfsDlOpen; vfs->xDlError = vfsDlError; vfs->xDlSym = vfsDlSym; vfs->xDlClose = vfsDlClose; vfs->xRandomness = vfsRandomness; vfs->xSleep = vfsSleep; vfs->xCurrentTime = vfsCurrentTime; vfs->xGetLastError = vfsGetLastError; vfs->xCurrentTimeInt64 = vfsCurrentTimeInt64; vfs->zName = name; return 0; } int VfsInit(struct sqlite3_vfs *vfs, const char *name) { tracef("vfs init"); return vfsInit(vfs, name); } void VfsClose(struct sqlite3_vfs *vfs) { tracef("vfs close"); struct vfs *v = vfs->pAppData; vfsDestroy(v); sqlite3_free(v); } static int vfsWalPoll(struct vfsWal *w, dqlite_vfs_frame **frames, unsigned *n) { struct vfsFrame *last; uint32_t commit; unsigned i; if (w->n_tx == 0) { *frames = NULL; *n = 0; return 0; } /* Check if the last frame in the transaction has the commit marker. */ last = w->tx[w->n_tx - 1]; commit = vfsFrameGetDatabaseSize(last); if (commit == 0) { *frames = NULL; *n = 0; return 0; } *frames = sqlite3_malloc64(sizeof **frames * w->n_tx); if (*frames == NULL) { return DQLITE_NOMEM; } *n = w->n_tx; for (i = 0; i < w->n_tx; i++) { dqlite_vfs_frame *frame = &(*frames)[i]; uint32_t page_number = vfsFrameGetPageNumber(w->tx[i]); frame->data = w->tx[i]->page; frame->page_number = page_number; /* Release the vfsFrame object, but not its buf attribute, since * responsibility for that memory has been transferred to the * caller. */ sqlite3_free(w->tx[i]); } w->n_tx = 0; return 0; } int VfsPoll(sqlite3_vfs *vfs, const char *filename, dqlite_vfs_frame **frames, unsigned *n) { tracef("vfs poll filename:%s", filename); struct vfs *v; struct vfsDatabase *database; struct vfsShm *shm; struct vfsWal *wal; int rv; v = (struct vfs *)(vfs->pAppData); database = vfsDatabaseLookup(v, filename); if (database == NULL) { tracef("not found"); return DQLITE_ERROR; } shm = &database->shm; wal = &database->wal; if (wal == NULL) { *frames = NULL; *n = 0; return 0; } rv = vfsWalPoll(wal, frames, n); if (rv != 0) { tracef("wal poll failed %d", rv); return rv; } /* If some frames have been written take the write lock. */ if (*n > 0) { rv = vfsShmLock(shm, 0, 1, SQLITE_SHM_EXCLUSIVE); if (rv != 0) { tracef("shm lock failed %d", rv); return rv; } vfsAmendWalIndexHeader(database); } return 0; } /* Return the salt-1 field stored in the WAL header.*/ static uint32_t vfsWalGetSalt1(struct vfsWal *w) { /* `hdr` field is pointer aligned, cast is safe */ return *(uint32_t *)__builtin_assume_aligned(&w->hdr[16], sizeof(uint32_t)); } /* Return the salt-2 field stored in the WAL header.*/ static uint32_t vfsWalGetSalt2(struct vfsWal *w) { /* `hdr` field is pointer aligned, cast is safe */ return *(uint32_t *)__builtin_assume_aligned(&w->hdr[20], sizeof(uint32_t)); } /* Return the checksum-1 field stored in the WAL header.*/ static uint32_t vfsWalGetChecksum1(struct vfsWal *w) { return vfsGet32(&w->hdr[24]); } /* Return the checksum-2 field stored in the WAL header.*/ static uint32_t vfsWalGetChecksum2(struct vfsWal *w) { return vfsGet32(&w->hdr[28]); } /* Append the given pages as new frames. */ static int vfsWalAppend(struct vfsWal *w, unsigned database_n_pages, unsigned n, unsigned long *page_numbers, uint8_t *pages) { struct vfsFrame **frames; /* New frames array. */ uint32_t page_size; uint32_t database_size; unsigned i; unsigned j; uint32_t salt[2]; uint32_t checksum[2]; /* No pending transactions. */ assert(w->n_tx == 0); page_size = vfsWalGetPageSize(w); assert(page_size > 0); /* Get the salt from the WAL header. */ salt[0] = vfsWalGetSalt1(w); salt[1] = vfsWalGetSalt2(w); /* If there are currently no frames in the WAL, the starting database * size will be equal to the current number of pages in the main * database, and the starting checksum should be set to the one stored * in the WAL header. Otherwise, the starting database size and checksum * will be the ones stored in the last frame of the WAL. */ if (w->n_frames == 0) { database_size = (uint32_t)database_n_pages; checksum[0] = vfsWalGetChecksum1(w); checksum[1] = vfsWalGetChecksum2(w); } else { struct vfsFrame *frame = w->frames[w->n_frames - 1]; checksum[0] = vfsFrameGetChecksum1(frame); checksum[1] = vfsFrameGetChecksum2(frame); database_size = vfsFrameGetDatabaseSize(frame); } frames = sqlite3_realloc64(w->frames, sizeof *frames * (w->n_frames + n)); if (frames == NULL) { goto oom; } w->frames = frames; for (i = 0; i < n; i++) { struct vfsFrame *frame = vfsFrameCreate(page_size); uint32_t page_number = (uint32_t)page_numbers[i]; uint32_t commit = 0; uint8_t *page = &pages[i * page_size]; if (frame == NULL) { goto oom_after_frames_alloc; } if (page_number > database_size) { database_size = page_number; } /* For commit records, the size of the database file in pages * after the commit. For all other records, zero. */ if (i == n - 1) { commit = database_size; } vfsFrameFill(frame, page_number, commit, salt, checksum, page, page_size); frames[w->n_frames + i] = frame; } w->n_frames += n; return 0; oom_after_frames_alloc: for (j = 0; j < i; j++) { vfsFrameDestroy(frames[w->n_frames + j]); } oom: return DQLITE_NOMEM; } /* Write the header of a brand new WAL file image. */ static void vfsWalStartHeader(struct vfsWal *w, uint32_t page_size) { assert(page_size > 0); uint32_t checksum[2] = {0, 0}; /* SQLite calculates checksums for the WAL header and frames either * using little endian or big endian byte order when adding up 32-bit * words. The byte order that should be used is recorded in the WAL file * header by setting the least significant bit of the magic value stored * in the first 32 bits. This allows portability of the WAL file across * hosts with different native byte order. * * When creating a brand new WAL file, SQLite will set the byte order * bit to match the host's native byte order, so checksums are a bit * more efficient. * * In Dqlite the WAL file image is always generated at run time on the * host, so we can always use the native byte order. */ vfsPut32(VFS__WAL_MAGIC | VFS__BIGENDIAN, &w->hdr[0]); vfsPut32(VFS__WAL_VERSION, &w->hdr[4]); vfsPut32(page_size, &w->hdr[8]); vfsPut32(0, &w->hdr[12]); sqlite3_randomness(8, &w->hdr[16]); vfsChecksum(w->hdr, 24, checksum, checksum); vfsPut32(checksum[0], w->hdr + 24); vfsPut32(checksum[1], w->hdr + 28); } /* Invalidate the WAL index header, forcing the next connection that tries to * start a read transaction to rebuild the WAL index by reading the WAL. * * No read or write lock must be currently held. */ static void vfsInvalidateWalIndexHeader(struct vfsDatabase *d) { struct vfsShm *shm = &d->shm; uint8_t *header = shm->regions[0]; unsigned i; for (i = 0; i < SQLITE_SHM_NLOCK; i++) { assert(shm->shared[i] == 0); assert(shm->exclusive[i] == 0); } /* The walIndexTryHdr function in sqlite/wal.c (which is indirectly * called by sqlite3WalBeginReadTransaction), compares the first and * second copy of the WAL index header to see if it is valid. Changing * the first byte of each of the two copies is enough to make the check * fail. */ header[0] = 1; header[VFS__WAL_INDEX_HEADER_SIZE] = 0; } int VfsApply(sqlite3_vfs *vfs, const char *filename, unsigned n, unsigned long *page_numbers, void *frames) { tracef("vfs apply filename %s n %u", filename, n); struct vfs *v; struct vfsDatabase *database; struct vfsWal *wal; struct vfsShm *shm; int rv; v = (struct vfs *)(vfs->pAppData); database = vfsDatabaseLookup(v, filename); assert(database != NULL); wal = &database->wal; shm = &database->shm; /* If there's no page size set in the WAL header, it must mean that WAL * file was never written. In that case we need to initialize the WAL * header. */ if (vfsWalGetPageSize(wal) == 0) { vfsWalStartHeader(wal, vfsDatabaseGetPageSize(database)); } rv = vfsWalAppend(wal, database->n_pages, n, page_numbers, frames); if (rv != 0) { tracef("wal append failed rv:%d n_pages:%u n:%u", rv, database->n_pages, n); return rv; } /* If a write lock is held it means that this is the VFS that orginated * this commit and on which dqlite_vfs_poll() was called. In that case * we release the lock and update the WAL index. * * Otherwise, if the WAL index header is mapped it means that this VFS * has one or more open connections even if it's not the one that * originated the transaction (this can happen for example when applying * a Raft barrier and replaying the Raft log in order to serve a request * of a newly connected client). */ if (shm->exclusive[0] == 1) { shm->exclusive[0] = 0; vfsAmendWalIndexHeader(database); } else { if (shm->n_regions > 0) { vfsInvalidateWalIndexHeader(database); } } return 0; } int VfsAbort(sqlite3_vfs *vfs, const char *filename) { tracef("vfs abort filename %s", filename); struct vfs *v; struct vfsDatabase *database; int rv; v = (struct vfs *)(vfs->pAppData); database = vfsDatabaseLookup(v, filename); rv = vfsShmUnlock(&database->shm, 0, 1, SQLITE_SHM_EXCLUSIVE); if (rv != 0) { tracef("shm unlock failed %d", rv); return rv; } return 0; } /* Extract the number of pages field from the database header. */ static uint32_t vfsDatabaseGetNumberOfPages(struct vfsDatabase *d) { uint8_t *page; assert(d->n_pages > 0); page = d->pages[0]; /* The page size is stored in the 16th and 17th bytes of the first * database page (big-endian) */ return vfsGet32(&page[28]); } static void vfsDatabaseSnapshot(struct vfsDatabase *d, uint8_t **cursor) { uint32_t page_size; unsigned i; page_size = vfsDatabaseGetPageSize(d); assert(page_size > 0); assert(d->n_pages == vfsDatabaseGetNumberOfPages(d)); for (i = 0; i < d->n_pages; i++) { memcpy(*cursor, d->pages[i], page_size); *cursor += page_size; } } static void vfsWalSnapshot(struct vfsWal *w, uint8_t **cursor) { uint32_t page_size; unsigned i; if (w->n_frames == 0) { return; } memcpy(*cursor, w->hdr, VFS__WAL_HEADER_SIZE); *cursor += VFS__WAL_HEADER_SIZE; page_size = vfsWalGetPageSize(w); assert(page_size > 0); for (i = 0; i < w->n_frames; i++) { struct vfsFrame *frame = w->frames[i]; memcpy(*cursor, frame->header, FORMAT__WAL_FRAME_HDR_SIZE); *cursor += FORMAT__WAL_FRAME_HDR_SIZE; memcpy(*cursor, frame->page, page_size); *cursor += page_size; } } int VfsSnapshot(sqlite3_vfs *vfs, const char *filename, void **data, size_t *n) { tracef("vfs snapshot filename %s", filename); struct vfs *v; struct vfsDatabase *database; struct vfsWal *wal; uint8_t *cursor; v = (struct vfs *)(vfs->pAppData); database = vfsDatabaseLookup(v, filename); if (database == NULL) { tracef("not found"); *data = NULL; *n = 0; return 0; } if (database->n_pages != vfsDatabaseGetNumberOfPages(database)) { tracef("corrupt"); return SQLITE_CORRUPT; } wal = &database->wal; *n = vfsDatabaseFileSize(database) + vfsWalFileSize(wal); /* TODO: we should fix the tests and use sqlite3_malloc instead. */ *data = raft_malloc(*n); if (*data == NULL) { tracef("malloc"); return DQLITE_NOMEM; } cursor = *data; vfsDatabaseSnapshot(database, &cursor); vfsWalSnapshot(wal, &cursor); return 0; } static int vfsDatabaseRestore(struct vfsDatabase *d, const uint8_t *data, size_t n) { uint32_t page_size = vfsParsePageSize(vfsGet16(&data[16])); unsigned n_pages; void **pages; unsigned i; int rv; assert(page_size > 0); /* Check that the page size of the snapshot is consistent with what we * have here. */ assert(vfsDatabaseGetPageSize(d) == page_size); n_pages = (unsigned)vfsGet32(&data[28]); if (n < n_pages * page_size) { return DQLITE_ERROR; } pages = sqlite3_malloc64(sizeof *pages * n_pages); if (pages == NULL) { goto oom; } for (i = 0; i < n_pages; i++) { void *page = sqlite3_malloc64(page_size); if (page == NULL) { unsigned j; for (j = 0; j < i; j++) { sqlite3_free(pages[j]); } goto oom_after_pages_alloc; } pages[i] = page; memcpy(page, &data[i * page_size], page_size); } /* Truncate any existing content. */ rv = vfsDatabaseTruncate(d, 0); assert(rv == 0); d->pages = pages; d->n_pages = n_pages; return 0; oom_after_pages_alloc: sqlite3_free(pages); oom: return DQLITE_NOMEM; } static int vfsWalRestore(struct vfsWal *w, const uint8_t *data, size_t n, uint32_t page_size) { struct vfsFrame **frames; unsigned n_frames; unsigned i; int rv; if (n == 0) { return 0; } assert(w->n_tx == 0); assert(n > VFS__WAL_HEADER_SIZE); assert(((n - VFS__WAL_HEADER_SIZE) % vfsFrameSize(page_size)) == 0); n_frames = (unsigned)((n - VFS__WAL_HEADER_SIZE) / vfsFrameSize(page_size)); frames = sqlite3_malloc64(sizeof *frames * n_frames); if (frames == NULL) { goto oom; } for (i = 0; i < n_frames; i++) { struct vfsFrame *frame = vfsFrameCreate(page_size); const uint8_t *p; if (frame == NULL) { unsigned j; for (j = 0; j < i; j++) { vfsFrameDestroy(frames[j]); } goto oom_after_frames_alloc; } frames[i] = frame; p = &data[VFS__WAL_HEADER_SIZE + i * vfsFrameSize(page_size)]; memcpy(frame->header, p, VFS__FRAME_HEADER_SIZE); memcpy(frame->page, p + VFS__FRAME_HEADER_SIZE, page_size); } memcpy(w->hdr, data, VFS__WAL_HEADER_SIZE); rv = vfsWalTruncate(w, 0); assert(rv == 0); w->frames = frames; w->n_frames = n_frames; return 0; oom_after_frames_alloc: sqlite3_free(frames); oom: return DQLITE_NOMEM; } int VfsRestore(sqlite3_vfs *vfs, const char *filename, const void *data, size_t n) { tracef("vfs restore filename %s size %zd", filename, n); struct vfs *v; struct vfsDatabase *database; struct vfsWal *wal; uint32_t page_size; size_t offset; int rv; v = (struct vfs *)(vfs->pAppData); database = vfsDatabaseLookup(v, filename); assert(database != NULL); wal = &database->wal; /* Truncate any existing content. */ rv = vfsWalTruncate(wal, 0); if (rv != 0) { tracef("wal truncate failed %d", rv); return rv; } /* Restore the content of the main database and of the WAL. */ rv = vfsDatabaseRestore(database, data, n); if (rv != 0) { tracef("database restore failed %d", rv); return rv; } page_size = vfsDatabaseGetPageSize(database); offset = database->n_pages * page_size; rv = vfsWalRestore(wal, data + offset, n - offset, page_size); if (rv != 0) { tracef("wal restore failed %d", rv); return rv; } return 0; } dqlite-1.9.1/src/vfs.h000066400000000000000000000023141416342771300145520ustar00rootroot00000000000000#ifndef VFS_H_ #define VFS_H_ #include #include "config.h" /* Initialize the given SQLite VFS interface with dqlite's custom * implementation. */ int VfsInit(struct sqlite3_vfs *vfs, const char *name); /* Release all memory associated with the given dqlite in-memory VFS * implementation. * * This function also automatically unregister the implementation from the * SQLite global registry. */ void VfsClose(struct sqlite3_vfs *vfs); /* Check if the last sqlite3_step() call triggered a write transaction, and * return its content if so. */ int VfsPoll(sqlite3_vfs *vfs, const char *database, dqlite_vfs_frame **frames, unsigned *n); /* Append the given frames to the WAL. */ int VfsApply(sqlite3_vfs *vfs, const char *filename, unsigned n, unsigned long *page_numbers, void *frames); /* Cancel a pending transaction. */ int VfsAbort(sqlite3_vfs *vfs, const char *filename); /* Make a full snapshot of a database. */ int VfsSnapshot(sqlite3_vfs *vfs, const char *filename, void **data, size_t *n); /* Restore a database snapshot. */ int VfsRestore(sqlite3_vfs *vfs, const char *filename, const void *data, size_t n); #endif /* VFS_H_ */ dqlite-1.9.1/test/000077500000000000000000000000001416342771300137735ustar00rootroot00000000000000dqlite-1.9.1/test/integration/000077500000000000000000000000001416342771300163165ustar00rootroot00000000000000dqlite-1.9.1/test/integration/main.c000066400000000000000000000000621416342771300174040ustar00rootroot00000000000000#include "../lib/runner.h" RUNNER("integration") dqlite-1.9.1/test/integration/test_client.c000066400000000000000000000075421416342771300210070ustar00rootroot00000000000000#include "../lib/heap.h" #include "../lib/runner.h" #include "../lib/server.h" #include "../lib/sqlite.h" /****************************************************************************** * * Helper macros. * ******************************************************************************/ /* Send the initial client handshake. */ #define HANDSHAKE \ { \ int rv_; \ rv_ = clientSendHandshake(f->client); \ munit_assert_int(rv_, ==, 0); \ } /* Open a test database. */ #define OPEN \ { \ int rv_; \ rv_ = clientSendOpen(f->client, "test"); \ munit_assert_int(rv_, ==, 0); \ rv_ = clientRecvDb(f->client); \ munit_assert_int(rv_, ==, 0); \ } /* Prepare a statement. */ #define PREPARE(SQL, STMT_ID) \ { \ int rv_; \ rv_ = clientSendPrepare(f->client, SQL); \ munit_assert_int(rv_, ==, 0); \ rv_ = clientRecvStmt(f->client, STMT_ID); \ munit_assert_int(rv_, ==, 0); \ } /* Execute a statement. */ #define EXEC(STMT_ID, LAST_INSERT_ID, ROWS_AFFECTED) \ { \ int rv_; \ rv_ = clientSendExec(f->client, STMT_ID); \ munit_assert_int(rv_, ==, 0); \ rv_ = clientRecvResult(f->client, LAST_INSERT_ID, \ ROWS_AFFECTED); \ munit_assert_int(rv_, ==, 0); \ } /* Perform a query. */ #define QUERY(STMT_ID, ROWS) \ { \ int rv_; \ rv_ = clientSendQuery(f->client, STMT_ID); \ munit_assert_int(rv_, ==, 0); \ rv_ = clientRecvRows(f->client, ROWS); \ munit_assert_int(rv_, ==, 0); \ } /****************************************************************************** * * Handle client requests * ******************************************************************************/ SUITE(client); struct fixture { struct test_server server; struct client *client; }; static void *setUp(const MunitParameter params[], void *user_data) { struct fixture *f = munit_malloc(sizeof *f); (void)user_data; test_heap_setup(params, user_data); test_sqlite_setup(params); test_server_setup(&f->server, 1, params); test_server_start(&f->server); f->client = test_server_client(&f->server); HANDSHAKE; OPEN; return f; } static void tearDown(void *data) { struct fixture *f = data; test_server_tear_down(&f->server); test_sqlite_tear_down(); test_heap_tear_down(data); free(f); } TEST(client, exec, setUp, tearDown, 0, NULL) { struct fixture *f = data; unsigned stmt_id; unsigned last_insert_id; unsigned rows_affected; (void)params; PREPARE("CREATE TABLE test (n INT)", &stmt_id); EXEC(stmt_id, &last_insert_id, &rows_affected); return MUNIT_OK; } TEST(client, query, setUp, tearDown, 0, NULL) { struct fixture *f = data; unsigned stmt_id; unsigned last_insert_id; unsigned rows_affected; unsigned i; struct rows rows; (void)params; PREPARE("CREATE TABLE test (n INT)", &stmt_id); EXEC(stmt_id, &last_insert_id, &rows_affected); PREPARE("BEGIN", &stmt_id); EXEC(stmt_id, &last_insert_id, &rows_affected); PREPARE("INSERT INTO test (n) VALUES(123)", &stmt_id); for (i = 0; i < 256; i++) { EXEC(stmt_id, &last_insert_id, &rows_affected); } PREPARE("COMMIT", &stmt_id); EXEC(stmt_id, &last_insert_id, &rows_affected); PREPARE("SELECT n FROM test", &stmt_id); QUERY(stmt_id, &rows); clientCloseRows(&rows); return MUNIT_OK; } dqlite-1.9.1/test/integration/test_membership.c000066400000000000000000000130701416342771300216550ustar00rootroot00000000000000#include "../../src/client.h" #include "../../src/server.h" #include "../lib/endpoint.h" #include "../lib/fs.h" #include "../lib/heap.h" #include "../lib/runner.h" #include "../lib/server.h" #include "../lib/sqlite.h" /****************************************************************************** * * Fixture * ******************************************************************************/ #define N_SERVERS 3 #define FIXTURE \ struct test_server servers[N_SERVERS]; \ struct client *client #define SETUP \ unsigned i_; \ test_heap_setup(params, user_data); \ test_sqlite_setup(params); \ for (i_ = 0; i_ < N_SERVERS; i_++) { \ struct test_server *server = &f->servers[i_]; \ test_server_setup(server, i_ + 1, params); \ } \ test_server_network(f->servers, N_SERVERS); \ for (i_ = 0; i_ < N_SERVERS; i_++) { \ struct test_server *server = &f->servers[i_]; \ test_server_start(server); \ } \ SELECT(1) #define TEAR_DOWN \ unsigned i_; \ for (i_ = 0; i_ < N_SERVERS; i_++) { \ test_server_tear_down(&f->servers[i_]); \ } \ test_sqlite_tear_down(); \ test_heap_tear_down(data) /****************************************************************************** * * Helper macros. * ******************************************************************************/ /* Use the client connected to the server with the given ID. */ #define SELECT(ID) f->client = test_server_client(&f->servers[ID - 1]) /* Send the initial client handshake. */ #define HANDSHAKE \ { \ int rv_; \ rv_ = clientSendHandshake(f->client); \ munit_assert_int(rv_, ==, 0); \ } /* Send an add request. */ #define ADD(ID, ADDRESS) \ { \ int rv_; \ rv_ = clientSendAdd(f->client, ID, ADDRESS); \ munit_assert_int(rv_, ==, 0); \ rv_ = clientRecvEmpty(f->client); \ munit_assert_int(rv_, ==, 0); \ } /* Send an assign role request. */ #define ASSIGN(ID, ROLE) \ { \ int rv_; \ rv_ = clientSendAssign(f->client, ID, ROLE); \ munit_assert_int(rv_, ==, 0); \ rv_ = clientRecvEmpty(f->client); \ munit_assert_int(rv_, ==, 0); \ } /* Send a remove request. */ #define REMOVE(ID) \ { \ int rv_; \ rv_ = clientSendRemove(f->client, ID); \ munit_assert_int(rv_, ==, 0); \ rv_ = clientRecvEmpty(f->client); \ munit_assert_int(rv_, ==, 0); \ } /* Open a test database. */ #define OPEN \ { \ int rv_; \ rv_ = clientSendOpen(f->client, "test"); \ munit_assert_int(rv_, ==, 0); \ rv_ = clientRecvDb(f->client); \ munit_assert_int(rv_, ==, 0); \ } /* Prepare a statement. */ #define PREPARE(SQL, STMT_ID) \ { \ int rv_; \ rv_ = clientSendPrepare(f->client, SQL); \ munit_assert_int(rv_, ==, 0); \ rv_ = clientRecvStmt(f->client, STMT_ID); \ munit_assert_int(rv_, ==, 0); \ } /* Execute a statement. */ #define EXEC(STMT_ID, LAST_INSERT_ID, ROWS_AFFECTED) \ { \ int rv_; \ rv_ = clientSendExec(f->client, STMT_ID); \ munit_assert_int(rv_, ==, 0); \ rv_ = clientRecvResult(f->client, LAST_INSERT_ID, \ ROWS_AFFECTED); \ munit_assert_int(rv_, ==, 0); \ } /****************************************************************************** * * join * ******************************************************************************/ SUITE(membership) struct fixture { FIXTURE; }; static void *setUp(const MunitParameter params[], void *user_data) { struct fixture *f = munit_malloc(sizeof *f); SETUP; return f; } static void tearDown(void *data) { struct fixture *f = data; TEAR_DOWN; free(f); } TEST(membership, join, setUp, tearDown, 0, NULL) { struct fixture *f = data; unsigned id = 2; const char *address = "@2"; unsigned stmt_id; unsigned last_insert_id; unsigned rows_affected; HANDSHAKE; ADD(id, address); ASSIGN(id, 1 /* voter */); OPEN; PREPARE("CREATE TABLE test (n INT)", &stmt_id); EXEC(stmt_id, &last_insert_id, &rows_affected); PREPARE("INSERT INTO test(n) VALUES(1)", &stmt_id); EXEC(stmt_id, &last_insert_id, &rows_affected); /* The table is visible from the new node */ SELECT(2); HANDSHAKE; OPEN; PREPARE("SELECT * FROM test", &stmt_id); /* TODO: fix the standalone test for remove */ SELECT(1); REMOVE(id); return MUNIT_OK; } dqlite-1.9.1/test/integration/test_node.c000066400000000000000000000243101416342771300204460ustar00rootroot00000000000000#include "../lib/heap.h" #include "../lib/runner.h" #include "../lib/fs.h" #include "../lib/sqlite.h" #include "../../include/dqlite.h" #include "../../src/protocol.h" #include "../../src/utils.h" /****************************************************************************** * * Fixture * ******************************************************************************/ struct fixture { char *dir; /* Data directory. */ dqlite_node *node; /* Node instance. */ }; static void *setUp(const MunitParameter params[], void *user_data) { struct fixture *f = munit_malloc(sizeof *f); int rv; test_heap_setup(params, user_data); test_sqlite_setup(params); f->dir = test_dir_setup(); rv = dqlite_node_create(1, "1", f->dir, &f->node); munit_assert_int(rv, ==, 0); rv = dqlite_node_set_bind_address(f->node, "@123"); munit_assert_int(rv, ==, 0); return f; } static void *setUpInet(const MunitParameter params[], void *user_data) { struct fixture *f = munit_malloc(sizeof *f); int rv; test_heap_setup(params, user_data); test_sqlite_setup(params); f->dir = test_dir_setup(); rv = dqlite_node_create(1, "1", f->dir, &f->node); munit_assert_int(rv, ==, 0); rv = dqlite_node_set_bind_address(f->node, "127.0.0.1:9001"); munit_assert_int(rv, ==, 0); return f; } /* Tests if node starts/stops successfully and also performs some memory cleanup */ static void startStopNode(struct fixture *f) { munit_assert_int(dqlite_node_start(f->node), ==, 0); munit_assert_int(dqlite_node_stop(f->node), ==, 0); } /* Recovery only works if a node has been started regularly for a first time. */ static void *setUpForRecovery(const MunitParameter params[], void *user_data) { int rv; struct fixture *f = setUp(params, user_data); startStopNode(f); dqlite_node_destroy(f->node); rv = dqlite_node_create(1, "1", f->dir, &f->node); munit_assert_int(rv, ==, 0); rv = dqlite_node_set_bind_address(f->node, "@123"); munit_assert_int(rv, ==, 0); return f; } static void tearDown(void *data) { struct fixture *f = data; dqlite_node_destroy(f->node); test_dir_tear_down(f->dir); test_sqlite_tear_down(); test_heap_tear_down(data); free(f); } SUITE(node); /****************************************************************************** * * dqlite_node_start * ******************************************************************************/ TEST(node, start, setUp, tearDown, 0, NULL) { struct fixture *f = data; int rv; rv = dqlite_node_start(f->node); munit_assert_int(rv, ==, 0); rv = dqlite_node_stop(f->node); munit_assert_int(rv, ==, 0); return MUNIT_OK; } TEST(node, startInet, setUpInet, tearDown, 0, NULL) { struct fixture *f = data; int rv; rv = dqlite_node_start(f->node); munit_assert_int(rv, ==, 0); rv = dqlite_node_stop(f->node); munit_assert_int(rv, ==, 0); return MUNIT_OK; } TEST(node, snapshotParams, setUp, tearDown, 0, NULL) { struct fixture *f = data; int rv; rv = dqlite_node_set_snapshot_params(f->node, 2048, 2048); munit_assert_int(rv, ==, 0); startStopNode(f); return MUNIT_OK; } TEST(node, snapshotParamsRunning, setUp, tearDown, 0, NULL) { struct fixture *f = data; int rv; rv = dqlite_node_start(f->node); munit_assert_int(rv, ==, 0); rv = dqlite_node_set_snapshot_params(f->node, 2048, 2048); munit_assert_int(rv, !=, 0); rv = dqlite_node_stop(f->node); munit_assert_int(rv, ==, 0); return MUNIT_OK; } TEST(node, snapshotParamsTrailingTooSmall, setUp, tearDown, 0, NULL) { struct fixture *f = data; int rv; rv = dqlite_node_set_snapshot_params(f->node, 512, 512); munit_assert_int(rv, !=, 0); startStopNode(f); return MUNIT_OK; } TEST(node, snapshotParamsThresholdLargerThanTrailing, setUp, tearDown, 0, NULL) { struct fixture *f = data; int rv; rv = dqlite_node_set_snapshot_params(f->node, 2049, 2048); munit_assert_int(rv, !=, 0); startStopNode(f); return MUNIT_OK; } TEST(node, networkLatency, setUp, tearDown, 0, NULL) { struct fixture *f = data; int rv; rv = dqlite_node_set_network_latency(f->node, 3600000000000ULL); munit_assert_int(rv, ==, 0); startStopNode(f); return MUNIT_OK; } TEST(node, networkLatencyRunning, setUp, tearDown, 0, NULL) { struct fixture *f = data; int rv; rv = dqlite_node_start(f->node); munit_assert_int(rv, ==, 0); rv = dqlite_node_set_network_latency(f->node, 3600000000000ULL); munit_assert_int(rv, ==, DQLITE_MISUSE); rv = dqlite_node_stop(f->node); munit_assert_int(rv, ==, 0); return MUNIT_OK; } TEST(node, networkLatencyTooLarge, setUp, tearDown, 0, NULL) { struct fixture *f = data; int rv; rv = dqlite_node_set_network_latency(f->node, 3600000000000ULL + 1ULL); munit_assert_int(rv, ==, DQLITE_MISUSE); startStopNode(f); return MUNIT_OK; } TEST(node, networkLatencyMs, setUp, tearDown, 0, NULL) { struct fixture *f = data; int rv; rv = dqlite_node_set_network_latency_ms(f->node, 5); munit_assert_int(rv, ==, 0); rv = dqlite_node_set_network_latency_ms(f->node, (3600U * 1000U)); munit_assert_int(rv, ==, 0); startStopNode(f); return MUNIT_OK; } TEST(node, networkLatencyMsRunning, setUp, tearDown, 0, NULL) { struct fixture *f = data; int rv; rv = dqlite_node_start(f->node); munit_assert_int(rv, ==, 0); rv = dqlite_node_set_network_latency_ms(f->node, 2); munit_assert_int(rv, ==, DQLITE_MISUSE); rv = dqlite_node_stop(f->node); munit_assert_int(rv, ==, 0); return MUNIT_OK; } TEST(node, networkLatencyMsTooSmall, setUp, tearDown, 0, NULL) { struct fixture *f = data; int rv; rv = dqlite_node_set_network_latency_ms(f->node, 0); munit_assert_int(rv, ==, DQLITE_MISUSE); startStopNode(f); return MUNIT_OK; } TEST(node, networkLatencyMsTooLarge, setUp, tearDown, 0, NULL) { struct fixture *f = data; int rv; rv = dqlite_node_set_network_latency_ms(f->node, (3600U * 1000U) + 1); munit_assert_int(rv, ==, DQLITE_MISUSE); startStopNode(f); return MUNIT_OK; } /****************************************************************************** * * dqlite_node_recover * ******************************************************************************/ TEST(node, recover, setUpForRecovery, tearDown, 0, NULL) { struct fixture *f = data; int rv; /* Setup the infos structs */ static struct dqlite_node_info infos[2] = {0}; infos[0].id = 1; infos[0].address = "1"; infos[1].id = 2; infos[1].address = "2"; rv = dqlite_node_recover(f->node, infos, 2); munit_assert_int(rv, ==, 0); startStopNode(f); return MUNIT_OK; } TEST(node, recoverExt, setUpForRecovery, tearDown, 0, NULL) { struct fixture *f = data; int rv; /* Setup the infos structs */ static struct dqlite_node_info_ext infos[2] = {0}; infos[0].size = sizeof(*infos); infos[0].id = dqlite_generate_node_id("1"); infos[0].address = PTR_TO_UINT64("1"); infos[0].dqlite_role = DQLITE_VOTER; infos[1].size = sizeof(*infos); infos[1].id = dqlite_generate_node_id("2");; infos[1].address = PTR_TO_UINT64("2"); infos[1].dqlite_role = DQLITE_SPARE; rv = dqlite_node_recover_ext(f->node, infos, 2); munit_assert_int(rv, ==, 0); startStopNode(f); return MUNIT_OK; } TEST(node, recoverExtUnaligned, setUpForRecovery, tearDown, 0, NULL) { struct fixture *f = data; int rv; /* Setup the infos structs */ static struct dqlite_node_info_ext infos[1] = {0}; infos[0].size = sizeof(*infos) + 1; /* Unaligned */ infos[0].id = 1; infos[0].address = PTR_TO_UINT64("1"); infos[0].dqlite_role = DQLITE_VOTER; rv = dqlite_node_recover_ext(f->node, infos, 1); munit_assert_int(rv, ==, DQLITE_MISUSE); startStopNode(f); return MUNIT_OK; } TEST(node, recoverExtTooSmall, setUpForRecovery, tearDown, 0, NULL) { struct fixture *f = data; int rv; /* Setup the infos structs */ static struct dqlite_node_info_ext infos[1] = {0}; infos[0].size = DQLITE_NODE_INFO_EXT_SZ_ORIG - 1; infos[0].id = 1; infos[0].address = PTR_TO_UINT64("1"); infos[0].dqlite_role = DQLITE_VOTER; rv = dqlite_node_recover_ext(f->node, infos, 1); munit_assert_int(rv, ==, DQLITE_MISUSE); startStopNode(f); return MUNIT_OK; } struct dqlite_node_info_ext_new { struct dqlite_node_info_ext orig; uint64_t new1; uint64_t new2; }; TEST(node, recoverExtNewFields, setUpForRecovery, tearDown, 0, NULL) { struct fixture *f = data; int rv; /* Setup the infos structs */ static struct dqlite_node_info_ext_new infos[1] = {0}; infos[0].orig.size = sizeof(*infos); infos[0].orig.id = 1; infos[0].orig.address = PTR_TO_UINT64("1"); infos[0].orig.dqlite_role = DQLITE_VOTER; infos[0].new1 = 0; infos[0].new2 = 0; rv = dqlite_node_recover_ext(f->node, (struct dqlite_node_info_ext*) infos, 1); munit_assert_int(rv, ==, 0); startStopNode(f); return MUNIT_OK; } TEST(node, recoverExtNewFieldsNotZero, setUpForRecovery, tearDown, 0, NULL) { struct fixture *f = data; int rv; /* Setup the infos structs */ static struct dqlite_node_info_ext_new infos[1] = {0}; infos[0].orig.size = sizeof(*infos); infos[0].orig.id = 1; infos[0].orig.address = PTR_TO_UINT64("1"); infos[0].orig.dqlite_role = DQLITE_VOTER; infos[0].new1 = 0; infos[0].new2 = 1; /* This will cause a failure */ rv = dqlite_node_recover_ext(f->node, (struct dqlite_node_info_ext*) infos, 1); munit_assert_int(rv, ==, DQLITE_MISUSE); startStopNode(f); return MUNIT_OK; } dqlite-1.9.1/test/integration/test_vfs.c000066400000000000000000000732011416342771300203220ustar00rootroot00000000000000#include #include #include "../lib/heap.h" #include "../lib/runner.h" #include "../lib/sqlite.h" #include "../../include/dqlite.h" SUITE(vfs); #define N_VFS 2 struct fixture { struct sqlite3_vfs vfs[N_VFS]; /* A "cluster" of VFS objects. */ char names[8][N_VFS]; /* Registration names */ }; static void *setUp(const MunitParameter params[], void *user_data) { struct fixture *f = munit_malloc(sizeof *f); unsigned i; int rv; SETUP_HEAP; SETUP_SQLITE; for (i = 0; i < N_VFS; i++) { sprintf(f->names[i], "%u", i + 1); rv = dqlite_vfs_init(&f->vfs[i], f->names[i]); munit_assert_int(rv, ==, 0); rv = sqlite3_vfs_register(&f->vfs[i], 0); munit_assert_int(rv, ==, 0); } return f; } static void tearDown(void *data) { struct fixture *f = data; unsigned i; int rv; for (i = 0; i < N_VFS; i++) { rv = sqlite3_vfs_unregister(&f->vfs[i]); munit_assert_int(rv, ==, 0); dqlite_vfs_close(&f->vfs[i]); } TEAR_DOWN_SQLITE; TEAR_DOWN_HEAP; free(f); } extern unsigned dq_sqlite_pending_byte; static void tearDownRestorePendingByte(void *data) { sqlite3_test_control(SQLITE_TESTCTRL_PENDING_BYTE, 0x40000000); dq_sqlite_pending_byte = 0x40000000; tearDown(data); } #define PAGE_SIZE 512 #define PRAGMA(DB, COMMAND) \ _rv = sqlite3_exec(DB, "PRAGMA " COMMAND, NULL, NULL, NULL); \ if (_rv != SQLITE_OK) { \ munit_errorf("PRAGMA " COMMAND ": %s (%d)", \ sqlite3_errmsg(DB), _rv); \ } /* Open a new database connection on the given VFS. */ #define OPEN(VFS, DB) \ do { \ int _flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE; \ int _rv; \ _rv = sqlite3_open_v2("test.db", &DB, _flags, VFS); \ munit_assert_int(_rv, ==, SQLITE_OK); \ _rv = sqlite3_extended_result_codes(DB, 1); \ munit_assert_int(_rv, ==, SQLITE_OK); \ PRAGMA(DB, "page_size=512"); \ PRAGMA(DB, "synchronous=OFF"); \ PRAGMA(DB, "journal_mode=WAL"); \ PRAGMA(DB, "cache_size=1"); \ _rv = sqlite3_db_config(DB, SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE, \ 1, NULL); \ munit_assert_int(_rv, ==, SQLITE_OK); \ } while (0) /* Close a database connection. */ #define CLOSE(DB) \ do { \ int _rv; \ _rv = sqlite3_close(DB); \ munit_assert_int(_rv, ==, SQLITE_OK); \ } while (0) /* Prepare a statement. */ #define PREPARE(DB, STMT, SQL) \ do { \ int _rv; \ _rv = sqlite3_prepare_v2(DB, SQL, -1, &STMT, NULL); \ if (_rv != SQLITE_OK) { \ munit_errorf("prepare '%s': %s (%d)", SQL, \ sqlite3_errmsg(DB), _rv); \ } \ } while (0) /* Reset a statement. */ #define RESET(STMT, RV) \ do { \ int _rv; \ _rv = sqlite3_reset(STMT); \ munit_assert_int(_rv, ==, RV); \ } while (0) /* Finalize a statement. */ #define FINALIZE(STMT) \ do { \ int _rv; \ _rv = sqlite3_finalize(STMT); \ munit_assert_int(_rv, ==, SQLITE_OK); \ } while (0) /* Shortcut for PREPARE, STEP, FINALIZE. */ #define EXEC(DB, SQL) \ do { \ sqlite3_stmt *_stmt; \ PREPARE(DB, _stmt, SQL); \ STEP(_stmt, SQLITE_DONE); \ FINALIZE(_stmt); \ } while (0) /* Step through a statement and assert that the given value is returned. */ #define STEP(STMT, RV) \ do { \ int _rv; \ _rv = sqlite3_step(STMT); \ if (_rv != RV) { \ munit_errorf("step: %s (%d)", \ sqlite3_errmsg(sqlite3_db_handle(STMT)), \ _rv); \ } \ } while (0) /* Hold WAL replication information about a single transaction. */ struct tx { unsigned n; unsigned long *page_numbers; void *frames; }; /* Poll the given VFS object and serialize the transaction data into the given * tx object. */ #define POLL(VFS, TX) \ do { \ sqlite3_vfs *vfs = sqlite3_vfs_find(VFS); \ dqlite_vfs_frame *_frames; \ unsigned _i; \ int _rv; \ memset(&TX, 0, sizeof TX); \ _rv = dqlite_vfs_poll(vfs, "test.db", &_frames, &TX.n); \ munit_assert_int(_rv, ==, 0); \ if (_frames != NULL) { \ TX.page_numbers = \ munit_malloc(sizeof *TX.page_numbers * TX.n); \ TX.frames = munit_malloc(PAGE_SIZE * TX.n); \ for (_i = 0; _i < TX.n; _i++) { \ dqlite_vfs_frame *_frame = &_frames[_i]; \ TX.page_numbers[_i] = _frame->page_number; \ memcpy(TX.frames + _i * PAGE_SIZE, \ _frame->data, PAGE_SIZE); \ sqlite3_free(_frame->data); \ } \ sqlite3_free(_frames); \ } \ } while (0) /* Apply WAL frames to the given VFS. */ #define APPLY(VFS, TX) \ do { \ sqlite3_vfs *vfs = sqlite3_vfs_find(VFS); \ int _rv; \ _rv = dqlite_vfs_apply(vfs, "test.db", TX.n, TX.page_numbers, \ TX.frames); \ munit_assert_int(_rv, ==, 0); \ } while (0) /* Abort a transaction on the given VFS. */ #define ABORT(VFS) \ do { \ sqlite3_vfs *vfs = sqlite3_vfs_find(VFS); \ int _rv; \ _rv = dqlite_vfs_abort(vfs, "test.db"); \ munit_assert_int(_rv, ==, 0); \ } while (0) /* Release all memory used by a struct tx object. */ #define DONE(TX) \ do { \ free(TX.frames); \ free(TX.page_numbers); \ } while (0) /* Peform a full checkpoint on the given database. */ #define CHECKPOINT(DB) \ do { \ int _size; \ int _ckpt; \ int _rv; \ _rv = sqlite3_wal_checkpoint_v2( \ DB, "main", SQLITE_CHECKPOINT_TRUNCATE, &_size, &_ckpt); \ if (_rv != SQLITE_OK) { \ munit_errorf("checkpoint: %s (%d)", \ sqlite3_errmsg(DB), _rv); \ } \ } while (0) struct snapshot { void *data; size_t n; }; /* Take a snapshot of the database on the given VFS. */ #define SNAPSHOT(VFS, SNAPSHOT) \ do { \ sqlite3_vfs *vfs = sqlite3_vfs_find(VFS); \ int _rv; \ _rv = dqlite_vfs_snapshot(vfs, "test.db", &SNAPSHOT.data, \ &SNAPSHOT.n); \ munit_assert_int(_rv, ==, 0); \ } while (0) /* Restore a snapshot onto the given VFS. */ #define RESTORE(VFS, SNAPSHOT) \ do { \ sqlite3_vfs *vfs = sqlite3_vfs_find(VFS); \ int _rv; \ _rv = dqlite_vfs_restore(vfs, "test.db", SNAPSHOT.data, \ SNAPSHOT.n); \ munit_assert_int(_rv, ==, 0); \ } while (0) /* Open and close a new connection using the dqlite VFS. */ TEST(vfs, open, setUp, tearDown, 0, NULL) { sqlite3 *db; OPEN("1", db); CLOSE(db); return MUNIT_OK; } /* New frames appended to the WAL file by a sqlite3_step() call that has * triggered a write transactions are not immediately visible to other * connections after sqlite3_step() has returned. */ TEST(vfs, writeTransactionNotImmediatelyVisible, setUp, tearDown, 0, NULL) { sqlite3 *db1; sqlite3 *db2; sqlite3_stmt *stmt; int rv; OPEN("1", db1); EXEC(db1, "CREATE TABLE test(n INT)"); OPEN("1", db2); rv = sqlite3_prepare_v2(db2, "SELECT * FROM test", -1, &stmt, NULL); munit_assert_int(rv, ==, SQLITE_ERROR); munit_assert_string_equal(sqlite3_errmsg(db2), "no such table: test"); CLOSE(db1); CLOSE(db2); return MUNIT_OK; } /* Invoking dqlite_vfs_poll() after a call to sqlite3_step() has triggered a * write transaction returns the newly appended WAL frames. */ TEST(vfs, pollAfterWriteTransaction, setUp, tearDown, 0, NULL) { sqlite3 *db; sqlite3_stmt *stmt; struct tx tx; unsigned i; OPEN("1", db); PREPARE(db, stmt, "CREATE TABLE test(n INT)"); STEP(stmt, SQLITE_DONE); POLL("1", tx); munit_assert_ptr_not_null(tx.frames); munit_assert_int(tx.n, ==, 2); for (i = 0; i < tx.n; i++) { munit_assert_int(tx.page_numbers[i], ==, i + 1); } DONE(tx); FINALIZE(stmt); CLOSE(db); return MUNIT_OK; } /* Invoking dqlite_vfs_poll() after a call to sqlite3_step() has triggered a * write transaction sets a write lock on the WAL, so calls to sqlite3_step() * from other connections return SQLITE_BUSY if they try to start a write * transaction. */ TEST(vfs, pollAcquireWriteLock, setUp, tearDown, 0, NULL) { sqlite3 *db1; sqlite3 *db2; sqlite3_stmt *stmt1; sqlite3_stmt *stmt2; struct tx tx; OPEN("1", db1); OPEN("1", db2); PREPARE(db1, stmt1, "CREATE TABLE test(n INT)"); PREPARE(db2, stmt2, "CREATE TABLE test2(n INT)"); STEP(stmt1, SQLITE_DONE); POLL("1", tx); STEP(stmt2, SQLITE_BUSY); RESET(stmt2, SQLITE_BUSY); FINALIZE(stmt1); FINALIZE(stmt2); CLOSE(db1); CLOSE(db2); DONE(tx); return MUNIT_OK; } /* If the page cache limit is exceeded during a call to sqlite3_step() that has * triggered a write transaction, some WAL frames will be written and then * overwritten before the final commit. Only the final version of the frame is * included in the set returned by dqlite_vfs_poll(). */ TEST(vfs, pollAfterPageStress, setUp, tearDown, 0, NULL) { sqlite3 *db; sqlite3_stmt *stmt; struct tx tx; unsigned i; char sql[64]; OPEN("1", db); EXEC(db, "CREATE TABLE test(n INT)"); POLL("1", tx); APPLY("1", tx); DONE(tx); EXEC(db, "BEGIN"); for (i = 0; i < 163; i++) { sprintf(sql, "INSERT INTO test(n) VALUES(%d)", i + 1); EXEC(db, sql); POLL("1", tx); munit_assert_int(tx.n, ==, 0); } for (i = 0; i < 163; i++) { sprintf(sql, "UPDATE test SET n=%d WHERE n=%d", i, i + 1); EXEC(db, sql); POLL("1", tx); munit_assert_int(tx.n, ==, 0); } EXEC(db, "COMMIT"); POLL("1", tx); /* Five frames were replicated and the first frame actually contains a * spill of the third page. */ munit_assert_int(tx.n, ==, 6); munit_assert_int(tx.page_numbers[0], ==, 3); munit_assert_int(tx.page_numbers[1], ==, 4); munit_assert_int(tx.page_numbers[2], ==, 5); munit_assert_int(tx.page_numbers[3], ==, 1); munit_assert_int(tx.page_numbers[4], ==, 2); APPLY("1", tx); DONE(tx); /* All records have been inserted. */ PREPARE(db, stmt, "SELECT * FROM test"); for (i = 0; i < 163; i++) { STEP(stmt, SQLITE_ROW); munit_assert_int(sqlite3_column_int(stmt, 0), ==, i); } STEP(stmt, SQLITE_DONE); FINALIZE(stmt); CLOSE(db); return MUNIT_OK; } /* Set the SQLite PENDING_BYTE at the start of the second page and make sure * all data entry is successful. */ TEST(vfs, adaptPendingByte, setUp, tearDownRestorePendingByte, 0, NULL) { sqlite3 *db; sqlite3_stmt *stmt; struct tx tx; int i; int n; char sql[64]; /* Set the pending byte at the start of the second page */ const unsigned new_pending_byte = 512; dq_sqlite_pending_byte = new_pending_byte; sqlite3_test_control(SQLITE_TESTCTRL_PENDING_BYTE, new_pending_byte); OPEN("1", db); EXEC(db, "CREATE TABLE test(n INT)"); POLL("1", tx); APPLY("1", tx); DONE(tx); EXEC(db, "BEGIN"); n = 65536; for (i = 0; i < n; i++) { sprintf(sql, "INSERT INTO test(n) VALUES(%d)", i); EXEC(db, sql); POLL("1", tx); munit_assert_uint(tx.n, ==, 0); } EXEC(db, "COMMIT"); POLL("1", tx); APPLY("1", tx); DONE(tx); /* All records have been inserted. */ PREPARE(db, stmt, "SELECT * FROM test"); for (i = 0; i < n; i++) { STEP(stmt, SQLITE_ROW); munit_assert_int(sqlite3_column_int(stmt, 0), ==, i); } STEP(stmt, SQLITE_DONE); FINALIZE(stmt); CLOSE(db); return MUNIT_OK; } /* Use dqlite_vfs_apply() to actually modify the WAL after a write transaction * was triggered by a call to sqlite3_step(), then perform a read transaction * and check that it can see the transaction changes. */ TEST(vfs, applyMakesTransactionVisible, setUp, tearDown, 0, NULL) { sqlite3 *db; sqlite3_stmt *stmt; struct tx tx; OPEN("1", db); EXEC(db, "CREATE TABLE test(n INT)"); POLL("1", tx); APPLY("1", tx); DONE(tx); PREPARE(db, stmt, "SELECT * FROM test"); STEP(stmt, SQLITE_DONE); FINALIZE(stmt); CLOSE(db); return MUNIT_OK; } /* Use dqlite_vfs_apply() to actually modify the WAL after a write transaction * was triggered by an explicit "COMMIT" statement and check that changes are * visible. */ TEST(vfs, applyExplicitTransaction, setUp, tearDown, 0, NULL) { sqlite3 *db; sqlite3_stmt *stmt; struct tx tx; OPEN("1", db); PREPARE(db, stmt, "BEGIN"); STEP(stmt, SQLITE_DONE); POLL("1", tx); munit_assert_int(tx.n, ==, 0); FINALIZE(stmt); PREPARE(db, stmt, "CREATE TABLE test(n INT)"); STEP(stmt, SQLITE_DONE); POLL("1", tx); munit_assert_int(tx.n, ==, 0); FINALIZE(stmt); PREPARE(db, stmt, "COMMIT"); STEP(stmt, SQLITE_DONE); POLL("1", tx); munit_assert_int(tx.n, ==, 2); APPLY("1", tx); DONE(tx); FINALIZE(stmt); PREPARE(db, stmt, "SELECT * FROM test"); STEP(stmt, SQLITE_DONE); FINALIZE(stmt); CLOSE(db); return MUNIT_OK; } /* Perform two consecutive full write transactions using sqlite3_step(), * dqlite_vfs_poll() and dqlite_vfs_apply(), then run a read transaction and * check that it can see all committed changes. */ TEST(vfs, consecutiveWriteTransactions, setUp, tearDown, 0, NULL) { sqlite3 *db; sqlite3_stmt *stmt; struct tx tx; OPEN("1", db); EXEC(db, "CREATE TABLE test(n INT)"); POLL("1", tx); APPLY("1", tx); DONE(tx); EXEC(db, "INSERT INTO test(n) VALUES(123)"); POLL("1", tx); APPLY("1", tx); DONE(tx); PREPARE(db, stmt, "SELECT * FROM test"); STEP(stmt, SQLITE_ROW); munit_assert_int(sqlite3_column_int(stmt, 0), ==, 123); STEP(stmt, SQLITE_DONE); FINALIZE(stmt); CLOSE(db); return MUNIT_OK; } /* Perform three consecutive write transactions, then re-open the database and * finally run a read transaction and check that it can see all committed * changes. */ TEST(vfs, reopenAfterConsecutiveWriteTransactions, setUp, tearDown, 0, NULL) { sqlite3 *db; sqlite3_stmt *stmt; struct tx tx; OPEN("1", db); EXEC(db, "CREATE TABLE foo(id INT)"); POLL("1", tx); APPLY("1", tx); DONE(tx); EXEC(db, "CREATE TABLE bar (id INT)"); POLL("1", tx); APPLY("1", tx); DONE(tx); EXEC(db, "INSERT INTO foo(id) VALUES(1)"); POLL("1", tx); APPLY("1", tx); DONE(tx); CLOSE(db); OPEN("1", db); PREPARE(db, stmt, "SELECT * FROM sqlite_master"); STEP(stmt, SQLITE_ROW); FINALIZE(stmt); CLOSE(db); return MUNIT_OK; } /* Use dqlite_vfs_apply() to actually modify the WAL after a write transaction * was triggered by sqlite3_step(), and verify that the transaction is visible * from another existing connection. */ TEST(vfs, transactionIsVisibleFromExistingConnection, setUp, tearDown, 0, NULL) { sqlite3 *db1; sqlite3 *db2; sqlite3_stmt *stmt; struct tx tx; OPEN("1", db1); OPEN("1", db2); EXEC(db1, "CREATE TABLE test(n INT)"); POLL("1", tx); APPLY("1", tx); DONE(tx); PREPARE(db2, stmt, "SELECT * FROM test"); STEP(stmt, SQLITE_DONE); FINALIZE(stmt); CLOSE(db1); CLOSE(db2); return MUNIT_OK; } /* Use dqlite_vfs_apply() to actually modify the WAL after a write transaction * was triggered by sqlite3_step(), and verify that the transaction is visible * from a brand new connection. */ TEST(vfs, transactionIsVisibleFromNewConnection, setUp, tearDown, 0, NULL) { sqlite3 *db1; sqlite3 *db2; sqlite3_stmt *stmt; struct tx tx; OPEN("1", db1); EXEC(db1, "CREATE TABLE test(n INT)"); POLL("1", tx); APPLY("1", tx); DONE(tx); OPEN("1", db2); PREPARE(db2, stmt, "SELECT * FROM test"); STEP(stmt, SQLITE_DONE); FINALIZE(stmt); CLOSE(db1); CLOSE(db2); return MUNIT_OK; } /* Use dqlite_vfs_apply() to actually modify the WAL after a write transaction * was triggered by sqlite3_step(), then close the connection and open a new * one. A read transaction started in the new connection can see the changes * committed by the first one. */ TEST(vfs, transactionIsVisibleFromReopenedConnection, setUp, tearDown, 0, NULL) { sqlite3 *db; sqlite3_stmt *stmt; struct tx tx; OPEN("1", db); EXEC(db, "CREATE TABLE test(n INT)"); POLL("1", tx); APPLY("1", tx); DONE(tx); CLOSE(db); OPEN("1", db); PREPARE(db, stmt, "SELECT * FROM test"); STEP(stmt, SQLITE_DONE); FINALIZE(stmt); CLOSE(db); return MUNIT_OK; } /* Use dqlite_vfs_apply() to replicate the very first write transaction on a * different VFS than the one that initially generated it. In that case it's * necessary to initialize the database file on the other VFS by opening and * closing a connection. */ TEST(vfs, firstApplyOnDifferentVfs, setUp, tearDown, 0, NULL) { sqlite3 *db1; sqlite3 *db2; sqlite3_stmt *stmt; struct tx tx; OPEN("1", db1); PREPARE(db1, stmt, "CREATE TABLE test(n INT)"); STEP(stmt, SQLITE_DONE); POLL("1", tx); APPLY("1", tx); OPEN("2", db2); CLOSE(db2); APPLY("2", tx); DONE(tx); FINALIZE(stmt); CLOSE(db1); return MUNIT_OK; } /* Use dqlite_vfs_apply() to replicate a second write transaction on a different * VFS than the one that initially generated it. In that case it's not necessary * to do anything special before calling dqlite_vfs_apply(). */ TEST(vfs, secondApplyOnDifferentVfs, setUp, tearDown, 0, NULL) { sqlite3 *db1; sqlite3 *db2; struct tx tx; OPEN("1", db1); EXEC(db1, "CREATE TABLE test(n INT)"); POLL("1", tx); APPLY("1", tx); OPEN("2", db2); CLOSE(db2); APPLY("2", tx); DONE(tx); EXEC(db1, "INSERT INTO test(n) VALUES(123)"); POLL("1", tx); APPLY("1", tx); APPLY("2", tx); DONE(tx); CLOSE(db1); return MUNIT_OK; } /* Use dqlite_vfs_apply() to replicate a second write transaction on a different * VFS than the one that initially generated it and that has an open connection * which has built the WAL index header by preparing a statement. */ TEST(vfs, applyOnDifferentVfsWithOpenConnection, setUp, tearDown, 0, NULL) { sqlite3 *db1; sqlite3 *db2; sqlite3_stmt *stmt; struct tx tx; OPEN("1", db1); PREPARE(db1, stmt, "CREATE TABLE test(n INT)"); STEP(stmt, SQLITE_DONE); FINALIZE(stmt); POLL("1", tx); APPLY("1", tx); OPEN("2", db2); CLOSE(db2); APPLY("2", tx); DONE(tx); EXEC(db1, "INSERT INTO test(n) VALUES(123)"); POLL("1", tx); CLOSE(db1); OPEN("2", db2); PREPARE(db2, stmt, "PRAGMA cache_size=-5000"); FINALIZE(stmt); APPLY("2", tx); PREPARE(db2, stmt, "SELECT * FROM test"); STEP(stmt, SQLITE_ROW); FINALIZE(stmt); DONE(tx); CLOSE(db2); return MUNIT_OK; } /* A write transaction that gets replicated to a different VFS is visible to a * new connection opened on that VFS. */ TEST(vfs, transactionVisibleOnDifferentVfs, setUp, tearDown, 0, NULL) { sqlite3 *db1; sqlite3 *db2; sqlite3_stmt *stmt; struct tx tx; OPEN("1", db1); EXEC(db1, "CREATE TABLE test(n INT)"); POLL("1", tx); APPLY("1", tx); OPEN("2", db2); CLOSE(db2); APPLY("2", tx); DONE(tx); CLOSE(db1); OPEN("2", db1); PREPARE(db1, stmt, "SELECT * FROM test"); STEP(stmt, SQLITE_DONE); FINALIZE(stmt); CLOSE(db1); return MUNIT_OK; } /* Calling dqlite_vfs_abort() to cancel a transaction releases the write * lock on the WAL. */ TEST(vfs, abort, setUp, tearDown, 0, NULL) { sqlite3 *db1; sqlite3 *db2; sqlite3_stmt *stmt1; sqlite3_stmt *stmt2; struct tx tx; OPEN("1", db1); OPEN("1", db2); PREPARE(db1, stmt1, "CREATE TABLE test(n INT)"); PREPARE(db2, stmt2, "CREATE TABLE test2(n INT)"); STEP(stmt1, SQLITE_DONE); POLL("1", tx); ABORT("1"); STEP(stmt2, SQLITE_DONE); FINALIZE(stmt1); FINALIZE(stmt2); CLOSE(db1); CLOSE(db2); DONE(tx); return MUNIT_OK; } /* Perform a checkpoint after a write transaction has completed, then perform * another write transaction and check that changes both before and after the * checkpoint are visible. */ TEST(vfs, checkpoint, setUp, tearDown, 0, NULL) { sqlite3 *db1; sqlite3 *db2; sqlite3_stmt *stmt; struct tx tx; OPEN("1", db1); EXEC(db1, "CREATE TABLE test(n INT)"); POLL("1", tx); APPLY("1", tx); DONE(tx); EXEC(db1, "INSERT INTO test(n) VALUES(123)"); POLL("1", tx); APPLY("1", tx); DONE(tx); OPEN("1", db2); CHECKPOINT(db2); CLOSE(db2); EXEC(db1, "INSERT INTO test(n) VALUES(456)"); POLL("1", tx); APPLY("1", tx); DONE(tx); PREPARE(db1, stmt, "SELECT * FROM test"); STEP(stmt, SQLITE_ROW); munit_assert_int(sqlite3_column_int(stmt, 0), ==, 123); STEP(stmt, SQLITE_ROW); munit_assert_int(sqlite3_column_int(stmt, 0), ==, 456); STEP(stmt, SQLITE_DONE); FINALIZE(stmt); CLOSE(db1); return MUNIT_OK; } /* Replicate a write transaction that happens after a checkpoint. */ TEST(vfs, applyOnDifferentVfsAfterCheckpoint, setUp, tearDown, 0, NULL) { sqlite3 *db; sqlite3_stmt *stmt; struct tx tx1; struct tx tx2; struct tx tx3; OPEN("1", db); EXEC(db, "CREATE TABLE test(n INT)"); POLL("1", tx1); APPLY("1", tx1); EXEC(db, "INSERT INTO test(n) VALUES(123)"); POLL("1", tx2); APPLY("1", tx2); CHECKPOINT(db); EXEC(db, "INSERT INTO test(n) VALUES(456)"); POLL("1", tx3); APPLY("1", tx3); CLOSE(db); OPEN("2", db); CLOSE(db); APPLY("2", tx1); APPLY("2", tx2); OPEN("2", db); CHECKPOINT(db); CLOSE(db); APPLY("2", tx3); OPEN("2", db); PREPARE(db, stmt, "SELECT * FROM test ORDER BY n"); STEP(stmt, SQLITE_ROW); munit_assert_int(sqlite3_column_int(stmt, 0), ==, 123); STEP(stmt, SQLITE_ROW); munit_assert_int(sqlite3_column_int(stmt, 0), ==, 456); STEP(stmt, SQLITE_DONE); FINALIZE(stmt); CLOSE(db); DONE(tx1); DONE(tx2); DONE(tx3); return MUNIT_OK; } /* Replicate to another VFS a series of changes including a checkpoint, then * perform a new write transaction on that other VFS. */ TEST(vfs, checkpointThenPerformTransaction, setUp, tearDown, 0, NULL) { sqlite3 *db1; sqlite3 *db2; struct tx tx1; struct tx tx2; struct tx tx3; OPEN("1", db1); EXEC(db1, "CREATE TABLE test(n INT)"); POLL("1", tx1); APPLY("1", tx1); EXEC(db1, "INSERT INTO test(n) VALUES(123)"); POLL("1", tx2); APPLY("1", tx2); CHECKPOINT(db1); EXEC(db1, "INSERT INTO test(n) VALUES(456)"); POLL("1", tx3); APPLY("1", tx3); CLOSE(db1); OPEN("2", db1); APPLY("2", tx1); APPLY("2", tx2); OPEN("2", db2); CHECKPOINT(db2); CLOSE(db2); APPLY("2", tx3); DONE(tx1); DONE(tx2); DONE(tx3); EXEC(db1, "INSERT INTO test(n) VALUES(789)"); POLL("2", tx1); APPLY("2", tx1); DONE(tx1); CLOSE(db1); return MUNIT_OK; } /* Rollback a transaction that didn't hit the page cache limit and hence didn't * perform any pre-commit WAL writes. */ TEST(vfs, rollbackTransactionWithoutPageStress, setUp, tearDown, 0, NULL) { sqlite3 *db; struct tx tx; sqlite3_stmt *stmt; OPEN("1", db); EXEC(db, "CREATE TABLE test(n INT)"); POLL("1", tx); APPLY("1", tx); DONE(tx); EXEC(db, "BEGIN"); EXEC(db, "INSERT INTO test(n) VALUES(1)"); EXEC(db, "ROLLBACK"); POLL("1", tx); munit_assert_int(tx.n, ==, 0); PREPARE(db, stmt, "SELECT * FROM test"); STEP(stmt, SQLITE_DONE); RESET(stmt, SQLITE_OK); EXEC(db, "INSERT INTO test(n) VALUES(1)"); POLL("1", tx); APPLY("1", tx); DONE(tx); STEP(stmt, SQLITE_ROW); FINALIZE(stmt); CLOSE(db); return MUNIT_OK; } /* Rollback a transaction that hit the page cache limit and hence performed some * pre-commit WAL writes. */ TEST(vfs, rollbackTransactionWithPageStress, setUp, tearDown, 0, NULL) { sqlite3 *db; sqlite3_stmt *stmt; struct tx tx; unsigned i; OPEN("1", db); EXEC(db, "CREATE TABLE test(n INT)"); POLL("1", tx); APPLY("1", tx); DONE(tx); EXEC(db, "BEGIN"); for (i = 0; i < 163; i++) { char sql[64]; sprintf(sql, "INSERT INTO test(n) VALUES(%d)", i + 1); EXEC(db, sql); POLL("1", tx); munit_assert_int(tx.n, ==, 0); } EXEC(db, "ROLLBACK"); POLL("1", tx); munit_assert_int(tx.n, ==, 0); PREPARE(db, stmt, "SELECT * FROM test"); STEP(stmt, SQLITE_DONE); RESET(stmt, SQLITE_OK); EXEC(db, "INSERT INTO test(n) VALUES(1)"); POLL("1", tx); APPLY("1", tx); DONE(tx); STEP(stmt, SQLITE_ROW); FINALIZE(stmt); CLOSE(db); return MUNIT_OK; } /* A snapshot of a brand new database that has been just initialized contains * just the first page of the main database file. */ TEST(vfs, snapshotInitialDatabase, setUp, tearDown, 0, NULL) { sqlite3 *db; struct snapshot snapshot; uint8_t *page; uint8_t page_size[2] = {2, 0}; /* Big-endian page size */ uint8_t database_size[4] = {0, 0, 0, 1}; /* Big-endian database size */ OPEN("1", db); CLOSE(db); SNAPSHOT("1", snapshot); munit_assert_int(snapshot.n, ==, PAGE_SIZE); page = snapshot.data; munit_assert_int(memcmp(&page[16], page_size, 2), ==, 0); munit_assert_int(memcmp(&page[28], database_size, 4), ==, 0); raft_free(snapshot.data); return MUNIT_OK; } /* A snapshot of a database after the first write transaction gets applied * contains the first page of the database plus the WAL file containing the * transaction frames. */ TEST(vfs, snapshotAfterFirstTransaction, setUp, tearDown, 0, NULL) { sqlite3 *db; struct snapshot snapshot; struct tx tx; uint8_t *page; uint8_t page_size[2] = {2, 0}; /* Big-endian page size */ uint8_t database_size[4] = {0, 0, 0, 1}; /* Big-endian database size */ OPEN("1", db); EXEC(db, "CREATE TABLE test(n INT)"); POLL("1", tx); APPLY("1", tx); DONE(tx); CLOSE(db); SNAPSHOT("1", snapshot); munit_assert_int(snapshot.n, ==, PAGE_SIZE + 32 + (24 + PAGE_SIZE) * 2); page = snapshot.data; munit_assert_int(memcmp(&page[16], page_size, 2), ==, 0); munit_assert_int(memcmp(&page[28], database_size, 4), ==, 0); raft_free(snapshot.data); return MUNIT_OK; } /* A snapshot of a database after a checkpoint contains all checkpointed pages * and no WAL frames. */ TEST(vfs, snapshotAfterCheckpoint, setUp, tearDown, 0, NULL) { sqlite3 *db; struct snapshot snapshot; struct tx tx; uint8_t *page; uint8_t page_size[2] = {2, 0}; /* Big-endian page size */ uint8_t database_size[4] = {0, 0, 0, 2}; /* Big-endian database size */ OPEN("1", db); EXEC(db, "CREATE TABLE test(n INT)"); POLL("1", tx); APPLY("1", tx); DONE(tx); CHECKPOINT(db); CLOSE(db); SNAPSHOT("1", snapshot); munit_assert_int(snapshot.n, ==, PAGE_SIZE * 2); page = snapshot.data; munit_assert_int(memcmp(&page[16], page_size, 2), ==, 0); munit_assert_int(memcmp(&page[28], database_size, 4), ==, 0); raft_free(snapshot.data); return MUNIT_OK; } /* Restore a snapshot taken after a brand new database has been just * initialized. */ TEST(vfs, restoreInitialDatabase, setUp, tearDown, 0, NULL) { sqlite3 *db; struct snapshot snapshot; OPEN("1", db); CLOSE(db); SNAPSHOT("1", snapshot); OPEN("2", db); CLOSE(db); RESTORE("2", snapshot); raft_free(snapshot.data); return MUNIT_OK; } /* Restore a snapshot of a database taken after the first write transaction gets * applied. */ TEST(vfs, restoreAfterFirstTransaction, setUp, tearDown, 0, NULL) { sqlite3 *db; sqlite3_stmt *stmt; struct snapshot snapshot; struct tx tx; OPEN("1", db); EXEC(db, "CREATE TABLE test(n INT)"); POLL("1", tx); APPLY("1", tx); DONE(tx); CLOSE(db); SNAPSHOT("1", snapshot); OPEN("2", db); CLOSE(db); RESTORE("2", snapshot); OPEN("2", db); PREPARE(db, stmt, "SELECT * FROM test"); STEP(stmt, SQLITE_DONE); FINALIZE(stmt); CLOSE(db); raft_free(snapshot.data); return MUNIT_OK; } /* Restore a snapshot of a database while a connection is open. */ TEST(vfs, restoreWithOpenConnection, setUp, tearDown, 0, NULL) { sqlite3 *db; sqlite3_stmt *stmt; struct snapshot snapshot; struct tx tx; OPEN("1", db); EXEC(db, "CREATE TABLE test(n INT)"); POLL("1", tx); APPLY("1", tx); DONE(tx); CLOSE(db); SNAPSHOT("1", snapshot); OPEN("2", db); RESTORE("2", snapshot); PREPARE(db, stmt, "SELECT * FROM test"); STEP(stmt, SQLITE_DONE); FINALIZE(stmt); CLOSE(db); raft_free(snapshot.data); return MUNIT_OK; } dqlite-1.9.1/test/lib/000077500000000000000000000000001416342771300145415ustar00rootroot00000000000000dqlite-1.9.1/test/lib/client.h000066400000000000000000000016301416342771300161700ustar00rootroot00000000000000/* Setup a test dqlite client. */ #include "endpoint.h" #ifndef TEST_CLIENT_H #define TEST_CLIENT_H #define FIXTURE_CLIENT \ struct client client; \ struct test_endpoint endpoint; \ int server #define SETUP_CLIENT \ { \ int _rv; \ int _client; \ test_endpoint_setup(&f->endpoint, params); \ _rv = listen(f->endpoint.fd, 16); \ munit_assert_int(_rv, ==, 0); \ test_endpoint_pair(&f->endpoint, &f->server, &_client); \ clientInit(&f->client, _client); \ } #define TEAR_DOWN_CLIENT \ clientClose(&f->client); \ test_endpoint_tear_down(&f->endpoint) #endif /* TEST_CLIENT_H */ dqlite-1.9.1/test/lib/cluster.h000066400000000000000000000136031416342771300163760ustar00rootroot00000000000000/** * Helpers to setup a raft cluster in test fixtures. * * Each raft instance will use its own dqlite FSM, which in turn will be created * using its own config, registry and logger. * * The fixture will also register a VFS and a SQLite replication object for each * raft instance, using "test" as registration name, where is the raft * instance index. * * This fixture is meant to be used as base-line fixture for most higher-level * tests. */ #ifndef TEST_CLUSTER_H #define TEST_CLUSTER_H #include #include #include "../../src/config.h" #include "../../src/fsm.h" #include "../../src/registry.h" #include "../../src/vfs.h" #include "../lib/heap.h" #include "../lib/logger.h" #include "../lib/sqlite.h" #define N_SERVERS 3 #define V1 0 #define V2 1 struct server { struct logger logger; struct config config; sqlite3_vfs vfs; struct registry registry; }; #define FIXTURE_CLUSTER \ struct server servers[N_SERVERS]; \ struct raft_fsm fsms[N_SERVERS]; \ struct raft_fixture cluster; #define SETUP_CLUSTER(VERSION) \ { \ struct raft_configuration _configuration; \ unsigned _i; \ int _rv; \ SETUP_HEAP; \ SETUP_SQLITE; \ _rv = raft_fixture_init(&f->cluster, N_SERVERS, f->fsms); \ munit_assert_int(_rv, ==, 0); \ for (_i = 0; _i < N_SERVERS; _i++) { \ SETUP_SERVER(_i, VERSION); \ } \ _rv = raft_fixture_configuration(&f->cluster, N_SERVERS, \ &_configuration); \ munit_assert_int(_rv, ==, 0); \ _rv = raft_fixture_bootstrap(&f->cluster, &_configuration); \ munit_assert_int(_rv, ==, 0); \ raft_configuration_close(&_configuration); \ _rv = raft_fixture_start(&f->cluster); \ munit_assert_int(_rv, ==, 0); \ } #define SETUP_SERVER(I, VERSION) \ { \ struct server *_s = &f->servers[I]; \ struct raft_fsm *_fsm = &f->fsms[I]; \ char address[16]; \ int _rc; \ \ test_logger_setup(params, &_s->logger); \ \ sprintf(address, "%d", I + 1); \ \ _rc = config__init(&_s->config, I + 1, address); \ munit_assert_int(_rc, ==, 0); \ \ registry__init(&_s->registry, &_s->config); \ \ _rc = VfsInit(&_s->vfs, _s->config.name); \ munit_assert_int(_rc, ==, 0); \ _rc = sqlite3_vfs_register(&_s->vfs, 0); \ munit_assert_int(_rc, ==, 0); \ \ _rc = fsm__init(_fsm, &_s->config, &_s->registry); \ munit_assert_int(_rc, ==, 0); \ } #define TEAR_DOWN_CLUSTER \ { \ int _i; \ for (_i = 0; _i < N_SERVERS; _i++) { \ TEAR_DOWN_SERVER(_i); \ } \ raft_fixture_close(&f->cluster); \ TEAR_DOWN_SQLITE; \ TEAR_DOWN_HEAP; \ } #define TEAR_DOWN_SERVER(I) \ { \ struct server *s = &f->servers[I]; \ struct raft_fsm *fsm = &f->fsms[I]; \ fsm__close(fsm); \ registry__close(&s->registry); \ sqlite3_vfs_unregister(&s->vfs); \ VfsClose(&s->vfs); \ config__close(&s->config); \ test_logger_tear_down(&s->logger); \ } #define CLUSTER_CONFIG(I) &f->servers[I].config #define CLUSTER_LOGGER(I) &f->servers[I].logger #define CLUSTER_LEADER(I) &f->servers[I].leader #define CLUSTER_REGISTRY(I) &f->servers[I].registry #define CLUSTER_RAFT(I) raft_fixture_get(&f->cluster, I) #define CLUSTER_LAST_INDEX(I) raft_last_index(CLUSTER_RAFT(I)) #define CLUSTER_DISCONNECT(I, J) raft_fixture_disconnect(&f->cluster, I, J) #define CLUSTER_RECONNECT(I, J) raft_fixture_reconnect(&f->cluster, I, J) #define CLUSTER_ELECT(I) raft_fixture_elect(&f->cluster, I) #define CLUSTER_DEPOSE raft_fixture_depose(&f->cluster) #define CLUSTER_APPLIED(N) \ { \ int _i; \ for (_i = 0; _i < N_SERVERS; _i++) { \ bool done; \ done = raft_fixture_step_until_applied(&f->cluster, \ _i, N, 1000); \ munit_assert_true(done); \ } \ } #define CLUSTER_STEP raft_fixture_step(&f->cluster) #define CLUSTER_SNAPSHOT_THRESHOLD(I, N) \ raft_set_snapshot_threshold(CLUSTER_RAFT(I), N) #define CLUSTER_SNAPSHOT_TRAILING(I, N) \ raft_set_snapshot_trailing(CLUSTER_RAFT(I), N) #endif /* TEST_CLUSTER_H */ dqlite-1.9.1/test/lib/config.h000066400000000000000000000012251416342771300161570ustar00rootroot00000000000000/** * Options object for tests. */ #ifndef TEST_OPTIONS_H #define TEST_OPTIONS_H #include "../../src/config.h" #include "logger.h" #define FIXTURE_CONFIG struct config config; #define SETUP_CONFIG \ { \ int rc; \ rc = config__init(&f->config, 1, "1"); \ munit_assert_int(rc, ==, 0); \ test_logger_setup(params, &f->config.logger); \ } #define TEAR_DOWN_CONFIG \ test_logger_tear_down(&f->config.logger); \ config__close(&f->config) #endif /* TEST_OPTIONS_H */ dqlite-1.9.1/test/lib/endpoint.c000066400000000000000000000100641416342771300165260ustar00rootroot00000000000000#include #include #include #include #include "endpoint.h" static int getFamily(const MunitParameter params[]) { const char *family = NULL; if (params != NULL) { family = munit_parameters_get(params, TEST_ENDPOINT_FAMILY); } if (family == NULL) { family = "unix"; } if (strcmp(family, "tcp") == 0) { return AF_INET; } else if (strcmp(family, "unix") == 0) { return AF_UNIX; } munit_errorf("unexpected socket family: %s", family); return -1; } void test_endpoint_setup(struct test_endpoint *e, const MunitParameter params[]) { struct sockaddr *address; socklen_t size; int rv; e->family = getFamily(params); /* Initialize the appropriate socket address structure, depending on the * selected socket family. */ switch (e->family) { case AF_INET: /* TCP socket on loopback device */ memset(&e->in_address, 0, sizeof e->in_address); e->in_address.sin_family = AF_INET; e->in_address.sin_addr.s_addr = inet_addr("127.0.0.1"); e->in_address.sin_port = 0; /* Get a random free port */ address = (struct sockaddr *)(&e->in_address); size = sizeof e->in_address; break; case AF_UNIX: /* Abstract Unix socket */ memset(&e->un_address, 0, sizeof e->un_address); e->un_address.sun_family = AF_UNIX; strcpy(e->un_address.sun_path, ""); /* Random address */ address = (struct sockaddr *)(&e->un_address); size = sizeof e->un_address; break; default: munit_errorf("unexpected socket family: %d", e->family); } /* Create the listener fd. */ e->fd = socket(e->family, SOCK_STREAM, 0); if (e->fd < 0) { munit_errorf("socket(): %s", strerror(errno)); } /* Bind the listener fd. */ rv = bind(e->fd, address, size); if (rv != 0) { munit_errorf("bind(): %s", strerror(errno)); } /* Get the actual addressed assigned by the kernel and save it back in * the relevant struct server field (pointed to by address). */ rv = getsockname(e->fd, address, &size); if (rv != 0) { munit_errorf("getsockname(): %s", strerror(errno)); } /* Render the endpoint address. */ switch (e->family) { case AF_INET: sprintf(e->address, "127.0.0.1:%d", htons(e->in_address.sin_port)); break; case AF_UNIX: /* TODO */ break; } } void test_endpoint_tear_down(struct test_endpoint *e) { close(e->fd); } int test_endpoint_connect(struct test_endpoint *e) { struct sockaddr *address; socklen_t size; int fd; int rv; switch (e->family) { case AF_INET: address = (struct sockaddr *)&e->in_address; size = sizeof e->in_address; break; case AF_UNIX: address = (struct sockaddr *)&e->un_address; size = sizeof e->un_address; break; } /* Create the socket. */ fd = socket(e->family, SOCK_STREAM, 0); if (fd < 0) { munit_errorf("socket(): %s", strerror(errno)); } /* Connect to the server */ rv = connect(fd, address, size); if (rv != 0 && errno != ECONNREFUSED) { munit_errorf("connect(): %s", strerror(errno)); } return fd; } int test_endpoint_accept(struct test_endpoint *e) { struct sockaddr_in in_address; struct sockaddr_un un_address; struct sockaddr *address; socklen_t size; int fd; int rv; switch (e->family) { case AF_INET: address = (struct sockaddr *)&in_address; size = sizeof in_address; break; case AF_UNIX: address = (struct sockaddr *)&un_address; size = sizeof un_address; break; } /* Accept the client connection. */ fd = accept(e->fd, address, &size); if (fd < 0) { /* Check if the endpoint has been closed, so this is benign. */ if (errno == EBADF || errno == EINVAL || errno == ENOTSOCK) { return -1; } munit_errorf("accept(): %s", strerror(errno)); } /* Set non-blocking mode */ rv = fcntl(fd, F_SETFL, O_NONBLOCK); if (rv != 0) { munit_errorf("set non-blocking mode: %s", strerror(errno)); } return fd; } void test_endpoint_pair(struct test_endpoint *e, int *server, int *client) { *client = test_endpoint_connect(e); *server = test_endpoint_accept(e); } const char *test_endpoint_address(struct test_endpoint *e) { return e->address; } char *test_endpoint_family_values[] = {"tcp", "unix", NULL}; dqlite-1.9.1/test/lib/endpoint.h000066400000000000000000000035121416342771300165330ustar00rootroot00000000000000/* Helpers to create and connect Unix or TCP sockets. */ #ifndef TEST_ENDPOINT_H #define TEST_ENDPOINT_H #include #include #include "munit.h" /* A few tests depend on knowing that certain reads and writes will not be short * and will happen immediately. */ #define TEST_SOCKET_MIN_BUF_SIZE 4096 /* Munit parameter defining the socket type to use in test_endpoint_setup. * * If set to "unix" a pair of unix abstract sockets will be created. If set to * "tcp" a pair of TCP sockets using the loopback interface will be created. */ #define TEST_ENDPOINT_FAMILY "endpoint-family" /* Null-terminated list of legal values for TEST_ENDPOINT_FAMILY. Currently * "unix" and "tcp". */ extern char *test_endpoint_family_values[]; /* Listening socket endpoint. */ struct test_endpoint { char address[256]; /* Rendered address string. */ sa_family_t family; /* Address family (either AF_INET or AF_UNIX) */ int fd; /* Listening socket. */ union { /* Server address (either a TCP or Unix) */ struct sockaddr_in in_address; struct sockaddr_un un_address; }; }; /* Create a listening endpoint. * * This will bind a random address and start listening to it. */ void test_endpoint_setup(struct test_endpoint *e, const MunitParameter params[]); /* Tear down a listening endpoint. */ void test_endpoint_tear_down(struct test_endpoint *e); /* Establish a new client connection. */ int test_endpoint_connect(struct test_endpoint *e); /* Accept a new client connection. */ int test_endpoint_accept(struct test_endpoint *e); /* Connect and accept a connection, returning the pair of connected sockets. */ void test_endpoint_pair(struct test_endpoint *e, int *server, int *client); /* Return the endpoint address. */ const char *test_endpoint_address(struct test_endpoint *e); #endif /* TEST_ENDPOINT_H */ dqlite-1.9.1/test/lib/fault.c000066400000000000000000000024321416342771300160210ustar00rootroot00000000000000#include "fault.h" #include "munit.h" void test_fault_init(struct test_fault *f) { f->countdown = -1; f->n = -1; f->enabled = false; } bool test_fault_tick(struct test_fault *f) { if (MUNIT_UNLIKELY(!f->enabled)) { return false; } /* If the initial delay parameter was set to -1, then never fail. This is * the most common case. */ if (MUNIT_LIKELY(f->countdown < 0)) { return false; } /* If we did not yet reach 'delay' ticks, then just decrease the countdown. */ if (f->countdown > 0) { f->countdown--; return false; } munit_assert_int(f->countdown, ==, 0); /* We reached 'delay' ticks, let's see how many times we have to trigger the * fault, if any. */ if (f->n < 0) { /* Trigger the fault forever. */ return true; } if (f->n > 0) { /* Trigger the fault at least this time. */ f->n--; return true; } munit_assert_int(f->n, ==, 0); /* We reached 'repeat' ticks, let's stop triggering the fault. */ f->countdown--; return false; } void test_fault_config(struct test_fault *f, int delay, int repeat) { f->countdown = delay; f->n = repeat; } void test_fault_enable(struct test_fault *f) { f->enabled = true; } dqlite-1.9.1/test/lib/fault.h000066400000000000000000000015761416342771300160360ustar00rootroot00000000000000/** * Helper for test components supporting fault injection. */ #ifndef TEST_FAULT_H #define TEST_FAULT_H #include /** * Information about a fault that should occurr in a component. */ struct test_fault { int countdown; /* Trigger the fault when this counter gets to zero. */ int n; /* Repeat the fault this many times. Default is -1. */ bool enabled; /* Enable fault triggering. */ }; /** * Initialize a fault. */ void test_fault_init(struct test_fault *f); /** * Advance the counters of the fault. Return true if the fault should be * triggered, false otherwise. */ bool test_fault_tick(struct test_fault *f); /** * Configure the fault with the given values. */ void test_fault_config(struct test_fault *f, int delay, int repeat); /** * Enable fault triggering. */ void test_fault_enable(struct test_fault *f); #endif /* TEST_FAULT_H */ dqlite-1.9.1/test/lib/fs.c000066400000000000000000000015761416342771300153260ustar00rootroot00000000000000#include #include #include #include #include #include "fs.h" #include "munit.h" char *test_dir_setup() { char *dir = munit_malloc(strlen(TEST__DIR_TEMPLATE) + 1); strcpy(dir, TEST__DIR_TEMPLATE); munit_assert_ptr_not_null(mkdtemp(dir)); return dir; } static int test__dir_tear_down_nftw_fn(const char * path, const struct stat *sb, int type, struct FTW * ftwb) { int rc; (void)sb; (void)type; (void)ftwb; rc = remove(path); munit_assert_int(rc, ==, 0); return 0; } void test_dir_tear_down(char *dir) { int rc; rc = nftw(dir, test__dir_tear_down_nftw_fn, 10, FTW_DEPTH | FTW_MOUNT | FTW_PHYS); munit_assert_int(rc, ==, 0); free(dir); } dqlite-1.9.1/test/lib/fs.h000066400000000000000000000004221416342771300153200ustar00rootroot00000000000000#ifndef DQLITE_TEST_FS_H #define DQLITE_TEST_FS_H #define TEST__DIR_TEMPLATE "/tmp/dqlite-test-XXXXXX" /* Setup a temporary directory. */ char *test_dir_setup(void); /* Remove the temporary directory. */ void test_dir_tear_down(char *dir); #endif /* DQLITE_TEST_FS_H */ dqlite-1.9.1/test/lib/heap.c000066400000000000000000000123101416342771300156170ustar00rootroot00000000000000#include #include "fault.h" #include "heap.h" /* This structure is used to encapsulate the global state variables used by * malloc() fault simulation. */ struct mem_fault { struct test_fault fault; /* Fault trigger */ sqlite3_mem_methods m; /* Actual malloc implementation */ }; /* We need to use a global variable here because after a sqlite3_mem_methods * instance has been installed using sqlite3_config(), and after * sqlite3_initialize() has been called, there's no way to retrieve it back with * sqlite3_config(). */ static struct mem_fault memFault; /* A version of sqlite3_mem_methods.xMalloc() that includes fault simulation * logic.*/ static void *mem_fault_malloc(int n) { void *p = NULL; if (!test_fault_tick(&memFault.fault)) { p = memFault.m.xMalloc(n); } return p; } /* A version of sqlite3_mem_methods.xRealloc() that includes fault simulation * logic. */ static void *mem_fault_realloc(void *old, int n) { void *p = NULL; if (!test_fault_tick(&memFault.fault)) { p = memFault.m.xRealloc(old, n); } return p; } /* The following method calls are passed directly through to the underlying * malloc system: * * xFree * xSize * xRoundup * xInit * xShutdown */ static void mem_fault_free(void *p) { memFault.m.xFree(p); } static int mem_fault_size(void *p) { return memFault.m.xSize(p); } static int mem_fault_roundup(int n) { return memFault.m.xRoundup(n); } static int mem_fault_init(void *p) { (void)p; return memFault.m.xInit(memFault.m.pAppData); } static void mem_fault_shutdown(void *p) { (void)p; memFault.m.xShutdown(memFault.m.pAppData); } /* Wrap the given SQLite memory management instance with the faulty memory * management interface. By default no faults will be triggered. */ static void mem_wrap(sqlite3_mem_methods *m, sqlite3_mem_methods *wrap) { test_fault_init(&memFault.fault); memFault.m = *m; wrap->xMalloc = mem_fault_malloc; wrap->xFree = mem_fault_free; wrap->xRealloc = mem_fault_realloc; wrap->xSize = mem_fault_size; wrap->xRoundup = mem_fault_roundup; wrap->xInit = mem_fault_init; wrap->xShutdown = mem_fault_shutdown; wrap->pAppData = &memFault; } /* Unwrap the given faulty memory management instance returning the original * one. */ static void mem_unwrap(sqlite3_mem_methods *wrap, sqlite3_mem_methods *m) { (void)wrap; *m = memFault.m; } /* Get the current number of outstanding malloc()'s without a matching free() * and the total number of used memory. */ static void mem_stats(int *malloc_count, int *memory_used) { int rc; int watermark; rc = sqlite3_status(SQLITE_STATUS_MALLOC_COUNT, malloc_count, &watermark, 1); if (rc != SQLITE_OK) { munit_errorf("can't get malloc count: %s", sqlite3_errstr(rc)); } rc = sqlite3_status(SQLITE_STATUS_MEMORY_USED, memory_used, &watermark, 1); if (rc != SQLITE_OK) { munit_errorf("can't get memory: %s\n:", sqlite3_errstr(rc)); } } /* Ensure we're starting from a clean memory state with no allocations and * optionally inject malloc failures. */ void test_heap_setup(const MunitParameter params[], void *user_data) { int malloc_count; int memory_used; const char *fault_delay; const char *fault_repeat; sqlite3_mem_methods mem; sqlite3_mem_methods mem_fault; int rc; (void)params; (void)user_data; /* Install the faulty malloc implementation */ rc = sqlite3_config(SQLITE_CONFIG_GETMALLOC, &mem); if (rc != SQLITE_OK) { munit_errorf("can't get default mem: %s", sqlite3_errstr(rc)); } mem_wrap(&mem, &mem_fault); rc = sqlite3_config(SQLITE_CONFIG_MALLOC, &mem_fault); if (rc != SQLITE_OK) { munit_errorf("can't set faulty mem: %s", sqlite3_errstr(rc)); } /* Check that memory is clean. */ mem_stats(&malloc_count, &memory_used); if (malloc_count > 0 || memory_used > 0) { munit_errorf( "setup memory:\n bytes: %11d\n allocations: %5d\n", malloc_count, memory_used); } /* Optionally inject memory allocation failures. */ fault_delay = munit_parameters_get(params, "mem-fault-delay"); fault_repeat = munit_parameters_get(params, "mem-fault-repeat"); munit_assert((fault_delay != NULL && fault_repeat != NULL) || (fault_delay == NULL && fault_repeat == NULL)); if (fault_delay != NULL) { test_heap_fault_config(atoi(fault_delay), atoi(fault_repeat)); } } /* Ensure we're starting leaving a clean memory behind. */ void test_heap_tear_down(void *data) { sqlite3_mem_methods mem; sqlite3_mem_methods mem_fault; int rc; (void)data; int malloc_count; int memory_used; mem_stats(&malloc_count, &memory_used); if (malloc_count > 0 || memory_used > 0) { munit_errorf( "teardown memory:\n bytes: %11d\n allocations: %5d\n", memory_used, malloc_count); } /* Restore default memory management. */ rc = sqlite3_config(SQLITE_CONFIG_GETMALLOC, &mem_fault); if (rc != SQLITE_OK) { munit_errorf("can't get faulty mem: %s", sqlite3_errstr(rc)); } mem_unwrap(&mem_fault, &mem); rc = sqlite3_config(SQLITE_CONFIG_MALLOC, &mem); if (rc != SQLITE_OK) { munit_errorf("can't reset default mem: %s", sqlite3_errstr(rc)); } } void test_heap_fault_config(int delay, int repeat) { test_fault_config(&memFault.fault, delay, repeat); } void test_heap_fault_enable() { test_fault_enable(&memFault.fault); } dqlite-1.9.1/test/lib/heap.h000066400000000000000000000023431416342771300156310ustar00rootroot00000000000000#ifndef DQLITE_TEST_HEAP_H #define DQLITE_TEST_HEAP_H #include "munit.h" /* Munit parameter defining the delay of the faulty memory implementation. */ #define TEST_HEAP_FAULT_DELAY "mem-fault-delay" /* Munit parameter defining the repeat of the faulty memory implementation. */ #define TEST_HEAP_FAULT_REPEAT "mem-fault-repeat" void test_heap_setup(const MunitParameter params[], void *user_data); void test_heap_tear_down(void *data); /* Configure the faulty memory management implementation so malloc()-related * functions start returning NULL pointers after 'delay' calls, and keep failing * for 'repeat' consecutive times. * * Note that the faults won't automatically take place, an explicit call to * test_mem_fault_enable() is needed. This allows configuration and actual * behavior to happen at different times (e.g. configure at test setup time and * enable at test case time). */ void test_heap_fault_config(int delay, int repeat); /* Enable the faulty behavior, which from this point on will honor the * parameters passed to test_mem_fault_config(). */ void test_heap_fault_enable(void); #define SETUP_HEAP test_heap_setup(params, user_data); #define TEAR_DOWN_HEAP test_heap_tear_down(data); #endif /* DQLITE_TEST_HEAP_H */ dqlite-1.9.1/test/lib/leader.h000066400000000000000000000013661416342771300161540ustar00rootroot00000000000000/** * Setup a test struct leader object. */ #ifndef TEST_LEADER_H #define TEST_LEADER_H #include "../../src/leader.h" #include "../../src/registry.h" #define FIXTURE_LEADER struct leader leader #define SETUP_LEADER \ { \ struct db *db; \ int rv; \ rv = registry__db_get(&f->registry, "test.db", &db); \ munit_assert_int(rv, ==, 0); \ rv = leader__init(&f->leader, db, &f->raft); \ munit_assert_int(rv, ==, 0); \ } #define TEAR_DOWN_LEADER leader__close(&f->leader) #endif /* TEST_LEADER_H */ dqlite-1.9.1/test/lib/logger.c000066400000000000000000000022001416342771300161560ustar00rootroot00000000000000#include #include #include "../../include/dqlite.h" #include "logger.h" #include "munit.h" void test_logger_emit(void *data, int level, const char *format, va_list args) { struct test_logger *t = data; char buf[1024]; const char *level_name; int i; (void)data; switch (level) { case DQLITE_DEBUG: level_name = "DEBUG"; break; case DQLITE_INFO: level_name = "INFO "; break; case DQLITE_WARN: level_name = "WARN "; break; case DQLITE_LOG_ERROR: level_name = "ERROR"; break; }; buf[0] = 0; sprintf(buf + strlen(buf), "%2d -> [%s] ", t->id, level_name); vsnprintf(buf + strlen(buf), 1024 - strlen(buf), format, args); munit_log(MUNIT_LOG_INFO, buf); return; snprintf(buf + strlen(buf), 1024 - strlen(buf), " "); for (i = strlen(buf); i < 85; i++) { buf[i] = ' '; } munit_log(MUNIT_LOG_INFO, buf); } void test_logger_setup(const MunitParameter params[], struct logger *l) { struct test_logger *t; (void)params; t = munit_malloc(sizeof *t); t->data = NULL; l->data = t; l->emit = test_logger_emit; } void test_logger_tear_down(struct logger *l) { free(l->data); } dqlite-1.9.1/test/lib/logger.h000066400000000000000000000010641416342771300161720ustar00rootroot00000000000000/** * Test logger. */ #ifndef TEST_LOGGER_H #define TEST_LOGGER_H #include "../../src/logger.h" #include "munit.h" void test_logger_setup(const MunitParameter params[], struct logger *l); void test_logger_tear_down(struct logger *l); struct test_logger { unsigned id; void *data; }; void test_logger_emit(void *data, int level, const char *fmt, va_list args); #define FIXTURE_LOGGER struct logger logger; #define SETUP_LOGGER test_logger_setup(params, &f->logger); #define TEAR_DOWN_LOGGER test_logger_tear_down(&f->logger); #endif /* TEST_LOGGER_H */ dqlite-1.9.1/test/lib/munit.c000066400000000000000000002044061416342771300160470ustar00rootroot00000000000000/* Copyright (c) 2013-2018 Evan Nemerson * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, copy, * modify, merge, publish, distribute, sublicense, and/or sell copies * of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ /*** Configuration ***/ /* This is just where the output from the test goes. It's really just * meant to let you choose stdout or stderr, but if anyone really want * to direct it to a file let me know, it would be fairly easy to * support. */ #if !defined(MUNIT_OUTPUT_FILE) # define MUNIT_OUTPUT_FILE stdout #endif /* This is a bit more useful; it tells µnit how to format the seconds in * timed tests. If your tests run for longer you might want to reduce * it, and if your computer is really fast and your tests are tiny you * can increase it. */ #if !defined(MUNIT_TEST_TIME_FORMAT) # define MUNIT_TEST_TIME_FORMAT "0.8f" #endif /* If you have long test names you might want to consider bumping * this. The result information takes 43 characters. */ #if !defined(MUNIT_TEST_NAME_LEN) # define MUNIT_TEST_NAME_LEN 37 #endif /* If you don't like the timing information, you can disable it by * defining MUNIT_DISABLE_TIMING. */ #if !defined(MUNIT_DISABLE_TIMING) # define MUNIT_ENABLE_TIMING #endif /*** End configuration ***/ #if defined(_POSIX_C_SOURCE) && (_POSIX_C_SOURCE < 200809L) # undef _POSIX_C_SOURCE #endif #if !defined(_POSIX_C_SOURCE) # define _POSIX_C_SOURCE 200809L #endif /* Solaris freaks out if you try to use a POSIX or SUS standard without * the "right" C standard. */ #if defined(_XOPEN_SOURCE) # undef _XOPEN_SOURCE #endif #if defined(__STDC_VERSION__) # if __STDC_VERSION__ >= 201112L # define _XOPEN_SOURCE 700 # elif __STDC_VERSION__ >= 199901L # define _XOPEN_SOURCE 600 # endif #endif /* Because, according to Microsoft, POSIX is deprecated. You've got * to appreciate the chutzpah. */ #if defined(_MSC_VER) && !defined(_CRT_NONSTDC_NO_DEPRECATE) # define _CRT_NONSTDC_NO_DEPRECATE #endif #if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) # include #elif defined(_WIN32) /* https://msdn.microsoft.com/en-us/library/tf4dy80a.aspx */ #endif #include #include #include #include #include #include #include #include #if !defined(MUNIT_NO_NL_LANGINFO) && !defined(_WIN32) #define MUNIT_NL_LANGINFO #include #include #include #endif #if !defined(_WIN32) # include # include # include #else # include # include # include # if !defined(STDERR_FILENO) # define STDERR_FILENO _fileno(stderr) # endif #endif #include "munit.h" #define MUNIT_STRINGIFY(x) #x #define MUNIT_XSTRINGIFY(x) MUNIT_STRINGIFY(x) #if defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__SUNPRO_CC) || defined(__IBMCPP__) # define MUNIT_THREAD_LOCAL __thread #elif (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201102L)) || defined(_Thread_local) # define MUNIT_THREAD_LOCAL _Thread_local #elif defined(_WIN32) # define MUNIT_THREAD_LOCAL __declspec(thread) #endif /* MSVC 12.0 will emit a warning at /W4 for code like 'do { ... } * while (0)', or 'do { ... } while (true)'. I'm pretty sure nobody * at Microsoft compiles with /W4. */ #if defined(_MSC_VER) && (_MSC_VER <= 1800) #pragma warning(disable: 4127) #endif #if defined(_WIN32) || defined(__EMSCRIPTEN__) # define MUNIT_NO_FORK #endif #if defined(__EMSCRIPTEN__) # define MUNIT_NO_BUFFER #endif /*** Logging ***/ static MunitLogLevel munit_log_level_visible = MUNIT_LOG_INFO; static MunitLogLevel munit_log_level_fatal = MUNIT_LOG_ERROR; #if defined(MUNIT_THREAD_LOCAL) static MUNIT_THREAD_LOCAL bool munit_error_jmp_buf_valid = false; static MUNIT_THREAD_LOCAL jmp_buf munit_error_jmp_buf; #endif /* At certain warning levels, mingw will trigger warnings about * suggesting the format attribute, which we've explicity *not* set * because it will then choke on our attempts to use the MS-specific * I64 modifier for size_t (which we have to use since MSVC doesn't * support the C99 z modifier). */ #if defined(__MINGW32__) || defined(__MINGW64__) # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wsuggest-attribute=format" #endif MUNIT_PRINTF(5,0) static void munit_logf_exv(MunitLogLevel level, FILE* fp, const char* filename, int line, const char* format, va_list ap) { if (level < munit_log_level_visible) return; switch (level) { case MUNIT_LOG_DEBUG: fputs("Debug", fp); break; case MUNIT_LOG_INFO: fputs("Info", fp); break; case MUNIT_LOG_WARNING: fputs("Warning", fp); break; case MUNIT_LOG_ERROR: fputs("Error", fp); break; default: munit_logf_ex(MUNIT_LOG_ERROR, filename, line, "Invalid log level (%d)", level); return; } fputs(": ", fp); if (filename != NULL) fprintf(fp, "%s:%d: ", filename, line); vfprintf(fp, format, ap); fputc('\n', fp); } MUNIT_PRINTF(3,4) static void munit_logf_internal(MunitLogLevel level, FILE* fp, const char* format, ...) { va_list ap; va_start(ap, format); munit_logf_exv(level, fp, NULL, 0, format, ap); va_end(ap); } static void munit_log_internal(MunitLogLevel level, FILE* fp, const char* message) { munit_logf_internal(level, fp, "%s", message); } void munit_logf_ex(MunitLogLevel level, const char* filename, int line, const char* format, ...) { va_list ap; va_start(ap, format); munit_logf_exv(level, stderr, filename, line, format, ap); va_end(ap); if (level >= munit_log_level_fatal) { #if defined(MUNIT_THREAD_LOCAL) if (munit_error_jmp_buf_valid) longjmp(munit_error_jmp_buf, 1); #endif abort(); } } void munit_errorf_ex(const char* filename, int line, const char* format, ...) { va_list ap; va_start(ap, format); munit_logf_exv(MUNIT_LOG_ERROR, stderr, filename, line, format, ap); va_end(ap); #if defined(MUNIT_THREAD_LOCAL) if (munit_error_jmp_buf_valid) longjmp(munit_error_jmp_buf, 1); #endif abort(); } #if defined(__MINGW32__) || defined(__MINGW64__) #pragma GCC diagnostic pop #endif #if !defined(MUNIT_STRERROR_LEN) # define MUNIT_STRERROR_LEN 80 #endif static void munit_log_errno(MunitLogLevel level, FILE* fp, const char* msg) { #if defined(MUNIT_NO_STRERROR_R) || (defined(__MINGW32__) && !defined(MINGW_HAS_SECURE_API)) munit_logf_internal(level, fp, "%s: %s (%d)", msg, strerror(errno), errno); #else char munit_error_str[MUNIT_STRERROR_LEN]; munit_error_str[0] = '\0'; #if !defined(_WIN32) strerror_r(errno, munit_error_str, MUNIT_STRERROR_LEN); #else strerror_s(munit_error_str, MUNIT_STRERROR_LEN, errno); #endif munit_logf_internal(level, fp, "%s: %s (%d)", msg, munit_error_str, errno); #endif } /*** Memory allocation ***/ void* munit_malloc_ex(const char* filename, int line, size_t size) { void* ptr; if (size == 0) return NULL; ptr = calloc(1, size); if (MUNIT_UNLIKELY(ptr == NULL)) { munit_logf_ex(MUNIT_LOG_ERROR, filename, line, "Failed to allocate %" MUNIT_SIZE_MODIFIER "u bytes.", size); } return ptr; } /*** Timer code ***/ #if defined(MUNIT_ENABLE_TIMING) #define psnip_uint64_t munit_uint64_t #define psnip_uint32_t munit_uint32_t /* Code copied from portable-snippets * . If you need to * change something, please do it there so we can keep the code in * sync. */ /* Clocks (v1) * Portable Snippets - https://gitub.com/nemequ/portable-snippets * Created by Evan Nemerson * * To the extent possible under law, the authors have waived all * copyright and related or neighboring rights to this code. For * details, see the Creative Commons Zero 1.0 Universal license at * https://creativecommons.org/publicdomain/zero/1.0/ */ #if !defined(PSNIP_CLOCK_H) #define PSNIP_CLOCK_H #if !defined(psnip_uint64_t) # include "../exact-int/exact-int.h" #endif #if !defined(PSNIP_CLOCK_STATIC_INLINE) # if defined(__GNUC__) # define PSNIP_CLOCK__COMPILER_ATTRIBUTES __attribute__((__unused__)) # else # define PSNIP_CLOCK__COMPILER_ATTRIBUTES # endif # define PSNIP_CLOCK__FUNCTION PSNIP_CLOCK__COMPILER_ATTRIBUTES static #endif enum PsnipClockType { /* This clock provides the current time, in units since 1970-01-01 * 00:00:00 UTC not including leap seconds. In other words, UNIX * time. Keep in mind that this clock doesn't account for leap * seconds, and can go backwards (think NTP adjustments). */ PSNIP_CLOCK_TYPE_WALL = 1, /* The CPU time is a clock which increases only when the current * process is active (i.e., it doesn't increment while blocking on * I/O). */ PSNIP_CLOCK_TYPE_CPU = 2, /* Monotonic time is always running (unlike CPU time), but it only ever moves forward unless you reboot the system. Things like NTP adjustments have no effect on this clock. */ PSNIP_CLOCK_TYPE_MONOTONIC = 3 }; struct PsnipClockTimespec { psnip_uint64_t seconds; psnip_uint64_t nanoseconds; }; /* Methods we support: */ #define PSNIP_CLOCK_METHOD_CLOCK_GETTIME 1 #define PSNIP_CLOCK_METHOD_TIME 2 #define PSNIP_CLOCK_METHOD_GETTIMEOFDAY 3 #define PSNIP_CLOCK_METHOD_QUERYPERFORMANCECOUNTER 4 #define PSNIP_CLOCK_METHOD_MACH_ABSOLUTE_TIME 5 #define PSNIP_CLOCK_METHOD_CLOCK 6 #define PSNIP_CLOCK_METHOD_GETPROCESSTIMES 7 #define PSNIP_CLOCK_METHOD_GETRUSAGE 8 #define PSNIP_CLOCK_METHOD_GETSYSTEMTIMEPRECISEASFILETIME 9 #define PSNIP_CLOCK_METHOD_GETTICKCOUNT64 10 #include #if defined(HEDLEY_UNREACHABLE) # define PSNIP_CLOCK_UNREACHABLE() HEDLEY_UNREACHABLE() #else # define PSNIP_CLOCK_UNREACHABLE() assert(0) #endif /* Choose an implementation */ /* #undef PSNIP_CLOCK_WALL_METHOD */ /* #undef PSNIP_CLOCK_CPU_METHOD */ /* #undef PSNIP_CLOCK_MONOTONIC_METHOD */ /* We want to be able to detect the libc implementation, so we include ( isn't available everywhere). */ #if defined(__unix__) || defined(__unix) || defined(__linux__) # include # include #endif #if defined(_POSIX_TIMERS) && (_POSIX_TIMERS > 0) /* These are known to work without librt. If you know of others * please let us know so we can add them. */ # if \ (defined(__GLIBC__) && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 17))) || \ (defined(__FreeBSD__)) # define PSNIP_CLOCK_HAVE_CLOCK_GETTIME # elif !defined(PSNIP_CLOCK_NO_LIBRT) # define PSNIP_CLOCK_HAVE_CLOCK_GETTIME # endif #endif #if defined(_WIN32) # if !defined(PSNIP_CLOCK_CPU_METHOD) # define PSNIP_CLOCK_CPU_METHOD PSNIP_CLOCK_METHOD_GETPROCESSTIMES # endif # if !defined(PSNIP_CLOCK_MONOTONIC_METHOD) # define PSNIP_CLOCK_MONOTONIC_METHOD PSNIP_CLOCK_METHOD_QUERYPERFORMANCECOUNTER # endif #endif #if defined(__MACH__) && !defined(__gnu_hurd__) # if !defined(PSNIP_CLOCK_MONOTONIC_METHOD) # define PSNIP_CLOCK_MONOTONIC_METHOD PSNIP_CLOCK_METHOD_MACH_ABSOLUTE_TIME # endif #endif #if defined(PSNIP_CLOCK_HAVE_CLOCK_GETTIME) # include # if !defined(PSNIP_CLOCK_WALL_METHOD) # if defined(CLOCK_REALTIME_PRECISE) # define PSNIP_CLOCK_WALL_METHOD PSNIP_CLOCK_METHOD_CLOCK_GETTIME # define PSNIP_CLOCK_CLOCK_GETTIME_WALL CLOCK_REALTIME_PRECISE # elif !defined(__sun) # define PSNIP_CLOCK_WALL_METHOD PSNIP_CLOCK_METHOD_CLOCK_GETTIME # define PSNIP_CLOCK_CLOCK_GETTIME_WALL CLOCK_REALTIME # endif # endif # if !defined(PSNIP_CLOCK_CPU_METHOD) # if defined(_POSIX_CPUTIME) || defined(CLOCK_PROCESS_CPUTIME_ID) # define PSNIP_CLOCK_CPU_METHOD PSNIP_CLOCK_METHOD_CLOCK_GETTIME # define PSNIP_CLOCK_CLOCK_GETTIME_CPU CLOCK_PROCESS_CPUTIME_ID # elif defined(CLOCK_VIRTUAL) # define PSNIP_CLOCK_CPU_METHOD PSNIP_CLOCK_METHOD_CLOCK_GETTIME # define PSNIP_CLOCK_CLOCK_GETTIME_CPU CLOCK_VIRTUAL # endif # endif # if !defined(PSNIP_CLOCK_MONOTONIC_METHOD) # if defined(CLOCK_MONOTONIC_RAW) # define PSNIP_CLOCK_MONOTONIC_METHOD PSNIP_CLOCK_METHOD_CLOCK_GETTIME # define PSNIP_CLOCK_CLOCK_GETTIME_MONOTONIC CLOCK_MONOTONIC # elif defined(CLOCK_MONOTONIC_PRECISE) # define PSNIP_CLOCK_MONOTONIC_METHOD PSNIP_CLOCK_METHOD_CLOCK_GETTIME # define PSNIP_CLOCK_CLOCK_GETTIME_MONOTONIC CLOCK_MONOTONIC_PRECISE # elif defined(_POSIX_MONOTONIC_CLOCK) || defined(CLOCK_MONOTONIC) # define PSNIP_CLOCK_MONOTONIC_METHOD PSNIP_CLOCK_METHOD_CLOCK_GETTIME # define PSNIP_CLOCK_CLOCK_GETTIME_MONOTONIC CLOCK_MONOTONIC # endif # endif #endif #if defined(_POSIX_VERSION) && (_POSIX_VERSION >= 200112L) # if !defined(PSNIP_CLOCK_WALL_METHOD) # define PSNIP_CLOCK_WALL_METHOD PSNIP_CLOCK_METHOD_GETTIMEOFDAY # endif #endif #if !defined(PSNIP_CLOCK_WALL_METHOD) # define PSNIP_CLOCK_WALL_METHOD PSNIP_CLOCK_METHOD_TIME #endif #if !defined(PSNIP_CLOCK_CPU_METHOD) # define PSNIP_CLOCK_CPU_METHOD PSNIP_CLOCK_METHOD_CLOCK #endif /* Primarily here for testing. */ #if !defined(PSNIP_CLOCK_MONOTONIC_METHOD) && defined(PSNIP_CLOCK_REQUIRE_MONOTONIC) # error No monotonic clock found. #endif /* Implementations */ #if \ (defined(PSNIP_CLOCK_CPU_METHOD) && (PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME)) || \ (defined(PSNIP_CLOCK_WALL_METHOD) && (PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME)) || \ (defined(PSNIP_CLOCK_MONOTONIC_METHOD) && (PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME)) || \ (defined(PSNIP_CLOCK_CPU_METHOD) && (PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_CLOCK)) || \ (defined(PSNIP_CLOCK_WALL_METHOD) && (PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_CLOCK)) || \ (defined(PSNIP_CLOCK_MONOTONIC_METHOD) && (PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_CLOCK)) || \ (defined(PSNIP_CLOCK_CPU_METHOD) && (PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_TIME)) || \ (defined(PSNIP_CLOCK_WALL_METHOD) && (PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_TIME)) || \ (defined(PSNIP_CLOCK_MONOTONIC_METHOD) && (PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_TIME)) # include #endif #if \ (defined(PSNIP_CLOCK_CPU_METHOD) && (PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_GETTIMEOFDAY)) || \ (defined(PSNIP_CLOCK_WALL_METHOD) && (PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_GETTIMEOFDAY)) || \ (defined(PSNIP_CLOCK_MONOTONIC_METHOD) && (PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_GETTIMEOFDAY)) # include #endif #if \ (defined(PSNIP_CLOCK_CPU_METHOD) && (PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_GETPROCESSTIMES)) || \ (defined(PSNIP_CLOCK_WALL_METHOD) && (PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_GETPROCESSTIMES)) || \ (defined(PSNIP_CLOCK_MONOTONIC_METHOD) && (PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_GETPROCESSTIMES)) || \ (defined(PSNIP_CLOCK_CPU_METHOD) && (PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_GETTICKCOUNT64)) || \ (defined(PSNIP_CLOCK_WALL_METHOD) && (PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_GETTICKCOUNT64)) || \ (defined(PSNIP_CLOCK_MONOTONIC_METHOD) && (PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_GETTICKCOUNT64)) # include #endif #if \ (defined(PSNIP_CLOCK_CPU_METHOD) && (PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_GETRUSAGE)) || \ (defined(PSNIP_CLOCK_WALL_METHOD) && (PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_GETRUSAGE)) || \ (defined(PSNIP_CLOCK_MONOTONIC_METHOD) && (PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_GETRUSAGE)) # include # include #endif #if \ (defined(PSNIP_CLOCK_CPU_METHOD) && (PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_MACH_ABSOLUTE_TIME)) || \ (defined(PSNIP_CLOCK_WALL_METHOD) && (PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_MACH_ABSOLUTE_TIME)) || \ (defined(PSNIP_CLOCK_MONOTONIC_METHOD) && (PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_MACH_ABSOLUTE_TIME)) # include # include # include #endif /*** Implementations ***/ #define PSNIP_CLOCK_NSEC_PER_SEC ((psnip_uint32_t) (1000000000ULL)) #if \ (defined(PSNIP_CLOCK_CPU_METHOD) && (PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME)) || \ (defined(PSNIP_CLOCK_WALL_METHOD) && (PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME)) || \ (defined(PSNIP_CLOCK_MONOTONIC_METHOD) && (PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME)) PSNIP_CLOCK__FUNCTION psnip_uint32_t psnip_clock__clock_getres (clockid_t clk_id) { struct timespec res; int r; r = clock_getres(clk_id, &res); if (r != 0) return 0; return (psnip_uint32_t) (PSNIP_CLOCK_NSEC_PER_SEC / res.tv_nsec); } PSNIP_CLOCK__FUNCTION int psnip_clock__clock_gettime (clockid_t clk_id, struct PsnipClockTimespec* res) { struct timespec ts; if (clock_gettime(clk_id, &ts) != 0) return -10; res->seconds = (psnip_uint64_t) (ts.tv_sec); res->nanoseconds = (psnip_uint64_t) (ts.tv_nsec); return 0; } #endif PSNIP_CLOCK__FUNCTION psnip_uint32_t psnip_clock_wall_get_precision (void) { #if !defined(PSNIP_CLOCK_WALL_METHOD) return 0; #elif defined(PSNIP_CLOCK_WALL_METHOD) && PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME return psnip_clock__clock_getres(PSNIP_CLOCK_CLOCK_GETTIME_WALL); #elif defined(PSNIP_CLOCK_WALL_METHOD) && PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_GETTIMEOFDAY return 1000000; #elif defined(PSNIP_CLOCK_WALL_METHOD) && PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_TIME return 1; #else return 0; #endif } PSNIP_CLOCK__FUNCTION int psnip_clock_wall_get_time (struct PsnipClockTimespec* res) { (void) res; #if !defined(PSNIP_CLOCK_WALL_METHOD) return -2; #elif defined(PSNIP_CLOCK_WALL_METHOD) && PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME return psnip_clock__clock_gettime(PSNIP_CLOCK_CLOCK_GETTIME_WALL, res); #elif defined(PSNIP_CLOCK_WALL_METHOD) && PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_TIME res->seconds = time(NULL); res->nanoseconds = 0; #elif defined(PSNIP_CLOCK_WALL_METHOD) && PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_GETTIMEOFDAY struct timeval tv; if (gettimeofday(&tv, NULL) != 0) return -6; res->seconds = tv.tv_sec; res->nanoseconds = tv.tv_usec * 1000; #else return -2; #endif return 0; } PSNIP_CLOCK__FUNCTION psnip_uint32_t psnip_clock_cpu_get_precision (void) { #if !defined(PSNIP_CLOCK_CPU_METHOD) return 0; #elif defined(PSNIP_CLOCK_CPU_METHOD) && PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME return psnip_clock__clock_getres(PSNIP_CLOCK_CLOCK_GETTIME_CPU); #elif defined(PSNIP_CLOCK_CPU_METHOD) && PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_CLOCK return CLOCKS_PER_SEC; #elif defined(PSNIP_CLOCK_CPU_METHOD) && PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_GETPROCESSTIMES return PSNIP_CLOCK_NSEC_PER_SEC / 100; #else return 0; #endif } PSNIP_CLOCK__FUNCTION int psnip_clock_cpu_get_time (struct PsnipClockTimespec* res) { #if !defined(PSNIP_CLOCK_CPU_METHOD) (void) res; return -2; #elif defined(PSNIP_CLOCK_CPU_METHOD) && PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME return psnip_clock__clock_gettime(PSNIP_CLOCK_CLOCK_GETTIME_CPU, res); #elif defined(PSNIP_CLOCK_CPU_METHOD) && PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_CLOCK clock_t t = clock(); if (t == ((clock_t) -1)) return -5; res->seconds = t / CLOCKS_PER_SEC; res->nanoseconds = (t % CLOCKS_PER_SEC) * (PSNIP_CLOCK_NSEC_PER_SEC / CLOCKS_PER_SEC); #elif defined(PSNIP_CLOCK_CPU_METHOD) && PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_GETPROCESSTIMES FILETIME CreationTime, ExitTime, KernelTime, UserTime; LARGE_INTEGER date, adjust; if (!GetProcessTimes(GetCurrentProcess(), &CreationTime, &ExitTime, &KernelTime, &UserTime)) return -7; /* http://www.frenk.com/2009/12/convert-filetime-to-unix-timestamp/ */ date.HighPart = UserTime.dwHighDateTime; date.LowPart = UserTime.dwLowDateTime; adjust.QuadPart = 11644473600000 * 10000; date.QuadPart -= adjust.QuadPart; res->seconds = date.QuadPart / 10000000; res->nanoseconds = (date.QuadPart % 10000000) * (PSNIP_CLOCK_NSEC_PER_SEC / 100); #elif PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_GETRUSAGE struct rusage usage; if (getrusage(RUSAGE_SELF, &usage) != 0) return -8; res->seconds = usage.ru_utime.tv_sec; res->nanoseconds = tv.tv_usec * 1000; #else (void) res; return -2; #endif return 0; } PSNIP_CLOCK__FUNCTION psnip_uint32_t psnip_clock_monotonic_get_precision (void) { #if !defined(PSNIP_CLOCK_MONOTONIC_METHOD) return 0; #elif defined(PSNIP_CLOCK_MONOTONIC_METHOD) && PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME return psnip_clock__clock_getres(PSNIP_CLOCK_CLOCK_GETTIME_MONOTONIC); #elif defined(PSNIP_CLOCK_MONOTONIC_METHOD) && PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_MACH_ABSOLUTE_TIME static mach_timebase_info_data_t tbi = { 0, }; if (tbi.denom == 0) mach_timebase_info(&tbi); return (psnip_uint32_t) (tbi.numer / tbi.denom); #elif defined(PSNIP_CLOCK_MONOTONIC_METHOD) && PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_GETTICKCOUNT64 return 1000; #elif defined(PSNIP_CLOCK_MONOTONIC_METHOD) && PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_QUERYPERFORMANCECOUNTER LARGE_INTEGER Frequency; QueryPerformanceFrequency(&Frequency); return (psnip_uint32_t) ((Frequency.QuadPart > PSNIP_CLOCK_NSEC_PER_SEC) ? PSNIP_CLOCK_NSEC_PER_SEC : Frequency.QuadPart); #else return 0; #endif } PSNIP_CLOCK__FUNCTION int psnip_clock_monotonic_get_time (struct PsnipClockTimespec* res) { #if !defined(PSNIP_CLOCK_MONOTONIC_METHOD) (void) res; return -2; #elif defined(PSNIP_CLOCK_MONOTONIC_METHOD) && PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME return psnip_clock__clock_gettime(PSNIP_CLOCK_CLOCK_GETTIME_MONOTONIC, res); #elif defined(PSNIP_CLOCK_MONOTONIC_METHOD) && PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_MACH_ABSOLUTE_TIME psnip_uint64_t nsec = mach_absolute_time(); static mach_timebase_info_data_t tbi = { 0, }; if (tbi.denom == 0) mach_timebase_info(&tbi); nsec *= ((psnip_uint64_t) tbi.numer) / ((psnip_uint64_t) tbi.denom); res->seconds = nsec / PSNIP_CLOCK_NSEC_PER_SEC; res->nanoseconds = nsec % PSNIP_CLOCK_NSEC_PER_SEC; #elif defined(PSNIP_CLOCK_MONOTONIC_METHOD) && PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_QUERYPERFORMANCECOUNTER LARGE_INTEGER t, f; if (QueryPerformanceCounter(&t) == 0) return -12; QueryPerformanceFrequency(&f); res->seconds = t.QuadPart / f.QuadPart; res->nanoseconds = t.QuadPart % f.QuadPart; if (f.QuadPart > PSNIP_CLOCK_NSEC_PER_SEC) res->nanoseconds /= f.QuadPart / PSNIP_CLOCK_NSEC_PER_SEC; else res->nanoseconds *= PSNIP_CLOCK_NSEC_PER_SEC / f.QuadPart; #elif defined(PSNIP_CLOCK_MONOTONIC_METHOD) && PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_GETTICKCOUNT64 const ULONGLONG msec = GetTickCount64(); res->seconds = msec / 1000; res->nanoseconds = sec % 1000; #else return -2; #endif return 0; } /* Returns the number of ticks per second for the specified clock. * For example, a clock with millisecond precision would return 1000, * and a clock with 1 second (such as the time() function) would * return 1. * * If the requested clock isn't available, it will return 0. * Hopefully this will be rare, but if it happens to you please let us * know so we can work on finding a way to support your system. * * Note that different clocks on the same system often have a * different precisions. */ PSNIP_CLOCK__FUNCTION psnip_uint32_t psnip_clock_get_precision (enum PsnipClockType clock_type) { switch (clock_type) { case PSNIP_CLOCK_TYPE_MONOTONIC: return psnip_clock_monotonic_get_precision (); case PSNIP_CLOCK_TYPE_CPU: return psnip_clock_cpu_get_precision (); case PSNIP_CLOCK_TYPE_WALL: return psnip_clock_wall_get_precision (); } PSNIP_CLOCK_UNREACHABLE(); return 0; } /* Set the provided timespec to the requested time. Returns 0 on * success, or a negative value on failure. */ PSNIP_CLOCK__FUNCTION int psnip_clock_get_time (enum PsnipClockType clock_type, struct PsnipClockTimespec* res) { assert(res != NULL); switch (clock_type) { case PSNIP_CLOCK_TYPE_MONOTONIC: return psnip_clock_monotonic_get_time (res); case PSNIP_CLOCK_TYPE_CPU: return psnip_clock_cpu_get_time (res); case PSNIP_CLOCK_TYPE_WALL: return psnip_clock_wall_get_time (res); } return -1; } #endif /* !defined(PSNIP_CLOCK_H) */ static psnip_uint64_t munit_clock_get_elapsed(struct PsnipClockTimespec* start, struct PsnipClockTimespec* end) { psnip_uint64_t r = (end->seconds - start->seconds) * PSNIP_CLOCK_NSEC_PER_SEC; if (end->nanoseconds < start->nanoseconds) { r -= (start->nanoseconds - end->nanoseconds); } else { r += (end->nanoseconds - start->nanoseconds); } return r; } #else # include #endif /* defined(MUNIT_ENABLE_TIMING) */ /*** PRNG stuff ***/ /* This is (unless I screwed up, which is entirely possible) the * version of PCG with 32-bit state. It was chosen because it has a * small enough state that we should reliably be able to use CAS * instead of requiring a lock for thread-safety. * * If I did screw up, I probably will not bother changing it unless * there is a significant bias. It's really not important this be * particularly strong, as long as it is fairly random it's much more * important that it be reproducible, so bug reports have a better * chance of being reproducible. */ #if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) && !defined(__STDC_NO_ATOMICS__) && !defined(__EMSCRIPTEN__) && (!defined(__GNUC_MINOR__) || (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ > 8)) # define HAVE_STDATOMIC #elif defined(__clang__) # if __has_extension(c_atomic) # define HAVE_CLANG_ATOMICS # endif #endif /* Workaround for http://llvm.org/bugs/show_bug.cgi?id=26911 */ #if defined(__clang__) && defined(_WIN32) # undef HAVE_STDATOMIC # if defined(__c2__) # undef HAVE_CLANG_ATOMICS # endif #endif #if defined(_OPENMP) # define ATOMIC_UINT32_T uint32_t # define ATOMIC_UINT32_INIT(x) (x) #elif defined(HAVE_STDATOMIC) # include # define ATOMIC_UINT32_T _Atomic uint32_t # define ATOMIC_UINT32_INIT(x) ATOMIC_VAR_INIT(x) #elif defined(HAVE_CLANG_ATOMICS) # define ATOMIC_UINT32_T _Atomic uint32_t # define ATOMIC_UINT32_INIT(x) (x) #elif defined(_WIN32) # define ATOMIC_UINT32_T volatile LONG # define ATOMIC_UINT32_INIT(x) (x) #else # define ATOMIC_UINT32_T volatile uint32_t # define ATOMIC_UINT32_INIT(x) (x) #endif static ATOMIC_UINT32_T munit_rand_state = ATOMIC_UINT32_INIT(42); #if defined(_OPENMP) static inline void munit_atomic_store(ATOMIC_UINT32_T* dest, ATOMIC_UINT32_T value) { #pragma omp critical (munit_atomics) *dest = value; } static inline uint32_t munit_atomic_load(ATOMIC_UINT32_T* src) { int ret; #pragma omp critical (munit_atomics) ret = *src; return ret; } static inline uint32_t munit_atomic_cas(ATOMIC_UINT32_T* dest, ATOMIC_UINT32_T* expected, ATOMIC_UINT32_T desired) { bool ret; #pragma omp critical (munit_atomics) { if (*dest == *expected) { *dest = desired; ret = true; } else { ret = false; } } return ret; } #elif defined(HAVE_STDATOMIC) # define munit_atomic_store(dest, value) atomic_store(dest, value) # define munit_atomic_load(src) atomic_load(src) # define munit_atomic_cas(dest, expected, value) atomic_compare_exchange_weak(dest, expected, value) #elif defined(HAVE_CLANG_ATOMICS) # define munit_atomic_store(dest, value) __c11_atomic_store(dest, value, __ATOMIC_SEQ_CST) # define munit_atomic_load(src) __c11_atomic_load(src, __ATOMIC_SEQ_CST) # define munit_atomic_cas(dest, expected, value) __c11_atomic_compare_exchange_weak(dest, expected, value, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST) #elif defined(__GNUC__) && (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7) # define munit_atomic_store(dest, value) __atomic_store_n(dest, value, __ATOMIC_SEQ_CST) # define munit_atomic_load(src) __atomic_load_n(src, __ATOMIC_SEQ_CST) # define munit_atomic_cas(dest, expected, value) __atomic_compare_exchange_n(dest, expected, value, true, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST) #elif defined(__GNUC__) && (__GNUC__ >= 4) # define munit_atomic_store(dest,value) do { *(dest) = (value); } while (0) # define munit_atomic_load(src) (*(src)) # define munit_atomic_cas(dest, expected, value) __sync_bool_compare_and_swap(dest, *expected, value) #elif defined(_WIN32) /* Untested */ # define munit_atomic_store(dest,value) do { *(dest) = (value); } while (0) # define munit_atomic_load(src) (*(src)) # define munit_atomic_cas(dest, expected, value) InterlockedCompareExchange((dest), (value), *(expected)) #else # warning No atomic implementation, PRNG will not be thread-safe # define munit_atomic_store(dest, value) do { *(dest) = (value); } while (0) # define munit_atomic_load(src) (*(src)) static inline bool munit_atomic_cas(ATOMIC_UINT32_T* dest, ATOMIC_UINT32_T* expected, ATOMIC_UINT32_T desired) { if (*dest == *expected) { *dest = desired; return true; } else { return false; } } #endif #define MUNIT_PRNG_MULTIPLIER (747796405U) #define MUNIT_PRNG_INCREMENT (1729U) static munit_uint32_t munit_rand_next_state(munit_uint32_t state) { return state * MUNIT_PRNG_MULTIPLIER + MUNIT_PRNG_INCREMENT; } static munit_uint32_t munit_rand_from_state(munit_uint32_t state) { munit_uint32_t res = ((state >> ((state >> 28) + 4)) ^ state) * (277803737U); res ^= res >> 22; return res; } void munit_rand_seed(munit_uint32_t seed) { munit_uint32_t state = munit_rand_next_state(seed + MUNIT_PRNG_INCREMENT); munit_atomic_store(&munit_rand_state, state); } static munit_uint32_t munit_rand_generate_seed(void) { munit_uint32_t seed, state; #if defined(MUNIT_ENABLE_TIMING) struct PsnipClockTimespec wc = { 0, 0 }; psnip_clock_get_time(PSNIP_CLOCK_TYPE_WALL, &wc); seed = (munit_uint32_t) wc.nanoseconds; #else seed = (munit_uint32_t) time(NULL); #endif state = munit_rand_next_state(seed + MUNIT_PRNG_INCREMENT); return munit_rand_from_state(state); } static munit_uint32_t munit_rand_state_uint32(munit_uint32_t* state) { const munit_uint32_t old = *state; *state = munit_rand_next_state(old); return munit_rand_from_state(old); } munit_uint32_t munit_rand_uint32(void) { munit_uint32_t old, state; do { old = munit_atomic_load(&munit_rand_state); state = munit_rand_next_state(old); } while (!munit_atomic_cas(&munit_rand_state, &old, state)); return munit_rand_from_state(old); } static void munit_rand_state_memory(munit_uint32_t* state, size_t size, munit_uint8_t data[MUNIT_ARRAY_PARAM(size)]) { size_t members_remaining = size / sizeof(munit_uint32_t); size_t bytes_remaining = size % sizeof(munit_uint32_t); munit_uint8_t* b = data; munit_uint32_t rv; while (members_remaining-- > 0) { rv = munit_rand_state_uint32(state); memcpy(b, &rv, sizeof(munit_uint32_t)); b += sizeof(munit_uint32_t); } if (bytes_remaining != 0) { rv = munit_rand_state_uint32(state); memcpy(b, &rv, bytes_remaining); } } void munit_rand_memory(size_t size, munit_uint8_t data[MUNIT_ARRAY_PARAM(size)]) { munit_uint32_t old, state; do { state = old = munit_atomic_load(&munit_rand_state); munit_rand_state_memory(&state, size, data); } while (!munit_atomic_cas(&munit_rand_state, &old, state)); } static munit_uint32_t munit_rand_state_at_most(munit_uint32_t* state, munit_uint32_t salt, munit_uint32_t max) { /* We want (UINT32_MAX + 1) % max, which in unsigned arithmetic is the same * as (UINT32_MAX + 1 - max) % max = -max % max. We compute -max using not * to avoid compiler warnings. */ const munit_uint32_t min = (~max + 1U) % max; munit_uint32_t x; if (max == (~((munit_uint32_t) 0U))) return munit_rand_state_uint32(state) ^ salt; max++; do { x = munit_rand_state_uint32(state) ^ salt; } while (x < min); return x % max; } static munit_uint32_t munit_rand_at_most(munit_uint32_t salt, munit_uint32_t max) { munit_uint32_t old, state; munit_uint32_t retval; do { state = old = munit_atomic_load(&munit_rand_state); retval = munit_rand_state_at_most(&state, salt, max); } while (!munit_atomic_cas(&munit_rand_state, &old, state)); return retval; } int munit_rand_int_range(int min, int max) { munit_uint64_t range = (munit_uint64_t) max - (munit_uint64_t) min; if (min > max) return munit_rand_int_range(max, min); if (range > (~((munit_uint32_t) 0U))) range = (~((munit_uint32_t) 0U)); return min + munit_rand_at_most(0, (munit_uint32_t) range); } double munit_rand_double(void) { munit_uint32_t old, state; double retval = 0.0; do { state = old = munit_atomic_load(&munit_rand_state); /* See http://mumble.net/~campbell/tmp/random_real.c for how to do * this right. Patches welcome if you feel that this is too * biased. */ retval = munit_rand_state_uint32(&state) / ((~((munit_uint32_t) 0U)) + 1.0); } while (!munit_atomic_cas(&munit_rand_state, &old, state)); return retval; } /*** Test suite handling ***/ typedef struct { unsigned int successful; unsigned int skipped; unsigned int failed; unsigned int errored; #if defined(MUNIT_ENABLE_TIMING) munit_uint64_t cpu_clock; munit_uint64_t wall_clock; #endif } MunitReport; typedef struct { const char* prefix; const MunitSuite* suite; const char** tests; munit_uint32_t seed; unsigned int iterations; MunitParameter* parameters; bool single_parameter_mode; void* user_data; MunitReport report; bool colorize; bool fork; bool show_stderr; bool fatal_failures; } MunitTestRunner; const char* munit_parameters_get(const MunitParameter params[], const char* key) { const MunitParameter* param; for (param = params ; param != NULL && param->name != NULL ; param++) if (strcmp(param->name, key) == 0) return param->value; return NULL; } #if defined(MUNIT_ENABLE_TIMING) static void munit_print_time(FILE* fp, munit_uint64_t nanoseconds) { fprintf(fp, "%" MUNIT_TEST_TIME_FORMAT, ((double) nanoseconds) / ((double) PSNIP_CLOCK_NSEC_PER_SEC)); } #endif /* Add a paramter to an array of parameters. */ static MunitResult munit_parameters_add(size_t* params_size, MunitParameter* params[MUNIT_ARRAY_PARAM(*params_size)], char* name, char* value) { *params = realloc(*params, sizeof(MunitParameter) * (*params_size + 2)); if (*params == NULL) return MUNIT_ERROR; (*params)[*params_size].name = name; (*params)[*params_size].value = value; (*params_size)++; (*params)[*params_size].name = NULL; (*params)[*params_size].value = NULL; return MUNIT_OK; } /* Concatenate two strings, but just return one of the components * unaltered if the other is NULL or "". */ static char* munit_maybe_concat(size_t* len, char* prefix, char* suffix) { char* res; size_t res_l; const size_t prefix_l = prefix != NULL ? strlen(prefix) : 0; const size_t suffix_l = suffix != NULL ? strlen(suffix) : 0; if (prefix_l == 0 && suffix_l == 0) { res = NULL; res_l = 0; } else if (prefix_l == 0 && suffix_l != 0) { res = suffix; res_l = suffix_l; } else if (prefix_l != 0 && suffix_l == 0) { res = prefix; res_l = prefix_l; } else { res_l = prefix_l + suffix_l; res = malloc(res_l + 1); memcpy(res, prefix, prefix_l); memcpy(res + prefix_l, suffix, suffix_l); res[res_l] = 0; } if (len != NULL) *len = res_l; return res; } /* Possbily free a string returned by munit_maybe_concat. */ static void munit_maybe_free_concat(char* s, const char* prefix, const char* suffix) { if (prefix != s && suffix != s) free(s); } /* Cheap string hash function, just used to salt the PRNG. */ static munit_uint32_t munit_str_hash(const char* name) { const char *p; munit_uint32_t h = 5381U; for (p = name; *p != '\0'; p++) h = (h << 5) + h + *p; return h; } static void munit_splice(int from, int to) { munit_uint8_t buf[1024]; #if !defined(_WIN32) ssize_t len; ssize_t bytes_written; ssize_t write_res; #else int len; int bytes_written; int write_res; #endif do { len = read(from, buf, sizeof(buf)); if (len > 0) { bytes_written = 0; do { write_res = write(to, buf + bytes_written, len - bytes_written); if (write_res < 0) break; bytes_written += write_res; } while (bytes_written < len); } else break; } while (true); } /* This is the part that should be handled in the child process */ static MunitResult munit_test_runner_exec(MunitTestRunner* runner, const MunitTest* test, const MunitParameter params[], MunitReport* report) { unsigned int iterations = runner->iterations; MunitResult result = MUNIT_FAIL; #if defined(MUNIT_ENABLE_TIMING) struct PsnipClockTimespec wall_clock_begin = { 0, 0 }, wall_clock_end = { 0, 0 }; struct PsnipClockTimespec cpu_clock_begin = { 0, 0 }, cpu_clock_end = { 0, 0 }; #endif unsigned int i = 0; if ((test->options & MUNIT_TEST_OPTION_SINGLE_ITERATION) == MUNIT_TEST_OPTION_SINGLE_ITERATION) iterations = 1; else if (iterations == 0) iterations = runner->suite->iterations; munit_rand_seed(runner->seed); do { void* data = (test->setup == NULL) ? runner->user_data : test->setup(params, runner->user_data); #if defined(MUNIT_ENABLE_TIMING) psnip_clock_get_time(PSNIP_CLOCK_TYPE_WALL, &wall_clock_begin); psnip_clock_get_time(PSNIP_CLOCK_TYPE_CPU, &cpu_clock_begin); #endif result = test->test(params, data); #if defined(MUNIT_ENABLE_TIMING) psnip_clock_get_time(PSNIP_CLOCK_TYPE_WALL, &wall_clock_end); psnip_clock_get_time(PSNIP_CLOCK_TYPE_CPU, &cpu_clock_end); #endif if (test->tear_down != NULL) test->tear_down(data); if (MUNIT_LIKELY(result == MUNIT_OK)) { report->successful++; #if defined(MUNIT_ENABLE_TIMING) report->wall_clock += munit_clock_get_elapsed(&wall_clock_begin, &wall_clock_end); report->cpu_clock += munit_clock_get_elapsed(&cpu_clock_begin, &cpu_clock_end); #endif } else { switch ((int) result) { case MUNIT_SKIP: report->skipped++; break; case MUNIT_FAIL: report->failed++; break; case MUNIT_ERROR: report->errored++; break; default: break; } break; } } while (++i < iterations); return result; } #if defined(MUNIT_EMOTICON) # define MUNIT_RESULT_STRING_OK ":)" # define MUNIT_RESULT_STRING_SKIP ":|" # define MUNIT_RESULT_STRING_FAIL ":(" # define MUNIT_RESULT_STRING_ERROR ":o" # define MUNIT_RESULT_STRING_TODO ":/" #else # define MUNIT_RESULT_STRING_OK "OK " # define MUNIT_RESULT_STRING_SKIP "SKIP " # define MUNIT_RESULT_STRING_FAIL "FAIL " # define MUNIT_RESULT_STRING_ERROR "ERROR" # define MUNIT_RESULT_STRING_TODO "TODO " #endif static void munit_test_runner_print_color(const MunitTestRunner* runner, const char* string, char color) { if (runner->colorize) fprintf(MUNIT_OUTPUT_FILE, "\x1b[3%cm%s\x1b[39m", color, string); else fputs(string, MUNIT_OUTPUT_FILE); } #if !defined(MUNIT_NO_BUFFER) static int munit_replace_stderr(FILE* stderr_buf) { if (stderr_buf != NULL) { const int orig_stderr = dup(STDERR_FILENO); int errfd = fileno(stderr_buf); if (MUNIT_UNLIKELY(errfd == -1)) { exit(EXIT_FAILURE); } dup2(errfd, STDERR_FILENO); return orig_stderr; } return -1; } static void munit_restore_stderr(int orig_stderr) { if (orig_stderr != -1) { dup2(orig_stderr, STDERR_FILENO); close(orig_stderr); } } #endif /* !defined(MUNIT_NO_BUFFER) */ /* Run a test with the specified parameters. */ static void munit_test_runner_run_test_with_params(MunitTestRunner* runner, const MunitTest* test, const MunitParameter params[]) { MunitResult result = MUNIT_OK; MunitReport report = { 0, 0, 0, 0, #if defined(MUNIT_ENABLE_TIMING) 0, 0 #endif }; unsigned int output_l; bool first; const MunitParameter* param; FILE* stderr_buf; #if !defined(MUNIT_NO_FORK) int pipefd[2]; pid_t fork_pid; ssize_t bytes_written = 0; ssize_t write_res; ssize_t bytes_read = 0; ssize_t read_res; int status = 0; pid_t changed_pid; #endif if (params != NULL) { output_l = 2; fputs(" ", MUNIT_OUTPUT_FILE); first = true; for (param = params ; param != NULL && param->name != NULL ; param++) { if (!first) { fputs(", ", MUNIT_OUTPUT_FILE); output_l += 2; } else { first = false; } output_l += fprintf(MUNIT_OUTPUT_FILE, "%s=%s", param->name, param->value); } while (output_l++ < MUNIT_TEST_NAME_LEN) { fputc(' ', MUNIT_OUTPUT_FILE); } } fflush(MUNIT_OUTPUT_FILE); stderr_buf = NULL; #if !defined(_WIN32) || defined(__MINGW32__) stderr_buf = tmpfile(); #else tmpfile_s(&stderr_buf); #endif if (stderr_buf == NULL) { munit_log_errno(MUNIT_LOG_ERROR, stderr, "unable to create buffer for stderr"); result = MUNIT_ERROR; goto print_result; } #if !defined(MUNIT_NO_FORK) if (runner->fork) { pipefd[0] = -1; pipefd[1] = -1; if (pipe(pipefd) != 0) { munit_log_errno(MUNIT_LOG_ERROR, stderr, "unable to create pipe"); result = MUNIT_ERROR; goto print_result; } fork_pid = fork(); if (fork_pid == 0) { int orig_stderr; close(pipefd[0]); orig_stderr = munit_replace_stderr(stderr_buf); munit_test_runner_exec(runner, test, params, &report); /* Note that we don't restore stderr. This is so we can buffer * things written to stderr later on (such as by * asan/tsan/ubsan, valgrind, etc.) */ close(orig_stderr); do { write_res = write(pipefd[1], ((munit_uint8_t*) (&report)) + bytes_written, sizeof(report) - bytes_written); if (write_res < 0) { if (stderr_buf != NULL) { munit_log_errno(MUNIT_LOG_ERROR, stderr, "unable to write to pipe"); } exit(EXIT_FAILURE); } bytes_written += write_res; } while ((size_t) bytes_written < sizeof(report)); if (stderr_buf != NULL) fclose(stderr_buf); close(pipefd[1]); exit(EXIT_SUCCESS); } else if (fork_pid == -1) { close(pipefd[0]); close(pipefd[1]); if (stderr_buf != NULL) { munit_log_errno(MUNIT_LOG_ERROR, stderr, "unable to fork"); } report.errored++; result = MUNIT_ERROR; } else { close(pipefd[1]); do { read_res = read(pipefd[0], ((munit_uint8_t*) (&report)) + bytes_read, sizeof(report) - bytes_read); if (read_res < 1) break; bytes_read += read_res; } while (bytes_read < (ssize_t) sizeof(report)); changed_pid = waitpid(fork_pid, &status, 0); if (MUNIT_LIKELY(changed_pid == fork_pid) && MUNIT_LIKELY(WIFEXITED(status))) { if (bytes_read != sizeof(report)) { munit_logf_internal(MUNIT_LOG_ERROR, stderr_buf, "child exited unexpectedly with status %d", WEXITSTATUS(status)); report.errored++; } else if (WEXITSTATUS(status) != EXIT_SUCCESS) { munit_logf_internal(MUNIT_LOG_ERROR, stderr_buf, "child exited with status %d", WEXITSTATUS(status)); report.errored++; } } else { if (WIFSIGNALED(status)) { #if defined(_XOPEN_VERSION) && (_XOPEN_VERSION >= 700) munit_logf_internal(MUNIT_LOG_ERROR, stderr_buf, "child killed by signal %d (%s)", WTERMSIG(status), strsignal(WTERMSIG(status))); #else munit_logf_internal(MUNIT_LOG_ERROR, stderr_buf, "child killed by signal %d", WTERMSIG(status)); #endif } else if (WIFSTOPPED(status)) { munit_logf_internal(MUNIT_LOG_ERROR, stderr_buf, "child stopped by signal %d", WSTOPSIG(status)); } report.errored++; } close(pipefd[0]); waitpid(fork_pid, NULL, 0); } } else #endif { #if !defined(MUNIT_NO_BUFFER) const volatile int orig_stderr = munit_replace_stderr(stderr_buf); #endif #if defined(MUNIT_THREAD_LOCAL) if (MUNIT_UNLIKELY(setjmp(munit_error_jmp_buf) != 0)) { result = MUNIT_FAIL; report.failed++; } else { munit_error_jmp_buf_valid = true; result = munit_test_runner_exec(runner, test, params, &report); } #else result = munit_test_runner_exec(runner, test, params, &report); #endif #if !defined(MUNIT_NO_BUFFER) munit_restore_stderr(orig_stderr); #endif /* Here just so that the label is used on Windows and we don't get * a warning */ goto print_result; } print_result: fputs("[ ", MUNIT_OUTPUT_FILE); if ((test->options & MUNIT_TEST_OPTION_TODO) == MUNIT_TEST_OPTION_TODO) { if (report.failed != 0 || report.errored != 0 || report.skipped != 0) { munit_test_runner_print_color(runner, MUNIT_RESULT_STRING_TODO, '3'); result = MUNIT_OK; } else { munit_test_runner_print_color(runner, MUNIT_RESULT_STRING_ERROR, '1'); if (MUNIT_LIKELY(stderr_buf != NULL)) munit_log_internal(MUNIT_LOG_ERROR, stderr_buf, "Test marked TODO, but was successful."); runner->report.failed++; result = MUNIT_ERROR; } } else if (report.failed > 0) { munit_test_runner_print_color(runner, MUNIT_RESULT_STRING_FAIL, '1'); runner->report.failed++; result = MUNIT_FAIL; } else if (report.errored > 0) { munit_test_runner_print_color(runner, MUNIT_RESULT_STRING_ERROR, '1'); runner->report.errored++; result = MUNIT_ERROR; } else if (report.skipped > 0) { munit_test_runner_print_color(runner, MUNIT_RESULT_STRING_SKIP, '3'); runner->report.skipped++; result = MUNIT_SKIP; } else if (report.successful > 1) { munit_test_runner_print_color(runner, MUNIT_RESULT_STRING_OK, '2'); #if defined(MUNIT_ENABLE_TIMING) fputs(" ] [ ", MUNIT_OUTPUT_FILE); munit_print_time(MUNIT_OUTPUT_FILE, report.wall_clock / report.successful); fputs(" / ", MUNIT_OUTPUT_FILE); munit_print_time(MUNIT_OUTPUT_FILE, report.cpu_clock / report.successful); fprintf(MUNIT_OUTPUT_FILE, " CPU ]\n %-" MUNIT_XSTRINGIFY(MUNIT_TEST_NAME_LEN) "s Total: [ ", ""); munit_print_time(MUNIT_OUTPUT_FILE, report.wall_clock); fputs(" / ", MUNIT_OUTPUT_FILE); munit_print_time(MUNIT_OUTPUT_FILE, report.cpu_clock); fputs(" CPU", MUNIT_OUTPUT_FILE); #endif runner->report.successful++; result = MUNIT_OK; } else if (report.successful > 0) { munit_test_runner_print_color(runner, MUNIT_RESULT_STRING_OK, '2'); #if defined(MUNIT_ENABLE_TIMING) fputs(" ] [ ", MUNIT_OUTPUT_FILE); munit_print_time(MUNIT_OUTPUT_FILE, report.wall_clock); fputs(" / ", MUNIT_OUTPUT_FILE); munit_print_time(MUNIT_OUTPUT_FILE, report.cpu_clock); fputs(" CPU", MUNIT_OUTPUT_FILE); #endif runner->report.successful++; result = MUNIT_OK; } fputs(" ]\n", MUNIT_OUTPUT_FILE); if (stderr_buf != NULL) { if (result == MUNIT_FAIL || result == MUNIT_ERROR || runner->show_stderr) { fflush(MUNIT_OUTPUT_FILE); rewind(stderr_buf); munit_splice(fileno(stderr_buf), STDERR_FILENO); fflush(stderr); } fclose(stderr_buf); } } static void munit_test_runner_run_test_wild(MunitTestRunner* runner, const MunitTest* test, const char* test_name, MunitParameter* params, MunitParameter* p) { const MunitParameterEnum* pe; char** values; MunitParameter* next; for (pe = test->parameters ; pe != NULL && pe->name != NULL ; pe++) { if (p->name == pe->name) break; } if (pe == NULL) return; for (values = pe->values ; *values != NULL ; values++) { next = p + 1; p->value = *values; if (next->name == NULL) { munit_test_runner_run_test_with_params(runner, test, params); } else { munit_test_runner_run_test_wild(runner, test, test_name, params, next); } if (runner->fatal_failures && (runner->report.failed != 0 || runner->report.errored != 0)) break; } } /* Run a single test, with every combination of parameters * requested. */ static void munit_test_runner_run_test(MunitTestRunner* runner, const MunitTest* test, const char* prefix) { char* test_name = munit_maybe_concat(NULL, (char*) prefix, (char*) test->name); /* The array of parameters to pass to * munit_test_runner_run_test_with_params */ MunitParameter* params = NULL; size_t params_l = 0; /* Wildcard parameters are parameters which have possible values * specified in the test, but no specific value was passed to the * CLI. That means we want to run the test once for every * possible combination of parameter values or, if --single was * passed to the CLI, a single time with a random set of * parameters. */ MunitParameter* wild_params = NULL; size_t wild_params_l = 0; const MunitParameterEnum* pe; const MunitParameter* cli_p; bool filled; unsigned int possible; char** vals; size_t first_wild; const MunitParameter* wp; int pidx; munit_rand_seed(runner->seed); fprintf(MUNIT_OUTPUT_FILE, "%-" MUNIT_XSTRINGIFY(MUNIT_TEST_NAME_LEN) "s", test_name); if (test->parameters == NULL) { /* No parameters. Simple, nice. */ munit_test_runner_run_test_with_params(runner, test, NULL); } else { fputc('\n', MUNIT_OUTPUT_FILE); for (pe = test->parameters ; pe != NULL && pe->name != NULL ; pe++) { /* Did we received a value for this parameter from the CLI? */ filled = false; for (cli_p = runner->parameters ; cli_p != NULL && cli_p->name != NULL ; cli_p++) { if (strcmp(cli_p->name, pe->name) == 0) { if (MUNIT_UNLIKELY(munit_parameters_add(¶ms_l, ¶ms, pe->name, cli_p->value) != MUNIT_OK)) goto cleanup; filled = true; break; } } if (filled) continue; /* Nothing from CLI, is the enum NULL/empty? We're not a * fuzzer… */ if (pe->values == NULL || pe->values[0] == NULL) continue; /* If --single was passed to the CLI, choose a value from the * list of possibilities randomly. */ if (runner->single_parameter_mode) { possible = 0; for (vals = pe->values ; *vals != NULL ; vals++) possible++; /* We want the tests to be reproducible, even if you're only * running a single test, but we don't want every test with * the same number of parameters to choose the same parameter * number, so use the test name as a primitive salt. */ pidx = munit_rand_at_most(munit_str_hash(test_name), possible - 1); if (MUNIT_UNLIKELY(munit_parameters_add(¶ms_l, ¶ms, pe->name, pe->values[pidx]) != MUNIT_OK)) goto cleanup; } else { /* We want to try every permutation. Put in a placeholder * entry, we'll iterate through them later. */ if (MUNIT_UNLIKELY(munit_parameters_add(&wild_params_l, &wild_params, pe->name, NULL) != MUNIT_OK)) goto cleanup; } } if (wild_params_l != 0) { first_wild = params_l; for (wp = wild_params ; wp != NULL && wp->name != NULL ; wp++) { for (pe = test->parameters ; pe != NULL && pe->name != NULL && pe->values != NULL ; pe++) { if (strcmp(wp->name, pe->name) == 0) { if (MUNIT_UNLIKELY(munit_parameters_add(¶ms_l, ¶ms, pe->name, pe->values[0]) != MUNIT_OK)) goto cleanup; } } } munit_test_runner_run_test_wild(runner, test, test_name, params, params + first_wild); } else { munit_test_runner_run_test_with_params(runner, test, params); } cleanup: free(params); free(wild_params); } munit_maybe_free_concat(test_name, prefix, test->name); } /* Recurse through the suite and run all the tests. If a list of * tests to run was provied on the command line, run only those * tests. */ static void munit_test_runner_run_suite(MunitTestRunner* runner, const MunitSuite* suite, const char* prefix) { size_t pre_l; char* pre = munit_maybe_concat(&pre_l, (char*) prefix, (char*) suite->prefix); const MunitTest* test; const char** test_name; const MunitSuite* child_suite; /* Run the tests. */ for (test = suite->tests ; test != NULL && test->test != NULL ; test++) { if (runner->tests != NULL) { /* Specific tests were requested on the CLI */ for (test_name = runner->tests ; test_name != NULL && *test_name != NULL ; test_name++) { if ((pre_l == 0 || strncmp(pre, *test_name, pre_l) == 0) && strncmp(test->name, *test_name + pre_l, strlen(*test_name + pre_l)) == 0) { munit_test_runner_run_test(runner, test, pre); if (runner->fatal_failures && (runner->report.failed != 0 || runner->report.errored != 0)) goto cleanup; } } } else { /* Run all tests */ munit_test_runner_run_test(runner, test, pre); } } if (runner->fatal_failures && (runner->report.failed != 0 || runner->report.errored != 0)) goto cleanup; /* Run any child suites. */ for (child_suite = suite->suites ; child_suite != NULL && child_suite->prefix != NULL ; child_suite++) { munit_test_runner_run_suite(runner, child_suite, pre); } cleanup: munit_maybe_free_concat(pre, prefix, suite->prefix); } static void munit_test_runner_run(MunitTestRunner* runner) { munit_test_runner_run_suite(runner, runner->suite, NULL); } static void munit_print_help(int argc, char* const argv[MUNIT_ARRAY_PARAM(argc)], void* user_data, const MunitArgument arguments[]) { const MunitArgument* arg; (void) argc; printf("USAGE: %s [OPTIONS...] [TEST...]\n\n", argv[0]); puts(" --seed SEED\n" " Value used to seed the PRNG. Must be a 32-bit integer in decimal\n" " notation with no separators (commas, decimals, spaces, etc.), or\n" " hexidecimal prefixed by \"0x\".\n" " --iterations N\n" " Run each test N times. 0 means the default number.\n" " --param name value\n" " A parameter key/value pair which will be passed to any test with\n" " takes a parameter of that name. If not provided, the test will be\n" " run once for each possible parameter value.\n" " --list Write a list of all available tests.\n" " --list-params\n" " Write a list of all available tests and their possible parameters.\n" " --single Run each parameterized test in a single configuration instead of\n" " every possible combination\n" " --log-visible debug|info|warning|error\n" " --log-fatal debug|info|warning|error\n" " Set the level at which messages of different severities are visible,\n" " or cause the test to terminate.\n" #if !defined(MUNIT_NO_FORK) " --no-fork Do not execute tests in a child process. If this option is supplied\n" " and a test crashes (including by failing an assertion), no further\n" " tests will be performed.\n" #endif " --fatal-failures\n" " Stop executing tests as soon as a failure is found.\n" " --show-stderr\n" " Show data written to stderr by the tests, even if the test succeeds.\n" " --color auto|always|never\n" " Colorize (or don't) the output.\n" /* 12345678901234567890123456789012345678901234567890123456789012345678901234567890 */ " --help Print this help message and exit.\n"); #if defined(MUNIT_NL_LANGINFO) setlocale(LC_ALL, ""); fputs((strcasecmp("UTF-8", nl_langinfo(CODESET)) == 0) ? "µnit" : "munit", stdout); #else puts("munit"); #endif printf(" %d.%d.%d\n" "Full documentation at: https://nemequ.github.io/munit/\n", (MUNIT_CURRENT_VERSION >> 16) & 0xff, (MUNIT_CURRENT_VERSION >> 8) & 0xff, (MUNIT_CURRENT_VERSION >> 0) & 0xff); for (arg = arguments ; arg != NULL && arg->name != NULL ; arg++) arg->write_help(arg, user_data); } static const MunitArgument* munit_arguments_find(const MunitArgument arguments[], const char* name) { const MunitArgument* arg; for (arg = arguments ; arg != NULL && arg->name != NULL ; arg++) if (strcmp(arg->name, name) == 0) return arg; return NULL; } static void munit_suite_list_tests(const MunitSuite* suite, bool show_params, const char* prefix) { size_t pre_l; char* pre = munit_maybe_concat(&pre_l, (char*) prefix, (char*) suite->prefix); const MunitTest* test; const MunitParameterEnum* params; bool first; char** val; const MunitSuite* child_suite; for (test = suite->tests ; test != NULL && test->name != NULL ; test++) { if (pre != NULL) fputs(pre, stdout); puts(test->name); if (show_params) { for (params = test->parameters ; params != NULL && params->name != NULL ; params++) { fprintf(stdout, " - %s: ", params->name); if (params->values == NULL) { puts("Any"); } else { first = true; for (val = params->values ; *val != NULL ; val++ ) { if(!first) { fputs(", ", stdout); } else { first = false; } fputs(*val, stdout); } putc('\n', stdout); } } } } for (child_suite = suite->suites ; child_suite != NULL && child_suite->prefix != NULL ; child_suite++) { munit_suite_list_tests(child_suite, show_params, pre); } munit_maybe_free_concat(pre, prefix, suite->prefix); } static bool munit_stream_supports_ansi(FILE *stream) { #if !defined(_WIN32) return isatty(fileno(stream)); #else #if !defined(__MINGW32__) size_t ansicon_size = 0; #endif if (isatty(fileno(stream))) { #if !defined(__MINGW32__) getenv_s(&ansicon_size, NULL, 0, "ANSICON"); return ansicon_size != 0; #else return getenv("ANSICON") != NULL; #endif } return false; #endif } int munit_suite_main_custom(const MunitSuite* suite, void* user_data, int argc, char* const argv[MUNIT_ARRAY_PARAM(argc)], const MunitArgument arguments[]) { int result = EXIT_FAILURE; MunitTestRunner runner; size_t parameters_size = 0; size_t tests_size = 0; int arg; char* envptr; unsigned long ts; char* endptr; unsigned long long iterations; MunitLogLevel level; const MunitArgument* argument; const char** runner_tests; unsigned int tests_run; unsigned int tests_total; runner.prefix = NULL; runner.suite = NULL; runner.tests = NULL; runner.seed = 0; runner.iterations = 0; runner.parameters = NULL; runner.single_parameter_mode = false; runner.user_data = NULL; runner.report.successful = 0; runner.report.skipped = 0; runner.report.failed = 0; runner.report.errored = 0; #if defined(MUNIT_ENABLE_TIMING) runner.report.cpu_clock = 0; runner.report.wall_clock = 0; #endif runner.colorize = false; #if !defined(_WIN32) runner.fork = true; #else runner.fork = false; #endif runner.show_stderr = false; runner.fatal_failures = false; runner.suite = suite; runner.user_data = user_data; runner.seed = munit_rand_generate_seed(); runner.colorize = munit_stream_supports_ansi(MUNIT_OUTPUT_FILE); for (arg = 1 ; arg < argc ; arg++) { if (strncmp("--", argv[arg], 2) == 0) { if (strcmp("seed", argv[arg] + 2) == 0) { if (arg + 1 >= argc) { munit_logf_internal(MUNIT_LOG_ERROR, stderr, "%s requires an argument", argv[arg]); goto cleanup; } envptr = argv[arg + 1]; ts = strtoul(argv[arg + 1], &envptr, 0); if (*envptr != '\0' || ts > (~((munit_uint32_t) 0U))) { munit_logf_internal(MUNIT_LOG_ERROR, stderr, "invalid value ('%s') passed to %s", argv[arg + 1], argv[arg]); goto cleanup; } runner.seed = (munit_uint32_t) ts; arg++; } else if (strcmp("iterations", argv[arg] + 2) == 0) { if (arg + 1 >= argc) { munit_logf_internal(MUNIT_LOG_ERROR, stderr, "%s requires an argument", argv[arg]); goto cleanup; } endptr = argv[arg + 1]; iterations = strtoul(argv[arg + 1], &endptr, 0); if (*endptr != '\0' || iterations > UINT_MAX) { munit_logf_internal(MUNIT_LOG_ERROR, stderr, "invalid value ('%s') passed to %s", argv[arg + 1], argv[arg]); goto cleanup; } runner.iterations = (unsigned int) iterations; arg++; } else if (strcmp("param", argv[arg] + 2) == 0) { if (arg + 2 >= argc) { munit_logf_internal(MUNIT_LOG_ERROR, stderr, "%s requires two arguments", argv[arg]); goto cleanup; } runner.parameters = realloc(runner.parameters, sizeof(MunitParameter) * (parameters_size + 2)); if (runner.parameters == NULL) { munit_log_internal(MUNIT_LOG_ERROR, stderr, "failed to allocate memory"); goto cleanup; } runner.parameters[parameters_size].name = (char*) argv[arg + 1]; runner.parameters[parameters_size].value = (char*) argv[arg + 2]; parameters_size++; runner.parameters[parameters_size].name = NULL; runner.parameters[parameters_size].value = NULL; arg += 2; } else if (strcmp("color", argv[arg] + 2) == 0) { if (arg + 1 >= argc) { munit_logf_internal(MUNIT_LOG_ERROR, stderr, "%s requires an argument", argv[arg]); goto cleanup; } if (strcmp(argv[arg + 1], "always") == 0) runner.colorize = true; else if (strcmp(argv[arg + 1], "never") == 0) runner.colorize = false; else if (strcmp(argv[arg + 1], "auto") == 0) runner.colorize = munit_stream_supports_ansi(MUNIT_OUTPUT_FILE); else { munit_logf_internal(MUNIT_LOG_ERROR, stderr, "invalid value ('%s') passed to %s", argv[arg + 1], argv[arg]); goto cleanup; } arg++; } else if (strcmp("help", argv[arg] + 2) == 0) { munit_print_help(argc, argv, user_data, arguments); result = EXIT_SUCCESS; goto cleanup; } else if (strcmp("single", argv[arg] + 2) == 0) { runner.single_parameter_mode = true; } else if (strcmp("show-stderr", argv[arg] + 2) == 0) { runner.show_stderr = true; #if !defined(_WIN32) } else if (strcmp("no-fork", argv[arg] + 2) == 0) { runner.fork = false; #endif } else if (strcmp("fatal-failures", argv[arg] + 2) == 0) { runner.fatal_failures = true; } else if (strcmp("log-visible", argv[arg] + 2) == 0 || strcmp("log-fatal", argv[arg] + 2) == 0) { if (arg + 1 >= argc) { munit_logf_internal(MUNIT_LOG_ERROR, stderr, "%s requires an argument", argv[arg]); goto cleanup; } if (strcmp(argv[arg + 1], "debug") == 0) level = MUNIT_LOG_DEBUG; else if (strcmp(argv[arg + 1], "info") == 0) level = MUNIT_LOG_INFO; else if (strcmp(argv[arg + 1], "warning") == 0) level = MUNIT_LOG_WARNING; else if (strcmp(argv[arg + 1], "error") == 0) level = MUNIT_LOG_ERROR; else { munit_logf_internal(MUNIT_LOG_ERROR, stderr, "invalid value ('%s') passed to %s", argv[arg + 1], argv[arg]); goto cleanup; } if (strcmp("log-visible", argv[arg] + 2) == 0) munit_log_level_visible = level; else munit_log_level_fatal = level; arg++; } else if (strcmp("list", argv[arg] + 2) == 0) { munit_suite_list_tests(suite, false, NULL); result = EXIT_SUCCESS; goto cleanup; } else if (strcmp("list-params", argv[arg] + 2) == 0) { munit_suite_list_tests(suite, true, NULL); result = EXIT_SUCCESS; goto cleanup; } else { argument = munit_arguments_find(arguments, argv[arg] + 2); if (argument == NULL) { munit_logf_internal(MUNIT_LOG_ERROR, stderr, "unknown argument ('%s')", argv[arg]); goto cleanup; } if (!argument->parse_argument(suite, user_data, &arg, argc, argv)) goto cleanup; } } else { runner_tests = realloc((void*) runner.tests, sizeof(char*) * (tests_size + 2)); if (runner_tests == NULL) { munit_log_internal(MUNIT_LOG_ERROR, stderr, "failed to allocate memory"); goto cleanup; } runner.tests = runner_tests; runner.tests[tests_size++] = argv[arg]; runner.tests[tests_size] = NULL; } } fflush(stderr); fprintf(MUNIT_OUTPUT_FILE, "Running test suite with seed 0x%08" PRIx32 "...\n", runner.seed); munit_test_runner_run(&runner); tests_run = runner.report.successful + runner.report.failed + runner.report.errored; tests_total = tests_run + runner.report.skipped; if (tests_run == 0) { fprintf(stderr, "No tests run, %d (100%%) skipped.\n", runner.report.skipped); } else { fprintf(MUNIT_OUTPUT_FILE, "%d of %d (%0.0f%%) tests successful, %d (%0.0f%%) test skipped.\n", runner.report.successful, tests_run, (((double) runner.report.successful) / ((double) tests_run)) * 100.0, runner.report.skipped, (((double) runner.report.skipped) / ((double) tests_total)) * 100.0); } if (runner.report.failed == 0 && runner.report.errored == 0) { result = EXIT_SUCCESS; } cleanup: free(runner.parameters); free((void*) runner.tests); return result; } int munit_suite_main(const MunitSuite* suite, void* user_data, int argc, char* const argv[MUNIT_ARRAY_PARAM(argc)]) { return munit_suite_main_custom(suite, user_data, argc, argv, NULL); } dqlite-1.9.1/test/lib/munit.h000066400000000000000000000422131416342771300160500ustar00rootroot00000000000000/* µnit Testing Framework * Copyright (c) 2013-2017 Evan Nemerson * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, copy, * modify, merge, publish, distribute, sublicense, and/or sell copies * of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #if !defined(MUNIT_H) #define MUNIT_H #include #include #define MUNIT_VERSION(major, minor, revision) \ (((major) << 16) | ((minor) << 8) | (revision)) #define MUNIT_CURRENT_VERSION MUNIT_VERSION(0, 4, 1) #if defined(_MSC_VER) && (_MSC_VER < 1600) # define munit_int8_t __int8 # define munit_uint8_t unsigned __int8 # define munit_int16_t __int16 # define munit_uint16_t unsigned __int16 # define munit_int32_t __int32 # define munit_uint32_t unsigned __int32 # define munit_int64_t __int64 # define munit_uint64_t unsigned __int64 #else # include # define munit_int8_t int8_t # define munit_uint8_t uint8_t # define munit_int16_t int16_t # define munit_uint16_t uint16_t # define munit_int32_t int32_t # define munit_uint32_t uint32_t # define munit_int64_t int64_t # define munit_uint64_t uint64_t #endif #if defined(_MSC_VER) && (_MSC_VER < 1800) # if !defined(PRIi8) # define PRIi8 "i" # endif # if !defined(PRIi16) # define PRIi16 "i" # endif # if !defined(PRIi32) # define PRIi32 "i" # endif # if !defined(PRIi64) # define PRIi64 "I64i" # endif # if !defined(PRId8) # define PRId8 "d" # endif # if !defined(PRId16) # define PRId16 "d" # endif # if !defined(PRId32) # define PRId32 "d" # endif # if !defined(PRId64) # define PRId64 "I64d" # endif # if !defined(PRIx8) # define PRIx8 "x" # endif # if !defined(PRIx16) # define PRIx16 "x" # endif # if !defined(PRIx32) # define PRIx32 "x" # endif # if !defined(PRIx64) # define PRIx64 "I64x" # endif # if !defined(PRIu8) # define PRIu8 "u" # endif # if !defined(PRIu16) # define PRIu16 "u" # endif # if !defined(PRIu32) # define PRIu32 "u" # endif # if !defined(PRIu64) # define PRIu64 "I64u" # endif # if !defined(bool) # define bool int # endif # if !defined(true) # define true (!0) # endif # if !defined(false) # define false (!!0) # endif #else # include # include #endif #if defined(__cplusplus) extern "C" { #endif #if defined(__GNUC__) # define MUNIT_LIKELY(expr) (__builtin_expect ((expr), 1)) # define MUNIT_UNLIKELY(expr) (__builtin_expect ((expr), 0)) # define MUNIT_UNUSED __attribute__((__unused__)) #else # define MUNIT_LIKELY(expr) (expr) # define MUNIT_UNLIKELY(expr) (expr) # define MUNIT_UNUSED #endif #if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && !defined(__PGI) # define MUNIT_ARRAY_PARAM(name) name #else # define MUNIT_ARRAY_PARAM(name) #endif #if !defined(_WIN32) # define MUNIT_SIZE_MODIFIER "z" # define MUNIT_CHAR_MODIFIER "hh" # define MUNIT_SHORT_MODIFIER "h" #else # if defined(_M_X64) || defined(__amd64__) # define MUNIT_SIZE_MODIFIER "I64" # else # define MUNIT_SIZE_MODIFIER "" # endif # define MUNIT_CHAR_MODIFIER "" # define MUNIT_SHORT_MODIFIER "" #endif #if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L # define MUNIT_NO_RETURN _Noreturn #elif defined(__GNUC__) # define MUNIT_NO_RETURN __attribute__((__noreturn__)) #elif defined(_MSC_VER) # define MUNIT_NO_RETURN __declspec(noreturn) #else # define MUNIT_NO_RETURN #endif #if defined(_MSC_VER) && (_MSC_VER >= 1500) # define MUNIT__PUSH_DISABLE_MSVC_C4127 __pragma(warning(push)) __pragma(warning(disable:4127)) # define MUNIT__POP_DISABLE_MSVC_C4127 __pragma(warning(pop)) #else # define MUNIT__PUSH_DISABLE_MSVC_C4127 # define MUNIT__POP_DISABLE_MSVC_C4127 #endif typedef enum { MUNIT_LOG_DEBUG, MUNIT_LOG_INFO, MUNIT_LOG_WARNING, MUNIT_LOG_ERROR } MunitLogLevel; #if defined(__GNUC__) && !defined(__MINGW32__) # define MUNIT_PRINTF(string_index, first_to_check) __attribute__((format (printf, string_index, first_to_check))) #else # define MUNIT_PRINTF(string_index, first_to_check) #endif MUNIT_PRINTF(4, 5) void munit_logf_ex(MunitLogLevel level, const char* filename, int line, const char* format, ...); #define munit_logf(level, format, ...) \ munit_logf_ex(level, __FILE__, __LINE__, format, __VA_ARGS__) #define munit_log(level, msg) \ munit_logf(level, "%s", msg) MUNIT_NO_RETURN MUNIT_PRINTF(3, 4) void munit_errorf_ex(const char* filename, int line, const char* format, ...); #define munit_errorf(format, ...) \ munit_errorf_ex(__FILE__, __LINE__, format, __VA_ARGS__) #define munit_error(msg) \ munit_errorf("%s", msg) #define munit_assert(expr) \ do { \ if (!MUNIT_LIKELY(expr)) { \ munit_error("assertion failed: " #expr); \ } \ MUNIT__PUSH_DISABLE_MSVC_C4127 \ } while (0) \ MUNIT__POP_DISABLE_MSVC_C4127 #define munit_assert_true(expr) \ do { \ if (!MUNIT_LIKELY(expr)) { \ munit_error("assertion failed: " #expr " is not true"); \ } \ MUNIT__PUSH_DISABLE_MSVC_C4127 \ } while (0) \ MUNIT__POP_DISABLE_MSVC_C4127 #define munit_assert_false(expr) \ do { \ if (!MUNIT_LIKELY(!(expr))) { \ munit_error("assertion failed: " #expr " is not false"); \ } \ MUNIT__PUSH_DISABLE_MSVC_C4127 \ } while (0) \ MUNIT__POP_DISABLE_MSVC_C4127 #define munit_assert_type_full(prefix, suffix, T, fmt, a, op, b) \ do { \ T munit_tmp_a_ = (a); \ T munit_tmp_b_ = (b); \ if (!(munit_tmp_a_ op munit_tmp_b_)) { \ munit_errorf("assertion failed: %s %s %s (" prefix "%" fmt suffix " %s " prefix "%" fmt suffix ")", \ #a, #op, #b, munit_tmp_a_, #op, munit_tmp_b_); \ } \ MUNIT__PUSH_DISABLE_MSVC_C4127 \ } while (0) \ MUNIT__POP_DISABLE_MSVC_C4127 #define munit_assert_type(T, fmt, a, op, b) \ munit_assert_type_full("", "", T, fmt, a, op, b) #define munit_assert_char(a, op, b) \ munit_assert_type_full("'\\x", "'", char, "02" MUNIT_CHAR_MODIFIER "x", a, op, b) #define munit_assert_uchar(a, op, b) \ munit_assert_type_full("'\\x", "'", unsigned char, "02" MUNIT_CHAR_MODIFIER "x", a, op, b) #define munit_assert_short(a, op, b) \ munit_assert_type(short, MUNIT_SHORT_MODIFIER "d", a, op, b) #define munit_assert_ushort(a, op, b) \ munit_assert_type(unsigned short, MUNIT_SHORT_MODIFIER "u", a, op, b) #define munit_assert_int(a, op, b) \ munit_assert_type(int, "d", a, op, b) #define munit_assert_uint(a, op, b) \ munit_assert_type(unsigned int, "u", a, op, b) #define munit_assert_long(a, op, b) \ munit_assert_type(long int, "ld", a, op, b) #define munit_assert_ulong(a, op, b) \ munit_assert_type(unsigned long int, "lu", a, op, b) #define munit_assert_llong(a, op, b) \ munit_assert_type(long long int, "lld", a, op, b) #define munit_assert_ullong(a, op, b) \ munit_assert_type(unsigned long long int, "llu", a, op, b) #define munit_assert_size(a, op, b) \ munit_assert_type(size_t, MUNIT_SIZE_MODIFIER "u", a, op, b) #define munit_assert_float(a, op, b) \ munit_assert_type(float, "f", a, op, b) #define munit_assert_double(a, op, b) \ munit_assert_type(double, "g", a, op, b) #define munit_assert_ptr(a, op, b) \ munit_assert_type(const void*, "p", a, op, b) #define munit_assert_int8(a, op, b) \ munit_assert_type(munit_int8_t, PRIi8, a, op, b) #define munit_assert_uint8(a, op, b) \ munit_assert_type(munit_uint8_t, PRIu8, a, op, b) #define munit_assert_int16(a, op, b) \ munit_assert_type(munit_int16_t, PRIi16, a, op, b) #define munit_assert_uint16(a, op, b) \ munit_assert_type(munit_uint16_t, PRIu16, a, op, b) #define munit_assert_int32(a, op, b) \ munit_assert_type(munit_int32_t, PRIi32, a, op, b) #define munit_assert_uint32(a, op, b) \ munit_assert_type(munit_uint32_t, PRIu32, a, op, b) #define munit_assert_int64(a, op, b) \ munit_assert_type(munit_int64_t, PRIi64, a, op, b) #define munit_assert_uint64(a, op, b) \ munit_assert_type(munit_uint64_t, PRIu64, a, op, b) #define munit_assert_double_equal(a, b, precision) \ do { \ const double munit_tmp_a_ = (a); \ const double munit_tmp_b_ = (b); \ const double munit_tmp_diff_ = ((munit_tmp_a_ - munit_tmp_b_) < 0) ? \ -(munit_tmp_a_ - munit_tmp_b_) : \ (munit_tmp_a_ - munit_tmp_b_); \ if (MUNIT_UNLIKELY(munit_tmp_diff_ > 1e-##precision)) { \ munit_errorf("assertion failed: %s == %s (%0." #precision "g == %0." #precision "g)", \ #a, #b, munit_tmp_a_, munit_tmp_b_); \ } \ MUNIT__PUSH_DISABLE_MSVC_C4127 \ } while (0) \ MUNIT__POP_DISABLE_MSVC_C4127 #include #define munit_assert_string_equal(a, b) \ do { \ const char* munit_tmp_a_ = a; \ const char* munit_tmp_b_ = b; \ if (MUNIT_UNLIKELY(strcmp(munit_tmp_a_, munit_tmp_b_) != 0)) { \ munit_errorf("assertion failed: string %s == %s (\"%s\" == \"%s\")", \ #a, #b, munit_tmp_a_, munit_tmp_b_); \ } \ MUNIT__PUSH_DISABLE_MSVC_C4127 \ } while (0) \ MUNIT__POP_DISABLE_MSVC_C4127 #define munit_assert_string_not_equal(a, b) \ do { \ const char* munit_tmp_a_ = a; \ const char* munit_tmp_b_ = b; \ if (MUNIT_UNLIKELY(strcmp(munit_tmp_a_, munit_tmp_b_) == 0)) { \ munit_errorf("assertion failed: string %s != %s (\"%s\" == \"%s\")", \ #a, #b, munit_tmp_a_, munit_tmp_b_); \ } \ MUNIT__PUSH_DISABLE_MSVC_C4127 \ } while (0) \ MUNIT__POP_DISABLE_MSVC_C4127 #define munit_assert_memory_equal(size, a, b) \ do { \ const unsigned char* munit_tmp_a_ = (const unsigned char*) (a); \ const unsigned char* munit_tmp_b_ = (const unsigned char*) (b); \ const size_t munit_tmp_size_ = (size); \ if (MUNIT_UNLIKELY(memcmp(munit_tmp_a_, munit_tmp_b_, munit_tmp_size_)) != 0) { \ size_t munit_tmp_pos_; \ for (munit_tmp_pos_ = 0 ; munit_tmp_pos_ < munit_tmp_size_ ; munit_tmp_pos_++) { \ if (munit_tmp_a_[munit_tmp_pos_] != munit_tmp_b_[munit_tmp_pos_]) { \ munit_errorf("assertion failed: memory %s == %s, at offset %" MUNIT_SIZE_MODIFIER "u", \ #a, #b, munit_tmp_pos_); \ break; \ } \ } \ } \ MUNIT__PUSH_DISABLE_MSVC_C4127 \ } while (0) \ MUNIT__POP_DISABLE_MSVC_C4127 #define munit_assert_memory_not_equal(size, a, b) \ do { \ const unsigned char* munit_tmp_a_ = (const unsigned char*) (a); \ const unsigned char* munit_tmp_b_ = (const unsigned char*) (b); \ const size_t munit_tmp_size_ = (size); \ if (MUNIT_UNLIKELY(memcmp(munit_tmp_a_, munit_tmp_b_, munit_tmp_size_)) == 0) { \ munit_errorf("assertion failed: memory %s != %s (%zu bytes)", \ #a, #b, munit_tmp_size_); \ } \ MUNIT__PUSH_DISABLE_MSVC_C4127 \ } while (0) \ MUNIT__POP_DISABLE_MSVC_C4127 #define munit_assert_ptr_equal(a, b) \ munit_assert_ptr(a, ==, b) #define munit_assert_ptr_not_equal(a, b) \ munit_assert_ptr(a, !=, b) #define munit_assert_null(ptr) \ munit_assert_ptr(ptr, ==, NULL) #define munit_assert_not_null(ptr) \ munit_assert_ptr(ptr, !=, NULL) #define munit_assert_ptr_null(ptr) \ munit_assert_ptr(ptr, ==, NULL) #define munit_assert_ptr_not_null(ptr) \ munit_assert_ptr(ptr, !=, NULL) /*** Memory allocation ***/ void* munit_malloc_ex(const char* filename, int line, size_t size); #define munit_malloc(size) \ munit_malloc_ex(__FILE__, __LINE__, (size)) #define munit_new(type) \ ((type*) munit_malloc(sizeof(type))) #define munit_calloc(nmemb, size) \ munit_malloc((nmemb) * (size)) #define munit_newa(type, nmemb) \ ((type*) munit_calloc((nmemb), sizeof(type))) /*** Random number generation ***/ void munit_rand_seed(munit_uint32_t seed); munit_uint32_t munit_rand_uint32(void); int munit_rand_int_range(int min, int max); double munit_rand_double(void); void munit_rand_memory(size_t size, munit_uint8_t buffer[MUNIT_ARRAY_PARAM(size)]); /*** Tests and Suites ***/ typedef enum { /* Test successful */ MUNIT_OK, /* Test failed */ MUNIT_FAIL, /* Test was skipped */ MUNIT_SKIP, /* Test failed due to circumstances not intended to be tested * (things like network errors, invalid parameter value, failure to * allocate memory in the test harness, etc.). */ MUNIT_ERROR } MunitResult; typedef struct { char* name; char** values; } MunitParameterEnum; typedef struct { char* name; char* value; } MunitParameter; const char* munit_parameters_get(const MunitParameter params[], const char* key); typedef enum { MUNIT_TEST_OPTION_NONE = 0, MUNIT_TEST_OPTION_SINGLE_ITERATION = 1 << 0, MUNIT_TEST_OPTION_TODO = 1 << 1 } MunitTestOptions; typedef MunitResult (* MunitTestFunc)(const MunitParameter params[], void* user_data_or_fixture); typedef void* (* MunitTestSetup)(const MunitParameter params[], void* user_data); typedef void (* MunitTestTearDown)(void* fixture); typedef struct { char* name; MunitTestFunc test; MunitTestSetup setup; MunitTestTearDown tear_down; MunitTestOptions options; MunitParameterEnum* parameters; } MunitTest; typedef enum { MUNIT_SUITE_OPTION_NONE = 0 } MunitSuiteOptions; typedef struct MunitSuite_ MunitSuite; struct MunitSuite_ { char* prefix; MunitTest* tests; MunitSuite* suites; unsigned int iterations; MunitSuiteOptions options; }; int munit_suite_main(const MunitSuite* suite, void* user_data, int argc, char* const argv[MUNIT_ARRAY_PARAM(argc)]); /* Note: I'm not very happy with this API; it's likely to change if I * figure out something better. Suggestions welcome. */ typedef struct MunitArgument_ MunitArgument; struct MunitArgument_ { char* name; bool (* parse_argument)(const MunitSuite* suite, void* user_data, int* arg, int argc, char* const argv[MUNIT_ARRAY_PARAM(argc)]); void (* write_help)(const MunitArgument* argument, void* user_data); }; int munit_suite_main_custom(const MunitSuite* suite, void* user_data, int argc, char* const argv[MUNIT_ARRAY_PARAM(argc)], const MunitArgument arguments[]); #if defined(MUNIT_ENABLE_ASSERT_ALIASES) #define assert_true(expr) munit_assert_true(expr) #define assert_false(expr) munit_assert_false(expr) #define assert_char(a, op, b) munit_assert_char(a, op, b) #define assert_uchar(a, op, b) munit_assert_uchar(a, op, b) #define assert_short(a, op, b) munit_assert_short(a, op, b) #define assert_ushort(a, op, b) munit_assert_ushort(a, op, b) #define assert_int(a, op, b) munit_assert_int(a, op, b) #define assert_uint(a, op, b) munit_assert_uint(a, op, b) #define assert_long(a, op, b) munit_assert_long(a, op, b) #define assert_ulong(a, op, b) munit_assert_ulong(a, op, b) #define assert_llong(a, op, b) munit_assert_llong(a, op, b) #define assert_ullong(a, op, b) munit_assert_ullong(a, op, b) #define assert_size(a, op, b) munit_assert_size(a, op, b) #define assert_float(a, op, b) munit_assert_float(a, op, b) #define assert_double(a, op, b) munit_assert_double(a, op, b) #define assert_ptr(a, op, b) munit_assert_ptr(a, op, b) #define assert_int8(a, op, b) munit_assert_int8(a, op, b) #define assert_uint8(a, op, b) munit_assert_uint8(a, op, b) #define assert_int16(a, op, b) munit_assert_int16(a, op, b) #define assert_uint16(a, op, b) munit_assert_uint16(a, op, b) #define assert_int32(a, op, b) munit_assert_int32(a, op, b) #define assert_uint32(a, op, b) munit_assert_uint32(a, op, b) #define assert_int64(a, op, b) munit_assert_int64(a, op, b) #define assert_uint64(a, op, b) munit_assert_uint64(a, op, b) #define assert_double_equal(a, b, precision) munit_assert_double_equal(a, b, precision) #define assert_string_equal(a, b) munit_assert_string_equal(a, b) #define assert_string_not_equal(a, b) munit_assert_string_not_equal(a, b) #define assert_memory_equal(size, a, b) munit_assert_memory_equal(size, a, b) #define assert_memory_not_equal(size, a, b) munit_assert_memory_not_equal(size, a, b) #define assert_ptr_equal(a, b) munit_assert_ptr_equal(a, b) #define assert_ptr_not_equal(a, b) munit_assert_ptr_not_equal(a, b) #define assert_ptr_null(ptr) munit_assert_null_equal(ptr) #define assert_ptr_not_null(ptr) munit_assert_not_null(ptr) #define assert_null(ptr) munit_assert_null(ptr) #define assert_not_null(ptr) munit_assert_not_null(ptr) #endif /* defined(MUNIT_ENABLE_ASSERT_ALIASES) */ #if defined(__cplusplus) } #endif #endif /* !defined(MUNIT_H) */ #if defined(MUNIT_ENABLE_ASSERT_ALIASES) # if defined(assert) # undef assert # endif # define assert(expr) munit_assert(expr) #endif dqlite-1.9.1/test/lib/raft.h000066400000000000000000000054231416342771300156520ustar00rootroot00000000000000/** * Helpers for setting up a standalone raft instance with a libuv transport. */ #ifndef TEST_RAFT_H #define TEST_RAFT_H #include #include #include #include "../../src/fsm.h" #include "../../src/transport.h" #include "fs.h" #include "logger.h" #include "munit.h" #include "uv.h" #define FIXTURE_RAFT \ char *dir; \ struct uv_loop_s loop; \ struct raft_uv_transport raft_transport; \ struct raft_io raft_io; \ struct raft_fsm fsm; \ struct raft raft #define SETUP_RAFT \ { \ int rv2; \ f->dir = test_dir_setup(); \ test_uv_setup(params, &f->loop); \ rv2 = raftProxyInit(&f->raft_transport, &f->loop); \ munit_assert_int(rv2, ==, 0); \ rv2 = raft_uv_init(&f->raft_io, &f->loop, f->dir, \ &f->raft_transport); \ munit_assert_int(rv2, ==, 0); \ rv2 = fsm__init(&f->fsm, &f->config, &f->registry); \ munit_assert_int(rv2, ==, 0); \ rv2 = raft_init(&f->raft, &f->raft_io, &f->fsm, 1, "1"); \ munit_assert_int(rv2, ==, 0); \ } #define TEAR_DOWN_RAFT \ { \ raft_close(&f->raft, NULL); \ test_uv_stop(&f->loop); \ raft_uv_close(&f->raft_io); \ fsm__close(&f->fsm); \ test_uv_tear_down(&f->loop); \ raftProxyClose(&f->raft_transport); \ test_dir_tear_down(f->dir); \ } /** * Bootstrap the fixture raft instance with a configuration containing only * itself. */ #define RAFT_BOOTSTRAP \ { \ struct raft_configuration configuration; \ int rv2; \ raft_configuration_init(&configuration); \ rv2 = raft_configuration_add(&configuration, 1, "1", true); \ munit_assert_int(rv2, ==, 0); \ rv2 = raft_bootstrap(&f->raft, &configuration); \ munit_assert_int(rv2, ==, 0); \ raft_configuration_close(&configuration); \ } #define RAFT_START \ { \ int rv2; \ rv2 = raft_start(&f->raft); \ munit_assert_int(rv2, ==, 0); \ } #endif /* TEST_RAFT_H */ dqlite-1.9.1/test/lib/registry.h000066400000000000000000000004341416342771300165630ustar00rootroot00000000000000#ifndef TEST_REGISTRY_H #define TEST_REGISTRY_H #include "../../src/registry.h" #define FIXTURE_REGISTRY struct registry registry #define SETUP_REGISTRY registry__init(&f->registry, &f->config) #define TEAR_DOWN_REGISTRY registry__close(&f->registry); #endif /* TEST_REGISTRY_H */ dqlite-1.9.1/test/lib/runner.h000066400000000000000000000421201416342771300162220ustar00rootroot00000000000000/* Convenience macros to reduce munit boiler plate. */ #ifndef TEST_RUNNER_H #define TEST_RUNNER_H #include #include "munit.h" /* Top-level suites array declaration. * * These top-level suites hold all module-level child suites and must be defined * and then set as child suites of a root suite created at runtime by the test * runner's main(). This can be done using the RUNNER macro. */ extern MunitSuite _main_suites[]; extern int _main_suites_n; /* Maximum number of test cases for each suite */ #define SUITE__CAP 128 #define TEST__CAP SUITE__CAP /* Define the top-level suites array and the main() function of the test. */ #define RUNNER(NAME) \ MunitSuite _main_suites[SUITE__CAP]; \ int _main_suites_n = 0; \ \ int main(int argc, char *argv[MUNIT_ARRAY_PARAM(argc)]) \ { \ signal(SIGPIPE, SIG_IGN); \ MunitSuite suite = {(char *)"", NULL, _main_suites, 1, 0}; \ return munit_suite_main(&suite, (void *)NAME, argc, argv); \ } /* Declare and register a new test suite #S belonging to the file's test module. * * A test suite is a pair of static variables: * * static MunitTest _##S##_suites[SUITE__CAP] * static MunitTest _##S##_tests[SUITE__CAP] * * The tests and suites attributes of the next available MunitSuite slot in the * _module_suites array will be set to the suite's tests and suites arrays, and * the prefix attribute of the slot will be set to /S. */ #define SUITE(S) \ SUITE__DECLARE(S) \ SUITE__ADD_CHILD(main, #S, S) /* Declare and register a new test. */ #define TEST(S, C, SETUP, TEAR_DOWN, OPTIONS, PARAMS) \ static MunitResult test_##S##_##C(const MunitParameter params[], \ void *data); \ TEST__ADD_TO_SUITE(S, C, SETUP, TEAR_DOWN, OPTIONS, PARAMS) \ static MunitResult test_##S##_##C( \ MUNIT_UNUSED const MunitParameter params[], \ MUNIT_UNUSED void *data) #define SKIP_IF_NO_FIXTURE \ if (f == NULL) { \ return MUNIT_SKIP; \ } /* Declare the MunitSuite[] and the MunitTest[] arrays that compose the test * suite identified by S. */ #define SUITE__DECLARE(S) \ static MunitSuite _##S##_suites[SUITE__CAP]; \ static MunitTest _##S##_tests[SUITE__CAP]; \ static MunitTestSetup _##S##_setup = NULL; \ static MunitTestTearDown _##S##_tear_down = NULL; \ static int _##S##_suites_n = 0; \ static int _##S##_tests_n = 0; \ __attribute__((constructor(101))) static void _##S##_init(void) \ { \ memset(_##S##_suites, 0, sizeof(_##S##_suites)); \ memset(_##S##_tests, 0, sizeof(_##S##_tests)); \ (void)_##S##_suites_n; \ (void)_##S##_tests_n; \ (void)_##S##_setup; \ (void)_##S##_tear_down; \ } /* Set the tests and suites attributes of the next available slot of the * MunitSuite[] array of S1 to the MunitTest[] and MunitSuite[] arrays of S2, * using the given PREXIX. */ #define SUITE__ADD_CHILD(S1, PREFIX, S2) \ __attribute__((constructor(102))) static void _##S1##_##S2##_init(void) \ { \ int n = _##S1##_suites_n; \ _##S1##_suites[n].prefix = PREFIX; \ _##S1##_suites[n].tests = _##S2##_tests; \ _##S1##_suites[n].suites = _##S2##_suites; \ _##S1##_suites[n].iterations = 0; \ _##S1##_suites[n].options = 0; \ _##S1##_suites_n = n + 1; \ } /* Add a test case to the MunitTest[] array of suite S. */ #define TEST__ADD_TO_SUITE(S, C, SETUP, TEAR_DOWN, OPTIONS, PARAMS) \ __attribute__((constructor(103))) static void _##S##_tests_##C##_init(void) \ { \ MunitTest *tests = _##S##_tests; \ int n = _##S##_tests_n; \ TEST__SET_IN_ARRAY(tests, n, "/" #C, test_##S##_##C, SETUP, \ TEAR_DOWN, OPTIONS, PARAMS); \ _##S##_tests_n = n + 1; \ } /* Set the values of the I'th test case slot in the given test array */ #define TEST__SET_IN_ARRAY(TESTS, I, NAME, FUNC, SETUP, TEAR_DOWN, OPTIONS, \ PARAMS) \ TESTS[I].name = NAME; \ TESTS[I].test = FUNC; \ TESTS[I].setup = SETUP; \ TESTS[I].tear_down = TEAR_DOWN; \ TESTS[I].options = OPTIONS; \ TESTS[I].parameters = PARAMS /** * Declare and register a new test module #M. * * A test module is a test suite (i.e. a pair of MunitTest[] and MunitSuite[] * arrays), directly or indirectly containing all test cases in a test file. * * This macro uses hard-coded names to declare the module's tests and suites * arrays static, so they can be easly referenced in other static declarations * generated by the macros below: * * static MunitTest _module_tests[TEST__CAP]; * static MunitSuite _module_suites[TEST__CAP]; * * The tests and suites attributes of the next available MunitSuite slot in the * top-level suites array will be set to the module's tests and suites arrays, * and the prefix attribute of the slot will be set to #M. * * Each test file should declare one and only one test module. */ #define TEST_MODULE(M) \ TEST_SUITE__DECLARE(module); \ TEST_SUITE__ADD_CHILD(main, #M, module); /** * Declare and register a new test suite #S belonging to the file's test module. * * A test suite is a pair of static variables: * * static MunitTest _##S##_suites[TEST__CAP] * static MunitTest _##S##_tests[TEST__CAP] * * The tests and suites attributes of the next available MunitSuite slot in the * #_module_suites array will be set to the suite's tests and suites arrays, and * the prefix attribute of the slot will be set to /S. * * All tests in the suite will use the same setup and tear down functions. */ #define TEST_SUITE(S) \ TEST_SUITE__DECLARE(S); \ TEST_SUITE__ADD_CHILD(module, "/" #S, S); /** * Declare a setup function. * * Possible signatures are: * * - TEST_SETUP(S): Declare the setup function for suite S inline. * - TEST_SETUP(S, F): Set the setup function for suite S to F. */ #define TEST_SETUP(...) TEST_SETUP__MACRO_CHOOSER(__VA_ARGS__)(__VA_ARGS__) #define TEST_SETUP_(S) \ static void *S##_setup(const MunitParameter[], void *); \ _##S##_setup = S##_setup; \ static void *S##_setup(const MunitParameter params[], void *user_data) /** * Declare a tear down function. * * Possible signatures are: * * - TEST_SETUP(S): Declare the tear down function for suite S inline. * - TEST_SETUP(S, F): Set the tear down function for suite S to F. */ #define TEST_TEAR_DOWN(...) \ TEST_TEAR_DOWN__MACRO_CHOOSER(__VA_ARGS__)(__VA_ARGS__) /** * Declare and register a new group of tests #G, belonging to suite #S in the * file's test module. */ #define TEST_GROUP(C, T) \ static MunitTest _##C##_##T##_tests[TEST__CAP]; \ static int _##C##_##T##_tests_n = 0; \ TEST_SUITE__ADD_GROUP(C, T); /** * Declare and register a new test case. * * Possible signatures are: * * - TEST_CASE(C): C gets added to the tests array of the file module. * - TEST_CASE(S, C): C gets added to the tests array of suite S. * - TEST_CASE(S, G, C): C gets added to the tests array of group G in suite S. * * The test body declaration must follow the macro. */ #define TEST_CASE(...) TEST_CASE__MACRO_CHOOSER(__VA_ARGS__)(__VA_ARGS__) /* Declare the MunitSuite[] and the MunitTest[] arrays that compose the test * suite identified by S. */ #define TEST_SUITE__DECLARE(S) \ static MunitSuite _##S##_suites[TEST__CAP]; \ static MunitTest _##S##_tests[TEST__CAP]; \ static MunitTestSetup _##S##_setup = NULL; \ static MunitTestTearDown _##S##_tear_down = NULL; \ static int _##S##_suites_n = 0; \ static int _##S##_tests_n = 0; \ __attribute__((constructor)) static void _##S##_init(void) \ { \ memset(_##S##_suites, 0, sizeof(_##S##_suites)); \ memset(_##S##_tests, 0, sizeof(_##S##_tests)); \ (void)_##S##_suites_n; \ (void)_##S##_tests_n; \ (void)_##S##_setup; \ (void)_##S##_tear_down; \ } /* Set the tests and suites attributes of the next available slot of the * MunitSuite[] array of S1 to the MunitTest[] and MunitSuite[] arrays of S2, * using the given PREXIX. */ #define TEST_SUITE__ADD_CHILD(S1, PREFIX, S2) \ __attribute__((constructor)) static void _##S1##_##S2##_init(void) \ { \ int n = _##S1##_suites_n; \ _##S1##_suites[n].prefix = PREFIX; \ _##S1##_suites[n].tests = _##S2##_tests; \ _##S1##_suites[n].suites = _##S2##_suites; \ _##S1##_suites[n].iterations = 0; \ _##S1##_suites[n].options = 0; \ _##S1##_suites_n = n + 1; \ } /* Set the tests attribute of the next available slot of the MunitSuite[] array * of S to the MunitTest[] array of G, using /G as prefix. */ #define TEST_SUITE__ADD_GROUP(S, G) \ __attribute__((constructor)) static void _##S##_##G##_init(void) \ { \ int n = _##S##_suites_n; \ _##S##_suites[n].prefix = "/" #G; \ _##S##_suites[n].tests = _##S##_##G##_tests; \ _##S##_suites[n].suites = NULL; \ _##S##_suites[n].iterations = 0; \ _##S##_suites[n].options = 0; \ _##S##_suites_n = n + 1; \ } /* Choose the appropriate TEST_SETUP__N_ARGS() macro depending on the number of * arguments passed to TEST_SETUP(). */ #define TEST_SETUP__MACRO_CHOOSER(...) \ TEST__GET_3RD_ARG(__VA_ARGS__, TEST_SETUP__2_ARGS, TEST_SETUP__1_ARGS) #define TEST_SETUP__1_ARGS(S) \ static void *S##__setup(const MunitParameter[], void *); \ __attribute__((constructor)) static void _##S##_setup_init(void) \ { \ _##S##_setup = S##__setup; \ } \ static void *S##__setup(const MunitParameter params[], void *user_data) #define TEST_SETUP__2_ARGS(S, F) \ __attribute__((constructor)) static void _##S##_setup_init(void) \ { \ _##S##_setup = F; \ } /* Choose the appropriate TEST_TEAR_DOWN__N_ARGS() macro depending on the number * of arguments passed to TEST_TEAR_DOWN(). */ #define TEST_TEAR_DOWN__MACRO_CHOOSER(...) \ TEST__GET_3RD_ARG(__VA_ARGS__, TEST_TEAR_DOWN__2_ARGS, \ TEST_TEAR_DOWN__1_ARGS) #define TEST_TEAR_DOWN__1_ARGS(S) \ static void S##__tear_down(void *data); \ __attribute__((constructor)) static void _##S##__tear_down_init(void) \ { \ _##S##_tear_down = S##__tear_down; \ } \ static void S##__tear_down(void *data) #define TEST_TEAR_DOWN__2_ARGS(S, F) \ __attribute__((constructor)) static void _##S##_tear_down_init(void) \ { \ _##S##_tear_down = F; \ } /* Choose the appropriate TEST_CASE__N_ARGS() macro depending on the number of * arguments passed to TEST_CASE(). */ #define TEST_CASE__MACRO_CHOOSER(...) \ TEST__GET_5TH_ARG(__VA_ARGS__, TEST_CASE__4_ARGS, TEST_CASE__3_ARGS, \ TEST_CASE__2_ARGS) /* Add the test case to the module's MunitTest[] array. */ #define TEST_CASE__2_ARGS(C, PARAMS) \ static MunitResult test_##C(const MunitParameter[], void *); \ TEST_CASE__ADD_TO_MODULE(C, PARAMS); \ static MunitResult test_##C(const MunitParameter params[], void *data) /* Add test case C to the MunitTest[] array of suite S. */ #define TEST_CASE__3_ARGS(S, C, PARAMS) \ static MunitResult test_##S##_##C(const MunitParameter[], void *); \ TEST_CASE__ADD_TO_SUITE(S, C, PARAMS); \ static MunitResult test_##S##_##C(const MunitParameter params[], \ void *data) /* Add test case C to the MunitTest[] array of group G of suite S. */ #define TEST_CASE__4_ARGS(S, G, C, PARAMS) \ static MunitResult test_##S##_##G##_##C(const MunitParameter[], \ void *); \ TEST_CASE__ADD_TO_GROUP(S, G, C, PARAMS); \ static MunitResult test_##S##_##G##_##C(const MunitParameter params[], \ void *data) /* Add a test case to the MunitTest[] array of the file module. */ #define TEST_CASE__ADD_TO_MODULE(C, PARAMS) \ __attribute__((constructor)) static void _module_tests_##C##_init( \ void) \ { \ MunitTest *tests = _module_tests; \ int n = _module_tests_n; \ TEST_CASE__SET_IN_ARRAY(tests, n, "/" #C, test_##C, NULL, \ NULL, PARAMS); \ _module_tests_n = n + 1; \ } /* Add a test case to the MunitTest[] array of suite S. */ #define TEST_CASE__ADD_TO_SUITE(S, C, PARAMS) \ __attribute__((constructor)) static void _##S##_tests_##C##_init(void) \ { \ MunitTest *tests = _##S##_tests; \ int n = _##S##_tests_n; \ TEST_CASE__SET_IN_ARRAY(tests, n, "/" #C, test_##S##_##C, \ _##S##_setup, _##S##_tear_down, \ PARAMS); \ _##S##_tests_n = n + 1; \ } /* Add a test case to MunitTest[] array of group G in suite S. */ #define TEST_CASE__ADD_TO_GROUP(S, G, C, PARAMS) \ __attribute__( \ (constructor)) static void _##S##_##G##_tests_##C##_init(void) \ { \ MunitTest *tests = _##S##_##G##_tests; \ int n = _##S##_##G##_tests_n; \ TEST_CASE__SET_IN_ARRAY(tests, n, "/" #C, \ test_##S##_##G##_##C, _##S##_setup, \ _##S##_tear_down, PARAMS); \ _##S##_##G##_tests_n = n + 1; \ } /* Set the values of the I'th test case slot in the given test array */ #define TEST_CASE__SET_IN_ARRAY(TESTS, I, NAME, FUNC, SETUP, TEAR_DOWN, \ PARAMS) \ TESTS[I].name = NAME; \ TESTS[I].test = FUNC; \ TESTS[I].setup = SETUP; \ TESTS[I].tear_down = TEAR_DOWN; \ TESTS[I].options = 0; \ TESTS[I].parameters = PARAMS #define TEST__GET_3RD_ARG(arg1, arg2, arg3, ...) arg3 #define TEST__GET_5TH_ARG(arg1, arg2, arg3, arg4, arg5, ...) arg5 #endif /* TEST_RUNNER_H */ dqlite-1.9.1/test/lib/server.c000066400000000000000000000042161416342771300162160ustar00rootroot00000000000000#include "unistd.h" #include "fs.h" #include "server.h" static int endpointConnect(void *data, const char *address, int *fd) { struct sockaddr_un addr; int rv; (void)address; (void)data; memset(&addr, 0, sizeof addr); addr.sun_family = AF_UNIX; strcpy(addr.sun_path + 1, address + 1); *fd = socket(AF_UNIX, SOCK_STREAM, 0); munit_assert_int(*fd, !=, -1); rv = connect(*fd, (struct sockaddr *)&addr, sizeof(sa_family_t) + strlen(address + 1) + 1); munit_assert_int(rv, ==, 0); return 0; } void test_server_setup(struct test_server *s, const unsigned id, const MunitParameter params[]) { (void)params; s->id = id; sprintf(s->address, "@%u", id); s->dir = test_dir_setup(); memset(s->others, 0, sizeof s->others); } void test_server_tear_down(struct test_server *s) { int rv; clientClose(&s->client); close(s->client.fd); rv = dqlite_node_stop(s->dqlite); munit_assert_int(rv, ==, 0); dqlite_node_destroy(s->dqlite); test_dir_tear_down(s->dir); } void test_server_start(struct test_server *s) { int client; int rv; rv = dqlite_node_create(s->id, s->address, s->dir, &s->dqlite); munit_assert_int(rv, ==, 0); rv = dqlite_node_set_bind_address(s->dqlite, s->address); munit_assert_int(rv, ==, 0); rv = dqlite_node_set_connect_func(s->dqlite, endpointConnect, s); munit_assert_int(rv, ==, 0); rv = dqlite_node_start(s->dqlite); munit_assert_int(rv, ==, 0); /* Connect a client. */ rv = endpointConnect(NULL, s->address, &client); munit_assert_int(rv, ==, 0); rv = clientInit(&s->client, client); munit_assert_int(rv, ==, 0); } struct client *test_server_client(struct test_server *s) { return &s->client; } static void setOther(struct test_server *s, struct test_server *other) { unsigned i = other->id - 1; munit_assert_ptr_null(s->others[i]); s->others[i] = other; } void test_server_network(struct test_server *servers, unsigned n_servers) { unsigned i; unsigned j; for (i = 0; i < n_servers; i++) { for (j = 0; j < n_servers; j++) { struct test_server *server = &servers[i]; struct test_server *other = &servers[j]; if (i == j) { continue; } setOther(server, other); } } } dqlite-1.9.1/test/lib/server.h000066400000000000000000000022361416342771300162230ustar00rootroot00000000000000/* Setup fully blown servers running in standalone threads. */ #ifndef TEST_SERVER_H #define TEST_SERVER_H #include #include #include "../../src/client.h" #include "../../include/dqlite.h" #include "endpoint.h" #include "munit.h" struct test_server { unsigned id; /* Server ID. */ char address[8]; /* Server address. */ char *dir; /* Data directory. */ dqlite_node *dqlite; /* Dqlite instance. */ struct client client; /* Connected client. */ struct test_server *others[5]; /* Other servers, by ID-1. */ }; /* Initialize the test server. */ void test_server_setup(struct test_server *s, unsigned id, const MunitParameter params[]); /* Cleanup the test server. */ void test_server_tear_down(struct test_server *s); /* Start the test server. */ void test_server_start(struct test_server *s); /* Connect all the given the servers to each other. */ void test_server_network(struct test_server *servers, unsigned n_servers); /* Return a client connected to the server. */ struct client *test_server_client(struct test_server *s); #endif /* TEST_SERVER_H */ dqlite-1.9.1/test/lib/sqlite.c000066400000000000000000000006101416342771300162030ustar00rootroot00000000000000#include #include "sqlite.h" void test_sqlite_setup(const MunitParameter params[]) { int rc; (void)params; rc = sqlite3_shutdown(); if (rc != SQLITE_OK) { munit_errorf("sqlite_init(): %s", sqlite3_errstr(rc)); } } void test_sqlite_tear_down() { int rc; rc = sqlite3_shutdown(); if (rc != SQLITE_OK) { munit_errorf("sqlite_shutdown(): %s", sqlite3_errstr(rc)); } } dqlite-1.9.1/test/lib/sqlite.h000066400000000000000000000006031416342771300162120ustar00rootroot00000000000000/* Global SQLite configuration. */ #ifndef TEST_SQLITE_H #define TEST_SQLITE_H #include "munit.h" /* Setup SQLite global state. */ void test_sqlite_setup(const MunitParameter params[]); /* Teardown SQLite global state. */ void test_sqlite_tear_down(void); #define SETUP_SQLITE test_sqlite_setup(params); #define TEAR_DOWN_SQLITE test_sqlite_tear_down(); #endif /* TEST_SQLITE_H */ dqlite-1.9.1/test/lib/stmt.h000066400000000000000000000015511416342771300157030ustar00rootroot00000000000000/** * Setup a test prepared statement. */ #ifndef TEST_STMT_H #define TEST_STMT_H #include #define FIXTURE_STMT sqlite3_stmt *stmt #define STMT_PREPARE(CONN, STMT, SQL) \ { \ int rc; \ rc = sqlite3_prepare_v2(CONN, SQL, -1, &STMT, NULL); \ munit_assert_int(rc, ==, 0); \ } #define STMT_FINALIZE(STMT) sqlite3_finalize(STMT) #define STMT_EXEC(CONN, SQL) \ { \ int rc; \ char *msg; \ rc = sqlite3_exec(CONN, SQL, NULL, NULL, &msg); \ munit_assert_int(rc, ==, SQLITE_OK); \ } #endif /* TEST_STMT_H */ dqlite-1.9.1/test/lib/uv.c000066400000000000000000000025301416342771300153370ustar00rootroot00000000000000#include "uv.h" #define TEST_UV_MAX_LOOP_RUN 10 /* Max n. of loop iterations upon teardown */ void test_uv_setup(const MunitParameter params[], struct uv_loop_s *l) { int rv; (void)params; rv = uv_loop_init(l); munit_assert_int(rv, ==, 0); } int test_uv_run(struct uv_loop_s *l, unsigned n) { unsigned i; int rv; munit_assert_int(n, >, 0); for (i = 0; i < n; i++) { rv = uv_run(l, UV_RUN_ONCE); if (rv < 0) { munit_errorf("uv_run: %s (%d)", uv_strerror(rv), rv); } if (rv == 0) { break; } } return rv; } void test_uv_stop(struct uv_loop_s *l) { unsigned n_handles; /* Spin a few times to trigger pending callbacks. */ n_handles = test_uv_run(l, TEST_UV_MAX_LOOP_RUN); if (n_handles > 0) { munit_errorf("loop has still %d pending active handles", n_handles); } } static void test_uv__walk_cb(uv_handle_t *handle, void *arg) { (void)arg; munit_logf(MUNIT_LOG_INFO, "handle %d", handle->type); } void test_uv_tear_down(struct uv_loop_s *l) { int rv; rv = uv_loop_close(l); if (rv != 0) { uv_walk(l, test_uv__walk_cb, NULL); munit_errorf("uv_loop_close: %s (%d)", uv_strerror(rv), rv); } rv = uv_replace_allocator(malloc, realloc, calloc, free); munit_assert_int(rv, ==, 0); } dqlite-1.9.1/test/lib/uv.h000066400000000000000000000051731416342771300153520ustar00rootroot00000000000000/** * Add support for using the libuv loop in tests. */ #ifndef TEST_UV_H #define TEST_UV_H #include #include "munit.h" /* Max n. of loop iterations ran by a single function call */ #define TEST_UV_MAX_LOOP_RUN 10 /** * Initialize the given libuv loop. */ void test_uv_setup(const MunitParameter params[], struct uv_loop_s *l); /** * Run the loop until there are no pending active handles. * * If there are still pending active handles after 10 loop iterations, the test * will fail. * * This is meant to be used in tear down functions. */ void test_uv_stop(struct uv_loop_s *l); /** * Tear down the loop making sure no active handles are left. */ void test_uv_tear_down(struct uv_loop_s *l); /** * Run the loop until there are no pending active handles or the given amount of * iterations is reached. * * Return non-zero if there are pending handles. */ int test_uv_run(struct uv_loop_s *l, unsigned n); /** * Run the loop until the given function returns true. * * If the loop exhausts all active handles or if #TEST_UV_MAX_LOOP_RUN is * reached without @f returning #true, the test fails. */ #define test_uv_run_until(DATA, FUNC) \ { \ unsigned i; \ int rv; \ for (i = 0; i < TEST_UV_MAX_LOOP_RUN; i++) { \ if (FUNC(DATA)) { \ break; \ } \ rv = uv_run(&f->loop, UV_RUN_ONCE); \ if (rv < 0) { \ munit_errorf("uv_run: %s", uv_strerror(rv)); \ } \ if (rv == 0) { \ if (FUNC(DATA)) { \ break; \ } \ munit_errorf( \ "uv_run: stopped after %u iterations", \ i + 1); \ } \ } \ if (i == TEST_UV_MAX_LOOP_RUN) { \ munit_errorf( \ "uv_run: condition not met in %d iterations", \ TEST_UV_MAX_LOOP_RUN); \ } \ } #endif /* TEST_UV_H */ dqlite-1.9.1/test/lib/vfs.h000066400000000000000000000013121416342771300155050ustar00rootroot00000000000000/** * Setup an in-memory VFS instance to use in tests. */ #ifndef TEST_VFS_H #define TEST_VFS_H #include "../../src/vfs.h" #define FIXTURE_VFS struct sqlite3_vfs vfs; #define SETUP_VFS \ { \ int rv_; \ rv_ = VfsInit(&f->vfs, f->config.name); \ munit_assert_int(rv_, ==, 0); \ rv_ = sqlite3_vfs_register(&f->vfs, 0); \ munit_assert_int(rv_, ==, 0); \ } #define TEAR_DOWN_VFS \ { \ sqlite3_vfs_unregister(&f->vfs); \ VfsClose(&f->vfs); \ } #endif /* TEST_VFS_H */ dqlite-1.9.1/test/test_error.c000066400000000000000000000154661416342771300163430ustar00rootroot00000000000000#include #include "../include/dqlite.h" #include "../src/error.h" #include "./lib/heap.h" #include "./lib/runner.h" #include "./lib/sqlite.h" TEST_MODULE(error); /****************************************************************************** * * Setup and tear down * ******************************************************************************/ static void *setup(const MunitParameter params[], void *user_data) { dqlite__error *error; test_heap_setup(params, user_data); test_sqlite_setup(params); error = (dqlite__error *)munit_malloc(sizeof(*error)); dqlite__error_init(error); return error; } static void tear_down(void *data) { dqlite__error *error = data; dqlite__error_close(error); test_sqlite_tear_down(); test_heap_tear_down(data); free(error); } /****************************************************************************** * * dqlite__error_printf * ******************************************************************************/ TEST_SUITE(printf); TEST_SETUP(printf, setup); TEST_TEAR_DOWN(printf, tear_down); TEST_CASE(printf, success, NULL) { dqlite__error *error = data; (void)params; munit_assert_true(dqlite__error_is_null(error)); dqlite__error_printf(error, "hello %s", "world"); munit_assert_string_equal(*error, "hello world"); return MUNIT_OK; } TEST_CASE(printf, override, NULL) { dqlite__error *error = data; (void)params; dqlite__error_printf(error, "hello %s", "world"); dqlite__error_printf(error, "I'm %s!", "here"); munit_assert_string_equal(*error, "I'm here!"); return MUNIT_OK; } TEST_CASE(printf, oom, NULL) { dqlite__error *error = data; (void)params; test_heap_fault_config(0, 1); test_heap_fault_enable(); dqlite__error_printf(error, "hello %s", "world"); munit_assert_string_equal(*error, "error message unavailable (out of memory)"); return MUNIT_OK; } /****************************************************************************** * * dqlite__error_wrapf * ******************************************************************************/ TEST_SUITE(wrapf); TEST_SETUP(wrapf, setup); TEST_TEAR_DOWN(wrapf, tear_down); TEST_CASE(wrapf, success, NULL) { dqlite__error *error = data; dqlite__error cause; (void)params; dqlite__error_init(&cause); dqlite__error_printf(&cause, "hello %s", "world"); dqlite__error_wrapf(error, &cause, "boom"); dqlite__error_close(&cause); munit_assert_string_equal(*error, "boom: hello world"); return MUNIT_OK; } TEST_CASE(wrapf, null_cause, NULL) { dqlite__error *error = data; dqlite__error cause; (void)params; dqlite__error_init(&cause); dqlite__error_wrapf(error, &cause, "boom"); dqlite__error_close(&cause); munit_assert_string_equal(*error, "boom: (null)"); return MUNIT_OK; } TEST_CASE(wrapf, itself, NULL) { dqlite__error *error = data; (void)params; dqlite__error_printf(error, "I'm %s!", "here"); dqlite__error_wrapf(error, error, "boom"); munit_assert_string_equal(*error, "boom: I'm here!"); return MUNIT_OK; } /****************************************************************************** * * dqlite__error_oom * ******************************************************************************/ TEST_SUITE(oom); TEST_SETUP(oom, setup); TEST_TEAR_DOWN(oom, tear_down); TEST_CASE(oom, success, NULL) { dqlite__error *error = data; (void)params; dqlite__error_oom(error, "boom"); munit_assert_string_equal(*error, "boom: out of memory"); return MUNIT_OK; } TEST_CASE(oom, vargs, NULL) { dqlite__error *error = data; (void)params; dqlite__error_oom(error, "boom %d", 123); munit_assert_string_equal(*error, "boom 123: out of memory"); return MUNIT_OK; } /****************************************************************************** * * dqlite__error_sys * ******************************************************************************/ TEST_SUITE(sys); TEST_SETUP(sys, setup); TEST_TEAR_DOWN(sys, tear_down); TEST_CASE(sys, success, NULL) { dqlite__error *error = data; (void)params; open("/foo/bar/egg/baz", 0); dqlite__error_sys(error, "boom"); munit_assert_string_equal(*error, "boom: No such file or directory"); return MUNIT_OK; } /****************************************************************************** * * dqlite__error_uv * ******************************************************************************/ TEST_SUITE(uv); TEST_SETUP(uv, setup); TEST_TEAR_DOWN(uv, tear_down); TEST_CASE(uv, success, NULL) { dqlite__error *error = data; (void)params; dqlite__error_uv(error, UV_EBUSY, "boom"); munit_assert_string_equal(*error, "boom: resource busy or locked (EBUSY)"); return MUNIT_OK; } /****************************************************************************** * * dqlite__error_copy * ******************************************************************************/ TEST_SUITE(copy); TEST_SETUP(copy, setup); TEST_TEAR_DOWN(copy, tear_down); TEST_CASE(copy, success, NULL) { dqlite__error *error = data; int err; char *msg; (void)params; dqlite__error_printf(error, "hello %s", "world"); err = dqlite__error_copy(error, &msg); munit_assert_int(err, ==, 0); munit_assert_string_equal(msg, "hello world"); sqlite3_free(msg); return MUNIT_OK; } TEST_CASE(copy, null, NULL) { dqlite__error *error = data; int err; char *msg; (void)params; err = dqlite__error_copy(error, &msg); munit_assert_int(err, ==, DQLITE_ERROR); munit_assert_ptr_equal(msg, NULL); return MUNIT_OK; } TEST_CASE(copy, oom, NULL) { dqlite__error *error = data; int err; char *msg; (void)params; return MUNIT_SKIP; test_heap_fault_config(2, 1); test_heap_fault_enable(); dqlite__error_printf(error, "hello"); err = dqlite__error_copy(error, &msg); munit_assert_int(err, ==, DQLITE_NOMEM); munit_assert_ptr_equal(msg, NULL); return MUNIT_OK; } /****************************************************************************** * * dqlite__error_is_disconnect * ******************************************************************************/ TEST_SUITE(is_disconnect); TEST_SETUP(is_disconnect, setup); TEST_TEAR_DOWN(is_disconnect, tear_down); TEST_CASE(is_disconnect, eof, NULL) { dqlite__error *error = data; (void)params; dqlite__error_uv(error, UV_EOF, "boom"); munit_assert_true(dqlite__error_is_disconnect(error)); return MUNIT_OK; } TEST_CASE(is_disconnect, econnreset, NULL) { dqlite__error *error = data; (void)params; dqlite__error_uv(error, UV_ECONNRESET, "boom"); munit_assert_true(dqlite__error_is_disconnect(error)); return MUNIT_OK; } TEST_CASE(is_disconnect, other, NULL) { dqlite__error *error = data; (void)params; dqlite__error_printf(error, "boom"); munit_assert_true(!dqlite__error_is_disconnect(error)); return MUNIT_OK; } TEST_CASE(is_disconnect, null, NULL) { dqlite__error *error = data; (void)params; munit_assert_true(!dqlite__error_is_disconnect(error)); return MUNIT_OK; } dqlite-1.9.1/test/test_integration.c000066400000000000000000000210721416342771300175230ustar00rootroot00000000000000#include #include #include "../include/dqlite.h" #include "./lib/runner.h" TEST_MODULE(integration); #if 0 /****************************************************************************** * * Helpers * ******************************************************************************/ /* A worker that keeps inserting rows into a test table and fetching them back, * checking that they have been all inserted. */ struct worker { struct test_client *client; /* A connected client */ int i; /* Worker index */ int a; /* Start inserting from this number */ int n; /* Number of insertions to perform */ pthread_t thread; /* System thread we run in */ }; static void *__worker_run(void *arg) { struct worker *w; char *leader; uint64_t heartbeat; uint32_t db_id; int b; int i; munit_assert_ptr_not_null(arg); w = (struct worker *)arg; /* Initialize the connection and open a database. */ test_client_handshake(w->client); test_client_leader(w->client, &leader); test_client_client(w->client, &heartbeat); test_client_open(w->client, "test.db", &db_id); b = w->a + w->n; for (i = w->a; i < b; i++) { uint32_t stmt_id; char sql[128]; struct test_client_result result; struct test_client_rows rows; struct test_client_row *row; int j; /* Insert a row in the test table. */ sprintf(sql, "INSERT INTO test(n) VALUES(%d)", i); test_client_prepare(w->client, db_id, sql, &stmt_id); test_client_exec(w->client, db_id, stmt_id, &result); munit_assert_int(result.rows_affected, ==, 1); test_client_finalize(w->client, db_id, stmt_id); /* Fetch all rows within our own working range. */ sprintf(sql, "SELECT n FROM test WHERE n >= %d AND n < %d", w->a, b); test_client_prepare(w->client, db_id, sql, &stmt_id); test_client_query(w->client, db_id, stmt_id, &rows); munit_assert_int(rows.column_count, ==, 1); munit_assert_string_equal(rows.column_names[0], "n"); row = rows.next; for (j = w->a; j <= i; j++) { munit_assert_ptr_not_null(row); munit_assert_int(row->types[0], ==, SQLITE_INTEGER); munit_assert_int(*(int64_t *)row->values[0], ==, j); row = row->next; } test_client_rows_close(&rows); test_client_finalize(w->client, db_id, stmt_id); } return 0; } static void __worker_start(struct worker *w, struct test_server *server, int i, int a, int n) { int err; w->i = i; w->a = a; w->n = n; test_server_connect(server, &w->client); err = pthread_create(&w->thread, 0, &__worker_run, (void *)w); if (err) { munit_errorf("failed to spawn test worker thread: %s", strerror(errno)); } } static void __worker_wait(struct worker *w) { int err; void *retval; err = pthread_join(w->thread, &retval); if (err) { munit_errorf("failed to wait test worker thread: %s", strerror(errno)); } test_client_close(w->client); free(w->client); } /****************************************************************************** * * Setup and tear down * ******************************************************************************/ static void *setup(const MunitParameter params[], void *user_data) { struct test_server *server; const char *errmsg; int err; (void)user_data; (void)params; err = dqlite_init(&errmsg); munit_assert_int(err, ==, 0); server = test_server_start("unix", params); return server; } static void tear_down(void *data) { struct test_server *server = data; int rc; test_server_stop(server); rc = sqlite3_shutdown(); munit_assert_int(rc, ==, 0); } /****************************************************************************** * * Tests * ******************************************************************************/ TEST_SUITE(exec); TEST_SETUP(exec, setup); TEST_TEAR_DOWN(exec, tear_down); #include TEST_CASE(exec, single_query, NULL) { struct test_server *server = data; struct test_client *client; char *leader; uint64_t heartbeat; uint32_t db_id; uint32_t stmt_id; struct test_client_result result; struct test_client_rows rows; (void)params; test_server_connect(server, &client); /* Initialize the connection and open a database. */ test_client_handshake(client); test_client_leader(client, &leader); test_client_client(client, &heartbeat); test_client_open(client, "test.db", &db_id); munit_assert_int(db_id, ==, 0); /* Create a test table. */ test_client_prepare(client, db_id, "CREATE TABLE test (n INT)", &stmt_id); test_client_exec(client, db_id, stmt_id, &result); test_client_finalize(client, db_id, stmt_id); /* Insert a row in the test table. */ test_client_prepare(client, db_id, "INSERT INTO test VALUES(123)", &stmt_id); munit_assert_int(stmt_id, ==, 0); test_client_exec(client, db_id, stmt_id, &result); munit_assert_int(result.last_insert_id, ==, 1); munit_assert_int(result.rows_affected, ==, 1); test_client_finalize(client, db_id, stmt_id); /* Select rows from the test table. */ test_client_prepare(client, db_id, "SELECT n FROM test", &stmt_id); munit_assert_int(stmt_id, ==, 0); test_client_query(client, db_id, stmt_id, &rows); munit_assert_int(rows.column_count, ==, 1); munit_assert_string_equal(rows.column_names[0], "n"); munit_assert_ptr_not_null(rows.next); munit_assert_int(rows.next->types[0], ==, SQLITE_INTEGER); munit_assert_int(*(int64_t *)rows.next->values[0], ==, 123); test_client_rows_close(&rows); test_client_finalize(client, db_id, stmt_id); test_client_close(client); free(client); return MUNIT_OK; } TEST_CASE(exec, large_query, NULL) { struct test_server *server = data; struct test_client *client; char *leader; uint64_t heartbeat; uint32_t db_id; uint32_t stmt_id; struct test_client_result result; struct test_client_rows rows; int i; (void)params; test_server_connect(server, &client); /* Initialize the connection and open a database. */ test_client_handshake(client); test_client_leader(client, &leader); test_client_client(client, &heartbeat); test_client_open(client, "test.db", &db_id); munit_assert_int(db_id, ==, 0); /* Create a test table. */ test_client_prepare(client, db_id, "CREATE TABLE test (n INT)", &stmt_id); test_client_exec(client, db_id, stmt_id, &result); test_client_finalize(client, db_id, stmt_id); test_client_prepare(client, db_id, "BEGIN", &stmt_id); test_client_exec(client, db_id, stmt_id, &result); test_client_finalize(client, db_id, stmt_id); /* Insert lots of rows in the test table. */ test_client_prepare(client, db_id, "INSERT INTO test VALUES(123456789)", &stmt_id); for (i = 0; i < 256; i++) { munit_assert_int(stmt_id, ==, 0); test_client_exec(client, db_id, stmt_id, &result); munit_assert_int(result.rows_affected, ==, 1); } test_client_finalize(client, db_id, stmt_id); test_client_prepare(client, db_id, "COMMIT", &stmt_id); test_client_exec(client, db_id, stmt_id, &result); test_client_finalize(client, db_id, stmt_id); /* Select all rows from the test table. */ test_client_prepare(client, db_id, "SELECT n FROM test", &stmt_id); munit_assert_int(stmt_id, ==, 0); test_client_query(client, db_id, stmt_id, &rows); munit_assert_int(rows.column_count, ==, 1); munit_assert_string_equal(rows.column_names[0], "n"); munit_assert_ptr_not_null(rows.next); munit_assert_int(rows.next->types[0], ==, SQLITE_INTEGER); munit_assert_int(*(int64_t *)rows.next->values[0], ==, 123456789); test_client_rows_close(&rows); test_client_finalize(client, db_id, stmt_id); test_client_close(client); free(client); return MUNIT_OK; } TEST_CASE(exec, multi_thread, NULL) { struct test_server *server = data; struct worker *workers; struct test_client *client; struct test_client_result result; char *leader; uint64_t heartbeat; uint32_t db_id; uint32_t stmt_id; (void)params; int n = 2; int i; test_server_connect(server, &client); /* Initialize the connection and open a database. */ test_client_handshake(client); test_client_leader(client, &leader); test_client_client(client, &heartbeat); test_client_open(client, "test.db", &db_id); munit_assert_int(db_id, ==, 0); /* Create a test table and close this client. */ test_client_prepare(client, db_id, "CREATE TABLE test (n INT)", &stmt_id); test_client_exec(client, db_id, stmt_id, &result); test_client_finalize(client, db_id, stmt_id); test_client_close(client); /* Spawn the workers. */ workers = munit_malloc(n * sizeof *workers); for (i = 0; i < n; i++) { __worker_start(&(workers[i]), server, i, i * 100000, 4); } /* Wait for the workers. */ for (i = 0; i < n; i++) { __worker_wait(&(workers[i])); } free(client); free(workers); return MUNIT_OK; } #endif dqlite-1.9.1/test/unit/000077500000000000000000000000001416342771300147525ustar00rootroot00000000000000dqlite-1.9.1/test/unit/ext/000077500000000000000000000000001416342771300155525ustar00rootroot00000000000000dqlite-1.9.1/test/unit/ext/test_uv.c000066400000000000000000000117231416342771300174130ustar00rootroot00000000000000#include #include #include #include "../../../src/lib/transport.h" #include "../../lib/endpoint.h" #include "../../lib/runner.h" #include "../../lib/uv.h" TEST_MODULE(ext_uv); /****************************************************************************** * * Helpers * ******************************************************************************/ struct fixture { struct uv_loop_s loop; struct uv_stream_s *listener; struct test_endpoint endpoint; int client; union { uv_tcp_t tcp; uv_pipe_t pipe; uv_stream_t stream; }; }; /* Return a buffer of size TEST_SOCKET_MIN_BUF_SIZE */ static uv_buf_t *buf_malloc(void) { uv_buf_t *buf = munit_malloc(sizeof *buf); buf->base = munit_malloc(TEST_SOCKET_MIN_BUF_SIZE); buf->len = TEST_SOCKET_MIN_BUF_SIZE; return buf; } /* Free the buffer returned by buf_malloc() */ static void buf_free(uv_buf_t *buf) { free(buf->base); free(buf); } /****************************************************************************** * * Parameters * ******************************************************************************/ /* Run the tests using both TCP and Unix sockets. */ static MunitParameterEnum endpointParams[] = { {TEST_ENDPOINT_FAMILY, test_endpoint_family_values}, {NULL, NULL}, }; /****************************************************************************** * * Setup and tear down * ******************************************************************************/ static void listenCb(uv_stream_t *listener, int status) { struct fixture *f = listener->data; int rv; munit_assert_int(status, ==, 0); switch (listener->type) { case UV_TCP: rv = uv_tcp_init(&f->loop, &f->tcp); munit_assert_int(rv, ==, 0); break; case UV_NAMED_PIPE: rv = uv_pipe_init(&f->loop, &f->pipe, 0); munit_assert_int(rv, ==, 0); break; default: munit_assert(0); } rv = uv_accept(listener, &f->stream); munit_assert_int(rv, ==, 0); } static void *setup(const MunitParameter params[], void *user_data) { struct fixture *f = munit_malloc(sizeof *f); int rv; (void)user_data; test_uv_setup(params, &f->loop); test_endpoint_setup(&f->endpoint, params); rv = transport__stream(&f->loop, f->endpoint.fd, &f->listener); munit_assert_int(rv, ==, 0); f->listener->data = f; rv = uv_listen(f->listener, 128, listenCb); munit_assert_int(rv, ==, 0); f->client = test_endpoint_connect(&f->endpoint); test_uv_run(&f->loop, 1); return f; } static void tear_down(void *data) { struct fixture *f = data; int rv; rv = close(f->client); munit_assert_int(rv, ==, 0); uv_close((struct uv_handle_s *)f->listener, (uv_close_cb)raft_free); test_endpoint_tear_down(&f->endpoint); uv_close((uv_handle_t *)(&f->stream), NULL); test_uv_stop(&f->loop); test_uv_tear_down(&f->loop); free(f); } /****************************************************************************** * * uv_write * ******************************************************************************/ TEST_SUITE(write); TEST_SETUP(write, setup); TEST_TEAR_DOWN(write, tear_down); /* Writing an amount of data below the buffer size makes that data immediately * available for reading. */ TEST_CASE(write, sync, endpointParams) { struct fixture *f = data; uv_write_t req; uv_buf_t *buf1 = buf_malloc(); uv_buf_t *buf2 = buf_malloc(); int rv; (void)params; rv = uv_write(&req, &f->stream, buf1, 1, NULL); munit_assert_int(rv, ==, 0); rv = read(f->client, buf2->base, buf2->len); munit_assert_int(rv, ==, buf2->len); test_uv_run(&f->loop, 1); buf_free(buf1); buf_free(buf2); return MUNIT_OK; } /****************************************************************************** * * uv_read * ******************************************************************************/ TEST_SUITE(read); TEST_SETUP(read, setup); TEST_TEAR_DOWN(read, tear_down); static void test_read_sync__alloc_cb(uv_handle_t *stream, size_t _, uv_buf_t *buf) { (void)stream; (void)_; buf->len = TEST_SOCKET_MIN_BUF_SIZE; buf->base = munit_malloc(TEST_SOCKET_MIN_BUF_SIZE); } static void test_read_sync__read_cb(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) { bool *read_cb_called; /* Apprently there's an empty read before the actual one. */ if (nread == 0) { free(buf->base); return; } munit_assert_int(nread, ==, TEST_SOCKET_MIN_BUF_SIZE); munit_assert_int(buf->len, ==, TEST_SOCKET_MIN_BUF_SIZE); read_cb_called = stream->data; *read_cb_called = true; free(buf->base); } /* Reading an amount of data below the buffer happens synchronously. */ TEST_CASE(read, sync, endpointParams) { struct fixture *f = data; uv_buf_t *buf = buf_malloc(); int rv; bool read_cb_called; (void)params; f->stream.data = &read_cb_called; rv = uv_read_start(&f->stream, test_read_sync__alloc_cb, test_read_sync__read_cb); rv = write(f->client, buf->base, buf->len); munit_assert_int(rv, ==, buf->len); test_uv_run(&f->loop, 1); munit_assert_true(read_cb_called); buf_free(buf); return MUNIT_OK; } dqlite-1.9.1/test/unit/lib/000077500000000000000000000000001416342771300155205ustar00rootroot00000000000000dqlite-1.9.1/test/unit/lib/test_buffer.c000066400000000000000000000053061416342771300202000ustar00rootroot00000000000000#include "../../../src/lib/buffer.h" #include "../../lib/runner.h" TEST_MODULE(lib_buffer); /****************************************************************************** * * Fixture * ******************************************************************************/ struct fixture { struct buffer buffer; }; static void *setup(const MunitParameter params[], void *user_data) { struct fixture *f = munit_malloc(sizeof *f); int rc; (void)params; (void)user_data; rc = buffer__init(&f->buffer); munit_assert_int(rc, ==, 0); return f; } static void tear_down(void *data) { struct fixture *f = data; buffer__close(&f->buffer); free(f); } /****************************************************************************** * * Helper macros. * ******************************************************************************/ #define ADVANCE(SIZE) \ { \ cursor = buffer__advance(&f->buffer, SIZE); \ munit_assert_ptr_not_null(cursor); \ } /****************************************************************************** * * Assertions. * ******************************************************************************/ #define ASSERT_N_PAGES(N) munit_assert_int(f->buffer.n_pages, ==, N) /****************************************************************************** * * buffer__init * ******************************************************************************/ TEST_SUITE(init); TEST_SETUP(init, setup); TEST_TEAR_DOWN(init, tear_down); /* If n is 0, then the prefix is used to dermine the number of elements of the * tuple. */ TEST_CASE(init, n_pages, NULL) { struct fixture *f = data; (void)params; ASSERT_N_PAGES(1); munit_assert_long(f->buffer.page_size, ==, sysconf(_SC_PAGESIZE)); return MUNIT_OK; } /****************************************************************************** * * buffer__advance * ******************************************************************************/ TEST_SUITE(advance); TEST_SETUP(advance, setup); TEST_TEAR_DOWN(advance, tear_down); /* The buffer already has enough capacity. */ TEST_CASE(advance, enough, NULL) { struct fixture *f = data; void *cursor; (void)params; ADVANCE(16); ASSERT_N_PAGES(1); return MUNIT_OK; } /* The buffer needs to double its size once. */ TEST_CASE(advance, double, NULL) { struct fixture *f = data; void *cursor; (void)params; ADVANCE(16 + f->buffer.page_size); ASSERT_N_PAGES(2); return MUNIT_OK; } /* The buffer needs to double its sice twice. */ TEST_CASE(advance, double_twice, NULL) { struct fixture *f = data; void *cursor; (void)params; ADVANCE(16 + 3 * f->buffer.page_size); ASSERT_N_PAGES(4); return MUNIT_OK; } dqlite-1.9.1/test/unit/lib/test_registry.c000066400000000000000000000200101416342771300205640ustar00rootroot00000000000000#include #include "../../../src/lib/registry.h" #include "../../lib/runner.h" TEST_MODULE(lib_registry); struct test_item { size_t id; int *ptr; }; static void test_item_init(struct test_item *i) { munit_assert(i != NULL); i->ptr = (int *)sqlite3_malloc(sizeof(*(i->ptr))); *i->ptr = 123; } static void test_item_close(struct test_item *i) { munit_assert(i != NULL); munit_assert(i->ptr != NULL); sqlite3_free(i->ptr); } static const char *test_item_hash(struct test_item *i) { munit_assert(i != NULL); return "x"; } REGISTRY(test_registry, test_item); REGISTRY_METHODS(test_registry, test_item); static void *setup(const MunitParameter params[], void *user_data) { struct test_registry *registry; (void)params; (void)user_data; registry = (struct test_registry *)munit_malloc(sizeof(*registry)); test_registry_init(registry); return registry; } static void tear_down(void *data) { struct test_registry *registry = data; test_registry_close(registry); free(registry); } TEST_SUITE(add); TEST_SETUP(add, setup); TEST_TEAR_DOWN(add, tear_down); static char *test_add_n[] = {"1", "2", "3", "5", "6", "7", "8", "9", "10", NULL}; static MunitParameterEnum test_add_params[] = { {"n", test_add_n}, {NULL, NULL}, }; /* Add N items. */ TEST_CASE(add, basic, test_add_params) { struct test_registry *registry = data; int err; struct test_item *item; int n; int i; n = atoi(munit_parameters_get(params, "n")); munit_assert_int(n, >, 0); for (i = 0; i < n; i++) { err = test_registry_add(registry, &item); munit_assert_int(err, ==, 0); munit_assert_ptr_not_equal(item, NULL); munit_assert_ptr_not_equal(item->ptr, NULL); munit_assert_int(123, ==, *item->ptr); } return MUNIT_OK; } /* Add three items, delete the second, and then add another one. The original ID * of the deleted item gets reused. */ TEST_CASE(add, del_add, NULL) { struct test_registry *registry = data; int err; struct test_item *item1; struct test_item *item2; struct test_item *item3; struct test_item *item4; int item2_id; (void)params; err = test_registry_add(registry, &item1); munit_assert_int(err, ==, 0); err = test_registry_add(registry, &item2); munit_assert_int(err, ==, 0); item2_id = item2->id; err = test_registry_add(registry, &item3); munit_assert_int(err, ==, 0); err = test_registry_del(registry, item2); munit_assert_int(err, ==, 0); err = test_registry_add(registry, &item4); munit_assert_int(err, ==, 0); munit_assert_int(item4->id, ==, item2_id); return MUNIT_OK; } /* Add N items and then delete them all. */ TEST_CASE(add, and_del, test_add_params) { struct test_registry *registry = data; int err; struct test_item **items; int n; int i; n = atoi(munit_parameters_get(params, "n")); munit_assert_int(n, >, 0); items = munit_malloc(n * sizeof(*items)); for (i = 0; i < n; i++) { err = test_registry_add(registry, &items[i]); munit_assert_int(err, ==, 0); } for (i = 0; i < n; i++) { err = test_registry_del(registry, items[i]); munit_assert_int(err, ==, 0); } free(items); return MUNIT_OK; } TEST_SUITE(get); TEST_SETUP(get, setup); TEST_TEAR_DOWN(get, tear_down); /* Retrieve a previously added item. */ TEST_CASE(get, basic, NULL) { struct test_registry *registry = data; int err; struct test_item *item; (void)params; err = test_registry_add(registry, &item); munit_assert_int(err, ==, 0); munit_assert_ptr_equal(test_registry_get(registry, item->id), item); return MUNIT_OK; } /* An item gets added and then deleted. Trying to fetch the item using its * former ID results in a NULL pointer. */ TEST_CASE(get, deleted, NULL) { struct test_registry *registry = data; int err; struct test_item *item; size_t id; (void)params; err = test_registry_add(registry, &item); munit_assert_int(err, ==, 0); id = item->id; err = test_registry_del(registry, item); munit_assert_int(err, ==, 0); munit_assert_ptr_equal(test_registry_get(registry, id), NULL); return MUNIT_OK; } /* Retrieve an item with an ID bigger than the current registry's length. */ TEST_CASE(get, out_of_bound, NULL) { struct test_registry *registry = data; struct test_item *item = test_registry_get(registry, 123); (void)params; munit_assert_ptr_equal(item, NULL); return MUNIT_OK; } TEST_SUITE(idx); TEST_SETUP(idx, setup); TEST_TEAR_DOWN(idx, tear_down); /* Find the index of a matching item. */ TEST_CASE(idx, found, NULL) { struct test_registry *registry = data; struct test_item *item; size_t i; int err; (void)params; err = test_registry_add(registry, &item); munit_assert_int(err, ==, 0); err = test_registry_idx(registry, "x", &i); munit_assert_int(err, ==, 0); munit_assert_int(i, ==, item->id); return MUNIT_OK; } /* No matching item. */ TEST_CASE(idx, not_found, NULL) { struct test_registry *registry = data; struct test_item *item1; struct test_item *item2; size_t i; int err; (void)params; err = test_registry_add(registry, &item1); munit_assert_int(err, ==, 0); err = test_registry_add(registry, &item2); munit_assert_int(err, ==, 0); err = test_registry_del(registry, item1); munit_assert_int(err, ==, 0); err = test_registry_idx(registry, "y", &i); munit_assert_int(err, ==, DQLITE_NOTFOUND); return MUNIT_OK; } TEST_SUITE(del); TEST_SETUP(del, setup); TEST_TEAR_DOWN(del, tear_down); /* Delete an item from the registry. */ TEST_CASE(del, basic, NULL) { struct test_registry *registry = data; int err; struct test_item *item; (void)params; err = test_registry_add(registry, &item); munit_assert_int(err, ==, 0); err = test_registry_del(registry, item); munit_assert_int(err, ==, 0); return MUNIT_OK; } /* Deleting an item twice results in an error. */ TEST_CASE(del, twice, NULL) { struct test_registry *registry = data; int err; struct test_item *item; struct test_item item_clone; (void)params; err = test_registry_add(registry, &item); munit_assert_int(err, ==, 0); item_clone.id = item->id; err = test_registry_del(registry, item); munit_assert_int(err, ==, 0); err = test_registry_del(registry, &item_clone); munit_assert_int(err, ==, DQLITE_NOTFOUND); return MUNIT_OK; } /* Deleting an item twice results in an error, also if the item being deleted * again has an ID lower than the highest one. */ TEST_CASE(del, twice_middle, NULL) { struct test_registry *registry = data; int err; struct test_item *item1; struct test_item *item2; struct test_item item1_clone; (void)params; err = test_registry_add(registry, &item1); munit_assert_int(err, ==, 0); item1_clone.id = item1->id; err = test_registry_add(registry, &item2); munit_assert_int(err, ==, 0); err = test_registry_del(registry, item1); munit_assert_int(err, ==, 0); err = test_registry_del(registry, &item1_clone); munit_assert_int(err, ==, DQLITE_NOTFOUND); return MUNIT_OK; } /* Deleting an item with an unknown ID results in an error. */ TEST_CASE(del, out_of_bounds, NULL) { struct test_registry *registry = data; struct test_item item; int err; (void)params; item.id = 123; err = test_registry_del(registry, &item); munit_assert_int(err, ==, DQLITE_NOTFOUND); return MUNIT_OK; } /* Add several items and then delete them. */ TEST_CASE(del, many, NULL) { struct test_registry *registry = data; int err; struct test_item *item1; struct test_item *item2; struct test_item *item3; (void)params; err = test_registry_add(registry, &item1); munit_assert_int(err, ==, 0); munit_assert_int(item1->id, ==, 0); err = test_registry_add(registry, &item2); munit_assert_int(err, ==, 0); munit_assert_int(item2->id, ==, 1); err = test_registry_add(registry, &item3); munit_assert_int(err, ==, 0); munit_assert_int(item3->id, ==, 2); munit_assert_int(3, ==, registry->len); munit_assert_int(4, ==, registry->cap); err = test_registry_del(registry, item3); munit_assert_int(err, ==, 0); munit_assert_int(2, ==, registry->len); munit_assert_int(4, ==, registry->cap); err = test_registry_del(registry, item2); munit_assert_int(err, ==, 0); munit_assert_int(1, ==, registry->len); munit_assert_int(2, ==, registry->cap); return MUNIT_OK; } dqlite-1.9.1/test/unit/lib/test_serialize.c000066400000000000000000000227641416342771300207250ustar00rootroot00000000000000#include "../../../src/lib/serialize.h" #include "../../lib/runner.h" TEST_MODULE(lib_serialize); /****************************************************************************** * * Simple schema with stock fields. * ******************************************************************************/ #define PERSON(X, ...) \ X(text, name, ##__VA_ARGS__) \ X(uint64, age, ##__VA_ARGS__) SERIALIZE__DEFINE(person, PERSON); SERIALIZE__IMPLEMENT(person, PERSON); /****************************************************************************** * * Complex schema with a custom field. * ******************************************************************************/ struct pages { uint16_t n; /* Number of pages */ uint16_t size; /* Size of each page */ uint32_t __unused__; void **bufs; /* Array of page buffers */ }; static void create_pages(unsigned n, unsigned size, struct pages *pages) { unsigned i; pages->n = n; pages->size = size; pages->bufs = munit_malloc(n * sizeof *pages->bufs); for (i = 0; i < pages->n; i++) { pages->bufs[i] = munit_malloc(size); } } static void destroy_pages(struct pages *pages) { unsigned i; for (i = 0; i < pages->n; i++) { free(pages->bufs[i]); } free(pages->bufs); } /* Opaque pointer to a struct pages object. */ typedef struct pages pages_t; typedef struct person person_t; static size_t pages__sizeof(const pages_t *pages) { return uint16__sizeof(&pages->n) + uint16__sizeof(&pages->size) + uint32__sizeof(&pages->__unused__) + pages->size * pages->n /* bufs */; } static void pages__encode(const pages_t *pages, void **cursor) { unsigned i; uint16__encode(&pages->n, cursor); uint16__encode(&pages->size, cursor); uint32__encode(&pages->__unused__, cursor); for (i = 0; i < pages->n; i++) { memcpy(*cursor, pages->bufs[i], pages->size); *cursor += pages->size; } } static int pages__decode(struct cursor *cursor, pages_t *pages) { unsigned i; uint16__decode(cursor, &pages->n); uint16__decode(cursor, &pages->size); uint32__decode(cursor, &pages->__unused__); pages->bufs = munit_malloc(pages->n * sizeof *pages->bufs); for (i = 0; i < pages->n; i++) { pages->bufs[i] = (void *)cursor->p; cursor->p += pages->size; cursor->cap -= pages->size; } return 0; } #define BOOK(X, ...) \ X(text, title, ##__VA_ARGS__) \ X(person, author, ##__VA_ARGS__) \ X(pages, pages, ##__VA_ARGS__) SERIALIZE__DEFINE(book, BOOK); SERIALIZE__IMPLEMENT(book, BOOK); /****************************************************************************** * * Fixture * ******************************************************************************/ struct fixture { struct person person; struct book book; }; static void *setup(const MunitParameter params[], void *user_data) { struct fixture *f = munit_malloc(sizeof *f); (void)params; (void)user_data; return f; } static void tear_down(void *data) { free(data); } /****************************************************************************** * * Fields definition. * ******************************************************************************/ TEST_SUITE(fields); TEST_SETUP(fields, setup); TEST_TEAR_DOWN(fields, tear_down); /* The expected fields are defined on the struct. */ TEST_CASE(fields, define, NULL) { struct fixture *f = data; (void)params; f->person.name = "John Doh"; f->person.age = 40; return MUNIT_OK; } /****************************************************************************** * * Sizeof method. * ******************************************************************************/ TEST_SUITE(sizeof); TEST_SETUP(sizeof, setup); TEST_TEAR_DOWN(sizeof, tear_down); /* Padding is added if needed. */ TEST_CASE(sizeof, padding, NULL) { struct fixture *f = data; size_t size; (void)params; f->person.name = "John Doh"; f->person.age = 40; size = person__sizeof(&f->person); munit_assert_int(size, ==, 16 /* name */ + 8 /* age */); return MUNIT_OK; } /* Padding is not added if a string ends exactly at word boundary. */ TEST_CASE(sizeof, no_padding, NULL) { struct fixture *f = data; size_t size; (void)params; f->person.name = "Joe Doh"; f->person.age = 40; size = person__sizeof(&f->person); munit_assert_int(size, ==, 8 /* name */ + 8 /* age */); return MUNIT_OK; } /****************************************************************************** * * Encode method. * ******************************************************************************/ TEST_SUITE(encode); TEST_SETUP(encode, setup); TEST_TEAR_DOWN(encode, tear_down); /* Padding is added if needed. */ TEST_CASE(encode, padding, NULL) { struct fixture *f = data; size_t size; void *buf; void *cursor; (void)params; f->person.name = "John Doh"; f->person.age = 40; size = person__sizeof(&f->person); buf = munit_malloc(size); cursor = buf; person__encode(&f->person, &cursor); munit_assert_string_equal(buf, "John Doh"); munit_assert_int(byte__flip64(*(uint64_t *)(buf + 16)), ==, 40); free(buf); return MUNIT_OK; } /* Padding is not added if a string ends exactly at word boundary. */ TEST_CASE(encode, no_padding, NULL) { struct fixture *f = data; size_t size; void *buf; void *cursor; (void)params; f->person.name = "Joe Doh"; f->person.age = 40; size = person__sizeof(&f->person); buf = munit_malloc(size); cursor = buf; person__encode(&f->person, &cursor); munit_assert_string_equal(buf, "Joe Doh"); munit_assert_int(byte__flip64(*(uint64_t *)(buf + 8)), ==, 40); free(buf); return MUNIT_OK; } /* Encode a custom complex field. */ TEST_CASE(encode, custom, NULL) { struct fixture *f = data; size_t size; void *buf; void *cursor; (void)params; f->book.title = "Les miserables"; f->book.author.name = "Victor Hugo"; f->book.author.age = 40; create_pages(2, 8, &f->book.pages); strcpy(f->book.pages.bufs[0], "Fantine"); strcpy(f->book.pages.bufs[1], "Cosette"); size = book__sizeof(&f->book); munit_assert_int(size, ==, 16 + /* title */ 16 + /* author name */ 8 + /* author age */ 2 + /* n pages */ 2 + /* page size */ 4 + /* unused */ 8 * 2 /* page buffers */); buf = munit_malloc(size); cursor = buf; book__encode(&f->book, &cursor); cursor = buf; munit_assert_string_equal(cursor, "Les miserables"); cursor += 16; munit_assert_string_equal(cursor, "Victor Hugo"); cursor += 16; munit_assert_int(byte__flip64(*(uint64_t *)cursor), ==, 40); cursor += 8; munit_assert_int(byte__flip16(*(uint16_t *)cursor), ==, 2); cursor += 2; munit_assert_int(byte__flip16(*(uint16_t *)cursor), ==, 8); cursor += 2; cursor += 4; /* Unused */ munit_assert_string_equal(cursor, "Fantine"); cursor += 8; munit_assert_string_equal(cursor, "Cosette"); free(buf); destroy_pages(&f->book.pages); return MUNIT_OK; } /****************************************************************************** * * Decode method. * ******************************************************************************/ TEST_SUITE(decode); TEST_SETUP(decode, setup); TEST_TEAR_DOWN(decode, tear_down); /* Padding is added if needed. */ TEST_CASE(decode, padding, NULL) { struct fixture *f = data; void *buf = munit_malloc(16 + 8); struct cursor cursor = {buf, 16 + 8}; (void)params; strcpy(buf, "John Doh"); *(uint64_t *)(buf + 16) = byte__flip64(40); person__decode(&cursor, &f->person); munit_assert_string_equal(f->person.name, "John Doh"); munit_assert_int(f->person.age, ==, 40); free(buf); return MUNIT_OK; } /* Padding is not added if a string ends exactly at word boundary. */ TEST_CASE(decode, no_padding, NULL) { struct fixture *f = data; void *buf = munit_malloc(16 + 8); struct cursor cursor = {buf, 16 + 8}; (void)params; strcpy(buf, "Joe Doh"); *(uint64_t *)(buf + 8) = byte__flip64(40); person__decode(&cursor, &f->person); munit_assert_string_equal(f->person.name, "Joe Doh"); munit_assert_int(f->person.age, ==, 40); free(buf); return MUNIT_OK; } /* The given buffer has not enough data. */ TEST_CASE(decode, short, NULL) { struct fixture *f = data; void *buf = munit_malloc(16); struct cursor cursor = {buf, 16}; int rc; (void)params; strcpy(buf, "John Doh"); rc = person__decode(&cursor, &f->person); munit_assert_int(rc, ==, DQLITE_PARSE); free(buf); return MUNIT_OK; } /* Decode a custom complex field. */ TEST_CASE(decode, custom, NULL) { struct fixture *f = data; size_t len = 16 + /* title */ 16 + /* author name */ 8 + /* author age */ 2 + /* n pages */ 2 + /* page size */ 4 + /* unused */ 8 * 2 /* page buffers */; void *buf = munit_malloc(len); void *p = buf; struct cursor cursor = {buf, len}; (void)params; strcpy(p, "Les miserables"); p += 16; strcpy(p, "Victor Hugo"); p += 16; *(uint64_t *)p = byte__flip64(40); p += 8; *(uint16_t *)p = byte__flip16(2); p += 2; *(uint16_t *)p = byte__flip16(8); p += 2; p += 4; /* Unused */ strcpy(p, "Fantine"); p += 8; strcpy(p, "Cosette"); book__decode(&cursor, &f->book); munit_assert_string_equal(f->book.title, "Les miserables"); munit_assert_string_equal(f->book.author.name, "Victor Hugo"); munit_assert_int(f->book.author.age, ==, 40); munit_assert_int(f->book.pages.n, ==, 2); munit_assert_int(f->book.pages.size, ==, 8); munit_assert_string_equal(f->book.pages.bufs[0], "Fantine"); munit_assert_string_equal(f->book.pages.bufs[1], "Cosette"); free(f->book.pages.bufs); free(buf); return MUNIT_OK; } dqlite-1.9.1/test/unit/lib/test_transport.c000066400000000000000000000121521416342771300207600ustar00rootroot00000000000000#include #include "../../../src/lib/transport.h" #include "../../lib/runner.h" #include "../../lib/endpoint.h" #include "../../lib/uv.h" TEST_MODULE(lib_transport); /****************************************************************************** * * Fixture * ******************************************************************************/ struct fixture { struct test_endpoint endpoint; struct uv_loop_s loop; struct transport transport; int client; struct { bool invoked; int status; } read; struct { bool invoked; int status; } write; }; static void read_cb(struct transport *transport, int status) { struct fixture *f = transport->data; f->read.invoked = true; f->read.status = status; } static void write_cb(struct transport *transport, int status) { struct fixture *f = transport->data; f->write.invoked = true; f->write.status = status; } static void *setup(const MunitParameter params[], void *user_data) { struct fixture *f = munit_malloc(sizeof *f); struct uv_stream_s *stream; int rv; int server; (void)user_data; test_endpoint_setup(&f->endpoint, params); rv = listen(f->endpoint.fd, 16); munit_assert_int(rv, ==, 0); test_endpoint_pair(&f->endpoint, &server, &f->client); test_uv_setup(params, &f->loop); rv = transport__stream(&f->loop, server, &stream); munit_assert_int(rv, ==, 0); rv = transport__init(&f->transport, stream); munit_assert_int(rv, ==, 0); f->transport.data = f; f->read.invoked = false; f->read.status = -1; f->write.invoked = false; f->write.status = -1; return f; } static void tear_down(void *data) { struct fixture *f = data; int rv; rv = close(f->client); munit_assert_int(rv, ==, 0); transport__close(&f->transport, NULL); test_uv_stop(&f->loop); test_uv_tear_down(&f->loop); test_endpoint_tear_down(&f->endpoint); free(data); } /****************************************************************************** * * Helper macros. * ******************************************************************************/ /* Allocate a libuv buffer with the given amount of bytes. */ #define BUF_ALLOC(N) {munit_malloc(N), N}; /* Start reading into the current buffer */ #define READ(BUF) \ { \ int rv2; \ rv2 = transport__read(&f->transport, BUF, read_cb); \ munit_assert_int(rv2, ==, 0); \ } /* Start writing the current buffer into the stream */ #define WRITE(BUF) \ { \ int rv2; \ rv2 = transport__write(&f->transport, BUF, write_cb); \ munit_assert_int(rv2, ==, 0); \ } /* Write N bytes into the client buffer. Each byte will contain a progressive * number starting from 1. */ #define CLIENT_WRITE(N) \ { \ uint8_t *buf_ = munit_malloc(N); \ unsigned i_; \ int rv_; \ for (i_ = 0; i_ < N; i_++) { \ buf_[i_] = i_ + 1; \ } \ rv_ = write(f->client, buf_, N); \ munit_assert_int(rv_, ==, N); \ free(buf_); \ } /****************************************************************************** * * Assertions. * ******************************************************************************/ /* Assert that the read callback was invoked with the given status. */ #define ASSERT_READ(STATUS) \ munit_assert_true(f->read.invoked); \ munit_assert_int(f->read.status, ==, STATUS); \ f->read.invoked = false; \ f->read.status = -1 /* Assert that the write callback was invoked with the given status. */ #define ASSERT_WRITE(STATUS) \ munit_assert_true(f->write.invoked); \ munit_assert_int(f->write.status, ==, STATUS); \ f->write.invoked = false; \ f->write.status = -1 /****************************************************************************** * * transport__read * ******************************************************************************/ TEST_SUITE(read); TEST_SETUP(read, setup); TEST_TEAR_DOWN(read, tear_down); TEST_CASE(read, success, NULL) { struct fixture *f = data; uv_buf_t buf = BUF_ALLOC(2); (void)params; CLIENT_WRITE(2); READ(&buf); test_uv_run(&f->loop, 1); ASSERT_READ(0); munit_assert_int(((uint8_t *)buf.base)[0], ==, 1); munit_assert_int(((uint8_t *)buf.base)[1], ==, 2); free(buf.base); return MUNIT_OK; } /****************************************************************************** * * transport__write * ******************************************************************************/ TEST_SUITE(write); TEST_SETUP(write, setup); TEST_TEAR_DOWN(write, tear_down); TEST_CASE(write, success, NULL) { struct fixture *f = data; uv_buf_t buf = BUF_ALLOC(2); (void)params; WRITE(&buf); test_uv_run(&f->loop, 1); ASSERT_WRITE(0); free(buf.base); return MUNIT_OK; } dqlite-1.9.1/test/unit/main.c000066400000000000000000000000541416342771300160410ustar00rootroot00000000000000#include "../lib/runner.h" RUNNER("unit"); dqlite-1.9.1/test/unit/test_command.c000066400000000000000000000020751416342771300175770ustar00rootroot00000000000000#include #include "../../src/command.h" #include "../lib/runner.h" TEST_MODULE(command); /****************************************************************************** * * Open. * ******************************************************************************/ TEST_SUITE(open); TEST_CASE(open, encode, NULL) { struct command_open c; struct raft_buffer buf; int rc; (void)data; (void)params; c.filename = "test.db"; rc = command__encode(COMMAND_OPEN, &c, &buf); munit_assert_int(rc, ==, 0); munit_assert_int(buf.len, ==, 16); raft_free(buf.base); return MUNIT_OK; } TEST_CASE(open, decode, NULL) { struct command_open c1; void *c2; int type; struct raft_buffer buf; int rc; (void)data; (void)params; c1.filename = "db"; rc = command__encode(COMMAND_OPEN, &c1, &buf); munit_assert_int(rc, ==, 0); rc = command__decode(&buf, &type, &c2); munit_assert_int(rc, ==, 0); munit_assert_int(type, ==, COMMAND_OPEN); munit_assert_string_equal(((struct command_open *)c2)->filename, "db"); raft_free(c2); raft_free(buf.base); return MUNIT_OK; } dqlite-1.9.1/test/unit/test_concurrency.c000066400000000000000000000244341416342771300205160ustar00rootroot00000000000000#include "../lib/cluster.h" #include "../lib/runner.h" #include "../../src/gateway.h" #include "../../src/protocol.h" #include "../../src/request.h" #include "../../src/response.h" TEST_MODULE(concurrency); /****************************************************************************** * * Fixture. * ******************************************************************************/ #define N_GATEWAYS 2 /* Context for a gateway handle request */ struct context { bool invoked; int status; int type; }; /* Standalone leader database connection */ struct connection { struct gateway gateway; struct buffer request; /* Request payload */ struct buffer response; /* Response payload */ struct handle handle; /* Async handle request */ struct context context; }; #define FIXTURE \ FIXTURE_CLUSTER; \ struct connection connections[N_GATEWAYS] #define SETUP \ unsigned i; \ int rc; \ SETUP_CLUSTER(V2); \ CLUSTER_ELECT(0); \ for (i = 0; i < N_GATEWAYS; i++) { \ struct connection *c = &f->connections[i]; \ struct request_open open; \ struct response_db db; \ gateway__init(&c->gateway, CLUSTER_CONFIG(0), \ CLUSTER_REGISTRY(0), CLUSTER_RAFT(0)); \ c->handle.data = &c->context; \ rc = buffer__init(&c->request); \ munit_assert_int(rc, ==, 0); \ rc = buffer__init(&c->response); \ munit_assert_int(rc, ==, 0); \ open.filename = "test"; \ open.vfs = ""; \ ENCODE(c, &open, open); \ HANDLE(c, OPEN); \ ASSERT_CALLBACK(c, 0, DB); \ DECODE(c, &db, db); \ munit_assert_int(db.id, ==, 0); \ } #define TEAR_DOWN \ unsigned i; \ for (i = 0; i < N_GATEWAYS; i++) { \ struct connection *c = &f->connections[i]; \ buffer__close(&c->request); \ buffer__close(&c->response); \ gateway__close(&c->gateway); \ } \ TEAR_DOWN_CLUSTER; static void fixture_handle_cb(struct handle *req, int status, int type) { struct context *c = req->data; c->invoked = true; c->status = status; c->type = type; } /****************************************************************************** * * Helper macros. * ******************************************************************************/ /* Reset the request buffer of the given connection and encode a request of the * given lower case name. */ #define ENCODE(C, REQUEST, LOWER) \ { \ size_t n2 = request_##LOWER##__sizeof(REQUEST); \ void *cursor; \ buffer__reset(&C->request); \ cursor = buffer__advance(&C->request, n2); \ munit_assert_ptr_not_null(cursor); \ request_##LOWER##__encode(REQUEST, &cursor); \ } /* Decode a response of the given lower/upper case name using the response * buffer of the given connection. */ #define DECODE(C, RESPONSE, LOWER) \ { \ struct cursor cursor; \ int rc2; \ cursor.p = buffer__cursor(&C->response, 0); \ cursor.cap = buffer__offset(&C->response); \ rc2 = response_##LOWER##__decode(&cursor, RESPONSE); \ munit_assert_int(rc2, ==, 0); \ } /* Submit a request of the given type to the given connection and check that no * error occurs. */ #define HANDLE(C, TYPE) \ { \ struct cursor cursor; \ int rc2; \ cursor.p = buffer__cursor(&C->request, 0); \ cursor.cap = buffer__offset(&C->request); \ buffer__reset(&C->response); \ rc2 = gateway__handle(&C->gateway, &C->handle, \ DQLITE_REQUEST_##TYPE, &cursor, \ &C->response, fixture_handle_cb); \ munit_assert_int(rc2, ==, 0); \ } /* Prepare a statement on the given connection. The ID will be saved in * the STMT_ID pointer. */ #define PREPARE(C, SQL, STMT_ID) \ { \ struct request_prepare prepare; \ struct response_stmt stmt; \ prepare.db_id = 0; \ prepare.sql = SQL; \ ENCODE(C, &prepare, prepare); \ HANDLE(C, PREPARE); \ ASSERT_CALLBACK(C, 0, STMT); \ DECODE(C, &stmt, stmt); \ *(STMT_ID) = stmt.id; \ } /* Submit a request to exec a statement. */ #define EXEC(C, STMT_ID) \ { \ struct request_exec exec; \ exec.db_id = 0; \ exec.stmt_id = STMT_ID; \ ENCODE(C, &exec, exec); \ HANDLE(C, EXEC); \ } /* Submit a query request. */ #define QUERY(C, STMT_ID) \ { \ struct request_query query; \ query.db_id = 0; \ query.stmt_id = STMT_ID; \ ENCODE(C, &query, query); \ HANDLE(C, QUERY); \ } /* Wait for the gateway of the given connection to finish handling a request. */ #define WAIT(C) \ { \ unsigned _i; \ for (_i = 0; _i < 50; _i++) { \ CLUSTER_STEP; \ if (C->context.invoked) { \ break; \ } \ } \ munit_assert_true(C->context.invoked); \ } /****************************************************************************** * * Assertions. * ******************************************************************************/ /* Assert that the handle callback of the given connection has been invoked with * the given status and response type.. */ #define ASSERT_CALLBACK(C, STATUS, UPPER) \ munit_assert_true(C->context.invoked); \ munit_assert_int(C->context.status, ==, STATUS); \ munit_assert_int(C->context.type, ==, DQLITE_RESPONSE_##UPPER); \ C->context.invoked = false /* Assert that the failure response generated by the gateway of the given * connection matches the given details. */ #define ASSERT_FAILURE(C, CODE, MESSAGE) \ { \ struct response_failure failure; \ DECODE(C, &failure, failure); \ munit_assert_int(failure.code, ==, CODE); \ munit_assert_string_equal(failure.message, MESSAGE); \ } /****************************************************************************** * * Concurrent exec requests * ******************************************************************************/ struct exec_fixture { FIXTURE; struct connection *c1; struct connection *c2; unsigned stmt_id1; unsigned stmt_id2; }; TEST_SUITE(exec); TEST_SETUP(exec) { struct exec_fixture *f = munit_malloc(sizeof *f); SETUP; f->c1 = &f->connections[0]; f->c2 = &f->connections[1]; return f; } TEST_TEAR_DOWN(exec) { struct exec_fixture *f = data; TEAR_DOWN; free(f); } /* If another leader connection has submitted an Open request and is waiting for * it to complete, SQLITE_BUSY is returned. */ TEST_CASE(exec, open, NULL) { struct exec_fixture *f = data; (void)params; PREPARE(f->c1, "CREATE TABLE test1 (n INT)", &f->stmt_id1); PREPARE(f->c2, "CREATE TABLE test2 (n INT)", &f->stmt_id2); EXEC(f->c1, f->stmt_id1); EXEC(f->c2, f->stmt_id2); WAIT(f->c2); ASSERT_CALLBACK(f->c2, 0, FAILURE); ASSERT_FAILURE(f->c2, SQLITE_BUSY, "database is locked"); WAIT(f->c1); ASSERT_CALLBACK(f->c1, 0, RESULT); return MUNIT_OK; } /* If an exec request is already in progress on another leader connection, * SQLITE_BUSY is returned. */ TEST_CASE(exec, tx, NULL) { struct exec_fixture *f = data; (void)params; /* Create a test table using connection 0 */ PREPARE(f->c1, "CREATE TABLE test (n INT)", &f->stmt_id1); EXEC(f->c1, f->stmt_id1); WAIT(f->c1); ASSERT_CALLBACK(f->c1, 0, RESULT); PREPARE(f->c1, "INSERT INTO test(n) VALUES(1)", &f->stmt_id1); PREPARE(f->c2, "INSERT INTO test(n) VALUES(1)", &f->stmt_id2); EXEC(f->c1, f->stmt_id1); EXEC(f->c2, f->stmt_id2); WAIT(f->c2); ASSERT_CALLBACK(f->c2, 0, FAILURE); ASSERT_FAILURE(f->c2, SQLITE_BUSY, "database is locked"); WAIT(f->c1); ASSERT_CALLBACK(f->c1, 0, RESULT); return MUNIT_OK; } /****************************************************************************** * * Concurrent query requests * ******************************************************************************/ struct query_fixture { FIXTURE; struct connection *c1; struct connection *c2; unsigned stmt_id1; unsigned stmt_id2; }; TEST_SUITE(query); TEST_SETUP(query) { struct exec_fixture *f = munit_malloc(sizeof *f); SETUP; f->c1 = &f->connections[0]; f->c2 = &f->connections[1]; PREPARE(f->c1, "CREATE TABLE test (n INT)", &f->stmt_id1); EXEC(f->c1, f->stmt_id1); WAIT(f->c1); return f; } TEST_TEAR_DOWN(query) { struct exec_fixture *f = data; TEAR_DOWN; free(f); } /* Handle a query request while there is a transaction in progress. */ TEST_CASE(query, tx, NULL) { struct exec_fixture *f = data; (void)params; PREPARE(f->c1, "INSERT INTO test VALUES(1)", &f->stmt_id1); PREPARE(f->c2, "SELECT n FROM test", &f->stmt_id2); EXEC(f->c1, f->stmt_id1); QUERY(f->c2, f->stmt_id2); WAIT(f->c1); WAIT(f->c2); ASSERT_CALLBACK(f->c1, 0, RESULT); ASSERT_CALLBACK(f->c2, 0, ROWS); return MUNIT_OK; } dqlite-1.9.1/test/unit/test_conn.c000066400000000000000000000256021416342771300171170ustar00rootroot00000000000000#include #include #include "../../include/dqlite.h" #include "../lib/client.h" #include "../lib/config.h" #include "../lib/heap.h" #include "../lib/logger.h" #include "../lib/raft.h" #include "../lib/registry.h" #include "../lib/runner.h" #include "../lib/sqlite.h" #include "../lib/vfs.h" #include "../../src/client.h" #include "../../src/conn.h" #include "../../src/gateway.h" #include "../../src/lib/transport.h" #include "../../src/transport.h" TEST_MODULE(conn); /****************************************************************************** * * Fixture * ******************************************************************************/ static void connCloseCb(struct conn *conn) { bool *closed = conn->queue[0]; *closed = true; } #define FIXTURE \ FIXTURE_LOGGER; \ FIXTURE_VFS; \ FIXTURE_CONFIG; \ FIXTURE_REGISTRY; \ FIXTURE_RAFT; \ FIXTURE_CLIENT; \ struct conn conn; \ bool closed; #define SETUP \ struct uv_stream_s *stream; \ int rv; \ SETUP_HEAP; \ SETUP_SQLITE; \ SETUP_LOGGER; \ SETUP_VFS; \ SETUP_CONFIG; \ SETUP_REGISTRY; \ SETUP_RAFT; \ SETUP_CLIENT; \ RAFT_BOOTSTRAP; \ RAFT_START; \ rv = transport__stream(&f->loop, f->server, &stream); \ munit_assert_int(rv, ==, 0); \ f->closed = false; \ f->conn.queue[0] = &f->closed; \ rv = conn__start(&f->conn, &f->config, &f->loop, &f->registry, \ &f->raft, stream, &f->raft_transport, connCloseCb); \ munit_assert_int(rv, ==, 0) #define TEAR_DOWN \ conn__stop(&f->conn); \ while (!f->closed) { \ test_uv_run(&f->loop, 1); \ }; \ TEAR_DOWN_RAFT; \ TEAR_DOWN_CLIENT; \ TEAR_DOWN_REGISTRY; \ TEAR_DOWN_CONFIG; \ TEAR_DOWN_VFS; \ TEAR_DOWN_LOGGER; \ TEAR_DOWN_SQLITE; \ TEAR_DOWN_HEAP /****************************************************************************** * * Helper macros. * ******************************************************************************/ /* Send the initial client handshake. */ #define HANDSHAKE \ { \ int rv2; \ rv2 = clientSendHandshake(&f->client); \ munit_assert_int(rv2, ==, 0); \ test_uv_run(&f->loop, 1); \ } /* Open a test database. */ #define OPEN \ { \ int rv2; \ rv2 = clientSendOpen(&f->client, "test"); \ munit_assert_int(rv2, ==, 0); \ test_uv_run(&f->loop, 2); \ rv2 = clientRecvDb(&f->client); \ munit_assert_int(rv2, ==, 0); \ } /* Prepare a statement. */ #define PREPARE(SQL, STMT_ID) \ { \ int rv2; \ rv2 = clientSendPrepare(&f->client, SQL); \ munit_assert_int(rv2, ==, 0); \ test_uv_run(&f->loop, 1); \ rv2 = clientRecvStmt(&f->client, STMT_ID); \ munit_assert_int(rv2, ==, 0); \ } /* Execute a statement. */ #define EXEC(STMT_ID, LAST_INSERT_ID, ROWS_AFFECTED, LOOP) \ { \ int rv2; \ rv2 = clientSendExec(&f->client, STMT_ID); \ munit_assert_int(rv2, ==, 0); \ test_uv_run(&f->loop, LOOP); \ rv2 = clientRecvResult(&f->client, LAST_INSERT_ID, \ ROWS_AFFECTED); \ munit_assert_int(rv2, ==, 0); \ } /* Execute a non-prepared statement. */ #define EXEC_SQL(SQL, LAST_INSERT_ID, ROWS_AFFECTED, LOOP) \ { \ int rv2; \ rv2 = clientSendExecSQL(&f->client, SQL); \ munit_assert_int(rv2, ==, 0); \ test_uv_run(&f->loop, LOOP); \ rv2 = clientRecvResult(&f->client, LAST_INSERT_ID, \ ROWS_AFFECTED); \ munit_assert_int(rv2, ==, 0); \ } /* Perform a query. */ #define QUERY(STMT_ID, ROWS) \ { \ int rv2; \ rv2 = clientSendQuery(&f->client, STMT_ID); \ munit_assert_int(rv2, ==, 0); \ test_uv_run(&f->loop, 2); \ rv2 = clientRecvRows(&f->client, ROWS); \ munit_assert_int(rv2, ==, 0); \ } /* Perform a non-prepared query. */ #define QUERY_SQL(SQL, ROWS) \ { \ int rv2; \ rv2 = clientSendQuerySql(&f->client, SQL); \ munit_assert_int(rv2, ==, 0); \ test_uv_run(&f->loop, 2); \ rv2 = clientRecvRows(&f->client, ROWS); \ munit_assert_int(rv2, ==, 0); \ } /****************************************************************************** * * Handle the handshake * ******************************************************************************/ TEST_SUITE(handshake); struct handshake_fixture { FIXTURE; }; TEST_SETUP(handshake) { struct handshake_fixture *f = munit_malloc(sizeof *f); SETUP; return f; } TEST_TEAR_DOWN(handshake) { struct handshake_fixture *f = data; TEAR_DOWN; free(f); } TEST_CASE(handshake, success, NULL) { struct handshake_fixture *f = data; (void)params; HANDSHAKE; return MUNIT_OK; } /****************************************************************************** * * Handle an open request * ******************************************************************************/ TEST_SUITE(open); struct open_fixture { FIXTURE; }; TEST_SETUP(open) { struct open_fixture *f = munit_malloc(sizeof *f); SETUP; HANDSHAKE; return f; } TEST_TEAR_DOWN(open) { struct open_fixture *f = data; TEAR_DOWN; free(f); } TEST_CASE(open, success, NULL) { struct open_fixture *f = data; (void)params; OPEN; return MUNIT_OK; } /****************************************************************************** * * Handle an prepare request * ******************************************************************************/ TEST_SUITE(prepare); struct prepare_fixture { FIXTURE; }; TEST_SETUP(prepare) { struct prepare_fixture *f = munit_malloc(sizeof *f); SETUP; HANDSHAKE; OPEN; return f; } TEST_TEAR_DOWN(prepare) { struct prepare_fixture *f = data; TEAR_DOWN; free(f); } TEST_CASE(prepare, success, NULL) { struct prepare_fixture *f = data; unsigned stmt_id; (void)params; PREPARE("CREATE TABLE test (n INT)", &stmt_id); munit_assert_int(stmt_id, ==, 0); return MUNIT_OK; } /****************************************************************************** * * Handle an exec * ******************************************************************************/ TEST_SUITE(exec); struct exec_fixture { FIXTURE; unsigned stmt_id; }; TEST_SETUP(exec) { struct exec_fixture *f = munit_malloc(sizeof *f); SETUP; HANDSHAKE; OPEN; return f; } TEST_TEAR_DOWN(exec) { struct exec_fixture *f = data; TEAR_DOWN; free(f); } TEST_CASE(exec, success, NULL) { struct exec_fixture *f = data; unsigned last_insert_id; unsigned rows_affected; (void)params; PREPARE("CREATE TABLE test (n INT)", &f->stmt_id); EXEC(f->stmt_id, &last_insert_id, &rows_affected, 8); munit_assert_int(last_insert_id, ==, 0); munit_assert_int(rows_affected, ==, 0); return MUNIT_OK; } TEST_CASE(exec, result, NULL) { struct exec_fixture *f = data; unsigned last_insert_id; unsigned rows_affected; (void)params; PREPARE("BEGIN", &f->stmt_id); EXEC(f->stmt_id, &last_insert_id, &rows_affected, 3); PREPARE("CREATE TABLE test (n INT)", &f->stmt_id); EXEC(f->stmt_id, &last_insert_id, &rows_affected, 6); PREPARE("INSERT INTO test (n) VALUES(123)", &f->stmt_id); EXEC(f->stmt_id, &last_insert_id, &rows_affected, 3); PREPARE("COMMIT", &f->stmt_id); EXEC(f->stmt_id, &last_insert_id, &rows_affected, 6); munit_assert_int(last_insert_id, ==, 1); munit_assert_int(rows_affected, ==, 1); return MUNIT_OK; } TEST_CASE(exec, close_while_in_flight, NULL) { struct exec_fixture *f = data; unsigned last_insert_id; unsigned rows_affected; int rv; (void)params; EXEC_SQL("CREATE TABLE test (n)", &last_insert_id, &rows_affected, 7); rv = clientSendExecSQL(&f->client, "INSERT INTO test(n) VALUES(1)"); munit_assert_int(rv, ==, 0); test_uv_run(&f->loop, 1); return MUNIT_OK; } /****************************************************************************** * * Handle a query * ******************************************************************************/ TEST_SUITE(query); struct query_fixture { FIXTURE; unsigned stmt_id; unsigned insert_stmt_id; unsigned last_insert_id; unsigned rows_affected; struct rows rows; }; TEST_SETUP(query) { struct query_fixture *f = munit_malloc(sizeof *f); unsigned stmt_id; SETUP; HANDSHAKE; OPEN; PREPARE("CREATE TABLE test (n INT)", &stmt_id); EXEC(stmt_id, &f->last_insert_id, &f->rows_affected, 7); PREPARE("INSERT INTO test(n) VALUES (123)", &f->insert_stmt_id); EXEC(f->insert_stmt_id, &f->last_insert_id, &f->rows_affected, 4); return f; } TEST_TEAR_DOWN(query) { struct query_fixture *f = data; clientCloseRows(&f->rows); TEAR_DOWN; free(f); } /* Perform a query yielding one row. */ TEST_CASE(query, one, NULL) { struct query_fixture *f = data; struct row *row; (void)params; PREPARE("SELECT n FROM test", &f->stmt_id); QUERY(f->stmt_id, &f->rows); munit_assert_int(f->rows.column_count, ==, 1); munit_assert_string_equal(f->rows.column_names[0], "n"); row = f->rows.next; munit_assert_ptr_not_null(row); munit_assert_ptr_null(row->next); munit_assert_int(row->values[0].type, ==, SQLITE_INTEGER); munit_assert_int(row->values[0].integer, ==, 123); return MUNIT_OK; } dqlite-1.9.1/test/unit/test_gateway.c000066400000000000000000001241541416342771300176250ustar00rootroot00000000000000#include "../../include/dqlite.h" #include "../../src/gateway.h" #include "../../src/request.h" #include "../../src/response.h" #include "../../src/tuple.h" #include "../lib/cluster.h" #include "../lib/runner.h" TEST_MODULE(gateway); /****************************************************************************** * * Fixture. * ******************************************************************************/ /* Context for a gateway handle request. */ struct context { bool invoked; int status; int type; }; /* Drive a single gateway. Each gateway is associated with a different raft * node. */ struct connection { struct gateway gateway; struct buffer buf1; /* Request payload */ struct buffer buf2; /* Response payload */ struct cursor cursor; /* Response read cursor */ struct handle handle; /* Async handle request */ struct context context; }; #define FIXTURE \ FIXTURE_CLUSTER; \ struct connection connections[N_SERVERS]; \ struct gateway *gateway; \ struct buffer *buf1; \ struct cursor *cursor; \ struct buffer *buf2; \ struct handle *handle; \ struct context *context; #define SETUP \ unsigned i; \ int rc; \ SETUP_CLUSTER(V2); \ for (i = 0; i < N_SERVERS; i++) { \ struct connection *c = &f->connections[i]; \ struct config *config; \ config = CLUSTER_CONFIG(i); \ config->page_size = 512; \ gateway__init(&c->gateway, config, CLUSTER_REGISTRY(i), \ CLUSTER_RAFT(i)); \ c->handle.data = &c->context; \ rc = buffer__init(&c->buf1); \ munit_assert_int(rc, ==, 0); \ rc = buffer__init(&c->buf2); \ munit_assert_int(rc, ==, 0); \ } \ SELECT(0) #define TEAR_DOWN \ unsigned i; \ for (i = 0; i < N_SERVERS; i++) { \ struct connection *c = &f->connections[i]; \ gateway__close(&c->gateway); \ buffer__close(&c->buf1); \ buffer__close(&c->buf2); \ } \ TEAR_DOWN_CLUSTER; static void handleCb(struct handle *req, int status, int type) { struct context *c = req->data; c->invoked = true; c->status = status; c->type = type; } /****************************************************************************** * * Helper macros. * ******************************************************************************/ /* Select which gateway to use for performing requests. */ #define SELECT(I) \ f->gateway = &f->connections[I].gateway; \ f->buf1 = &f->connections[I].buf1; \ f->buf2 = &f->connections[I].buf2; \ f->cursor = &f->connections[I].cursor; \ f->context = &f->connections[I].context; \ f->handle = &f->connections[I].handle /* Allocate the payload buffer, encode a request of the given lower case name * and initialize the fixture cursor. */ #define ENCODE(REQUEST, LOWER) \ { \ size_t n2 = request_##LOWER##__sizeof(REQUEST); \ void *cursor; \ buffer__reset(f->buf1); \ cursor = buffer__advance(f->buf1, n2); \ munit_assert_ptr_not_null(cursor); \ request_##LOWER##__encode(REQUEST, &cursor); \ } /* Encode N parameters with the given values */ #define ENCODE_PARAMS(N, VALUES) \ { \ struct tuple_encoder encoder; \ int i2; \ int rc2; \ rc2 = \ tuple_encoder__init(&encoder, N, TUPLE__PARAMS, f->buf1); \ munit_assert_int(rc2, ==, 0); \ for (i2 = 0; i2 < N; i2++) { \ rc2 = tuple_encoder__next(&encoder, &((VALUES)[i2])); \ munit_assert_int(rc2, ==, 0); \ } \ } /* Decode a response of the given lower/upper case name using the buffer that * was written by the gateway. */ #define DECODE(RESPONSE, LOWER) \ { \ int rc2; \ rc2 = response_##LOWER##__decode(f->cursor, RESPONSE); \ munit_assert_int(rc2, ==, 0); \ } /* Decode a row with N columns filling the given values. */ #define DECODE_ROW(N, VALUES) \ { \ struct tuple_decoder decoder; \ int i2; \ int rc2; \ rc2 = tuple_decoder__init(&decoder, N, f->cursor); \ munit_assert_int(rc2, ==, 0); \ for (i2 = 0; i2 < N; i2++) { \ rc2 = tuple_decoder__next(&decoder, &((VALUES)[i2])); \ munit_assert_int(rc2, ==, 0); \ } \ } /* Handle a request of the given type and check that no error occurs. */ #define HANDLE(TYPE) \ { \ int rc2; \ f->cursor->p = buffer__cursor(f->buf1, 0); \ f->cursor->cap = buffer__offset(f->buf1); \ buffer__reset(f->buf2); \ f->context->invoked = false; \ f->context->status = -1; \ f->context->type = -1; \ rc2 = gateway__handle(f->gateway, f->handle, \ DQLITE_REQUEST_##TYPE, f->cursor, \ f->buf2, handleCb); \ munit_assert_int(rc2, ==, 0); \ } /* Open a leader connection against the "test" database */ #define OPEN \ { \ struct request_open open; \ open.filename = "test"; \ open.vfs = ""; \ ENCODE(&open, open); \ HANDLE(OPEN); \ ASSERT_CALLBACK(0, DB); \ } /* Prepare a statement. The ID will be saved in stmt_id. */ #define PREPARE(SQL) \ { \ struct request_prepare prepare; \ struct response_stmt stmt; \ prepare.db_id = 0; \ prepare.sql = SQL; \ ENCODE(&prepare, prepare); \ HANDLE(PREPARE); \ ASSERT_CALLBACK(0, STMT); \ DECODE(&stmt, stmt); \ stmt_id = stmt.id; \ } /* Finalize the statement with the given ID. */ #define FINALIZE(STMT_ID) \ { \ struct request_finalize finalize; \ finalize.db_id = 0; \ finalize.stmt_id = STMT_ID; \ ENCODE(&finalize, finalize); \ HANDLE(FINALIZE); \ ASSERT_CALLBACK(0, EMPTY); \ } /* Submit a request to execute the given statement. */ #define EXEC_SUBMIT(STMT_ID) \ { \ struct request_exec exec; \ exec.db_id = 0; \ exec.stmt_id = STMT_ID; \ ENCODE(&exec, exec); \ HANDLE(EXEC); \ } /* Submit a request to execute the given statement. */ #define EXEC_SQL_SUBMIT(SQL) \ { \ struct request_exec_sql exec_sql; \ exec_sql.db_id = 0; \ exec_sql.sql = SQL; \ ENCODE(&exec_sql, exec_sql); \ HANDLE(EXEC_SQL); \ } /* Submit a request to execute the given statement. */ #define QUERY_SQL_SUBMIT(SQL) \ { \ struct request_query_sql query_sql; \ query_sql.db_id = 0; \ query_sql.sql = SQL; \ ENCODE(&query_sql, query_sql); \ HANDLE(QUERY_SQL); \ } /* Wait for the last request to complete */ #define WAIT \ { \ unsigned _i; \ for (_i = 0; _i < 60; _i++) { \ CLUSTER_STEP; \ if (f->context->invoked) { \ break; \ } \ } \ munit_assert_true(f->context->invoked); \ } /* Prepare and exec a statement. */ #define EXEC(SQL) \ { \ uint64_t _stmt_id; \ struct request_prepare prepare; \ struct response_stmt stmt; \ prepare.db_id = 0; \ prepare.sql = SQL; \ ENCODE(&prepare, prepare); \ HANDLE(PREPARE); \ ASSERT_CALLBACK(0, STMT); \ DECODE(&stmt, stmt); \ _stmt_id = stmt.id; \ EXEC_SUBMIT(_stmt_id); \ WAIT; \ ASSERT_CALLBACK(0, RESULT); \ FINALIZE(_stmt_id); \ } /* Execute a pragma statement to lowers SQLite's page cache size, in order to * force it to write uncommitted dirty pages to the WAL and hance trigger calls * to the xFrames hook with non-commit batches. */ #define LOWER_CACHE_SIZE EXEC("PRAGMA cache_size = 1") /****************************************************************************** * * Assertions. * ******************************************************************************/ /* Assert that the handle callback has been invoked with the given status and * response type. Also, initialize the fixture's cursor to read the response * buffer. */ #define ASSERT_CALLBACK(STATUS, UPPER) \ munit_assert_true(f->context->invoked); \ munit_assert_int(f->context->status, ==, STATUS); \ munit_assert_int(f->context->type, ==, DQLITE_RESPONSE_##UPPER); \ f->cursor->p = buffer__cursor(f->buf2, 0); \ f->cursor->cap = buffer__offset(f->buf2); \ buffer__reset(f->buf2); \ f->context->invoked = false; /* Assert that the failure response generated by the gateway matches the given * details. */ #define ASSERT_FAILURE(CODE, MESSAGE) \ { \ struct response_failure failure; \ int rc2; \ rc2 = response_failure__decode(f->cursor, &failure); \ munit_assert_int(rc2, ==, 0); \ munit_assert_int(failure.code, ==, CODE); \ munit_assert_string_equal(failure.message, MESSAGE); \ } /****************************************************************************** * * leader * ******************************************************************************/ struct leader_fixture { FIXTURE; struct request_leader request; struct response_server response; }; TEST_SUITE(leader); TEST_SETUP(leader) { struct leader_fixture *f = munit_malloc(sizeof *f); SETUP; return f; } TEST_TEAR_DOWN(leader) { struct leader_fixture *f = data; TEAR_DOWN; free(f); } /* If the leader is not available, an empty string is returned. */ TEST_CASE(leader, not_available, NULL) { struct leader_fixture *f = data; (void)params; ENCODE(&f->request, leader); HANDLE(LEADER); ASSERT_CALLBACK(0, SERVER); DECODE(&f->response, server); munit_assert_int(f->response.id, ==, 0); munit_assert_string_equal(f->response.address, ""); return MUNIT_OK; } /* The leader is the same node serving the request. */ TEST_CASE(leader, same_node, NULL) { struct leader_fixture *f = data; (void)params; CLUSTER_ELECT(0); ENCODE(&f->request, leader); HANDLE(LEADER); ASSERT_CALLBACK(0, SERVER); DECODE(&f->response, server); munit_assert_string_equal(f->response.address, "1"); return MUNIT_OK; } /* The leader is a different node than the one serving the request. */ TEST_CASE(leader, other_node, NULL) { struct leader_fixture *f = data; (void)params; CLUSTER_ELECT(1); ENCODE(&f->request, leader); HANDLE(LEADER); ASSERT_CALLBACK(0, SERVER); DECODE(&f->response, server); munit_assert_string_equal(f->response.address, "2"); return MUNIT_OK; } /****************************************************************************** * * open * ******************************************************************************/ struct open_fixture { FIXTURE; struct request_open request; struct response_db response; }; TEST_SUITE(open); TEST_SETUP(open) { struct open_fixture *f = munit_malloc(sizeof *f); SETUP; return f; } TEST_TEAR_DOWN(open) { struct open_fixture *f = data; TEAR_DOWN; free(f); } /* Successfully open a database connection. */ TEST_CASE(open, success, NULL) { struct open_fixture *f = data; (void)params; f->request.filename = "test"; f->request.vfs = ""; ENCODE(&f->request, open); HANDLE(OPEN); ASSERT_CALLBACK(0, DB); DECODE(&f->response, db); munit_assert_int(f->response.id, ==, 0); return MUNIT_OK; } TEST_GROUP(open, error); /* Attempting to open two databases on the same gateway results in an error. */ TEST_CASE(open, error, twice, NULL) { struct open_fixture *f = data; (void)params; f->request.filename = "test"; f->request.vfs = ""; ENCODE(&f->request, open); HANDLE(OPEN); ASSERT_CALLBACK(0, DB); ENCODE(&f->request, open); HANDLE(OPEN); ASSERT_CALLBACK(0, FAILURE); ASSERT_FAILURE(SQLITE_BUSY, "a database for this connection is already open"); return MUNIT_OK; } /****************************************************************************** * * prepare * ******************************************************************************/ struct prepare_fixture { FIXTURE; struct request_prepare request; struct response_stmt response; }; TEST_SUITE(prepare); TEST_SETUP(prepare) { struct prepare_fixture *f = munit_malloc(sizeof *f); SETUP; OPEN; return f; } TEST_TEAR_DOWN(prepare) { struct prepare_fixture *f = data; TEAR_DOWN; free(f); } /* Successfully prepare a statement. */ TEST_CASE(prepare, success, NULL) { struct prepare_fixture *f = data; (void)params; f->request.db_id = 0; f->request.sql = "CREATE TABLE test (n INT)"; ENCODE(&f->request, prepare); HANDLE(PREPARE); ASSERT_CALLBACK(0, STMT); DECODE(&f->response, stmt); munit_assert_int(f->response.id, ==, 0); return MUNIT_OK; } /****************************************************************************** * * exec * ******************************************************************************/ struct exec_fixture { FIXTURE; struct request_exec request; struct response_result response; }; TEST_SUITE(exec); TEST_SETUP(exec) { struct exec_fixture *f = munit_malloc(sizeof *f); SETUP; OPEN; return f; } TEST_TEAR_DOWN(exec) { struct exec_fixture *f = data; TEAR_DOWN; free(f); } /* Successfully execute a simple statement with no parameters. */ TEST_CASE(exec, simple, NULL) { struct exec_fixture *f = data; uint64_t stmt_id; (void)params; CLUSTER_ELECT(0); PREPARE("CREATE TABLE test (n INT)"); f->request.db_id = 0; f->request.stmt_id = stmt_id; ENCODE(&f->request, exec); HANDLE(EXEC); CLUSTER_APPLIED(2); ASSERT_CALLBACK(0, RESULT); DECODE(&f->response, result); munit_assert_int(f->response.last_insert_id, ==, 0); munit_assert_int(f->response.rows_affected, ==, 0); return MUNIT_OK; } /* Successfully execute a statement with a one parameter. */ TEST_CASE(exec, one_param, NULL) { struct exec_fixture *f = data; struct value value; uint64_t stmt_id; (void)params; CLUSTER_ELECT(0); /* Create the test table */ EXEC("CREATE TABLE test (n INT)"); /* Insert a row with one parameter */ PREPARE("INSERT INTO test VALUES (?)"); f->request.stmt_id = stmt_id; ENCODE(&f->request, exec); value.type = SQLITE_INTEGER; value.integer = 7; ENCODE_PARAMS(1, &value); HANDLE(EXEC); CLUSTER_APPLIED(3); ASSERT_CALLBACK(0, RESULT); DECODE(&f->response, result); munit_assert_int(f->response.last_insert_id, ==, 1); munit_assert_int(f->response.rows_affected, ==, 1); return MUNIT_OK; } /* Successfully execute a statement with a blob parameter. */ TEST_CASE(exec, blob, NULL) { struct exec_fixture *f = data; struct request_query query; struct value value; char buf[8] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'}; uint64_t stmt_id; uint64_t n; const char *column; (void)params; CLUSTER_ELECT(0); /* Create the test table */ EXEC("CREATE TABLE test (data BLOB)"); /* Insert a row with one parameter */ PREPARE("INSERT INTO test VALUES (?)"); f->request.stmt_id = stmt_id; ENCODE(&f->request, exec); value.type = SQLITE_BLOB; value.blob.base = buf; value.blob.len = sizeof buf; ENCODE_PARAMS(1, &value); HANDLE(EXEC); CLUSTER_APPLIED(3); ASSERT_CALLBACK(0, RESULT); DECODE(&f->response, result); munit_assert_int(f->response.last_insert_id, ==, 1); munit_assert_int(f->response.rows_affected, ==, 1); PREPARE("SELECT data FROM test"); query.db_id = 0; query.stmt_id = stmt_id; ENCODE(&query, query); HANDLE(QUERY); ASSERT_CALLBACK(0, ROWS); uint64__decode(f->cursor, &n); munit_assert_int(n, ==, 1); text__decode(f->cursor, &column); munit_assert_string_equal(column, "data"); DECODE_ROW(1, &value); munit_assert_int(value.type, ==, SQLITE_BLOB); munit_assert_int(value.blob.len, ==, sizeof buf); munit_assert_int(value.blob.base[0], ==, 'a'); munit_assert_int(value.blob.base[7], ==, 'h'); return MUNIT_OK; } /* The server is not the leader anymore when the first frames hook for a * non-commit frames batch fires. The same leader gets re-elected. */ TEST_CASE(exec, frames_not_leader_1st_non_commit_re_elected, NULL) { struct exec_fixture *f = data; uint64_t stmt_id; unsigned i; (void)params; CLUSTER_ELECT(0); /* Accumulate enough dirty data to fill the page cache */ LOWER_CACHE_SIZE; EXEC("CREATE TABLE test (n INT)"); EXEC("BEGIN"); for (i = 0; i < 162; i++) { EXEC("INSERT INTO test(n) VALUES(1)"); } /* Trigger a page cache flush to the WAL, which fails because we are not * leader anymore */ PREPARE("INSERT INTO test(n) VALUES(1)"); CLUSTER_DEPOSE; EXEC_SUBMIT(stmt_id); ASSERT_CALLBACK(0, FAILURE); ASSERT_FAILURE(SQLITE_IOERR_NOT_LEADER, "not leader"); /* Re-elect ourselves and re-try */ CLUSTER_ELECT(0); EXEC("INSERT INTO test(n) VALUES(1)"); return MUNIT_OK; } /* The server is not the leader anymore when the first frames hook for a * non-commit frames batch fires. Another leader gets re-elected. */ TEST_CASE(exec, frames_not_leader_1st_non_commit_other_elected, NULL) { struct exec_fixture *f = data; uint64_t stmt_id; unsigned i; (void)params; CLUSTER_ELECT(0); /* Accumulate enough dirty data to fill the page cache */ LOWER_CACHE_SIZE; EXEC("CREATE TABLE test (n INT)"); EXEC("BEGIN"); for (i = 0; i < 162; i++) { EXEC("INSERT INTO test(n) VALUES(1)"); } /* Trigger a page cache flush to the WAL, which fails because we are not * leader anymore */ PREPARE("INSERT INTO test(n) VALUES(1)"); CLUSTER_DEPOSE; EXEC_SUBMIT(stmt_id); ASSERT_CALLBACK(0, FAILURE); ASSERT_FAILURE(SQLITE_IOERR_NOT_LEADER, "not leader"); /* Elect another leader and re-try */ CLUSTER_ELECT(1); SELECT(1); OPEN; EXEC("INSERT INTO test(n) VALUES(1)"); return MUNIT_OK; } /* The server is not the leader anymore when the second frames hook for a * non-commit frames batch fires. The same leader gets re-elected. */ TEST_CASE(exec, frames_not_leader_2nd_non_commit_re_elected, NULL) { struct exec_fixture *f = data; uint64_t stmt_id; unsigned i; (void)params; CLUSTER_ELECT(0); /* Accumulate enough dirty data to fill the page cache a first time, * flush it and then fill it a second time. */ LOWER_CACHE_SIZE; EXEC("CREATE TABLE test (n INT)"); EXEC("BEGIN"); for (i = 0; i < 234; i++) { EXEC("INSERT INTO test(n) VALUES(1)"); } /* Trigger a second page cache flush to the WAL, which fails because we * are not leader anymore */ PREPARE("INSERT INTO test(n) VALUES(1)"); CLUSTER_DEPOSE; EXEC_SUBMIT(stmt_id); ASSERT_CALLBACK(0, FAILURE); ASSERT_FAILURE(SQLITE_IOERR_NOT_LEADER, "not leader"); /* Re-elect ourselves and re-try */ CLUSTER_ELECT(0); EXEC("INSERT INTO test(n) VALUES(1)"); return MUNIT_OK; } /* The gateway is closed while a raft commit is in flight. */ TEST_CASE(exec, close_while_in_flight, NULL) { struct exec_fixture *f = data; unsigned i; (void)params; CLUSTER_ELECT(0); /* Accumulate enough dirty data to fill the page cache and trigger * an apply request. */ LOWER_CACHE_SIZE; EXEC("CREATE TABLE test (n INT)"); EXEC("BEGIN"); for (i = 0; i < 162; i++) { EXEC("INSERT INTO test(n) VALUES(1)"); } /* Trigger a second page cache flush to the WAL, and abort before it's * done. */ EXEC_SQL_SUBMIT("INSERT INTO test(n) VALUES(1)"); return MUNIT_OK; } /* The server is not the leader anymore when the second frames hook for a * non-commit frames batch fires. Another leader gets elected. */ TEST_CASE(exec, frames_not_leader_2nd_non_commit_other_elected, NULL) { struct exec_fixture *f = data; uint64_t stmt_id; unsigned i; (void)params; CLUSTER_ELECT(0); /* Accumulate enough dirty data to fill the page cache a first time, * flush it and then fill it a second time. */ LOWER_CACHE_SIZE; EXEC("CREATE TABLE test (n INT)"); EXEC("BEGIN"); for (i = 0; i < 234; i++) { EXEC("INSERT INTO test(n) VALUES(1)"); } /* Trigger a second page cache flush to the WAL, which fails because we * are not leader anymore */ PREPARE("INSERT INTO test(n) VALUES(1)"); CLUSTER_DEPOSE; EXEC_SUBMIT(stmt_id); ASSERT_CALLBACK(0, FAILURE); /* Elect another leader and re-try */ CLUSTER_ELECT(1); SELECT(1); OPEN; EXEC("INSERT INTO test(n) VALUES(1)"); return MUNIT_OK; } /* The server loses leadership after trying to apply the first Frames command * for a non-commit frames batch. The same leader gets re-elected. */ TEST_CASE(exec, frames_leadership_lost_1st_non_commit_re_elected, NULL) { struct exec_fixture *f = data; uint64_t stmt_id; unsigned i; (void)params; CLUSTER_ELECT(0); /* Accumulate enough dirty data to fill the page cache */ LOWER_CACHE_SIZE; EXEC("CREATE TABLE test (n INT)"); EXEC("BEGIN"); for (i = 0; i < 162; i++) { EXEC("INSERT INTO test(n) VALUES(1)"); } /* Trigger a page cache flush to the WAL */ EXEC("INSERT INTO test(n) VALUES(1)"); /* Try to commit */ PREPARE("COMMIT"); EXEC_SUBMIT(stmt_id); CLUSTER_DEPOSE; ASSERT_CALLBACK(0, FAILURE); ASSERT_FAILURE(SQLITE_IOERR_LEADERSHIP_LOST, "disk I/O error"); /* Re-elect ourselves and re-try */ CLUSTER_ELECT(0); EXEC("INSERT INTO test(n) VALUES(1)"); return MUNIT_OK; } /* The server is not the leader anymore when the undo hook for a writing * transaction fires. The same leader gets re-elected. */ TEST_CASE(exec, undo_not_leader_pending_re_elected, NULL) { struct exec_fixture *f = data; uint64_t stmt_id; unsigned i; (void)params; CLUSTER_ELECT(0); /* Accumulate enough dirty data to fill the page cache a first time */ LOWER_CACHE_SIZE; EXEC("CREATE TABLE test (n INT)"); EXEC("BEGIN"); for (i = 0; i < 163; i++) { EXEC("INSERT INTO test(n) VALUES(1)"); } /* Trying to rollback fails because we are not leader anymore */ PREPARE("ROLLBACK"); CLUSTER_DEPOSE; EXEC_SUBMIT(stmt_id); ASSERT_CALLBACK(0, FAILURE); ASSERT_FAILURE(SQLITE_IOERR_NOT_LEADER, "not leader"); /* Re-elect ourselves and re-try */ CLUSTER_ELECT(0); EXEC("INSERT INTO test(n) VALUES(1)"); return MUNIT_OK; } /* The server is not the leader anymore when the undo hook for a writing * transaction fires. Another leader gets elected. */ TEST_CASE(exec, undo_not_leader_pending_other_elected, NULL) { struct exec_fixture *f = data; uint64_t stmt_id; unsigned i; (void)params; CLUSTER_ELECT(0); /* Accumulate enough dirty data to fill the page cache a first time */ LOWER_CACHE_SIZE; EXEC("CREATE TABLE test (n INT)"); EXEC("BEGIN"); for (i = 0; i < 163; i++) { EXEC("INSERT INTO test(n) VALUES(1)"); } /* Trying to rollback fails because we are not leader anymore */ PREPARE("ROLLBACK"); CLUSTER_DEPOSE; EXEC_SUBMIT(stmt_id); ASSERT_CALLBACK(0, FAILURE); ASSERT_FAILURE(SQLITE_IOERR_NOT_LEADER, "not leader"); /* Re-elect ourselves and re-try */ CLUSTER_ELECT(1); SELECT(1); OPEN; EXEC("INSERT INTO test(n) VALUES(1)"); return MUNIT_OK; } /* A follower remains behind and needs to restore state from a snapshot. */ TEST_CASE(exec, restore, NULL) { struct exec_fixture *f = data; uint64_t stmt_id; struct request_query request; struct response_rows response; struct value value; uint64_t n; const char *column; (void)params; CLUSTER_SNAPSHOT_THRESHOLD(0, 5); CLUSTER_SNAPSHOT_TRAILING(0, 2); CLUSTER_ELECT(0); CLUSTER_DISCONNECT(0, 1); EXEC("CREATE TABLE test (n INT)"); EXEC("INSERT INTO test(n) VALUES(1)"); EXEC("INSERT INTO test(n) VALUES(2)"); CLUSTER_RECONNECT(0, 1); CLUSTER_APPLIED(4); /* TODO: the query below fails because we can exec queries only against * the leader. */ return MUNIT_SKIP; /* The follower contains the expected rows. */ SELECT(1); OPEN; PREPARE("SELECT n FROM test"); request.db_id = 0; request.stmt_id = stmt_id; ENCODE(&request, query); HANDLE(QUERY); ASSERT_CALLBACK(0, ROWS); uint64__decode(f->cursor, &n); munit_assert_int(n, ==, 1); text__decode(f->cursor, &column); munit_assert_string_equal(column, "n"); DECODE_ROW(1, &value); munit_assert_int(value.type, ==, SQLITE_INTEGER); munit_assert_int(value.integer, ==, 1); DECODE_ROW(1, &value); munit_assert_int(value.type, ==, SQLITE_INTEGER); munit_assert_int(value.integer, ==, 2); DECODE(&response, rows); munit_assert_ullong(response.eof, ==, DQLITE_RESPONSE_ROWS_DONE); return MUNIT_OK; } /****************************************************************************** * * query * ******************************************************************************/ struct query_fixture { FIXTURE; struct request_query request; struct response_rows response; }; TEST_SUITE(query); TEST_SETUP(query) { struct query_fixture *f = munit_malloc(sizeof *f); SETUP; OPEN; CLUSTER_ELECT(0); EXEC("CREATE TABLE test (n INT, data BLOB)"); return f; } TEST_TEAR_DOWN(query) { struct query_fixture *f = data; TEAR_DOWN; free(f); } /* Successfully query a simple statement with no parameters and yielding no * rows. */ TEST_CASE(query, simple, NULL) { struct query_fixture *f = data; uint64_t stmt_id; uint64_t n; const char *column; (void)params; PREPARE("SELECT n FROM test"); f->request.db_id = 0; f->request.stmt_id = stmt_id; ENCODE(&f->request, query); HANDLE(QUERY); ASSERT_CALLBACK(0, ROWS); uint64__decode(f->cursor, &n); munit_assert_int(n, ==, 1); text__decode(f->cursor, &column); munit_assert_string_equal(column, "n"); DECODE(&f->response, rows); munit_assert_ullong(f->response.eof, ==, DQLITE_RESPONSE_ROWS_DONE); return MUNIT_OK; } /* Successfully query a simple statement with no parameters yielding one row. */ TEST_CASE(query, one_row, NULL) { struct query_fixture *f = data; uint64_t stmt_id; uint64_t n; const char *column; struct value value; (void)params; EXEC("INSERT INTO test(n) VALUES(666)"); PREPARE("SELECT n FROM test"); f->request.db_id = 0; f->request.stmt_id = stmt_id; ENCODE(&f->request, query); HANDLE(QUERY); ASSERT_CALLBACK(0, ROWS); uint64__decode(f->cursor, &n); munit_assert_int(n, ==, 1); text__decode(f->cursor, &column); munit_assert_string_equal(column, "n"); DECODE_ROW(1, &value); munit_assert_int(value.type, ==, SQLITE_INTEGER); munit_assert_int(value.integer, ==, 666); DECODE(&f->response, rows); munit_assert_ullong(f->response.eof, ==, DQLITE_RESPONSE_ROWS_DONE); return MUNIT_OK; } /* Calculate max amount of rows that can fit in 1 response buffer. * A response buffer has _SC_PAGESIZE size. * A response consists of n tuples each row_sz in size * and an 8B EOF marker. */ static unsigned max_rows_buffer(unsigned tuple_row_sz) { unsigned buf_sz = sysconf(_SC_PAGESIZE); unsigned eof_sz = 8; return (buf_sz - eof_sz) / tuple_row_sz; } /* Successfully query that yields a large number of rows that need to be split * into several reponses. */ TEST_CASE(query, large, NULL) { struct query_fixture *f = data; unsigned i; uint64_t stmt_id; uint64_t n; const char *column; struct value value; bool finished; (void)params; EXEC("BEGIN"); /* 16 = 8B header + 8B value (int) */ unsigned n_rows_buffer = max_rows_buffer(16); /* Insert 1 less than 2 response buffers worth of rows, otherwise we need * 3 responses, of which the last one contains no rows. */ for (i = 0; i < ((2 * n_rows_buffer) - 1); i++) { EXEC("INSERT INTO test(n) VALUES(123)"); } EXEC("COMMIT"); PREPARE("SELECT n FROM test"); f->request.db_id = 0; f->request.stmt_id = stmt_id; ENCODE(&f->request, query); HANDLE(QUERY); ASSERT_CALLBACK(0, ROWS); uint64__decode(f->cursor, &n); munit_assert_int(n, ==, 1); text__decode(f->cursor, &column); munit_assert_string_equal(column, "n"); /* First response contains max amount of rows */ for (i = 0; i < n_rows_buffer; i++) { DECODE_ROW(1, &value); munit_assert_int(value.type, ==, SQLITE_INTEGER); munit_assert_int(value.integer, ==, 123); } DECODE(&f->response, rows); munit_assert_ullong(f->response.eof, ==, DQLITE_RESPONSE_ROWS_PART); gateway__resume(f->gateway, &finished); munit_assert_false(finished); ASSERT_CALLBACK(0, ROWS); uint64__decode(f->cursor, &n); munit_assert_int(n, ==, 1); text__decode(f->cursor, &column); munit_assert_string_equal(column, "n"); /* Second, and last, response contains 1 less than maximum amount */ for (i = 0; i < n_rows_buffer - 1; i++) { DECODE_ROW(1, &value); munit_assert_int(value.type, ==, SQLITE_INTEGER); munit_assert_int(value.integer, ==, 123); } DECODE(&f->response, rows); munit_assert_ullong(f->response.eof, ==, DQLITE_RESPONSE_ROWS_DONE); gateway__resume(f->gateway, &finished); munit_assert_true(finished); return MUNIT_OK; } /* Perform a query using a prepared statement with parameters */ TEST_CASE(query, params, NULL) { struct query_fixture *f = data; struct value values[2]; uint64_t stmt_id; (void)params; EXEC("BEGIN"); EXEC("INSERT INTO test(n) VALUES(1)"); EXEC("INSERT INTO test(n) VALUES(2)"); EXEC("INSERT INTO test(n) VALUES(3)"); EXEC("INSERT INTO test(n) VALUES(4)"); EXEC("COMMIT"); PREPARE("SELECT n FROM test WHERE n > ? AND n < ?"); f->request.db_id = 0; f->request.stmt_id = stmt_id; ENCODE(&f->request, query); values[0].type = SQLITE_INTEGER; values[0].integer = 1; values[1].type = SQLITE_INTEGER; values[1].integer = 4; ENCODE_PARAMS(2, values); HANDLE(QUERY); ASSERT_CALLBACK(0, ROWS); return MUNIT_OK; } /* Interrupt a large query. */ TEST_CASE(query, interrupt, NULL) { struct query_fixture *f = data; struct request_interrupt interrupt; unsigned i; uint64_t stmt_id; uint64_t n; const char *column; struct value value; (void)params; EXEC("BEGIN"); /* 16 = 8B header + 8B value (int) */ unsigned n_rows_buffer = max_rows_buffer(16); /* Insert 2 response buffers worth of rows */ for (i = 0; i < 2 * n_rows_buffer; i++) { EXEC("INSERT INTO test(n) VALUES(123)"); } EXEC("COMMIT"); PREPARE("SELECT n FROM test"); f->request.db_id = 0; f->request.stmt_id = stmt_id; ENCODE(&f->request, query); HANDLE(QUERY); ASSERT_CALLBACK(0, ROWS); uint64__decode(f->cursor, &n); munit_assert_int(n, ==, 1); text__decode(f->cursor, &column); munit_assert_string_equal(column, "n"); for (i = 0; i < n_rows_buffer; i++) { DECODE_ROW(1, &value); munit_assert_int(value.type, ==, SQLITE_INTEGER); munit_assert_int(value.integer, ==, 123); } DECODE(&f->response, rows); munit_assert_ullong(f->response.eof, ==, DQLITE_RESPONSE_ROWS_PART); ENCODE(&interrupt, interrupt); HANDLE(INTERRUPT); ASSERT_CALLBACK(0, EMPTY); return MUNIT_OK; } /* Close the gateway during a large query. */ TEST_CASE(query, largeClose, NULL) { struct query_fixture *f = data; unsigned i; uint64_t stmt_id; uint64_t n; const char *column; struct value value; (void)params; EXEC("BEGIN"); /* 16 = 8B header + 8B value (int) */ unsigned n_rows_buffer = max_rows_buffer(16); /* Insert 2 response buffers worth of rows */ for (i = 0; i < 2 * n_rows_buffer; i++) { EXEC("INSERT INTO test(n) VALUES(123)"); } EXEC("COMMIT"); PREPARE("SELECT n FROM test"); f->request.db_id = 0; f->request.stmt_id = stmt_id; ENCODE(&f->request, query); HANDLE(QUERY); ASSERT_CALLBACK(0, ROWS); uint64__decode(f->cursor, &n); munit_assert_int(n, ==, 1); text__decode(f->cursor, &column); munit_assert_string_equal(column, "n"); for (i = 0; i < n_rows_buffer; i++) { DECODE_ROW(1, &value); munit_assert_int(value.type, ==, SQLITE_INTEGER); munit_assert_int(value.integer, ==, 123); } DECODE(&f->response, rows); munit_assert_ullong(f->response.eof, ==, DQLITE_RESPONSE_ROWS_PART); return MUNIT_OK; } /* Submit a query request right after the server has been re-elected and needs * to catch up with logs. */ TEST_CASE(query, barrier, NULL) { struct query_fixture *f = data; uint64_t stmt_id; (void)params; PREPARE("INSERT INTO test(n) VALUES(1)"); EXEC_SUBMIT(stmt_id); CLUSTER_DEPOSE; ASSERT_CALLBACK(0, FAILURE); /* Re-elect ourselves and issue a query request */ CLUSTER_ELECT(0); PREPARE("SELECT n FROM test"); f->request.db_id = 0; f->request.stmt_id = stmt_id; ENCODE(&f->request, query); HANDLE(QUERY); WAIT; ASSERT_CALLBACK(0, ROWS); return MUNIT_OK; } /* Submit a query request right after the server has been re-elected and needs * to catch up with logs, but close early */ TEST_CASE(query, barrierInFlightQuery, NULL) { struct query_fixture *f = data; uint64_t stmt_id; (void)params; PREPARE("INSERT INTO test(n) VALUES(1)"); EXEC_SUBMIT(stmt_id); CLUSTER_DEPOSE; ASSERT_CALLBACK(0, FAILURE); /* Re-elect ourselves and issue a query request */ CLUSTER_ELECT(0); PREPARE("SELECT n FROM test"); f->request.db_id = 0; f->request.stmt_id = stmt_id; ENCODE(&f->request, query); HANDLE(QUERY); return MUNIT_OK; } /* Submit a query sql request right after the server has been re-elected and needs * to catch up with logs, but close early */ TEST_CASE(query, barrierInFlightQuerySql, NULL) { struct query_fixture *f = data; uint64_t stmt_id; (void)params; PREPARE("INSERT INTO test(n) VALUES(1)"); EXEC_SUBMIT(stmt_id); CLUSTER_DEPOSE; ASSERT_CALLBACK(0, FAILURE); /* Re-elect ourselves and issue a query request */ CLUSTER_ELECT(0); QUERY_SQL_SUBMIT("SELECT n FROM test"); return MUNIT_OK; } /* Submit an exec request right after the server has been re-elected and needs * to catch up with logs, but close early */ TEST_CASE(query, barrierInFlightExec, NULL) { struct query_fixture *f = data; uint64_t stmt_id; (void)params; PREPARE("INSERT INTO test(n) VALUES(1)"); EXEC_SUBMIT(stmt_id); CLUSTER_DEPOSE; ASSERT_CALLBACK(0, FAILURE); /* Re-elect ourselves and issue an exec request */ CLUSTER_ELECT(0); PREPARE("INSERT INTO test(n) VALUES(2)"); EXEC_SUBMIT(stmt_id); return MUNIT_OK; } /****************************************************************************** * * finalize * ******************************************************************************/ struct finalize_fixture { FIXTURE; struct request_finalize request; struct response_empty response; }; TEST_SUITE(finalize); TEST_SETUP(finalize) { struct finalize_fixture *f = munit_malloc(sizeof *f); SETUP; OPEN; return f; } TEST_TEAR_DOWN(finalize) { struct finalize_fixture *f = data; TEAR_DOWN; free(f); } /* Finalize a prepared statement. */ TEST_CASE(finalize, success, NULL) { uint64_t stmt_id; struct finalize_fixture *f = data; (void)params; PREPARE("CREATE TABLE test (n INT)"); f->request.db_id = 0; f->request.stmt_id = stmt_id; ENCODE(&f->request, finalize); HANDLE(FINALIZE); ASSERT_CALLBACK(0, EMPTY); return MUNIT_OK; } /****************************************************************************** * * exec_sql * ******************************************************************************/ struct exec_sql_fixture { FIXTURE; struct request_exec_sql request; struct response_result response; }; TEST_SUITE(exec_sql); TEST_SETUP(exec_sql) { struct exec_sql_fixture *f = munit_malloc(sizeof *f); SETUP; CLUSTER_ELECT(0); OPEN; return f; } TEST_TEAR_DOWN(exec_sql) { struct exec_sql_fixture *f = data; TEAR_DOWN; free(f); } /* Exec a SQL text with a single query. */ TEST_CASE(exec_sql, single, NULL) { struct exec_sql_fixture *f = data; (void)params; f->request.db_id = 0; f->request.sql = "CREATE TABLE test (n INT)"; ENCODE(&f->request, exec_sql); HANDLE(EXEC_SQL); CLUSTER_APPLIED(2); ASSERT_CALLBACK(0, RESULT); return MUNIT_OK; } /* Exec a SQL text with a multiple queries. */ TEST_CASE(exec_sql, multi, NULL) { struct exec_sql_fixture *f = data; (void)params; f->request.db_id = 0; f->request.sql = "CREATE TABLE test (n INT); INSERT INTO test VALUES(1)"; ENCODE(&f->request, exec_sql); HANDLE(EXEC_SQL); WAIT; ASSERT_CALLBACK(0, RESULT); return MUNIT_OK; } /****************************************************************************** * * query_sql * ******************************************************************************/ struct query_sql_fixture { FIXTURE; struct request_query_sql request; struct response_rows response; }; TEST_SUITE(query_sql); TEST_SETUP(query_sql) { struct query_sql_fixture *f = munit_malloc(sizeof *f); SETUP; CLUSTER_ELECT(0); OPEN; EXEC("CREATE TABLE test (n INT)"); return f; } TEST_TEAR_DOWN(query_sql) { struct query_sql_fixture *f = data; TEAR_DOWN; free(f); } /* Exec a SQL query whose result set fits in a page. */ TEST_CASE(query_sql, small, NULL) { struct query_sql_fixture *f = data; (void)params; EXEC("INSERT INTO test VALUES(123)"); f->request.db_id = 0; f->request.sql = "SELECT n FROM test"; ENCODE(&f->request, query_sql); HANDLE(QUERY_SQL); ASSERT_CALLBACK(0, ROWS); return MUNIT_OK; } /* Exec a SQL query whose result set needs multiple pages. */ TEST_CASE(query_sql, large, NULL) { struct query_sql_fixture *f = data; (void)params; unsigned i; uint64_t n; const char *column; struct value value; bool finished; EXEC("BEGIN"); /* 16 = 8B header + 8B value (int) */ unsigned n_rows_buffer = max_rows_buffer(16); /* Insert 1 less than 2 response buffers worth of rows, otherwise we need * 3 responses, of which the last one contains no rows. */ for (i = 0; i < ((2 * n_rows_buffer) - 1); i++) { EXEC("INSERT INTO test(n) VALUES(123)"); } EXEC("COMMIT"); f->request.db_id = 0; f->request.sql = "SELECT n FROM test"; ENCODE(&f->request, query_sql); HANDLE(QUERY_SQL); ASSERT_CALLBACK(0, ROWS); uint64__decode(f->cursor, &n); munit_assert_int(n, ==, 1); text__decode(f->cursor, &column); munit_assert_string_equal(column, "n"); /* First response contains max amount of rows */ for (i = 0; i < n_rows_buffer; i++) { DECODE_ROW(1, &value); munit_assert_int(value.type, ==, SQLITE_INTEGER); munit_assert_int(value.integer, ==, 123); } DECODE(&f->response, rows); munit_assert_ullong(f->response.eof, ==, DQLITE_RESPONSE_ROWS_PART); gateway__resume(f->gateway, &finished); munit_assert_false(finished); ASSERT_CALLBACK(0, ROWS); uint64__decode(f->cursor, &n); munit_assert_int(n, ==, 1); text__decode(f->cursor, &column); munit_assert_string_equal(column, "n"); /* Second, and last, response contains 1 less than maximum amount */ for (i = 0; i < n_rows_buffer - 1; i++) { DECODE_ROW(1, &value); munit_assert_int(value.type, ==, SQLITE_INTEGER); munit_assert_int(value.integer, ==, 123); } DECODE(&f->response, rows); munit_assert_ullong(f->response.eof, ==, DQLITE_RESPONSE_ROWS_DONE); gateway__resume(f->gateway, &finished); munit_assert_true(finished); return MUNIT_OK; } /* Exec a SQL query whose result set needs multiple pages and close before * receiving the full result set. */ TEST_CASE(query_sql, largeClose, NULL) { struct query_sql_fixture *f = data; (void)params; unsigned i; uint64_t n; const char *column; struct value value; EXEC("BEGIN"); /* 16 = 8B header + 8B value (int) */ unsigned n_rows_buffer = max_rows_buffer(16); /* Insert 1 less than 2 response buffers worth of rows, otherwise we need * 3 responses, of which the last one contains no rows. */ for (i = 0; i < ((2 * n_rows_buffer) - 1); i++) { EXEC("INSERT INTO test(n) VALUES(123)"); } EXEC("COMMIT"); f->request.db_id = 0; f->request.sql = "SELECT n FROM test"; ENCODE(&f->request, query_sql); HANDLE(QUERY_SQL); ASSERT_CALLBACK(0, ROWS); uint64__decode(f->cursor, &n); munit_assert_int(n, ==, 1); text__decode(f->cursor, &column); munit_assert_string_equal(column, "n"); /* First response contains max amount of rows */ for (i = 0; i < n_rows_buffer; i++) { DECODE_ROW(1, &value); munit_assert_int(value.type, ==, SQLITE_INTEGER); munit_assert_int(value.integer, ==, 123); } DECODE(&f->response, rows); munit_assert_ullong(f->response.eof, ==, DQLITE_RESPONSE_ROWS_PART); return MUNIT_OK; } /* Perform a query with parameters */ TEST_CASE(query_sql, params, NULL) { struct query_sql_fixture *f = data; struct value values[2]; (void)params; EXEC("BEGIN"); EXEC("INSERT INTO test(n) VALUES(1)"); EXEC("INSERT INTO test(n) VALUES(2)"); EXEC("INSERT INTO test(n) VALUES(3)"); EXEC("INSERT INTO test(n) VALUES(4)"); EXEC("COMMIT"); f->request.db_id = 0; f->request.sql = "SELECT n FROM test WHERE n > ? AND n < ?"; ENCODE(&f->request, query_sql); values[0].type = SQLITE_INTEGER; values[0].integer = 1; values[1].type = SQLITE_INTEGER; values[1].integer = 4; ENCODE_PARAMS(2, values); HANDLE(QUERY_SQL); ASSERT_CALLBACK(0, ROWS); return MUNIT_OK; } dqlite-1.9.1/test/unit/test_registry.c000066400000000000000000000032341416342771300200270ustar00rootroot00000000000000#include "../lib/config.h" #include "../lib/heap.h" #include "../lib/logger.h" #include "../lib/registry.h" #include "../lib/runner.h" #include "../lib/sqlite.h" #include "../lib/vfs.h" TEST_MODULE(registry); #define FIXTURE \ FIXTURE_LOGGER; \ FIXTURE_VFS; \ FIXTURE_CONFIG; \ FIXTURE_REGISTRY; #define SETUP \ SETUP_HEAP; \ SETUP_SQLITE; \ SETUP_LOGGER; \ SETUP_VFS; \ SETUP_CONFIG; \ SETUP_REGISTRY; #define TEAR_DOWN \ TEAR_DOWN_REGISTRY; \ TEAR_DOWN_CONFIG; \ TEAR_DOWN_VFS; \ TEAR_DOWN_LOGGER; \ TEAR_DOWN_SQLITE; \ TEAR_DOWN_HEAP; /****************************************************************************** * * db-related APIs. * ******************************************************************************/ struct db_fixture { FIXTURE; }; TEST_SUITE(db); TEST_SETUP(db) { struct db_fixture *f = munit_malloc(sizeof *f); SETUP; return f; } TEST_TEAR_DOWN(db) { struct db_fixture *f = data; TEAR_DOWN; free(f); } /* Get a db that didn't exist before. */ TEST_CASE(db, get_new, NULL) { struct db_fixture *f = data; struct db *db; (void)params; int rc; rc = registry__db_get(&f->registry, "test.db", &db); munit_assert_int(rc, ==, 0); munit_assert_string_equal(db->filename, "test.db"); return MUNIT_OK; } /* Get a previously registered db. */ TEST_CASE(db, get_existing, NULL) { struct db_fixture *f = data; struct db *db1; struct db *db2; (void)params; int rc; rc = registry__db_get(&f->registry, "test.db", &db1); munit_assert_int(rc, ==, 0); rc = registry__db_get(&f->registry, "test.db", &db2); munit_assert_int(rc, ==, 0); munit_assert_ptr_equal(db1, db2); return MUNIT_OK; } dqlite-1.9.1/test/unit/test_replication.c000066400000000000000000000256451416342771300205020ustar00rootroot00000000000000#include "../lib/cluster.h" #include "../lib/runner.h" #include "../../src/format.h" #include "../../src/leader.h" TEST_MODULE(replication_v1); /****************************************************************************** * * Fixture * ******************************************************************************/ #define FIXTURE \ FIXTURE_CLUSTER; \ struct leader leaders[N_SERVERS]; \ sqlite3_stmt *stmt; #define SETUP \ unsigned i; \ SETUP_CLUSTER(V2) \ for (i = 0; i < N_SERVERS; i++) { \ SETUP_LEADER(i); \ } #define SETUP_LEADER(I) \ do { \ struct leader *leader = &f->leaders[I]; \ struct registry *registry = CLUSTER_REGISTRY(I); \ struct db *db; \ int rc2; \ rc2 = registry__db_get(registry, "test.db", &db); \ munit_assert_int(rc2, ==, 0); \ rc2 = leader__init(leader, db, CLUSTER_RAFT(I)); \ munit_assert_int(rc2, ==, 0); \ } while (0) #define TEAR_DOWN \ unsigned i; \ for (i = 0; i < N_SERVERS; i++) { \ TEAR_DOWN_LEADER(i); \ } \ TEAR_DOWN_CLUSTER #define TEAR_DOWN_LEADER(I) \ do { \ struct leader *leader = &f->leaders[I]; \ leader__close(leader); \ } while (0) /****************************************************************************** * * Helper macros. * ******************************************************************************/ /* Return the i'th leader object. */ #define LEADER(I) &f->leaders[I] /* Return the SQLite connection of the i'th leader object */ #define CONN(I) (LEADER(I))->conn /* Prepare the fixture's statement using the connection of the I'th leader */ #define PREPARE(I, SQL) \ { \ int rc2; \ rc2 = sqlite3_prepare_v2(CONN(I), SQL, -1, &f->stmt, NULL); \ munit_assert_int(rc2, ==, 0); \ } /* Reset the fixture's statement, expecting the given return code. */ #define RESET(RC) \ { \ int rc2; \ rc2 = sqlite3_reset(f->stmt); \ munit_assert_int(rc2, ==, RC); \ } /* Finalize the fixture's statement */ #define FINALIZE \ { \ int rc2; \ rc2 = sqlite3_finalize(f->stmt); \ munit_assert_int(rc2, ==, 0); \ } /* Submit an exec request using the I'th leader. */ #define EXEC(I) \ { \ int rc2; \ rc2 = leader__exec(LEADER(I), &f->req, f->stmt, \ fixture_exec_cb); \ munit_assert_int(rc2, ==, 0); \ } /* Convenience to prepare, execute and finalize a statement. */ #define EXEC_SQL(I, SQL) \ PREPARE(I, SQL); \ EXEC(I); \ CLUSTER_APPLIED(CLUSTER_LAST_INDEX(I)); \ FINALIZE /****************************************************************************** * * Helper macros. * ******************************************************************************/ /* Assert the number of pages in the WAL file on the I'th node. */ #define ASSERT_WAL_PAGES(I, N) \ { \ struct leader *leader_ = &f->leaders[I]; \ sqlite3_file *file_; \ sqlite_int64 size_; \ int pages_; \ int rv_; \ rv_ = sqlite3_file_control(leader_->conn, "main", \ SQLITE_FCNTL_JOURNAL_POINTER, \ &file_); \ munit_assert_int(rv_, ==, 0); \ rv_ = file_->pMethods->xFileSize(file_, &size_); \ munit_assert_int(rv_, ==, 0); \ pages_ = formatWalCalcFramesNumber( \ leader_->db->config->page_size, size_); \ munit_assert_int(pages_, ==, N); \ } /****************************************************************************** * * leader__init * ******************************************************************************/ struct init_fixture { FIXTURE; }; TEST_SUITE(init); TEST_SETUP(init) { struct init_fixture *f = munit_malloc(sizeof *f); SETUP; return f; } TEST_TEAR_DOWN(init) { struct init_fixture *f = data; TEAR_DOWN; free(f); } /* The connection is open and can be used. */ TEST_CASE(init, conn, NULL) { struct init_fixture *f = data; sqlite3_stmt *stmt; int rc; (void)params; rc = sqlite3_prepare_v2(CONN(0), "SELECT 1", -1, &stmt, NULL); munit_assert_int(rc, ==, 0); sqlite3_finalize(stmt); return MUNIT_OK; } /****************************************************************************** * * leader__exec * ******************************************************************************/ struct exec_fixture { FIXTURE; struct exec req; bool invoked; int status; }; static void fixture_exec_cb(struct exec *req, int status) { struct exec_fixture *f = req->data; f->invoked = true; f->status = status; } TEST_SUITE(exec); TEST_SETUP(exec) { struct exec_fixture *f = munit_malloc(sizeof *f); SETUP; f->req.data = f; return f; } TEST_TEAR_DOWN(exec) { struct exec_fixture *f = data; TEAR_DOWN; free(f); } TEST_CASE(exec, success, NULL) { struct exec_fixture *f = data; (void)params; CLUSTER_ELECT(0); PREPARE(0, "CREATE TABLE test (a INT)"); EXEC(0); CLUSTER_APPLIED(2); munit_assert_true(f->invoked); munit_assert_int(f->status, ==, SQLITE_DONE); FINALIZE; return MUNIT_OK; } /* A snapshot is taken after applying an entry. */ TEST_CASE(exec, snapshot, NULL) { struct exec_fixture *f = data; (void)params; CLUSTER_SNAPSHOT_THRESHOLD(0, 4); CLUSTER_ELECT(0); PREPARE(0, "CREATE TABLE test (n INT)"); EXEC(0); CLUSTER_APPLIED(2); FINALIZE; PREPARE(0, "INSERT INTO test(n) VALUES(1)"); EXEC(0); CLUSTER_APPLIED(3); munit_assert_true(f->invoked); munit_assert_int(f->status, ==, SQLITE_DONE); FINALIZE; return MUNIT_OK; } /* If a transaction is in progress, no snapshot is taken. */ TEST_CASE(exec, snapshot_busy, NULL) { struct exec_fixture *f = data; (void)params; unsigned i; CLUSTER_SNAPSHOT_THRESHOLD(0, 4); CLUSTER_ELECT(0); EXEC_SQL(0, "PRAGMA cache_size = 1"); EXEC_SQL(0, "CREATE TABLE test (n INT)"); EXEC_SQL(0, "BEGIN"); /* Accumulate enough dirty data to fill the page cache */ for (i = 0; i < 163; i++) { EXEC_SQL(0, "INSERT INTO test(n) VALUES(1)"); } return MUNIT_OK; } /* If the WAL size grows beyond the configured threshold, checkpoint it. */ TEST_CASE(exec, checkpoint, NULL) { struct exec_fixture *f = data; struct config *config = CLUSTER_CONFIG(0); (void)params; config->checkpoint_threshold = 3; CLUSTER_ELECT(0); EXEC_SQL(0, "CREATE TABLE test (n INT)"); EXEC_SQL(0, "INSERT INTO test(n) VALUES(1)"); /* The WAL was truncated. */ ASSERT_WAL_PAGES(0, 0); return MUNIT_OK; } /* If a read transaction is in progress, no checkpoint is taken. */ TEST_CASE(exec, checkpoint_read_lock, NULL) { struct exec_fixture *f = data; struct config *config = CLUSTER_CONFIG(0); struct registry *registry = CLUSTER_REGISTRY(0); struct db *db; struct leader leader2; char *errmsg; int rv; (void)params; config->checkpoint_threshold = 3; CLUSTER_ELECT(0); EXEC_SQL(0, "CREATE TABLE test (n INT)"); /* Initialize another leader. */ rv = registry__db_get(registry, "test.db", &db); munit_assert_int(rv, ==, 0); leader__init(&leader2, db, CLUSTER_RAFT(0)); /* Start a read transaction in the other leader. */ rv = sqlite3_exec(leader2.conn, "BEGIN", NULL, NULL, &errmsg); munit_assert_int(rv, ==, 0); rv = sqlite3_exec(leader2.conn, "SELECT * FROM test", NULL, NULL, &errmsg); munit_assert_int(rv, ==, 0); EXEC_SQL(0, "INSERT INTO test(n) VALUES(1)"); /* The WAL was not truncated. */ ASSERT_WAL_PAGES(0, 3); leader__close(&leader2); return MUNIT_OK; } /****************************************************************************** * * Fixture * ******************************************************************************/ struct fixture { FIXTURE_CLUSTER; struct leader leaders[N_SERVERS]; sqlite3_stmt *stmt; struct exec req; bool invoked; int status; }; static void *setUp(const MunitParameter params[], void *user_data) { struct fixture *f = munit_malloc(sizeof *f); SETUP_CLUSTER(V2); SETUP_LEADER(0); f->req.data = f; return f; } static void tearDown(void *data) { struct fixture *f = data; TEAR_DOWN_LEADER(0); TEAR_DOWN_CLUSTER; free(f); } SUITE(replication) static void execCb(struct exec *req, int status) { struct fixture *f = req->data; f->invoked = true; f->status = status; } TEST(replication, exec, setUp, tearDown, 0, NULL) { struct fixture *f = data; int rv; CLUSTER_ELECT(0); PREPARE(0, "BEGIN"); rv = leader__exec(LEADER(0), &f->req, f->stmt, execCb); munit_assert_int(rv, ==, 0); munit_assert_true(f->invoked); munit_assert_int(f->status, ==, SQLITE_DONE); f->invoked = false; FINALIZE; PREPARE(0, "CREATE TABLE test (a INT)"); rv = leader__exec(LEADER(0), &f->req, f->stmt, execCb); munit_assert_int(rv, ==, 0); munit_assert_true(f->invoked); munit_assert_int(f->status, ==, SQLITE_DONE); f->invoked = false; FINALIZE; PREPARE(0, "COMMIT"); rv = leader__exec(LEADER(0), &f->req, f->stmt, execCb); munit_assert_int(rv, ==, 0); munit_assert_false(f->invoked); FINALIZE; CLUSTER_APPLIED(2); munit_assert_true(f->invoked); munit_assert_int(f->status, ==, SQLITE_DONE); PREPARE(0, "SELECT * FROM test"); FINALIZE; SETUP_LEADER(1); PREPARE(1, "SELECT * FROM test"); FINALIZE; TEAR_DOWN_LEADER(1); return MUNIT_OK; } /* If the WAL size grows beyond the configured threshold, checkpoint it. */ TEST(replication, checkpoint, setUp, tearDown, 0, NULL) { struct fixture *f = data; struct config *config = CLUSTER_CONFIG(0); int rv; config->checkpoint_threshold = 3; CLUSTER_ELECT(0); PREPARE(0, "CREATE TABLE test (n INT)"); rv = leader__exec(LEADER(0), &f->req, f->stmt, execCb); munit_assert_int(rv, ==, 0); FINALIZE; CLUSTER_APPLIED(2); PREPARE(0, "INSERT INTO test(n) VALUES(1)"); rv = leader__exec(LEADER(0), &f->req, f->stmt, execCb); munit_assert_int(rv, ==, 0); FINALIZE; CLUSTER_APPLIED(4); /* The WAL was truncated. */ ASSERT_WAL_PAGES(0, 0); return MUNIT_OK; } dqlite-1.9.1/test/unit/test_request.c000066400000000000000000000054131416342771300176500ustar00rootroot00000000000000#include "../../src/request.h" #include "../lib/heap.h" #include "../lib/runner.h" TEST_MODULE(request); /****************************************************************************** * * Fixture * ******************************************************************************/ struct fixture { void *buf; }; static void *setup(const MunitParameter params[], void *user_data) { struct fixture *f; f = munit_malloc(sizeof *f); SETUP_HEAP; f->buf = NULL; return f; } static void tear_down(void *data) { struct fixture *f = data; free(f->buf); TEAR_DOWN_HEAP; free(f); } /****************************************************************************** * * Helper macros * ******************************************************************************/ #define ALLOC_BUF(N) f->buf = munit_malloc(N); /****************************************************************************** * * Serialize * ******************************************************************************/ TEST_SUITE(serialize); TEST_SETUP(serialize, setup); TEST_TEAR_DOWN(serialize, tear_down); TEST_CASE(serialize, leader, NULL) { struct fixture *f = data; struct request_leader request; void *cursor1; struct cursor cursor2; size_t n = request_leader__sizeof(&request); (void)params; ALLOC_BUF(n); cursor1 = f->buf; request_leader__encode(&request, &cursor1); cursor2.p = f->buf; cursor2.cap = n; request_leader__decode(&cursor2, &request); return MUNIT_OK; } /****************************************************************************** * * Decode * ******************************************************************************/ TEST_SUITE(decode); TEST_SETUP(decode, setup); TEST_TEAR_DOWN(decode, tear_down); TEST_CASE(decode, leader, NULL) { (void)data; (void)params; return MUNIT_OK; } #if 0 TEST_CASE(decode, client, NULL) { struct request *request = data; int err; (void)params; test_message_send_client(123, &request->message); err = request_decode(request); munit_assert_int(err, ==, 0); munit_assert_int(request->client.id, ==, 123); return MUNIT_OK; } TEST_CASE(decode, heartbeat, NULL) { struct request *request = data; int err; (void)params; test_message_send_heartbeat(666, &request->message); err = request_decode(request); munit_assert_int(err, ==, 0); munit_assert_int(request->heartbeat.timestamp, ==, 666); return MUNIT_OK; } TEST_CASE(decode, open, NULL) { struct request *request = data; int err; (void)params; test_message_send_open("test.db", 123, "volatile", &request->message); err = request_decode(request); munit_assert_int(err, ==, 0); munit_assert_string_equal(request->open.name, "test.db"); munit_assert_int(request->open.flags, ==, 123); munit_assert_string_equal(request->open.vfs, "volatile"); return MUNIT_OK; } #endif dqlite-1.9.1/test/unit/test_tuple.c000066400000000000000000000277601416342771300173220ustar00rootroot00000000000000#include #include "../../src/tuple.h" #include "../lib/runner.h" TEST_MODULE(tuple); /****************************************************************************** * * Helper macros. * ******************************************************************************/ #define DECODER_INIT(N) \ { \ int rc2; \ rc2 = tuple_decoder__init(&decoder, N, &cursor); \ munit_assert_int(rc2, ==, 0); \ } #define DECODER_NEXT \ { \ int rc2; \ rc2 = tuple_decoder__next(&decoder, &value); \ munit_assert_int(rc2, ==, 0); \ } #define ENCODER_INIT(N, FORMAT) \ { \ int rc2; \ rc2 = tuple_encoder__init(&f->encoder, N, FORMAT, &f->buffer); \ munit_assert_int(rc2, ==, 0); \ } #define ENCODER_NEXT \ { \ int rc2; \ rc2 = tuple_encoder__next(&f->encoder, &value); \ munit_assert_int(rc2, ==, 0); \ } /****************************************************************************** * * Assertions. * ******************************************************************************/ #define ASSERT_VALUE_TYPE(TYPE) munit_assert_int(value.type, ==, TYPE) /****************************************************************************** * * Decoder. * ******************************************************************************/ TEST_SUITE(decoder); TEST_GROUP(decoder, init); /* If n is 0, then the parameters format is used to dermine the number of * elements of the tuple. */ TEST_CASE(decoder, init, param, NULL) { struct tuple_decoder decoder; char buf[] = {2, 0, 0, 0, 0, 0, 0, 0, 0}; struct cursor cursor = {buf, sizeof buf}; (void)data; (void)params; DECODER_INIT(0); munit_assert_uint(decoder.n, ==, 2); munit_assert_uint(tuple_decoder__n(&decoder), ==, 2); return MUNIT_OK; } /* If n is not 0, then it is the number of elements. */ TEST_CASE(decoder, init, row, NULL) { struct tuple_decoder decoder; char buf[] = {2, 0, 0, 0, 0, 0, 0, 0, 0}; struct cursor cursor = {buf, sizeof buf}; (void)data; (void)params; DECODER_INIT(3); munit_assert_uint(decoder.n, ==, 3); munit_assert_uint(tuple_decoder__n(&decoder), ==, 3); return MUNIT_OK; } TEST_GROUP(decoder, row); /* Decode a tuple with row format and only one value. */ TEST_CASE(decoder, row, one_value, NULL) { struct tuple_decoder decoder; uint8_t buf[][8] = { {SQLITE_INTEGER, 0, 0, 0, 0, 0, 0, 0}, {7, 0, 0, 0, 0, 0, 0, 0}, }; struct cursor cursor = {buf, sizeof buf}; struct value value; (void)data; (void)params; DECODER_INIT(1); DECODER_NEXT; ASSERT_VALUE_TYPE(SQLITE_INTEGER); munit_assert_int64(value.integer, ==, 7); return MUNIT_OK; } /* Decode a tuple with row format and two values. */ TEST_CASE(decoder, row, two_values, NULL) { struct tuple_decoder decoder; uint8_t buf[][8] = { {SQLITE_INTEGER | SQLITE_TEXT << 4, 0, 0, 0, 0, 0, 0, 0}, {7, 0, 0, 0, 0, 0, 0, 0}, {'h', 'e', 'l', 'l', 'o', 0, 0, 0}, }; struct cursor cursor = {buf, sizeof buf}; struct value value; (void)data; (void)params; DECODER_INIT(2); DECODER_NEXT; ASSERT_VALUE_TYPE(SQLITE_INTEGER); munit_assert_int64(value.integer, ==, 7); DECODER_NEXT; ASSERT_VALUE_TYPE(SQLITE_TEXT); munit_assert_string_equal(value.text, "hello"); return MUNIT_OK; } TEST_GROUP(decoder, params); /* Decode a tuple with params format and only one value. */ TEST_CASE(decoder, params, one_value, NULL) { struct tuple_decoder decoder; uint8_t buf[][8] = { {1, SQLITE_INTEGER, 0, 0, 0, 0, 0, 0}, {7, 0, 0, 0, 0, 0, 0, 0}, }; struct cursor cursor = {buf, sizeof buf}; struct value value; (void)data; (void)params; DECODER_INIT(0); DECODER_NEXT; ASSERT_VALUE_TYPE(SQLITE_INTEGER); munit_assert_int64(value.integer, ==, 7); return MUNIT_OK; } /* Decode a tuple with params format and two values. */ TEST_CASE(decoder, params, two_values, NULL) { struct tuple_decoder decoder; uint8_t buf[][8] = { {2, SQLITE_INTEGER, SQLITE_TEXT, 0, 0, 0, 0, 0}, {7, 0, 0, 0, 0, 0, 0, 0}, {'h', 'e', 'l', 'l', 'o', 0, 0, 0}, }; struct cursor cursor = {buf, sizeof buf}; struct value value; (void)data; (void)params; DECODER_INIT(0); DECODER_NEXT; ASSERT_VALUE_TYPE(SQLITE_INTEGER); munit_assert_int64(value.integer, ==, 7); DECODER_NEXT; ASSERT_VALUE_TYPE(SQLITE_TEXT); munit_assert_string_equal(value.text, "hello"); return MUNIT_OK; } TEST_GROUP(decoder, type); /* Decode a floating point number. */ TEST_CASE(decoder, type, float, NULL) { struct tuple_decoder decoder; uint8_t buf[][8] __attribute__((aligned(sizeof(uint64_t)))) = { {SQLITE_FLOAT, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0}, }; struct cursor cursor = {buf, sizeof buf}; struct value value; double pi = 3.1415; (void)data; (void)params; memcpy(buf[1], &pi, sizeof pi); uint64_t *buf_value = __builtin_assume_aligned(buf[1], sizeof(uint64_t)); *buf_value = byte__flip64(*buf_value); DECODER_INIT(1); DECODER_NEXT; ASSERT_VALUE_TYPE(SQLITE_FLOAT); munit_assert_double(value.float_, ==, 3.1415); return MUNIT_OK; } /* Decode a null value. */ TEST_CASE(decoder, type, null, NULL) { struct tuple_decoder decoder; uint8_t buf[][8] __attribute__((aligned(sizeof(uint64_t)))) = { {SQLITE_NULL, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0}, }; struct cursor cursor = {buf, sizeof buf}; struct value value; (void)data; (void)params; DECODER_INIT(1); DECODER_NEXT; ASSERT_VALUE_TYPE(SQLITE_NULL); return MUNIT_OK; } /* Decode a date string in ISO8601 format. */ TEST_CASE(decoder, type, iso8601, NULL) { struct tuple_decoder decoder; uint8_t buf[5][8] __attribute__((aligned(sizeof(uint64_t)))) = { {DQLITE_ISO8601, 0, 0, 0, 0, 0, 0, 0}, }; struct cursor cursor = {buf, sizeof buf}; struct value value; (void)data; (void)params; strcpy((char *)buf[1], "2018-07-20 09:49:05+00:00"); DECODER_INIT(1); DECODER_NEXT; ASSERT_VALUE_TYPE(DQLITE_ISO8601); munit_assert_string_equal(value.iso8601, "2018-07-20 09:49:05+00:00"); return MUNIT_OK; } /* Decode a boolean. */ TEST_CASE(decoder, type, boolean, NULL) { struct tuple_decoder decoder; uint8_t buf[][8] __attribute__((aligned(sizeof(uint64_t)))) = { {DQLITE_BOOLEAN, 0, 0, 0, 0, 0, 0, 0}, {1, 0, 0, 0, 0, 0, 0, 0}, }; struct cursor cursor = {buf, sizeof buf}; struct value value; (void)data; (void)params; DECODER_INIT(1); DECODER_NEXT; ASSERT_VALUE_TYPE(DQLITE_BOOLEAN); munit_assert_uint64(value.boolean, ==, 1); return MUNIT_OK; } /****************************************************************************** * * Encoder. * ******************************************************************************/ struct encoder_fixture { struct buffer buffer; struct tuple_encoder encoder; }; TEST_SUITE(encoder); TEST_SETUP(encoder) { struct encoder_fixture *f = munit_malloc(sizeof *f); int rc; (void)params; (void)user_data; rc = buffer__init(&f->buffer); munit_assert_int(rc, ==, 0); return f; } TEST_TEAR_DOWN(encoder) { struct encoder_fixture *f = data; buffer__close(&f->buffer); free(data); } TEST_GROUP(encoder, row); /* Encode a tuple with row format and only one value. */ TEST_CASE(encoder, row, one_value, NULL) { struct encoder_fixture *f = data; struct value value; uint8_t(*buf)[8] = f->buffer.data; (void)params; ENCODER_INIT(1, TUPLE__ROW); value.type = SQLITE_INTEGER; value.integer = 7; ENCODER_NEXT; munit_assert_int(buf[0][0], ==, SQLITE_INTEGER); /* malloc'ed buffer is aligned suitably */ uint64_t *value_ptr = __builtin_assume_aligned(buf[1], sizeof(uint64_t)); munit_assert_uint64(*value_ptr, ==, byte__flip64(7)); return MUNIT_OK; } /* Encode a tuple with row format and two values. */ TEST_CASE(encoder, row, two_values, NULL) { struct encoder_fixture *f = data; struct value value; uint8_t(*buf)[8] = f->buffer.data; (void)params; ENCODER_INIT(2, TUPLE__ROW); value.type = SQLITE_INTEGER; value.integer = 7; ENCODER_NEXT; value.type = SQLITE_TEXT; value.text = "hello"; ENCODER_NEXT; munit_assert_int(buf[0][0], ==, SQLITE_INTEGER | SQLITE_TEXT << 4); /* malloc'ed buffer is aligned suitably */ uint64_t *value_ptr = __builtin_assume_aligned(buf[1], sizeof(uint64_t)); munit_assert_uint64(*value_ptr, ==, byte__flip64(7)); munit_assert_string_equal((const char *)buf[2], "hello"); return MUNIT_OK; } TEST_GROUP(encoder, params); /* Encode a tuple with params format and only one value. */ TEST_CASE(encoder, params, one_value, NULL) { struct encoder_fixture *f = data; struct value value; uint8_t(*buf)[8] = f->buffer.data; (void)params; ENCODER_INIT(1, TUPLE__PARAMS); value.type = SQLITE_INTEGER; value.integer = 7; ENCODER_NEXT; munit_assert_int(buf[0][0], ==, 1); munit_assert_int(buf[0][1], ==, SQLITE_INTEGER); uint64_t *value_ptr = __builtin_assume_aligned(buf[1], sizeof(uint64_t)); munit_assert_uint64(*value_ptr, ==, byte__flip64(7)); return MUNIT_OK; } /* Encode a tuple with params format and two values. */ TEST_CASE(encoder, params, two_values, NULL) { struct encoder_fixture *f = data; struct value value; uint8_t(*buf)[8] = f->buffer.data; (void)params; ENCODER_INIT(2, TUPLE__PARAMS); value.type = SQLITE_INTEGER; value.integer = 7; ENCODER_NEXT; value.type = SQLITE_TEXT; value.text = "hello"; ENCODER_NEXT; munit_assert_int(buf[0][0], ==, 2); munit_assert_int(buf[0][1], ==, SQLITE_INTEGER); munit_assert_int(buf[0][2], ==, SQLITE_TEXT); uint64_t *value_ptr = __builtin_assume_aligned(buf[1], sizeof(uint64_t)); munit_assert_uint64(*value_ptr, ==, byte__flip64(7)); munit_assert_string_equal((const char *)buf[2], "hello"); return MUNIT_OK; } TEST_GROUP(encoder, type); /* Encode a float parameter. */ TEST_CASE(encoder, type, float, NULL) { struct encoder_fixture *f = data; struct value value; uint8_t(*buf)[8] = f->buffer.data; (void)params; ENCODER_INIT(1, TUPLE__ROW); value.type = SQLITE_FLOAT; value.float_ = 3.1415; ENCODER_NEXT; munit_assert_int(buf[0][0], ==, SQLITE_FLOAT); uint64_t *value_ptr = __builtin_assume_aligned(buf[1], sizeof(uint64_t)); munit_assert_uint64(*value_ptr, ==, byte__flip64(*(uint64_t *)&value.float_)); return MUNIT_OK; } /* Encode a unix time parameter. */ TEST_CASE(encoder, type, unixtime, NULL) { struct encoder_fixture *f = data; struct value value; uint8_t(*buf)[8] = f->buffer.data; (void)params; ENCODER_INIT(1, TUPLE__ROW); value.type = DQLITE_UNIXTIME; value.unixtime = 12345; ENCODER_NEXT; munit_assert_int(buf[0][0], ==, DQLITE_UNIXTIME); uint64_t *value_ptr = __builtin_assume_aligned(buf[1], sizeof(uint64_t)); munit_assert_uint64(*value_ptr, ==, byte__flip64((uint64_t)value.unixtime)); return MUNIT_OK; } /* Encode an ISO8601 date string time parameter. */ TEST_CASE(encoder, type, iso8601, NULL) { struct encoder_fixture *f = data; struct value value; uint8_t(*buf)[8] = f->buffer.data; (void)params; ENCODER_INIT(1, TUPLE__ROW); value.type = DQLITE_ISO8601; value.iso8601 = "2018-07-20 09:49:05+00:00"; ENCODER_NEXT; munit_assert_int(buf[0][0], ==, DQLITE_ISO8601); munit_assert_string_equal((char *)buf[1], "2018-07-20 09:49:05+00:00"); return MUNIT_OK; } /* Encode a boolean parameter. */ TEST_CASE(encoder, type, boolean, NULL) { struct encoder_fixture *f = data; struct value value; uint8_t(*buf)[8] = f->buffer.data; (void)params; ENCODER_INIT(1, TUPLE__ROW); value.type = DQLITE_BOOLEAN; value.boolean = 1; ENCODER_NEXT; munit_assert_int(buf[0][0], ==, DQLITE_BOOLEAN); uint64_t *value_ptr = __builtin_assume_aligned(buf[1], sizeof(uint64_t)); munit_assert_uint64(*value_ptr, ==, byte__flip64(value.boolean)); return MUNIT_OK; } dqlite-1.9.1/test/unit/test_vfs.c000066400000000000000000001113221416342771300167530ustar00rootroot00000000000000#include #include #include #include "../../include/dqlite.h" #include "../lib/config.h" #include "../lib/fs.h" #include "../lib/heap.h" #include "../lib/runner.h" #include "../lib/sqlite.h" #include "../../src/format.h" #include "../../src/vfs.h" /****************************************************************************** * * Fixture * ******************************************************************************/ struct fixture { struct sqlite3_vfs vfs; }; static void *setUp(const MunitParameter params[], void *user_data) { struct fixture *f = munit_malloc(sizeof *f); int rv; SETUP_HEAP; SETUP_SQLITE; rv = VfsInit(&f->vfs, "dqlite"); munit_assert_int(rv, ==, 0); rv = sqlite3_vfs_register(&f->vfs, 0); munit_assert_int(rv, ==, 0); return f; } static void tearDown(void *data) { struct fixture *f = data; sqlite3_vfs_unregister(&f->vfs); VfsClose(&f->vfs); TEAR_DOWN_SQLITE; TEAR_DOWN_HEAP; free(f); } /****************************************************************************** * * Helpers * ******************************************************************************/ /* Helper for creating a new file */ static sqlite3_file *__file_create(sqlite3_vfs *vfs, const char *name, int type_flag) { sqlite3_file *file = munit_malloc(vfs->szOsFile); int flags; int rc; flags = SQLITE_OPEN_EXCLUSIVE | SQLITE_OPEN_CREATE | type_flag; rc = vfs->xOpen(vfs, name, file, flags, &flags); munit_assert_int(rc, ==, 0); return file; } /* Helper for creating a new database file */ static sqlite3_file *__file_create_main_db(sqlite3_vfs *vfs) { return __file_create(vfs, "test.db", SQLITE_OPEN_MAIN_DB); } /* Helper for allocating a buffer of 100 bytes containing a database header with * a page size field set to 512 bytes. */ static void *__buf_header_main_db(void) { char *buf = munit_malloc(100 * sizeof *buf); /* Set page size to 512. */ buf[16] = 2; buf[17] = 0; return buf; } /* Helper for allocating a buffer with the content of the first page, i.e. the * the header and some other bytes. */ static void *__buf_page_1(void) { char *buf = munit_malloc(512 * sizeof *buf); /* Set page size to 512. */ buf[16] = 2; buf[17] = 0; /* Set some other bytes */ buf[101] = 1; buf[256] = 2; buf[511] = 3; return buf; } /* Helper for allocating a buffer with the content of the second page. */ static void *__buf_page_2(void) { char *buf = munit_malloc(512 * sizeof *buf); buf[0] = 4; buf[256] = 5; buf[511] = 6; return buf; } /* Helper to execute a SQL statement. */ static void __db_exec(sqlite3 *db, const char *sql) { int rc; rc = sqlite3_exec(db, sql, NULL, NULL, NULL); munit_assert_int(rc, ==, SQLITE_OK); } /* Helper to open and initialize a database, setting the page size and * WAL mode. */ static sqlite3 *__db_open(void) { sqlite3 *db; int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE; int rc; rc = sqlite3_open_v2("test.db", &db, flags, "dqlite"); munit_assert_int(rc, ==, SQLITE_OK); __db_exec(db, "PRAGMA page_size=512"); __db_exec(db, "PRAGMA synchronous=OFF"); __db_exec(db, "PRAGMA journal_mode=WAL"); return db; } /* Helper to close a database. */ static void __db_close(sqlite3 *db) { int rv; rv = sqlite3_close(db); munit_assert_int(rv, ==, SQLITE_OK); } /* Helper get the mxFrame value of the WAL index object associated with the * given database. */ static uint32_t __wal_idx_mx_frame(sqlite3 *db) { sqlite3_file *file; volatile void *region; uint32_t mx_frame; int rc; rc = sqlite3_file_control(db, "main", SQLITE_FCNTL_FILE_POINTER, &file); munit_assert_int(rc, ==, SQLITE_OK); rc = file->pMethods->xShmMap(file, 0, 0, 0, ®ion); munit_assert_int(rc, ==, SQLITE_OK); /* The mxFrame number is 16th byte of the WAL index header. See also * https://sqlite.org/walformat.html. */ mx_frame = ((uint32_t *)region)[4]; return mx_frame; } /* Helper get the read mark array of the WAL index object associated with the * given database. */ static uint32_t *__wal_idx_read_marks(sqlite3 *db) { sqlite3_file *file; volatile void *region; uint32_t *idx; uint32_t *marks; int rc; marks = munit_malloc(FORMAT__WAL_NREADER * sizeof *marks); rc = sqlite3_file_control(db, "main", SQLITE_FCNTL_FILE_POINTER, &file); munit_assert_int(rc, ==, SQLITE_OK); rc = file->pMethods->xShmMap(file, 0, 0, 0, ®ion); munit_assert_int(rc, ==, SQLITE_OK); /* The read-mark array starts at the 100th byte of the WAL index * header. See also https://sqlite.org/walformat.html. */ idx = (uint32_t *)region; memcpy(marks, &idx[25], (sizeof *idx) * FORMAT__WAL_NREADER); return marks; } /* Helper that returns true if the i'th lock of the shared memmory reagion * associated with the given database is currently held. */ static int __shm_shared_lock_held(sqlite3 *db, int i) { sqlite3_file *file; int flags; int locked; int rc; rc = sqlite3_file_control(db, "main", SQLITE_FCNTL_FILE_POINTER, &file); munit_assert_int(rc, ==, SQLITE_OK); /* Try to acquire an exclusive lock, which will fail if the shared lock * is held. */ flags = SQLITE_SHM_LOCK | SQLITE_SHM_EXCLUSIVE; rc = file->pMethods->xShmLock(file, i, 1, flags); locked = rc == SQLITE_BUSY; if (rc == SQLITE_OK) { flags = SQLITE_SHM_UNLOCK | SQLITE_SHM_EXCLUSIVE; rc = file->pMethods->xShmLock(file, i, 1, flags); munit_assert_int(rc, ==, SQLITE_OK); } return locked; } /****************************************************************************** * * xOpen * ******************************************************************************/ SUITE(VfsOpen) /* If the EXCLUSIVE and CREATE flag are given, and the file already exists, an * error is returned. */ TEST(VfsOpen, exclusive, setUp, tearDown, 0, NULL) { struct fixture *f = data; sqlite3_file *file = munit_malloc(f->vfs.szOsFile); int flags; int rc; (void)params; flags = SQLITE_OPEN_CREATE | SQLITE_OPEN_MAIN_DB; rc = f->vfs.xOpen(&f->vfs, "test.db", file, flags, &flags); munit_assert_int(rc, ==, SQLITE_OK); flags |= SQLITE_OPEN_EXCLUSIVE; rc = f->vfs.xOpen(&f->vfs, "test.db", file, flags, &flags); munit_assert_int(rc, ==, SQLITE_CANTOPEN); munit_assert_int(EEXIST, ==, f->vfs.xGetLastError(&f->vfs, 0, 0)); free(file); return MUNIT_OK; } /* It's possible to open again a previously created file. In that case passing * SQLITE_OPEN_CREATE is not necessary. */ TEST(VfsOpen, again, setUp, tearDown, 0, NULL) { struct fixture *f = data; sqlite3_file *file = munit_malloc(f->vfs.szOsFile); int flags; int rc; (void)params; flags = SQLITE_OPEN_CREATE | SQLITE_OPEN_MAIN_DB; rc = f->vfs.xOpen(&f->vfs, "test.db", file, flags, &flags); munit_assert_int(rc, ==, SQLITE_OK); rc = file->pMethods->xClose(file); munit_assert_int(rc, ==, SQLITE_OK); flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_MAIN_DB; rc = f->vfs.xOpen(&f->vfs, "test.db", file, flags, &flags); munit_assert_int(rc, ==, 0); free(file); return MUNIT_OK; } /* If the file does not exist and the SQLITE_OPEN_CREATE flag is not passed, an * error is returned. */ TEST(VfsOpen, noent, setUp, tearDown, 0, NULL) { struct fixture *f = data; sqlite3_file *file = munit_malloc(f->vfs.szOsFile); int flags; int rc; (void)params; rc = f->vfs.xOpen(&f->vfs, "test.db", file, 0, &flags); munit_assert_int(rc, ==, SQLITE_CANTOPEN); munit_assert_int(ENOENT, ==, f->vfs.xGetLastError(&f->vfs, 0, 0)); free(file); return MUNIT_OK; } /* Trying to open a WAL file before its main database file results in an * error. */ TEST(VfsOpen, walBeforeDb, setUp, tearDown, 0, NULL) { struct fixture *f = data; sqlite3_file *file = munit_malloc(f->vfs.szOsFile); int flags; int rc; (void)params; flags = SQLITE_OPEN_CREATE | SQLITE_OPEN_WAL; rc = f->vfs.xOpen(&f->vfs, "test.db", file, flags, &flags); munit_assert_int(rc, ==, SQLITE_CANTOPEN); free(file); return MUNIT_OK; } /* Trying to run queries against a database that hasn't turned off the * synchronous flag results in an error. */ TEST(VfsOpen, synchronous, setUp, tearDown, 0, NULL) { struct fixture *f = data; sqlite3 *db; int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE; int rc; (void)params; rc = sqlite3_vfs_register(&f->vfs, 0); munit_assert_int(rc, ==, SQLITE_OK); rc = sqlite3_open_v2("test.db", &db, flags, f->vfs.zName); munit_assert_int(rc, ==, SQLITE_OK); __db_exec(db, "PRAGMA page_size=4092"); rc = sqlite3_exec(db, "PRAGMA journal_mode=WAL", NULL, NULL, NULL); munit_assert_int(rc, ==, SQLITE_IOERR); munit_assert_string_equal(sqlite3_errmsg(db), "disk I/O error"); __db_close(db); rc = sqlite3_vfs_unregister(&f->vfs); munit_assert_int(rc, ==, SQLITE_OK); return MUNIT_OK; } /* Out of memory when creating the content structure for a new file. */ TEST(VfsOpen, oom, setUp, tearDown, 0, NULL) { struct fixture *f = data; sqlite3_file *file = munit_malloc(f->vfs.szOsFile); int flags = SQLITE_OPEN_CREATE | SQLITE_OPEN_MAIN_DB; int rc; (void)params; test_heap_fault_config(0, 1); test_heap_fault_enable(); rc = f->vfs.xOpen(&f->vfs, "test.db", file, flags, &flags); munit_assert_int(rc, ==, SQLITE_CANTOPEN); free(file); return MUNIT_OK; } /* Out of memory when internally copying the filename. */ TEST(VfsOpen, oomFilename, setUp, tearDown, 0, NULL) { struct fixture *f = data; sqlite3_file *file = munit_malloc(f->vfs.szOsFile); int flags = SQLITE_OPEN_CREATE | SQLITE_OPEN_MAIN_DB; int rc; (void)params; test_heap_fault_config(1, 1); test_heap_fault_enable(); rc = f->vfs.xOpen(&f->vfs, "test.db", file, flags, &flags); munit_assert_int(rc, ==, SQLITE_CANTOPEN); free(file); return MUNIT_OK; } /* Open a temporary file. */ TEST(VfsOpen, tmp, setUp, tearDown, 0, NULL) { struct fixture *f = data; sqlite3_file *file = munit_malloc(f->vfs.szOsFile); int flags = 0; char buf[16]; int rc; (void)params; flags |= SQLITE_OPEN_CREATE; flags |= SQLITE_OPEN_READWRITE; flags |= SQLITE_OPEN_TEMP_JOURNAL; flags |= SQLITE_OPEN_DELETEONCLOSE; rc = f->vfs.xOpen(&f->vfs, NULL, file, flags, &flags); munit_assert_int(rc, ==, SQLITE_OK); rc = file->pMethods->xWrite(file, "hello", 5, 0); munit_assert_int(rc, ==, SQLITE_OK); memset(buf, 0, sizeof buf); rc = file->pMethods->xRead(file, buf, 5, 0); munit_assert_int(rc, ==, SQLITE_OK); munit_assert_string_equal(buf, "hello"); rc = file->pMethods->xClose(file); munit_assert_int(rc, ==, SQLITE_OK); free(file); return MUNIT_OK; } /****************************************************************************** * * xDelete * ******************************************************************************/ SUITE(VfsDelete) /* Delete a file. */ TEST(VfsDelete, success, setUp, tearDown, 0, NULL) { struct fixture *f = data; sqlite3_file *file = munit_malloc(f->vfs.szOsFile); int flags = SQLITE_OPEN_CREATE | SQLITE_OPEN_MAIN_DB; int rc; (void)params; rc = f->vfs.xOpen(&f->vfs, "test.db", file, flags, &flags); munit_assert_int(rc, ==, 0); rc = file->pMethods->xClose(file); munit_assert_int(rc, ==, 0); rc = f->vfs.xDelete(&f->vfs, "test.db", 0); munit_assert_int(rc, ==, 0); /* Trying to open the file again without the SQLITE_OPEN_CREATE flag * results in an error. */ rc = f->vfs.xOpen(&f->vfs, "test.db", file, 0, &flags); munit_assert_int(rc, ==, SQLITE_CANTOPEN); free(file); return MUNIT_OK; } /* Trying to delete a non-existing file results in an error. */ TEST(VfsDelete, enoent, setUp, tearDown, 0, NULL) { struct fixture *f = data; int rc; (void)params; rc = f->vfs.xDelete(&f->vfs, "test.db", 0); munit_assert_int(rc, ==, SQLITE_IOERR_DELETE_NOENT); munit_assert_int(ENOENT, ==, f->vfs.xGetLastError(&f->vfs, 0, 0)); return MUNIT_OK; } /****************************************************************************** * * xAccess * ******************************************************************************/ SUITE(VfsAccess) /* Accessing an existing file returns true. */ TEST(VfsAccess, success, setUp, tearDown, 0, NULL) { struct fixture *f = data; sqlite3_file *file = munit_malloc(f->vfs.szOsFile); int flags = SQLITE_OPEN_CREATE | SQLITE_OPEN_MAIN_DB; int rc; int exists; (void)params; rc = f->vfs.xOpen(&f->vfs, "test.db", file, flags, &flags); munit_assert_int(rc, ==, 0); rc = file->pMethods->xClose(file); munit_assert_int(rc, ==, 0); rc = f->vfs.xAccess(&f->vfs, "test.db", 0, &exists); munit_assert_int(rc, ==, 0); munit_assert_true(exists); free(file); return MUNIT_OK; } /* Trying to access a non existing file returns false. */ TEST(VfsAccess, noent, setUp, tearDown, 0, NULL) { struct fixture *f = data; int rc; int exists; (void)params; rc = f->vfs.xAccess(&f->vfs, "test.db", 0, &exists); munit_assert_int(rc, ==, 0); munit_assert_false(exists); return MUNIT_OK; } /****************************************************************************** * * xFullPathname * ******************************************************************************/ SUITE(VfsFullPathname); /* The xFullPathname API returns the filename unchanged. */ TEST(VfsFullPathname, success, setUp, tearDown, 0, NULL) { struct fixture *f = data; int rc; char pathname[10]; (void)params; rc = f->vfs.xFullPathname(&f->vfs, "test.db", 10, pathname); munit_assert_int(rc, ==, 0); munit_assert_string_equal(pathname, "test.db"); return MUNIT_OK; } /****************************************************************************** * * xClose * ******************************************************************************/ SUITE(VfsClose) /* Closing a file decreases its refcount so it's possible to delete it. */ TEST(VfsClose, thenDelete, setUp, tearDown, 0, NULL) { struct fixture *f = data; sqlite3_file *file = munit_malloc(f->vfs.szOsFile); int flags = SQLITE_OPEN_CREATE | SQLITE_OPEN_MAIN_DB; int rc; (void)params; rc = f->vfs.xOpen(&f->vfs, "test.db", file, flags, &flags); munit_assert_int(rc, ==, 0); rc = file->pMethods->xClose(file); munit_assert_int(rc, ==, 0); rc = f->vfs.xDelete(&f->vfs, "test.db", 0); munit_assert_int(rc, ==, 0); free(file); return MUNIT_OK; } /****************************************************************************** * * xRead * ******************************************************************************/ SUITE(VfsRead) /* Trying to read a file that was not written yet, results in an error. */ TEST(VfsRead, neverWritten, setUp, tearDown, 0, NULL) { struct fixture *f = data; sqlite3_file *file = __file_create_main_db(&f->vfs); int rc; char buf[1] = {123}; (void)params; rc = file->pMethods->xRead(file, (void *)buf, 1, 0); munit_assert_int(rc, ==, SQLITE_IOERR_SHORT_READ); /* The buffer gets filled with zero */ munit_assert_int(buf[0], ==, 0); free(file); return MUNIT_OK; } /****************************************************************************** * * xWrite * ******************************************************************************/ SUITE(VfsWrite) /* Write the header of the database file. */ TEST(VfsWrite, dbHeader, setUp, tearDown, 0, NULL) { struct fixture *f = data; sqlite3_file *file = __file_create_main_db(&f->vfs); void *buf = __buf_header_main_db(); int rc; (void)params; rc = file->pMethods->xWrite(file, buf, 100, 0); munit_assert_int(rc, ==, 0); free(file); free(buf); return MUNIT_OK; } /* Write the header of the database file, then the full first page and a second * page. */ TEST(VfsWrite, andReadPages, setUp, tearDown, 0, NULL) { struct fixture *f = data; sqlite3_file *file = __file_create_main_db(&f->vfs); int rc; char buf[512]; void *buf_header_main = __buf_header_main_db(); void *buf_page_1 = __buf_page_1(); void *buf_page_2 = __buf_page_2(); (void)params; memset(buf, 0, 512); /* Write the header. */ rc = file->pMethods->xWrite(file, buf_header_main, 100, 0); munit_assert_int(rc, ==, 0); /* Write the first page, containing the header and some content. */ rc = file->pMethods->xWrite(file, buf_page_1, 512, 0); munit_assert_int(rc, ==, 0); /* Write a second page. */ rc = file->pMethods->xWrite(file, buf_page_2, 512, 512); munit_assert_int(rc, ==, 0); /* Read the page header. */ rc = file->pMethods->xRead(file, (void *)buf, 512, 0); munit_assert_int(rc, ==, 0); munit_assert_int(buf[16], ==, 2); munit_assert_int(buf[17], ==, 0); munit_assert_int(buf[101], ==, 1); munit_assert_int(buf[256], ==, 2); munit_assert_int(buf[511], ==, 3); /* Read the second page. */ memset(buf, 0, 512); rc = file->pMethods->xRead(file, (void *)buf, 512, 512); munit_assert_int(rc, ==, 0); munit_assert_int(buf[0], ==, 4); munit_assert_int(buf[256], ==, 5); munit_assert_int(buf[511], ==, 6); free(buf_header_main); free(buf_page_1); free(buf_page_2); free(file); return MUNIT_OK; } /* Out of memory when trying to create a new page. */ TEST(VfsWrite, oomPage, setUp, tearDown, 0, NULL) { struct fixture *f = data; sqlite3_file *file = __file_create_main_db(&f->vfs); void *buf_header_main = __buf_header_main_db(); char buf[512]; int rc; test_heap_fault_config(0, 1); test_heap_fault_enable(); (void)params; memset(buf, 0, 512); /* Write the database header, which triggers creating the first page. */ rc = file->pMethods->xWrite(file, buf_header_main, 100, 0); munit_assert_int(rc, ==, SQLITE_NOMEM); free(buf_header_main); free(file); return MUNIT_OK; } /* Out of memory when trying to append a new page to the internal page array of * the content object. */ TEST(VfsWrite, oomPageArray, setUp, tearDown, 0, NULL) { struct fixture *f = data; sqlite3_file *file = __file_create_main_db(&f->vfs); void *buf_header_main = __buf_header_main_db(); char buf[512]; int rc; test_heap_fault_config(1, 1); test_heap_fault_enable(); (void)params; memset(buf, 0, 512); /* Write the database header, which triggers creating the first page. */ rc = file->pMethods->xWrite(file, buf_header_main, 100, 0); munit_assert_int(rc, ==, SQLITE_NOMEM); free(buf_header_main); free(file); return MUNIT_OK; } /* Out of memory when trying to create the content buffer of a new page. */ TEST(VfsWrite, oomPageBuf, setUp, tearDown, 0, NULL) { struct fixture *f = data; sqlite3_file *file = __file_create_main_db(&f->vfs); void *buf_header_main = __buf_header_main_db(); char buf[512]; int rc; test_heap_fault_config(1, 1); test_heap_fault_enable(); (void)params; memset(buf, 0, 512); /* Write the database header, which triggers creating the first page. */ rc = file->pMethods->xWrite(file, buf_header_main, 100, 0); munit_assert_int(rc, ==, SQLITE_NOMEM); free(buf_header_main); free(file); return MUNIT_OK; } /* Trying to write two pages beyond the last one results in an error. */ TEST(VfsWrite, beyondLast, setUp, tearDown, 0, NULL) { struct fixture *f = data; sqlite3_file *file = __file_create_main_db(&f->vfs); void *buf_page_1 = __buf_page_1(); void *buf_page_2 = __buf_page_2(); char buf[512]; int rc; (void)params; memset(buf, 0, 512); /* Write the first page. */ rc = file->pMethods->xWrite(file, buf_page_1, 512, 0); munit_assert_int(rc, ==, 0); /* Write the third page, without writing the second. */ rc = file->pMethods->xWrite(file, buf_page_2, 512, 1024); munit_assert_int(rc, ==, SQLITE_IOERR_WRITE); free(buf_page_1); free(buf_page_2); free(file); return MUNIT_OK; } /****************************************************************************** * * xTruncate * ******************************************************************************/ SUITE(VfsTruncate); /* Truncate the main database file. */ TEST(VfsTruncate, database, setUp, tearDown, 0, NULL) { struct fixture *f = data; sqlite3_file *file = __file_create_main_db(&f->vfs); void *buf_page_1 = __buf_page_1(); void *buf_page_2 = __buf_page_2(); int rc; sqlite_int64 size; (void)params; /* Initial size is 0. */ rc = file->pMethods->xFileSize(file, &size); munit_assert_int(rc, ==, 0); munit_assert_int(size, ==, 0); /* Truncating an empty file is a no-op. */ rc = file->pMethods->xTruncate(file, 0); munit_assert_int(rc, ==, 0); /* The size is still 0. */ rc = file->pMethods->xFileSize(file, &size); munit_assert_int(rc, ==, 0); munit_assert_int(size, ==, 0); /* Write the first page, containing the header. */ rc = file->pMethods->xWrite(file, buf_page_1, 512, 0); munit_assert_int(rc, ==, 0); /* Write a second page. */ rc = file->pMethods->xWrite(file, buf_page_2, 512, 512); munit_assert_int(rc, ==, 0); /* The size is 1024. */ rc = file->pMethods->xFileSize(file, &size); munit_assert_int(rc, ==, 0); munit_assert_int(size, ==, 1024); /* Truncate the second page. */ rc = file->pMethods->xTruncate(file, 512); munit_assert_int(rc, ==, 0); /* The size is 512. */ rc = file->pMethods->xFileSize(file, &size); munit_assert_int(rc, ==, 0); munit_assert_int(size, ==, 512); /* Truncate also the first. */ rc = file->pMethods->xTruncate(file, 0); munit_assert_int(rc, ==, 0); /* The size is 0. */ rc = file->pMethods->xFileSize(file, &size); munit_assert_int(rc, ==, 0); munit_assert_int(size, ==, 0); free(buf_page_1); free(buf_page_2); free(file); return MUNIT_OK; } /* Truncating a file which is not the main db file or the WAL file produces an * error. */ TEST(VfsTruncate, unexpected, setUp, tearDown, 0, NULL) { struct fixture *f = data; sqlite3_file *main_db = __file_create_main_db(&f->vfs); sqlite3_file *file = munit_malloc(f->vfs.szOsFile); int flags = SQLITE_OPEN_CREATE | SQLITE_OPEN_MAIN_JOURNAL; char buf[32]; int rc; (void)params; /* Open a journal file. */ rc = f->vfs.xOpen(&f->vfs, "test.db-journal", file, flags, &flags); munit_assert_int(rc, ==, 0); /* Write some content. */ rc = file->pMethods->xWrite(file, buf, 32, 0); munit_assert_int(rc, ==, 0); /* Truncating produces an error. */ rc = file->pMethods->xTruncate(file, 0); munit_assert_int(rc, ==, SQLITE_IOERR_TRUNCATE); free(file); free(main_db); return MUNIT_OK; } /* Truncating an empty file is a no-op. */ TEST(VfsTruncate, empty, setUp, tearDown, 0, NULL) { struct fixture *f = data; sqlite3_file *file = __file_create_main_db(&f->vfs); sqlite_int64 size; int rc; (void)params; /* Truncating an empty file is a no-op. */ rc = file->pMethods->xTruncate(file, 0); munit_assert_int(rc, ==, SQLITE_OK); /* Size is 0. */ rc = file->pMethods->xFileSize(file, &size); munit_assert_int(rc, ==, 0); munit_assert_int(size, ==, 0); free(file); return MUNIT_OK; } /* Trying to grow an empty file produces an error. */ TEST(VfsTruncate, emptyGrow, setUp, tearDown, 0, NULL) { struct fixture *f = data; sqlite3_file *file = __file_create_main_db(&f->vfs); int rc; (void)params; /* Truncating an empty file is a no-op. */ rc = file->pMethods->xTruncate(file, 512); munit_assert_int(rc, ==, SQLITE_IOERR_TRUNCATE); free(file); return MUNIT_OK; } /* Trying to truncate a main database file to a size which is not a multiple of * the page size produces an error. */ TEST(VfsTruncate, misaligned, setUp, tearDown, 0, NULL) { struct fixture *f = data; sqlite3_file *file = __file_create_main_db(&f->vfs); void *buf_page_1 = __buf_page_1(); int rc; (void)params; /* Write the first page, containing the header. */ rc = file->pMethods->xWrite(file, buf_page_1, 512, 0); munit_assert_int(rc, ==, 0); /* Truncating to an invalid size. */ rc = file->pMethods->xTruncate(file, 400); munit_assert_int(rc, ==, SQLITE_IOERR_TRUNCATE); free(buf_page_1); free(file); return MUNIT_OK; } /****************************************************************************** * * xShmMap * ******************************************************************************/ SUITE(VfsShmMap); static char *test_shm_map_oom_delay[] = {"0", "1", NULL}; static char *test_shm_map_oom_repeat[] = {"1", NULL}; static MunitParameterEnum test_shm_map_oom_params[] = { {TEST_HEAP_FAULT_DELAY, test_shm_map_oom_delay}, {TEST_HEAP_FAULT_REPEAT, test_shm_map_oom_repeat}, {NULL, NULL}, }; /* Out of memory when trying to initialize the internal VFS shm data struct. */ TEST(VfsShmMap, oom, setUp, tearDown, 0, test_shm_map_oom_params) { struct fixture *f = data; sqlite3_file *file = __file_create_main_db(&f->vfs); volatile void *region; int rc; (void)params; (void)data; test_heap_fault_enable(); rc = file->pMethods->xShmMap(file, 0, 32768, 1, ®ion); munit_assert_int(rc, ==, SQLITE_NOMEM); free(file); return MUNIT_OK; } /****************************************************************************** * * xShmLock * ******************************************************************************/ SUITE(VfsShmLock) /* If an exclusive lock is in place, getting a shared lock on any index of its * range fails. */ TEST(VfsShmLock, sharedBusy, setUp, tearDown, 0, NULL) { struct fixture *f = data; sqlite3_file *file = munit_malloc(f->vfs.szOsFile); int flags = SQLITE_OPEN_CREATE | SQLITE_OPEN_MAIN_DB; volatile void *region; int rc; (void)params; (void)data; rc = f->vfs.xOpen(&f->vfs, "test.db", file, flags, &flags); munit_assert_int(rc, ==, 0); rc = file->pMethods->xShmMap(file, 0, 32768, 1, ®ion); munit_assert_int(rc, ==, 0); /* Take an exclusive lock on a range. */ flags = SQLITE_SHM_LOCK | SQLITE_SHM_EXCLUSIVE; rc = file->pMethods->xShmLock(file, 2, 3, flags); munit_assert_int(rc, ==, 0); /* Attempting to get a shared lock on an index in that range fails. */ flags = SQLITE_SHM_LOCK | SQLITE_SHM_SHARED; rc = file->pMethods->xShmLock(file, 3, 1, flags); munit_assert_int(rc, ==, SQLITE_BUSY); free(file); return MUNIT_OK; } /* If a shared lock is in place on any of the indexes of the requested range, * getting an exclusive lock fails. */ TEST(VfsShmLock, exclBusy, setUp, tearDown, 0, NULL) { struct fixture *f = data; sqlite3_file *file = munit_malloc(f->vfs.szOsFile); int flags = SQLITE_OPEN_CREATE | SQLITE_OPEN_MAIN_DB; volatile void *region; int rc; (void)params; (void)data; rc = f->vfs.xOpen(&f->vfs, "test.db", file, flags, &flags); munit_assert_int(rc, ==, 0); rc = file->pMethods->xShmMap(file, 0, 32768, 1, ®ion); munit_assert_int(rc, ==, 0); /* Take a shared lock on index 3. */ flags = SQLITE_SHM_LOCK | SQLITE_SHM_SHARED; rc = file->pMethods->xShmLock(file, 3, 1, flags); munit_assert_int(rc, ==, 0); /* Attempting to get an exclusive lock on a range that contains index 3 * fails. */ flags = SQLITE_SHM_LOCK | SQLITE_SHM_EXCLUSIVE; rc = file->pMethods->xShmLock(file, 2, 3, flags); munit_assert_int(rc, ==, SQLITE_BUSY); free(file); return MUNIT_OK; } /* The native unix VFS implementation from SQLite allows to release a shared * memory lock without acquiring it first. */ TEST(VfsShmLock, releaseUnix, setUp, tearDown, 0, NULL) { (void)data; struct sqlite3_vfs *vfs = sqlite3_vfs_find("unix"); sqlite3_file *file = munit_malloc(vfs->szOsFile); int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_MAIN_DB; char *dir = test_dir_setup(); char buf[1024]; char *path; volatile void *region; int rc; (void)params; (void)data; /* The SQLite pager stores the Database filename, Journal filename, and * WAL filename consecutively in memory, in that order. The database * filename is prefixed by four zero bytes. Emulate that behavior here, * since the internal SQLite code triggered by the xShmMap unix * implementation relies on that.*/ memset(buf, 0, sizeof buf); path = buf + 4; sprintf(path, "%s/test.db", dir); rc = vfs->xOpen(vfs, path, file, flags, &flags); munit_assert_int(rc, ==, 0); rc = file->pMethods->xShmMap(file, 0, 32768, 1, ®ion); munit_assert_int(rc, ==, 0); flags = SQLITE_SHM_UNLOCK | SQLITE_SHM_EXCLUSIVE; rc = file->pMethods->xShmLock(file, 3, 1, flags); munit_assert_int(rc, ==, 0); flags = SQLITE_SHM_UNLOCK | SQLITE_SHM_SHARED; rc = file->pMethods->xShmLock(file, 2, 1, flags); munit_assert_int(rc, ==, 0); rc = file->pMethods->xShmUnmap(file, 1); munit_assert_int(rc, ==, 0); rc = file->pMethods->xClose(file); munit_assert_int(rc, ==, 0); test_dir_tear_down(dir); free(file); return MUNIT_OK; } /* The dqlite VFS implementation allows to release a shared memory lock without * acquiring it first. This is important because at open time sometimes SQLite * will do just that (release before acquire). */ TEST(VfsShmLock, release, setUp, tearDown, 0, NULL) { struct fixture *f = data; sqlite3_file *file = munit_malloc(f->vfs.szOsFile); int flags = SQLITE_OPEN_CREATE | SQLITE_OPEN_MAIN_DB; volatile void *region; int rc; (void)params; (void)data; rc = f->vfs.xOpen(&f->vfs, "test.db", file, flags, &flags); munit_assert_int(rc, ==, 0); rc = file->pMethods->xShmMap(file, 0, 32768, 1, ®ion); munit_assert_int(rc, ==, 0); flags = SQLITE_SHM_UNLOCK | SQLITE_SHM_SHARED; rc = file->pMethods->xShmLock(file, 3, 1, flags); munit_assert_int(rc, ==, 0); flags = SQLITE_SHM_UNLOCK | SQLITE_SHM_SHARED; rc = file->pMethods->xShmLock(file, 2, 1, flags); munit_assert_int(rc, ==, 0); rc = file->pMethods->xShmUnmap(file, 1); munit_assert_int(rc, ==, 0); rc = file->pMethods->xClose(file); munit_assert_int(rc, ==, 0); free(file); return MUNIT_OK; } /****************************************************************************** * * xFileControl * ******************************************************************************/ SUITE(VfsFileControl) /* Trying to set the journal mode to anything other than "wal" produces an * error. */ TEST(VfsFileControl, journal, setUp, tearDown, 0, NULL) { struct fixture *f = data; sqlite3_file *file = __file_create_main_db(&f->vfs); char *fnctl[] = { "", "journal_mode", "memory", "", }; int rc; (void)params; (void)data; /* Setting the page size a first time returns NOTFOUND, which is what * SQLite effectively expects. */ rc = file->pMethods->xFileControl(file, SQLITE_FCNTL_PRAGMA, fnctl); munit_assert_int(rc, ==, SQLITE_IOERR); free(file); return MUNIT_OK; } /****************************************************************************** * * xCurrentTime * ******************************************************************************/ SUITE(VfsCurrentTime) TEST(VfsCurrentTime, success, setUp, tearDown, 0, NULL) { struct fixture *f = data; double now; int rc; (void)params; rc = f->vfs.xCurrentTime(&f->vfs, &now); munit_assert_int(rc, ==, SQLITE_OK); munit_assert_double(now, >, 0); return MUNIT_OK; } /****************************************************************************** * * xSleep * ******************************************************************************/ SUITE(VfsSleep) /* The xSleep implementation is a no-op. */ TEST(VfsSleep, success, setUp, tearDown, 0, NULL) { struct fixture *f = data; int microseconds; (void)params; microseconds = f->vfs.xSleep(&f->vfs, 123); munit_assert_int(microseconds, ==, 123); return MUNIT_OK; } /****************************************************************************** * * VfsInit * ******************************************************************************/ SUITE(VfsInit); static char *test_create_oom_delay[] = {"0", NULL}; static char *test_create_oom_repeat[] = {"1", NULL}; static MunitParameterEnum test_create_oom_params[] = { {TEST_HEAP_FAULT_DELAY, test_create_oom_delay}, {TEST_HEAP_FAULT_REPEAT, test_create_oom_repeat}, {NULL, NULL}, }; TEST(VfsInit, oom, setUp, tearDown, 0, test_create_oom_params) { struct sqlite3_vfs vfs; int rv; (void)params; (void)data; test_heap_fault_enable(); rv = VfsInit(&vfs, "dqlite"); munit_assert_int(rv, ==, DQLITE_NOMEM); return MUNIT_OK; } /****************************************************************************** * * Integration * ******************************************************************************/ SUITE(VfsIntegration) /* Test our expections on the memory-mapped WAl index format. */ TEST(VfsIntegration, wal, setUp, tearDown, 0, NULL) { sqlite3 *db1; sqlite3 *db2; uint32_t *read_marks; int i; (void)data; (void)params; return MUNIT_SKIP; db1 = __db_open(); db2 = __db_open(); __db_exec(db1, "CREATE TABLE test (n INT)"); munit_assert_int(__wal_idx_mx_frame(db1), ==, 2); read_marks = __wal_idx_read_marks(db1); munit_assert_uint32(read_marks[0], ==, 0); munit_assert_uint32(read_marks[1], ==, 0); munit_assert_uint32(read_marks[2], ==, 0xffffffff); munit_assert_uint32(read_marks[3], ==, 0xffffffff); munit_assert_uint32(read_marks[4], ==, 0xffffffff); free(read_marks); /* Start a read transaction on db2 */ __db_exec(db2, "BEGIN"); __db_exec(db2, "SELECT * FROM test"); /* The max frame is set to 2, which is the current size of the WAL. */ munit_assert_int(__wal_idx_mx_frame(db2), ==, 2); /* The starting mx frame value has been saved in the read marks */ read_marks = __wal_idx_read_marks(db2); munit_assert_uint32(read_marks[0], ==, 0); munit_assert_uint32(read_marks[1], ==, 2); munit_assert_uint32(read_marks[2], ==, 0xffffffff); munit_assert_uint32(read_marks[3], ==, 0xffffffff); munit_assert_uint32(read_marks[4], ==, 0xffffffff); free(read_marks); /* A shared lock is held on the second read mark (read locks start at * 3). */ munit_assert_true(__shm_shared_lock_held(db2, 3 + 1)); /* Start a write transaction on db1 */ __db_exec(db1, "BEGIN"); for (i = 0; i < 100; i++) { __db_exec(db1, "INSERT INTO test(n) VALUES(1)"); } /* The mx frame is still 2 since the transaction is not committed. */ munit_assert_int(__wal_idx_mx_frame(db1), ==, 2); /* No extra read mark wal taken. */ read_marks = __wal_idx_read_marks(db1); munit_assert_uint32(read_marks[0], ==, 0); munit_assert_uint32(read_marks[1], ==, 2); munit_assert_uint32(read_marks[2], ==, 0xffffffff); munit_assert_uint32(read_marks[3], ==, 0xffffffff); munit_assert_uint32(read_marks[4], ==, 0xffffffff); free(read_marks); __db_exec(db1, "COMMIT"); /* The mx frame is now 6. */ munit_assert_int(__wal_idx_mx_frame(db1), ==, 6); /* The old read lock is still in place. */ munit_assert_true(__shm_shared_lock_held(db2, 3 + 1)); /* Start a read transaction on db1 */ __db_exec(db1, "BEGIN"); __db_exec(db1, "SELECT * FROM test"); /* The mx frame is still unchanged. */ munit_assert_int(__wal_idx_mx_frame(db1), ==, 6); /* A new read mark was taken. */ read_marks = __wal_idx_read_marks(db1); munit_assert_uint32(read_marks[0], ==, 0); munit_assert_uint32(read_marks[1], ==, 2); munit_assert_uint32(read_marks[2], ==, 6); munit_assert_uint32(read_marks[3], ==, 0xffffffff); munit_assert_uint32(read_marks[4], ==, 0xffffffff); free(read_marks); /* The old read lock is still in place. */ munit_assert_true(__shm_shared_lock_held(db2, 3 + 1)); /* The new read lock is in place as well. */ munit_assert_true(__shm_shared_lock_held(db2, 3 + 2)); __db_close(db1); __db_close(db2); return SQLITE_OK; } /* Full checkpoints are possible only when no read mark is set. */ TEST(VfsIntegration, checkpoint, setUp, tearDown, 0, NULL) { sqlite3 *db1; sqlite3 *db2; sqlite3_file *file1; /* main DB file */ sqlite3_file *file2; /* WAL file */ sqlite_int64 size; uint32_t *read_marks; unsigned mx_frame; char stmt[128]; int log, ckpt; int i; int rv; (void)data; (void)params; return MUNIT_SKIP; db1 = __db_open(); __db_exec(db1, "CREATE TABLE test (n INT)"); /* Insert a few rows so we grow the size of the WAL. */ __db_exec(db1, "BEGIN"); for (i = 0; i < 500; i++) { sprintf(stmt, "INSERT INTO test(n) VALUES(%d)", i); __db_exec(db1, stmt); } __db_exec(db1, "COMMIT"); /* Get the file objects for the main database and the WAL. */ rv = sqlite3_file_control(db1, "main", SQLITE_FCNTL_FILE_POINTER, &file1); munit_assert_int(rv, ==, 0); rv = sqlite3_file_control(db1, "main", SQLITE_FCNTL_JOURNAL_POINTER, &file2); munit_assert_int(rv, ==, 0); /* The WAL file has now 13 pages */ rv = file2->pMethods->xFileSize(file2, &size); munit_assert_int(formatWalCalcFramesNumber(512, size), ==, 13); mx_frame = __wal_idx_mx_frame(db1); munit_assert_int(mx_frame, ==, 13); /* Start a read transaction on a different connection, acquiring a * shared lock on all WAL pages. */ db2 = __db_open(); __db_exec(db2, "BEGIN"); __db_exec(db2, "SELECT * FROM test"); read_marks = __wal_idx_read_marks(db1); munit_assert_int(read_marks[1], ==, 13); free(read_marks); rv = file1->pMethods->xShmLock(file1, 3 + 1, 1, SQLITE_SHM_LOCK | SQLITE_SHM_EXCLUSIVE); munit_assert_int(rv, ==, SQLITE_BUSY); munit_assert_true(__shm_shared_lock_held(db1, 3 + 1)); /* Execute a new write transaction, deleting some of the pages we * inserted and creating new ones. */ __db_exec(db1, "BEGIN"); __db_exec(db1, "DELETE FROM test WHERE n > 200"); for (i = 0; i < 1000; i++) { sprintf(stmt, "INSERT INTO test(n) VALUES(%d)", i); __db_exec(db1, stmt); } __db_exec(db1, "COMMIT"); /* Since there's a shared read lock, a full checkpoint will fail. */ rv = sqlite3_wal_checkpoint_v2(db1, "main", SQLITE_CHECKPOINT_TRUNCATE, &log, &ckpt); munit_assert_int(rv, !=, 0); /* If we complete the read transaction the shared lock is realeased and * the checkpoint succeeds. */ __db_exec(db2, "COMMIT"); rv = sqlite3_wal_checkpoint_v2(db1, "main", SQLITE_CHECKPOINT_TRUNCATE, &log, &ckpt); munit_assert_int(rv, ==, 0); __db_close(db1); __db_close(db2); return SQLITE_OK; }