1
0
forked from blue/lmdbal

Compare commits

..

25 Commits

Author SHA1 Message Date
bfb1d007ad
Cursors refactoring part one
All checks were successful
Main LMDBAL workfow / Test LMDBAL with qt5 (push) Successful in 1m4s
Main LMDBAL workfow / Test LMDBAL with qt6 (push) Successful in 1m22s
Main LMDBAL workfow / Release documentation (push) Successful in 29s
2024-12-25 19:19:32 +02:00
ef86d0adf9
Some reformation, test cases and first bugfixes
All checks were successful
Main LMDBAL workfow / Test LMDBAL with qt5 (push) Successful in 1m2s
Main LMDBAL workfow / Test LMDBAL with qt6 (push) Successful in 1m18s
Main LMDBAL workfow / Release documentation (push) Successful in 27s
2024-12-24 18:35:56 +02:00
e88efb458f
Cursors get closed after transaction that open them
Some checks failed
Main LMDBAL workfow / Test LMDBAL with qt5 (push) Failing after 1m4s
Main LMDBAL workfow / Test LMDBAL with qt6 (push) Successful in 1m24s
Main LMDBAL workfow / Release documentation (push) Has been skipped
2024-12-24 14:59:58 +02:00
68ea7df6a9
Transactions now get closed with the database
All checks were successful
Main LMDBAL workfow / Test LMDBAL with qt5 (push) Successful in 1m3s
Main LMDBAL workfow / Test LMDBAL with qt6 (push) Successful in 1m20s
Main LMDBAL workfow / Release documentation (push) Successful in 25s
2024-12-22 19:39:35 +02:00
56d35d4832
pragma once and minor change in transactions
Some checks failed
Main LMDBAL workfow / Test LMDBAL with qt5 (push) Failing after 1m8s
Main LMDBAL workfow / Test LMDBAL with qt6 (push) Successful in 1m24s
Main LMDBAL workfow / Release documentation (push) Has been skipped
2024-12-17 20:03:48 +02:00
43d4900809
Some more build fixes
All checks were successful
Main LMDBAL workfow / Test LMDBAL with qt5 (push) Successful in 1m5s
Main LMDBAL workfow / Test LMDBAL with qt6 (push) Successful in 1m21s
LMDBAL Release workflow / Release qt5 build to AUR (release) Successful in 27s
LMDBAL Release workflow / Release qt6 build to AUR (release) Successful in 24s
Main LMDBAL workfow / Release documentation (push) Successful in 25s
2024-12-13 20:27:47 +02:00
02946bbe98
Typo in the build CI
All checks were successful
Main LMDBAL workfow / Test LMDBAL with qt5 (push) Successful in 1m4s
Main LMDBAL workfow / Test LMDBAL with qt6 (push) Successful in 1m20s
LMDBAL Release workflow / Release qt5 build to AUR (release) Successful in 24s
LMDBAL Release workflow / Release qt6 build to AUR (release) Successful in 23s
Main LMDBAL workfow / Release documentation (push) Successful in 25s
2024-12-03 19:45:15 +02:00
7aaab5e807
builds with different names to coexist
All checks were successful
Main LMDBAL workfow / Test LMDBAL with qt5 (push) Successful in 1m8s
Main LMDBAL workfow / Test LMDBAL with qt6 (push) Successful in 1m22s
Main LMDBAL workfow / Release documentation (push) Successful in 26s
LMDBAL Release workflow / Release qt5 build to AUR (release) Successful in 24s
LMDBAL Release workflow / Release qt6 build to AUR (release) Successful in 24s
2024-12-03 19:36:07 +02:00
649d967963
CI try 13
All checks were successful
Main LMDBAL workfow / Test LMDBAL with qt5 (push) Successful in 52s
Main LMDBAL workfow / Test LMDBAL with qt6 (push) Successful in 1m4s
LMDBAL Release workflow / Release qt5 build to AUR (release) Successful in 26s
LMDBAL Release workflow / Release qt6 build to AUR (release) Successful in 23s
Main LMDBAL workfow / Release documentation (push) Successful in 21s
2024-11-30 23:17:52 +02:00
5670453314
CI try 12
Some checks failed
Main LMDBAL workfow / Test LMDBAL with qt5 (push) Successful in 52s
Main LMDBAL workfow / Test LMDBAL with qt6 (push) Successful in 1m6s
LMDBAL Release workflow / Release qt5 build to AUR (release) Failing after 18s
LMDBAL Release workflow / Release qt6 build to AUR (release) Failing after 16s
Main LMDBAL workfow / Release documentation (push) Successful in 22s
2024-11-30 23:07:49 +02:00
2add0b1ae2
CI try 11
Some checks failed
Main LMDBAL workfow / Test LMDBAL with qt5 (push) Successful in 52s
Main LMDBAL workfow / Test LMDBAL with qt6 (push) Successful in 1m5s
Main LMDBAL workfow / Release documentation (push) Successful in 21s
LMDBAL Release workflow / Release qt5 build to AUR (release) Failing after 9s
LMDBAL Release workflow / Release qt6 build to AUR (release) Failing after 7s
2024-11-30 22:46:09 +02:00
50a13d7290
CI try 10
Some checks failed
Main LMDBAL workfow / Test LMDBAL with qt5 (push) Successful in 52s
Main LMDBAL workfow / Test LMDBAL with qt6 (push) Successful in 1m6s
LMDBAL Release workflow / Release qt5 build to AUR (release) Failing after 8s
LMDBAL Release workflow / Release qt6 build to AUR (release) Failing after 7s
Main LMDBAL workfow / Release documentation (push) Successful in 22s
2024-11-30 22:28:15 +02:00
75010c0bc6
CI try 9
Some checks failed
Main LMDBAL workfow / Test LMDBAL with qt6 (push) Successful in 1m5s
Main LMDBAL workfow / Test LMDBAL with qt5 (push) Successful in 52s
Main LMDBAL workfow / Release documentation (push) Successful in 22s
LMDBAL Release workflow / Release qt6 build to AUR (release) Failing after 4s
LMDBAL Release workflow / Release qt5 build to AUR (release) Failing after 3s
2024-11-30 21:19:50 +02:00
4c5e4bb6ac
CI try 8
Some checks failed
Main LMDBAL workfow / Debug Repository State (push) Successful in 5s
Main LMDBAL workfow / Test LMDBAL with qt5 (push) Failing after 0s
Main LMDBAL workfow / Test LMDBAL with qt6 (push) Failing after 0s
Main LMDBAL workfow / Release documentation (push) Has been skipped
2024-11-30 21:17:07 +02:00
f5d6fb9141
CI try 7
Some checks failed
Main LMDBAL workfow / Debug Repository State (push) Successful in 4s
Main LMDBAL workfow / Test LMDBAL with qt5 (push) Failing after 0s
Main LMDBAL workfow / Test LMDBAL with qt6 (push) Failing after 0s
Main LMDBAL workfow / Release documentation (push) Has been skipped
2024-11-30 21:11:30 +02:00
1083df36ee
CI try 6
Some checks failed
Main LMDBAL workfow / Debug Repository State (push) Successful in 4s
Main LMDBAL workfow / Test LMDBAL with qt5 (push) Failing after 0s
Main LMDBAL workfow / Test LMDBAL with qt6 (push) Failing after 0s
Main LMDBAL workfow / Release documentation (push) Has been skipped
2024-11-30 20:59:49 +02:00
551495843d
CI try 5
Some checks failed
Main LMDBAL workfow / Test LMDBAL with qt5 (push) Failing after 0s
Main LMDBAL workfow / Test LMDBAL with qt6 (push) Failing after 0s
Main LMDBAL workfow / Release documentation (push) Has been skipped
2024-11-30 20:39:34 +02:00
c3246fd2b5
CI try 5 2024-11-30 20:37:28 +02:00
8b7d548df6
CI try 4
Some checks failed
Main LMDBAL workfow / Test LMDBAL with qt5 (push) Failing after 2s
Main LMDBAL workfow / Test LMDBAL with qt6 (push) Failing after 1s
Main LMDBAL workfow / Release documentation (push) Successful in 16s
2024-11-30 19:46:43 +02:00
7c26b09056
CI try 3 2024-11-30 19:45:27 +02:00
29b126c30e
CI Try 2 2024-11-30 19:44:07 +02:00
93ed15e1da
CI try 1
All checks were successful
Main LMDBAL workfow / Archlinux (push) Successful in 58s
2024-11-30 19:30:19 +02:00
75aafd2750
A fix for CI hopefully
All checks were successful
Main LMDBAL workfow / Archlinux (push) Successful in 1m6s
2024-10-28 14:14:39 +02:00
4fd3799c19
Playing with CI
All checks were successful
Main LMDBAL workfow / Archlinux (push) Successful in 1m8s
2024-10-28 14:11:30 +02:00
def2b8bc4d
Some test fixes
All checks were successful
Main LMDBAL workfow / Archlinux (push) Successful in 1m5s
2024-10-06 20:11:44 +03:00
50 changed files with 1436 additions and 902 deletions

80
.gitea/workflows/aur.yml Normal file
View File

@ -0,0 +1,80 @@
name: Release to AUR
on:
workflow_call:
inputs:
package-name:
required: true
type: string
description:
required: true
type: string
depends:
required: true
type: string
flags:
required: true
type: string
workflow_dispatch:
inputs:
package-name:
description: "The name under which the package is going to be packaged to AUR"
type: string
default: "lmdbal"
description:
description: "The description of the package"
type: string
default: "LMDB Abstraction Layer"
depends:
description: "Additional dependencies"
type: string
default: ""
flags:
description: "Additional CMake flags"
type: string
default: ""
jobs:
aur:
name: Releasing ${{ inputs.package-name }} to AUR
runs-on: archlinux
steps:
- name: Download the release tarball
run: curl -sL ${{ gitea.server_url }}/${{ gitea.repository }}/archive/${{ gitea.event.release.tag_name }}.tar.gz --output tarball.tar.gz
- name: Calculate SHA256 for the tarball
run: echo "tbSum=$(sha256sum tarball.tar.gz | cut -d ' ' -f 1)" >> $GITHUB_ENV
- name: Unarchive tarball
run: tar -xvzf tarball.tar.gz
- name: Clone the AUR repository
run: |
echo "${{ secrets.DEPLOY_TO_AUR_PRIVATE_KEY }}" > key
chmod 600 key
GIT_SSH_COMMAND="ssh -i key -o 'IdentitiesOnly yes' -o 'StrictHostKeyChecking no'" git clone ssh://aur@aur.archlinux.org/${{ inputs.package-name }}.git aur
chmod 777 -R aur
cd aur
git config user.name ${{ secrets.DEPLOY_TO_AUR_USER_NAME }}
git config user.email ${{ secrets.DEPLOY_TO_AUR_EMAIL }}
git checkout -b master || git checkout master
- name: Copy PKGBUILD to the directory
run: cp lmdbal/packaging/Archlinux/PKGBUILD aur/
- name: Patch PKGBUILD file, and generate .SRCINFO
working-directory: aur
run: |
sed -i "/sha256sums=/c\sha256sums=('${{ env.tbSum }}')" PKGBUILD
sed -i "/pkgname=lmdbal/c\pkgname=${{ inputs.package-name }}" PKGBUILD
sed -i "/pkgver=1.0.0/c\pkgver=${{ gitea.event.release.tag_name }}" PKGBUILD
sed -i "/pkgdesc=\"LMDB Abstraction Layer\"/c\pkgdesc=\"${{ inputs.description }}\"" PKGBUILD
sed -i "/depends=( 'lmdb' )/c\depends=( 'lmdb' ${{ inputs.depends }} )" PKGBUILD
sed -i '/cmake . -D/s/$/ ${{ inputs.flags }}/' PKGBUILD
sudo -u build makepkg --printsrcinfo > .SRCINFO
- name: Commit package to aur
working-directory: aur
run: |
git add PKGBUILD .SRCINFO
git commit -m "${{ gitea.event.release.body }}"
GIT_SSH_COMMAND="ssh -i ../key -o 'IdentitiesOnly yes' -o 'StrictHostKeyChecking no'" git push origin master

View File

@ -6,8 +6,22 @@ on:
- master
jobs:
Archlinux:
test-qt5:
name: Test LMDBAL with qt5
uses: ./.gitea/workflows/test.yml
with:
flags: -D QT_VERSION_MAJOR=5 -D LMDBAL_NAME=LMDBAL-QT5
test-qt6:
name: Test LMDBAL with qt6
uses: ./.gitea/workflows/test.yml
with:
flags: -D QT_VERSION_MAJOR=6 -D LMDBAL_NAME=LMDBAL-QT6
docs:
name: Release documentation
runs-on: archlinux
needs: [test-qt5, test-qt6]
steps:
- name: Check out repository code
uses: actions/checkout@v3
@ -17,16 +31,12 @@ jobs:
- name: Configure
working-directory: ./build
run: cmake .. -D BUILD_TESTS=True -D BUILD_DOC_HTML=True -D BUILD_DOC_XML=True -D BUILD_DOC_MAN=True -D BUILD_DOXYGEN_AWESOME=True -D QT_VERSION_MAJOR=5
run: cmake .. BUILD_DOC_HTML=True -D BUILD_DOC_XML=True -D BUILD_DOC_MAN=True -D BUILD_DOXYGEN_AWESOME=True
- name: Build
working-directory: ./build
run: cmake --build .
- name: Run tests
working-directory: ./build/test
run: ./runUnitTests
- name: Copy docs via scp
uses: appleboy/scp-action@master
# working-directory: ./build/doc //doesn't work

View File

@ -5,42 +5,29 @@ on:
types: [published]
jobs:
Archlinux:
runs-on: archlinux
steps:
- name: Download the release tarball
run: curl -sL ${{ gitea.server_url }}/${{ gitea.repository }}/archive/${{ gitea.event.release.tag_name }}.tar.gz --output tarball.tar.gz
qt5:
name: Release qt5 build to AUR
uses: ./.gitea/workflows/aur.yml
with:
package-name: lmdbal-qt5
description: LMDB Abstraction Layer, qt5 version
depends: 'qt5-base'
flags: -D QT_VERSION_MAJOR=5 -D LMDBAL_NAME=LMDBALQT5
secrets:
DEPLOY_TO_AUR_PRIVATE_KEY: ${{ secrets.DEPLOY_TO_AUR_PRIVATE_KEY }}
DEPLOY_TO_AUR_USER_NAME: ${{ secrets.DEPLOY_TO_AUR_USER_NAME }}
DEPLOY_TO_AUR_EMAIL: ${{ secrets.DEPLOY_TO_AUR_EMAIL }}
- name: Calculate SHA256 for the tarball
run: echo "tbSum=$(sha256sum tarball.tar.gz | cut -d ' ' -f 1)" >> $GITHUB_ENV
- name: Unarchive tarball
run: tar -xvzf tarball.tar.gz
- name: Clone the AUR repository
run: |
echo "${{ secrets.DEPLOY_TO_AUR_PRIVATE_KEY }}" > key
chmod 600 key
GIT_SSH_COMMAND="ssh -i key -o 'IdentitiesOnly yes' -o 'StrictHostKeyChecking no'" git clone ssh://aur@aur.archlinux.org/lmdbal.git aur
chmod 777 -R aur
cd aur
git config user.name ${{ secrets.DEPLOY_TO_AUR_USER_NAME }}
git config user.email ${{ secrets.DEPLOY_TO_AUR_EMAIL }}
- name: Copy PKGBUILD to the directory
run: cp lmdbal/packaging/Archlinux/PKGBUILD aur/
- name: Put SHA256 sum to PKGBUILD file, and generate .SRCINFO
working-directory: aur
run: |
sed -i "/sha256sums=/c\sha256sums=('${{ env.tbSum }}')" PKGBUILD
sudo -u build makepkg --printsrcinfo > .SRCINFO
- name: Commit package to aur
working-directory: aur
run: |
git add PKGBUILD .SRCINFO
git commit -m "${{ gitea.event.release.body }}"
GIT_SSH_COMMAND="ssh -i ../key -o 'IdentitiesOnly yes' -o 'StrictHostKeyChecking no'" git push
qt6:
name: Release qt6 build to AUR
uses: ./.gitea/workflows/aur.yml
with:
package-name: lmdbal-qt6
description: LMDB Abstraction Layer, qt6 version
depends: 'qt6-base'
flags: -D QT_VERSION_MAJOR=6 -D LMDBAL_NAME=LMDBALQT6
secrets:
DEPLOY_TO_AUR_PRIVATE_KEY: ${{ secrets.DEPLOY_TO_AUR_PRIVATE_KEY }}
DEPLOY_TO_AUR_USER_NAME: ${{ secrets.DEPLOY_TO_AUR_USER_NAME }}
DEPLOY_TO_AUR_EMAIL: ${{ secrets.DEPLOY_TO_AUR_EMAIL }}

36
.gitea/workflows/test.yml Normal file
View File

@ -0,0 +1,36 @@
name: Build and run unit tests for LMDBAL
on:
workflow_call:
inputs:
flags:
required: true
type: string
workflow_dispatch:
inputs:
flags:
description: "Flags for CMake configure stage"
type: string
default: "lmdbal"
jobs:
test:
name: Building and rinning unit tests
runs-on: archlinux
steps:
- name: Check out repository code
uses: actions/checkout@v3
- name: Make a build directory
run: mkdir build
- name: Configure
working-directory: ./build
run: cmake .. -D BUILD_TESTS=True -D QT_VERSION_MAJOR= ${{ inputs.flags }}
- name: Build
working-directory: ./build
run: cmake --build .
- name: Run tests
working-directory: ./build/test
run: ./runUnitTests

View File

@ -1,5 +1,19 @@
# Changelog
# LMDBAL 0.6.0 (UNRELEASED)
### Improvements
- Less dereferencing
- Transactions are now closed with the database
- Cursors are now closed if the public transaction that opened them got closed
### Bug fixes
- SIGSEGV on closing database with opened transactions
# LMDBAL 0.5.4 (November 30, 2024)
### Improvements
- Technical release just to resolve build issues
- Now different flavours of the package can coexist in the system
# LMDBAL 0.5.3 (November 14, 2023)
### Improvements
- Now you don't need to link agains lmdb, just linking against LMDBAL is enough

View File

