Compare commits
56 Commits
Author | SHA1 | Date | |
---|---|---|---|
75aafd2750 | |||
4fd3799c19 | |||
def2b8bc4d | |||
fc55ec6737 | |||
3282524c8b | |||
c60bb10923 | |||
9baade7b8c | |||
deded0c4e1 | |||
a09030cd4e | |||
d2a68ef5ee | |||
8ad684144f | |||
a6ea9bedc5 | |||
dcf2d289dc | |||
f638228d24 | |||
6dddd06f93 | |||
d62eddc47e | |||
79240aa535 | |||
6b475f615e | |||
77ba8f9e7b | |||
3307860ca6 | |||
96d7d9ef64 | |||
a0eebc978d | |||
0079f6e96e | |||
6b348023bb | |||
de741eda21 | |||
a9aa6b549f | |||
fbcf94d1c2 | |||
275406df61 | |||
de210b44f5 | |||
dbbc46e7c9 | |||
a0288727e1 | |||
a19a141611 | |||
af07ca2795 | |||
a9be6be54f | |||
bcfc9c0dc2 | |||
e3759f1dbe | |||
f3242e2665 | |||
9611431295 | |||
437b65df2f | |||
c36230b252 | |||
0d39a613c6 | |||
beab78a863 | |||
f3a82acdbd | |||
a32c35910b | |||
2d40692560 | |||
f00f017b16 | |||
180c40370c | |||
06e1aca45a | |||
f0727aa73d | |||
7b26d57ab6 | |||
161a850088 | |||
d57d27f952 | |||
8ff5672655 | |||
8cb1e97e30 | |||
5fba60f7f0 | |||
69bf1fcc3d |
79
.gitea/workflows/aur.yml
Normal file
79
.gitea/workflows/aur.yml
Normal file
@ -0,0 +1,79 @@
|
||||
name: Release to AUR
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
package-name:
|
||||
required: true
|
||||
type: string
|
||||
version:
|
||||
required: true
|
||||
type: string
|
||||
description:
|
||||
required: true
|
||||
type: string
|
||||
depends:
|
||||
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"
|
||||
version:
|
||||
description: "The version of the published package"
|
||||
type: string
|
||||
default: "1.0.0"
|
||||
description:
|
||||
description: "The description of the package"
|
||||
type: string
|
||||
default: "LMDB Abstraction Layer"
|
||||
depends:
|
||||
description: "Additional dependencies"
|
||||
type: string
|
||||
default: ""
|
||||
|
||||
jobs:
|
||||
aur:
|
||||
name: Release ${{ 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 }}
|
||||
|
||||
|
||||
- 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=(${{ inputs.version }})" PKGBUILD
|
||||
sed -i "/pkgdesc=\"LMDB Abstraction Layer\"/c\pkgdesc=(\"${{ inputs.description }}\")" PKGBUILD
|
||||
sed -i "/depends=( 'lmdb' )/c\depends=( 'lmdb' ${{ inputs.depends }} ))" 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
|
39
.gitea/workflows/main.yml
Normal file
39
.gitea/workflows/main.yml
Normal file
@ -0,0 +1,39 @@
|
||||
name: Main LMDBAL workfow
|
||||
run-name: ${{ gitea.actor }} is running LMDBAL main workflow
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
Archlinux:
|
||||
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 BUILD_DOC_HTML=True -D BUILD_DOC_XML=True -D BUILD_DOC_MAN=True -D BUILD_DOXYGEN_AWESOME=True -D QT_VERSION_MAJOR=5
|
||||
|
||||
- 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
|
||||
with:
|
||||
host: ${{ secrets.DOMAIN_ROOT }}
|
||||
username: ${{ secrets.DEPLOY_USER_NAME }}
|
||||
key: ${{ secrets.DEPLOY_PRIVATE_KEY }}
|
||||
source: "build/doc/html/*,build/doc/xml/*,build/doc/man/*"
|
||||
target: "/srv/lmdbal/doc"
|
||||
strip_components: 2
|
46
.gitea/workflows/release.yml
Normal file
46
.gitea/workflows/release.yml
Normal file
@ -0,0 +1,46 @@
|
||||
name: LMDBAL Release workflow
|
||||
run-name: ${{ gitea.actor }} is running LMDBAL Release workflow on release ${{ gitea.event.release.tag_name }}
|
||||
on:
|
||||
release:
|
||||
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
|
||||
|
||||
- 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
|
||||
|
44
CHANGELOG.md
44
CHANGELOG.md
@ -1,5 +1,48 @@
|
||||
# Changelog
|
||||
|
||||
# LMDBAL 0.5.3 (November 14, 2023)
|
||||
### Improvements
|
||||
- Now you don't need to link agains lmdb, just linking against LMDBAL is enough
|
||||
|
||||
### Bug fixes
|
||||
- transaction error in LMDBAL::Cache::readAll
|
||||
|
||||
## LMDBAL 0.5.2 (November 01, 2023)
|
||||
### Improvements
|
||||
- RAII cursors
|
||||
- operation set for cursors
|
||||
|
||||
### Bug fixes
|
||||
- error beginning transaction is now correctly handled and doesn't segfault
|
||||
|
||||
## LMDBAL 0.5.1 (October 21, 2023)
|
||||
### Improvements
|
||||
- RAII transactions
|
||||
- reduced overhead for private transaction functions
|
||||
|
||||
### Bug fixes
|
||||
- bug fix with cache fallthough
|
||||
|
||||
## LMDBAL 0.5.0 (October 15, 2023)
|
||||
### New Features
|
||||
- duplicates support (only for table)
|
||||
|
||||
### Improvements
|
||||
- some more documentation
|
||||
- more tests
|
||||
|
||||
## LMDBAL 0.4.0 (August 13, 2023)
|
||||
### New Features
|
||||
- read only cursors
|
||||
|
||||
### Bug fixes
|
||||
- possible cache unsync
|
||||
- doxygen-awesome build bix
|
||||
|
||||
### Improvements
|
||||
- some more documentation
|
||||
- more tests
|
||||
|
||||
## LMDBAL 0.3.1 (April 14, 2023)
|
||||
### Bug fixes
|
||||
- build with qt5 now is possible again
|
||||
@ -7,6 +50,7 @@
|
||||
### Improvements
|
||||
- exception documentation
|
||||
|
||||
|
||||
## LMDBAL 0.3.0 (April 12, 2023)
|
||||
### New features
|
||||
- transaction functions
|
||||
|
@ -1,7 +1,7 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
project(LMDBAL
|
||||
VERSION 0.3.1
|
||||
VERSION 0.5.4
|
||||
DESCRIPTION "LMDB (Lightning Memory-Mapped Database Manager) Abstraction Layer"
|
||||
LANGUAGES CXX
|
||||
)
|
||||
@ -12,7 +12,9 @@ cmake_policy(SET CMP0079 NEW)
|
||||
|
||||
option(BUILD_STATIC "Builds library as static library" OFF)
|
||||
option(BUILD_TESTS "Builds tests" OFF)
|
||||
option(BUILD_DOC "Builds documentation" OFF)
|
||||
option(BUILD_DOC_MAN "Builds man page documentation" OFF)
|
||||
option(BUILD_DOC_HTML "Builds html documentation" OFF)
|
||||
option(BUILD_DOC_XML "Builds xml documentation" OFF)
|
||||
option(BUILD_DOXYGEN_AWESOME "Builds documentation alternative style" OFF)
|
||||
|
||||
include(GNUInstallDirs)
|
||||
@ -21,7 +23,7 @@ include(CMakePackageConfigHelpers)
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake")
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
|
||||
|
||||
if (NOT DEFINED QT_VERSION_MAJOR)
|
||||
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core)
|
||||
@ -54,14 +56,21 @@ target_compile_options(${PROJECT_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
|
||||
INTERFACE_${PROJECT_NAME}_MAJOR_VERSION 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
|
||||
)
|
||||
|
||||
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)
|
||||
endif()
|
||||
|
||||
add_subdirectory(src)
|
||||
if (BUILD_DOC)
|
||||
if (BUILD_DOC_MAN OR BUILD_DOC_HTML OR BUILD_DOC_XML)
|
||||
find_package(Doxygen)
|
||||
if (DOXYGEN_FOUND)
|
||||
add_subdirectory(doc)
|
||||
@ -74,14 +83,18 @@ if (BUILD_TESTS)
|
||||
add_subdirectory(test)
|
||||
endif ()
|
||||
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC
|
||||
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src>"
|
||||
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_LOW}>"
|
||||
target_include_directories(
|
||||
${PROJECT_NAME}
|
||||
PUBLIC
|
||||
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_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_link_libraries(${PROJECT_NAME} PRIVATE
|
||||
target_link_libraries(
|
||||
${PROJECT_NAME}
|
||||
Qt${QT_VERSION_MAJOR}::Core
|
||||
lmdb
|
||||
)
|
||||
@ -100,8 +113,7 @@ write_basic_package_version_file(
|
||||
|
||||
install(TARGETS ${PROJECT_NAME}
|
||||
EXPORT ${PROJECT_LOW}Targets
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/${PROJECT_LOW}
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_LOW}
|
||||
)
|
||||
|
||||
|
45
LICENSE.md
45
LICENSE.md
@ -1,4 +1,4 @@
|
||||
### GNU GENERAL PUBLIC LICENSE
|
||||
# GNU GENERAL PUBLIC LICENSE
|
||||
|
||||
Version 3, 29 June 2007
|
||||
|
||||
@ -8,7 +8,7 @@ Copyright (C) 2007 Free Software Foundation, Inc.
|
||||
Everyone is permitted to copy and distribute verbatim copies of this
|
||||
license document, but changing it is not allowed.
|
||||
|
||||
### Preamble
|
||||
## Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
@ -73,9 +73,9 @@ assures that patents cannot be used to render the program non-free.
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
### TERMS AND CONDITIONS
|
||||
## TERMS AND CONDITIONS
|
||||
|
||||
#### 0. Definitions.
|
||||
### 0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
@ -115,7 +115,7 @@ work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
#### 1. Source Code.
|
||||
### 1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work for
|
||||
making modifications to it. "Object code" means any non-source form of
|
||||
@ -156,7 +156,7 @@ regenerate automatically from other parts of the Corresponding Source.
|
||||
The Corresponding Source for a work in source code form is that same
|
||||
work.
|
||||
|
||||
#### 2. Basic Permissions.
|
||||
### 2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
@ -181,7 +181,7 @@ Conveying under any other circumstances is permitted solely under the
|
||||
conditions stated below. Sublicensing is not allowed; section 10 makes
|
||||
it unnecessary.
|
||||
|
||||
#### 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
### 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
@ -197,7 +197,7 @@ operation or modification of the work as a means of enforcing, against
|
||||
the work's users, your or third parties' legal rights to forbid
|
||||
circumvention of technological measures.
|
||||
|
||||
#### 4. Conveying Verbatim Copies.
|
||||
### 4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
@ -210,7 +210,7 @@ recipients a copy of this License along with the Program.
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
#### 5. Conveying Modified Source Versions.
|
||||
### 5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
@ -245,7 +245,7 @@ beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
#### 6. Conveying Non-Source Forms.
|
||||
### 6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms of
|
||||
sections 4 and 5, provided that you also convey the machine-readable
|
||||
@ -341,7 +341,7 @@ documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
#### 7. Additional Terms.
|
||||
### 7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
@ -400,7 +400,7 @@ Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions; the
|
||||
above requirements apply either way.
|
||||
|
||||
#### 8. Termination.
|
||||
### 8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
@ -428,7 +428,7 @@ this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
#### 9. Acceptance Not Required for Having Copies.
|
||||
### 9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or run
|
||||
a copy of the Program. Ancillary propagation of a covered work
|
||||
@ -439,7 +439,7 @@ modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
#### 10. Automatic Licensing of Downstream Recipients.
|
||||
### 10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
@ -464,7 +464,7 @@ rights granted under this License, and you may not initiate litigation
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
#### 11. Patents.
|
||||
### 11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
@ -533,7 +533,7 @@ Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
#### 12. No Surrender of Others' Freedom.
|
||||
### 12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
@ -546,7 +546,7 @@ from those to whom you convey the Program, the only way you could
|
||||
satisfy both those terms and this License would be to refrain entirely
|
||||
from conveying the Program.
|
||||
|
||||
#### 13. Use with the GNU Affero General Public License.
|
||||
### 13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
@ -557,7 +557,7 @@ but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
#### 14. Revised Versions of this License.
|
||||
### 14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions
|
||||
of the GNU General Public License from time to time. Such new versions
|
||||
@ -583,7 +583,7 @@ permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
#### 15. Disclaimer of Warranty.
|
||||
### 15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
@ -595,7 +595,7 @@ PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE
|
||||
DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR
|
||||
CORRECTION.
|
||||
|
||||
#### 16. Limitation of Liability.
|
||||
### 16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR
|
||||
@ -607,7 +607,7 @@ LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM
|
||||
TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER
|
||||
PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
#### 17. Interpretation of Sections 15 and 16.
|
||||
### 17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
@ -618,7 +618,7 @@ copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
### How to Apply These Terms to Your New Programs
|
||||
## How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
@ -673,3 +673,4 @@ library, you may consider it more useful to permit linking proprietary
|
||||
applications with the library. If this is what you want to do, use the
|
||||
GNU Lesser General Public License instead of this License. But first,
|
||||
please read <https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
|
||||
|
@ -7,10 +7,12 @@
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Qt 5 or higher
|
||||
- a compiler (c++ would do)
|
||||
- Qt 5 or higher (qt5-base would do)
|
||||
- lmdb
|
||||
- CMake 3.16 or higher
|
||||
- Doxygen (optional, for documentation)
|
||||
- gtest (optional, for tests)
|
||||
|
||||
### Using with CMake
|
||||
|
||||
@ -95,4 +97,4 @@ if you're in the same directory with it
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the GPLv3 License - see the [LICENSE.md](LICENSE.md) file for details
|
||||
This project is available under the GPL-3.0 License or any later version. The file [cmake/FindLMDB.cmake](cmake/FindLMDB.cmake) is available under the GPL-3.0 License only, - see the [LICENSE.md](LICENSE.md) file for details
|
||||
|
@ -1,28 +1,34 @@
|
||||
if (BUILD_DOC_HTML)
|
||||
set(DOXYGEN_GENERATE_HTML YES)
|
||||
endif()
|
||||
if (BUILD_DOC_MAN)
|
||||
set(DOXYGEN_GENERATE_MAN YES)
|
||||
endif()
|
||||
if (BUILD_DOC_XML)
|
||||
set(DOXYGEN_GENERATE_XML YES)
|
||||
endif()
|
||||
|
||||
if (BUILD_DOXYGEN_AWESOME)
|
||||
include(ExternalProject)
|
||||
ExternalProject_Add(doxygen-awesome-css
|
||||
GIT_REPOSITORY https://github.com/jothepro/doxygen-awesome-css.git
|
||||
GIT_TAG "v2.2.0"
|
||||
GIT_TAG "v2.2.1"
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND make
|
||||
BUILD_IN_SOURCE TRUE
|
||||
INSTALL_COMMAND make DESTDIR=${CMAKE_CURRENT_BINARY_DIR}/doxygen-awesome-css install
|
||||
INSTALL_COMMAND make PREFIX=${CMAKE_CURRENT_BINARY_DIR}/doxygen-awesome-css install
|
||||
)
|
||||
set (DOXYGEN_GENERATE_TREEVIEW YES)
|
||||
set (DOXYGEN_DISABLE_INDEX NO)
|
||||
set (DOXYGEN_FULL_SIDEBAR NO)
|
||||
set (DOXYGEN_HTML_EXTRA_STYLESHEET
|
||||
${CMAKE_CURRENT_BINARY_DIR}/doxygen-awesome-css/doxygen-awesome.css
|
||||
${CMAKE_CURRENT_BINARY_DIR}/doxygen-awesome-css/doxygen-awesome-sidebar-only.css
|
||||
${CMAKE_CURRENT_BINARY_DIR}/doxygen-awesome-css/doxygen-awesome-sidebar-only-darkmode-toggle.css
|
||||
${CMAKE_CURRENT_BINARY_DIR}/doxygen-awesome-css/share/doxygen-awesome-css/doxygen-awesome.css
|
||||
${CMAKE_CURRENT_BINARY_DIR}/doxygen-awesome-css/share/doxygen-awesome-css/doxygen-awesome-sidebar-only.css
|
||||
${CMAKE_CURRENT_BINARY_DIR}/doxygen-awesome-css/share/doxygen-awesome-css/doxygen-awesome-sidebar-only-darkmode-toggle.css
|
||||
custom.css
|
||||
)
|
||||
set (DOXYGEN_HTML_EXTRA_FILES
|
||||
${CMAKE_CURRENT_BINARY_DIR}/doxygen-awesome-css/doxygen-awesome-darkmode-toggle.js
|
||||
${CMAKE_CURRENT_BINARY_DIR}/doxygen-awesome-css/share/doxygen-awesome-css/doxygen-awesome-darkmode-toggle.js
|
||||
)
|
||||
set (DOXYGEN_FULL_SIDEBAR NO)
|
||||
set (DOXYGEN_HTML_COLORSTYLE "LIGHT")
|
||||
@ -36,12 +42,24 @@ doxygen_add_docs(
|
||||
ALL
|
||||
COMMENT "Generate man and html pages"
|
||||
)
|
||||
if (BUILD_DOC_MAN)
|
||||
install(DIRECTORY
|
||||
${CMAKE_CURRENT_BINARY_DIR}/man
|
||||
TYPE DOC
|
||||
)
|
||||
endif()
|
||||
if (BUILD_DOC_HTML)
|
||||
install(DIRECTORY
|
||||
${CMAKE_CURRENT_BINARY_DIR}/html
|
||||
TYPE DOC
|
||||
)
|
||||
endif()
|
||||
if (BUILD_DOC_XML)
|
||||
install(DIRECTORY
|
||||
${CMAKE_CURRENT_BINARY_DIR}/xml
|
||||
TYPE DOC
|
||||
)
|
||||
endif()
|
||||
|
||||
if (BUILD_DOXYGEN_AWESOME)
|
||||
add_dependencies(documentation doxygen-awesome-css)
|
||||
|
@ -1,12 +1,21 @@
|
||||
/*! \mainpage Getting Started
|
||||
*
|
||||
* Everything begins with a data nase represented by the class LMDBAL::Base.
|
||||
* Everything begins with a data base represented by the class LMDBAL::Base.
|
||||
* It repesents a collection of key-value storages that are going to be stored in a sigle data base directory.
|
||||
* To create a LMDBAL::Base you need to pick up a name of a directory that is going to be created on your machine.
|
||||
*
|
||||
* @code{.cpp}
|
||||
*
|
||||
* #include "base.h"
|
||||
*
|
||||
* //...
|
||||
*
|
||||
* LMDBAL::Base base("myDataBase");
|
||||
* @endcode
|
||||
*
|
||||
* LMDBAL::Base creates or opens existing directory with the given name in the location acquired with
|
||||
* <a class="el" href="https://doc.qt.io/qt-6/qstandardpaths.html">QStandardPaths</a>::<a class="el" href="https://doc.qt.io/qt-6/qstandardpaths.html#writableLocation">writableLocation</a>(<a class="el" href="https://doc.qt.io/qt-6/qstandardpaths.html">QStandardPaths</a>::<a class="el" href="https://doc.qt.io/qt-6/qstandardpaths.html#StandardLocation-enum">CacheLocation</a>)
|
||||
* so, the file system destination of your data would depend on the
|
||||
* so, the file system destination of your data depends on the
|
||||
* <a class="el" href="https://doc.qt.io/qt-6/qcoreapplication.html">QCoreApplication</a> configuration of your app.
|
||||
*
|
||||
* After you have created a LMDBAL::Base you probably want to obtain storage handlers.
|
||||
@ -15,17 +24,55 @@
|
||||
* <a class="el" href="https://en.cppreference.com/w/cpp/container/map">std::map</a>
|
||||
* to speed up the access.
|
||||
*
|
||||
* You can obtain handlers by calling LMDBAL::Base::addStorage(const std::string&) or LMDBAL::Base::addCache(const std::string& name).
|
||||
* @code{.cpp}
|
||||
*
|
||||
* #include "storage.h"
|
||||
* #include "cache.h"
|
||||
*
|
||||
* //...
|
||||
*
|
||||
* LMDBAL::Storage<uint32_t, uint32_t> storage = base.addStorage<uint32_t, uint32_t>("storage");
|
||||
* LMDBAL::Cache<int8_t, std::string> cache = base.addCache<int8_t, std::string>("cache");
|
||||
*
|
||||
* @endcode
|
||||
*
|
||||
* You can obtain handlers by calling LMDBAL::Base::addStorage() or LMDBAL::Base::addCache().
|
||||
* Note that the handlers still belong to the LMDBAL::Base and it's his responsibility to destroy them.
|
||||
* You are not obliged to save those handlers,
|
||||
* you can obtain them at any time later using methods LMDBAL::Base::getStorage(const std::string&) or LMDBAL::Base::getCache(const std::string&)
|
||||
* you can obtain them at any time later using methods LMDBAL::Base::getStorage() or LMDBAL::Base::getCache()
|
||||
* calling them with the same template types and names.
|
||||
*
|
||||
* @code{.cpp}
|
||||
*
|
||||
* //...
|
||||
*
|
||||
* base.open();
|
||||
*
|
||||
* @endcode
|
||||
*
|
||||
* After you have added all the storages you wanted it's time to open the data base with LMDBAL::Base::open().
|
||||
* At this point you are not allowed to add any more storages, otherwise LMDBAL::Opened exception will be thrown.
|
||||
* It's currently the limitation of this little library and I might solve it in the future.
|
||||
* Database will throw no exception if you will try to close the closed LMDBAL::Base or open again already opened one.
|
||||
* Also it will automatically close itself if you'll try to destoroy onpened LMDBAL::Base.
|
||||
*
|
||||
* @code{.cpp}
|
||||
*
|
||||
* //...
|
||||
*
|
||||
* storage->addRecord(54, 75);
|
||||
* cache->addRecord(9, "my value");
|
||||
*
|
||||
* uint32_t value1 = storage->getRecord(54); //75
|
||||
* std::string value2 = cache->getRecord(9); //"myValue"
|
||||
*
|
||||
* uint32_t count1 = storage->count(); //1
|
||||
* uint32_t count2 = cache->count(); //1
|
||||
*
|
||||
* storage->removeRecord(54);
|
||||
* cache->removeRecord(9);
|
||||
*
|
||||
* @endcode
|
||||
*
|
||||
* To discover how to store read and modify data take a look at LMDBAL::Storage and LMDBAL::Cache classes.
|
||||
*/
|
||||
|
@ -1,17 +1,17 @@
|
||||
# Maintainer: Yury Gubich <blue@macaw.me>
|
||||
pkgname=lmdbal
|
||||
pkgver=0.3.1
|
||||
pkgver=1.0.0
|
||||
pkgrel=1
|
||||
pkgdesc="LMDB Abstraction Layer, qt5 version"
|
||||
pkgdesc="LMDB Abstraction Layer"
|
||||
arch=('i686' 'x86_64')
|
||||
url="https://git.macaw.me/blue/lmdbal"
|
||||
license=('GPL3')
|
||||
depends=( 'lmdb' 'qt5-base')
|
||||
makedepends=('cmake>=3.16')
|
||||
depends=( 'lmdb' )
|
||||
makedepends=('cmake>=3.16' 'gcc')
|
||||
optdepends=()
|
||||
|
||||
source=("$pkgname-$pkgver.tar.gz::https://git.macaw.me/blue/lmdbal/archive/$pkgver.tar.gz")
|
||||
sha256sums=('df1a9687d81d609d160754285f2613d7e07fc6deb781d0fb0084e4857ea82e95')
|
||||
source=("$pkgname-$pkgver.tar.gz::https://git.macaw.me/blue/$pkgname/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
|
||||
|
@ -2,6 +2,7 @@ set(SOURCES
|
||||
exceptions.cpp
|
||||
storage.cpp
|
||||
base.cpp
|
||||
transaction.cpp
|
||||
)
|
||||
|
||||
set(HEADERS
|
||||
@ -9,29 +10,16 @@ set(HEADERS
|
||||
exceptions.h
|
||||
storage.h
|
||||
storage.hpp
|
||||
cursor.h
|
||||
cursor.hpp
|
||||
cache.h
|
||||
cache.hpp
|
||||
serializer.h
|
||||
serializer.hpp
|
||||
serializer_uint8.hpp
|
||||
serializer_uint16.hpp
|
||||
serializer_uint32.hpp
|
||||
serializer_uint64.hpp
|
||||
serializer_int8.hpp
|
||||
serializer_int16.hpp
|
||||
serializer_int32.hpp
|
||||
serializer_int64.hpp
|
||||
serializer_float.hpp
|
||||
serializer_double.hpp
|
||||
serializer_stdstring.hpp
|
||||
serializer_qstring.hpp
|
||||
serializer_qbytearray.hpp
|
||||
operators.hpp
|
||||
transaction.h
|
||||
)
|
||||
|
||||
target_sources(${PROJECT_NAME} PRIVATE
|
||||
${SOURCES}
|
||||
${HEADERS}
|
||||
)
|
||||
target_sources(${PROJECT_NAME} PRIVATE ${SOURCES})
|
||||
|
||||
add_subdirectory(serializer)
|
||||
|
||||
install(FILES ${HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_LOW})
|
||||
|
78
src/base.cpp
78
src/base.cpp
@ -19,6 +19,7 @@
|
||||
#include "base.h"
|
||||
#include "exceptions.h"
|
||||
#include "storage.h"
|
||||
#include "transaction.h"
|
||||
|
||||
#define UNUSED(x) (void)(x)
|
||||
|
||||
@ -67,13 +68,12 @@ LMDBAL::Base::~Base() {
|
||||
*/
|
||||
void LMDBAL::Base::close() {
|
||||
if (opened) {
|
||||
for (LMDBAL::TransactionID id : *transactions)
|
||||
for (const LMDBAL::TransactionID id : *transactions)
|
||||
abortTransaction(id, emptyName);
|
||||
|
||||
for (const std::pair<const std::string, iStorage*>& pair : storages) {
|
||||
iStorage* storage = pair.second;
|
||||
mdb_dbi_close(environment, storage->dbi);
|
||||
}
|
||||
for (const std::pair<const std::string, iStorage*>& pair : storages)
|
||||
pair.second->close();
|
||||
|
||||
mdb_env_close(environment);
|
||||
transactions->clear();
|
||||
opened = false;
|
||||
@ -101,7 +101,7 @@ void LMDBAL::Base::open() {
|
||||
TransactionID txn = beginPrivateTransaction(emptyName);
|
||||
for (const std::pair<const std::string, iStorage*>& pair : storages) {
|
||||
iStorage* storage = pair.second;
|
||||
int rc = storage->createStorage(txn);
|
||||
int rc = storage->open(txn);
|
||||
if (rc)
|
||||
throw Unknown(name, mdb_strerror(rc));
|
||||
}
|
||||
@ -121,8 +121,7 @@ bool LMDBAL::Base::removeDirectory() {
|
||||
if (opened)
|
||||
throw Opened(name, "remove database directory");
|
||||
|
||||
QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
|
||||
path += "/" + getName();
|
||||
QString path = getPath();
|
||||
QDir cache(path);
|
||||
|
||||
if (cache.exists())
|
||||
@ -149,10 +148,8 @@ QString LMDBAL::Base::createDirectory() {
|
||||
if (opened)
|
||||
throw Opened(name, "create database directory");
|
||||
|
||||
QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
|
||||
path += "/" + getName();
|
||||
QString path = getPath();
|
||||
QDir cache(path);
|
||||
|
||||
if (!cache.exists()) {
|
||||
bool res = cache.mkpath(path);
|
||||
if (!res)
|
||||
@ -170,6 +167,17 @@ QString LMDBAL::Base::createDirectory() {
|
||||
QString LMDBAL::Base::getName() const {
|
||||
return QString::fromStdString(name);}
|
||||
|
||||
/**
|
||||
* \brief Returns database name
|
||||
*
|
||||
* \returns database path
|
||||
*/
|
||||
QString LMDBAL::Base::getPath() const {
|
||||
QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
|
||||
path += "/" + getName();
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns database state
|
||||
@ -191,38 +199,63 @@ void LMDBAL::Base::drop() {
|
||||
if (!opened)
|
||||
throw Closed("drop", name);
|
||||
|
||||
TransactionID txn = beginTransaction();
|
||||
TransactionID txn = beginPrivateTransaction(emptyName);
|
||||
for (const std::pair<const std::string, iStorage*>& pair : storages) {
|
||||
int rc = pair.second->drop(txn);
|
||||
if (rc != MDB_SUCCESS) {
|
||||
abortTransaction(txn);
|
||||
abortPrivateTransaction(txn, emptyName);
|
||||
throw Unknown(name, mdb_strerror(rc), pair.first);
|
||||
}
|
||||
}
|
||||
commitTransaction(txn);
|
||||
|
||||
commitPrivateTransaction(txn, emptyName);
|
||||
for (const std::pair<const std::string, iStorage*>& pair : storages)
|
||||
pair.second->handleDrop();
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Begins read-only transaction
|
||||
*
|
||||
* \returns read-only transaction ID
|
||||
* This is the legitimate way to retrieve LMDBAL::Transaction.
|
||||
* LMDBAL::Transaction is considered runnig right after creation by this method.
|
||||
* You can terminate transaction manually calling LMDBAL::Transaction::terminate
|
||||
* but it's not required, because transaction will be terminated automatically
|
||||
* (if it was not terminated manually) upon the call of the destructor.
|
||||
*
|
||||
* You can not use termitated transaction any more.
|
||||
*
|
||||
* \returns read-only transaction
|
||||
*
|
||||
* \exception LMDBAL::Closed - thrown if the database is closed
|
||||
* \exception LMDBAL::Unknown - thrown if something unexpected happened
|
||||
*/
|
||||
LMDBAL::TransactionID LMDBAL::Base::beginReadOnlyTransaction() const {
|
||||
return beginReadOnlyTransaction(emptyName);}
|
||||
LMDBAL::Transaction LMDBAL::Base::beginReadOnlyTransaction() const {
|
||||
TransactionID id = beginReadOnlyTransaction(emptyName);
|
||||
return Transaction(id, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Begins writable transaction
|
||||
*
|
||||
* \returns writable transaction ID
|
||||
* This is the legitimate way to retrieve LMDBAL::WriteTransaction.
|
||||
* LMDBAL::WriteTransaction is considered runnig right after creation by this method.
|
||||
* You can commit all the changes made by this transaction calling LMDBAL::WriteTransaction::commit.
|
||||
* You can cancel any changes made bu this transaction calling LMDBAL::WriteTransaction::abort
|
||||
* (or LMDBAL::Transaction::terminate which LMDBAL::WriteTransaction inherits),
|
||||
* but it's not required, because transaction will be aborted automatically
|
||||
* (if it was not terminated (committed <b>OR</b> aborted) manually) upon the call of the destructor.
|
||||
*
|
||||
* You can not use termitated transaction any more.
|
||||
*
|
||||
* \returns writable transaction
|
||||
*
|
||||
* \exception LMDBAL::Closed - thrown if the database is closed
|
||||
* \exception LMDBAL::Unknown - thrown if something unexpected happened
|
||||
*/
|
||||
LMDBAL::TransactionID LMDBAL::Base::beginTransaction() const {
|
||||
return beginTransaction(emptyName);}
|
||||
LMDBAL::WriteTransaction LMDBAL::Base::beginTransaction() {
|
||||
TransactionID id = beginTransaction(emptyName);
|
||||
return WriteTransaction(id, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Aborts transaction
|
||||
@ -369,10 +402,9 @@ void LMDBAL::Base::commitTransaction(LMDBAL::TransactionID id, const std::string
|
||||
LMDBAL::TransactionID LMDBAL::Base::beginPrivateReadOnlyTransaction(const std::string& storageName) const {
|
||||
MDB_txn* txn;
|
||||
int rc = mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
|
||||
if (rc != MDB_SUCCESS) {
|
||||
mdb_txn_abort(txn);
|
||||
if (rc != MDB_SUCCESS)
|
||||
throw Unknown(name, mdb_strerror(rc), storageName);
|
||||
}
|
||||
|
||||
return txn;
|
||||
}
|
||||
|
||||
|
74
src/base.h
74
src/base.h
@ -36,6 +36,8 @@
|
||||
namespace LMDBAL {
|
||||
|
||||
class iStorage;
|
||||
class Transaction;
|
||||
class WriteTransaction;
|
||||
|
||||
template<class T>
|
||||
class Serializer;
|
||||
@ -46,10 +48,13 @@ class Storage;
|
||||
template <class K, class V>
|
||||
class Cache;
|
||||
|
||||
typedef MDB_txn* TransactionID; /**<I'm going to use transaction pointers as transaction IDs*/
|
||||
typedef MDB_txn* TransactionID; /**<\brief I'm going to use transaction pointers as transaction IDs*/
|
||||
typedef uint32_t SizeType; /**<\brief All LMDBAL sizes are uint32_t*/
|
||||
|
||||
class Base {
|
||||
friend class iStorage;
|
||||
friend class Transaction;
|
||||
friend class WriteTransaction;
|
||||
public:
|
||||
|
||||
Base(const QString& name, uint16_t mapSize = 10);
|
||||
@ -61,29 +66,30 @@ public:
|
||||
bool removeDirectory();
|
||||
QString createDirectory();
|
||||
QString getName() const;
|
||||
QString getPath() const;
|
||||
void drop();
|
||||
|
||||
TransactionID beginReadOnlyTransaction() const;
|
||||
TransactionID beginTransaction() const;
|
||||
void commitTransaction(TransactionID id);
|
||||
void abortTransaction(TransactionID id) const;
|
||||
Transaction beginReadOnlyTransaction() const;
|
||||
WriteTransaction beginTransaction();
|
||||
|
||||
template <class K, class V>
|
||||
LMDBAL::Storage<K, V>* addStorage(const std::string& name);
|
||||
LMDBAL::Storage<K, V>* addStorage(const std::string& storageName, bool duplicates = false);
|
||||
|
||||
template <class K, class V>
|
||||
LMDBAL::Cache<K, V>* addCache(const std::string& name);
|
||||
LMDBAL::Cache<K, V>* addCache(const std::string& storageName);
|
||||
|
||||
template <class K, class V>
|
||||
LMDBAL::Storage<K, V>* getStorage(const std::string& name);
|
||||
LMDBAL::Storage<K, V>* getStorage(const std::string& storageName);
|
||||
|
||||
template <class K, class V>
|
||||
LMDBAL::Cache<K, V>* getCache(const std::string& name);
|
||||
LMDBAL::Cache<K, V>* getCache(const std::string& storageName);
|
||||
|
||||
private:
|
||||
typedef std::map<std::string, LMDBAL::iStorage*> Storages; /**<Storage and Cache pointers are saved in the std::map*/
|
||||
typedef std::set<TransactionID> Transactions; /**<Piblic transaction IDs are saved in the std::set*/
|
||||
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*/
|
||||
|
||||
void commitTransaction(TransactionID id);
|
||||
void abortTransaction(TransactionID id) const;
|
||||
TransactionID beginReadOnlyTransaction(const std::string& storageName) const;
|
||||
TransactionID beginTransaction(const std::string& storageName) const;
|
||||
void commitTransaction(TransactionID id, const std::string& storageName);
|
||||
@ -114,7 +120,9 @@ private:
|
||||
* Defines that the database is going to have the following storage.
|
||||
* The LMDBAL::Base must be closed
|
||||
*
|
||||
* \param[in] _name - storage name
|
||||
* \param[in] storageName - storage name
|
||||
* \param[in] duplicates - true if key duplicates are allowed (false by default)
|
||||
*
|
||||
* \returns storage pointer. LMDBAL::Base keeps the ownership of the added storage, you don't need to destoroy it
|
||||
*
|
||||
* \tparam K - key type of the storage
|
||||
@ -124,14 +132,14 @@ private:
|
||||
* \exception LMDBAL::StorageDuplicate thrown if somebody tries to add storage with repeating name
|
||||
*/
|
||||
template <class K, class V>
|
||||
LMDBAL::Storage<K, V>* LMDBAL::Base::addStorage(const std::string& _name) {
|
||||
if (opened) {
|
||||
throw Opened(name, "add storage " + _name);
|
||||
}
|
||||
Storage<K, V>* storage = new Storage<K, V>(_name, this);
|
||||
std::pair<Storages::const_iterator, bool> pair = storages.insert(std::make_pair(_name, (iStorage*)storage));
|
||||
LMDBAL::Storage<K, V>* LMDBAL::Base::addStorage(const std::string& storageName, bool duplicates) {
|
||||
if (opened)
|
||||
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));
|
||||
if (!pair.second)
|
||||
throw StorageDuplicate(name, _name);
|
||||
throw StorageDuplicate(name, storageName);
|
||||
|
||||
return storage;
|
||||
}
|
||||
@ -142,7 +150,7 @@ LMDBAL::Storage<K, V>* LMDBAL::Base::addStorage(const std::string& _name) {
|
||||
* Defines that the database is going to have the following cache.
|
||||
* The LMDBAL::Base must be closed
|
||||
*
|
||||
* \param[in] _name - cache name
|
||||
* \param[in] storageName - cache name
|
||||
* \returns cache pointer. LMDBAL::Base keeps the ownership of the added cache, you don't need to destoroy it
|
||||
*
|
||||
* \tparam K - key type of the cache
|
||||
@ -152,14 +160,14 @@ LMDBAL::Storage<K, V>* LMDBAL::Base::addStorage(const std::string& _name) {
|
||||
* \exception LMDBAL::StorageDuplicate thrown if somebody tries to add cache with repeating name
|
||||
*/
|
||||
template<class K, class V>
|
||||
LMDBAL::Cache<K, V> * LMDBAL::Base::addCache(const std::string& _name) {
|
||||
if (opened) {
|
||||
throw Opened(name, "add cache " + _name);
|
||||
}
|
||||
Cache<K, V>* cache = new Cache<K, V>(_name, this);
|
||||
std::pair<Storages::const_iterator, bool> pair = storages.insert(std::make_pair(_name, (iStorage*)cache));
|
||||
LMDBAL::Cache<K, V> * LMDBAL::Base::addCache(const std::string& storageName) {
|
||||
if (opened)
|
||||
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));
|
||||
if (!pair.second)
|
||||
throw StorageDuplicate(name, _name);
|
||||
throw StorageDuplicate(name, storageName);
|
||||
|
||||
return cache;
|
||||
}
|
||||
@ -173,7 +181,7 @@ LMDBAL::Cache<K, V> * LMDBAL::Base::addCache(const std::string& _name) {
|
||||
* this method with template parameters <std::string, std::string>
|
||||
* on the same name of the previously added storage, or calling it on cache - the behaviour is undefined
|
||||
*
|
||||
* \param[in] _name - storage name
|
||||
* \param[in] storageName - storage name
|
||||
* \returns storage pointer. LMDBAL::Base keeps the ownership of the added storage, you don't need to destoroy it
|
||||
*
|
||||
* \tparam K - key type of the storage
|
||||
@ -182,8 +190,8 @@ LMDBAL::Cache<K, V> * LMDBAL::Base::addCache(const std::string& _name) {
|
||||
* \exception std::out_of_range thrown if storage with the given name was not found
|
||||
*/
|
||||
template <class K, class V>
|
||||
LMDBAL::Storage<K, V>* LMDBAL::Base::getStorage(const std::string& _name) {
|
||||
return static_cast<Storage<K, V>*>(storages.at(_name));
|
||||
LMDBAL::Storage<K, V>* LMDBAL::Base::getStorage(const std::string& storageName) {
|
||||
return static_cast<Storage<K, V>*>(storages.at(storageName));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -195,7 +203,7 @@ LMDBAL::Storage<K, V>* LMDBAL::Base::getStorage(const std::string& _name) {
|
||||
* this method with template parameters <std::string, std::string>
|
||||
* on the same name of the previously added cache, or calling it on storage - the behaviour is undefined
|
||||
*
|
||||
* \param[in] _name - cache name
|
||||
* \param[in] storageName - cache name
|
||||
* \returns cache pointer. LMDBAL::Base keeps the ownership of the added cache, you don't need to destoroy it
|
||||
*
|
||||
* \tparam K - key type of the cache
|
||||
@ -204,8 +212,8 @@ LMDBAL::Storage<K, V>* LMDBAL::Base::getStorage(const std::string& _name) {
|
||||
* \exception std::out_of_range thrown if cache with the given name was not found
|
||||
*/
|
||||
template <class K, class V>
|
||||
LMDBAL::Cache<K, V>* LMDBAL::Base::getCache(const std::string& _name) {
|
||||
return static_cast<Cache<K, V>*>(storages.at(_name));
|
||||
LMDBAL::Cache<K, V>* LMDBAL::Base::getCache(const std::string& storageName) {
|
||||
return static_cast<Cache<K, V>*>(storages.at(storageName));
|
||||
}
|
||||
|
||||
#endif //LMDBAL_BASE_H
|
||||
|
54
src/cache.h
54
src/cache.h
@ -51,12 +51,30 @@ class Cache : public Storage<K, V> {
|
||||
typedef std::map<TransactionID, Queue> TransactionCache;
|
||||
|
||||
protected:
|
||||
Cache(const std::string& name, Base* parent);
|
||||
Cache(Base* parent, const std::string& name, bool duplicates = false);
|
||||
~Cache() override;
|
||||
|
||||
virtual void handleDrop() override;
|
||||
virtual void transactionStarted(TransactionID txn, bool readOnly) const override;
|
||||
virtual void transactionCommited(TransactionID txn) override;
|
||||
virtual void transactionAborted(TransactionID txn) const override;
|
||||
|
||||
virtual void discoveredRecord(const K& key, const V& value) const override;
|
||||
virtual void discoveredRecord(const K& key, const V& value, TransactionID txn) const override;
|
||||
|
||||
virtual SizeType count(TransactionID txn) const override;
|
||||
virtual void addRecord(const K& key, const V& value, TransactionID txn) override;
|
||||
virtual bool forceRecord(const K& key, const V& value, TransactionID txn) override;
|
||||
virtual void changeRecord(const K& key, const V& value, TransactionID txn) override;
|
||||
virtual void removeRecord(const K& key, TransactionID txn) override;
|
||||
virtual bool checkRecord(const K& key, TransactionID txn) const override;
|
||||
virtual void getRecord(const K& key, V& out, TransactionID txn) const override;
|
||||
virtual V getRecord(const K& key, TransactionID txn) const override;
|
||||
virtual std::map<K, V> readAll(TransactionID txn) const override;
|
||||
virtual void readAll(std::map<K, V>& out, TransactionID txn) const override;
|
||||
virtual void replaceAll(const std::map<K, V>& data, TransactionID txn) override;
|
||||
virtual SizeType addRecords(const std::map<K, V>& data, TransactionID txn, bool overwrite = false) override;
|
||||
|
||||
private:
|
||||
void handleMode() const;
|
||||
|
||||
@ -69,51 +87,47 @@ private:
|
||||
void handleForceRecord(const K& key, const V& value, bool added);
|
||||
void handleReplaceAll(std::map<K, V>* data);
|
||||
void handleAddRecords(const std::map<K, V>& data, bool overwrite, SizeType newSize);
|
||||
void handleDrop();
|
||||
void appendToCache(const K& key, const V& value) const;
|
||||
|
||||
public:
|
||||
using Storage<K, V>::drop;
|
||||
virtual int drop(TransactionID transaction) override;
|
||||
using Storage<K, V>::addRecord;
|
||||
using Storage<K, V>::forceRecord;
|
||||
using Storage<K, V>::changeRecord;
|
||||
using Storage<K, V>::removeRecord;
|
||||
using Storage<K, V>::checkRecord;
|
||||
using Storage<K, V>::getRecord;
|
||||
using Storage<K, V>::readAll;
|
||||
using Storage<K, V>::replaceAll;
|
||||
using Storage<K, V>::addRecords;
|
||||
using Storage<K, V>::count;
|
||||
virtual int drop(const WriteTransaction& transaction) override;
|
||||
virtual void addRecord(const K& key, const V& value) override;
|
||||
virtual void addRecord(const K& key, const V& value, TransactionID txn) override;
|
||||
virtual bool forceRecord(const K& key, const V& value) override;
|
||||
virtual bool forceRecord(const K& key, const V& value, TransactionID txn) override;
|
||||
virtual void changeRecord(const K& key, const V& value) override;
|
||||
virtual void changeRecord(const K& key, const V& value, TransactionID txn) override;
|
||||
virtual void removeRecord(const K& key) override;
|
||||
virtual void removeRecord(const K& key, TransactionID txn) override;
|
||||
virtual bool checkRecord(const K& key) const override;
|
||||
virtual bool checkRecord(const K& key, TransactionID txn) const override;
|
||||
virtual void getRecord(const K& key, V& out) const override;
|
||||
virtual void getRecord(const K& key, V& out, TransactionID txn) const override;
|
||||
virtual V getRecord(const K& key) const override;
|
||||
virtual V getRecord(const K& key, TransactionID txn) const override;
|
||||
virtual SizeType count() const override;
|
||||
virtual SizeType count(TransactionID txn) const override;
|
||||
|
||||
virtual std::map<K, V> readAll() const override;
|
||||
virtual std::map<K, V> readAll(TransactionID txn) const override;
|
||||
virtual void readAll(std::map<K, V>& out) const override;
|
||||
virtual void readAll(std::map<K, V>& out, TransactionID txn) const override;
|
||||
virtual void replaceAll(const std::map<K, V>& data) override;
|
||||
virtual void replaceAll(const std::map<K, V>& data, TransactionID txn) override;
|
||||
virtual SizeType addRecords(const std::map<K, V>& data, bool overwrite = false) override;
|
||||
virtual SizeType addRecords(const std::map<K, V>& data, TransactionID txn, bool overwrite = false) override;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* \brief Cache mode
|
||||
*
|
||||
* Sometimes we have a complete information about the content of the database,
|
||||
* and we don't even need to refer to lmdb to fetch or check for data.
|
||||
* and we don't even need to call lmdb to fetch or check for data.
|
||||
* This member actually shows what is the current state, more about them you can read in Enum descriptions
|
||||
*
|
||||
* It's done with pointer to comply some of the const method limitations which would modify this state eventually
|
||||
*/
|
||||
Mode* mode;
|
||||
mutable Mode mode;
|
||||
std::map<K, V>* cache; /**<\brief Cached data*/
|
||||
std::set<K>* abscent; /**<\brief Set of keys that are definitely not in the cache*/
|
||||
SizeType* sizeDifference; /**<\brief Difference of size between cached data and amount of records in the lmdb storage*/
|
||||
mutable SizeType sizeDifference; /**<\brief Difference of size between cached data and amount of records in the lmdb storage*/
|
||||
TransactionCache* transactionCache; /**<\brief All changes made under under uncommited transactions*/
|
||||
};
|
||||
|
||||
|
219
src/cache.hpp
219
src/cache.hpp
@ -39,21 +39,18 @@
|
||||
/**
|
||||
* \brief Creates a cache
|
||||
*
|
||||
* \param[in] _name - name of the new cache
|
||||
* \param[in] parent - parent database pointed (borrowed)
|
||||
* \param[in] parent - LMDBAL::Base pointer for the owning database (borrowed)
|
||||
* \param[in] name - the name of the storage
|
||||
* \param[in] duplicates - true if key duplicates are allowed (false by default)
|
||||
*/
|
||||
template<class K, class V>
|
||||
LMDBAL::Cache<K, V>::Cache(const std::string& _name, Base* parent):
|
||||
Storage<K, V>(_name, parent),
|
||||
mode(new Mode),
|
||||
LMDBAL::Cache<K, V>::Cache(Base* parent, const std::string& name, bool duplicates):
|
||||
Storage<K, V>(parent, name, duplicates),
|
||||
mode(Mode::nothing),
|
||||
cache(new std::map<K, V>()),
|
||||
abscent(new std::set<K>()),
|
||||
sizeDifference(new uint32_t),
|
||||
transactionCache(new TransactionCache)
|
||||
{
|
||||
*mode = Mode::nothing;
|
||||
*sizeDifference = 0;
|
||||
}
|
||||
sizeDifference(0),
|
||||
transactionCache(new TransactionCache) {}
|
||||
|
||||
/**
|
||||
* \brief Destroys a cache
|
||||
@ -61,8 +58,6 @@ LMDBAL::Cache<K, V>::Cache(const std::string& _name, Base* parent):
|
||||
template<class K, class V>
|
||||
LMDBAL::Cache<K, V>::~Cache() {
|
||||
delete transactionCache;
|
||||
delete sizeDifference;
|
||||
delete mode;
|
||||
delete cache;
|
||||
delete abscent;
|
||||
}
|
||||
@ -80,8 +75,6 @@ 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) {
|
||||
iStorage::ensureOpened(iStorage::addRecordMethodName);
|
||||
|
||||
if (cache->count(key) > 0)
|
||||
iStorage::throwDuplicate(iStorage::toString(key));
|
||||
|
||||
@ -98,7 +91,7 @@ template<class K, class V>
|
||||
void LMDBAL::Cache<K, V>::handleAddRecord(const K& key, const V& value) {
|
||||
cache->insert(std::make_pair(key, value));
|
||||
|
||||
if (*mode != Mode::full)
|
||||
if (mode != Mode::full)
|
||||
abscent->erase(key);
|
||||
}
|
||||
|
||||
@ -114,8 +107,6 @@ bool LMDBAL::Cache<K, V>::forceRecord(const K& key, const V& value) {
|
||||
|
||||
template<class K, class V>
|
||||
bool LMDBAL::Cache<K, V>::forceRecord(const K& key, const V& value, TransactionID txn) {
|
||||
iStorage::ensureOpened(iStorage::forceRecordMethodName);
|
||||
|
||||
bool added = Storage<K, V>::forceRecord(key, value, txn);
|
||||
|
||||
typename TransactionCache::iterator tc = transactionCache->find(txn);
|
||||
@ -129,7 +120,7 @@ bool LMDBAL::Cache<K, V>::forceRecord(const K& key, const V& value, TransactionI
|
||||
|
||||
template<class K, class V>
|
||||
void LMDBAL::Cache<K, V>::handleForceRecord(const K& key, const V& value, bool added) {
|
||||
if (*mode == Mode::full) {
|
||||
if (mode == Mode::full) {
|
||||
(*cache)[key] = value;
|
||||
} else {
|
||||
if (added)
|
||||
@ -148,7 +139,7 @@ template<class K, class V>
|
||||
void LMDBAL::Cache<K, V>::changeRecord(const K& key, const V& value) {
|
||||
iStorage::ensureOpened(iStorage::changeRecordMethodName);
|
||||
|
||||
if (*mode == Mode::full) {
|
||||
if (mode == Mode::full) {
|
||||
typename std::map<K, V>::iterator itr = cache->find(key);
|
||||
if (itr == cache->end())
|
||||
iStorage::throwNotFound(iStorage::toString(key));
|
||||
@ -176,9 +167,7 @@ void LMDBAL::Cache<K, V>::changeRecord(const K& key, const V& value) {
|
||||
|
||||
template<class K, class V>
|
||||
void LMDBAL::Cache<K, V>::changeRecord(const K& key, const V& value, TransactionID txn) {
|
||||
iStorage::ensureOpened(iStorage::changeRecordMethodName);
|
||||
|
||||
if (*mode == Mode::full) {
|
||||
if (mode == Mode::full) {
|
||||
typename std::map<K, V>::iterator itr = cache->find(key);
|
||||
if (itr == cache->end())
|
||||
iStorage::throwNotFound(iStorage::toString(key));
|
||||
@ -205,7 +194,7 @@ void LMDBAL::Cache<K, V>::changeRecord(const K& key, const V& value, Transaction
|
||||
|
||||
template<class K, class V>
|
||||
void LMDBAL::Cache<K, V>::handleChangeRecord(const K& key, const V& value) {
|
||||
if (*mode == Mode::full) {
|
||||
if (mode == Mode::full) {
|
||||
cache->at(key) = value;
|
||||
} else {
|
||||
typename std::pair<typename std::map<K, V>::iterator, bool> res =
|
||||
@ -236,16 +225,15 @@ void LMDBAL::Cache<K, V>::getRecord(const K& key, V& out) const {
|
||||
return;
|
||||
}
|
||||
|
||||
if (*mode == Mode::full || abscent->count(key) != 0)
|
||||
if (mode == Mode::full || abscent->count(key) != 0)
|
||||
iStorage::throwNotFound(iStorage::toString(key));
|
||||
|
||||
try {
|
||||
Storage<K, V>::getRecord(key, out);
|
||||
cache->insert(std::make_pair(key, out));
|
||||
handleMode();
|
||||
appendToCache(key, out);
|
||||
return;
|
||||
} catch (const NotFound& error) {
|
||||
if (*mode != Mode::full)
|
||||
if (mode != Mode::full)
|
||||
abscent->insert(key);
|
||||
|
||||
throw error;
|
||||
@ -254,8 +242,6 @@ void LMDBAL::Cache<K, V>::getRecord(const K& key, V& out) const {
|
||||
|
||||
template<class K, class V>
|
||||
V LMDBAL::Cache<K, V>::getRecord(const K& key, TransactionID txn) const {
|
||||
iStorage::ensureOpened(iStorage::getRecordMethodName);
|
||||
|
||||
V value;
|
||||
Cache<K, V>::getRecord(key, value, txn);
|
||||
return value;
|
||||
@ -263,8 +249,6 @@ V LMDBAL::Cache<K, V>::getRecord(const K& key, TransactionID txn) const {
|
||||
|
||||
template<class K, class V>
|
||||
void LMDBAL::Cache<K, V>::getRecord(const K& key, V& out, TransactionID txn) const {
|
||||
iStorage::ensureOpened(iStorage::getRecordMethodName);
|
||||
|
||||
//if there are any changes made within this transaction
|
||||
//I will be able to see them among pending changes
|
||||
//so, I'm going to go through them in reverse order
|
||||
@ -338,24 +322,35 @@ void LMDBAL::Cache<K, V>::getRecord(const K& key, V& out, TransactionID txn) con
|
||||
return;
|
||||
}
|
||||
|
||||
if (*mode == Mode::full || abscent->count(key) != 0)
|
||||
if (mode == Mode::full || abscent->count(key) != 0)
|
||||
iStorage::throwNotFound(iStorage::toString(key));
|
||||
|
||||
try {
|
||||
Storage<K, V>::getRecord(key, out, txn);
|
||||
if (!currentTransaction) {
|
||||
cache->insert(std::make_pair(key, out));
|
||||
handleMode();
|
||||
}
|
||||
if (!currentTransaction)
|
||||
appendToCache(key, out);
|
||||
|
||||
return;
|
||||
} catch (const NotFound& error) {
|
||||
if (! currentTransaction && *mode != Mode::full)
|
||||
if (!currentTransaction && mode != Mode::full)
|
||||
abscent->insert(key);
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
template<class K, class V>
|
||||
void LMDBAL::Cache<K, V>::discoveredRecord(const K& key, const V& value) const {
|
||||
appendToCache(key, value);
|
||||
}
|
||||
|
||||
template<class K, class V>
|
||||
void LMDBAL::Cache<K, V>::discoveredRecord(const K& key, const V& value, TransactionID txn) const {
|
||||
typename TransactionCache::const_iterator tc = transactionCache->find(txn);
|
||||
if (tc == transactionCache->end()) //there is a way to look though all the records in transaction log and cache the new pair
|
||||
discoveredRecord(key, value); //if there is nothing in transaction log about it, but it seems like too much for a small gain
|
||||
}
|
||||
|
||||
template<class K, class V>
|
||||
bool LMDBAL::Cache<K, V>::checkRecord(const K& key) const {
|
||||
iStorage::ensureOpened(iStorage::checkRecordMethodName);
|
||||
@ -364,16 +359,16 @@ bool LMDBAL::Cache<K, V>::checkRecord(const K& key) const {
|
||||
if (itr != cache->end())
|
||||
return true;
|
||||
|
||||
if (*mode == Mode::full || abscent->count(key) != 0)
|
||||
if (mode == Mode::full || abscent->count(key) != 0)
|
||||
return false;
|
||||
|
||||
try {
|
||||
V value = Storage<K, V>::getRecord(key);
|
||||
cache->insert(std::make_pair(key, value));
|
||||
handleMode();
|
||||
appendToCache(key, value);
|
||||
|
||||
return true;
|
||||
} catch (const NotFound& error) {
|
||||
if (*mode != Mode::full)
|
||||
if (mode != Mode::full)
|
||||
abscent->insert(key);
|
||||
|
||||
return false;
|
||||
@ -382,8 +377,6 @@ bool LMDBAL::Cache<K, V>::checkRecord(const K& key) const {
|
||||
|
||||
template<class K, class V>
|
||||
bool LMDBAL::Cache<K, V>::checkRecord(const K& key, TransactionID txn) const {
|
||||
iStorage::ensureOpened(iStorage::checkRecordMethodName);
|
||||
|
||||
//if there are any changes made within this transaction
|
||||
//I will be able to see them among pending changes
|
||||
//so, I'm going to go through them in reverse order
|
||||
@ -439,33 +432,39 @@ bool LMDBAL::Cache<K, V>::checkRecord(const K& key, TransactionID txn) const {
|
||||
if (itr != cache->end())
|
||||
return true;
|
||||
|
||||
if (*mode == Mode::full || abscent->count(key) != 0)
|
||||
if (mode == Mode::full || abscent->count(key) != 0)
|
||||
return false;
|
||||
|
||||
try {
|
||||
V value = Storage<K, V>::getRecord(key, txn);
|
||||
if (!currentTransaction) {
|
||||
cache->insert(std::make_pair(key, value));
|
||||
handleMode();
|
||||
}
|
||||
if (!currentTransaction)
|
||||
appendToCache(key, value);
|
||||
|
||||
return true;
|
||||
} catch (const NotFound& error) {
|
||||
if (!currentTransaction && *mode != Mode::full)
|
||||
if (!currentTransaction && mode != Mode::full)
|
||||
abscent->insert(key);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
template<class K, class V>
|
||||
void LMDBAL::Cache<K, V>::appendToCache(const K& key, const V& value) const {
|
||||
typename std::pair<typename std::map<K, V>::const_iterator, bool> pair = cache->insert(std::make_pair(key, value));
|
||||
if (pair.second)
|
||||
handleMode();
|
||||
}
|
||||
|
||||
template<class K, class V>
|
||||
std::map<K, V> LMDBAL::Cache<K, V>::readAll() const {
|
||||
iStorage::ensureOpened(iStorage::readAllMethodName);
|
||||
|
||||
if (*mode != Mode::full) { //there is a room for optimization
|
||||
*mode = Mode::full; //I can read and deserialize only those values
|
||||
if (mode != Mode::full) { //there is a room for optimization
|
||||
mode = Mode::full; //I can read and deserialize only those values
|
||||
*cache = Storage<K, V>::readAll(); //that are missing in the cache
|
||||
abscent->clear();
|
||||
*sizeDifference = 0;
|
||||
sizeDifference = 0;
|
||||
}
|
||||
|
||||
return *cache;
|
||||
@ -475,19 +474,17 @@ template<class K, class V>
|
||||
void LMDBAL::Cache<K, V>::readAll(std::map<K, V>& out) const {
|
||||
iStorage::ensureOpened(iStorage::readAllMethodName);
|
||||
|
||||
if (*mode != Mode::full) { //there is a room for optimization
|
||||
*mode = Mode::full; //I can read and deserialize only those values
|
||||
if (mode != Mode::full) { //there is a room for optimization
|
||||
mode = Mode::full; //I can read and deserialize only those values
|
||||
Storage<K, V>::readAll(out); //that are missing in the cache
|
||||
*cache = out;
|
||||
abscent->clear();
|
||||
*sizeDifference = 0;
|
||||
sizeDifference = 0;
|
||||
}
|
||||
}
|
||||
|
||||
template<class K, class V>
|
||||
std::map<K, V> LMDBAL::Cache<K, V>::readAll(TransactionID txn) const {
|
||||
iStorage::ensureOpened(iStorage::readAllMethodName);
|
||||
|
||||
std::map<K, V> out;
|
||||
readAll(out, txn);
|
||||
|
||||
@ -496,12 +493,10 @@ std::map<K, V> LMDBAL::Cache<K, V>::readAll(TransactionID txn) const {
|
||||
|
||||
template<class K, class V>
|
||||
void LMDBAL::Cache<K, V>::readAll(std::map<K, V>& out, TransactionID txn) const {
|
||||
iStorage::ensureOpened(iStorage::readAllMethodName);
|
||||
|
||||
typename TransactionCache::iterator tc = transactionCache->find(txn);
|
||||
if (tc != transactionCache->end()) {
|
||||
Queue& queue = tc->second;
|
||||
if (*mode != Mode::full) {
|
||||
if (mode != Mode::full) {
|
||||
out = *cache;
|
||||
|
||||
for (typename Queue::const_iterator i = queue.begin(), end = queue.end(); i != end; ++i) {
|
||||
@ -552,12 +547,12 @@ void LMDBAL::Cache<K, V>::readAll(std::map<K, V>& out, TransactionID txn) const
|
||||
queue.emplace_back(Operation::replace, new std::map<K, V>(out)); //I can as well erase all previous cache entries
|
||||
}
|
||||
} else {
|
||||
if (*mode != Mode::full) { //there is a room for optimization
|
||||
*mode = Mode::full; //I can read and deserialize only those values
|
||||
Storage<K, V>::readAll(out); //that are missing in the cache
|
||||
if (mode != Mode::full) { //there is a room for optimization
|
||||
mode = Mode::full; //I can read and deserialize only those values
|
||||
Storage<K, V>::readAll(out, txn); //that are missing in the cache
|
||||
*cache = out;
|
||||
abscent->clear();
|
||||
*sizeDifference = 0;
|
||||
sizeDifference = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -567,10 +562,10 @@ void LMDBAL::Cache<K, V>::replaceAll(const std::map<K, V>& data) {
|
||||
Storage<K, V>::replaceAll(data);
|
||||
*cache = data;
|
||||
|
||||
if (*mode != Mode::full) {
|
||||
*mode = Mode::full;
|
||||
if (mode != Mode::full) {
|
||||
mode = Mode::full;
|
||||
abscent->clear();
|
||||
*sizeDifference = 0;
|
||||
sizeDifference = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -591,10 +586,10 @@ void LMDBAL::Cache<K, V>::handleReplaceAll(std::map<K, V>* data) {
|
||||
delete cache;
|
||||
cache = data;
|
||||
|
||||
if (*mode != Mode::full) {
|
||||
*mode = Mode::full;
|
||||
if (mode != Mode::full) {
|
||||
mode = Mode::full;
|
||||
abscent->clear();
|
||||
*sizeDifference = 0;
|
||||
sizeDifference = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -622,9 +617,8 @@ LMDBAL::SizeType LMDBAL::Cache<K, V>::addRecords(const std::map<K, V>& data, Tra
|
||||
|
||||
template<class K, class V>
|
||||
void LMDBAL::Cache<K, V>::handleAddRecords(const std::map<K, V>& data, bool overwrite, SizeType newSize) {
|
||||
Mode& m = *mode;
|
||||
if (m == Mode::nothing)
|
||||
m = Mode::size;
|
||||
if (mode == Mode::nothing)
|
||||
mode = Mode::size;
|
||||
|
||||
std::map<K, V>& c = *cache;
|
||||
std::set<K>& a = *abscent;
|
||||
@ -633,15 +627,15 @@ void LMDBAL::Cache<K, V>::handleAddRecords(const std::map<K, V>& data, bool over
|
||||
if (!res.second) {
|
||||
if (overwrite)
|
||||
res.first->second = pair.second;
|
||||
} else if (m != Mode::full) {
|
||||
} else if (mode != Mode::full) {
|
||||
a.erase(pair.first);
|
||||
}
|
||||
}
|
||||
|
||||
if (m != Mode::full) {
|
||||
*sizeDifference = newSize - c.size();
|
||||
if (*sizeDifference == 0) {
|
||||
*mode = Mode::full;
|
||||
if (mode != Mode::full) {
|
||||
sizeDifference = newSize - c.size();
|
||||
if (sizeDifference == 0) {
|
||||
mode = Mode::full;
|
||||
abscent->clear();
|
||||
}
|
||||
}
|
||||
@ -652,7 +646,7 @@ void LMDBAL::Cache<K, V>::removeRecord(const K& key) {
|
||||
iStorage::ensureOpened(iStorage::removeRecordMethodName);
|
||||
|
||||
bool noKey = false;
|
||||
if (*mode != Mode::full)
|
||||
if (mode != Mode::full)
|
||||
noKey = cache->count(key) == 0;
|
||||
else
|
||||
noKey = abscent->count(key) > 0;
|
||||
@ -666,10 +660,8 @@ void LMDBAL::Cache<K, V>::removeRecord(const K& key) {
|
||||
|
||||
template<class K, class V>
|
||||
void LMDBAL::Cache<K, V>::removeRecord(const K& key, TransactionID txn) {
|
||||
iStorage::ensureOpened(iStorage::removeRecordMethodName);
|
||||
|
||||
bool noKey = false;
|
||||
if (*mode != Mode::full)
|
||||
if (mode != Mode::full)
|
||||
noKey = cache->count(key) == 0;
|
||||
else
|
||||
noKey = abscent->count(key) > 0;
|
||||
@ -689,27 +681,27 @@ void LMDBAL::Cache<K, V>::handleRemoveRecord(const K& key) {
|
||||
if (cache->erase(key) == 0) //if it was not cached and we are now in size mode then the sizeDifference would decrease
|
||||
handleMode();
|
||||
|
||||
if (*mode != Mode::full)
|
||||
if (mode != Mode::full)
|
||||
abscent->insert(key);
|
||||
}
|
||||
|
||||
template<class K, class V>
|
||||
uint32_t LMDBAL::Cache<K, V>::count() const {
|
||||
switch (*mode) {
|
||||
switch (mode) {
|
||||
case Mode::nothing:
|
||||
{
|
||||
uint32_t sz = Storage<K, V>::count();
|
||||
*sizeDifference = sz - cache->size();
|
||||
sizeDifference = sz - cache->size();
|
||||
if (sz == 0) {
|
||||
*mode = Mode::full;
|
||||
mode = Mode::full;
|
||||
abscent->clear();
|
||||
} else {
|
||||
*mode = Mode::size;
|
||||
mode = Mode::size;
|
||||
}
|
||||
return sz;
|
||||
}
|
||||
case Mode::size:
|
||||
return cache->size() + *sizeDifference;
|
||||
return cache->size() + sizeDifference;
|
||||
case Mode::full:
|
||||
return cache->size();
|
||||
default:
|
||||
@ -719,7 +711,6 @@ uint32_t LMDBAL::Cache<K, V>::count() const {
|
||||
|
||||
template<class K, class V>
|
||||
uint32_t LMDBAL::Cache<K, V>::count(TransactionID txn) const {
|
||||
|
||||
int32_t diff = 0;
|
||||
bool currentTransaction = false;
|
||||
typename TransactionCache::const_iterator tc = transactionCache->find(txn);
|
||||
@ -755,22 +746,22 @@ uint32_t LMDBAL::Cache<K, V>::count(TransactionID txn) const {
|
||||
}
|
||||
}
|
||||
|
||||
switch (*mode) {
|
||||
switch (mode) {
|
||||
case Mode::nothing: {
|
||||
uint32_t sz = Storage<K, V>::count(txn);
|
||||
if (!currentTransaction) {
|
||||
*sizeDifference = sz - cache->size();
|
||||
sizeDifference = sz - cache->size();
|
||||
if (sz == 0) {
|
||||
*mode = Mode::full;
|
||||
mode = Mode::full;
|
||||
abscent->clear();
|
||||
} else {
|
||||
*mode = Mode::size;
|
||||
mode = Mode::size;
|
||||
}
|
||||
}
|
||||
return sz;
|
||||
}
|
||||
case Mode::size:
|
||||
return cache->size() + *sizeDifference + diff;
|
||||
return cache->size() + sizeDifference + diff;
|
||||
case Mode::full:
|
||||
return cache->size() + diff;
|
||||
default:
|
||||
@ -780,20 +771,25 @@ uint32_t LMDBAL::Cache<K, V>::count(TransactionID txn) const {
|
||||
|
||||
template<class K, class V>
|
||||
void LMDBAL::Cache<K, V>::handleMode() const {
|
||||
if (*mode == Mode::size) {
|
||||
--(*sizeDifference);
|
||||
if (*sizeDifference == 0) {
|
||||
*mode = Mode::full;
|
||||
if (mode == Mode::size) {
|
||||
--sizeDifference;
|
||||
if (sizeDifference == 0) {
|
||||
mode = Mode::full;
|
||||
abscent->clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<class K, class V>
|
||||
int LMDBAL::Cache<K, V>::drop(TransactionID transaction) {
|
||||
int res = Storage<K, V>::drop(transaction);
|
||||
int LMDBAL::Cache<K, V>::drop(const WriteTransaction& transaction) {
|
||||
iStorage::ensureOpened(iStorage::dropMethodName);
|
||||
TransactionID txn = iStorage::extractTransactionId(transaction, iStorage::dropMethodName);
|
||||
int res = Storage<K, V>::drop(txn);
|
||||
|
||||
typename TransactionCache::iterator tc = transactionCache->find(transaction);
|
||||
if (res != MDB_SUCCESS)
|
||||
return res;
|
||||
|
||||
typename TransactionCache::iterator tc = transactionCache->find(txn);
|
||||
if (tc != transactionCache->end())
|
||||
tc->second.emplace_back(Operation::drop, nullptr);
|
||||
|
||||
@ -804,8 +800,8 @@ template<class K, class V>
|
||||
void LMDBAL::Cache<K, V>::handleDrop() {
|
||||
cache->clear();
|
||||
abscent->clear();
|
||||
*mode = Mode::full;
|
||||
*sizeDifference = 0;
|
||||
mode = Mode::full;
|
||||
sizeDifference = 0;
|
||||
}
|
||||
|
||||
template<class K, class V>
|
||||
@ -845,27 +841,23 @@ void LMDBAL::Cache<K, V>::handleTransactionEntry(const Entry& entry) {
|
||||
std::pair<K, V>* pair = static_cast<std::pair<K, V>*>(entry.second);
|
||||
handleAddRecord(pair->first, pair->second);
|
||||
delete pair;
|
||||
}
|
||||
break;
|
||||
} break;
|
||||
case Operation::remove: {
|
||||
K* key = static_cast<K*>(entry.second);
|
||||
handleRemoveRecord(*key);
|
||||
delete key;
|
||||
}
|
||||
|
||||
break;
|
||||
} break;
|
||||
case Operation::change: {
|
||||
std::pair<K, V>* pair = static_cast<std::pair<K, V>*>(entry.second);
|
||||
handleChangeRecord(pair->first, pair->second);
|
||||
delete pair;
|
||||
}
|
||||
} break;
|
||||
case Operation::force: {
|
||||
std::tuple<bool, K, V>* tuple = static_cast<std::tuple<bool, K, V>*>(entry.second);
|
||||
const std::tuple<bool, K, V>& t = *tuple;
|
||||
handleForceRecord(std::get<1>(t), std::get<2>(t), std::get<0>(t));
|
||||
delete tuple;
|
||||
}
|
||||
break;
|
||||
} break;
|
||||
case Operation::drop:
|
||||
handleDrop();
|
||||
break;
|
||||
@ -877,8 +869,7 @@ void LMDBAL::Cache<K, V>::handleTransactionEntry(const Entry& entry) {
|
||||
const std::tuple<bool, SizeType, std::map<K, V>>& t = * tuple;
|
||||
handleAddRecords(std::get<2>(t), std::get<0>(t), std::get<1>(t));
|
||||
delete tuple;
|
||||
}
|
||||
break;
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
|
109
src/cursor.h
Normal file
109
src/cursor.h
Normal file
@ -0,0 +1,109 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#ifndef LMDBAL_CURSOR_H
|
||||
#define LMDBAL_CURSOR_H
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "lmdb.h"
|
||||
#include "base.h"
|
||||
#include "storage.h"
|
||||
#include "transaction.h"
|
||||
|
||||
namespace LMDBAL {
|
||||
|
||||
template <class K, class V>
|
||||
class Cursor {
|
||||
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);
|
||||
Cursor(const Cursor& other) = delete;
|
||||
Cursor(Cursor&& other);
|
||||
~Cursor();
|
||||
|
||||
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();
|
||||
std::pair<K, V> last();
|
||||
std::pair<K, V> next();
|
||||
std::pair<K, V> prev();
|
||||
std::pair<K, V> current() const;
|
||||
bool set(const K& target);
|
||||
|
||||
void first(K& key, V& value);
|
||||
void last(K& key, V& value);
|
||||
void next(K& key, V& value);
|
||||
void prev(K& key, V& value);
|
||||
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
|
661
src/cursor.hpp
Normal file
661
src/cursor.hpp
Normal file
@ -0,0 +1,661 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#ifndef LMDBAL_CURSOR_HPP
|
||||
#define LMDBAL_CURSOR_HPP
|
||||
|
||||
#include "cursor.h"
|
||||
#include <iostream>
|
||||
|
||||
/**
|
||||
* \class LMDBAL::Cursor
|
||||
* \brief An object to iterate storages.
|
||||
*
|
||||
* \tparam K type of the keys in the storage that this cursor would iterate
|
||||
* \tparam V type of the values in the storage that this cursor would iterate
|
||||
*
|
||||
* Cursor allowes you to perform sequential querries, navigate from one element to another at reduced operation price.
|
||||
* For now Cursors are read only but in the future you might also be able to write to the storage with them.
|
||||
*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
static uint32_t idCounter = 0;
|
||||
|
||||
/**
|
||||
* \brief Creates a cursor
|
||||
*
|
||||
* \param[in] parent a storage that created this cursor
|
||||
*/
|
||||
template<class K, class V>
|
||||
LMDBAL::Cursor<K, V>::Cursor(Storage<K, V>* parent):
|
||||
storage(parent),
|
||||
cursor(nullptr),
|
||||
state(closed),
|
||||
id(++idCounter)
|
||||
{
|
||||
storage->cursors[id] = this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Creates an empty cursor.
|
||||
*
|
||||
* It's not usable, but can exist just to be a target of moves
|
||||
*/
|
||||
template<class K, class V>
|
||||
LMDBAL::Cursor<K, V>::Cursor():
|
||||
storage(nullptr),
|
||||
cursor(nullptr),
|
||||
state(closed),
|
||||
id(0)
|
||||
{}
|
||||
|
||||
/**
|
||||
* \brief Moves from another 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)
|
||||
{
|
||||
other.terminated();
|
||||
if (id != 0)
|
||||
storage->cursors[id] = this;
|
||||
|
||||
other.freed();
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief A private function that turns cursor into an empty one
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
template<class K, class V>
|
||||
LMDBAL::Cursor<K, V>& LMDBAL::Cursor<K, V>::operator = (Cursor&& other) {
|
||||
terminated();
|
||||
|
||||
if (id != 0)
|
||||
storage->cursors.erase(id);
|
||||
|
||||
storage = other.storage;
|
||||
cursor = other.cursor;
|
||||
state = other.state;
|
||||
id = other.id;
|
||||
|
||||
if (id != 0) {
|
||||
other.freed();
|
||||
other.state = closed;
|
||||
|
||||
storage->cursors[id] = this;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Destroys a 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Turns cursor into an empty one, releasing resources
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
template<class K, class V>
|
||||
void LMDBAL::Cursor<K, V>::drop () {
|
||||
close();
|
||||
|
||||
if (id != 0)
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Queries the first element in the storage
|
||||
*
|
||||
* Notifies the storage of the queried element calling LMDBAL::Storage::discoveredRecord()
|
||||
*
|
||||
* \param[out] key a reference to an object the key of queried element is going to be assigned
|
||||
* \param[out] value a reference to an object the value of queried element is going to be assigned
|
||||
*
|
||||
* \exception LMDBAL::CursorNotReady thrown if you try to call this method on a closed cursor
|
||||
* \exception LMDBAL::NotFound thrown if there are no elements in the storage
|
||||
* \exception LMDBAL::Unknown thrown if there was some unexpected problem with lmdb
|
||||
*/
|
||||
template<class K, class V>
|
||||
void LMDBAL::Cursor<K, V>::first (K& key, V& value) {
|
||||
operateCursorRead(key, value, MDB_FIRST, firstMethodName, firstOperationName);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Queries the last element in the storage
|
||||
*
|
||||
* Notifies the storage of the queried element calling LMDBAL::Storage::discoveredRecord()
|
||||
*
|
||||
* \param[out] key a reference to an object the key of queried element is going to be assigned
|
||||
* \param[out] value a reference to an object the value of queried element is going to be assigned
|
||||
*
|
||||
* \exception LMDBAL::CursorNotReady thrown if you try to call this method on a closed cursor
|
||||
* \exception LMDBAL::NotFound thrown if there are no elements in the storage
|
||||
* \exception LMDBAL::Unknown thrown if there was some unexpected problem with lmdb
|
||||
*/
|
||||
template<class K, class V>
|
||||
void LMDBAL::Cursor<K, V>::last (K& key, V& value) {
|
||||
operateCursorRead(key, value, MDB_LAST, lastMethodName, lastOperationName);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Queries the next element from the storage
|
||||
*
|
||||
* If there was no operation before this method positions the cursor on the first element and returns it
|
||||
* so, it's basically doing the same as LMDBAL::Cursor::first(K key, V value).
|
||||
*
|
||||
* It will also throw LMDBAL::NotFound if you call this method being on the last element
|
||||
* or if there are no elements in the database.
|
||||
*
|
||||
* Notifies the storage of the queried element calling LMDBAL::Storage::discoveredRecord()
|
||||
*
|
||||
* \param[out] key a reference to an object the key of queried element is going to be assigned
|
||||
* \param[out] value a reference to an object the value of queried element is going to be assigned
|
||||
*
|
||||
* \exception LMDBAL::CursorNotReady thrown if you try to call this method on a closed cursor
|
||||
* \exception LMDBAL::NotFound thrown if the cursor already was on the last element or if there are no elements in the storage
|
||||
* \exception LMDBAL::Unknown thrown if there was some unexpected problem with lmdb
|
||||
*/
|
||||
template<class K, class V>
|
||||
void LMDBAL::Cursor<K, V>::next (K& key, V& value) {
|
||||
operateCursorRead(key, value, MDB_NEXT, nextMethodName, nextOperationName);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Queries the previous element from the storage
|
||||
*
|
||||
* If there was no operation before this method positions the cursor on the last element and returns it
|
||||
* so, it's basically doing the same as LMDBAL::Cursor::last(K key, V value).
|
||||
*
|
||||
* It will also throw LMDBAL::NotFound if you call this method being on the first element
|
||||
* or if there are no elements in the database.
|
||||
*
|
||||
* Notifies the storage of the queried element calling LMDBAL::Storage::discoveredRecord()
|
||||
*
|
||||
* \param[out] key a reference to an object the key of queried element is going to be assigned
|
||||
* \param[out] value a reference to an object the value of queried element is going to be assigned
|
||||
*
|
||||
* \exception LMDBAL::CursorNotReady thrown if you try to call this method on a closed cursor
|
||||
* \exception LMDBAL::NotFound thrown if the cursor already was on the first element or if there are no elements in the storage
|
||||
* \exception LMDBAL::Unknown thrown if there was some unexpected problem with lmdb
|
||||
*/
|
||||
template<class K, class V>
|
||||
void LMDBAL::Cursor<K, V>::prev (K& key, V& value) {
|
||||
operateCursorRead(key, value, MDB_PREV, prevMethodName, prevOperationName);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns current cursor element from the storage
|
||||
*
|
||||
* If there was no operation before this method throws LMDBAL::Unknown for some reason
|
||||
*
|
||||
* Notifies the storage of the queried element calling LMDBAL::Storage::discoveredRecord()
|
||||
*
|
||||
* \param[out] key a reference to an object the key of queried element is going to be assigned
|
||||
* \param[out] value a reference to an object the value of queried element is going to be assigned
|
||||
*
|
||||
* \exception LMDBAL::CursorNotReady thrown if you try to call this method on a closed cursor
|
||||
* \exception LMDBAL::NotFound probably never thrown but there might be still some corner case I don't know about
|
||||
* \exception LMDBAL::Unknown thrown if there was some unexpected problem with lmdb
|
||||
*/
|
||||
template<class K, class V>
|
||||
void LMDBAL::Cursor<K, V>::current (K& key, V& value) const {
|
||||
operateCursorRead(key, value, MDB_GET_CURRENT, currentMethodName, currentOperationName);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Queries the first element in the storage
|
||||
*
|
||||
* Notifies the storage of the queried element calling LMDBAL::Storage::discoveredRecord()
|
||||
*
|
||||
* \returns std::pair where first is element key and second is element value
|
||||
*
|
||||
* \exception LMDBAL::CursorNotReady thrown if you try to call this method on a closed cursor
|
||||
* \exception LMDBAL::NotFound thrown if there are no elements in the storage
|
||||
* \exception LMDBAL::Unknown thrown if there was some unexpected problem with lmdb
|
||||
*/
|
||||
template<class K, class V>
|
||||
std::pair<K, V> LMDBAL::Cursor<K, V>::first () {
|
||||
std::pair<K, V> result;
|
||||
operateCursorRead(result.first, result.second, MDB_FIRST, firstMethodName, firstOperationName);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Queries the last element in the storage
|
||||
*
|
||||
* Notifies the storage of the queried element calling LMDBAL::Storage::discoveredRecord()
|
||||
*
|
||||
* \returns std::pair where first is element key and second is element value
|
||||
*
|
||||
* \exception LMDBAL::CursorNotReady thrown if you try to call this method on a closed cursor
|
||||
* \exception LMDBAL::NotFound thrown if there are no elements in the storage
|
||||
* \exception LMDBAL::Unknown thrown if there was some unexpected problem with lmdb
|
||||
*/
|
||||
template<class K, class V>
|
||||
std::pair<K, V> LMDBAL::Cursor<K, V>::last () {
|
||||
std::pair<K, V> result;
|
||||
operateCursorRead(result.first, result.second, MDB_LAST, lastMethodName, lastOperationName);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Queries the next element from the storage
|
||||
*
|
||||
* If there was no operation before this method positions the cursor on the first element and returns it
|
||||
* so, it's basically doing the same as LMDBAL::Cursor::first().
|
||||
*
|
||||
* It will also throw LMDBAL::NotFound if you call this method being on the last element
|
||||
* or if there are no elements in the database.
|
||||
*
|
||||
* Notifies the storage of the queried element calling LMDBAL::Storage::discoveredRecord()
|
||||
*
|
||||
* \returns std::pair where first is element key and second is element value
|
||||
*
|
||||
* \exception LMDBAL::CursorNotReady thrown if you try to call this method on a closed cursor
|
||||
* \exception LMDBAL::NotFound thrown if the cursor already was on the last element or if there are no elements in the storage
|
||||
* \exception LMDBAL::Unknown thrown if there was some unexpected problem with lmdb
|
||||
*/
|
||||
template<class K, class V>
|
||||
std::pair<K, V> LMDBAL::Cursor<K, V>::next () {
|
||||
std::pair<K, V> result;
|
||||
operateCursorRead(result.first, result.second, MDB_NEXT, nextMethodName, nextOperationName);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Queries the previous element from the storage
|
||||
*
|
||||
* If there was no operation before this method positions the cursor on the last element and returns it
|
||||
* so, it's basically doing the same as LMDBAL::Cursor::last().
|
||||
*
|
||||
* It will also throw LMDBAL::NotFound if you call this method being on the first element
|
||||
* or if there are no elements in the database.
|
||||
*
|
||||
* Notifies the storage of the queried element calling LMDBAL::Storage::discoveredRecord()
|
||||
*
|
||||
* \returns std::pair where first is element key and second is element value
|
||||
*
|
||||
* \exception LMDBAL::CursorNotReady thrown if you try to call this method on a closed cursor
|
||||
* \exception LMDBAL::NotFound thrown if the cursor already was on the first element or if there are no elements in the storage
|
||||
* \exception LMDBAL::Unknown thrown if there was some unexpected problem with lmdb
|
||||
*/
|
||||
template<class K, class V>
|
||||
std::pair<K, V> LMDBAL::Cursor<K, V>::prev () {
|
||||
std::pair<K, V> result;
|
||||
operateCursorRead(result.first, result.second, MDB_PREV, prevMethodName, prevOperationName);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns current cursor element from the storage
|
||||
*
|
||||
* If there was no operation before this method throws LMDBAL::Unknown for some reason
|
||||
*
|
||||
* Notifies the storage of the queried element calling LMDBAL::Storage::discoveredRecord()
|
||||
*
|
||||
* \returns std::pair where first is element key and second is element value
|
||||
*
|
||||
* \exception LMDBAL::CursorNotReady thrown if you try to call this method on a closed cursor
|
||||
* \exception LMDBAL::NotFound probably never thrown but there might be still some corner case I don't know about
|
||||
* \exception LMDBAL::Unknown thrown if there was no positioning operation before of if there was some unexpected problem with lmdb
|
||||
*/
|
||||
template<class K, class V>
|
||||
std::pair<K, V> LMDBAL::Cursor<K, V>::current () const {
|
||||
std::pair<K, V> result;
|
||||
operateCursorRead(result.first, result.second, MDB_GET_CURRENT, currentMethodName, currentOperationName);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Sets cursors to the defined position
|
||||
*
|
||||
* If exactly the same element wasn't found it sets the cursor to the first
|
||||
* element greater then the key and returns false
|
||||
*
|
||||
* \param[in] key a key of the element you would like the cursor to be
|
||||
* \returns true if the exact value was found, false otherwise
|
||||
*
|
||||
* \exception LMDBAL::CursorNotReady thrown if you try to call this method on a closed cursor
|
||||
* \exception LMDBAL::Unknown thrown if there was some unexpected problem
|
||||
*/
|
||||
template<class K, class V>
|
||||
bool LMDBAL::Cursor<K, V>::set (const K& key) {
|
||||
if (state == closed)
|
||||
storage->throwCursorNotReady(setMethodName);
|
||||
|
||||
MDB_val mdbKey = storage->keySerializer.setData(key);
|
||||
int result = storage->_mdbCursorSet(cursor, mdbKey);
|
||||
if (result == MDB_SUCCESS)
|
||||
return true;
|
||||
else if (result == MDB_NOTFOUND)
|
||||
return false;
|
||||
|
||||
storage->throwUnknown(result);
|
||||
return false; //unreachable, just to suppress the warning
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief a private mothod that actually doing all the reading
|
||||
*
|
||||
* Queries the storage, deserializes the output, notifies the storage of the queried element calling LMDBAL::Storage::discoveredRecord()
|
||||
*
|
||||
* \param[out] key a reference to an object the key of queried element is going to be assigned
|
||||
* \param[out] value a reference to an object the value of queried element is going to be assigned
|
||||
* \param[in] operation LMDB cursor <a class="el" href="http://www.lmdb.tech/doc/group__mdb.html#ga1206b2af8b95e7f6b0ef6b28708c9127">operation code</a>
|
||||
* \param[in] methodName a name of the method you called it from, just for the exception message if something goes not as expected
|
||||
* \param[in] operationName a name of the opeartion, just for the exception message if something goes not as expected
|
||||
*
|
||||
* \exception LMDBAL::CursorNotReady thrown if you try to call this method on a closed cursor
|
||||
* \exception LMDBAL::NotFound mostly thrown if the query wasn't found
|
||||
* \exception LMDBAL::Unknown mostly thrown if there was some unexpected problem with lmdb
|
||||
*/
|
||||
template<class K, class V>
|
||||
void LMDBAL::Cursor<K, V>::operateCursorRead(
|
||||
K& key,
|
||||
V& value,
|
||||
MDB_cursor_op operation,
|
||||
const std::string& methodName,
|
||||
const std::string& operationName
|
||||
) const {
|
||||
if (state == closed)
|
||||
storage->throwCursorNotReady(methodName);
|
||||
|
||||
MDB_val mdbKey, mdbValue;
|
||||
int result = storage->_mdbCursorGet(cursor, mdbKey, mdbValue, operation);
|
||||
if (result != MDB_SUCCESS)
|
||||
storage->throwNotFoundOrUnknown(result, operationName);
|
||||
|
||||
storage->keySerializer.deserialize(mdbKey, key);
|
||||
storage->valueSerializer.deserialize(mdbValue, value);
|
||||
|
||||
if (state == openedPrivate)
|
||||
storage->discoveredRecord(key, value);
|
||||
else
|
||||
storage->discoveredRecord(key, value, storage->_mdbCursorTxn(cursor));
|
||||
}
|
||||
|
||||
#endif //LMDBAL_CURSOR_HPP
|
@ -55,6 +55,31 @@ std::string LMDBAL::Closed::getMessage() const {
|
||||
return msg;
|
||||
}
|
||||
|
||||
LMDBAL::CursorNotReady::CursorNotReady(
|
||||
const std::string& p_operation,
|
||||
const std::string& p_dbName,
|
||||
const std::string& p_tableName
|
||||
):
|
||||
Exception(),
|
||||
operation(p_operation),
|
||||
dbName(p_dbName),
|
||||
tableName(p_tableName) {}
|
||||
|
||||
std::string LMDBAL::CursorNotReady::getMessage() const {
|
||||
std::string msg = "An attempt to perform operation " + operation
|
||||
+ " on closed cursor that belongs to the table " + tableName
|
||||
+ " within database " + dbName;
|
||||
return msg;
|
||||
}
|
||||
|
||||
LMDBAL::CursorEmpty::CursorEmpty(const std::string & operation):
|
||||
Exception(),
|
||||
operation(operation) {}
|
||||
|
||||
std::string LMDBAL::CursorEmpty::getMessage() const {
|
||||
return "An attempt to perform an operation \"" + operation + "\" on an empty cursor";
|
||||
}
|
||||
|
||||
LMDBAL::Opened::Opened(const std::string& p_dbName, const std::string& p_action):
|
||||
Exception(),
|
||||
dbName(p_dbName),
|
||||
@ -95,7 +120,6 @@ std::string LMDBAL::StorageDuplicate::getMessage() const {
|
||||
+ " but the database already has a storage with given name";
|
||||
}
|
||||
|
||||
|
||||
LMDBAL::Exist::Exist(
|
||||
const std::string& p_key,
|
||||
const std::string& p_dbName,
|
||||
@ -113,6 +137,29 @@ std::string LMDBAL::Exist::getMessage() const {
|
||||
+ " but it already has an element with given id";
|
||||
}
|
||||
|
||||
LMDBAL::TransactionTerminated::TransactionTerminated(
|
||||
const std::string& dbName,
|
||||
const std::string& tableName,
|
||||
const std::string& action
|
||||
) :
|
||||
dbName(dbName),
|
||||
tableName(tableName),
|
||||
action(action)
|
||||
{}
|
||||
|
||||
std::string LMDBAL::TransactionTerminated::getMessage() const {
|
||||
std::string result = "Error";
|
||||
|
||||
if (!action.empty())
|
||||
result += " perfoming action " + action;
|
||||
|
||||
result += " in database " + dbName;
|
||||
result += ", table " + tableName;
|
||||
result += ". The transaction is already terminated";
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
LMDBAL::Unknown::Unknown(
|
||||
const std::string& p_dbName,
|
||||
const std::string& message,
|
||||
|
@ -35,7 +35,7 @@ public:
|
||||
|
||||
virtual std::string getMessage() const = 0; /**<\brief returns exception message*/
|
||||
|
||||
const char* what() const noexcept( true ) override;
|
||||
const char* what() const noexcept( true ) override; /**<\brief system exception method that is actually called to show the message*/
|
||||
};
|
||||
|
||||
/**
|
||||
@ -76,6 +76,44 @@ private:
|
||||
std::optional<std::string> tableName;
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Thrown if the cursor was operated in closed state
|
||||
*/
|
||||
class CursorNotReady : public Exception {
|
||||
public:
|
||||
/**
|
||||
* \brief Creates exception
|
||||
*
|
||||
* \param operation - text name of the method that was called on closed cursor
|
||||
* \param dbName - name of the database
|
||||
* \param tableName - name of the storage owning the cursor
|
||||
*/
|
||||
CursorNotReady(const std::string& operation, const std::string& dbName, const std::string& tableName);
|
||||
|
||||
std::string getMessage() const;
|
||||
private:
|
||||
std::string operation;
|
||||
std::string dbName;
|
||||
std::string tableName;
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Thrown if an empty cursor was somehow operated
|
||||
*/
|
||||
class CursorEmpty : public Exception {
|
||||
public:
|
||||
/**
|
||||
* \brief Creates exception
|
||||
*
|
||||
* \param operation - text name of the method that was called on an empty cursor
|
||||
*/
|
||||
CursorEmpty(const std::string& operation);
|
||||
|
||||
std::string getMessage() const;
|
||||
private:
|
||||
std::string operation;
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Thrown if something in the database was called on opened state and it is not supported
|
||||
*/
|
||||
@ -156,6 +194,27 @@ private:
|
||||
std::string tableName;
|
||||
};
|
||||
|
||||
/**
|
||||
* Thrown if there was an attempt to perform action using terminated transaction
|
||||
*/
|
||||
class TransactionTerminated : public Exception {
|
||||
public:
|
||||
/**
|
||||
* \brief Creates exception
|
||||
*
|
||||
* \param dbName - name of the database
|
||||
* \param tableName - name of the storage that was operated with
|
||||
* \param action - optional action, just to enrich the exception message
|
||||
*/
|
||||
TransactionTerminated(const std::string& dbName, const std::string& tableName, const std::string& action = "");
|
||||
|
||||
std::string getMessage() const;
|
||||
private:
|
||||
std::string dbName;
|
||||
std::string tableName;
|
||||
std::string action;
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Thrown if something unexpected happened
|
||||
*/
|
||||
|
19
src/serializer/CMakeLists.txt
Normal file
19
src/serializer/CMakeLists.txt
Normal file
@ -0,0 +1,19 @@
|
||||
set(HEADERS
|
||||
serializer.h
|
||||
serializer.hpp
|
||||
serializer_uint8.hpp
|
||||
serializer_uint16.hpp
|
||||
serializer_uint32.hpp
|
||||
serializer_uint64.hpp
|
||||
serializer_int8.hpp
|
||||
serializer_int16.hpp
|
||||
serializer_int32.hpp
|
||||
serializer_int64.hpp
|
||||
serializer_float.hpp
|
||||
serializer_double.hpp
|
||||
serializer_stdstring.hpp
|
||||
serializer_qstring.hpp
|
||||
serializer_qbytearray.hpp
|
||||
)
|
||||
|
||||
install(FILES ${HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_LOW})
|
@ -21,6 +21,22 @@
|
||||
|
||||
#include "serializer.h"
|
||||
|
||||
/**
|
||||
* \class LMDBAL::Serializer
|
||||
* \brief A class handling serialization/deserialization
|
||||
*
|
||||
* A class that is constructed in every LMDBAL::Storage
|
||||
* to serialize or deserialize keys and values.
|
||||
*
|
||||
* It serializes to and deserializes from <a class="el" href="http://www.lmdb.tech/doc/group__mdb.html#structMDB__val">MDB_val</a>
|
||||
*
|
||||
* \tparam K type of the keys of the storage
|
||||
* \tparam V type of the values of the storage
|
||||
*/
|
||||
|
||||
/**
|
||||
* \brief Creates an empty Serializer
|
||||
*/
|
||||
template<class T>
|
||||
LMDBAL::Serializer<T>::Serializer() :
|
||||
bytes(),
|
||||
@ -30,6 +46,13 @@ LMDBAL::Serializer<T>::Serializer() :
|
||||
buffer.open(QIODevice::ReadWrite);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Creates a Serializer with some data in it
|
||||
*
|
||||
* The data automatically gets serialized
|
||||
*
|
||||
* \param[in] value - a value that is assigned to the serializer
|
||||
*/
|
||||
template<class T>
|
||||
LMDBAL::Serializer<T>::Serializer(const T& value) :
|
||||
bytes(),
|
||||
@ -40,11 +63,23 @@ LMDBAL::Serializer<T>::Serializer(const T& value) :
|
||||
_setData(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Destoys the serializer
|
||||
*/
|
||||
template<class T>
|
||||
LMDBAL::Serializer<T>::~Serializer() {
|
||||
buffer.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Sets the data to the seriazer
|
||||
*
|
||||
* This is a normal way to seriaze value
|
||||
*
|
||||
* \param[in] value - a value you want to serialize
|
||||
*
|
||||
* \returns serialized value
|
||||
*/
|
||||
template<class T>
|
||||
MDB_val LMDBAL::Serializer<T>::setData(const T& value) {
|
||||
clear();
|
||||
@ -52,6 +87,15 @@ MDB_val LMDBAL::Serializer<T>::setData(const T& value) {
|
||||
return getData();
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Deserializes value
|
||||
*
|
||||
* This is a normal way to deseriaze value
|
||||
*
|
||||
* \param[in] value - a value you want to deserialize
|
||||
*
|
||||
* \returns deserialized value
|
||||
*/
|
||||
template<class T>
|
||||
T LMDBAL::Serializer<T>::deserialize(const MDB_val& value) {
|
||||
T result;
|
||||
@ -60,6 +104,14 @@ T LMDBAL::Serializer<T>::deserialize(const MDB_val& value) {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Deserializes value
|
||||
*
|
||||
* This is a normal way to deseriaze value
|
||||
*
|
||||
* \param[in] value - a value you want to deserialize
|
||||
* \param[out] result - deserialized value
|
||||
*/
|
||||
template<class T>
|
||||
void LMDBAL::Serializer<T>::deserialize(const MDB_val& value, T& result) {
|
||||
clear();
|
||||
@ -67,11 +119,21 @@ void LMDBAL::Serializer<T>::deserialize(const MDB_val& value, T& result) {
|
||||
stream >> result;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Private function that handles serialization
|
||||
*
|
||||
* \param[in] value - a value you want to serialize
|
||||
*/
|
||||
template<class T>
|
||||
void LMDBAL::Serializer<T>::_setData(const T& value) {
|
||||
stream << value;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Clears the state of serializer
|
||||
*
|
||||
* Normally you don't need to call this function
|
||||
*/
|
||||
template<class T>
|
||||
void LMDBAL::Serializer<T>::clear() {
|
||||
if (buffer.pos() > 0) {
|
||||
@ -79,6 +141,15 @@ void LMDBAL::Serializer<T>::clear() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns the data if it already was serialized
|
||||
*
|
||||
* Normally you don't need to call this function
|
||||
*
|
||||
* This may be usefull if you called LMDBAL::Serilizer::setData() but lost the result
|
||||
*
|
||||
* \returns Serialized data
|
||||
*/
|
||||
template<class T>
|
||||
MDB_val LMDBAL::Serializer<T>::getData() {
|
||||
MDB_val val;
|
161
src/storage.cpp
161
src/storage.cpp
@ -23,6 +23,8 @@
|
||||
/**
|
||||
* \class LMDBAL::iStorage
|
||||
*
|
||||
* \brief Storage interface
|
||||
*
|
||||
* This is a interface-like class, it's designed to be an inner database interface to
|
||||
* be used as a polymorphic entity, and provide protected interaction with the database
|
||||
* from the heirs code
|
||||
@ -30,11 +32,16 @@
|
||||
|
||||
/**
|
||||
* \brief Constructs a storage interface
|
||||
*
|
||||
* \param[in] parent - LMDBAL::Base pointer for the owning database (borrowed)
|
||||
* \param[in] name - the name of the storage
|
||||
* \param[in] duplicates - true if key duplicates are allowed (false by default)
|
||||
*/
|
||||
LMDBAL::iStorage::iStorage(const std::string& p_name, Base* parent):
|
||||
LMDBAL::iStorage::iStorage(Base* parent, const std::string& name, bool duplicates):
|
||||
dbi(),
|
||||
db(parent),
|
||||
name(p_name)
|
||||
name(name),
|
||||
duplicates(duplicates)
|
||||
{}
|
||||
|
||||
/**
|
||||
@ -42,6 +49,31 @@ LMDBAL::iStorage::iStorage(const std::string& p_name, Base* parent):
|
||||
*/
|
||||
LMDBAL::iStorage::~iStorage() {}
|
||||
|
||||
/**
|
||||
* \brief A private virtual function I need to close each storage in the database
|
||||
*/
|
||||
void LMDBAL::iStorage::close() {
|
||||
mdb_dbi_close(db->environment, dbi);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Checks if the transaction is still active, returns inner TransactionID
|
||||
*
|
||||
* This method is for internal usage only
|
||||
*
|
||||
* \param[in] txn - a transaction, you want to extract ID from
|
||||
* \param[in] action - a description of what you're going to do, in case of exception the error description will be verbose
|
||||
* \returns - Transaction inner TransactionID
|
||||
*
|
||||
* \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 {
|
||||
if (!txn.isActive())
|
||||
throw TransactionTerminated(db->name, name, action);
|
||||
|
||||
return txn.txn;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Drops content of a storage interface
|
||||
*
|
||||
@ -53,7 +85,7 @@ LMDBAL::iStorage::~iStorage() {}
|
||||
void LMDBAL::iStorage::drop() {
|
||||
ensureOpened(dropMethodName);
|
||||
|
||||
TransactionID txn = db->beginTransaction();
|
||||
TransactionID txn = beginTransaction();
|
||||
int rc = iStorage::drop(txn);
|
||||
if (rc != MDB_SUCCESS) {
|
||||
abortTransaction(txn);
|
||||
@ -61,6 +93,7 @@ void LMDBAL::iStorage::drop() {
|
||||
}
|
||||
|
||||
db->commitTransaction(txn);
|
||||
handleDrop();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -75,6 +108,21 @@ int LMDBAL::iStorage::drop(TransactionID transaction) {
|
||||
return mdb_drop(transaction, dbi, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Drops content of a storage interface (public transaction variant)
|
||||
*
|
||||
* Just performs content drop
|
||||
*
|
||||
* \param[in] txn - transaction ID, must be writable transaction!
|
||||
* \returns MDB_SUCCESS if everything went fine, MDB_<error> code otherwise
|
||||
*
|
||||
* \exception LMDBAL::TransactionTerminated thrown if the transaction was not active
|
||||
*/
|
||||
int LMDBAL::iStorage::drop(const WriteTransaction& txn) {
|
||||
ensureOpened(dropMethodName);
|
||||
return drop(extractTransactionId(txn, dropMethodName));
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Helper function, thows exception if the database is not opened
|
||||
*
|
||||
@ -112,7 +160,7 @@ LMDBAL::SizeType LMDBAL::iStorage::count() const {
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Storage size (transaction variant)
|
||||
* \brief Storage size (private transaction variant)
|
||||
*
|
||||
* \param[in] txn - transaction ID, can be read-only transaction
|
||||
* \returns amount of records in the storage
|
||||
@ -128,6 +176,21 @@ LMDBAL::SizeType LMDBAL::iStorage::count(TransactionID txn) const {
|
||||
return stat.ms_entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Storage size (public transaction variant)
|
||||
*
|
||||
* \param[in] txn - transaction, can be read-only transaction
|
||||
* \returns amount of records in the storage
|
||||
*
|
||||
* \exception LMDBAL::Closed thrown if the database was closed
|
||||
* \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 {
|
||||
ensureOpened(countMethodName);
|
||||
return count(extractTransactionId(txn, countMethodName));
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Throws LMDBAL::Exist or LMDBAL::Unknown (transaction vairiant)
|
||||
*
|
||||
@ -245,6 +308,18 @@ bool LMDBAL::iStorage::isDBOpened() const {
|
||||
void LMDBAL::iStorage::throwUnknown(int rc) const {
|
||||
throw Unknown(db->name, mdb_strerror(rc), name);}
|
||||
|
||||
/**
|
||||
* \brief Throws LMDBAL::Unknown
|
||||
*
|
||||
* Helper function ment to be used in heirs and reduce the code a bit
|
||||
*
|
||||
* \param[in] message - a message you wish to appear in the exception reason
|
||||
*
|
||||
* \exception LMDBAL::Unknown thrown everytime
|
||||
*/
|
||||
void LMDBAL::iStorage::throwUnknown(const std::string& message) const {
|
||||
throw Unknown(db->name, message, name);}
|
||||
|
||||
/**
|
||||
* \brief Throws LMDBAL::Exist
|
||||
*
|
||||
@ -269,6 +344,18 @@ void LMDBAL::iStorage::throwDuplicate(const std::string& key) const {
|
||||
void LMDBAL::iStorage::throwNotFound(const std::string& key) const {
|
||||
throw NotFound(key, db->name, name);}
|
||||
|
||||
/**
|
||||
* \brief Throws LMDBAL::CursorNotReady
|
||||
*
|
||||
* Helper function ment to be used in heirs and reduce the code a bit
|
||||
*
|
||||
* \param[in] method - called cursor method name, just to show in std::exception::what() message
|
||||
*
|
||||
* \exception LMDBAL::CursorNotReady thrown everytime
|
||||
*/
|
||||
void LMDBAL::iStorage::throwCursorNotReady(const std::string& method) const {
|
||||
throw CursorNotReady(method, db->name, name);}
|
||||
|
||||
/**
|
||||
* \brief Begins read-only transaction
|
||||
*
|
||||
@ -352,5 +439,71 @@ void LMDBAL::iStorage::transactionCommited(LMDBAL::TransactionID txn) {
|
||||
void LMDBAL::iStorage::transactionAborted(LMDBAL::TransactionID txn) const {
|
||||
UNUSED(txn);}
|
||||
|
||||
/**
|
||||
* \brief A method where database additionally handles drop
|
||||
*
|
||||
* 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() {}
|
||||
|
||||
int LMDBAL::iStorage::_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) {
|
||||
return mdb_put(txn, dbi, &key, &data, flags);
|
||||
}
|
||||
|
||||
int LMDBAL::iStorage::_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) {
|
||||
return mdb_del(txn, dbi, &key, NULL);
|
||||
}
|
||||
|
||||
int LMDBAL::iStorage::_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 {
|
||||
return mdb_stat(txn, dbi, &stat);
|
||||
}
|
||||
|
||||
int LMDBAL::iStorage::_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 {
|
||||
return mdb_cursor_open(txn, dbi, cursor);
|
||||
}
|
||||
|
||||
void LMDBAL::iStorage::_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 {
|
||||
return mdb_cursor_get(cursor, &key, &data, operation);
|
||||
}
|
||||
|
||||
int LMDBAL::iStorage::_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) {
|
||||
return mdb_cursor_del(cursor, flags);
|
||||
}
|
||||
|
||||
int LMDBAL::iStorage::_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 {
|
||||
return mdb_cursor_renew(txn, cursor);
|
||||
}
|
||||
|
||||
MDB_txn* LMDBAL::iStorage::_mdbCursorTxn(MDB_cursor* cursor) const {
|
||||
return mdb_cursor_txn(cursor);
|
||||
}
|
||||
|
116
src/storage.h
116
src/storage.h
@ -19,22 +19,37 @@
|
||||
#ifndef LMDBAL_STORAGE_H
|
||||
#define LMDBAL_STORAGE_H
|
||||
|
||||
#include <type_traits>
|
||||
#include <cstring>
|
||||
|
||||
#include "base.h"
|
||||
#include "serializer.h"
|
||||
#include "cursor.h"
|
||||
#include "transaction.h"
|
||||
|
||||
class BaseTest;
|
||||
class DuplicatesTest;
|
||||
class CacheCursorTest;
|
||||
class StorageCursorTest;
|
||||
|
||||
namespace LMDBAL {
|
||||
|
||||
typedef uint32_t SizeType;
|
||||
|
||||
class iStorage {
|
||||
friend class Base;
|
||||
public:
|
||||
|
||||
protected:
|
||||
iStorage(const std::string& name, Base* parent);
|
||||
iStorage(Base* parent, const std::string& name, bool duplicates = false);
|
||||
virtual ~iStorage();
|
||||
|
||||
virtual int createStorage(MDB_txn * transaction) = 0;
|
||||
/**
|
||||
* \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;
|
||||
@ -46,9 +61,12 @@ protected:
|
||||
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);
|
||||
@ -56,20 +74,44 @@ protected:
|
||||
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(TransactionID transaction);
|
||||
virtual int drop(const WriteTransaction& txn);
|
||||
virtual SizeType count() const;
|
||||
virtual SizeType count(TransactionID txn) 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*/
|
||||
@ -82,8 +124,8 @@ protected:
|
||||
inline static const std::string addRecordsMethodName = "addRecords"; /**<\brief member function name, just for exceptions*/
|
||||
|
||||
protected:
|
||||
template <class T>
|
||||
int makeStorage(MDB_txn* transaction);
|
||||
template <class K, class V>
|
||||
int makeStorage(MDB_txn* transaction, bool duplicates = false);
|
||||
|
||||
template <class T>
|
||||
static std::string toString(const T& value);
|
||||
@ -91,41 +133,67 @@ protected:
|
||||
|
||||
template <class K, class V>
|
||||
class Storage : public iStorage {
|
||||
friend class ::BaseTest;
|
||||
friend class ::DuplicatesTest;
|
||||
friend class ::CacheCursorTest;
|
||||
friend class ::StorageCursorTest;
|
||||
friend class Base;
|
||||
friend class Cursor<K, V>;
|
||||
protected:
|
||||
Storage(const std::string& name, Base* parent);
|
||||
Storage(Base* parent, const std::string& name, bool duplicates = false);
|
||||
~Storage() override;
|
||||
|
||||
virtual void discoveredRecord(const K& key, const V& value) const;
|
||||
virtual void discoveredRecord(const K& key, const V& value, TransactionID txn) const;
|
||||
uint32_t flags() const;
|
||||
|
||||
virtual void addRecord(const K& key, const V& value, TransactionID txn);
|
||||
virtual bool forceRecord(const K& key, const V& value, TransactionID txn);
|
||||
virtual void changeRecord(const K& key, const V& value, TransactionID txn);
|
||||
virtual void removeRecord(const K& key, TransactionID txn);
|
||||
virtual bool checkRecord(const K& key, TransactionID txn) const;
|
||||
virtual void getRecord(const K& key, V& value, TransactionID txn) const;
|
||||
virtual V getRecord(const K& key, TransactionID txn) const;
|
||||
virtual std::map<K, V> readAll(TransactionID txn) const;
|
||||
virtual void readAll(std::map<K, V>& result, TransactionID txn) const;
|
||||
virtual void replaceAll(const std::map<K, V>& data, TransactionID txn);
|
||||
virtual uint32_t addRecords(const std::map<K, V>& data, TransactionID txn, bool overwrite = false);
|
||||
|
||||
public:
|
||||
using iStorage::drop;
|
||||
virtual void addRecord(const K& key, const V& value);
|
||||
virtual void addRecord(const K& key, const V& value, TransactionID txn);
|
||||
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
|
||||
virtual bool forceRecord(const K& key, const V& value, TransactionID txn);
|
||||
virtual bool forceRecord(const K& key, const V& value, const WriteTransaction& txn);
|
||||
virtual void changeRecord(const K& key, const V& value);
|
||||
virtual void changeRecord(const K& key, const V& value, TransactionID txn);
|
||||
virtual void changeRecord(const K& key, const V& value, const WriteTransaction& txn);
|
||||
virtual void removeRecord(const K& key);
|
||||
virtual void removeRecord(const K& key, TransactionID txn);
|
||||
virtual void removeRecord(const K& key, const WriteTransaction& txn);
|
||||
virtual bool checkRecord(const K& key) const; //checks if there is a record with given key
|
||||
virtual bool checkRecord(const K& key, TransactionID txn) const;
|
||||
virtual bool checkRecord(const K& key, const Transaction& txn) const;
|
||||
virtual void getRecord(const K& key, V& value) const;
|
||||
virtual void getRecord(const K& key, V& value, TransactionID txn) const;
|
||||
virtual void getRecord(const K& key, V& value, const Transaction& txn) const;
|
||||
virtual V getRecord(const K& key) const;
|
||||
virtual V getRecord(const K& key, TransactionID txn) const;
|
||||
virtual V getRecord(const K& key, const Transaction& txn) const;
|
||||
virtual std::map<K, V> readAll() const;
|
||||
virtual std::map<K, V> readAll(TransactionID txn) const;
|
||||
virtual std::map<K, V> readAll(const Transaction& txn) const;
|
||||
virtual void readAll(std::map<K, V>& result) const;
|
||||
virtual void readAll(std::map<K, V>& result, TransactionID txn) const;
|
||||
virtual void readAll(std::map<K, V>& result, const Transaction& txn) const;
|
||||
virtual void replaceAll(const std::map<K, V>& data);
|
||||
virtual void replaceAll(const std::map<K, V>& data, TransactionID txn);
|
||||
virtual void replaceAll(const std::map<K, V>& data, const WriteTransaction& txn);
|
||||
virtual uint32_t addRecords(const std::map<K, V>& data, bool overwrite = false);
|
||||
virtual uint32_t addRecords(const std::map<K, V>& data, TransactionID txn, bool overwrite = false);
|
||||
virtual uint32_t addRecords(const std::map<K, V>& data, const WriteTransaction& txn, bool overwrite = false);
|
||||
|
||||
Cursor<K, V> createCursor();
|
||||
void destroyCursor(Cursor<K, V>& cursor);
|
||||
|
||||
protected:
|
||||
Serializer<K>* keySerializer; /**<\brief internal object that would serialize and deserialize keys*/
|
||||
Serializer<V>* valueSerializer; /**<\brief internal object that would serialize and deserialize values*/
|
||||
mutable Serializer<K> keySerializer; /**<\brief internal object that would serialize and deserialize keys*/
|
||||
mutable Serializer<V> valueSerializer; /**<\brief internal object that would serialize and deserialize values*/
|
||||
std::map<uint32_t, Cursor<K, V>*> cursors; /**<\brief a set of cursors that has been created under this storage*/
|
||||
|
||||
int createStorage(MDB_txn* transaction) override;
|
||||
int open(MDB_txn* transaction) override;
|
||||
void close() override;
|
||||
};
|
||||
|
||||
}
|
||||
|
766
src/storage.hpp
766
src/storage.hpp
File diff suppressed because it is too large
Load Diff
154
src/transaction.cpp
Normal file
154
src/transaction.cpp
Normal file
@ -0,0 +1,154 @@
|
||||
#include "transaction.h"
|
||||
|
||||
/**
|
||||
* \class LMDBAL::Transaction
|
||||
* \brief Public read only transaction
|
||||
*
|
||||
* This class provides read only transactions you can use
|
||||
* to speed to your queries keeping them thread safe.
|
||||
* LMDBAL::Transaction is <b>NOT COPYABLE</b> but <b>MOVABLE</b>.
|
||||
* Transaction can be in one of two states: active or terminated.
|
||||
* The way to receive an active LMDBAL::Transaction is to call LMDBAL::Base::beginReadOnlyTransaction.
|
||||
*
|
||||
* Active transactions become terminated upon the call of LMDBAL::Transaction::terminate.
|
||||
* Active transactions automaticaly terminate themselves upon destruction.
|
||||
*
|
||||
* You <b>CAN NOT</b> use inactive transactions for any query.
|
||||
*/
|
||||
|
||||
/**
|
||||
* \brief Constructs inactive transaction
|
||||
*/
|
||||
LMDBAL::Transaction::Transaction():
|
||||
txn(nullptr),
|
||||
active(false),
|
||||
parent(nullptr)
|
||||
{}
|
||||
|
||||
/**
|
||||
* \brief Constructs an active transaction
|
||||
*/
|
||||
LMDBAL::Transaction::Transaction(TransactionID txn, const Base* parent) :
|
||||
txn(txn),
|
||||
active(true),
|
||||
parent(parent)
|
||||
{}
|
||||
|
||||
/**
|
||||
* \brief Moves transaction to a new object
|
||||
*/
|
||||
LMDBAL::Transaction::Transaction(Transaction&& other):
|
||||
txn(other.txn),
|
||||
active(other.active),
|
||||
parent(other.parent)
|
||||
{
|
||||
other.active = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Destroys transaction
|
||||
*/
|
||||
LMDBAL::Transaction::~Transaction() {
|
||||
terminate();
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Move-assigns transaction to the new object
|
||||
*/
|
||||
LMDBAL::Transaction& LMDBAL::Transaction::operator=(Transaction&& other) {
|
||||
terminate();
|
||||
|
||||
txn = other.txn;
|
||||
active = other.active;
|
||||
parent = other.parent;
|
||||
|
||||
other.active = false;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Terminates transaction if it was active
|
||||
*
|
||||
* Transaction becomes terminated after calling this method
|
||||
*/
|
||||
void LMDBAL::Transaction::terminate() {
|
||||
if (active) {
|
||||
parent->abortTransaction(txn);
|
||||
active = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns transaction states
|
||||
*
|
||||
* \returns true if the transaction is active, false otherwise
|
||||
*/
|
||||
bool LMDBAL::Transaction::isActive() const {
|
||||
return active; //todo may be it's better if I query it from DB?
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* \class LMDBAL::WriteTransaction
|
||||
* \brief Public writable transaction
|
||||
*
|
||||
* This class provides writable transactions you can use
|
||||
* to speed to your queries and modifications keeping them thread safe.
|
||||
* LMDBAL::WriteTransaction is <b>NOT COPYABLE</b> but <b>MOVABLE</b>.
|
||||
* Transaction can be in one of two states: active or terminated.
|
||||
* The way to receive an active LMDBAL::WriteTransaction is to call LMDBAL::Base::beginTransaction.
|
||||
* You can use LMDBAL::WriteTransaction for everything instead of LMDBAL::Transaction
|
||||
*
|
||||
* Active transactions become terminated upon the call of
|
||||
* LMDBAL::WriteTransaction::abort or LMDBAL::WriteTransaction::commit.
|
||||
* Calling LMDBAL::Transaction::terminate on LMDBAL::WriteTransaction
|
||||
* is exactly the same as calling LMDBAL::WriteTransaction::abort.
|
||||
*
|
||||
* Active transactions automaticaly terminate themselves upon destruction.
|
||||
* <b>For LMDBAL::WriteTransaction default behaviour upon destruction is to abort.</b>
|
||||
*
|
||||
* You <b>CAN NOT</b> use inactive transactions for any query.
|
||||
*/
|
||||
|
||||
/**
|
||||
* \brief Constructs active write transaction
|
||||
*/
|
||||
LMDBAL::WriteTransaction::WriteTransaction(TransactionID txn, Base* parent):
|
||||
Transaction(txn, parent)
|
||||
{}
|
||||
|
||||
/**
|
||||
* \brief Constructs inactive write transaction
|
||||
*/
|
||||
LMDBAL::WriteTransaction::WriteTransaction():
|
||||
Transaction()
|
||||
{}
|
||||
|
||||
/**
|
||||
* \brief Moves transaction to the new object
|
||||
*/
|
||||
LMDBAL::WriteTransaction::WriteTransaction(WriteTransaction&& other):
|
||||
Transaction(std::move(other))
|
||||
{}
|
||||
|
||||
/**
|
||||
* \brief Aborts transaction cancelling all changes
|
||||
*
|
||||
* Transaction becomes terminated after calling this method
|
||||
*/
|
||||
void LMDBAL::WriteTransaction::abort() {
|
||||
terminate();
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Commits transaction submitting all changes
|
||||
*
|
||||
* Transaction becomes terminated after calling this method
|
||||
*/
|
||||
void LMDBAL::WriteTransaction::commit() {
|
||||
if (active) {
|
||||
const_cast<Base*>(parent)->commitTransaction(txn);
|
||||
active = false;
|
||||
}
|
||||
}
|
46
src/transaction.h
Normal file
46
src/transaction.h
Normal file
@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include "base.h"
|
||||
|
||||
namespace LMDBAL {
|
||||
class iStorage;
|
||||
|
||||
class Transaction {
|
||||
friend class Base;
|
||||
friend class iStorage;
|
||||
public:
|
||||
explicit Transaction();
|
||||
explicit Transaction(Transaction&& other);
|
||||
Transaction(const Transaction& other) = delete;
|
||||
Transaction& operator = (const Transaction& other) = delete;
|
||||
Transaction& operator = (Transaction&& other);
|
||||
virtual ~Transaction();
|
||||
|
||||
void terminate();
|
||||
bool isActive() const;
|
||||
|
||||
protected:
|
||||
Transaction(TransactionID txn, const Base* parent);
|
||||
|
||||
protected:
|
||||
TransactionID txn; /**<\brief Transaction inner handler*/
|
||||
bool active; /**<\brief Transaction state*/
|
||||
const Base* parent; /**<\brief Pointer to the database this transaction belongs to*/
|
||||
};
|
||||
|
||||
class WriteTransaction : public Transaction {
|
||||
friend class Base;
|
||||
public:
|
||||
explicit WriteTransaction();
|
||||
explicit WriteTransaction(WriteTransaction&& other);
|
||||
WriteTransaction(const WriteTransaction& other) = delete;
|
||||
WriteTransaction& operator = (const WriteTransaction& other) = delete;
|
||||
|
||||
void commit();
|
||||
void abort();
|
||||
|
||||
protected:
|
||||
WriteTransaction(TransactionID txn, Base* parent);
|
||||
};
|
||||
|
||||
}
|
@ -7,11 +7,13 @@ add_executable(runUnitTests
|
||||
serialization.cpp
|
||||
storagetransaction.cpp
|
||||
cachetransaction.cpp
|
||||
storagecursor.cpp
|
||||
cachecursor.cpp
|
||||
duplicates.cpp
|
||||
)
|
||||
|
||||
target_compile_options(runUnitTests PRIVATE -fPIC)
|
||||
target_compile_options(runUnitTests PRIVATE -fPIC -Wall -Wextra -O0)
|
||||
|
||||
target_include_directories(runUnitTests PRIVATE ${CMAKE_SOURCE_DIR}/src)
|
||||
target_include_directories(runUnitTests PRIVATE ${Qt${QT_VERSION_MAJOR}_INCLUDE_DIRS})
|
||||
target_include_directories(runUnitTests PRIVATE ${Qt${QT_VERSION_MAJOR}Core_INCLUDE_DIRS})
|
||||
|
||||
@ -20,7 +22,6 @@ target_link_libraries(
|
||||
GTest::gtest_main
|
||||
${PROJECT_NAME}
|
||||
Qt${QT_VERSION_MAJOR}::Core
|
||||
lmdb
|
||||
)
|
||||
include(GoogleTest)
|
||||
gtest_discover_tests(runUnitTests)
|
||||
|
157
test/basic.cpp
157
test/basic.cpp
@ -5,6 +5,7 @@
|
||||
#include "cache.h"
|
||||
|
||||
#include <QString>
|
||||
#include <QVariant>
|
||||
|
||||
class BaseTest : public ::testing::Test {
|
||||
protected:
|
||||
@ -12,16 +13,23 @@ protected:
|
||||
::testing::Test(),
|
||||
t1(db->getStorage<uint32_t, uint32_t>("table1")),
|
||||
t2(db->getStorage<QString, QString>("table2")),
|
||||
c1(db->getCache<int8_t, std::string>("cache1")) {}
|
||||
c1(db->getCache<int8_t, std::string>("cache1")),
|
||||
c2(db->getCache<std::string, QVariant>("cache2")) {}
|
||||
|
||||
~BaseTest() {}
|
||||
|
||||
uint32_t getT1Flags() const {return t1->flags();}
|
||||
uint32_t getT2Flags() const {return t2->flags();}
|
||||
uint32_t getC1Flags() const {return c1->flags();}
|
||||
uint32_t getC2Flags() const {return c2->flags();}
|
||||
|
||||
static void SetUpTestSuite() {
|
||||
if (db == nullptr) {
|
||||
db = new LMDBAL::Base("testBase");
|
||||
db->addStorage<uint32_t, uint32_t>("table1");
|
||||
db->addStorage<QString, QString>("table2");
|
||||
db->addCache<int8_t, std::string>("cache1");
|
||||
db->addCache<std::string, QVariant>("cache2");
|
||||
}
|
||||
}
|
||||
|
||||
@ -37,6 +45,7 @@ protected:
|
||||
LMDBAL::Storage<uint32_t, uint32_t>* t1;
|
||||
LMDBAL::Storage<QString, QString>* t2;
|
||||
LMDBAL::Cache<int8_t, std::string>* c1;
|
||||
LMDBAL::Cache<std::string, QVariant>* c2;
|
||||
};
|
||||
|
||||
|
||||
@ -56,6 +65,33 @@ TEST_F(BaseTest, OpeningClosingDatabase) {
|
||||
EXPECT_EQ(db->ready(), true);
|
||||
}
|
||||
|
||||
TEST_F(BaseTest, Flags) {
|
||||
uint32_t t1Flags = getT1Flags();
|
||||
uint32_t t2Flags = getT2Flags();
|
||||
uint32_t c1Flags = getC1Flags();
|
||||
uint32_t c2Flags = getC2Flags();
|
||||
|
||||
EXPECT_TRUE(t1Flags & MDB_INTEGERKEY);
|
||||
EXPECT_FALSE(t1Flags & MDB_DUPSORT);
|
||||
EXPECT_FALSE(t1Flags & MDB_DUPFIXED);
|
||||
EXPECT_FALSE(t1Flags & MDB_INTEGERDUP);
|
||||
|
||||
EXPECT_FALSE(t2Flags & MDB_INTEGERKEY);
|
||||
EXPECT_FALSE(t2Flags & MDB_DUPSORT);
|
||||
EXPECT_FALSE(t2Flags & MDB_DUPFIXED);
|
||||
EXPECT_FALSE(t2Flags & MDB_INTEGERDUP);
|
||||
|
||||
EXPECT_TRUE(c1Flags & MDB_INTEGERKEY);
|
||||
EXPECT_FALSE(c1Flags & MDB_DUPSORT);
|
||||
EXPECT_FALSE(c1Flags & MDB_DUPFIXED);
|
||||
EXPECT_FALSE(c1Flags & MDB_INTEGERDUP);
|
||||
|
||||
EXPECT_FALSE(c2Flags & MDB_INTEGERKEY);
|
||||
EXPECT_FALSE(c2Flags & MDB_DUPSORT);
|
||||
EXPECT_FALSE(c2Flags & MDB_DUPFIXED);
|
||||
EXPECT_FALSE(c2Flags & MDB_INTEGERDUP);
|
||||
}
|
||||
|
||||
TEST_F(BaseTest, AddingIntegerKey) {
|
||||
EXPECT_EQ(db->ready(), true);
|
||||
t1->addRecord(1, 2);
|
||||
@ -77,10 +113,22 @@ 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) {
|
||||
EXPECT_EQ(db->ready(), true);
|
||||
c2->addRecord("regrets", "blah balah");
|
||||
c2->addRecord("fossil fingers", 842);
|
||||
c2->addRecord("preloaded cut", 539.75);
|
||||
c2->addRecord("dihotomy", false);
|
||||
|
||||
EXPECT_EQ(c2->getRecord("regrets"), "blah balah");
|
||||
EXPECT_EQ(c2->getRecord("fossil fingers"), 842);
|
||||
EXPECT_EQ(c2->getRecord("preloaded cut"), 539.75);
|
||||
EXPECT_EQ(c2->getRecord("dihotomy"), false);
|
||||
}
|
||||
|
||||
TEST_F(BaseTest, AddingRepeatingKey) {
|
||||
@ -94,6 +142,9 @@ TEST_F(BaseTest, AddingRepeatingKey) {
|
||||
|
||||
EXPECT_THROW(c1->addRecord(-4, "world"), LMDBAL::Exist);
|
||||
EXPECT_EQ(c1->getRecord(-4), "testing goes brrr");
|
||||
|
||||
EXPECT_THROW(c2->addRecord("dihotomy", "pired"), LMDBAL::Exist);
|
||||
EXPECT_EQ(c2->getRecord("dihotomy"), false);
|
||||
}
|
||||
|
||||
TEST_F(BaseTest, GettingNotExistingKeys) {
|
||||
@ -102,10 +153,16 @@ TEST_F(BaseTest, GettingNotExistingKeys) {
|
||||
EXPECT_THROW(t2->getRecord("almonds"), LMDBAL::NotFound);
|
||||
EXPECT_THROW(t1->getRecord(64), LMDBAL::NotFound);
|
||||
EXPECT_THROW(c1->getRecord(21), LMDBAL::NotFound);
|
||||
EXPECT_THROW(c2->getRecord("praising"), LMDBAL::NotFound);
|
||||
}
|
||||
|
||||
TEST_F(BaseTest, Persistence) {
|
||||
EXPECT_EQ(db->ready(), true);
|
||||
uint32_t t1Size = t1->count();
|
||||
uint32_t t2Size = t2->count();
|
||||
uint32_t c1Size = c1->count();
|
||||
uint32_t c2Size = c2->count();
|
||||
|
||||
db->close();
|
||||
delete db;
|
||||
|
||||
@ -113,26 +170,45 @@ TEST_F(BaseTest, Persistence) {
|
||||
t1 = db->addStorage<uint32_t, uint32_t>("table1");
|
||||
t2 = db->addStorage<QString, QString>("table2");
|
||||
c1 = db->addCache<int8_t, std::string>("cache1");
|
||||
c2 = db->addCache<std::string, QVariant>("cache2");
|
||||
db->open();
|
||||
|
||||
EXPECT_EQ(t1->count(), t1Size);
|
||||
EXPECT_EQ(t1->getRecord(3), 15);
|
||||
EXPECT_EQ(t1->getRecord(1), 2);
|
||||
EXPECT_EQ(t1->getRecord(2), 2);
|
||||
EXPECT_EQ(t1->count(), t1Size);
|
||||
|
||||
|
||||
EXPECT_EQ(t2->count(), t2Size);
|
||||
EXPECT_EQ(t2->getRecord("hello"), "world");
|
||||
EXPECT_EQ(t2->getRecord("aaa"), "gagdfsdf");
|
||||
EXPECT_EQ(t2->getRecord("sdfhga"), "DSFFDG");
|
||||
EXPECT_EQ(t2->getRecord("sdfsda"), "shgsdgfa");
|
||||
EXPECT_EQ(t2->count(), t2Size);
|
||||
|
||||
EXPECT_EQ(c1->getRecord(-116), "whatever");
|
||||
EXPECT_EQ(c1->count(), c1Size);
|
||||
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");
|
||||
EXPECT_EQ(c1->checkRecord(-4), true);
|
||||
EXPECT_EQ(c1->count(), c1Size);
|
||||
EXPECT_EQ(c1->getRecord(-37), "aaaaa tss tsss tsss tsss aaaaaaa");
|
||||
EXPECT_EQ(c1->getRecord(2), "blah balah");
|
||||
EXPECT_EQ(c1->count(), c1Size);
|
||||
|
||||
EXPECT_EQ(c2->count(), c2Size);
|
||||
EXPECT_EQ(c2->getRecord("regrets"), "blah balah");
|
||||
EXPECT_EQ(c2->getRecord("fossil fingers"), 842);
|
||||
EXPECT_EQ(c2->getRecord("preloaded cut"), 539.75);
|
||||
EXPECT_EQ(c2->getRecord("dihotomy"), false);
|
||||
|
||||
EXPECT_THROW(t2->getRecord("cats"), LMDBAL::NotFound);
|
||||
EXPECT_THROW(t1->getRecord(7893), LMDBAL::NotFound);
|
||||
EXPECT_THROW(c1->getRecord(89), LMDBAL::NotFound);
|
||||
EXPECT_THROW(c2->getRecord("marathons"), LMDBAL::NotFound);
|
||||
}
|
||||
|
||||
TEST_F(BaseTest, CountAndDrop) {
|
||||
@ -140,21 +216,24 @@ TEST_F(BaseTest, CountAndDrop) {
|
||||
EXPECT_EQ(t1->count(), 3);
|
||||
EXPECT_EQ(t2->count(), 4);
|
||||
EXPECT_EQ(c1->count(), 4);
|
||||
|
||||
EXPECT_EQ(c2->count(), 4);
|
||||
db->drop();
|
||||
|
||||
EXPECT_EQ(t1->count(), 0);
|
||||
EXPECT_EQ(t2->count(), 0);
|
||||
EXPECT_EQ(c1->count(), 0);
|
||||
EXPECT_EQ(c2->count(), 0);
|
||||
|
||||
t1->addRecord(2, 2);
|
||||
t2->addRecord("sdfhga", "world");
|
||||
c1->addRecord(15, "world");
|
||||
c1->addRecord(12, "grr grr");
|
||||
c2->addRecord("blues", -749);
|
||||
|
||||
EXPECT_EQ(t1->count(), 1);
|
||||
EXPECT_EQ(t2->count(), 1);
|
||||
EXPECT_EQ(c1->count(), 2);
|
||||
EXPECT_EQ(c2->count(), 1);
|
||||
}
|
||||
|
||||
TEST_F(BaseTest, Change) {
|
||||
@ -162,37 +241,45 @@ TEST_F(BaseTest, Change) {
|
||||
EXPECT_EQ(t1->count(), 1);
|
||||
EXPECT_EQ(t2->count(), 1);
|
||||
EXPECT_EQ(c1->count(), 2);
|
||||
EXPECT_EQ(c2->count(), 1);
|
||||
|
||||
EXPECT_EQ(t1->getRecord(2), 2);
|
||||
EXPECT_EQ(t2->getRecord("sdfhga"), "world");
|
||||
EXPECT_EQ(c1->getRecord(15), "world");
|
||||
EXPECT_EQ(c1->getRecord(12), "grr grr");
|
||||
EXPECT_EQ(c2->getRecord("blues"), -749);
|
||||
|
||||
t1->addRecord(58, 39);
|
||||
t2->addRecord("lawfirm", "stumble");
|
||||
c1->addRecord(89, "answer");
|
||||
c2->addRecord("wielders", QStringList({"calcium", "eagles"}));
|
||||
|
||||
t1->changeRecord(2, 49);
|
||||
t2->changeRecord("sdfhga", "void");
|
||||
c1->changeRecord(15, "recording");
|
||||
c1->changeRecord(12, "thermal");
|
||||
c2->changeRecord("blues", true);
|
||||
|
||||
EXPECT_THROW(t1->changeRecord(37, 49), LMDBAL::NotFound);
|
||||
EXPECT_THROW(t2->changeRecord("precision", "cryoplastics"), LMDBAL::NotFound);
|
||||
EXPECT_THROW(c1->changeRecord(-62, "sharks"), LMDBAL::NotFound);
|
||||
EXPECT_THROW(c2->changeRecord("visions", 90), LMDBAL::NotFound);
|
||||
|
||||
EXPECT_EQ(t1->getRecord(2), 49);
|
||||
EXPECT_EQ(t2->getRecord("sdfhga"), "void");
|
||||
EXPECT_EQ(c1->getRecord(15), "recording");
|
||||
EXPECT_EQ(c1->getRecord(12), "thermal");
|
||||
EXPECT_EQ(c2->getRecord("blues"), true);
|
||||
|
||||
EXPECT_EQ(t1->getRecord(58), 39);
|
||||
EXPECT_EQ(t2->getRecord("lawfirm"), "stumble");
|
||||
EXPECT_EQ(c1->getRecord(89), "answer");
|
||||
EXPECT_EQ(c2->getRecord("wielders"), QStringList({"calcium", "eagles"}));
|
||||
|
||||
EXPECT_EQ(t1->count(), 2);
|
||||
EXPECT_EQ(t2->count(), 2);
|
||||
EXPECT_EQ(c1->count(), 3);
|
||||
EXPECT_EQ(c2->count(), 2);
|
||||
}
|
||||
|
||||
TEST_F(BaseTest, Force) {
|
||||
@ -204,21 +291,30 @@ TEST_F(BaseTest, Force) {
|
||||
EXPECT_EQ(t2->forceRecord("lawfirm", "paracetamol"), false); //changing
|
||||
EXPECT_EQ(c1->forceRecord(89, "canine"), false); //changing
|
||||
EXPECT_EQ(c1->forceRecord(98, "duration"), true); //adding
|
||||
EXPECT_EQ(c2->forceRecord("wielders", "charm"), false); //changing
|
||||
EXPECT_EQ(c2->forceRecord("tedious", 5732.89), true); //adding
|
||||
|
||||
EXPECT_EQ(t1->getRecord(2), 49);
|
||||
EXPECT_EQ(t1->getRecord(58), 35);
|
||||
EXPECT_EQ(t1->getRecord(68), 36);
|
||||
|
||||
EXPECT_EQ(t2->getRecord("sdfhga"), "void");
|
||||
EXPECT_EQ(t2->getRecord("prophecy"), "dumpling");
|
||||
EXPECT_EQ(t2->getRecord("lawfirm"), "paracetamol");
|
||||
|
||||
EXPECT_EQ(c1->getRecord(15), "recording");
|
||||
EXPECT_EQ(c1->getRecord(12), "thermal");
|
||||
EXPECT_EQ(c1->getRecord(89), "canine");
|
||||
EXPECT_EQ(c1->getRecord(98), "duration");
|
||||
|
||||
EXPECT_EQ(c2->getRecord("blues"), true);
|
||||
EXPECT_EQ(c2->getRecord("tedious"), 5732.89);
|
||||
EXPECT_EQ(c2->getRecord("wielders"), "charm");
|
||||
|
||||
EXPECT_EQ(t1->count(), 3);
|
||||
EXPECT_EQ(t2->count(), 3);
|
||||
EXPECT_EQ(c1->count(), 4);
|
||||
EXPECT_EQ(c2->count(), 3);
|
||||
}
|
||||
|
||||
TEST_F(BaseTest, ReadAll) {
|
||||
@ -227,21 +323,29 @@ TEST_F(BaseTest, ReadAll) {
|
||||
std::map<uint32_t, uint32_t> m1 = t1->readAll();
|
||||
std::map<QString, QString> m2 = t2->readAll();
|
||||
std::map<int8_t, std::string> m3 = c1->readAll();
|
||||
std::map<std::string, QVariant> m4 = c2->readAll();
|
||||
|
||||
EXPECT_EQ(m1.at(2), 49);
|
||||
EXPECT_EQ(m1.at(58), 35);
|
||||
EXPECT_EQ(m1.at(68), 36);
|
||||
|
||||
EXPECT_EQ(m2.at("sdfhga"), "void");
|
||||
EXPECT_EQ(m2.at("prophecy"), "dumpling");
|
||||
EXPECT_EQ(m2.at("lawfirm"), "paracetamol");
|
||||
|
||||
EXPECT_EQ(m3.at(15), "recording");
|
||||
EXPECT_EQ(m3.at(12), "thermal");
|
||||
EXPECT_EQ(m3.at(89), "canine");
|
||||
EXPECT_EQ(m3.at(98), "duration");
|
||||
|
||||
EXPECT_EQ(m4.at("blues"), true);
|
||||
EXPECT_EQ(m4.at("tedious"), 5732.89);
|
||||
EXPECT_EQ(m4.at("wielders"), "charm");
|
||||
|
||||
EXPECT_EQ(m1.size(), 3);
|
||||
EXPECT_EQ(m2.size(), 3);
|
||||
EXPECT_EQ(m3.size(), 4);
|
||||
EXPECT_EQ(m4.size(), 3);
|
||||
}
|
||||
|
||||
TEST_F(BaseTest, ReplaceAll) {
|
||||
@ -259,10 +363,14 @@ TEST_F(BaseTest, ReplaceAll) {
|
||||
{"ronin", "cheese"}
|
||||
});
|
||||
c1->replaceAll({});
|
||||
c2->replaceAll({
|
||||
{"kind", 73}
|
||||
});
|
||||
|
||||
EXPECT_EQ(t1->count(), 4);
|
||||
EXPECT_EQ(t2->count(), 3);
|
||||
EXPECT_EQ(c1->count(), 0);
|
||||
EXPECT_EQ(c2->count(), 1);
|
||||
|
||||
EXPECT_FALSE(t1->checkRecord(2));
|
||||
EXPECT_FALSE(t1->checkRecord(58));
|
||||
@ -277,6 +385,10 @@ TEST_F(BaseTest, ReplaceAll) {
|
||||
EXPECT_FALSE(c1->checkRecord(89));
|
||||
EXPECT_FALSE(c1->checkRecord(98));
|
||||
|
||||
EXPECT_FALSE(c2->checkRecord("blues"));
|
||||
EXPECT_FALSE(c2->checkRecord("tedious"));
|
||||
EXPECT_FALSE(c2->checkRecord("wielders"));
|
||||
|
||||
EXPECT_EQ(t1->getRecord(7), 48);
|
||||
EXPECT_EQ(t1->getRecord(194), 582);
|
||||
EXPECT_EQ(t1->getRecord(857), 39);
|
||||
@ -286,6 +398,8 @@ TEST_F(BaseTest, ReplaceAll) {
|
||||
EXPECT_EQ(t2->getRecord("cluster"), "throttle");
|
||||
EXPECT_EQ(t2->getRecord("ronin"), "cheese");
|
||||
|
||||
EXPECT_EQ(c2->getRecord("kind"), 73);
|
||||
|
||||
c1->replaceAll({
|
||||
{68, "quality"},
|
||||
{31, "ridgid body"},
|
||||
@ -302,8 +416,6 @@ TEST_F(BaseTest, ReplaceAll) {
|
||||
EXPECT_EQ(c1->getRecord(-117), "lance of Michael");
|
||||
}
|
||||
|
||||
|
||||
|
||||
TEST_F(BaseTest, AddRecords) {
|
||||
EXPECT_EQ(db->ready(), true);
|
||||
|
||||
@ -432,4 +544,33 @@ TEST_F(BaseTest, AddRecords) {
|
||||
EXPECT_EQ(c1->getRecord(-32), "stitched");
|
||||
EXPECT_EQ(c1->getRecord(123), "horibly unforseen");
|
||||
|
||||
LMDBAL::SizeType s4 = c2->addRecords({
|
||||
{"and wholesome", "preaching"},
|
||||
{"ginger", false}
|
||||
});
|
||||
|
||||
EXPECT_EQ(c2->count(), 3);
|
||||
EXPECT_EQ(s4, 3);
|
||||
|
||||
EXPECT_THROW(
|
||||
s4 = c2->addRecords({
|
||||
{"returning favor", 'c'},
|
||||
{"ginger", 23},
|
||||
{"frames", true}
|
||||
}), LMDBAL::Exist
|
||||
);
|
||||
EXPECT_EQ(c2->count(), 3);
|
||||
|
||||
s4 = c2->addRecords({
|
||||
{"and wholesome", -1},
|
||||
{"orcid", 89.9}
|
||||
}, true);
|
||||
|
||||
EXPECT_EQ(c2->count(), 4);
|
||||
EXPECT_EQ(s4, 4);
|
||||
|
||||
EXPECT_EQ(c2->getRecord("and wholesome"), -1);
|
||||
EXPECT_EQ(c2->getRecord("orcid"), 89.9);
|
||||
EXPECT_EQ(c2->getRecord("ginger"), false);
|
||||
EXPECT_EQ(c2->getRecord("kind"), 73);
|
||||
}
|
||||
|
440
test/cachecursor.cpp
Normal file
440
test/cachecursor.cpp
Normal file
@ -0,0 +1,440 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "base.h"
|
||||
#include "storage.h"
|
||||
#include "cache.h"
|
||||
#include "cursor.h"
|
||||
|
||||
class CacheCursorTest : public ::testing::Test {
|
||||
protected:
|
||||
CacheCursorTest():
|
||||
::testing::Test(),
|
||||
cache (db->getCache<uint64_t, std::string>("table1")),
|
||||
emptyCache (db->getCache<uint64_t, std::string>("empty")) {}
|
||||
|
||||
~CacheCursorTest() {}
|
||||
|
||||
static void SetUpTestSuite() {
|
||||
if (db == nullptr) {
|
||||
db = new LMDBAL::Base("testBase");
|
||||
db->addCache<uint64_t, std::string>("table1");
|
||||
db->addCache<uint64_t, std::string>("empty");
|
||||
db->open();
|
||||
}
|
||||
}
|
||||
|
||||
int getCacheCursorsSize() const {
|
||||
return cache->cursors.size();
|
||||
}
|
||||
|
||||
static void TearDownTestSuite() {
|
||||
cursor.drop();
|
||||
transaction.terminate();
|
||||
db->close();
|
||||
db->removeDirectory();
|
||||
delete db;
|
||||
db = nullptr;
|
||||
}
|
||||
|
||||
static LMDBAL::Base* db;
|
||||
static LMDBAL::Cursor<uint64_t, std::string> cursor;
|
||||
static LMDBAL::Transaction transaction;
|
||||
|
||||
LMDBAL::Cache<uint64_t, std::string>* cache;
|
||||
LMDBAL::Cache<uint64_t, std::string>* emptyCache;
|
||||
};
|
||||
|
||||
LMDBAL::Base* CacheCursorTest::db = nullptr;
|
||||
LMDBAL::Cursor<uint64_t, std::string> CacheCursorTest::cursor;
|
||||
LMDBAL::Transaction CacheCursorTest::transaction;
|
||||
|
||||
static const std::map<uint64_t, std::string> data({
|
||||
{245665783, "bothering nerds"},
|
||||
{3458, "resilent pick forefront"},
|
||||
{105190, "apportunity legal bat"},
|
||||
{6510, "outside"},
|
||||
{7438537, "damocles plush apparently rusty"},
|
||||
{19373572, "local guidence"},
|
||||
{138842, "forgetting tusks prepare"},
|
||||
{981874, "butchered soaking pawn"},
|
||||
{19302, "tanned inmate"},
|
||||
{178239, "custody speaks neurotic"},
|
||||
});
|
||||
|
||||
TEST_F(CacheCursorTest, PopulatingTheTable) {
|
||||
uint32_t amount = cache->addRecords(data);
|
||||
EXPECT_EQ(amount, data.size());
|
||||
}
|
||||
|
||||
TEST_F(CacheCursorTest, Creation) {
|
||||
EXPECT_EQ(getCacheCursorsSize(), 0);
|
||||
cursor = cache->createCursor();
|
||||
EXPECT_EQ(getCacheCursorsSize(), 1);
|
||||
|
||||
EXPECT_THROW(cursor.first(), LMDBAL::CursorNotReady);
|
||||
EXPECT_THROW(cursor.last(), LMDBAL::CursorNotReady);
|
||||
EXPECT_THROW(cursor.next(), LMDBAL::CursorNotReady);
|
||||
EXPECT_THROW(cursor.prev(), LMDBAL::CursorNotReady);
|
||||
EXPECT_THROW(cursor.current(), LMDBAL::CursorNotReady);
|
||||
|
||||
cursor.open();
|
||||
}
|
||||
|
||||
TEST_F(CacheCursorTest, FirstPrivate) {
|
||||
EXPECT_EQ(getCacheCursorsSize(), 1);
|
||||
EXPECT_EQ(cache->count(), data.size());
|
||||
|
||||
std::pair<uint64_t, std::string> element = cursor.first();
|
||||
std::map<uint64_t, std::string>::const_iterator reference = data.begin();
|
||||
|
||||
EXPECT_EQ(element.first, reference->first);
|
||||
EXPECT_EQ(element.second, reference->second);
|
||||
EXPECT_EQ(cache->count(), data.size());
|
||||
}
|
||||
|
||||
TEST_F(CacheCursorTest, NextPrivate) {
|
||||
EXPECT_EQ(getCacheCursorsSize(), 1);
|
||||
std::map<uint64_t, std::string>::const_iterator reference = data.begin();
|
||||
|
||||
reference++;
|
||||
for (; reference != data.end(); ++reference) {
|
||||
std::pair<uint64_t, std::string> element = cursor.next();
|
||||
EXPECT_EQ(element.first, reference->first);
|
||||
EXPECT_EQ(element.second, reference->second);
|
||||
EXPECT_EQ(cache->count(), data.size());
|
||||
}
|
||||
|
||||
EXPECT_THROW(cursor.next(), LMDBAL::NotFound);
|
||||
EXPECT_EQ(cache->count(), data.size());
|
||||
|
||||
std::pair<uint64_t, std::string> element = cursor.first();
|
||||
reference = data.begin();
|
||||
|
||||
EXPECT_EQ(element.first, reference->first);
|
||||
EXPECT_EQ(element.second, reference->second);
|
||||
EXPECT_EQ(cache->count(), data.size());
|
||||
}
|
||||
|
||||
TEST_F(CacheCursorTest, LastPrivate) {
|
||||
EXPECT_EQ(getCacheCursorsSize(), 1);
|
||||
EXPECT_EQ(cache->count(), data.size());
|
||||
|
||||
std::pair<uint64_t, std::string> element = cursor.last();
|
||||
std::map<uint64_t, std::string>::const_reverse_iterator reference = data.rbegin();
|
||||
|
||||
EXPECT_EQ(element.first, reference->first);
|
||||
EXPECT_EQ(element.second, reference->second);
|
||||
EXPECT_EQ(cache->count(), data.size());
|
||||
}
|
||||
|
||||
TEST_F(CacheCursorTest, PrevPrivate) {
|
||||
EXPECT_EQ(getCacheCursorsSize(), 1);
|
||||
std::map<uint64_t, std::string>::const_reverse_iterator reference = data.rbegin();
|
||||
|
||||
reference++;
|
||||
for (; reference != data.rend(); ++reference) {
|
||||
std::pair<uint64_t, std::string> element = cursor.prev();
|
||||
EXPECT_EQ(element.first, reference->first);
|
||||
EXPECT_EQ(element.second, reference->second);
|
||||
EXPECT_EQ(cache->count(), data.size());
|
||||
}
|
||||
|
||||
EXPECT_THROW(cursor.prev(), LMDBAL::NotFound);
|
||||
EXPECT_EQ(cache->count(), data.size());
|
||||
|
||||
std::pair<uint64_t, std::string> element = cursor.last();
|
||||
reference = data.rbegin();
|
||||
|
||||
EXPECT_EQ(element.first, reference->first);
|
||||
EXPECT_EQ(element.second, reference->second);
|
||||
EXPECT_EQ(cache->count(), data.size());
|
||||
}
|
||||
|
||||
TEST_F(CacheCursorTest, CurrentPrivate) {
|
||||
EXPECT_EQ(getCacheCursorsSize(), 1);
|
||||
std::pair<uint64_t, std::string> element = cursor.first();
|
||||
std::map<uint64_t, std::string>::const_iterator reference = data.begin();
|
||||
|
||||
EXPECT_EQ(element.first, reference->first);
|
||||
EXPECT_EQ(element.second, reference->second);
|
||||
|
||||
element = cursor.current();
|
||||
EXPECT_EQ(cache->count(), data.size());
|
||||
|
||||
EXPECT_EQ(element.first, reference->first);
|
||||
EXPECT_EQ(element.second, reference->second);
|
||||
|
||||
cursor.next();
|
||||
element = cursor.current();
|
||||
++reference;
|
||||
|
||||
EXPECT_EQ(element.first, reference->first);
|
||||
EXPECT_EQ(element.second, reference->second);
|
||||
|
||||
cursor.next();
|
||||
cursor.next();
|
||||
cursor.prev();
|
||||
element = cursor.current();
|
||||
++reference;
|
||||
++reference;
|
||||
--reference;
|
||||
|
||||
EXPECT_EQ(element.first, reference->first);
|
||||
EXPECT_EQ(element.second, reference->second);
|
||||
EXPECT_EQ(cache->count(), data.size());
|
||||
}
|
||||
|
||||
TEST_F(CacheCursorTest, SettingPrivate) {
|
||||
EXPECT_EQ(getCacheCursorsSize(), 1);
|
||||
|
||||
EXPECT_FALSE(cursor.set(6684));
|
||||
EXPECT_EQ(cursor.current().second, "tanned inmate");
|
||||
|
||||
std::map<uint64_t, std::string>::const_iterator reference = data.begin();
|
||||
std::advance(reference, 5);
|
||||
EXPECT_TRUE(cursor.set(reference->first));
|
||||
EXPECT_EQ(cursor.current().second, reference->second);
|
||||
|
||||
++reference;
|
||||
cursor.next();
|
||||
EXPECT_EQ(cursor.current().second, reference->second);
|
||||
|
||||
++reference;
|
||||
cursor.next();
|
||||
EXPECT_EQ(cursor.current().second, reference->second);
|
||||
|
||||
--reference;
|
||||
cursor.prev();
|
||||
EXPECT_EQ(cursor.current().second, reference->second);
|
||||
|
||||
--reference;
|
||||
cursor.prev();
|
||||
EXPECT_EQ(cursor.current().second, reference->second);
|
||||
|
||||
--reference;
|
||||
cursor.prev();
|
||||
EXPECT_EQ(cursor.current().second, reference->second);
|
||||
}
|
||||
|
||||
TEST_F(CacheCursorTest, Destruction) {
|
||||
EXPECT_EQ(getCacheCursorsSize(), 1);
|
||||
cursor.close();
|
||||
EXPECT_EQ(getCacheCursorsSize(), 1);
|
||||
|
||||
EXPECT_THROW(cursor.first(), LMDBAL::CursorNotReady);
|
||||
EXPECT_THROW(cursor.last(), LMDBAL::CursorNotReady);
|
||||
EXPECT_THROW(cursor.next(), LMDBAL::CursorNotReady);
|
||||
EXPECT_THROW(cursor.prev(), LMDBAL::CursorNotReady);
|
||||
EXPECT_THROW(cursor.current(), LMDBAL::CursorNotReady);
|
||||
|
||||
cursor = LMDBAL::Cursor<uint64_t, std::string>();
|
||||
EXPECT_EQ(getCacheCursorsSize(), 0);
|
||||
|
||||
cursor = cache->createCursor();
|
||||
EXPECT_EQ(getCacheCursorsSize(), 1);
|
||||
}
|
||||
|
||||
TEST_F(CacheCursorTest, FirstPublic) {
|
||||
EXPECT_EQ(getCacheCursorsSize(), 1);
|
||||
transaction = db->beginTransaction();
|
||||
|
||||
cursor.open(transaction);
|
||||
std::pair<uint64_t, std::string> element = cursor.first();
|
||||
std::map<uint64_t, std::string>::const_iterator reference = data.begin();
|
||||
|
||||
EXPECT_EQ(element.first, reference->first);
|
||||
EXPECT_EQ(element.second, reference->second);
|
||||
EXPECT_EQ(cache->count(), data.size());
|
||||
}
|
||||
|
||||
TEST_F(CacheCursorTest, NextPublic) {
|
||||
EXPECT_EQ(getCacheCursorsSize(), 1);
|
||||
std::map<uint64_t, std::string>::const_iterator reference = data.begin();
|
||||
|
||||
reference++;
|
||||
for (; reference != data.end(); ++reference) {
|
||||
std::pair<uint64_t, std::string> element = cursor.next();
|
||||
EXPECT_EQ(element.first, reference->first);
|
||||
EXPECT_EQ(element.second, reference->second);
|
||||
EXPECT_EQ(cache->count(), data.size());
|
||||
}
|
||||
|
||||
EXPECT_THROW(cursor.next(), LMDBAL::NotFound);
|
||||
EXPECT_EQ(cache->count(), data.size());
|
||||
|
||||
std::pair<uint64_t, std::string> element = cursor.first();
|
||||
reference = data.begin();
|
||||
|
||||
EXPECT_EQ(element.first, reference->first);
|
||||
EXPECT_EQ(element.second, reference->second);
|
||||
}
|
||||
|
||||
TEST_F(CacheCursorTest, LastPublic) {
|
||||
EXPECT_EQ(getCacheCursorsSize(), 1);
|
||||
std::pair<uint64_t, std::string> element = cursor.last();
|
||||
std::map<uint64_t, std::string>::const_reverse_iterator reference = data.rbegin();
|
||||
|
||||
EXPECT_EQ(element.first, reference->first);
|
||||
EXPECT_EQ(element.second, reference->second);
|
||||
EXPECT_EQ(cache->count(), data.size());
|
||||
}
|
||||
|
||||
TEST_F(CacheCursorTest, PrevPublic) {
|
||||
EXPECT_EQ(getCacheCursorsSize(), 1);
|
||||
std::map<uint64_t, std::string>::const_reverse_iterator reference = data.rbegin();
|
||||
|
||||
reference++;
|
||||
for (; reference != data.rend(); ++reference) {
|
||||
std::pair<uint64_t, std::string> element = cursor.prev();
|
||||
EXPECT_EQ(element.first, reference->first);
|
||||
EXPECT_EQ(element.second, reference->second);
|
||||
EXPECT_EQ(cache->count(), data.size());
|
||||
}
|
||||
|
||||
EXPECT_THROW(cursor.prev(), LMDBAL::NotFound);
|
||||
|
||||
std::pair<uint64_t, std::string> element = cursor.last();
|
||||
reference = data.rbegin();
|
||||
|
||||
EXPECT_EQ(element.first, reference->first);
|
||||
EXPECT_EQ(element.second, reference->second);
|
||||
EXPECT_EQ(cache->count(), data.size());
|
||||
}
|
||||
|
||||
TEST_F(CacheCursorTest, CurrentPublic) {
|
||||
EXPECT_EQ(getCacheCursorsSize(), 1);
|
||||
std::pair<uint64_t, std::string> element = cursor.first();
|
||||
std::map<uint64_t, std::string>::const_iterator reference = data.begin();
|
||||
|
||||
EXPECT_EQ(element.first, reference->first);
|
||||
EXPECT_EQ(element.second, reference->second);
|
||||
|
||||
element = cursor.current();
|
||||
EXPECT_EQ(cache->count(), data.size());
|
||||
|
||||
EXPECT_EQ(element.first, reference->first);
|
||||
EXPECT_EQ(element.second, reference->second);
|
||||
|
||||
cursor.next();
|
||||
element = cursor.current();
|
||||
++reference;
|
||||
|
||||
EXPECT_EQ(element.first, reference->first);
|
||||
EXPECT_EQ(element.second, reference->second);
|
||||
|
||||
cursor.next();
|
||||
cursor.next();
|
||||
cursor.prev();
|
||||
element = cursor.current();
|
||||
++reference;
|
||||
++reference;
|
||||
--reference;
|
||||
|
||||
EXPECT_EQ(element.first, reference->first);
|
||||
EXPECT_EQ(element.second, reference->second);
|
||||
EXPECT_EQ(cache->count(), data.size());
|
||||
}
|
||||
|
||||
TEST_F(CacheCursorTest, SettingPublic) {
|
||||
EXPECT_EQ(getCacheCursorsSize(), 1);
|
||||
|
||||
EXPECT_FALSE(cursor.set(557));
|
||||
EXPECT_EQ(cursor.current().second, "resilent pick forefront");
|
||||
|
||||
std::map<uint64_t, std::string>::const_iterator reference = data.begin();
|
||||
std::advance(reference, 3);
|
||||
EXPECT_TRUE(cursor.set(reference->first));
|
||||
EXPECT_EQ(cursor.current().second, reference->second);
|
||||
|
||||
++reference;
|
||||
cursor.next();
|
||||
EXPECT_EQ(cursor.current().second, reference->second);
|
||||
|
||||
++reference;
|
||||
cursor.next();
|
||||
EXPECT_EQ(cursor.current().second, reference->second);
|
||||
|
||||
--reference;
|
||||
cursor.prev();
|
||||
EXPECT_EQ(cursor.current().second, reference->second);
|
||||
|
||||
--reference;
|
||||
cursor.prev();
|
||||
EXPECT_EQ(cursor.current().second, reference->second);
|
||||
|
||||
--reference;
|
||||
cursor.prev();
|
||||
EXPECT_EQ(cursor.current().second, reference->second);
|
||||
}
|
||||
|
||||
TEST_F(CacheCursorTest, CursorRAIIBehaviour) {
|
||||
int initialiCursorsAmount = getCacheCursorsSize();
|
||||
{
|
||||
LMDBAL::Cursor<uint64_t, std::string> cur = cache->createCursor();
|
||||
EXPECT_EQ(initialiCursorsAmount + 1, getCacheCursorsSize());
|
||||
cur.open(transaction);
|
||||
EXPECT_NO_THROW(cur.first());
|
||||
}
|
||||
|
||||
EXPECT_EQ(initialiCursorsAmount, getCacheCursorsSize());
|
||||
LMDBAL::Cursor<uint64_t, std::string> cur;
|
||||
EXPECT_EQ(initialiCursorsAmount, getCacheCursorsSize());
|
||||
EXPECT_EQ(cur.empty(), true);
|
||||
cur = cache->createCursor();
|
||||
EXPECT_EQ(cur.empty(), false);
|
||||
EXPECT_EQ(initialiCursorsAmount + 1, getCacheCursorsSize());
|
||||
EXPECT_THROW(emptyCache->destroyCursor(cur), LMDBAL::Unknown);
|
||||
cache->destroyCursor(cur);
|
||||
EXPECT_EQ(cur.empty(), true);
|
||||
EXPECT_EQ(initialiCursorsAmount, getCacheCursorsSize());
|
||||
|
||||
cur = cache->createCursor();
|
||||
EXPECT_EQ(initialiCursorsAmount + 1, getCacheCursorsSize());
|
||||
EXPECT_EQ(cur.empty(), false);
|
||||
|
||||
cur.drop();
|
||||
EXPECT_EQ(cur.empty(), true);
|
||||
EXPECT_EQ(initialiCursorsAmount, getCacheCursorsSize());
|
||||
|
||||
EXPECT_NO_THROW(cur.drop());
|
||||
EXPECT_THROW(cache->destroyCursor(cur), LMDBAL::Unknown);
|
||||
}
|
||||
|
||||
TEST_F(CacheCursorTest, CornerCases) {
|
||||
transaction.terminate();
|
||||
EXPECT_THROW(cursor.current(), LMDBAL::Unknown);
|
||||
cursor.close();
|
||||
|
||||
LMDBAL::Cursor<uint64_t, std::string> emptyCursor = emptyCache->createCursor();
|
||||
emptyCursor.open();
|
||||
EXPECT_THROW(emptyCursor.first(), LMDBAL::NotFound);
|
||||
EXPECT_THROW(emptyCursor.last(), LMDBAL::NotFound);
|
||||
EXPECT_THROW(emptyCursor.next(), LMDBAL::NotFound);
|
||||
EXPECT_THROW(emptyCursor.prev(), LMDBAL::NotFound);
|
||||
EXPECT_THROW(emptyCursor.current(), LMDBAL::Unknown);
|
||||
emptyCursor.close();
|
||||
|
||||
cursor.open();
|
||||
EXPECT_THROW(cursor.current(), LMDBAL::Unknown); //yeah, nice thing to write in the doc
|
||||
|
||||
std::map<uint64_t, std::string>::const_reverse_iterator breference = data.rbegin();
|
||||
std::pair<uint64_t, std::string> element(cursor.prev());
|
||||
EXPECT_EQ(element.first, breference->first); //nice thing to write in the doc, again!
|
||||
EXPECT_EQ(element.second, breference->second);
|
||||
element = cursor.current();
|
||||
EXPECT_EQ(element.first, breference->first);
|
||||
EXPECT_EQ(element.second, breference->second);
|
||||
EXPECT_THROW(cursor.next(), LMDBAL::NotFound);
|
||||
cursor.close();
|
||||
|
||||
cursor.open();
|
||||
element = cursor.next();
|
||||
std::map<uint64_t, std::string>::const_iterator reference = data.begin();
|
||||
EXPECT_EQ(element.first, reference->first);
|
||||
EXPECT_EQ(element.second, reference->second);
|
||||
element = cursor.current();
|
||||
EXPECT_EQ(element.first, reference->first);
|
||||
EXPECT_EQ(element.second, reference->second);
|
||||
EXPECT_THROW(cursor.prev(), LMDBAL::NotFound);
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ TEST_F(CacheTransactionsTest, Adding) {
|
||||
EXPECT_EQ(c1->count(), 0);
|
||||
EXPECT_EQ(c2->count(), 0);
|
||||
|
||||
LMDBAL::TransactionID txn = db->beginTransaction();
|
||||
LMDBAL::WriteTransaction txn = db->beginTransaction();
|
||||
c1->addRecord(5, 13, txn);
|
||||
c1->addRecord(-53, 782, txn);
|
||||
c1->addRecord(5892, -37829, txn);
|
||||
@ -76,7 +76,7 @@ TEST_F(CacheTransactionsTest, Adding) {
|
||||
EXPECT_EQ(c1->count(), 0);
|
||||
EXPECT_EQ(c2->count(), 0);
|
||||
|
||||
db->commitTransaction(txn);
|
||||
txn.commit();
|
||||
|
||||
EXPECT_EQ(c1->count(), 3);
|
||||
EXPECT_EQ(c1->getRecord(5), 13);
|
||||
@ -95,7 +95,7 @@ TEST_F(CacheTransactionsTest, Aborting) {
|
||||
LMDBAL::SizeType s1 = c1->count();
|
||||
LMDBAL::SizeType s2 = c2->count();
|
||||
|
||||
LMDBAL::TransactionID txn = db->beginTransaction();
|
||||
LMDBAL::WriteTransaction txn = db->beginTransaction();
|
||||
c1->addRecord(18, 40, txn);
|
||||
c1->addRecord(85, -4, txn);
|
||||
c1->addRecord(-5, -3, txn);
|
||||
@ -107,7 +107,7 @@ TEST_F(CacheTransactionsTest, Aborting) {
|
||||
EXPECT_EQ(c1->count(), s1);
|
||||
EXPECT_EQ(c2->count(), s2);
|
||||
|
||||
db->abortTransaction(txn);
|
||||
txn.abort();
|
||||
|
||||
EXPECT_EQ(c1->count(), s1);
|
||||
EXPECT_EQ(c2->count(), s2);
|
||||
@ -116,7 +116,7 @@ TEST_F(CacheTransactionsTest, Aborting) {
|
||||
TEST_F(CacheTransactionsTest, Reading) {
|
||||
EXPECT_EQ(db->ready(), true);
|
||||
|
||||
LMDBAL::TransactionID txn = db->beginReadOnlyTransaction();
|
||||
LMDBAL::Transaction txn = db->beginReadOnlyTransaction();
|
||||
|
||||
EXPECT_EQ(c1->count(txn), 3);
|
||||
EXPECT_EQ(c1->getRecord(5, txn), 13);
|
||||
@ -128,14 +128,14 @@ TEST_F(CacheTransactionsTest, Reading) {
|
||||
EXPECT_FLOAT_EQ(c2->getRecord("decallence", txn), 8532.48);
|
||||
EXPECT_FLOAT_EQ(c2->getRecord("prevent recovery", txn), -64.64);
|
||||
|
||||
db->abortTransaction(txn);
|
||||
txn.terminate();
|
||||
}
|
||||
|
||||
TEST_F(CacheTransactionsTest, ConcurentReading) {
|
||||
EXPECT_EQ(db->ready(), true);
|
||||
|
||||
LMDBAL::SizeType size = c1->count();
|
||||
LMDBAL::TransactionID txn = db->beginTransaction();
|
||||
LMDBAL::WriteTransaction txn = db->beginTransaction();
|
||||
EXPECT_EQ(c1->getRecord(5, txn), 13);
|
||||
EXPECT_EQ(c1->getRecord(5), 13);
|
||||
|
||||
@ -160,7 +160,7 @@ TEST_F(CacheTransactionsTest, ConcurentReading) {
|
||||
EXPECT_EQ(c1->count(txn), 1);
|
||||
EXPECT_EQ(c1->count(), size);
|
||||
|
||||
db->commitTransaction(txn);
|
||||
txn.commit();
|
||||
|
||||
EXPECT_FALSE(c1->checkRecord(5));
|
||||
EXPECT_EQ(c1->count(), 1);
|
||||
@ -182,8 +182,9 @@ TEST_F(CacheTransactionsTest, ConcurentModification) {
|
||||
|
||||
int pid = fork();
|
||||
if (pid == 0) { // I am the child
|
||||
usleep(1);
|
||||
std::cout << "beggining second transaction" << std::endl;
|
||||
LMDBAL::TransactionID txn2 = db->beginTransaction(); //<--- this is where the execution should pause
|
||||
LMDBAL::WriteTransaction txn2 = db->beginTransaction(); //<--- this is where the execution should pause
|
||||
//and wait for the first transaction to get finished
|
||||
std::cout << "checking result of the first transaction value" << std::endl;
|
||||
EXPECT_EQ(c1->getRecord(5, txn2), 812);
|
||||
@ -198,13 +199,13 @@ TEST_F(CacheTransactionsTest, ConcurentModification) {
|
||||
EXPECT_EQ(c1->getRecord(5), 812);
|
||||
|
||||
std::cout << "commiting second transaction" << std::endl;
|
||||
db->commitTransaction(txn2);
|
||||
txn2.commit();
|
||||
|
||||
std::cout << "quitting child thread" << std::endl;
|
||||
exit(testing::Test::HasFailure());
|
||||
} else { // I am the parent
|
||||
std::cout << "beggining first transaction" << std::endl;
|
||||
LMDBAL::TransactionID txn1 = db->beginTransaction();
|
||||
LMDBAL::WriteTransaction txn1 = db->beginTransaction();
|
||||
|
||||
std::cout << "putting parent thread to sleep for 5 ms" << std::endl;
|
||||
usleep(5);
|
||||
@ -219,7 +220,7 @@ TEST_F(CacheTransactionsTest, ConcurentModification) {
|
||||
EXPECT_FALSE(c1->checkRecord(5));
|
||||
|
||||
std::cout << "commiting first transaction" << std::endl;
|
||||
db->commitTransaction(txn1);
|
||||
txn1.commit();
|
||||
|
||||
std::cout << "waiting for the other thread to finish" << std::endl;
|
||||
ASSERT_EQ(0, waitForChildFork(pid)); //child process should have no problems
|
||||
@ -229,3 +230,52 @@ TEST_F(CacheTransactionsTest, ConcurentModification) {
|
||||
EXPECT_EQ(c1->getRecord(5), -46);
|
||||
}
|
||||
|
||||
TEST_F(CacheTransactionsTest, RAIIResourceFree) {
|
||||
EXPECT_EQ(db->ready(), true);
|
||||
|
||||
int pid = fork();
|
||||
if (pid == 0) { // I am the child
|
||||
usleep(1);
|
||||
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
|
||||
std::cout << "checking result of the parent transaction value" << std::endl;
|
||||
EXPECT_FALSE(c1->checkRecord(221, txn2));
|
||||
|
||||
std::cout << "performing modification from the child thread" << std::endl;
|
||||
c1->addRecord(221, 14, txn2);
|
||||
|
||||
std::cout << "commiting child transaction" << std::endl;
|
||||
txn2.commit();
|
||||
|
||||
std::cout << "quitting child thread, letting child transaction be destroyed after commit" << std::endl;
|
||||
exit(testing::Test::HasFailure());
|
||||
} else { // I am the parent
|
||||
std::cout << "beggining parent transaction" << std::endl;
|
||||
{
|
||||
LMDBAL::WriteTransaction txn1 = db->beginTransaction();
|
||||
|
||||
std::cout << "putting parent thread to sleep for 5 ms" << std::endl;
|
||||
usleep(5);
|
||||
std::cout << "parent thread woke up" << std::endl;
|
||||
|
||||
std::cout << "adding value from parent thread" << std::endl;
|
||||
c1->addRecord(221, 320, txn1);
|
||||
|
||||
std::cout << "checking value from parent thread using transaction" << std::endl;
|
||||
EXPECT_EQ(c1->getRecord(221, txn1), 320);
|
||||
|
||||
std::cout << "checking value independently from parent thread" << std::endl;
|
||||
EXPECT_FALSE(c1->checkRecord(221));
|
||||
|
||||
std::cout << "implicitly aborting transaction by leaving the scope" << std::endl;
|
||||
}
|
||||
|
||||
std::cout << "child thread should resume after this line" << std::endl;
|
||||
ASSERT_EQ(0, waitForChildFork(pid)); //child process should have no problems
|
||||
}
|
||||
|
||||
std::cout << "checking the final result" << std::endl;
|
||||
EXPECT_EQ(c1->getRecord(221), 14);
|
||||
}
|
||||
|
||||
|
497
test/duplicates.cpp
Normal file
497
test/duplicates.cpp
Normal file
@ -0,0 +1,497 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <set>
|
||||
|
||||
#include "base.h"
|
||||
#include "storage.h"
|
||||
#include "cursor.h"
|
||||
|
||||
class DuplicatesTest : public ::testing::Test {
|
||||
protected:
|
||||
DuplicatesTest():
|
||||
::testing::Test(),
|
||||
tu1(db->getStorage<int16_t, uint16_t>("sameSizeInts")),
|
||||
tu2(db->getStorage<std::string, int8_t>("stringInt")),
|
||||
tu3(db->getStorage<float, float>("floatFloat")),
|
||||
tu4(db->getStorage<uint16_t, double>("intDouble")),
|
||||
tu5(db->getStorage<float, int64_t>("floatLong")) {}
|
||||
|
||||
~DuplicatesTest() {}
|
||||
|
||||
uint32_t getTU1Flags() const {return tu1->flags();}
|
||||
uint32_t getTU2Flags() const {return tu2->flags();}
|
||||
uint32_t getTU3Flags() const {return tu3->flags();}
|
||||
uint32_t getTU4Flags() const {return tu4->flags();}
|
||||
uint32_t getTU5Flags() const {return tu5->flags();}
|
||||
|
||||
static void SetUpTestSuite() {
|
||||
if (db == nullptr) {
|
||||
db = new LMDBAL::Base("testBase");
|
||||
db->addStorage<int16_t, uint16_t>("sameSizeInts", true);
|
||||
db->addStorage<std::string, int8_t>("stringInt", true);
|
||||
db->addStorage<float, float>("floatFloat", true);
|
||||
db->addStorage<uint16_t, double>("intDouble", true);
|
||||
db->addStorage<float, int64_t>("floatLong", true);
|
||||
|
||||
db->open();
|
||||
}
|
||||
}
|
||||
|
||||
static void TearDownTestSuite() {
|
||||
db->close();
|
||||
db->removeDirectory();
|
||||
delete db;
|
||||
db = nullptr;
|
||||
}
|
||||
|
||||
static LMDBAL::Base* db;
|
||||
|
||||
LMDBAL::Storage<int16_t, uint16_t>* tu1;
|
||||
LMDBAL::Storage<std::string, int8_t>* tu2;
|
||||
LMDBAL::Storage<float, float>* tu3;
|
||||
LMDBAL::Storage<uint16_t, double>* tu4;
|
||||
LMDBAL::Storage<float, int64_t>* tu5;
|
||||
};
|
||||
|
||||
LMDBAL::Base* DuplicatesTest::db = nullptr;
|
||||
|
||||
TEST_F(DuplicatesTest, Flags) {
|
||||
uint32_t tu1Flags = getTU1Flags();
|
||||
uint32_t tu2Flags = getTU2Flags();
|
||||
uint32_t tu3Flags = getTU3Flags();
|
||||
uint32_t tu4Flags = getTU4Flags();
|
||||
uint32_t tu5Flags = getTU5Flags();
|
||||
|
||||
EXPECT_TRUE(tu1Flags & MDB_INTEGERKEY);
|
||||
EXPECT_TRUE(tu1Flags & MDB_DUPSORT);
|
||||
EXPECT_TRUE(tu1Flags & MDB_DUPFIXED);
|
||||
EXPECT_FALSE(tu1Flags & MDB_INTEGERDUP);
|
||||
|
||||
EXPECT_FALSE(tu2Flags & MDB_INTEGERKEY);
|
||||
EXPECT_TRUE(tu2Flags & MDB_DUPSORT);
|
||||
EXPECT_TRUE(tu2Flags & MDB_DUPFIXED);
|
||||
EXPECT_FALSE(tu2Flags & MDB_INTEGERDUP);
|
||||
|
||||
EXPECT_FALSE(tu3Flags & MDB_INTEGERKEY);
|
||||
EXPECT_TRUE(tu3Flags & MDB_DUPSORT);
|
||||
EXPECT_TRUE(tu3Flags & MDB_DUPFIXED);
|
||||
EXPECT_FALSE(tu3Flags & MDB_INTEGERDUP);
|
||||
|
||||
EXPECT_TRUE(tu4Flags & MDB_INTEGERKEY);
|
||||
EXPECT_TRUE(tu4Flags & MDB_DUPSORT);
|
||||
EXPECT_TRUE(tu4Flags & MDB_DUPFIXED);
|
||||
EXPECT_FALSE(tu4Flags & MDB_INTEGERDUP);
|
||||
|
||||
EXPECT_FALSE(tu5Flags & MDB_INTEGERKEY);
|
||||
EXPECT_TRUE(tu5Flags & MDB_DUPSORT);
|
||||
EXPECT_TRUE(tu5Flags & MDB_DUPFIXED);
|
||||
EXPECT_TRUE(tu5Flags & MDB_INTEGERDUP);
|
||||
}
|
||||
|
||||
TEST_F(DuplicatesTest, Adding) {
|
||||
tu1->addRecord(1, 1);
|
||||
tu1->addRecord(2, 2);
|
||||
tu1->addRecord(2, 1);
|
||||
tu1->addRecord(1, 2);
|
||||
EXPECT_THROW(tu1->addRecord(1, 1), LMDBAL::Exist);
|
||||
EXPECT_THROW(tu1->addRecord(1, 2), LMDBAL::Exist);
|
||||
EXPECT_THROW(tu1->addRecord(2, 2), LMDBAL::Exist);
|
||||
|
||||
EXPECT_EQ(tu1->count(), 4);
|
||||
EXPECT_EQ(tu1->getRecord(1), 1);
|
||||
EXPECT_EQ(tu1->getRecord(2), 1);
|
||||
|
||||
tu2->addRecord("brass boulers", -54);
|
||||
tu2->addRecord("grief ", 61);
|
||||
tu2->addRecord("grief ", 19);
|
||||
tu2->addRecord("grief ", 32);
|
||||
tu2->addRecord("miracles of a lunch", 44);
|
||||
tu2->addRecord("miracles of a lunch", 102);
|
||||
tu2->addRecord("miracles of a lunch", -72);
|
||||
|
||||
EXPECT_THROW(tu2->addRecord("grief ", 19), LMDBAL::Exist);
|
||||
EXPECT_THROW(tu2->addRecord("brass boulers", -54), LMDBAL::Exist);
|
||||
EXPECT_EQ(tu2->count(), 7);
|
||||
EXPECT_EQ(tu2->getRecord("grief "), 19);
|
||||
EXPECT_EQ(tu2->getRecord("miracles of a lunch"), 44); //apparently ints are compared as uints
|
||||
|
||||
tu3->addRecord(7.2, 697);
|
||||
tu3->addRecord(5119, -998.53);
|
||||
tu3->addRecord(7.2001, 4);
|
||||
tu3->addRecord(7.2, -113);
|
||||
tu3->addRecord(7.2, -53.5478);
|
||||
float tu3ds = 0.432924;
|
||||
tu3->addRecord(5119, tu3ds);
|
||||
|
||||
EXPECT_THROW(tu3->addRecord(5119, -998.53), LMDBAL::Exist);
|
||||
EXPECT_THROW(tu3->addRecord(7.2001, 4), LMDBAL::Exist);
|
||||
tu3->addRecord(7.20001, 4.00000001); //not sure how exactly, but it works
|
||||
|
||||
EXPECT_EQ(tu3->count(), 7);
|
||||
|
||||
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);
|
||||
|
||||
tu4->addRecord(327, 463.28348);
|
||||
tu4->addRecord(327, 79.624923);
|
||||
tu4->addRecord(172, 0.00001);
|
||||
tu4->addRecord(172, 0.00000001);
|
||||
EXPECT_THROW(tu4->addRecord(172, 0.00000001), LMDBAL::Exist);
|
||||
EXPECT_THROW(tu4->addRecord(172, 0.00001), LMDBAL::Exist);
|
||||
EXPECT_THROW(tu4->addRecord(327, 79.624923), LMDBAL::Exist);
|
||||
|
||||
EXPECT_EQ(tu4->count(), 4);
|
||||
|
||||
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);
|
||||
tu5->addRecord(-84.7, 45656754);
|
||||
int64_t intMax = std::numeric_limits<int32_t>::max();
|
||||
int64_t intMin = std::numeric_limits<int32_t>::min();
|
||||
int64_t longMax = std::numeric_limits<int64_t>::max();
|
||||
int64_t longMin = std::numeric_limits<int64_t>::min();
|
||||
|
||||
tu5->addRecord(52.87, intMax);
|
||||
EXPECT_THROW(tu5->addRecord(52.87, intMax), LMDBAL::Exist);
|
||||
tu5->addRecord(52.87, intMin);
|
||||
EXPECT_THROW(tu5->addRecord(52.87, intMin), LMDBAL::Exist);
|
||||
tu5->addRecord(52.87, longMax);
|
||||
EXPECT_THROW(tu5->addRecord(52.87, longMax), LMDBAL::Exist);
|
||||
tu5->addRecord(52.87, longMin);
|
||||
EXPECT_THROW(tu5->addRecord(52.87, longMin), LMDBAL::Exist);
|
||||
|
||||
EXPECT_EQ(tu5->count(), 6);
|
||||
EXPECT_EQ(tu5->getRecord(-84.7), 45656753);
|
||||
EXPECT_EQ(tu5->getRecord(52.87), intMax);
|
||||
}
|
||||
|
||||
TEST_F(DuplicatesTest, Forcing) {
|
||||
LMDBAL::SizeType tu1Size = tu1->count();
|
||||
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(res56.count(tu1->getRecord(-56)), 1);
|
||||
|
||||
res56.insert(14);
|
||||
EXPECT_TRUE(tu1->forceRecord(-56, 14));
|
||||
EXPECT_EQ(tu1->count(), tu1Size += 1);
|
||||
EXPECT_EQ(res56.count(tu1->getRecord(-56)), 1);
|
||||
|
||||
EXPECT_FALSE(tu1->forceRecord(-56, 274));
|
||||
EXPECT_EQ(tu1->count(), tu1Size);
|
||||
|
||||
LMDBAL::SizeType tu2Size = tu2->count();
|
||||
tu2->addRecord("printable", -2);
|
||||
tu2->addRecord("printable", 4);
|
||||
EXPECT_EQ(tu2->count(), tu2Size += 2);
|
||||
EXPECT_TRUE(tu2->forceRecord("printable", 18));
|
||||
EXPECT_EQ(tu2->count(), tu2Size += 1);
|
||||
EXPECT_EQ(tu2->getRecord("printable"), 4);
|
||||
EXPECT_TRUE(tu2->forceRecord("printable", 3));
|
||||
EXPECT_EQ(tu2->count(), tu2Size += 1);
|
||||
EXPECT_EQ(tu2->getRecord("printable"), 3);
|
||||
EXPECT_FALSE(tu2->forceRecord("printable", 4));
|
||||
EXPECT_EQ(tu2->count(), tu2Size);
|
||||
|
||||
LMDBAL::SizeType tu3Size = tu3->count();
|
||||
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(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(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(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(res84.count(tu4->getRecord(84)), 1);
|
||||
|
||||
EXPECT_FALSE(tu4->forceRecord(84, -359.109));
|
||||
EXPECT_EQ(tu4->count(), tu4Size);
|
||||
|
||||
LMDBAL::SizeType tu5Size = tu5->count();
|
||||
tu5->addRecord(0.45, -85645);
|
||||
tu5->addRecord(0.45, 10573);
|
||||
tu5->addRecord(0.45, 573);
|
||||
tu5->addRecord(0.45, 73285);
|
||||
EXPECT_EQ(tu5->count(), tu5Size += 4);
|
||||
EXPECT_TRUE(tu5->forceRecord(0.45, -473));
|
||||
EXPECT_EQ(tu5->count(), tu5Size += 1);
|
||||
EXPECT_EQ(tu5->getRecord(0.45), 573);
|
||||
EXPECT_TRUE(tu5->forceRecord(0.45, 394));
|
||||
EXPECT_EQ(tu5->count(), tu5Size += 1);
|
||||
EXPECT_EQ(tu5->getRecord(0.45), 394);
|
||||
EXPECT_FALSE(tu5->forceRecord(0.45, 10573));
|
||||
EXPECT_EQ(tu5->count(), tu5Size);
|
||||
}
|
||||
|
||||
TEST_F(DuplicatesTest, Changing) {
|
||||
LMDBAL::SizeType tu1Size = tu1->count();
|
||||
EXPECT_THROW(tu1->changeRecord(-31, 53), LMDBAL::NotFound);
|
||||
EXPECT_EQ(tu1->count(), tu1Size);
|
||||
tu1->addRecord(-31, 53);
|
||||
EXPECT_EQ(tu1->count(), tu1Size += 1);
|
||||
EXPECT_EQ(tu1->getRecord(-31), 53);
|
||||
tu1->changeRecord(-31, 53); //should just do nothing usefull, but work normally
|
||||
EXPECT_EQ(tu1->getRecord(-31), 53);
|
||||
tu1->changeRecord(-31, 19);
|
||||
EXPECT_EQ(tu1->count(), tu1Size);
|
||||
EXPECT_EQ(tu1->getRecord(-31), 19);
|
||||
tu1->addRecord(-31, 60);
|
||||
EXPECT_EQ(tu1->count(), tu1Size += 1);
|
||||
EXPECT_EQ(tu1->getRecord(-31), 19);
|
||||
tu1->changeRecord(-31, 16);
|
||||
EXPECT_EQ(tu1->count(), tu1Size);
|
||||
EXPECT_EQ(tu1->getRecord(-31), 16);
|
||||
tu1->changeRecord(-31, 203);
|
||||
EXPECT_EQ(tu1->count(), tu1Size);
|
||||
EXPECT_EQ(tu1->getRecord(-31), 60);
|
||||
EXPECT_THROW(tu1->changeRecord(-31, 203), LMDBAL::Exist);
|
||||
|
||||
LMDBAL::SizeType tu2Size = tu2->count();
|
||||
EXPECT_THROW(tu2->changeRecord("jeremy spins", -5), LMDBAL::NotFound);
|
||||
EXPECT_EQ(tu2->count(), tu2Size);
|
||||
tu2->addRecord("jeremy spins", -5);
|
||||
EXPECT_EQ(tu2->count(), tu2Size += 1);
|
||||
EXPECT_EQ(tu2->getRecord("jeremy spins"), -5);
|
||||
tu2->changeRecord("jeremy spins", -5); //should just do nothing usefull, but work normally
|
||||
EXPECT_EQ(tu2->getRecord("jeremy spins"), -5);
|
||||
tu2->changeRecord("jeremy spins", 11);
|
||||
EXPECT_EQ(tu2->count(), tu2Size);
|
||||
EXPECT_EQ(tu2->getRecord("jeremy spins"), 11);
|
||||
tu2->addRecord("jeremy spins", 24);
|
||||
EXPECT_EQ(tu2->count(), tu2Size += 1);
|
||||
EXPECT_EQ(tu2->getRecord("jeremy spins"), 11);
|
||||
tu2->changeRecord("jeremy spins", 4);
|
||||
EXPECT_EQ(tu2->count(), tu2Size);
|
||||
EXPECT_EQ(tu2->getRecord("jeremy spins"), 4);
|
||||
tu2->changeRecord("jeremy spins", -7);
|
||||
EXPECT_EQ(tu2->count(), tu2Size);
|
||||
EXPECT_EQ(tu2->getRecord("jeremy spins"), 24); //cuz it's compared as usigned down there
|
||||
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);
|
||||
EXPECT_EQ(tu3->count(), tu3Size += 1);
|
||||
EXPECT_EQ(tu3->getRecord(26.7), 68.22f);
|
||||
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(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);
|
||||
tu3->changeRecord(26.7, 54.33);
|
||||
EXPECT_EQ(tu3->count(), tu3Size);
|
||||
EXPECT_EQ(tu3->getRecord(26.7), 22.16f);
|
||||
EXPECT_THROW(tu3->changeRecord(26.7, 54.33), LMDBAL::Exist);
|
||||
|
||||
LMDBAL::SizeType tu4Size = tu4->count();
|
||||
EXPECT_THROW(tu4->changeRecord(852, 6795.349), LMDBAL::NotFound);
|
||||
EXPECT_EQ(tu4->count(), tu4Size);
|
||||
tu4->addRecord(852, 6795.349);
|
||||
EXPECT_EQ(tu4->count(), tu4Size += 1);
|
||||
EXPECT_EQ(tu4->getRecord(852), 6795.349);
|
||||
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(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(res852.count(tu4->getRecord(852)), 1);
|
||||
EXPECT_THROW(tu4->changeRecord(852, 46324.1135), LMDBAL::Exist);
|
||||
}
|
||||
|
||||
TEST_F(DuplicatesTest, GettingAllRecords) {
|
||||
LMDBAL::Transaction txn = db->beginReadOnlyTransaction();
|
||||
bool cycle;
|
||||
LMDBAL::SizeType iterations;
|
||||
|
||||
std::map<int16_t, uint16_t> m1;
|
||||
std::set<int16_t> k1;
|
||||
LMDBAL::Cursor<int16_t, uint16_t> c1 = tu1->createCursor();
|
||||
tu1->readAll(m1, txn);
|
||||
c1.open(txn);
|
||||
|
||||
cycle = false;
|
||||
iterations = 0;
|
||||
do {
|
||||
try {
|
||||
std::pair<int16_t, uint16_t> pair = c1.next();
|
||||
cycle = true;
|
||||
std::pair<std::set<int16_t>::const_iterator, bool> probe = k1.insert(pair.first);
|
||||
if (probe.second) {
|
||||
uint16_t valueAll = m1.at(pair.first);
|
||||
uint16_t valueGet;
|
||||
EXPECT_NO_THROW(tu1->getRecord(pair.first, valueGet, txn));
|
||||
EXPECT_EQ(valueAll, valueGet);
|
||||
}
|
||||
++iterations;
|
||||
} catch (const LMDBAL::NotFound& e) {
|
||||
cycle = false;
|
||||
}
|
||||
} while (cycle);
|
||||
|
||||
EXPECT_EQ(iterations, tu1->count(txn));
|
||||
EXPECT_EQ(k1.size(), m1.size());
|
||||
EXPECT_NE(iterations, 0);
|
||||
EXPECT_NE(k1.size(), 0);
|
||||
c1.drop();
|
||||
|
||||
|
||||
std::map<std::string, int8_t> m2;
|
||||
std::set<std::string> k2;
|
||||
LMDBAL::Cursor<std::string, int8_t> c2 = tu2->createCursor();
|
||||
tu2->readAll(m2, txn);
|
||||
c2.open(txn);
|
||||
|
||||
cycle = false;
|
||||
iterations = 0;
|
||||
do {
|
||||
try {
|
||||
std::pair<std::string, int8_t> pair = c2.next();
|
||||
cycle = true;
|
||||
std::pair<std::set<std::string>::const_iterator, bool> probe = k2.insert(pair.first);
|
||||
if (probe.second) {
|
||||
int8_t valueAll = m2.at(pair.first);
|
||||
int8_t valueGet;
|
||||
EXPECT_NO_THROW(tu2->getRecord(pair.first, valueGet, txn));
|
||||
EXPECT_EQ(valueAll, valueGet);
|
||||
}
|
||||
++iterations;
|
||||
} catch (const LMDBAL::NotFound& e) {
|
||||
cycle = false;
|
||||
}
|
||||
} while (cycle);
|
||||
|
||||
EXPECT_EQ(iterations, tu2->count(txn));
|
||||
EXPECT_EQ(k2.size(), m2.size());
|
||||
EXPECT_NE(iterations, 0);
|
||||
EXPECT_NE(k2.size(), 0);
|
||||
c2.drop();
|
||||
|
||||
|
||||
std::map<float, float> m3;
|
||||
std::set<float> k3;
|
||||
LMDBAL::Cursor<float, float> c3 = tu3->createCursor();
|
||||
tu3->readAll(m3, txn);
|
||||
c3.open(txn);
|
||||
|
||||
cycle = false;
|
||||
iterations = 0;
|
||||
do {
|
||||
try {
|
||||
std::pair<float, float> pair = c3.next();
|
||||
cycle = true;
|
||||
std::pair<std::set<float>::const_iterator, bool> probe = k3.insert(pair.first);
|
||||
if (probe.second) {
|
||||
float valueAll = m3.at(pair.first);
|
||||
float valueGet;
|
||||
EXPECT_NO_THROW(tu3->getRecord(pair.first, valueGet, txn));
|
||||
EXPECT_EQ(valueAll, valueGet);
|
||||
}
|
||||
++iterations;
|
||||
} catch (const LMDBAL::NotFound& e) {
|
||||
cycle = false;
|
||||
}
|
||||
} while (cycle);
|
||||
|
||||
EXPECT_EQ(iterations, tu3->count(txn));
|
||||
EXPECT_EQ(k3.size(), m3.size());
|
||||
EXPECT_NE(iterations, 0);
|
||||
EXPECT_NE(k3.size(), 0);
|
||||
c3.drop();
|
||||
|
||||
|
||||
std::map<uint16_t, double> m4;
|
||||
std::set<uint16_t> k4;
|
||||
LMDBAL::Cursor<uint16_t, double> c4 = tu4->createCursor();
|
||||
tu4->readAll(m4, txn);
|
||||
c4.open(txn);
|
||||
|
||||
cycle = false;
|
||||
iterations = 0;
|
||||
do {
|
||||
try {
|
||||
std::pair<uint16_t, double> pair = c4.next();
|
||||
cycle = true;
|
||||
std::pair<std::set<uint16_t>::const_iterator, bool> probe = k4.insert(pair.first);
|
||||
if (probe.second) {
|
||||
double valueAll = m4.at(pair.first);
|
||||
double valueGet;
|
||||
EXPECT_NO_THROW(tu4->getRecord(pair.first, valueGet, txn));
|
||||
EXPECT_EQ(valueAll, valueGet);
|
||||
}
|
||||
++iterations;
|
||||
} catch (const LMDBAL::NotFound& e) {
|
||||
cycle = false;
|
||||
}
|
||||
} while (cycle);
|
||||
c4.drop();
|
||||
|
||||
EXPECT_EQ(iterations, tu4->count(txn));
|
||||
EXPECT_EQ(k4.size(), m4.size());
|
||||
EXPECT_NE(iterations, 0);
|
||||
EXPECT_NE(k4.size(), 0);
|
||||
|
||||
txn.terminate();
|
||||
}
|
@ -4,12 +4,13 @@
|
||||
#include <operators.hpp>
|
||||
|
||||
TEST(Serialization, Double) {
|
||||
double source = 5344.6542;
|
||||
double source1 = 5344.6542;
|
||||
double source2 = 0.4329248;
|
||||
LMDBAL::Serializer<double> serializer;
|
||||
LMDBAL::Serializer<double> serializer2(source);
|
||||
LMDBAL::Serializer<double> serializer2(source1);
|
||||
LMDBAL::Serializer<double> deserializer;
|
||||
|
||||
serializer.setData(source);
|
||||
serializer.setData(source1);
|
||||
MDB_val data = serializer.getData();
|
||||
MDB_val data2 = serializer2.getData();
|
||||
|
||||
@ -19,11 +20,16 @@ TEST(Serialization, Double) {
|
||||
double destination;
|
||||
serializer.deserialize(data, destination);
|
||||
|
||||
EXPECT_DOUBLE_EQ(source, destination);
|
||||
EXPECT_DOUBLE_EQ(source1, destination);
|
||||
|
||||
double dest2 = serializer.deserialize(data);
|
||||
|
||||
EXPECT_DOUBLE_EQ(source, dest2);
|
||||
EXPECT_DOUBLE_EQ(source1, dest2);
|
||||
|
||||
data = serializer.setData(source2);
|
||||
serializer.deserialize(data, destination);
|
||||
|
||||
EXPECT_DOUBLE_EQ(source2, destination);
|
||||
}
|
||||
|
||||
TEST(Serialization, Float) {
|
||||
|
417
test/storagecursor.cpp
Normal file
417
test/storagecursor.cpp
Normal file
@ -0,0 +1,417 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "base.h"
|
||||
#include "storage.h"
|
||||
#include "cursor.h"
|
||||
|
||||
class StorageCursorTest : public ::testing::Test {
|
||||
protected:
|
||||
StorageCursorTest():
|
||||
::testing::Test(),
|
||||
table (db->getStorage<uint64_t, std::string>("table1")),
|
||||
emptyTable (db->getStorage<uint64_t, std::string>("empty")) {}
|
||||
|
||||
~StorageCursorTest() {}
|
||||
|
||||
static void SetUpTestSuite() {
|
||||
if (db == nullptr) {
|
||||
db = new LMDBAL::Base("testBase");
|
||||
db->addStorage<uint64_t, std::string>("table1");
|
||||
db->addStorage<uint64_t, std::string>("empty");
|
||||
db->open();
|
||||
}
|
||||
}
|
||||
|
||||
int getTableCursorsSize() const {
|
||||
return table->cursors.size();
|
||||
}
|
||||
|
||||
static void TearDownTestSuite() {
|
||||
cursor.drop();
|
||||
transaction.terminate();
|
||||
db->close();
|
||||
db->removeDirectory();
|
||||
delete db;
|
||||
db = nullptr;
|
||||
}
|
||||
|
||||
static LMDBAL::Base* db;
|
||||
static LMDBAL::Cursor<uint64_t, std::string> cursor;
|
||||
static LMDBAL::Transaction transaction;
|
||||
|
||||
LMDBAL::Storage<uint64_t, std::string>* table;
|
||||
LMDBAL::Storage<uint64_t, std::string>* emptyTable;
|
||||
};
|
||||
|
||||
LMDBAL::Base* StorageCursorTest::db = nullptr;
|
||||
LMDBAL::Cursor<uint64_t, std::string> StorageCursorTest::cursor;
|
||||
LMDBAL::Transaction StorageCursorTest::transaction = LMDBAL::Transaction();
|
||||
|
||||
static const std::map<uint64_t, std::string> data({
|
||||
{245665783, "bothering nerds"},
|
||||
{3458, "resilent pick forefront"},
|
||||
{105190, "apportunity legal bat"},
|
||||
{6510, "outside"},
|
||||
{7438537, "damocles plush apparently rusty"},
|
||||
{19373572, "local guidence"},
|
||||
{138842, "forgetting tusks prepare"},
|
||||
{981874, "butchered soaking pawn"},
|
||||
{19302, "tanned inmate"},
|
||||
{178239, "custody speaks neurotic"},
|
||||
});
|
||||
|
||||
TEST_F(StorageCursorTest, PopulatingTheTable) {
|
||||
uint32_t amount = table->addRecords(data);
|
||||
EXPECT_EQ(amount, data.size());
|
||||
}
|
||||
|
||||
TEST_F(StorageCursorTest, Creation) {
|
||||
EXPECT_EQ(getTableCursorsSize(), 0);
|
||||
cursor = table->createCursor();
|
||||
EXPECT_EQ(getTableCursorsSize(), 1);
|
||||
|
||||
EXPECT_THROW(cursor.first(), LMDBAL::CursorNotReady);
|
||||
EXPECT_THROW(cursor.last(), LMDBAL::CursorNotReady);
|
||||
EXPECT_THROW(cursor.next(), LMDBAL::CursorNotReady);
|
||||
EXPECT_THROW(cursor.prev(), LMDBAL::CursorNotReady);
|
||||
EXPECT_THROW(cursor.current(), LMDBAL::CursorNotReady);
|
||||
|
||||
cursor.open();
|
||||
}
|
||||
|
||||
TEST_F(StorageCursorTest, FirstPrivate) {
|
||||
EXPECT_EQ(getTableCursorsSize(), 1);
|
||||
std::pair<uint64_t, std::string> element = cursor.first();
|
||||
std::map<uint64_t, std::string>::const_iterator reference = data.begin();
|
||||
|
||||
EXPECT_EQ(element.first, reference->first);
|
||||
EXPECT_EQ(element.second, reference->second);
|
||||
}
|
||||
|
||||
TEST_F(StorageCursorTest, NextPrivate) {
|
||||
EXPECT_EQ(getTableCursorsSize(), 1);
|
||||
std::map<uint64_t, std::string>::const_iterator reference = data.begin();
|
||||
|
||||
reference++;
|
||||
for (; reference != data.end(); ++reference) {
|
||||
std::pair<uint64_t, std::string> element = cursor.next();
|
||||
EXPECT_EQ(element.first, reference->first);
|
||||
EXPECT_EQ(element.second, reference->second);
|
||||
}
|
||||
|
||||
EXPECT_THROW(cursor.next(), LMDBAL::NotFound);
|
||||
|
||||
std::pair<uint64_t, std::string> element = cursor.first();
|
||||
reference = data.begin();
|
||||
|
||||
EXPECT_EQ(element.first, reference->first);
|
||||
EXPECT_EQ(element.second, reference->second);
|
||||
}
|
||||
|
||||
TEST_F(StorageCursorTest, LastPrivate) {
|
||||
EXPECT_EQ(getTableCursorsSize(), 1);
|
||||
std::pair<uint64_t, std::string> element = cursor.last();
|
||||
std::map<uint64_t, std::string>::const_reverse_iterator reference = data.rbegin();
|
||||
|
||||
EXPECT_EQ(element.first, reference->first);
|
||||
EXPECT_EQ(element.second, reference->second);
|
||||
}
|
||||
|
||||
TEST_F(StorageCursorTest, PrevPrivate) {
|
||||
EXPECT_EQ(getTableCursorsSize(), 1);
|
||||
std::map<uint64_t, std::string>::const_reverse_iterator reference = data.rbegin();
|
||||
|
||||
reference++;
|
||||
for (; reference != data.rend(); ++reference) {
|
||||
std::pair<uint64_t, std::string> element = cursor.prev();
|
||||
EXPECT_EQ(element.first, reference->first);
|
||||
EXPECT_EQ(element.second, reference->second);
|
||||
}
|
||||
|
||||
EXPECT_THROW(cursor.prev(), LMDBAL::NotFound);
|
||||
|
||||
std::pair<uint64_t, std::string> element = cursor.last();
|
||||
reference = data.rbegin();
|
||||
|
||||
EXPECT_EQ(element.first, reference->first);
|
||||
EXPECT_EQ(element.second, reference->second);
|
||||
}
|
||||
|
||||
TEST_F(StorageCursorTest, CurrentPrivate) {
|
||||
EXPECT_EQ(getTableCursorsSize(), 1);
|
||||
std::pair<uint64_t, std::string> element = cursor.first();
|
||||
std::map<uint64_t, std::string>::const_iterator reference = data.begin();
|
||||
|
||||
EXPECT_EQ(element.first, reference->first);
|
||||
EXPECT_EQ(element.second, reference->second);
|
||||
|
||||
element = cursor.current();
|
||||
|
||||
EXPECT_EQ(element.first, reference->first);
|
||||
EXPECT_EQ(element.second, reference->second);
|
||||
|
||||
cursor.next();
|
||||
element = cursor.current();
|
||||
++reference;
|
||||
|
||||
EXPECT_EQ(element.first, reference->first);
|
||||
EXPECT_EQ(element.second, reference->second);
|
||||
|
||||
cursor.next();
|
||||
cursor.next();
|
||||
cursor.prev();
|
||||
element = cursor.current();
|
||||
++reference;
|
||||
++reference;
|
||||
--reference;
|
||||
|
||||
EXPECT_EQ(element.first, reference->first);
|
||||
EXPECT_EQ(element.second, reference->second);
|
||||
}
|
||||
|
||||
TEST_F(StorageCursorTest, SettingPrivate) {
|
||||
EXPECT_EQ(getTableCursorsSize(), 1);
|
||||
|
||||
EXPECT_FALSE(cursor.set(6684));
|
||||
EXPECT_EQ(cursor.current().second, "tanned inmate");
|
||||
|
||||
std::map<uint64_t, std::string>::const_iterator reference = data.begin();
|
||||
std::advance(reference, 5);
|
||||
EXPECT_TRUE(cursor.set(reference->first));
|
||||
EXPECT_EQ(cursor.current().second, reference->second);
|
||||
|
||||
++reference;
|
||||
cursor.next();
|
||||
EXPECT_EQ(cursor.current().second, reference->second);
|
||||
|
||||
++reference;
|
||||
cursor.next();
|
||||
EXPECT_EQ(cursor.current().second, reference->second);
|
||||
|
||||
--reference;
|
||||
cursor.prev();
|
||||
EXPECT_EQ(cursor.current().second, reference->second);
|
||||
|
||||
--reference;
|
||||
cursor.prev();
|
||||
EXPECT_EQ(cursor.current().second, reference->second);
|
||||
|
||||
--reference;
|
||||
cursor.prev();
|
||||
EXPECT_EQ(cursor.current().second, reference->second);
|
||||
}
|
||||
|
||||
TEST_F(StorageCursorTest, Destruction) {
|
||||
EXPECT_EQ(getTableCursorsSize(), 1);
|
||||
cursor.close();
|
||||
EXPECT_EQ(getTableCursorsSize(), 1);
|
||||
|
||||
EXPECT_THROW(cursor.first(), LMDBAL::CursorNotReady);
|
||||
EXPECT_THROW(cursor.last(), LMDBAL::CursorNotReady);
|
||||
EXPECT_THROW(cursor.next(), LMDBAL::CursorNotReady);
|
||||
EXPECT_THROW(cursor.prev(), LMDBAL::CursorNotReady);
|
||||
EXPECT_THROW(cursor.current(), LMDBAL::CursorNotReady);
|
||||
|
||||
cursor = LMDBAL::Cursor<uint64_t, std::string>();
|
||||
EXPECT_EQ(getTableCursorsSize(), 0);
|
||||
|
||||
cursor = table->createCursor();
|
||||
EXPECT_EQ(getTableCursorsSize(), 1);
|
||||
}
|
||||
|
||||
TEST_F(StorageCursorTest, FirstPublic) {
|
||||
EXPECT_EQ(getTableCursorsSize(), 1);
|
||||
transaction = db->beginReadOnlyTransaction();
|
||||
|
||||
cursor.open(transaction);
|
||||
std::pair<uint64_t, std::string> element = cursor.first();
|
||||
std::map<uint64_t, std::string>::const_iterator reference = data.begin();
|
||||
|
||||
EXPECT_EQ(element.first, reference->first);
|
||||
EXPECT_EQ(element.second, reference->second);
|
||||
}
|
||||
|
||||
TEST_F(StorageCursorTest, NextPublic) {
|
||||
EXPECT_EQ(getTableCursorsSize(), 1);
|
||||
std::map<uint64_t, std::string>::const_iterator reference = data.begin();
|
||||
|
||||
reference++;
|
||||
for (; reference != data.end(); ++reference) {
|
||||
std::pair<uint64_t, std::string> element = cursor.next();
|
||||
EXPECT_EQ(element.first, reference->first);
|
||||
EXPECT_EQ(element.second, reference->second);
|
||||
}
|
||||
|
||||
EXPECT_THROW(cursor.next(), LMDBAL::NotFound);
|
||||
|
||||
std::pair<uint64_t, std::string> element = cursor.first();
|
||||
reference = data.begin();
|
||||
|
||||
EXPECT_EQ(element.first, reference->first);
|
||||
EXPECT_EQ(element.second, reference->second);
|
||||
}
|
||||
|
||||
TEST_F(StorageCursorTest, LastPublic) {
|
||||
EXPECT_EQ(getTableCursorsSize(), 1);
|
||||
std::pair<uint64_t, std::string> element = cursor.last();
|
||||
std::map<uint64_t, std::string>::const_reverse_iterator reference = data.rbegin();
|
||||
|
||||
EXPECT_EQ(element.first, reference->first);
|
||||
EXPECT_EQ(element.second, reference->second);
|
||||
}
|
||||
|
||||
TEST_F(StorageCursorTest, PrevPublic) {
|
||||
EXPECT_EQ(getTableCursorsSize(), 1);
|
||||
std::map<uint64_t, std::string>::const_reverse_iterator reference = data.rbegin();
|
||||
|
||||
reference++;
|
||||
for (; reference != data.rend(); ++reference) {
|
||||
std::pair<uint64_t, std::string> element = cursor.prev();
|
||||
EXPECT_EQ(element.first, reference->first);
|
||||
EXPECT_EQ(element.second, reference->second);
|
||||
}
|
||||
|
||||
EXPECT_THROW(cursor.prev(), LMDBAL::NotFound);
|
||||
|
||||
std::pair<uint64_t, std::string> element = cursor.last();
|
||||
reference = data.rbegin();
|
||||
|
||||
EXPECT_EQ(element.first, reference->first);
|
||||
EXPECT_EQ(element.second, reference->second);
|
||||
}
|
||||
|
||||
TEST_F(StorageCursorTest, CurrentPublic) {
|
||||
EXPECT_EQ(getTableCursorsSize(), 1);
|
||||
std::pair<uint64_t, std::string> element = cursor.first();
|
||||
std::map<uint64_t, std::string>::const_iterator reference = data.begin();
|
||||
|
||||
EXPECT_EQ(element.first, reference->first);
|
||||
EXPECT_EQ(element.second, reference->second);
|
||||
|
||||
element = cursor.current();
|
||||
|
||||
EXPECT_EQ(element.first, reference->first);
|
||||
EXPECT_EQ(element.second, reference->second);
|
||||
|
||||
cursor.next();
|
||||
element = cursor.current();
|
||||
++reference;
|
||||
|
||||
EXPECT_EQ(element.first, reference->first);
|
||||
EXPECT_EQ(element.second, reference->second);
|
||||
|
||||
cursor.next();
|
||||
cursor.next();
|
||||
cursor.prev();
|
||||
element = cursor.current();
|
||||
++reference;
|
||||
++reference;
|
||||
--reference;
|
||||
|
||||
EXPECT_EQ(element.first, reference->first);
|
||||
EXPECT_EQ(element.second, reference->second);
|
||||
}
|
||||
|
||||
TEST_F(StorageCursorTest, SettingPublic) {
|
||||
EXPECT_EQ(getTableCursorsSize(), 1);
|
||||
|
||||
EXPECT_FALSE(cursor.set(557));
|
||||
EXPECT_EQ(cursor.current().second, "resilent pick forefront");
|
||||
|
||||
std::map<uint64_t, std::string>::const_iterator reference = data.begin();
|
||||
std::advance(reference, 3);
|
||||
EXPECT_TRUE(cursor.set(reference->first));
|
||||
EXPECT_EQ(cursor.current().second, reference->second);
|
||||
|
||||
++reference;
|
||||
cursor.next();
|
||||
EXPECT_EQ(cursor.current().second, reference->second);
|
||||
|
||||
++reference;
|
||||
cursor.next();
|
||||
EXPECT_EQ(cursor.current().second, reference->second);
|
||||
|
||||
--reference;
|
||||
cursor.prev();
|
||||
EXPECT_EQ(cursor.current().second, reference->second);
|
||||
|
||||
--reference;
|
||||
cursor.prev();
|
||||
EXPECT_EQ(cursor.current().second, reference->second);
|
||||
|
||||
--reference;
|
||||
cursor.prev();
|
||||
EXPECT_EQ(cursor.current().second, reference->second);
|
||||
}
|
||||
|
||||
TEST_F(StorageCursorTest, CursorRAIIBehaviour) {
|
||||
int initialiCursorsAmount = getTableCursorsSize();
|
||||
{
|
||||
LMDBAL::Cursor<uint64_t, std::string> cur = table->createCursor();
|
||||
EXPECT_EQ(initialiCursorsAmount + 1, getTableCursorsSize());
|
||||
cur.open(transaction);
|
||||
EXPECT_NO_THROW(cur.first());
|
||||
}
|
||||
|
||||
EXPECT_EQ(initialiCursorsAmount, getTableCursorsSize());
|
||||
LMDBAL::Cursor<uint64_t, std::string> cur;
|
||||
EXPECT_EQ(initialiCursorsAmount, getTableCursorsSize());
|
||||
EXPECT_EQ(cur.empty(), true);
|
||||
cur = table->createCursor();
|
||||
EXPECT_EQ(cur.empty(), false);
|
||||
EXPECT_EQ(initialiCursorsAmount + 1, getTableCursorsSize());
|
||||
EXPECT_THROW(emptyTable->destroyCursor(cur), LMDBAL::Unknown);
|
||||
table->destroyCursor(cur);
|
||||
EXPECT_EQ(cur.empty(), true);
|
||||
EXPECT_EQ(initialiCursorsAmount, getTableCursorsSize());
|
||||
|
||||
cur = table->createCursor();
|
||||
EXPECT_EQ(initialiCursorsAmount + 1, getTableCursorsSize());
|
||||
EXPECT_EQ(cur.empty(), false);
|
||||
|
||||
cur.drop();
|
||||
EXPECT_EQ(cur.empty(), true);
|
||||
EXPECT_EQ(initialiCursorsAmount, getTableCursorsSize());
|
||||
|
||||
EXPECT_NO_THROW(cur.drop());
|
||||
EXPECT_THROW(table->destroyCursor(cur), LMDBAL::Unknown);
|
||||
}
|
||||
|
||||
TEST_F(StorageCursorTest, CornerCases) {
|
||||
EXPECT_EQ(getTableCursorsSize(), 1);
|
||||
transaction.terminate();
|
||||
EXPECT_THROW(cursor.current(), LMDBAL::Unknown);
|
||||
cursor.close();
|
||||
|
||||
LMDBAL::Cursor<uint64_t, std::string> emptyCursor = emptyTable->createCursor();
|
||||
emptyCursor.open();
|
||||
EXPECT_THROW(emptyCursor.first(), LMDBAL::NotFound);
|
||||
EXPECT_THROW(emptyCursor.last(), LMDBAL::NotFound);
|
||||
EXPECT_THROW(emptyCursor.next(), LMDBAL::NotFound);
|
||||
EXPECT_THROW(emptyCursor.prev(), LMDBAL::NotFound);
|
||||
EXPECT_THROW(emptyCursor.current(), LMDBAL::Unknown);
|
||||
emptyCursor.close();
|
||||
|
||||
cursor.open();
|
||||
EXPECT_THROW(cursor.current(), LMDBAL::Unknown); //yeah, nice thing to write in the doc
|
||||
|
||||
std::map<uint64_t, std::string>::const_reverse_iterator breference = data.rbegin();
|
||||
std::pair<uint64_t, std::string> element(cursor.prev());
|
||||
EXPECT_EQ(element.first, breference->first); //nice thing to write in the doc, again!
|
||||
EXPECT_EQ(element.second, breference->second);
|
||||
element = cursor.current();
|
||||
EXPECT_EQ(element.first, breference->first);
|
||||
EXPECT_EQ(element.second, breference->second);
|
||||
EXPECT_THROW(cursor.next(), LMDBAL::NotFound);
|
||||
cursor.close();
|
||||
|
||||
cursor.open();
|
||||
element = cursor.next();
|
||||
std::map<uint64_t, std::string>::const_iterator reference = data.begin();
|
||||
EXPECT_EQ(element.first, reference->first);
|
||||
EXPECT_EQ(element.second, reference->second);
|
||||
element = cursor.current();
|
||||
EXPECT_EQ(element.first, reference->first);
|
||||
EXPECT_EQ(element.second, reference->second);
|
||||
EXPECT_THROW(cursor.prev(), LMDBAL::NotFound);
|
||||
}
|
@ -22,9 +22,9 @@ protected:
|
||||
}
|
||||
if (WIFEXITED(status)) {
|
||||
const int exit_status = WEXITSTATUS(status);
|
||||
if (exit_status != 0) {
|
||||
if (exit_status != 0)
|
||||
std::cerr << "[----------] Non-zero exit status " << exit_status << " from test!" << std::endl;
|
||||
}
|
||||
|
||||
return exit_status;
|
||||
} else {
|
||||
std::cerr << "[----------] Non-normal exit from child!" << std::endl;
|
||||
@ -64,7 +64,7 @@ TEST_F(StorageTransactionsTest, Adding) {
|
||||
EXPECT_EQ(t1->count(), 0);
|
||||
EXPECT_EQ(t2->count(), 0);
|
||||
|
||||
LMDBAL::TransactionID txn = db->beginTransaction();
|
||||
LMDBAL::WriteTransaction txn = db->beginTransaction();
|
||||
t1->addRecord(5, 13, txn);
|
||||
t1->addRecord(-53, 782, txn);
|
||||
t1->addRecord(5892, -37829, txn);
|
||||
@ -76,7 +76,7 @@ TEST_F(StorageTransactionsTest, Adding) {
|
||||
EXPECT_EQ(t1->count(), 0);
|
||||
EXPECT_EQ(t2->count(), 0);
|
||||
|
||||
db->commitTransaction(txn);
|
||||
txn.commit();
|
||||
|
||||
EXPECT_EQ(t1->count(), 3);
|
||||
EXPECT_EQ(t1->getRecord(5), 13);
|
||||
@ -95,7 +95,7 @@ TEST_F(StorageTransactionsTest, Aborting) {
|
||||
LMDBAL::SizeType s1 = t1->count();
|
||||
LMDBAL::SizeType s2 = t2->count();
|
||||
|
||||
LMDBAL::TransactionID txn = db->beginTransaction();
|
||||
LMDBAL::WriteTransaction txn = db->beginTransaction();
|
||||
t1->addRecord(18, 40, txn);
|
||||
t1->addRecord(85, -4, txn);
|
||||
t1->addRecord(-5, -3, txn);
|
||||
@ -107,7 +107,7 @@ TEST_F(StorageTransactionsTest, Aborting) {
|
||||
EXPECT_EQ(t1->count(), s1);
|
||||
EXPECT_EQ(t2->count(), s2);
|
||||
|
||||
db->abortTransaction(txn);
|
||||
txn.abort();
|
||||
|
||||
EXPECT_EQ(t1->count(), s1);
|
||||
EXPECT_EQ(t2->count(), s2);
|
||||
@ -116,7 +116,7 @@ TEST_F(StorageTransactionsTest, Aborting) {
|
||||
TEST_F(StorageTransactionsTest, Reading) {
|
||||
EXPECT_EQ(db->ready(), true);
|
||||
|
||||
LMDBAL::TransactionID txn = db->beginReadOnlyTransaction();
|
||||
LMDBAL::Transaction txn = db->beginReadOnlyTransaction();
|
||||
|
||||
EXPECT_EQ(t1->count(txn), 3);
|
||||
EXPECT_EQ(t1->getRecord(5, txn), 13);
|
||||
@ -128,14 +128,14 @@ TEST_F(StorageTransactionsTest, Reading) {
|
||||
EXPECT_FLOAT_EQ(t2->getRecord("decallence", txn), 8532.48);
|
||||
EXPECT_FLOAT_EQ(t2->getRecord("prevent recovery", txn), -64.64);
|
||||
|
||||
db->abortTransaction(txn);
|
||||
txn.terminate();
|
||||
}
|
||||
|
||||
TEST_F(StorageTransactionsTest, ConcurentReading) {
|
||||
EXPECT_EQ(db->ready(), true);
|
||||
|
||||
LMDBAL::SizeType size = t1->count();
|
||||
LMDBAL::TransactionID txn = db->beginTransaction();
|
||||
LMDBAL::WriteTransaction txn = db->beginTransaction();
|
||||
EXPECT_EQ(t1->getRecord(5, txn), 13);
|
||||
EXPECT_EQ(t1->getRecord(5), 13);
|
||||
|
||||
@ -160,7 +160,7 @@ TEST_F(StorageTransactionsTest, ConcurentReading) {
|
||||
EXPECT_EQ(t1->count(txn), 1);
|
||||
EXPECT_EQ(t1->count(), size);
|
||||
|
||||
db->commitTransaction(txn);
|
||||
txn.commit();
|
||||
|
||||
EXPECT_FALSE(t1->checkRecord(5));
|
||||
EXPECT_EQ(t1->count(), 1);
|
||||
@ -181,8 +181,9 @@ TEST_F(StorageTransactionsTest, ConcurentModification) {
|
||||
|
||||
int pid = fork();
|
||||
if (pid == 0) { // I am the child
|
||||
usleep(1);
|
||||
std::cout << "beggining second transaction" << std::endl;
|
||||
LMDBAL::TransactionID txn2 = db->beginTransaction(); //<--- this is where the execution should pause
|
||||
LMDBAL::WriteTransaction txn2 = db->beginTransaction(); //<--- this is where the execution should pause
|
||||
//and wait for the first transaction to get finished
|
||||
std::cout << "checking result of the first transaction value" << std::endl;
|
||||
EXPECT_EQ(t1->getRecord(5, txn2), 812);
|
||||
@ -197,13 +198,13 @@ TEST_F(StorageTransactionsTest, ConcurentModification) {
|
||||
EXPECT_EQ(t1->getRecord(5), 812);
|
||||
|
||||
std::cout << "commiting second transaction" << std::endl;
|
||||
db->commitTransaction(txn2);
|
||||
txn2.commit();
|
||||
|
||||
std::cout << "quitting child thread" << std::endl;
|
||||
exit(testing::Test::HasFailure());
|
||||
} else { // I am the parent
|
||||
std::cout << "beggining first transaction" << std::endl;
|
||||
LMDBAL::TransactionID txn1 = db->beginTransaction();
|
||||
LMDBAL::WriteTransaction txn1 = db->beginTransaction();
|
||||
|
||||
std::cout << "putting parent thread to sleep for 5 ms" << std::endl;
|
||||
usleep(5);
|
||||
@ -218,12 +219,61 @@ TEST_F(StorageTransactionsTest, ConcurentModification) {
|
||||
EXPECT_FALSE(t1->checkRecord(5));
|
||||
|
||||
std::cout << "commiting first transaction" << std::endl;
|
||||
db->commitTransaction(txn1);
|
||||
txn1.commit();
|
||||
|
||||
std::cout << "waiting for the other thread to finish" << std::endl;
|
||||
ASSERT_EQ(0, waitForChildFork(pid)); //child process should have no problems
|
||||
}
|
||||
|
||||
std::cout << "checking final result" << std::endl;
|
||||
std::cout << "checking the final result" << std::endl;
|
||||
EXPECT_EQ(t1->getRecord(5), -46);
|
||||
}
|
||||
|
||||
TEST_F(StorageTransactionsTest, RAIIResourceFree) {
|
||||
EXPECT_EQ(db->ready(), true);
|
||||
|
||||
int pid = fork();
|
||||
if (pid == 0) { // I am the child
|
||||
usleep(1);
|
||||
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
|
||||
std::cout << "checking result of the parent transaction value" << std::endl;
|
||||
EXPECT_FALSE(t1->checkRecord(221, txn2));
|
||||
|
||||
std::cout << "performing modification from the child thread" << std::endl;
|
||||
t1->addRecord(221, 14, txn2);
|
||||
|
||||
std::cout << "commiting child transaction" << std::endl;
|
||||
txn2.commit();
|
||||
|
||||
std::cout << "quitting child thread, letting child transaction be destroyed after commit" << std::endl;
|
||||
exit(testing::Test::HasFailure());
|
||||
} else { // I am the parent
|
||||
std::cout << "beggining parent transaction" << std::endl;
|
||||
{
|
||||
LMDBAL::WriteTransaction txn1 = db->beginTransaction();
|
||||
|
||||
std::cout << "putting parent thread to sleep for 5 ms" << std::endl;
|
||||
usleep(5);
|
||||
std::cout << "parent thread woke up" << std::endl;
|
||||
|
||||
std::cout << "adding value from parent thread" << std::endl;
|
||||
t1->addRecord(221, 320, txn1);
|
||||
|
||||
std::cout << "checking value from parent thread using transaction" << std::endl;
|
||||
EXPECT_EQ(t1->getRecord(221, txn1), 320);
|
||||
|
||||
std::cout << "checking value independently from parent thread" << std::endl;
|
||||
EXPECT_FALSE(t1->checkRecord(221));
|
||||
|
||||
std::cout << "implicitly aborting transaction by leaving the scope" << std::endl;
|
||||
}
|
||||
|
||||
std::cout << "child thread should resume after this line" << std::endl;
|
||||
ASSERT_EQ(0, waitForChildFork(pid)); //child process should have no problems
|
||||
}
|
||||
|
||||
std::cout << "checking the final result" << std::endl;
|
||||
EXPECT_EQ(t1->getRecord(221), 14);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user