@ -1,15 +1,16 @@
cmake_minimum_required(VERSION 3.16)
project(LMDBAL
VERSION 0.5.4
VERSION 0.6.0
DESCRIPTION "LMDB (Lightning Memory-Mapped Database Manager) Abstraction Layer"
LANGUAGES CXX
)
string(TOLOWER ${PROJECT_NAME} PROJECT_LOW)
cmake_policy(SET CMP0076 NEW)
cmake_policy(SET CMP0079 NEW)
set(LMDBAL_NAME ${PROJECT_NAME} CACHE STRING "Override for library name and install path")
option(BUILD_STATIC "Builds library as static library" OFF)
option(BUILD_TESTS "Builds tests" OFF)
option(BUILD_DOC_MAN "Builds man page documentation" OFF)
@ -25,6 +26,8 @@ set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
string(TOLOWER ${LMDBAL_NAME} LMDBAL_NAME_LOW)
if (NOT DEFINED QT_VERSION_MAJOR)
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core)
endif()
@ -38,9 +41,9 @@ if (NOT CMAKE_BUILD_TYPE)
endif ()
if (BUILD_STATIC)
add_library(${PROJECT_NAME} STATIC)
add_library(${LMDBAL_NAME} STATIC)
else ()
add_library(${PROJECT_NAME} SHARED)
add_library(${LMDBAL_NAME} SHARED)
endif()
if (CMAKE_BUILD_TYPE STREQUAL "Release")
@ -52,21 +55,21 @@ elseif (CMAKE_BUILD_TYPE STREQUAL "Debug")
endif()
message("Compilation options: " ${COMPILE_OPTIONS})
target_compile_options(${PROJECT_NAME} PRIVATE ${COMPILE_OPTIONS})
target_compile_options(${LMDBAL_NAME} PRIVATE ${COMPILE_OPTIONS})
set_property(TARGET ${PROJECT_NAME} PROPERTY VERSION ${version})
set_property(TARGET ${PROJECT_NAME} PROPERTY SOVERSION 1)
set_property(TARGET ${PROJECT_NAME} PROPERTY EXPORT_NAME ${PROJECT_NAME})
set_property(TARGET ${PROJECT_NAME} PROPERTY INTERFACE_${PROJECT_NAME}_MAJOR_VERSION 1)
set_property(TARGET ${PROJECT_NAME} APPEND PROPERTY
COMPATIBLE_INTERFACE_STRING ${PROJECT_NAME}_MAJOR_VERSION
set_property(TARGET ${LMDBAL_NAME} PROPERTY VERSION ${version})
set_property(TARGET ${LMDBAL_NAME} PROPERTY SOVERSION 1)
set_property(TARGET ${LMDBAL_NAME} PROPERTY EXPORT_NAME ${LMDBAL_NAME})
set_property(TARGET ${LMDBAL_NAME} PROPERTY INTERFACE_${LMDBAL_NAME}_MAJOR_VERSION 1)
set_property(TARGET ${LMDBAL_NAME} APPEND PROPERTY
COMPATIBLE_INTERFACE_STRING ${LMDBAL_NAME}_MAJOR_VERSION
)
if (UNIX)
set_property(TARGET ${PROJECT_NAME} PROPERTY INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}/${PROJECT_LOW}")
set_property(TARGET ${PROJECT_NAME} PROPERTY INSTALL_RPATH_USE_LINK_PATH TRUE)
set_property(TARGET ${PROJECT_NAME} PROPERTY SKIP_BUILD_RPATH FALSE)
set_property(TARGET ${PROJECT_NAME} PROPERTY BUILD_WITH_INSTALL_RPATH FALSE)
set_property(TARGET ${LMDBAL_NAME} PROPERTY INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}/${LMDBAL_NAME_LOW}")
set_property(TARGET ${LMDBAL_NAME} PROPERTY INSTALL_RPATH_USE_LINK_PATH TRUE)
set_property(TARGET ${LMDBAL_NAME} PROPERTY SKIP_BUILD_RPATH FALSE)
set_property(TARGET ${LMDBAL_NAME} PROPERTY BUILD_WITH_INSTALL_RPATH FALSE)
endif()
add_subdirectory(src)
@ -84,47 +87,47 @@ if (BUILD_TESTS)
endif ()
target_include_directories(
${PROJECT_NAME}
${LMDBAL_NAME}
PUBLIC
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_LOW}>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${LMDBAL_NAME_LOW}>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/serializer>
)
target_include_directories(${PROJECT_NAME} PRIVATE ${Qt${QT_VERSION_MAJOR}_INCLUDE_DIRS})
target_include_directories(${PROJECT_NAME} PRIVATE ${Qt${QT_VERSION_MAJOR}Core_INCLUDE_DIRS})
target_include_directories(${LMDBAL_NAME} PRIVATE ${Qt${QT_VERSION_MAJOR}_INCLUDE_DIRS})
target_include_directories(${LMDBAL_NAME} PRIVATE ${Qt${QT_VERSION_MAJOR}Core_INCLUDE_DIRS})
target_link_libraries(
${PROJECT_NAME}
${LMDBAL_NAME}
Qt${QT_VERSION_MAJOR}::Core
lmdb
)
configure_package_config_file(
${CMAKE_CURRENT_SOURCE_DIR}/cmake/Config.cmake.in
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_LOW}Config.cmake"
INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_LOW}
"${CMAKE_CURRENT_BINARY_DIR}/${LMDBAL_NAME_LOW}Config.cmake"
INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${LMDBAL_NAME_LOW}
)
write_basic_package_version_file(
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_LOW}ConfigVersion.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/${LMDBAL_NAME_LOW}ConfigVersion.cmake"
VERSION "${version}"
COMPATIBILITY AnyNewerVersion
)
install(TARGETS ${PROJECT_NAME}
EXPORT ${PROJECT_LOW}Targets
install(TARGETS ${LMDBAL_NAME}
EXPORT ${LMDBAL_NAME_LOW}Targets
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_LOW}
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${LMDBAL_NAME_LOW}
)
install(EXPORT ${PROJECT_LOW}Targets
FILE ${PROJECT_LOW}Targets.cmake
NAMESPACE ${PROJECT_NAME}::
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_LOW}
install(EXPORT ${LMDBAL_NAME_LOW}Targets
FILE ${LMDBAL_NAME_LOW}Targets.cmake
NAMESPACE ${LMDBAL_NAME}::
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${LMDBAL_NAME_LOW}
)
install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_LOW}Config.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_LOW}ConfigVersion.cmake"
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_LOW}
"${CMAKE_CURRENT_BINARY_DIR}/${LMDBAL_NAME_LOW}Config.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/${LMDBAL_NAME_LOW}ConfigVersion.cmake"
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${LMDBAL_NAME_LOW}
)

View File

@ -1,14 +1,15 @@
# LMDBAL - Lightning Memory Data Base Abstraction Level
[![AUR license](https://img.shields.io/aur/license/lmdbal?style=flat-square)](https://git.macaw.me/blue/lmdbal/raw/branch/master/LICENSE.md)
[![AUR version](https://img.shields.io/aur/version/lmdbal?style=flat-square)](https://aur.archlinux.org/packages/lmdbal/)
[![AUR qt5 version](https://img.shields.io/aur/version/lmdbal-qt5?style=flat-square&label=lmdbal-qt5)](https://aur.archlinux.org/packages/lmdbal-qt5/)
[![AUR qt6 version](https://img.shields.io/aur/version/lmdbal-qt6?style=flat-square&label=lmdbal-qt6)](https://aur.archlinux.org/packages/lmdbal-qt6/)
[![Liberapay patrons](https://img.shields.io/liberapay/patrons/macaw.me?logo=liberapay&style=flat-square)](https://liberapay.com/macaw.me)
[![Documentation](https://img.shields.io/badge/Documentation-HTML-green)](https://macaw.me/lmdbal/doc/html)
### Prerequisites
- a compiler (c++ would do)
- Qt 5 or higher (qt5-base would do)
- a c++ compiler (g++ would do)
- Qt 5 or 6 or higher (qt5-base or qt6-base would do)
- lmdb
- CMake 3.16 or higher
- Doxygen (optional, for documentation)
@ -84,6 +85,7 @@ Here is the list of keys you can pass to configuration phase of `cmake ..`:
- `BUILD_DOC` - `True` build doxygen documentation, `False` does not (default is `False`);
- `BUILD_DOXYGEN_AWESOME` - `True` build doxygen awesome theme if `BUILD_DOC` is also `True` (default is `False`);
- `QT_VERSION_MAJOR` - `5` links against Qt5, `6` links agains Qt6, there is no default, so, if you didn't specify it the project will chose automatically;
- `LMDBAL_NAME` - `LMDBAL` builds main target with this name, also install path witll be this name lowercase, usefull if you want to build `LMDBAL-QT6` not to conflict with `LMDBAL-QT5`;
#### Running tests

View File

@ -1,5 +1,5 @@
@PACKAGE_INIT@
include("${CMAKE_CURRENT_LIST_DIR}/lmdbalTargets.cmake")
include("${CMAKE_CURRENT_LIST_DIR}/@LMDBAL_NAME_LOW@Targets.cmake")
check_required_components(lmdbal)
check_required_components(@LMDBAL_NAME_LOW@)

View File

@ -1,23 +1,23 @@
# Maintainer: Yury Gubich <blue@macaw.me>
pkgname=lmdbal
pkgver=0.5.4
pkgrel=1
pkgdesc="LMDB Abstraction Layer, qt5 version"
pkgver=1.0.0
pkgrel=4
pkgdesc="LMDB Abstraction Layer"
arch=('i686' 'x86_64')
url="https://git.macaw.me/blue/lmdbal"
license=('GPL3')
depends=( 'lmdb' 'qt5-base')
depends=( 'lmdb' )
makedepends=('cmake>=3.16' 'gcc')
optdepends=()
source=("$pkgname-$pkgver.tar.gz::https://git.macaw.me/blue/$pkgname/archive/$pkgver.tar.gz")
source=("lmdbal-$pkgver-$pkgrel.tar.gz::https://git.macaw.me/blue/lmdbal/archive/$pkgver.tar.gz")
sha256sums=('SKIP')
build() {
cd "$srcdir/$pkgname"
cmake . -D CMAKE_INSTALL_PREFIX=/usr -D CMAKE_BUILD_TYPE=Release -D QT_VERSION_MAJOR=5
cd "$srcdir/lmdbal"
cmake . -D CMAKE_INSTALL_PREFIX=/usr -D CMAKE_BUILD_TYPE=Release
cmake --build .
}
package() {
cd "$srcdir/$pkgname"
cd "$srcdir/lmdbal"
DESTDIR="$pkgdir/" cmake --install .
}

View File

@ -1,8 +1,9 @@
set(SOURCES
exceptions.cpp
storage.cpp
storagecommon.cpp
base.cpp
transaction.cpp
cursorcommon.cpp
)
set(HEADERS
@ -10,16 +11,19 @@ set(HEADERS
exceptions.h
storage.h
storage.hpp
storagecommon.h
storagecommon.hpp
cursor.h
cursor.hpp
cursorcommon.h
cache.h
cache.hpp
operators.hpp
transaction.h
)
target_sources(${PROJECT_NAME} PRIVATE ${SOURCES})
target_sources(${LMDBAL_NAME} PRIVATE ${SOURCES})
add_subdirectory(serializer)
install(FILES ${HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_LOW})
install(FILES ${HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${LMDBAL_NAME_LOW})

View File

@ -43,7 +43,7 @@ LMDBAL::Base::Base(const QString& _name, uint16_t _mapSize):
size(_mapSize),
environment(),
storages(),
transactions(new Transactions())
transactions()
{}
/**
@ -52,9 +52,7 @@ LMDBAL::Base::Base(const QString& _name, uint16_t _mapSize):
LMDBAL::Base::~Base() {
close();
delete transactions;
for (const std::pair<const std::string, iStorage*>& pair : storages)
for (const std::pair<const std::string, StorageCommon*>& pair : storages)
delete pair.second;
}
@ -68,14 +66,16 @@ LMDBAL::Base::~Base() {
*/
void LMDBAL::Base::close() {
if (opened) {
for (const LMDBAL::TransactionID id : *transactions)
abortTransaction(id, emptyName);
for (const std::pair<LMDBAL::TransactionID, LMDBAL::Transaction*> pair : transactions) {
abortTransaction(pair.first, emptyName);
pair.second->reset();
}
for (const std::pair<const std::string, iStorage*>& pair : storages)
for (const std::pair<const std::string, StorageCommon*>& pair : storages)
pair.second->close();
mdb_env_close(environment);
transactions->clear();
transactions.clear();
opened = false;
}
}
@ -99,8 +99,8 @@ void LMDBAL::Base::open() {
mdb_env_open(environment, path.toStdString().c_str(), 0, 0664);
TransactionID txn = beginPrivateTransaction(emptyName);
for (const std::pair<const std::string, iStorage*>& pair : storages) {
iStorage* storage = pair.second;
for (const std::pair<const std::string, StorageCommon*>& pair : storages) {
StorageCommon* storage = pair.second;
int rc = storage->open(txn);
if (rc)
throw Unknown(name, mdb_strerror(rc));
@ -200,7 +200,7 @@ void LMDBAL::Base::drop() {
throw Closed("drop", name);
TransactionID txn = beginPrivateTransaction(emptyName);
for (const std::pair<const std::string, iStorage*>& pair : storages) {
for (const std::pair<const std::string, StorageCommon*>& pair : storages) {
int rc = pair.second->drop(txn);
if (rc != MDB_SUCCESS) {
abortPrivateTransaction(txn, emptyName);
@ -209,7 +209,7 @@ void LMDBAL::Base::drop() {
}
commitPrivateTransaction(txn, emptyName);
for (const std::pair<const std::string, iStorage*>& pair : storages)
for (const std::pair<const std::string, StorageCommon*>& pair : storages)
pair.second->handleDrop();
}
@ -261,12 +261,13 @@ LMDBAL::WriteTransaction LMDBAL::Base::beginTransaction() {
* \brief Aborts transaction
*
* Terminates transaction cancelling changes.
* This is an optimal way to terminate read-only transactions
* Every storage receives notification about this transaction being aborted.
* This is an optimal way to abort public transactions
*
* \param[in] id - transaction ID you want to abort
*
* \exception LMDBAL::Closed - thrown if the database is closed
* \exception LMDBAL::Unknown - thrown if transaction with given ID was not found or if something unexpected happened
* \exception LMDBAL::Unknown - thrown if something unexpected happened
*/
void LMDBAL::Base::abortTransaction(LMDBAL::TransactionID id) const {
return abortTransaction(id, emptyName);}
@ -275,11 +276,13 @@ void LMDBAL::Base::abortTransaction(LMDBAL::TransactionID id) const {
* \brief Commits transaction
*
* Terminates transaction applying changes.
* Every storage receives notification about this transaction being committed.
* This is an optimal way to commit public transactions
*
* \param[in] id - transaction ID you want to commit
*
* \exception LMDBAL::Closed - thrown if the database is closed
* \exception LMDBAL::Unknown - thrown if transaction with given ID was not found or if something unexpected happened
* \exception LMDBAL::Unknown - thrown if something unexpected happened
*/
void LMDBAL::Base::commitTransaction(LMDBAL::TransactionID id) {
return commitTransaction(id, emptyName);}
@ -287,7 +290,9 @@ void LMDBAL::Base::commitTransaction(LMDBAL::TransactionID id) {
/**
* \brief Begins read-only transaction
*
* This function is intended to be called from subordinate storage or cache
* This function is intended to be called from subordinate storage, cache, transaction or cursor.
* Every storage receives notification about this transaction being started.
* This is an optimal way to begin read-only public transactions.
*
* \param[in] storageName - name of the storage/cache that you begin transaction from, needed just to inform if something went wrong
*
@ -301,8 +306,7 @@ LMDBAL::TransactionID LMDBAL::Base::beginReadOnlyTransaction(const std::string&
throw Closed("beginReadOnlyTransaction", name, storageName);
TransactionID txn = beginPrivateReadOnlyTransaction(storageName);
transactions->emplace(txn);
for (const std::pair<const std::string, LMDBAL::iStorage*>& pair : storages)
for (const std::pair<const std::string, LMDBAL::StorageCommon*>& pair : storages)
pair.second->transactionStarted(txn, true);
return txn;
@ -311,7 +315,9 @@ LMDBAL::TransactionID LMDBAL::Base::beginReadOnlyTransaction(const std::string&
/**
* \brief Begins writable transaction
*
* This function is intended to be called from subordinate storage or cache
* This function is intended to be called from subordinate storage, cache, transaction or cursor.
* Every storage receives notification about this transaction being started.
* This is an optimal way to begin public transactions.
*
* \param[in] storageName - name of the storage/cache that you begin transaction from, needed just to inform if something went wrong
*
@ -325,8 +331,7 @@ LMDBAL::TransactionID LMDBAL::Base::beginTransaction(const std::string& storageN
throw Closed("beginTransaction", name, storageName);
TransactionID txn = beginPrivateTransaction(storageName);
transactions->emplace(txn);
for (const std::pair<const std::string, LMDBAL::iStorage*>& pair : storages)
for (const std::pair<const std::string, LMDBAL::StorageCommon*>& pair : storages)
pair.second->transactionStarted(txn, false);
return txn;
@ -336,55 +341,46 @@ LMDBAL::TransactionID LMDBAL::Base::beginTransaction(const std::string& storageN
* \brief Aborts transaction
*
* Terminates transaction cancelling changes.
* This is an optimal way to terminate read-only transactions.
* This function is intended to be called from subordinate storage or cache
* Every storage receives notification about this transaction being aborted.
* This is an optimal way to abort public transactions.
* This function is intended to be called from subordinate storage, cache, transaction or cursor
*
* \param[in] id - transaction ID you want to abort
* \param[in] storageName - name of the storage/cache that you begin transaction from, needed just to inform if something went wrong
*
* \exception LMDBAL::Closed - thrown if the database is closed
* \exception LMDBAL::Unknown - thrown if transaction with given ID was not found or if something unexpected happened
* \exception LMDBAL::Unknown - thrown if something unexpected happened
*/
void LMDBAL::Base::abortTransaction(LMDBAL::TransactionID id, const std::string& storageName) const {
if (!opened)
throw Closed("abortTransaction", name, storageName);
Transactions::iterator itr = transactions->find(id);
if (itr == transactions->end()) //TODO may be it's a good idea to make an exception class for this
throw Unknown(name, "unable to abort transaction: transaction was not found", storageName);
abortPrivateTransaction(id, storageName);
for (const std::pair<const std::string, LMDBAL::iStorage*>& pair : storages)
for (const std::pair<const std::string, LMDBAL::StorageCommon*>& pair : storages)
pair.second->transactionAborted(id);
transactions->erase(itr);
}
/**
* \brief Commits transaction
*
* Terminates transaction applying changes.
* This function is intended to be called from subordinate storage or cache
* Every storage receives notification about this transaction being committed.
* This is an optimal way to commit public transactions
* This function is intended to be called from subordinate storage, cache, transaction or cursor
*
* \param[in] id - transaction ID you want to commit
* \param[in] storageName - name of the storage/cache that you begin transaction from, needed just to inform if something went wrong
*
* \exception LMDBAL::Closed - thrown if the database is closed
* \exception LMDBAL::Unknown - thrown if transaction with given ID was not found or if something unexpected happened
* \exception LMDBAL::Unknown - thrown if something unexpected happened
*/
void LMDBAL::Base::commitTransaction(LMDBAL::TransactionID id, const std::string& storageName) {
if (!opened)
throw Closed("abortTransaction", name, storageName);
Transactions::iterator itr = transactions->find(id);
if (itr == transactions->end()) //TODO may be it's a good idea to make an exception class for this
throw Unknown(name, "unable to commit transaction: transaction was not found", storageName);
commitPrivateTransaction(id, storageName);
for (const std::pair<const std::string, LMDBAL::iStorage*>& pair : storages)
for (const std::pair<const std::string, LMDBAL::StorageCommon*>& pair : storages)
pair.second->transactionCommited(id);
transactions->erase(itr);
}
/**

View File

@ -16,8 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LMDBAL_BASE_H
#define LMDBAL_BASE_H
#pragma once
#include <map>
#include <set>
@ -35,7 +34,7 @@
namespace LMDBAL {
class iStorage;
class StorageCommon;
class Transaction;
class WriteTransaction;
@ -52,7 +51,7 @@ typedef MDB_txn* TransactionID; /**<\brief I'm going to
typedef uint32_t SizeType; /**<\brief All LMDBAL sizes are uint32_t*/
class Base {
friend class iStorage;
friend class StorageCommon;
friend class Transaction;
friend class WriteTransaction;
public:
@ -85,8 +84,8 @@ public:
LMDBAL::Cache<K, V>* getCache(const std::string& storageName);
private:
typedef std::map<std::string, LMDBAL::iStorage*> Storages; /**<\brief Storage and Cache pointers are saved in the std::map*/
typedef std::set<TransactionID> Transactions; /**<\brief Piblic transaction IDs are saved in the std::set*/
typedef std::map<std::string, LMDBAL::StorageCommon *> Storages; /**<\brief Storage and Cache pointers are saved in the std::map*/
typedef std::map<TransactionID, Transaction*> Transactions; /**<\brief Piblic transaction IDs are saved in the std::set*/
void commitTransaction(TransactionID id);
void abortTransaction(TransactionID id) const;
@ -106,7 +105,7 @@ private:
uint16_t size; /**<\brief lmdb map size in MiB*/
MDB_env* environment; /**<\brief lmdb environment handle*/
Storages storages; /**<\brief Registered storages and caches*/
Transactions* transactions; /**<\brief Active public transactions*/
mutable Transactions transactions; /**<\brief Active public transactions*/
inline static const std::string emptyName = ""; /**<\brief Empty string for general fallback purposes*/
};
@ -137,7 +136,7 @@ LMDBAL::Storage<K, V>* LMDBAL::Base::addStorage(const std::string& storageName,
throw Opened(name, "add storage " + storageName);
Storage<K, V>* storage = new Storage<K, V>(this, storageName, duplicates);
std::pair<Storages::const_iterator, bool> pair = storages.insert(std::make_pair(storageName, (iStorage*)storage));
std::pair<Storages::const_iterator, bool> pair = storages.insert(std::make_pair(storageName, (StorageCommon *)storage));
if (!pair.second)
throw StorageDuplicate(name, storageName);
@ -165,7 +164,7 @@ LMDBAL::Cache<K, V> * LMDBAL::Base::addCache(const std::string& storageName) {
throw Opened(name, "add cache " + storageName);
Cache<K, V>* cache = new Cache<K, V>(this, storageName, false);
std::pair<Storages::const_iterator, bool> pair = storages.insert(std::make_pair(storageName, (iStorage*)cache));
std::pair<Storages::const_iterator, bool> pair = storages.insert(std::make_pair(storageName, (StorageCommon *)cache));
if (!pair.second)
throw StorageDuplicate(name, storageName);
@ -215,5 +214,3 @@ template <class K, class V>
LMDBAL::Cache<K, V>* LMDBAL::Base::getCache(const std::string& storageName) {
return static_cast<Cache<K, V>*>(storages.at(storageName));
}
#endif //LMDBAL_BASE_H

View File

@ -16,8 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LMDBAL_CACHE_H
#define LMDBAL_CACHE_H
#pragma once
#include <map>
#include <set>
@ -134,5 +133,3 @@ protected:
}
#include "cache.hpp"
#endif // LMDBAL_CACHE_H

View File

@ -16,8 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LMDBAL_CACHE_HPP
#define LMDBAL_CACHE_HPP
#pragma once
#include "cache.h"
#include "exceptions.h"
@ -64,10 +63,10 @@ LMDBAL::Cache<K, V>::~Cache() {
template<class K, class V>
void LMDBAL::Cache<K, V>::addRecord(const K& key, const V& value) {
iStorage::ensureOpened(iStorage::addRecordMethodName);
StorageCommon::ensureOpened(StorageCommon::addRecordMethodName);
if (cache->count(key) > 0)
iStorage::throwDuplicate(iStorage::toString(key));
StorageCommon::throwDuplicate(StorageCommon::toString(key));
Storage<K, V>::addRecord(key, value);
handleAddRecord(key, value);
@ -76,7 +75,7 @@ void LMDBAL::Cache<K, V>::addRecord(const K& key, const V& value) {
template<class K, class V>
void LMDBAL::Cache<K, V>::addRecord(const K& key, const V& value, TransactionID txn) {
if (cache->count(key) > 0)
iStorage::throwDuplicate(iStorage::toString(key));
StorageCommon::throwDuplicate(StorageCommon::toString(key));
Storage<K, V>::addRecord(key, value, txn);
@ -97,7 +96,7 @@ void LMDBAL::Cache<K, V>::handleAddRecord(const K& key, const V& value) {
template<class K, class V>
bool LMDBAL::Cache<K, V>::forceRecord(const K& key, const V& value) {
iStorage::ensureOpened(iStorage::forceRecordMethodName);
StorageCommon::ensureOpened(StorageCommon::forceRecordMethodName);
bool added = Storage<K, V>::forceRecord(key, value);
handleForceRecord(key, value, added);
@ -137,18 +136,18 @@ void LMDBAL::Cache<K, V>::handleForceRecord(const K& key, const V& value, bool a
template<class K, class V>
void LMDBAL::Cache<K, V>::changeRecord(const K& key, const V& value) {
iStorage::ensureOpened(iStorage::changeRecordMethodName);
StorageCommon::ensureOpened(StorageCommon::changeRecordMethodName);
if (mode == Mode::full) {
typename std::map<K, V>::iterator itr = cache->find(key);
if (itr == cache->end())
iStorage::throwNotFound(iStorage::toString(key));
StorageCommon::throwNotFound(StorageCommon::toString(key));
Storage<K, V>::changeRecord(key, value);
itr->second = value;
} else {
if (abscent->count(key) > 0)
iStorage::throwNotFound(iStorage::toString(key));
StorageCommon::throwNotFound(StorageCommon::toString(key));
try {
Storage<K, V>::changeRecord(key, value);
@ -170,12 +169,12 @@ void LMDBAL::Cache<K, V>::changeRecord(const K& key, const V& value, Transaction
if (mode == Mode::full) {
typename std::map<K, V>::iterator itr = cache->find(key);
if (itr == cache->end())
iStorage::throwNotFound(iStorage::toString(key));
StorageCommon::throwNotFound(StorageCommon::toString(key));
Storage<K, V>::changeRecord(key, value, txn);
} else {
if (abscent->count(key) > 0)
iStorage::throwNotFound(iStorage::toString(key));
StorageCommon::throwNotFound(StorageCommon::toString(key));
try {
Storage<K, V>::changeRecord(key, value, txn);
@ -208,7 +207,7 @@ void LMDBAL::Cache<K, V>::handleChangeRecord(const K& key, const V& value) {
template<class K, class V>
V LMDBAL::Cache<K, V>::getRecord(const K& key) const {
iStorage::ensureOpened(iStorage::getRecordMethodName);
StorageCommon::ensureOpened(StorageCommon::getRecordMethodName);
V value;
Cache<K, V>::getRecord(key, value);
@ -217,7 +216,7 @@ V LMDBAL::Cache<K, V>::getRecord(const K& key) const {
template<class K, class V>
void LMDBAL::Cache<K, V>::getRecord(const K& key, V& out) const {
iStorage::ensureOpened(iStorage::getRecordMethodName);
StorageCommon::ensureOpened(StorageCommon::getRecordMethodName);
typename std::map<K, V>::const_iterator itr = cache->find(key);
if (itr != cache->end()) {
@ -226,7 +225,7 @@ void LMDBAL::Cache<K, V>::getRecord(const K& key, V& out) const {
}
if (mode == Mode::full || abscent->count(key) != 0)
iStorage::throwNotFound(iStorage::toString(key));
StorageCommon::throwNotFound(StorageCommon::toString(key));
try {
Storage<K, V>::getRecord(key, out);
@ -270,7 +269,7 @@ void LMDBAL::Cache<K, V>::getRecord(const K& key, V& out, TransactionID txn) con
}
break;
case Operation::remove:
iStorage::throwNotFound(iStorage::toString(key));
StorageCommon::throwNotFound(StorageCommon::toString(key));
break;
case Operation::change:
if (static_cast<std::pair<K, V>*>(entry.second)->first == key) {
@ -286,7 +285,7 @@ void LMDBAL::Cache<K, V>::getRecord(const K& key, V& out, TransactionID txn) con
}
break;
case Operation::drop:
iStorage::throwNotFound(iStorage::toString(key));
StorageCommon::throwNotFound(StorageCommon::toString(key));
break;
case Operation::replace: {
std::map<K, V>* newMap = static_cast<std::map<K, V>*>(entry.second);
@ -295,7 +294,7 @@ void LMDBAL::Cache<K, V>::getRecord(const K& key, V& out, TransactionID txn) con
out = vitr->second;
return;
} else {
iStorage::throwNotFound(iStorage::toString(key));
StorageCommon::throwNotFound(StorageCommon::toString(key));
}
}
break;
@ -323,7 +322,7 @@ void LMDBAL::Cache<K, V>::getRecord(const K& key, V& out, TransactionID txn) con
}
if (mode == Mode::full || abscent->count(key) != 0)
iStorage::throwNotFound(iStorage::toString(key));
StorageCommon::throwNotFound(StorageCommon::toString(key));
try {
Storage<K, V>::getRecord(key, out, txn);
@ -353,7 +352,7 @@ void LMDBAL::Cache<K, V>::discoveredRecord(const K& key, const V& value, Transac
template<class K, class V>
bool LMDBAL::Cache<K, V>::checkRecord(const K& key) const {
iStorage::ensureOpened(iStorage::checkRecordMethodName);
StorageCommon::ensureOpened(StorageCommon::checkRecordMethodName);
typename std::map<K, V>::const_iterator itr = cache->find(key);
if (itr != cache->end())
@ -458,7 +457,7 @@ void LMDBAL::Cache<K, V>::appendToCache(const K& key, const V& value) const {
template<class K, class V>
std::map<K, V> LMDBAL::Cache<K, V>::readAll() const {
iStorage::ensureOpened(iStorage::readAllMethodName);
StorageCommon::ensureOpened(StorageCommon::readAllMethodName);
if (mode != Mode::full) { //there is a room for optimization
mode = Mode::full; //I can read and deserialize only those values
@ -472,7 +471,7 @@ std::map<K, V> LMDBAL::Cache<K, V>::readAll() const {
template<class K, class V>
void LMDBAL::Cache<K, V>::readAll(std::map<K, V>& out) const {
iStorage::ensureOpened(iStorage::readAllMethodName);
StorageCommon::ensureOpened(StorageCommon::readAllMethodName);
if (mode != Mode::full) { //there is a room for optimization
mode = Mode::full; //I can read and deserialize only those values
@ -643,7 +642,7 @@ void LMDBAL::Cache<K, V>::handleAddRecords(const std::map<K, V>& data, bool over
template<class K, class V>
void LMDBAL::Cache<K, V>::removeRecord(const K& key) {
iStorage::ensureOpened(iStorage::removeRecordMethodName);
StorageCommon::ensureOpened(StorageCommon::removeRecordMethodName);
bool noKey = false;
if (mode != Mode::full)
@ -652,7 +651,7 @@ void LMDBAL::Cache<K, V>::removeRecord(const K& key) {
noKey = abscent->count(key) > 0;
if (noKey)
iStorage::throwNotFound(iStorage::toString(key));
StorageCommon::throwNotFound(StorageCommon::toString(key));
Storage<K, V>::removeRecord(key);
handleRemoveRecord(key);
@ -667,7 +666,7 @@ void LMDBAL::Cache<K, V>::removeRecord(const K& key, TransactionID txn) {
noKey = abscent->count(key) > 0;
if (noKey)
iStorage::throwNotFound(iStorage::toString(key));
StorageCommon::throwNotFound(StorageCommon::toString(key));
Storage<K, V>::removeRecord(key, txn);
@ -782,8 +781,8 @@ void LMDBAL::Cache<K, V>::handleMode() const {
template<class K, class V>
int LMDBAL::Cache<K, V>::drop(const WriteTransaction& transaction) {
iStorage::ensureOpened(iStorage::dropMethodName);
TransactionID txn = iStorage::extractTransactionId(transaction, iStorage::dropMethodName);
StorageCommon::ensureOpened(StorageCommon::dropMethodName);
TransactionID txn = StorageCommon::extractTransactionId(transaction, StorageCommon::dropMethodName);
int res = Storage<K, V>::drop(txn);
if (res != MDB_SUCCESS)
@ -898,5 +897,3 @@ void LMDBAL::Cache<K, V>::destroyTransactionEntry(const Entry& entry) const {
break;
}
}
#endif //LMDBAL_CACHE_HPP

View File

@ -16,8 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LMDBAL_CURSOR_H
#define LMDBAL_CURSOR_H
#pragma once
#include <string>
@ -25,19 +24,13 @@
#include "base.h"
#include "storage.h"
#include "transaction.h"
#include "cursorcommon.h"
namespace LMDBAL {
template <class K, class V>
class Cursor {
class Cursor : public CursorCommon {
friend class Storage<K, V>;
private:
enum State { /**<Cursor state:*/
closed, /**< - closed*/
openedPublic, /**< - opened with public transaction, all storages will be notified about it after it's done*/
openedPrivate /**< - opened with private transaction, only current storage will be notified when cursor is closed*/
};
public:
Cursor();
Cursor(Storage<K, V>* parent);
@ -48,14 +41,6 @@ public:
Cursor& operator = (const Cursor& other) = delete;
Cursor& operator = (Cursor&& other);
void open();
void open(const Transaction& transaction);
void renew();
void renew(const Transaction& transaction);
void close();
bool opened() const;
bool empty() const;
void drop();
std::pair<K, V> first();
@ -72,38 +57,10 @@ public:
void current(K& key, V& value) const;
private:
void dropped();
void freed();
void terminated();
void operateCursorRead(K& key, V& value, MDB_cursor_op operation, const std::string& methodName, const std::string& operationName) const;
private:
Storage<K, V>* storage;
MDB_cursor* cursor;
State state;
uint32_t id;
inline static const std::string openCursorMethodName = "Cursor::open"; /**<\brief member function name, just for exceptions*/
inline static const std::string closeCursorMethodName = "Cursor::close"; /**<\brief member function name, just for exceptions*/
inline static const std::string renewCursorMethodName = "Cursor::renew"; /**<\brief member function name, just for exceptions*/
inline static const std::string firstMethodName = "first"; /**<\brief member function name, just for exceptions*/
inline static const std::string lastMethodName = "last"; /**<\brief member function name, just for exceptions*/
inline static const std::string nextMethodName = "next"; /**<\brief member function name, just for exceptions*/
inline static const std::string prevMethodName = "prev"; /**<\brief member function name, just for exceptions*/
inline static const std::string currentMethodName = "current"; /**<\brief member function name, just for exceptions*/
inline static const std::string setMethodName = "set"; /**<\brief member function name, just for exceptions*/
inline static const std::string firstOperationName = "Cursor::first"; /**<\brief member function name, just for exceptions*/
inline static const std::string lastOperationName = "Cursor::last"; /**<\brief member function name, just for exceptions*/
inline static const std::string nextOperationName = "Cursor::next"; /**<\brief member function name, just for exceptions*/
inline static const std::string prevOperationName = "Cursor::prev"; /**<\brief member function name, just for exceptions*/
inline static const std::string currentOperationName = "Cursor::current"; /**<\brief member function name, just for exceptions*/
};
};
#include "cursor.hpp"
#endif //LMDBAL_CURSOR_H

View File

@ -16,10 +16,10 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LMDBAL_CURSOR_HPP
#define LMDBAL_CURSOR_HPP
#pragma once
#include "cursor.h"
#include <iostream>
/**
@ -41,8 +41,6 @@
* You are not supposed to instantiate or destory instances of this class yourself!
*/
static uint32_t idCounter = 0;
/**
* \brief Creates a cursor
*
@ -50,12 +48,9 @@ static uint32_t idCounter = 0;
*/
template<class K, class V>
LMDBAL::Cursor<K, V>::Cursor(Storage<K, V>* parent):
storage(parent),
cursor(nullptr),
state(closed),
id(++idCounter)
CursorCommon(parent)
{
storage->cursors[id] = this;
parent->cursors[id] = this;
}
/**
@ -65,10 +60,7 @@ LMDBAL::Cursor<K, V>::Cursor(Storage<K, V>* parent):
*/
template<class K, class V>
LMDBAL::Cursor<K, V>::Cursor():
storage(nullptr),
cursor(nullptr),
state(closed),
id(0)
CursorCommon()
{}
/**
@ -76,57 +68,39 @@ LMDBAL::Cursor<K, V>::Cursor():
*/
template<class K, class V>
LMDBAL::Cursor<K, V>::Cursor(Cursor&& other):
storage(other.storage),
cursor(other.cursor),
state(other.state),
id(other.id)
CursorCommon(std::move(other))
{
other.terminated();
if (id != 0)
storage->cursors[id] = this;
other.freed();
if (!empty())
static_cast<Storage<K, V>*>(storage)->cursors[id] = this;
}
/**
* \brief A private function that turns cursor into an empty one
* \brief Move assignment operator
*
* This function is called from LMDBAL::Storage, when it gets destroyed, but still has some valid.
* Those cursors will become empty, and can't be used anymore
* Transfers other cursor into this one
*/
template<class K, class V>
LMDBAL::Cursor<K, V>& LMDBAL::Cursor<K, V>::operator = (Cursor&& other) {
terminated();
if (!empty() && other.empty())
static_cast<Storage<K, V>*>(storage)->cursors.erase(id);
if (id != 0)
storage->cursors.erase(id);
CursorCommon::operator=(std::move(other));
storage = other.storage;
cursor = other.cursor;
state = other.state;
id = other.id;
if (id != 0) {
other.freed();
other.state = closed;
storage->cursors[id] = this;
}
if (!empty())
static_cast<Storage<K, V>*>(storage)->cursors[id] = this;
return *this;
}
/**
* \brief Destroys a cursor
* \brief Destroys this cursor
*
* If the cursor wasn't properly closed - it's going to be upon destruction
*/
template<class K, class V>
LMDBAL::Cursor<K, V>::~Cursor () {
close();
if (id != 0)
storage->cursors.erase(id);
static_cast<Storage<K, V>*>(storage)->cursors.erase(id);
}
/**
@ -140,251 +114,9 @@ void LMDBAL::Cursor<K, V>::drop () {
close();
if (id != 0)
storage->cursors.erase(id);
static_cast<Storage<K, V>*>(storage)->cursors.erase(id);
freed();
}
/**
* \brief A private method that turns cursor into an empty one
*
* This function is called from LMDBAL::Storage, when it gets destroyed, but still has some valid cursors.
* Those cursors will become empty, and can't be used anymore
*/
template<class K, class V>
void LMDBAL::Cursor<K, V>::dropped () {
terminated();
freed();
}
/**
* \brief A private method that turns cursor into an empty one (submethod)
*
* This function is called from LMDBAL::Storage, when the cursor is getting destoryed.
* Those cursors will become empty, and can't be used anymore
*/
template<class K, class V>
void LMDBAL::Cursor<K, V>::freed () {
cursor = nullptr;
storage = nullptr;
id = 0;
}
/**
* \brief Returns true if the cursor is empty
*
* Empty cursors can't be used, they can be only targets of move operations
*/
template<class K, class V>
bool LMDBAL::Cursor<K, V>::empty () const {
return id == 0;
}
/**
* \brief A private function the storage owning this cursor will call to inform this cursor that the thansaction needs to be aborted
*/
template<class K, class V>
void LMDBAL::Cursor<K, V>::terminated () {
close(); //for now it's the same, but if I ever going to make writable cursor - here is where it's gonna be different
}
/**
* \brief Opens the cursor for operations.
*
* This is a normal way to start the sequence of operations with the cursor.
* This variant of the function creates a read only transaction just for this cursor
*
* This function should be called when the LMDBAL::Storage is already opened and before any query with this cursor!
* It will do nothing to a cursor that was already opened (no matter what way).
*
* \exception LMDBAL::Closed thrown if you try to open the cursor on a closed database
* \exception LMDBAL::Unknown thrown if there was a problem opening the cursor by the lmdb, or to begin a transaction
* \exception LMDBAL::CursorEmpty thrown if the cursor was empty
*/
template<class K, class V>
void LMDBAL::Cursor<K, V>::open () {
if (empty())
throw CursorEmpty(openCursorMethodName);
storage->ensureOpened(openCursorMethodName);
switch (state) {
case closed: {
TransactionID txn = storage->beginReadOnlyTransaction();
int result = storage->_mdbCursorOpen(txn, &cursor);
if (result != MDB_SUCCESS)
storage->throwUnknown(result, txn);
storage->transactionStarted(txn, true);
state = openedPrivate;
} break;
default:
break;
}
}
/**
* \brief Opens the cursor for operations.
*
* This is a normal way to start the sequence of operations with the cursor.
* This variant of the function uses for queries a transaction you have obtained somewhere else.
*
* This function should be called when the LMDBAL::Storage is already opened and before any query with this cursor!
* It will do nothing to a cursor that was already opened (no matter what way).
*
* \param[in] transaction - a transaction, can be read only
*
* \exception LMDBAL::Closed thrown if you try to open the cursor on a closed database
* \exception LMDBAL::Unknown thrown if there was a problem opening the cursor by the lmdb
* \exception LMDBAL::TransactionTerminated thrown if the passed transaction not active, any action with it's inner ID is an error
* \exception LMDBAL::CursorEmpty thrown if the cursor was empty
*/
template<class K, class V>
void LMDBAL::Cursor<K, V>::open (const Transaction& transaction) {
if (empty())
throw CursorEmpty(openCursorMethodName);
storage->ensureOpened(openCursorMethodName);
TransactionID txn = storage->extractTransactionId(transaction, openCursorMethodName);
switch (state) {
case closed: {
int result = storage->_mdbCursorOpen(txn, &cursor);
if (result != MDB_SUCCESS)
storage->throwUnknown(result);
state = openedPublic;
} break;
default:
break;
}
}
/**
* \brief Renews a cursor
*
* This function aborts current transaction if the cursor was opened with it's own transaction
* (does not mess up if the transaction was public),
* creates new private transaction and rebinds this cursor to it.
*
* Theoretically you could call this method if your public transaction was aborted (or commited)
* but you wish to continue to keep working with your cursor.
* Or if you just want to rebind your cursor to a new private transaction.
*
* This function does nothing if the cursor is closed
*
* \exception LMDBAL::Closed thrown if you try to renew the cursor on a closed database
* \exception LMDBAL::Unknown thrown if there was a problem beginning new transaction or if there was a problem renewing the cursor by lmdb
* \exception LMDBAL::CursorEmpty thrown if the cursor was empty
*/
template<class K, class V>
void LMDBAL::Cursor<K, V>::renew () {
if (empty())
throw CursorEmpty(openCursorMethodName);
storage->ensureOpened(renewCursorMethodName);
switch (state) {
case openedPrivate: {
TransactionID txn = storage->_mdbCursorTxn(cursor);
storage->abortTransaction(txn);
storage->transactionAborted(txn);
[[fallthrough]];
}
case openedPublic: {
TransactionID txn = storage->beginReadOnlyTransaction();
int result = storage->_mdbCursorRenew(txn, cursor);
if (result != MDB_SUCCESS)
storage->throwUnknown(result, txn);
storage->transactionStarted(txn, true);
state = openedPrivate;
} break;
default:
break;
}
}
/**
* \brief Renews a cursor
*
* This function aborts current transaction if the cursor was opened with it's own transaction
* (does not mess up if the transaction was public),
* and rebinds this cursor to a passed new transaction.
*
* Theoretically you could call this method if your previous public transaction was aborted (or commited)
* but you wish to continue to keep working with your cursor.
* Or if you just want to rebind your cursor to another public transaction.
*
* This function does nothing if the cursor is closed
*
* \param[in] transaction - a transaction you wish this cursor to be bound to
*
* \exception LMDBAL::Closed thrown if you try to renew the cursor on a closed database
* \exception LMDBAL::Unknown thrown if there was a problem renewing the cursor by lmdb
* \exception LMDBAL::TransactionTerminated thrown if the passed transaction not active, any action with it's inner ID is an error
* \exception LMDBAL::CursorEmpty thrown if the cursor was empty
*/
template<class K, class V>
void LMDBAL::Cursor<K, V>::renew (const Transaction& transaction) {
if (empty())
throw CursorEmpty(openCursorMethodName);
storage->ensureOpened(renewCursorMethodName);
TransactionID txn = storage->extractTransactionId(transaction, renewCursorMethodName);
switch (state) {
case openedPrivate: {
TransactionID txn = storage->_mdbCursorTxn(cursor);
storage->abortTransaction(txn);
storage->transactionAborted(txn);
[[fallthrough]];
}
case openedPublic: {
int result = storage->_mdbCursorRenew(txn, cursor);
if (result != MDB_SUCCESS)
storage->throwUnknown(result);
state = openedPublic;
} break;
default:
break;
}
}
/**
* \brief Termiates a sequence of operations with the cursor
*
* This is a normal way to tell that you're done with the cursor and don't want to continue the sequence of queries.
* The state of the cursor is lost after calling this method, some inner resorce is freed.
*
* If the cursor was opened with the private transaction - the owner storage will be notified of the aborted transaction.
*
* This function does nothing on a closed cursor.
*/
template<class K, class V>
void LMDBAL::Cursor<K, V>::close () {
switch (state) {
case openedPublic: {
storage->_mdbCursorClose(cursor);
state = closed;
} break;
case openedPrivate: {
TransactionID txn = storage->_mdbCursorTxn(cursor);
storage->_mdbCursorClose(cursor);
storage->abortTransaction(txn);
storage->transactionAborted(txn);
state = closed;
} break;
default:
break;
}
}
/**
* \brief Tells if the cursor is open
*/
template<class K, class V>
bool LMDBAL::Cursor<K, V>::opened () const {
return state != closed;
reset();
}
/**
@ -605,16 +337,16 @@ std::pair<K, V> LMDBAL::Cursor<K, V>::current () const {
template<class K, class V>
bool LMDBAL::Cursor<K, V>::set (const K& key) {
if (state == closed)
storage->throwCursorNotReady(setMethodName);
static_cast<Storage<K, V>*>(storage)->throwCursorNotReady(setMethodName);
MDB_val mdbKey = storage->keySerializer.setData(key);
int result = storage->_mdbCursorSet(cursor, mdbKey);
MDB_val mdbKey = static_cast<Storage<K, V>*>(storage)->keySerializer.setData(key);
int result = static_cast<Storage<K, V>*>(storage)->_mdbCursorSet(handle, mdbKey);
if (result == MDB_SUCCESS)
return true;
else if (result == MDB_NOTFOUND)
return false;
storage->throwUnknown(result);
static_cast<Storage<K, V>*>(storage)->throwUnknown(result);
return false; //unreachable, just to suppress the warning
}
@ -642,20 +374,18 @@ void LMDBAL::Cursor<K, V>::operateCursorRead(
const std::string& operationName
) const {
if (state == closed)
storage->throwCursorNotReady(methodName);
static_cast<Storage<K, V>*>(storage)->throwCursorNotReady(methodName);
MDB_val mdbKey, mdbValue;
int result = storage->_mdbCursorGet(cursor, mdbKey, mdbValue, operation);
int result = static_cast<Storage<K, V>*>(storage)->_mdbCursorGet(handle, mdbKey, mdbValue, operation);
if (result != MDB_SUCCESS)
storage->throwNotFoundOrUnknown(result, operationName);
static_cast<Storage<K, V>*>(storage)->throwNotFoundOrUnknown(result, operationName);
storage->keySerializer.deserialize(mdbKey, key);
storage->valueSerializer.deserialize(mdbValue, value);
static_cast<Storage<K, V>*>(storage)->keySerializer.deserialize(mdbKey, key);
static_cast<Storage<K, V>*>(storage)->valueSerializer.deserialize(mdbValue, value);
if (state == openedPrivate)
storage->discoveredRecord(key, value);
static_cast<Storage<K, V>*>(storage)->discoveredRecord(key, value);
else
storage->discoveredRecord(key, value, storage->_mdbCursorTxn(cursor));
static_cast<Storage<K, V>*>(storage)->discoveredRecord(key, value, static_cast<Storage<K, V>*>(storage)->_mdbCursorTxn(handle));
}
#endif //LMDBAL_CURSOR_HPP

342
src/cursorcommon.cpp Normal file
View File

@ -0,0 +1,342 @@
/*
* LMDB Abstraction Layer.
* Copyright (C) 2023 Yury Gubich <blue@macaw.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "cursorcommon.h"
/**
* \class LMDBAL::CursorCommon
* \brief An object to manage cursor internals and state.
*
* Cursors are owned by the storage, they die with the storage.
* They also get closed if the storage is closed (if you close by the database for example)
*
* You can obtain an instance of this class calling LMDBAL::Storage::createCursor()
* and destory it calling LMDBAL::Storage::destoryCursor() at any time, LMDBAL::Base doesn't necessarily need to be opened.
*
* You are not supposed to instantiate or destory instances of this class yourself!
*/
#include "storagecommon.h"
static uint32_t idCounter = 0;
/**
* \brief Creates a empty class
*/
LMDBAL::CursorCommon::CursorCommon ():
id(0),
state(closed),
handle(nullptr),
storage(nullptr)
{}
/**
* \brief Creates a cursor
*
* \param[in] _storage a storage that created this cursor
*/
LMDBAL::CursorCommon::CursorCommon (StorageCommon* _storage):
id(++idCounter),
state(closed),
handle(nullptr),
storage(_storage)
{}
/**
* \brief Moves other cursor into this class
*
* \param[in] other other instance that is being moved
*/
LMDBAL::CursorCommon::CursorCommon (CursorCommon&& other):
id(other.id),
state(other.state),
handle(other.handle),
storage(other.storage)
{
other.dropped();
if (state == openedPublic)
storage->attachCursorToTransaction(id, handle, this);
}
/**
* \brief Destroys this cursor
*
* If the cursor wasn't properly closed - it's going to be upon destruction
*/
LMDBAL::CursorCommon::~CursorCommon () noexcept {
close();
}
/**
* \brief Move assignment operator
*
* Transfers other cursor into this one
*/
LMDBAL::CursorCommon& LMDBAL::CursorCommon::operator = (CursorCommon&& other) {
terminated();
id = other.id;
state = other.state;
handle = other.handle;
storage = other.storage;
other.reset();
if (state == openedPublic)
storage->attachCursorToTransaction(id, handle, this);
return *this;
}
/**
* \brief A private method that turns cursor into an empty one
*
* This method is called from LMDBAL::Storage, when the cursor is getting destoryed.
* After this method cursors will become empty, and can't be used anymore
*/
void LMDBAL::CursorCommon::reset () {
id = 0;
state = closed;
handle = nullptr;
storage = nullptr;
}
/**
* \brief A private method that turns cursor into an empty one
*
* This function is called from LMDBAL::Storage, when it gets destroyed, but still has some valid cursors.
* Those cursors will become empty, and can't be used anymore
*/
void LMDBAL::CursorCommon::dropped () {
terminated();
reset();
}
/**
* \brief A private function called to inform the cursor he has been terminated
*
* Is expected to be called from transaction, database, storage or move constructor
*/
void LMDBAL::CursorCommon::terminated () {
switch (state) {
case openedPublic:
storage->_mdbCursorClose(handle);
state = closed;
break;
case openedPrivate:
storage->closeCursorTransaction(handle, true);
state = closed;
break;
default:
break;
}
}
/**
* \brief Termiates a sequence of operations with the cursor
*
* This is a normal way to tell that you're done with the cursor and don't want to continue the sequence of queries.
* The state of the cursor is lost after calling this method, some inner resorce is freed.
*
* If the cursor was opened with the private transaction - the owner storage will be notified of the aborted transaction.
*
* This function does nothing on a closed cursor.
*/
void LMDBAL::CursorCommon::close () {
switch (state) {
case openedPublic:
storage->disconnectCursorFromTransaction(id, handle);
storage->_mdbCursorClose(handle);
state = closed;
break;
case openedPrivate:
storage->closeCursorTransaction(handle, true);
state = closed;
break;
default:
break;
}
}
/**
* \brief Opens the cursor for operations.
*
* This is a normal way to start the sequence of operations with the cursor.
* This variant of the function creates a read only transaction just for this cursor
*
* This function should be called when the LMDBAL::Storage is already opened and before any query with this cursor!
* It will do nothing to a cursor that was already opened (no matter what way).
*
* \exception LMDBAL::Closed thrown if you try to open the cursor on a closed database
* \exception LMDBAL::Unknown thrown if there was a problem opening the cursor by the lmdb, or to begin a transaction
* \exception LMDBAL::CursorEmpty thrown if the cursor was empty
*/
void LMDBAL::CursorCommon::open () {
if (empty())
throw CursorEmpty(openCursorMethodName);
switch (state) {
case closed:
storage->openCursorTransaction(&handle, false);
state = openedPrivate;
break;
default:
break;
}
}
/**
* \brief Opens the cursor for operations.
*
* This is a normal way to start the sequence of operations with the cursor.
* This variant of the function uses for queries a transaction you have obtained somewhere else.
*
* This function should be called when the LMDBAL::Storage is already opened and before any query with this cursor!
* It will do nothing to a cursor that was already opened (no matter what way).
*
* \param[in] transaction - a transaction, can be read only
*
* \exception LMDBAL::Closed thrown if you try to open the cursor on a closed database
* \exception LMDBAL::Unknown thrown if there was a problem opening the cursor by the lmdb
* \exception LMDBAL::TransactionTerminated thrown if the passed transaction not active, any action with it's inner ID is an error
* \exception LMDBAL::CursorEmpty thrown if the cursor was empty
*/
void LMDBAL::CursorCommon::open (const Transaction& transaction) {
if (empty())
throw CursorEmpty(openCursorMethodName);
storage->ensureOpened(openCursorMethodName);
TransactionID txn = storage->extractTransactionId(transaction, openCursorMethodName);
switch (state) {
case closed: {
int result = storage->_mdbCursorOpen(txn, &handle);
if (result != MDB_SUCCESS)
storage->throwUnknown(result);
storage->attachCursorToTransaction(id, handle, this);
state = openedPublic;
} break;
default:
break;
}
}
/**
* \brief Renews a cursor
*
* This function aborts current transaction if the cursor was opened with it's own transaction
* (does not mess up if the transaction was public),
* creates new private transaction and rebinds this cursor to it.
*
* Theoretically you could call this method if your public transaction was aborted (or commited)
* but you wish to continue to keep working with your cursor.
* Or if you just want to rebind your cursor to a new private transaction.
*
* This function does nothing if the cursor is closed
*
* \exception LMDBAL::Closed thrown if you try to renew the cursor on a closed database
* \exception LMDBAL::Unknown thrown if there was a problem beginning new transaction or if there was a problem renewing the cursor by lmdb
* \exception LMDBAL::CursorEmpty thrown if the cursor was empty
*/
void LMDBAL::CursorCommon::renew () {
if (empty())
throw CursorEmpty(openCursorMethodName);
storage->ensureOpened(renewCursorMethodName);
switch (state) {
case openedPrivate:
storage->closeCursorTransaction(handle, false);
storage->openCursorTransaction(&handle, true);
break;
case openedPublic:
storage->disconnectCursorFromTransaction(id, handle);
storage->openCursorTransaction(&handle, true);
state = openedPrivate;
break;
default:
break;
}
}
/**
* \brief Renews a cursor
*
* This function aborts current transaction if the cursor was opened with it's own transaction
* (does not mess up if the transaction was public),
* and rebinds this cursor to a passed new transaction.
*
* Theoretically you could call this method if your previous public transaction was aborted (or commited)
* but you wish to continue to keep working with your cursor.
* Or if you just want to rebind your cursor to another public transaction.
*
* This function does nothing if the cursor is closed
*
* \param[in] transaction - a transaction you wish this cursor to be bound to
*
* \exception LMDBAL::Closed thrown if you try to renew the cursor on a closed database
* \exception LMDBAL::Unknown thrown if there was a problem renewing the cursor by lmdb
* \exception LMDBAL::TransactionTerminated thrown if the passed transaction not active, any action with it's inner ID is an error
* \exception LMDBAL::CursorEmpty thrown if the cursor was empty
*/
void LMDBAL::CursorCommon::renew (const Transaction& transaction) {
if (empty())
throw CursorEmpty(openCursorMethodName);
storage->ensureOpened(renewCursorMethodName);
TransactionID txn = storage->extractTransactionId(transaction, renewCursorMethodName);
switch (state) {
case openedPrivate: {
storage->closeCursorTransaction(handle, false);
int result = storage->_mdbCursorRenew(txn, handle);
if (result != MDB_SUCCESS)
storage->throwUnknown(result);
storage->attachCursorToTransaction(id, handle, this);
state = openedPublic;
} break;
case openedPublic: {
storage->disconnectCursorFromTransaction(id, handle);
int result = storage->_mdbCursorRenew(txn, handle);
if (result != MDB_SUCCESS)
storage->throwUnknown(result);
storage->attachCursorToTransaction(id, handle, this);
} break;
default:
break;
}
}
/**
* \brief Returns true if the cursor is empty
*
* Empty cursors can't be used, they can be only targets of move operations
*/
bool LMDBAL::CursorCommon::empty () const {
return id == 0;
}
/**
* \brief Tells if the cursor is open
*/
bool LMDBAL::CursorCommon::opened () const {
return state != closed;
}

90
src/cursorcommon.h Normal file
View File

@ -0,0 +1,90 @@
/*
* LMDB Abstraction Layer.
* Copyright (C) 2023 Yury Gubich <blue@macaw.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stdint.h>
#include <string>
#include <lmdb.h>
namespace LMDBAL {
class Transaction;
class StorageCommon;
class CursorCommon {
friend class Transaction;
protected:
enum State { /**<Cursor state:*/
closed, /**< - closed*/
openedPublic, /**< - opened with public transaction, all storages will be notified about it after it's done*/
openedPrivate /**< - opened with private transaction, only current storage will be notified when cursor is closed*/
};
protected:
CursorCommon();
CursorCommon(StorageCommon* storage);
CursorCommon(const CursorCommon& other) = delete;
CursorCommon(CursorCommon&& other);
virtual ~CursorCommon() noexcept;
CursorCommon& operator = (const CursorCommon& other) = delete;
CursorCommon& operator = (CursorCommon&& other);
public:
void open();
void open(const Transaction& transaction);
void renew();
void renew(const Transaction& transaction);
bool opened() const;
bool empty() const;
void close();
protected:
void terminated();
void dropped();
void reset();
protected:
uint32_t id;
State state;
MDB_cursor* handle;
StorageCommon* storage;
private:
inline static const std::string openCursorMethodName = "Cursor::open"; /**<\brief member function name, just for exceptions*/
inline static const std::string closeCursorMethodName = "Cursor::close"; /**<\brief member function name, just for exceptions*/
inline static const std::string renewCursorMethodName = "Cursor::renew"; /**<\brief member function name, just for exceptions*/
protected:
inline static const std::string firstMethodName = "first"; /**<\brief member function name, just for exceptions in heir*/
inline static const std::string lastMethodName = "last"; /**<\brief member function name, just for exceptions in heir*/
inline static const std::string nextMethodName = "next"; /**<\brief member function name, just for exceptions in heir*/
inline static const std::string prevMethodName = "prev"; /**<\brief member function name, just for exceptions in heir*/
inline static const std::string currentMethodName = "current"; /**<\brief member function name, just for exceptions in heir*/
inline static const std::string setMethodName = "set"; /**<\brief member function name, just for exceptions in heir*/
inline static const std::string firstOperationName = "Cursor::first"; /**<\brief member function name, just for exceptions in heir*/
inline static const std::string lastOperationName = "Cursor::last"; /**<\brief member function name, just for exceptions in heir*/
inline static const std::string nextOperationName = "Cursor::next"; /**<\brief member function name, just for exceptions in heir*/
inline static const std::string prevOperationName = "Cursor::prev"; /**<\brief member function name, just for exceptions in heir*/
inline static const std::string currentOperationName = "Cursor::current"; /**<\brief member function name, just for exceptions in heir*/
};
}

View File

@ -16,8 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LMDBAL_EXCEPTIONS_H
#define LMDBAL_EXCEPTIONS_H
#pragma once
#include <stdexcept>
#include <string>
@ -237,5 +236,3 @@ private:
};
}
#endif //LMDBAL_EXCEPTIONS_H

View File

@ -16,8 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LMDBAL_OPERATORS_HPP
#define LMDBAL_OPERATORS_HPP
#pragma once
#include <map>
#include <set>
@ -209,5 +208,3 @@ QDataStream& operator >> (QDataStream &in, std::list<K>& container) {
return in;
}
#endif //LMDBAL_OPERATORS_HPP

View File

@ -16,4 +16,4 @@ set(HEADERS
serializer_qbytearray.hpp
)
install(FILES ${HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_LOW})
install(FILES ${HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${LMDBAL_NAME_LOW})

View File

@ -16,8 +16,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LMDBAL_SERIALIZER_H
#define LMDBAL_SERIALIZER_H
#pragma once
#include <cstring>
@ -68,5 +68,3 @@ private:
#include "serializer_stdstring.hpp"
#include "serializer_qstring.hpp"
#include "serializer_qbytearray.hpp"
#endif // LMDBAL_SERIALIZER_H

View File

@ -16,8 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LMDBAL_SERIALIZER_HPP
#define LMDBAL_SERIALIZER_HPP
#pragma once
#include "serializer.h"
@ -159,5 +158,3 @@ MDB_val LMDBAL::Serializer<T>::getData() {
return val;
}
#endif //LMDBAL_SERIALIZER_HPP

View File

@ -16,8 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LMDBAL_SERIALIZER_DOUBLE_HPP
#define LMDBAL_SERIALIZER_DOUBLE_HPP
#pragma once
namespace LMDBAL {
@ -53,7 +52,5 @@ private:
}
#endif //LMDBAL_SERIALIZER_DOUBLE_HPP

View File

@ -16,8 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LMDBAL_SERIALIZER_FLOAT_HPP
#define LMDBAL_SERIALIZER_FLOAT_HPP
#pragma once
namespace LMDBAL {
@ -52,8 +51,3 @@ private:
};
}
#endif //LMDBAL_SERIALIZER_FLOAT_HPP

View File

@ -16,8 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LMDBAL_SERIALIZER_INT16_HPP
#define LMDBAL_SERIALIZER_INT16_HPP
#pragma once
#include <stdint.h>
@ -54,5 +53,3 @@ private:
};
}
#endif //LMDBAL_SERIALIZER_INT16_HPP

View File

@ -16,8 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LMDBAL_SERIALIZER_INT32_HPP
#define LMDBAL_SERIALIZER_INT32_HPP
#pragma once
#include <stdint.h>
@ -54,8 +53,3 @@ private:
};
}
#endif //LMDBAL_SERIALIZER_INT32_HPP

View File

@ -16,8 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LMDBAL_SERIALIZER_INT64_HPP
#define LMDBAL_SERIALIZER_INT64_HPP
#pragma once
#include <stdint.h>
@ -54,8 +53,3 @@ private:
};
}
#endif //LMDBAL_SERIALIZER_INT64_HPP

View File

@ -16,8 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LMDBAL_SERIALIZER_INT8_HPP
#define LMDBAL_SERIALIZER_INT8_HPP
#pragma once
#include <stdint.h>
@ -54,8 +53,3 @@ private:
};
}
#endif //LMDBAL_SERIALIZER_INT8_HPP

View File

@ -16,8 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LMDBAL_SERIALIZER_QBYTEARRAY_HPP
#define LMDBAL_SERIALIZER_QBYTEARRAY_HPP
#pragma once
#include <QByteArray>
@ -56,9 +55,3 @@ private:
};
}
#endif //LMDBAL_SERIALIZER_QBYTEARRAY_HPP

View File

@ -16,8 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LMDBAL_SERIALIZER_QSTRING_HPP
#define LMDBAL_SERIALIZER_QSTRING_HPP
#pragma once
#include <QString>
#include <QByteArray>
@ -56,10 +55,3 @@ private:
};
}
#endif //LMDBAL_SERIALIZER_QSTRING_HPP

View File

@ -16,8 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LMDBAL_SERIALIZER_STDSTRING_HPP
#define LMDBAL_SERIALIZER_STDSTRING_HPP
#pragma once
#include <string>
@ -54,9 +53,3 @@ private:
};
}
#endif //LMDBAL_SERIALIZER_STDSTRING_HPP

View File

@ -16,8 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LMDBAL_SERIALIZER_UINT16_HPP
#define LMDBAL_SERIALIZER_UINT16_HPP
#pragma once
#include <stdint.h>
@ -54,7 +53,3 @@ private:
};
}
#endif //LMDBAL_SERIALIZER_UINT16_HPP

View File

@ -16,8 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LMDBAL_SERIALIZER_UINT32_HPP
#define LMDBAL_SERIALIZER_UINT32_HPP
#pragma once
#include <stdint.h>
@ -54,5 +53,3 @@ private:
};
}
#endif //LMDBAL_SERIALIZER_UINT32_HPP

View File

@ -16,8 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LMDBAL_SERIALIZER_UINT64_HPP
#define LMDBAL_SERIALIZER_UINT64_HPP
#pragma once
#include <stdint.h>
@ -54,6 +53,3 @@ private:
};
}
#endif //LMDBAL_SERIALIZER_UINT64_HPP

View File

@ -16,8 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LMDBAL_SERIALIZER_UINT8_HPP
#define LMDBAL_SERIALIZER_UINT8_HPP
#pragma once
#include <stdint.h>
@ -54,8 +53,3 @@ private:
};
}
#endif //LMDBAL_SERIALIZER_UINT8_HPP

View File

@ -16,8 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LMDBAL_STORAGE_H
#define LMDBAL_STORAGE_H
#pragma once
#include <type_traits>
#include <cstring>
@ -26,6 +25,7 @@
#include "serializer.h"
#include "cursor.h"
#include "transaction.h"
#include "storagecommon.h"
class BaseTest;
class DuplicatesTest;
@ -34,105 +34,8 @@ class StorageCursorTest;
namespace LMDBAL {
class iStorage {
friend class Base;
public:
protected:
iStorage(Base* parent, const std::string& name, bool duplicates = false);
virtual ~iStorage();
/**
* \brief A private virtual function I need to open each storage in the database
*
* \param[in] transaction - lmdb transaction to call <a class="el" href="http://www.lmdb.tech/doc/group__mdb.html#gac08cad5b096925642ca359a6d6f0562a">mdb_dbi_open</a>
* \returns MDB_SUCCESS if everything went smooth or MDB_<error> -like error code
*/
virtual int open(MDB_txn * transaction) = 0;
virtual void close();
virtual void handleDrop();
bool isDBOpened() const;
const std::string& dbName() const;
void ensureOpened(const std::string& methodName) const;
void throwDuplicateOrUnknown(int rc, const std::string& key) const;
void throwDuplicateOrUnknown(int rc, TransactionID txn, const std::string& key) const;
void throwNotFoundOrUnknown(int rc, const std::string& key) const;
void throwNotFoundOrUnknown(int rc, TransactionID txn, const std::string& key) const;
void throwUnknown(int rc, TransactionID txn) const;
void throwUnknown(int rc) const;
void throwUnknown(const std::string& message) const;
void throwDuplicate(const std::string& key) const;
void throwNotFound(const std::string& key) const;
void throwCursorNotReady(const std::string& method) const;
TransactionID extractTransactionId(const Transaction& txn, const std::string& action = "") const;
TransactionID beginReadOnlyTransaction() const;
TransactionID beginTransaction() const;
void commitTransaction(TransactionID id);
void abortTransaction(TransactionID id) const;
virtual void transactionStarted(TransactionID txn, bool readOnly) const;
virtual void transactionCommited(TransactionID txn);
virtual void transactionAborted(TransactionID txn) const;
virtual int drop(TransactionID transaction);
virtual SizeType count(TransactionID txn) const;
int _mdbOpen(MDB_txn* txn, unsigned int flags = 0);
int _mdbPut(MDB_txn* txn, MDB_val& key, MDB_val& data, unsigned int flags = 0);
int _mdbGet(MDB_txn* txn, MDB_val& key, MDB_val& data) const;
int _mdbDel(MDB_txn* txn, MDB_val& key);
int _mdbDel(MDB_txn* txn, MDB_val& key, MDB_val& data);
int _mdbStat(MDB_txn* txn, MDB_stat& stat) const;
int _mdbFlags(MDB_txn* txn, uint32_t& flags) const;
int _mdbCursorOpen(MDB_txn* txn, MDB_cursor** cursor) const;
void _mdbCursorClose(MDB_cursor* cursor) const;
int _mdbCursorGet(MDB_cursor* cursor, MDB_val& key, MDB_val& data, MDB_cursor_op operation) const;
int _mdbCursorSet(MDB_cursor* cursor, MDB_val& key) const;
int _mdbCursorDel(MDB_cursor* cursor, unsigned int flags = 0);
int _mdbCursorPut(MDB_cursor* cursor, MDB_val& key, MDB_val& data, unsigned int flags = 0);
int _mdbCursorRenew(MDB_txn* txn, MDB_cursor* cursor) const;
MDB_txn* _mdbCursorTxn(MDB_cursor* cursor) const;
public:
virtual void drop();
virtual int drop(const WriteTransaction& txn);
virtual SizeType count() const;
virtual SizeType count(const Transaction& txn) const;
protected:
MDB_dbi dbi; /**<\brief lmdb storage handle*/
Base* db; /**<\brief parent database pointer (borrowed)*/
const std::string name; /**<\brief this storage name*/
const bool duplicates; /**<\brief true if storage supports duplicates*/
inline static const std::string dropMethodName = "drop"; /**<\brief member function name, just for exceptions*/
inline static const std::string countMethodName = "count"; /**<\brief member function name, just for exceptions*/
inline static const std::string flagsMethodName = "flags"; /**<\brief member function name, just for exceptions*/
inline static const std::string addRecordMethodName = "addRecord"; /**<\brief member function name, just for exceptions*/
inline static const std::string forceRecordMethodName = "forceRecord"; /**<\brief member function name, just for exceptions*/
inline static const std::string changeRecordMethodName = "changeRecord"; /**<\brief member function name, just for exceptions*/
inline static const std::string removeRecordMethodName = "removeRecord"; /**<\brief member function name, just for exceptions*/
inline static const std::string checkRecordMethodName = "checkRecord"; /**<\brief member function name, just for exceptions*/
inline static const std::string getRecordMethodName = "getRecord"; /**<\brief member function name, just for exceptions*/
inline static const std::string readAllMethodName = "readAllRecord"; /**<\brief member function name, just for exceptions*/
inline static const std::string replaceAllMethodName = "replaceAll"; /**<\brief member function name, just for exceptions*/
inline static const std::string addRecordsMethodName = "addRecords"; /**<\brief member function name, just for exceptions*/
protected:
template <class K, class V>
int makeStorage(MDB_txn* transaction, bool duplicates = false);
template <class T>
static std::string toString(const T& value);
};
template <class K, class V>
class Storage : public iStorage {
class Storage : public StorageCommon {
friend class ::BaseTest;
friend class ::DuplicatesTest;
friend class ::CacheCursorTest;
@ -160,7 +63,7 @@ protected:
virtual uint32_t addRecords(const std::map<K, V>& data, TransactionID txn, bool overwrite = false);
public:
using iStorage::drop;
using StorageCommon::drop;
virtual void addRecord(const K& key, const V& value);
virtual void addRecord(const K& key, const V& value, const WriteTransaction& txn);
virtual bool forceRecord(const K& key, const V& value); //returns true if there was addition, false if change
@ -199,5 +102,3 @@ protected:
}
#include "storage.hpp"
#endif //LMDBAL_STORAGE_H

View File

@ -16,8 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LMDBAL_STORAGE_HPP
#define LMDBAL_STORAGE_HPP
#pragma once
#include "storage.h"
#include "exceptions.h"
@ -47,7 +46,7 @@
*/
template<class K, class V>
LMDBAL::Storage<K, V>::Storage(Base* parent, const std::string& name, bool duplicates):
iStorage(parent, name, duplicates),
StorageCommon(parent, name, duplicates),
keySerializer(),
valueSerializer(),
cursors()
@ -1016,7 +1015,7 @@ void LMDBAL::Storage<K, V>::close() {
for (const std::pair<const uint32_t, Cursor<K, V>*>& pair : cursors)
pair.second->terminated();
iStorage::close();
StorageCommon::close();
}
/**
@ -1052,7 +1051,7 @@ void LMDBAL::Storage<K, V>::destroyCursor(LMDBAL::Cursor<K, V>& cursor) {
cursor.close();
cursors.erase(itr);
cursor.freed();
cursor.reset();
}
/**
@ -1104,83 +1103,3 @@ void LMDBAL::Storage<K, V>::discoveredRecord(const K& key, const V& value, Trans
UNUSED(value);
UNUSED(txn);
}
/**
* \brief A functiion to actually open <a class="el" href="http://www.lmdb.tech/doc/group__mdb.html#gadbe68a06c448dfb62da16443d251a78b">MDB_dbi</a> storage
*
* \tparam K type of keys in opening storage
*
* \param[in] transaction - lmdb transaction to call <a class="el" href="http://www.lmdb.tech/doc/group__mdb.html#gac08cad5b096925642ca359a6d6f0562a">mdb_dbi_open</a>, must be a writable transaction!
* \param[in] duplicates - true if key duplicates are allowed (false by default)
*
* \returns MDB_SUCCESS if everything went smooth or MDB_<error> -like error code
*
* This is a way to optimise database using MDB_INTEGERKEY flag,
* when the key is actually kind of an integer
* This infrastructure also allowes us to customize mdb_dbi_open call in the future
*/
template<class K, class V>
inline int LMDBAL::iStorage::makeStorage(MDB_txn* transaction, bool duplicates) {
unsigned int flags = MDB_CREATE;
if constexpr (std::is_integral<K>::value)
flags |= MDB_INTEGERKEY;
if (duplicates) {
flags |= MDB_DUPSORT;
if constexpr (std::is_scalar<V>::value)
flags |= MDB_DUPFIXED;
if constexpr (
std::is_same<V, uint32_t>::value ||
std::is_same<V, int32_t>::value ||
std::is_same<V, uint64_t>::value ||
std::is_same<V, int64_t>::value
) //for some reason lmdb breaks if it's not one of these types in MDB_DUPFIXED mode
flags |= MDB_INTEGERDUP;
}
return _mdbOpen(transaction, flags);
}
/**
* \brief A method to cast a value (which can be a value or a key) to string.
*
* This function is mainly used in exceptions, to report which key was duplicated or not found.
* You can define your own specializations to this function in case std::to_string doesn't cover your case
*
* \param[in] value a value that should be converted to string
* \returns a string presentation of value
*/
template<class T>
inline std::string LMDBAL::iStorage::toString(const T& value) {
return std::to_string(value);
}
/**
* \brief A method to cast a value (which can be a value or a key) to string.
*
* QString spectialization
*
* \param[in] value a value that should be converted to string
* \returns a string presentation of value
*/
template<>
inline std::string LMDBAL::iStorage::toString(const QString& value) {
return value.toStdString();
}
/**
* \brief A method to cast a value (which can be a value or a key) to string.
*
* std::string spectialization
*
* \param[in] value a value that should be converted to string
* \returns a string presentation of value
*/
template<>
inline std::string LMDBAL::iStorage::toString(const std::string& value) {
return value;
}
#endif //LMDBAL_STORAGE_HPP

View File

@ -16,7 +16,9 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "storage.h"
#include "storagecommon.h"
#include "cursorcommon.h"
#define UNUSED(x) (void)(x)
@ -37,7 +39,7 @@
* \param[in] name - the name of the storage
* \param[in] duplicates - true if key duplicates are allowed (false by default)
*/
LMDBAL::iStorage::iStorage(Base* parent, const std::string& name, bool duplicates):
LMDBAL::StorageCommon::StorageCommon(Base* parent, const std::string& name, bool duplicates):
dbi(),
db(parent),
name(name),
@ -47,12 +49,12 @@ LMDBAL::iStorage::iStorage(Base* parent, const std::string& name, bool duplicate
/**
* \brief Destroys a storage interface
*/
LMDBAL::iStorage::~iStorage() {}
LMDBAL::StorageCommon::~StorageCommon () {}
/**
* \brief A private virtual function I need to close each storage in the database
* \brief A private virtual function to close each storage in the database
*/
void LMDBAL::iStorage::close() {
void LMDBAL::StorageCommon::close() {
mdb_dbi_close(db->environment, dbi);
}
@ -67,7 +69,7 @@ void LMDBAL::iStorage::close() {
*
* \exception LMDBAL::TransactionTerminated thrown if the passed transaction not active, any action with it's inner ID is an error
*/
LMDBAL::TransactionID LMDBAL::iStorage::extractTransactionId(const Transaction& txn, const std::string& action) const {
LMDBAL::TransactionID LMDBAL::StorageCommon::extractTransactionId(const Transaction& txn, const std::string& action) const {
if (!txn.isActive())
throw TransactionTerminated(db->name, name, action);
@ -82,11 +84,11 @@ LMDBAL::TransactionID LMDBAL::iStorage::extractTransactionId(const Transaction&
* \exception LMDBAL::Closed thrown if the database was closed
* \exception LMDBAL::Unknown thrown if something unexpected happened
*/
void LMDBAL::iStorage::drop() {
void LMDBAL::StorageCommon::drop() {
ensureOpened(dropMethodName);
TransactionID txn = beginTransaction();
int rc = iStorage::drop(txn);
int rc = StorageCommon::drop(txn);
if (rc != MDB_SUCCESS) {
abortTransaction(txn);
throw Unknown(db->name, mdb_strerror(rc), name);
@ -104,7 +106,7 @@ void LMDBAL::iStorage::drop() {
* \param[in] transaction - transaction ID, must be writable transaction!
* \returns MDB_SUCCESS if everything went fine, MDB_<error> code otherwise
*/
int LMDBAL::iStorage::drop(TransactionID transaction) {
int LMDBAL::StorageCommon::drop(TransactionID transaction) {
return mdb_drop(transaction, dbi, 0);
}
@ -118,7 +120,7 @@ int LMDBAL::iStorage::drop(TransactionID transaction) {
*
* \exception LMDBAL::TransactionTerminated thrown if the transaction was not active
*/
int LMDBAL::iStorage::drop(const WriteTransaction& txn) {
int LMDBAL::StorageCommon::drop(const WriteTransaction& txn) {
ensureOpened(dropMethodName);
return drop(extractTransactionId(txn, dropMethodName));
}
@ -130,7 +132,7 @@ int LMDBAL::iStorage::drop(const WriteTransaction& txn) {
*
* \exception LMDBAL::Closed thrown if the database was closed
*/
void LMDBAL::iStorage::ensureOpened(const std::string& methodName) const {
void LMDBAL::StorageCommon::ensureOpened(const std::string& methodName) const {
if (!isDBOpened())
throw Closed(methodName, db->name, name);
}
@ -143,7 +145,7 @@ void LMDBAL::iStorage::ensureOpened(const std::string& methodName) const {
* \exception LMDBAL::Closed thrown if the database was closed
* \exception LMDBAL::Unknown thrown if something unexpected happened
*/
LMDBAL::SizeType LMDBAL::iStorage::count() const {
LMDBAL::SizeType LMDBAL::StorageCommon::count() const {
ensureOpened(countMethodName);
TransactionID txn = beginReadOnlyTransaction();
@ -159,6 +161,82 @@ LMDBAL::SizeType LMDBAL::iStorage::count() const {
return amount;
}
/**
* \brief Links cursor to the transaction it has been opened with
*
* This a service function is designed to be called by from LMDBAL::Cursor
* Cursor must be opened by a public transaction
*
* \exception std::out_of_range thrown if LMDBAL::Transaction pointer wasn't found in the LMDBAL::Base
*/
void LMDBAL::StorageCommon::attachCursorToTransaction (uint32_t cursorId, MDB_cursor* cursorHandle, CursorCommon* pointer) const {
TransactionID txnID = _mdbCursorTxn(cursorHandle);
Transaction* txn = db->transactions.at(txnID);
txn->cursors[cursorId] = pointer;
}
/**
* \brief Opens a transaction that is ment to be private to LMDBAL::Cursor, but public to the LMDBAL::Storage
*
* This method is ment to be called from LMDBAL::Cursor
*
* \param[out] cursor - cursor handle
* \param[in] renew - true if instead of opening cursor should be renewed
*
* \exception LMDBAL::Closed thrown if you try to open the cursor on a closed database
* \exception LMDBAL::Unknown thrown if there was a problem opening the cursor by the lmdb, or to begin a transaction
*/
void LMDBAL::StorageCommon::openCursorTransaction (MDB_cursor** cursor, bool renew) const {
ensureOpened(openCursorTransactionMethodName);
TransactionID txn = beginReadOnlyTransaction();
int result;
if (renew)
result = _mdbCursorRenew(txn, *cursor);
else
result = _mdbCursorOpen(txn, cursor);
if (result != MDB_SUCCESS)
throwUnknown(result, txn);
transactionStarted(txn, true);
}
/**
* \brief Closes transaction that is private to LMDBAL::Cursor, but public to the LMDBAL::Storage
*
* This method is ment to be called from LMDBAL::Cursor
*
* \param[in] cursor - cursor handle
* \param[in] closeCursor - true if the cursor should also get closed, false if you wish to leave it open
*/
void LMDBAL::StorageCommon::closeCursorTransaction (MDB_cursor* cursor, bool closeCursor) const {
TransactionID txn = _mdbCursorTxn(cursor);
if (closeCursor)
_mdbCursorClose(cursor);
abortTransaction(txn);
transactionAborted(txn);
}
/**
* \brief Disconnects cursor from the transaction it has been opened with
*
* This a service function is designed to be called by from LMDBAL::Cursor
* Cursor must be still opened by a public transaction
*
* \exception std::out_of_range thrown if LMDBAL::Transaction pointer wasn't found in the LMDBAL::Base
*/
void LMDBAL::StorageCommon::disconnectCursorFromTransaction (uint32_t cursorId, MDB_cursor* cursorHandle) const {
TransactionID txnID = _mdbCursorTxn(cursorHandle);
Transaction* txn = db->transactions.at(txnID);
txn->cursors.erase(cursorId);
}
/**
* \brief Storage size (private transaction variant)
*
@ -167,7 +245,7 @@ LMDBAL::SizeType LMDBAL::iStorage::count() const {
*
* \exception LMDBAL::Unknown thrown if something unexpected happened
*/
LMDBAL::SizeType LMDBAL::iStorage::count(TransactionID txn) const {
LMDBAL::SizeType LMDBAL::StorageCommon::count(TransactionID txn) const {
MDB_stat stat;
int rc = mdb_stat(txn, dbi, &stat);
if (rc != MDB_SUCCESS)
@ -186,7 +264,7 @@ LMDBAL::SizeType LMDBAL::iStorage::count(TransactionID txn) const {
* \exception LMDBAL::Unknown thrown if something unexpected happened
* \exception LMDBAL::TransactionTerminated thrown if the passed transaction not active, any action with it's inner ID is an error
*/
LMDBAL::SizeType LMDBAL::iStorage::count(const Transaction& txn) const {
LMDBAL::SizeType LMDBAL::StorageCommon::count(const Transaction& txn) const {
ensureOpened(countMethodName);
return count(extractTransactionId(txn, countMethodName));
}
@ -203,7 +281,7 @@ LMDBAL::SizeType LMDBAL::iStorage::count(const Transaction& txn) const {
* \exception LMDBAL::Exist thrown if rc == MDB_KEYEXIST
* \exception LMDBAL::Unknown thrown if rc != MDB_KEYEXIST
*/
void LMDBAL::iStorage::throwDuplicateOrUnknown(int rc, TransactionID txn, const std::string& key) const {
void LMDBAL::StorageCommon::throwDuplicateOrUnknown(int rc, TransactionID txn, const std::string& key) const {
abortTransaction(txn);
throwDuplicateOrUnknown(rc, key);
}
@ -220,7 +298,7 @@ void LMDBAL::iStorage::throwDuplicateOrUnknown(int rc, TransactionID txn, const
* \exception LMDBAL::NotFound thrown if rc == MDB_NOTFOUND
* \exception LMDBAL::Unknown thrown if rc != MDB_NOTFOUND
*/
void LMDBAL::iStorage::throwNotFoundOrUnknown(int rc, LMDBAL::TransactionID txn, const std::string& key) const {
void LMDBAL::StorageCommon::throwNotFoundOrUnknown(int rc, LMDBAL::TransactionID txn, const std::string& key) const {
abortTransaction(txn);
throwNotFoundOrUnknown(rc, key);
}
@ -236,7 +314,7 @@ void LMDBAL::iStorage::throwNotFoundOrUnknown(int rc, LMDBAL::TransactionID txn,
* \exception LMDBAL::Exist thrown if rc == MDB_KEYEXIST
* \exception LMDBAL::Unknown thrown if rc != MDB_KEYEXIST
*/
void LMDBAL::iStorage::throwDuplicateOrUnknown(int rc, const std::string& key) const {
void LMDBAL::StorageCommon::throwDuplicateOrUnknown(int rc, const std::string& key) const {
if (rc == MDB_KEYEXIST)
throwDuplicate(key);
else
@ -254,7 +332,7 @@ void LMDBAL::iStorage::throwDuplicateOrUnknown(int rc, const std::string& key) c
* \exception LMDBAL::NotFound thrown if rc == MDB_NOTFOUND
* \exception LMDBAL::Unknown thrown if rc != MDB_NOTFOUND
*/
void LMDBAL::iStorage::throwNotFoundOrUnknown(int rc, const std::string& key) const {
void LMDBAL::StorageCommon::throwNotFoundOrUnknown(int rc, const std::string& key) const {
if (rc == MDB_NOTFOUND)
throwNotFound(key);
else
@ -271,7 +349,7 @@ void LMDBAL::iStorage::throwNotFoundOrUnknown(int rc, const std::string& key) co
*
* \exception LMDBAL::Unknown thrown everytime
*/
void LMDBAL::iStorage::throwUnknown(int rc, LMDBAL::TransactionID txn) const {
void LMDBAL::StorageCommon::throwUnknown(int rc, LMDBAL::TransactionID txn) const {
abortTransaction(txn);
throwUnknown(rc);
}
@ -283,7 +361,7 @@ void LMDBAL::iStorage::throwUnknown(int rc, LMDBAL::TransactionID txn) const {
*
* \returns database name
*/
const std::string & LMDBAL::iStorage::dbName() const {
const std::string & LMDBAL::StorageCommon::dbName() const {
return db->name;}
/**
@ -293,7 +371,7 @@ const std::string & LMDBAL::iStorage::dbName() const {
*
* \returns true if database is ipened, false otherwise
*/
bool LMDBAL::iStorage::isDBOpened() const {
bool LMDBAL::StorageCommon::isDBOpened() const {
return db->opened;}
/**
@ -305,7 +383,7 @@ bool LMDBAL::iStorage::isDBOpened() const {
*
* \exception LMDBAL::Unknown thrown everytime
*/
void LMDBAL::iStorage::throwUnknown(int rc) const {
void LMDBAL::StorageCommon::throwUnknown(int rc) const {
throw Unknown(db->name, mdb_strerror(rc), name);}
/**
@ -317,7 +395,7 @@ void LMDBAL::iStorage::throwUnknown(int rc) const {
*
* \exception LMDBAL::Unknown thrown everytime
*/
void LMDBAL::iStorage::throwUnknown(const std::string& message) const {
void LMDBAL::StorageCommon::throwUnknown(const std::string& message) const {
throw Unknown(db->name, message, name);}
/**
@ -329,7 +407,7 @@ void LMDBAL::iStorage::throwUnknown(const std::string& message) const {
*
* \exception LMDBAL::Exist thrown everytime
*/
void LMDBAL::iStorage::throwDuplicate(const std::string& key) const {
void LMDBAL::StorageCommon::throwDuplicate(const std::string& key) const {
throw Exist(key, db->name, name);}
/**
@ -341,7 +419,7 @@ void LMDBAL::iStorage::throwDuplicate(const std::string& key) const {
*
* \exception LMDBAL::NotFound thrown everytime
*/
void LMDBAL::iStorage::throwNotFound(const std::string& key) const {
void LMDBAL::StorageCommon::throwNotFound(const std::string& key) const {
throw NotFound(key, db->name, name);}
/**
@ -353,7 +431,7 @@ void LMDBAL::iStorage::throwNotFound(const std::string& key) const {
*
* \exception LMDBAL::CursorNotReady thrown everytime
*/
void LMDBAL::iStorage::throwCursorNotReady(const std::string& method) const {
void LMDBAL::StorageCommon::throwCursorNotReady(const std::string& method) const {
throw CursorNotReady(method, db->name, name);}
/**
@ -363,7 +441,7 @@ void LMDBAL::iStorage::throwCursorNotReady(const std::string& method) const {
*
* \returns read only transaction
*/
LMDBAL::TransactionID LMDBAL::iStorage::beginReadOnlyTransaction() const {
LMDBAL::TransactionID LMDBAL::StorageCommon::beginReadOnlyTransaction() const {
return db->beginPrivateReadOnlyTransaction(name);}
/**
@ -373,7 +451,7 @@ LMDBAL::TransactionID LMDBAL::iStorage::beginReadOnlyTransaction() const {
*
* \returns read only transaction
*/
LMDBAL::TransactionID LMDBAL::iStorage::beginTransaction() const {
LMDBAL::TransactionID LMDBAL::StorageCommon::beginTransaction() const {
return db->beginPrivateTransaction(name);}
/**
@ -381,7 +459,7 @@ LMDBAL::TransactionID LMDBAL::iStorage::beginTransaction() const {
*
* Ment to be called from heirs, name is reported to the database just to be displayed in std::exception::what() message
*/
void LMDBAL::iStorage::abortTransaction(LMDBAL::TransactionID id) const {
void LMDBAL::StorageCommon::abortTransaction(LMDBAL::TransactionID id) const {
db->abortPrivateTransaction(id, name);}
/**
@ -391,7 +469,7 @@ void LMDBAL::iStorage::abortTransaction(LMDBAL::TransactionID id) const {
*
* \exception LMDBAL::Unknown thrown if something unexpected happened
*/
void LMDBAL::iStorage::commitTransaction(LMDBAL::TransactionID id) {
void LMDBAL::StorageCommon::commitTransaction(LMDBAL::TransactionID id) {
db->commitPrivateTransaction(id, name);}
/**
@ -406,7 +484,7 @@ void LMDBAL::iStorage::commitTransaction(LMDBAL::TransactionID id) {
* \param[in] txn - ID of started transaction
* \param[in] readOnly - true if transaction is read-only, false otherwise
*/
void LMDBAL::iStorage::transactionStarted(LMDBAL::TransactionID txn, bool readOnly) const {
void LMDBAL::StorageCommon::transactionStarted(LMDBAL::TransactionID txn, bool readOnly) const {
UNUSED(txn);
UNUSED(readOnly);
}
@ -422,7 +500,7 @@ void LMDBAL::iStorage::transactionStarted(LMDBAL::TransactionID txn, bool readOn
*
* \param[in] txn - ID of started transaction
*/
void LMDBAL::iStorage::transactionCommited(LMDBAL::TransactionID txn) {
void LMDBAL::StorageCommon::transactionCommited(LMDBAL::TransactionID txn) {
UNUSED(txn);}
/**
@ -436,7 +514,7 @@ void LMDBAL::iStorage::transactionCommited(LMDBAL::TransactionID txn) {
*
* \param[in] txn - ID of started transaction
*/
void LMDBAL::iStorage::transactionAborted(LMDBAL::TransactionID txn) const {
void LMDBAL::StorageCommon::transactionAborted(LMDBAL::TransactionID txn) const {
UNUSED(txn);}
/**
@ -445,65 +523,65 @@ void LMDBAL::iStorage::transactionAborted(LMDBAL::TransactionID txn) const {
* It's a protected method that is called to optimise drop process
* after the transaction is commited. Used just for optimisations.
*/
void LMDBAL::iStorage::handleDrop() {}
void LMDBAL::StorageCommon::handleDrop() {}
int LMDBAL::iStorage::_mdbOpen(MDB_txn *txn, unsigned int flags) {
int LMDBAL::StorageCommon::_mdbOpen(MDB_txn *txn, unsigned int flags) {
return mdb_dbi_open(txn, name.c_str(), flags, &dbi);
}
int LMDBAL::iStorage::_mdbPut(MDB_txn* txn, MDB_val& key, MDB_val& data, unsigned int flags) {
int LMDBAL::StorageCommon::_mdbPut(MDB_txn* txn, MDB_val& key, MDB_val& data, unsigned int flags) {
return mdb_put(txn, dbi, &key, &data, flags);
}
int LMDBAL::iStorage::_mdbGet(MDB_txn* txn, MDB_val& key, MDB_val& data) const {
int LMDBAL::StorageCommon::_mdbGet(MDB_txn* txn, MDB_val& key, MDB_val& data) const {
return mdb_get(txn, dbi, &key, &data);
}
int LMDBAL::iStorage::_mdbDel(MDB_txn* txn, MDB_val& key) {
int LMDBAL::StorageCommon::_mdbDel(MDB_txn* txn, MDB_val& key) {
return mdb_del(txn, dbi, &key, NULL);
}
int LMDBAL::iStorage::_mdbDel(MDB_txn* txn, MDB_val& key, MDB_val& data) {
int LMDBAL::StorageCommon::_mdbDel(MDB_txn* txn, MDB_val& key, MDB_val& data) {
return mdb_del(txn, dbi, &key, &data);
}
int LMDBAL::iStorage::_mdbStat(MDB_txn* txn, MDB_stat& stat) const {
int LMDBAL::StorageCommon::_mdbStat(MDB_txn* txn, MDB_stat& stat) const {
return mdb_stat(txn, dbi, &stat);
}
int LMDBAL::iStorage::_mdbFlags(MDB_txn* txn, uint32_t& flags) const {
int LMDBAL::StorageCommon::_mdbFlags(MDB_txn* txn, uint32_t& flags) const {
return mdb_dbi_flags(txn, dbi, &flags);
}
int LMDBAL::iStorage::_mdbCursorOpen(MDB_txn* txn, MDB_cursor** cursor) const {
int LMDBAL::StorageCommon::_mdbCursorOpen(MDB_txn* txn, MDB_cursor** cursor) const {
return mdb_cursor_open(txn, dbi, cursor);
}
void LMDBAL::iStorage::_mdbCursorClose(MDB_cursor *cursor) const {
void LMDBAL::StorageCommon::_mdbCursorClose(MDB_cursor *cursor) const {
mdb_cursor_close(cursor);
}
int LMDBAL::iStorage::_mdbCursorGet(MDB_cursor* cursor, MDB_val& key, MDB_val& data, MDB_cursor_op operation) const {
int LMDBAL::StorageCommon::_mdbCursorGet(MDB_cursor* cursor, MDB_val& key, MDB_val& data, MDB_cursor_op operation) const {
return mdb_cursor_get(cursor, &key, &data, operation);
}
int LMDBAL::iStorage::_mdbCursorSet(MDB_cursor* cursor, MDB_val& key) const {
int LMDBAL::StorageCommon::_mdbCursorSet(MDB_cursor* cursor, MDB_val& key) const {
return mdb_cursor_get(cursor, &key, NULL, MDB_SET);
}
int LMDBAL::iStorage::_mdbCursorDel(MDB_cursor* cursor, unsigned int flags) {
int LMDBAL::StorageCommon::_mdbCursorDel(MDB_cursor* cursor, unsigned int flags) {
return mdb_cursor_del(cursor, flags);
}
int LMDBAL::iStorage::_mdbCursorPut(MDB_cursor* cursor, MDB_val& key, MDB_val& data, unsigned int flags) {
int LMDBAL::StorageCommon::_mdbCursorPut(MDB_cursor* cursor, MDB_val& key, MDB_val& data, unsigned int flags) {
return mdb_cursor_put(cursor, &key, &data, flags);
}
int LMDBAL::iStorage::_mdbCursorRenew(MDB_txn* txn, MDB_cursor* cursor) const {
int LMDBAL::StorageCommon::_mdbCursorRenew(MDB_txn* txn, MDB_cursor* cursor) const {
return mdb_cursor_renew(txn, cursor);
}
MDB_txn* LMDBAL::iStorage::_mdbCursorTxn(MDB_cursor* cursor) const {
MDB_txn* LMDBAL::StorageCommon::_mdbCursorTxn(MDB_cursor* cursor) const {
return mdb_cursor_txn(cursor);
}

139
src/storagecommon.h Normal file
View File

@ -0,0 +1,139 @@
/*
* LMDB Abstraction Layer.
* Copyright (C) 2023 Yury Gubich <blue@macaw.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <string>
#include <stdint.h>
#include <lmdb.h>
#include "base.h"
#include "transaction.h"
namespace LMDBAL {
class CursorCommon;
class StorageCommon {
friend class Base;
friend class CursorCommon;
public:
protected:
StorageCommon(Base* parent, const std::string& name, bool duplicates = false);
virtual ~ StorageCommon();
/**
* \brief A private virtual function I need to open each storage in the database
*
* \param[in] transaction - lmdb transaction to call <a class="el" href="http://www.lmdb.tech/doc/group__mdb.html#gac08cad5b096925642ca359a6d6f0562a">mdb_dbi_open</a>
* \returns MDB_SUCCESS if everything went smooth or MDB_<error> -like error code
*/
virtual int open(MDB_txn * transaction) = 0;
virtual void close();
virtual void handleDrop();
bool isDBOpened() const;
const std::string& dbName() const;
void ensureOpened(const std::string& methodName) const;
void throwDuplicateOrUnknown(int rc, const std::string& key) const;
void throwDuplicateOrUnknown(int rc, TransactionID txn, const std::string& key) const;
void throwNotFoundOrUnknown(int rc, const std::string& key) const;
void throwNotFoundOrUnknown(int rc, TransactionID txn, const std::string& key) const;
void throwUnknown(int rc, TransactionID txn) const;
void throwUnknown(int rc) const;
void throwUnknown(const std::string& message) const;
void throwDuplicate(const std::string& key) const;
void throwNotFound(const std::string& key) const;
void throwCursorNotReady(const std::string& method) const;
TransactionID extractTransactionId(const Transaction& txn, const std::string& action = "") const;
TransactionID beginReadOnlyTransaction() const;
TransactionID beginTransaction() const;
void commitTransaction(TransactionID id);
void abortTransaction(TransactionID id) const;
virtual void transactionStarted(TransactionID txn, bool readOnly) const;
virtual void transactionCommited(TransactionID txn);
virtual void transactionAborted(TransactionID txn) const;
virtual int drop(TransactionID transaction);
virtual SizeType count(TransactionID txn) const;
void openCursorTransaction(MDB_cursor** cursor, bool renew = false) const;
void closeCursorTransaction(MDB_cursor* cursor, bool closeCursor = false) const;
void attachCursorToTransaction(uint32_t cursorId, MDB_cursor* cursorHandle, CursorCommon* pointer) const;
void disconnectCursorFromTransaction(uint32_t cursorId, MDB_cursor* cursorHandle) const;
int _mdbOpen(MDB_txn* txn, unsigned int flags = 0);
int _mdbPut(MDB_txn* txn, MDB_val& key, MDB_val& data, unsigned int flags = 0);
int _mdbGet(MDB_txn* txn, MDB_val& key, MDB_val& data) const;
int _mdbDel(MDB_txn* txn, MDB_val& key);
int _mdbDel(MDB_txn* txn, MDB_val& key, MDB_val& data);
int _mdbStat(MDB_txn* txn, MDB_stat& stat) const;
int _mdbFlags(MDB_txn* txn, uint32_t& flags) const;
int _mdbCursorOpen(MDB_txn* txn, MDB_cursor** cursor) const;
void _mdbCursorClose(MDB_cursor* cursor) const;
int _mdbCursorGet(MDB_cursor* cursor, MDB_val& key, MDB_val& data, MDB_cursor_op operation) const;
int _mdbCursorSet(MDB_cursor* cursor, MDB_val& key) const;
int _mdbCursorDel(MDB_cursor* cursor, unsigned int flags = 0);
int _mdbCursorPut(MDB_cursor* cursor, MDB_val& key, MDB_val& data, unsigned int flags = 0);
int _mdbCursorRenew(MDB_txn* txn, MDB_cursor* cursor) const;
MDB_txn* _mdbCursorTxn(MDB_cursor* cursor) const;
public:
virtual void drop();
virtual int drop(const WriteTransaction& txn);
virtual SizeType count() const;
virtual SizeType count(const Transaction& txn) const;
protected:
MDB_dbi dbi; /**<\brief lmdb storage handle*/
Base* db; /**<\brief parent database pointer (borrowed)*/
const std::string name; /**<\brief this storage name*/
const bool duplicates; /**<\brief true if storage supports duplicates*/
inline static const std::string dropMethodName = "drop"; /**<\brief member function name, just for exceptions*/
inline static const std::string countMethodName = "count"; /**<\brief member function name, just for exceptions*/
inline static const std::string flagsMethodName = "flags"; /**<\brief member function name, just for exceptions*/
inline static const std::string addRecordMethodName = "addRecord"; /**<\brief member function name, just for exceptions*/
inline static const std::string forceRecordMethodName = "forceRecord"; /**<\brief member function name, just for exceptions*/
inline static const std::string changeRecordMethodName = "changeRecord"; /**<\brief member function name, just for exceptions*/
inline static const std::string removeRecordMethodName = "removeRecord"; /**<\brief member function name, just for exceptions*/
inline static const std::string checkRecordMethodName = "checkRecord"; /**<\brief member function name, just for exceptions*/
inline static const std::string getRecordMethodName = "getRecord"; /**<\brief member function name, just for exceptions*/
inline static const std::string readAllMethodName = "readAllRecord"; /**<\brief member function name, just for exceptions*/
inline static const std::string replaceAllMethodName = "replaceAll"; /**<\brief member function name, just for exceptions*/
inline static const std::string addRecordsMethodName = "addRecords"; /**<\brief member function name, just for exceptions*/
inline static const std::string openCursorTransactionMethodName = "openCursorTransaction"; /**<\brief member function name, just for exceptions*/
protected:
template <class K, class V>
int makeStorage(MDB_txn* transaction, bool duplicates = false);
template <class T>
static std::string toString(const T& value);
};
}
#include "storagecommon.hpp"

99
src/storagecommon.hpp Normal file
View File

@ -0,0 +1,99 @@
/*
* LMDB Abstraction Layer.
* Copyright (C) 2023 Yury Gubich <blue@macaw.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "storagecommon.h"
/**
* \brief A functiion to actually open <a class="el" href="http://www.lmdb.tech/doc/group__mdb.html#gadbe68a06c448dfb62da16443d251a78b">MDB_dbi</a> storage
*
* \tparam K type of keys in opening storage
*
* \param[in] transaction - lmdb transaction to call <a class="el" href="http://www.lmdb.tech/doc/group__mdb.html#gac08cad5b096925642ca359a6d6f0562a">mdb_dbi_open</a>, must be a writable transaction!
* \param[in] duplicates - true if key duplicates are allowed (false by default)
*
* \returns MDB_SUCCESS if everything went smooth or MDB_<error> -like error code
*
* This is a way to optimise database using MDB_INTEGERKEY flag,
* when the key is actually kind of an integer
* This infrastructure also allowes us to customize mdb_dbi_open call in the future
*/
template<class K, class V>
inline int LMDBAL::StorageCommon::makeStorage(MDB_txn* transaction, bool duplicates) {
unsigned int flags = MDB_CREATE;
if constexpr (std::is_integral<K>::value)
flags |= MDB_INTEGERKEY;
if (duplicates) {
flags |= MDB_DUPSORT;
if constexpr (std::is_scalar<V>::value)
flags |= MDB_DUPFIXED;
if constexpr (
std::is_same<V, uint32_t>::value ||
std::is_same<V, int32_t>::value ||
std::is_same<V, uint64_t>::value ||
std::is_same<V, int64_t>::value
) //for some reason lmdb breaks if it's not one of these types in MDB_DUPFIXED mode
flags |= MDB_INTEGERDUP;
}
return _mdbOpen(transaction, flags);
}
/**
* \brief A method to cast a value (which can be a value or a key) to string.
*
* This function is mainly used in exceptions, to report which key was duplicated or not found.
* You can define your own specializations to this function in case std::to_string doesn't cover your case
*
* \param[in] value a value that should be converted to string
* \returns a string presentation of value
*/
template<class T>
inline std::string LMDBAL::StorageCommon::toString(const T& value) {
return std::to_string(value);
}
/**
* \brief A method to cast a value (which can be a value or a key) to string.
*
* QString spectialization
*
* \param[in] value a value that should be converted to string
* \returns a string presentation of value
*/
template<>
inline std::string LMDBAL::StorageCommon::toString(const QString& value) {
return value.toStdString();
}
/**
* \brief A method to cast a value (which can be a value or a key) to string.
*
* std::string spectialization
*
* \param[in] value a value that should be converted to string
* \returns a string presentation of value
*/
template<>
inline std::string LMDBAL::StorageCommon::toString(const std::string& value) {
return value;
}

View File

@ -1,5 +1,25 @@
/*
* LMDB Abstraction Layer.
* Copyright (C) 2023 Yury Gubich <blue@macaw.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "transaction.h"
#include "cursorcommon.h"
/**
* \class LMDBAL::Transaction
* \brief Public read only transaction
@ -22,7 +42,8 @@
LMDBAL::Transaction::Transaction():
txn(nullptr),
active(false),
parent(nullptr)
parent(nullptr),
cursors()
{}
/**
@ -31,8 +52,11 @@ LMDBAL::Transaction::Transaction():
LMDBAL::Transaction::Transaction(TransactionID txn, const Base* parent) :
txn(txn),
active(true),
parent(parent)
{}
parent(parent),
cursors()
{
parent->transactions[txn] = this;
}
/**
* \brief Moves transaction to a new object
@ -40,9 +64,14 @@ LMDBAL::Transaction::Transaction(TransactionID txn, const Base* parent) :
LMDBAL::Transaction::Transaction(Transaction&& other):
txn(other.txn),
active(other.active),
parent(other.parent)
parent(other.parent),
cursors(other.cursors)
{
other.active = false;
if (active) {
parent->transactions[txn] = this;
other.reset();
}
}
/**
@ -56,13 +85,21 @@ LMDBAL::Transaction::~Transaction() {
* \brief Move-assigns transaction to the new object
*/
LMDBAL::Transaction& LMDBAL::Transaction::operator=(Transaction&& other) {
if (this == &other)
return *this;
terminate();
txn = other.txn;
active = other.active;
parent = other.parent;
cursors = other.cursors;
other.active = false;
if (active) {
parent->transactions[txn] = this;
other.reset();
}
return *this;
}
@ -74,11 +111,32 @@ LMDBAL::Transaction& LMDBAL::Transaction::operator=(Transaction&& other) {
*/
void LMDBAL::Transaction::terminate() {
if (active) {
closeCursors();
parent->abortTransaction(txn);
active = false;
parent->transactions.erase(txn);
reset();
}
}
/**
* \brief Resets inner transaction properties to inactive state
*/
void LMDBAL::Transaction::reset() {
active = false;
txn = nullptr;
parent = nullptr;
cursors.clear();
}
/**
* \brief Closes attached curors;
*/
void LMDBAL::Transaction::closeCursors () {
for (const std::pair<const uint32_t, CursorCommon*>& pair : cursors)
pair.second->terminated();
}
/**
* \brief Returns transaction states
*
@ -132,6 +190,12 @@ LMDBAL::WriteTransaction::WriteTransaction(WriteTransaction&& other):
Transaction(std::move(other))
{}
LMDBAL::WriteTransaction& LMDBAL::WriteTransaction::operator=(WriteTransaction&& other) {
Transaction::operator=(std::move(other));
return *this;
}
/**
* \brief Aborts transaction cancelling all changes
*
@ -148,7 +212,9 @@ void LMDBAL::WriteTransaction::abort() {
*/
void LMDBAL::WriteTransaction::commit() {
if (active) {
closeCursors();
const_cast<Base*>(parent)->commitTransaction(txn);
active = false;
parent->transactions.erase(txn);
reset();
}
}

View File

@ -1,13 +1,32 @@
/*
* LMDB Abstraction Layer.
* Copyright (C) 2023 Yury Gubich <blue@macaw.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "base.h"
namespace LMDBAL {
class iStorage;
class StorageCommon;
class CursorCommon;
class Transaction {
friend class Base;
friend class iStorage;
friend class StorageCommon;
public:
explicit Transaction();
explicit Transaction(Transaction&& other);
@ -21,11 +40,14 @@ public:
protected:
Transaction(TransactionID txn, const Base* parent);
void reset();
void closeCursors();
protected:
TransactionID txn; /**<\brief Transaction inner handler*/
bool active; /**<\brief Transaction state*/
const Base* parent; /**<\brief Pointer to the database this transaction belongs to*/
std::map<uint32_t, CursorCommon*> cursors; /**<\brief a collection of cursors curently opened under this transaction*/
};
class WriteTransaction : public Transaction {
@ -35,6 +57,7 @@ public:
explicit WriteTransaction(WriteTransaction&& other);
WriteTransaction(const WriteTransaction& other) = delete;
WriteTransaction& operator = (const WriteTransaction& other) = delete;
WriteTransaction& operator = (WriteTransaction&& other);
void commit();
void abort();

View File

@ -20,7 +20,7 @@ target_include_directories(runUnitTests PRIVATE ${Qt${QT_VERSION_MAJOR}Core_INCL
target_link_libraries(
runUnitTests
GTest::gtest_main
${PROJECT_NAME}
${LMDBAL_NAME}
Qt${QT_VERSION_MAJOR}::Core
)
include(GoogleTest)

View File

@ -113,10 +113,9 @@ TEST_F(BaseTest, AddingKeysToCache) {
EXPECT_EQ(db->ready(), true);
c1->addRecord(2, "blah balah");
c1->addRecord(-4, "testing goes brrr");
c1->addRecord(140, "whatever");
c1->addRecord(40, "whatever");
c1->addRecord(-37, "aaaaa tss tsss tsss tsss aaaaaaa");
EXPECT_EQ(c1->getRecord(140), "whatever");
EXPECT_EQ(c1->getRecord(-116), "whatever");
EXPECT_EQ(c1->getRecord(40), "whatever");
}
TEST_F(BaseTest, AddingKeysToVariableCache) {
@ -189,8 +188,8 @@ TEST_F(BaseTest, Persistence) {
EXPECT_EQ(t2->count(), t2Size);
EXPECT_EQ(c1->count(), c1Size);
EXPECT_EQ(c1->checkRecord(-116), true);
EXPECT_EQ(c1->getRecord(-116), "whatever");
EXPECT_EQ(c1->checkRecord(40), true);
EXPECT_EQ(c1->getRecord(40), "whatever");
EXPECT_EQ(c1->checkRecord(-4), true);
EXPECT_EQ(c1->getRecord(-4), "testing goes brrr");
EXPECT_EQ(c1->getRecord(-4), "testing goes brrr");

View File

@ -402,7 +402,7 @@ TEST_F(CacheCursorTest, CursorRAIIBehaviour) {
TEST_F(CacheCursorTest, CornerCases) {
transaction.terminate();
EXPECT_THROW(cursor.current(), LMDBAL::Unknown);
EXPECT_THROW(cursor.current(), LMDBAL::CursorNotReady);
cursor.close();
LMDBAL::Cursor<uint64_t, std::string> emptyCursor = emptyCache->createCursor();
@ -436,5 +436,40 @@ TEST_F(CacheCursorTest, CornerCases) {
EXPECT_EQ(element.first, reference->first);
EXPECT_EQ(element.second, reference->second);
EXPECT_THROW(cursor.prev(), LMDBAL::NotFound);
cursor.close();
}
TEST_F(CacheCursorTest, TerminatedTransaction) {
LMDBAL::Cursor<uint64_t, std::string> cr = cache->createCursor();
{
LMDBAL::Transaction txn = db->beginReadOnlyTransaction();
cr.open(txn);
EXPECT_NO_THROW(cr.first());
}
EXPECT_FALSE(cr.opened());
LMDBAL::Transaction txn2;
{
LMDBAL::Transaction txn = db->beginReadOnlyTransaction();
EXPECT_TRUE(txn.isActive());
EXPECT_FALSE(txn2.isActive());
cr.open(txn);
EXPECT_TRUE(cr.opened());
txn2 = std::move(txn);
EXPECT_FALSE(txn.isActive());
EXPECT_TRUE(txn2.isActive());
EXPECT_TRUE(cr.opened());
}
EXPECT_TRUE(txn2.isActive());
EXPECT_TRUE(cr.opened());
txn2.terminate();
EXPECT_FALSE(txn2.isActive());
EXPECT_FALSE(cr.opened());
}

View File

@ -182,7 +182,7 @@ TEST_F(CacheTransactionsTest, ConcurentModification) {
int pid = fork();
if (pid == 0) { // I am the child
usleep(1);
usleep(5);
std::cout << "beggining second transaction" << std::endl;
LMDBAL::WriteTransaction txn2 = db->beginTransaction(); //<--- this is where the execution should pause
//and wait for the first transaction to get finished
@ -208,7 +208,7 @@ TEST_F(CacheTransactionsTest, ConcurentModification) {
LMDBAL::WriteTransaction txn1 = db->beginTransaction();
std::cout << "putting parent thread to sleep for 5 ms" << std::endl;
usleep(5);
usleep(10);
std::cout << "adding first transaction value" << std::endl;
c1->addRecord(5, 812, txn1);
@ -235,7 +235,7 @@ TEST_F(CacheTransactionsTest, RAIIResourceFree) {
int pid = fork();
if (pid == 0) { // I am the child
usleep(1);
usleep(5);
std::cout << "beggining child transaction" << std::endl;
LMDBAL::WriteTransaction txn2 = db->beginTransaction(); //<--- this is where the execution should pause
//and wait for the first transaction to get finished
@ -256,7 +256,7 @@ TEST_F(CacheTransactionsTest, RAIIResourceFree) {
LMDBAL::WriteTransaction txn1 = db->beginTransaction();
std::cout << "putting parent thread to sleep for 5 ms" << std::endl;
usleep(5);
usleep(10);
std::cout << "parent thread woke up" << std::endl;
std::cout << "adding value from parent thread" << std::endl;
@ -279,3 +279,26 @@ TEST_F(CacheTransactionsTest, RAIIResourceFree) {
EXPECT_EQ(c1->getRecord(221), 14);
}
TEST_F(CacheTransactionsTest, TransactionTerminationOnClose) {
LMDBAL::WriteTransaction txn = db->beginTransaction();
c1->addRecord(578, 4552, txn);
EXPECT_EQ(c1->getRecord(578, txn), 4552);
EXPECT_EQ(c1->checkRecord(578), false);
db->close();
db->open();
EXPECT_EQ(txn.isActive(), false);
EXPECT_THROW(c1->getRecord(578, txn), LMDBAL::TransactionTerminated);
EXPECT_NO_THROW(txn.commit());
EXPECT_EQ(c1->checkRecord(578), false);
txn = db->beginTransaction();
c1->addRecord(578, 4552, txn);
txn.commit();
EXPECT_EQ(c1->getRecord(578), 4552);
}

View File

@ -130,7 +130,10 @@ TEST_F(DuplicatesTest, Adding) {
tu3->addRecord(7.20001, 4.00000001); //not sure how exactly, but it works
EXPECT_EQ(tu3->count(), 7);
EXPECT_EQ(tu3->getRecord(7.2), -113);
std::set<float> res72({-113, -53.5478, 697, 4, 4.00000001});
EXPECT_EQ(res72.count(tu3->getRecord(7.2)), 1);
float tu3dd = tu3->getRecord(5119);
EXPECT_TRUE(tu3ds == tu3dd);
EXPECT_EQ(tu3ds, tu3dd);
@ -144,8 +147,11 @@ TEST_F(DuplicatesTest, Adding) {
EXPECT_THROW(tu4->addRecord(327, 79.624923), LMDBAL::Exist);
EXPECT_EQ(tu4->count(), 4);
EXPECT_EQ(tu4->getRecord(172), 0.00000001);
EXPECT_EQ(tu4->getRecord(327), 463.28348); //since they are not int's they are compared sort of lexicographically
std::set<double> res327({463.28348, 79.624923});
std::set<double> res172({0.00001, 0.00000001});
EXPECT_EQ(res172.count(tu4->getRecord(172)), 1);
EXPECT_EQ(res327.count(tu4->getRecord(327)), 1);
tu5->addRecord(-84.7, 45656753);
EXPECT_THROW(tu5->addRecord(-84.7, 45656753), LMDBAL::Exist);
@ -174,13 +180,19 @@ TEST_F(DuplicatesTest, Forcing) {
tu1->addRecord(-56, 71);
tu1->addRecord(-56, 274);
tu1->addRecord(-56, 732);
std::set<uint16_t> res56({71, 274, 732});
EXPECT_EQ(tu1->count(), tu1Size += 3);
EXPECT_TRUE(tu1->forceRecord(-56, 322));
res56.insert(322);
EXPECT_EQ(tu1->count(), tu1Size += 1);
EXPECT_EQ(tu1->getRecord(-56), 274); //like yeah, it's really counterintuitive, since it's compared byte by byte
EXPECT_EQ(res56.count(tu1->getRecord(-56)), 1);
res56.insert(14);
EXPECT_TRUE(tu1->forceRecord(-56, 14));
EXPECT_EQ(tu1->count(), tu1Size += 1);
EXPECT_EQ(tu1->getRecord(-56), 14);
EXPECT_EQ(res56.count(tu1->getRecord(-56)), 1);
EXPECT_FALSE(tu1->forceRecord(-56, 274));
EXPECT_EQ(tu1->count(), tu1Size);
@ -201,26 +213,38 @@ TEST_F(DuplicatesTest, Forcing) {
tu3->addRecord(17.3, 93.21);
tu3->addRecord(17.3, 6.6);
tu3->addRecord(17.3, 105.1);
std::set<float> res17({93.21, 6.6, 105.1});
EXPECT_EQ(tu3->count(), tu3Size += 3);
EXPECT_TRUE(tu3->forceRecord(17.3, 74.9));
res17.insert(74.9);
EXPECT_EQ(tu3->count(), tu3Size += 1);
EXPECT_EQ(tu3->getRecord(17.3), 105.1f); //here too, really one should not use this function with duplicates,
EXPECT_TRUE(tu3->forceRecord(17.3, 5.1)); //unless he wishes for kinda randomish result
EXPECT_EQ(res17.count(tu3->getRecord(17.3)), 1);
EXPECT_TRUE(tu3->forceRecord(17.3, 5.1));
res17.insert(5.1);
EXPECT_EQ(tu3->count(), tu3Size += 1);
EXPECT_EQ(tu3->getRecord(17.3), 5.1f);
EXPECT_EQ(res17.count(tu3->getRecord(17.3)), 1);
EXPECT_FALSE(tu3->forceRecord(17.3, 93.21));
EXPECT_EQ(tu3->count(), tu3Size);
LMDBAL::SizeType tu4Size = tu4->count();
tu4->addRecord(84, -359.109);
tu4->addRecord(84, 2879.654);
std::set<double> res84({-359.109, 2879.654});
EXPECT_EQ(tu4->count(), tu4Size += 2);
EXPECT_TRUE(tu4->forceRecord(84, 72.9));
res84.insert(72.9);
EXPECT_EQ(tu4->count(), tu4Size += 1);
EXPECT_EQ(tu4->getRecord(84), 2879.654);
EXPECT_EQ(res84.count(tu4->getRecord(84)), 1);
EXPECT_TRUE(tu4->forceRecord(84, 2679.5));
res84.insert(2679.5);
EXPECT_EQ(tu4->count(), tu4Size += 1);
EXPECT_EQ(tu4->getRecord(84), 2679.5);
EXPECT_EQ(res84.count(tu4->getRecord(84)), 1);
EXPECT_FALSE(tu4->forceRecord(84, -359.109));
EXPECT_EQ(tu4->count(), tu4Size);
@ -286,6 +310,7 @@ TEST_F(DuplicatesTest, Changing) {
EXPECT_THROW(tu2->changeRecord("jeremy spins", -7), LMDBAL::Exist);
LMDBAL::SizeType tu3Size = tu3->count();
std::set<float> res26;
EXPECT_THROW(tu3->changeRecord(26.7, 68.22), LMDBAL::NotFound);
EXPECT_EQ(tu3->count(), tu3Size);
tu3->addRecord(26.7, 68.22);
@ -294,11 +319,13 @@ TEST_F(DuplicatesTest, Changing) {
tu3->changeRecord(26.7, 68.22); //should just do nothing usefull, but work normally
EXPECT_EQ(tu3->getRecord(26.7), 68.22f);
tu3->changeRecord(26.7, 23.18);
res26.insert(23.18);
EXPECT_EQ(tu3->count(), tu3Size);
EXPECT_EQ(tu3->getRecord(26.7), 23.18f);
tu3->addRecord(26.7, 22.16);
res26.insert(22.16);
EXPECT_EQ(tu3->count(), tu3Size += 1);
EXPECT_EQ(tu3->getRecord(26.7), 23.18f);
EXPECT_EQ(res26.count(tu3->getRecord(26.7)), 1);
tu3->changeRecord(26.7, 21.7);
EXPECT_EQ(tu3->count(), tu3Size);
EXPECT_EQ(tu3->getRecord(26.7), 21.7f);
@ -316,17 +343,21 @@ TEST_F(DuplicatesTest, Changing) {
tu4->changeRecord(852, 6795.349); //should just do nothing usefull, but work normally
EXPECT_EQ(tu4->getRecord(852), 6795.349);
tu4->changeRecord(852, 13.54);
std::set<double> res852({13.54});
EXPECT_EQ(tu4->count(), tu4Size);
EXPECT_EQ(tu4->getRecord(852), 13.54);
tu4->addRecord(852, 213.85);
res852.insert(213.85);
EXPECT_EQ(tu4->count(), tu4Size += 1);
EXPECT_EQ(tu4->getRecord(852), 13.54);
EXPECT_EQ(res852.count(tu4->getRecord(852)), 1);
tu4->changeRecord(852, 236.21);
res852.insert(236.21);
EXPECT_EQ(tu4->count(), tu4Size);
EXPECT_EQ(tu4->getRecord(852), 236.21);
tu4->changeRecord(852, 46324.1135);
res852.insert(46324.1135);
EXPECT_EQ(tu4->count(), tu4Size);
EXPECT_EQ(tu4->getRecord(852), 213.85);
EXPECT_EQ(res852.count(tu4->getRecord(852)), 1);
EXPECT_THROW(tu4->changeRecord(852, 46324.1135), LMDBAL::Exist);
}

View File

@ -380,7 +380,7 @@ TEST_F(StorageCursorTest, CursorRAIIBehaviour) {
TEST_F(StorageCursorTest, CornerCases) {
EXPECT_EQ(getTableCursorsSize(), 1);
transaction.terminate();
EXPECT_THROW(cursor.current(), LMDBAL::Unknown);
EXPECT_THROW(cursor.current(), LMDBAL::CursorNotReady);
cursor.close();
LMDBAL::Cursor<uint64_t, std::string> emptyCursor = emptyTable->createCursor();
@ -414,4 +414,40 @@ TEST_F(StorageCursorTest, CornerCases) {
EXPECT_EQ(element.first, reference->first);
EXPECT_EQ(element.second, reference->second);
EXPECT_THROW(cursor.prev(), LMDBAL::NotFound);
cursor.close();
}
TEST_F(StorageCursorTest, TerminatedTransaction) {
LMDBAL::Cursor<uint64_t, std::string> cr = table->createCursor();
{
LMDBAL::Transaction txn = db->beginReadOnlyTransaction();
cr.open(txn);
EXPECT_NO_THROW(cr.first());
}
EXPECT_FALSE(cr.opened());
LMDBAL::Transaction txn2;
{
LMDBAL::Transaction txn = db->beginReadOnlyTransaction();
EXPECT_TRUE(txn.isActive());
EXPECT_FALSE(txn2.isActive());
cr.open(txn);
EXPECT_TRUE(cr.opened());
txn2 = std::move(txn);
EXPECT_FALSE(txn.isActive());
EXPECT_TRUE(txn2.isActive());
EXPECT_TRUE(cr.opened());
}
EXPECT_TRUE(txn2.isActive());
EXPECT_TRUE(cr.opened());
txn2.terminate();
EXPECT_FALSE(txn2.isActive());
EXPECT_FALSE(cr.opened());
}

View File

@ -181,7 +181,7 @@ TEST_F(StorageTransactionsTest, ConcurentModification) {
int pid = fork();
if (pid == 0) { // I am the child
usleep(1);
usleep(5);
std::cout << "beggining second transaction" << std::endl;
LMDBAL::WriteTransaction txn2 = db->beginTransaction(); //<--- this is where the execution should pause
//and wait for the first transaction to get finished
@ -207,7 +207,7 @@ TEST_F(StorageTransactionsTest, ConcurentModification) {
LMDBAL::WriteTransaction txn1 = db->beginTransaction();
std::cout << "putting parent thread to sleep for 5 ms" << std::endl;
usleep(5);
usleep(10);
std::cout << "adding first transaction value" << std::endl;
t1->addRecord(5, 812, txn1);
@ -234,7 +234,7 @@ TEST_F(StorageTransactionsTest, RAIIResourceFree) {
int pid = fork();
if (pid == 0) { // I am the child
usleep(1);
usleep(5);
std::cout << "beggining child transaction" << std::endl;
LMDBAL::WriteTransaction txn2 = db->beginTransaction(); //<--- this is where the execution should pause
//and wait for the first transaction to get finished
@ -255,7 +255,7 @@ TEST_F(StorageTransactionsTest, RAIIResourceFree) {
LMDBAL::WriteTransaction txn1 = db->beginTransaction();
std::cout << "putting parent thread to sleep for 5 ms" << std::endl;
usleep(5);
usleep(10);
std::cout << "parent thread woke up" << std::endl;
std::cout << "adding value from parent thread" << std::endl;
@ -277,3 +277,27 @@ TEST_F(StorageTransactionsTest, RAIIResourceFree) {
std::cout << "checking the final result" << std::endl;
EXPECT_EQ(t1->getRecord(221), 14);
}
TEST_F(StorageTransactionsTest, TransactionTerminationOnClose) {
LMDBAL::WriteTransaction txn = db->beginTransaction();
t1->addRecord(543, 229, txn);
EXPECT_EQ(t1->getRecord(543, txn), 229);
EXPECT_EQ(t1->checkRecord(543), false);
db->close();
db->open();
EXPECT_EQ(txn.isActive(), false);
EXPECT_THROW(t1->getRecord(543, txn), LMDBAL::TransactionTerminated);
EXPECT_NO_THROW(txn.commit());
EXPECT_EQ(t1->checkRecord(543), false);
txn = db->beginTransaction();
t1->addRecord(543, 229, txn);
txn.commit();
EXPECT_EQ(t1->getRecord(543), 229);
}