2023-03-23 17:27:46 +00:00
/*
* 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/>.
*/
2022-09-05 20:25:39 +00:00
2023-03-21 11:05:54 +00:00
# include "base.h"
2022-09-05 20:25:39 +00:00
# include "exceptions.h"
2023-03-20 15:37:13 +00:00
# include "storage.h"
2022-09-05 20:25:39 +00:00
2023-04-02 13:00:21 +00:00
# define UNUSED(x) (void)(x)
2023-04-10 21:01:19 +00:00
/**
* \ class LMDBAL : : Base
* \ brief Database abstraction
*
* This is a basic class that represents the database as a collection of storages .
* Storages is something key - value database has instead of tables in classic SQL databases .
*/
/**
* \ brief Creates the database
*
2023-04-11 15:11:27 +00:00
* \ param [ in ] _name - name of the database , it is going to affect folder name that is created to store data
2023-04-12 15:36:33 +00:00
* \ param [ in ] _mapSize - LMDB map size ( MiB ) , multiplied by 1024 ^ 2 and passed to < a class = " el " href= " http : //www.lmdb.tech/doc/group__mdb.html#gaa2506ec8dab3d969b0e609cd82e619e5">mdb_env_set_mapsize</a> during the call of LMDBAL::Base::open()
2023-04-10 21:01:19 +00:00
*/
2023-04-11 15:11:27 +00:00
LMDBAL : : Base : : Base ( const QString & _name , uint16_t _mapSize ) :
name ( _name . toStdString ( ) ) ,
2022-09-05 20:25:39 +00:00
opened ( false ) ,
2023-04-11 15:11:27 +00:00
size ( _mapSize ) ,
2022-09-05 20:25:39 +00:00
environment ( ) ,
2023-04-02 13:00:21 +00:00
storages ( ) ,
2023-03-20 15:37:13 +00:00
transactions ( new Transactions ( ) )
{ }
2022-09-05 20:25:39 +00:00
2023-04-10 21:01:19 +00:00
/**
* \ brief Destroys the database
*/
2023-03-21 11:05:54 +00:00
LMDBAL : : Base : : ~ Base ( ) {
2022-09-05 20:25:39 +00:00
close ( ) ;
2023-03-20 15:37:13 +00:00
delete transactions ;
2023-04-02 13:00:21 +00:00
for ( const std : : pair < const std : : string , iStorage * > & pair : storages )
2022-09-05 20:25:39 +00:00
delete pair . second ;
}
2023-04-10 21:01:19 +00:00
/**
* \ brief Closes the database
*
* Closes all lmdb handles , aborts all public transactions .
* This function will do nothing on closed database
*
* \ exception LMDBAL : : Unknown - thrown if something went wrong aborting transactions
*/
2023-03-21 11:05:54 +00:00
void LMDBAL : : Base : : close ( ) {
2022-09-05 20:25:39 +00:00
if ( opened ) {
2023-03-23 17:27:46 +00:00
for ( LMDBAL : : TransactionID id : * transactions )
2023-04-02 13:00:21 +00:00
abortTransaction ( id , emptyName ) ;
2023-03-23 17:27:46 +00:00
2023-04-02 13:00:21 +00:00
for ( const std : : pair < const std : : string , iStorage * > & pair : storages ) {
iStorage * storage = pair . second ;
mdb_dbi_close ( environment , storage - > dbi ) ;
2022-09-05 20:25:39 +00:00
}
mdb_env_close ( environment ) ;
2023-03-23 17:27:46 +00:00
transactions - > clear ( ) ;
2022-09-05 20:25:39 +00:00
opened = false ;
}
}
2023-03-23 17:27:46 +00:00
/**
2023-04-10 21:01:19 +00:00
* \ brief Opens the database
*
* Almost every LMDBAL : : Base require it to be opened , this function does it .
* It laso creates the directory for the database if it was an initial launch .
* This function will do nothing on opened database
*
* \ exception LMDBAL : : Unknown - thrown if something went wrong opening storages and caches
2023-03-23 17:27:46 +00:00
*/
2023-03-21 11:05:54 +00:00
void LMDBAL : : Base : : open ( ) {
2022-09-05 20:25:39 +00:00
if ( ! opened ) {
mdb_env_create ( & environment ) ;
2022-12-17 10:06:37 +00:00
QString path = createDirectory ( ) ;
2022-09-05 20:25:39 +00:00
2023-04-02 13:00:21 +00:00
mdb_env_set_maxdbs ( environment , storages . size ( ) ) ;
2022-09-05 20:25:39 +00:00
mdb_env_set_mapsize ( environment , size * 1024UL * 1024UL ) ;
mdb_env_open ( environment , path . toStdString ( ) . c_str ( ) , 0 , 0664 ) ;
2023-04-02 13:00:21 +00:00
TransactionID txn = beginPrivateTransaction ( emptyName ) ;
for ( const std : : pair < const std : : string , iStorage * > & pair : storages ) {
iStorage * storage = pair . second ;
int rc = storage - > createStorage ( txn ) ;
2023-03-20 15:37:13 +00:00
if ( rc )
2022-09-05 20:25:39 +00:00
throw Unknown ( name , mdb_strerror ( rc ) ) ;
}
2023-04-02 13:00:21 +00:00
commitPrivateTransaction ( txn , emptyName ) ;
2022-09-05 20:25:39 +00:00
opened = true ;
}
}
2023-04-10 21:01:19 +00:00
/**
* \ brief Removes database directory
*
* \ returns true if removal was successfull of if no directory was created where it ' s expected to be , false otherwise
*
* \ exception LMDBAL : : Opened - thrown if this function was called on opened database
*/
2023-03-21 11:05:54 +00:00
bool LMDBAL : : Base : : removeDirectory ( ) {
2023-03-20 15:37:13 +00:00
if ( opened )
2022-09-17 12:31:58 +00:00
throw Opened ( name , " remove database directory " ) ;
2023-03-20 15:37:13 +00:00
2022-09-15 21:34:39 +00:00
QString path ( QStandardPaths : : writableLocation ( QStandardPaths : : CacheLocation ) ) ;
path + = " / " + getName ( ) ;
QDir cache ( path ) ;
2023-03-20 15:37:13 +00:00
if ( cache . exists ( ) )
2022-09-15 21:34:39 +00:00
return cache . removeRecursively ( ) ;
2023-03-20 15:37:13 +00:00
else
2022-09-15 21:34:39 +00:00
return true ;
}
2023-04-10 21:01:19 +00:00
/**
* \ brief Creates database directory
*
* 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
* < a class = " el " href= " https : //doc.qt.io/qt-6/qcoreapplication.html">QCoreApplication</a> configuration of your app.
* This function does nothing if the directory was already created
*
* \ returns the path of the created directory
*
* \ exception LMDBAL : : Opened - thrown if called on opened database
* \ exception LMDBAL : : Directory - if the database couldn ' t create the folder
*/
2023-03-21 11:05:54 +00:00
QString LMDBAL : : Base : : createDirectory ( ) {
2023-03-20 15:37:13 +00:00
if ( opened )
2022-12-17 10:06:37 +00:00
throw Opened ( name , " create database directory " ) ;
QString path ( QStandardPaths : : writableLocation ( QStandardPaths : : CacheLocation ) ) ;
path + = " / " + getName ( ) ;
QDir cache ( path ) ;
if ( ! cache . exists ( ) ) {
bool res = cache . mkpath ( path ) ;
2023-03-20 15:37:13 +00:00
if ( ! res )
2022-12-17 10:06:37 +00:00
throw Directory ( path . toStdString ( ) ) ;
}
return path ;
}
2023-04-10 21:01:19 +00:00
/**
* \ brief Returns database name
*
* \ returns database name
*/
2023-03-21 11:05:54 +00:00
QString LMDBAL : : Base : : getName ( ) const {
2023-03-20 15:37:13 +00:00
return QString : : fromStdString ( name ) ; }
2022-12-17 10:06:37 +00:00
2023-04-10 21:01:19 +00:00
/**
* \ brief Returns database state
*
* \ returns true if the database is opened and ready for work , false otherwise
*/
2023-03-21 11:05:54 +00:00
bool LMDBAL : : Base : : ready ( ) const {
2023-03-20 15:37:13 +00:00
return opened ; }
2022-09-14 22:18:31 +00:00
2023-04-10 21:01:19 +00:00
/**
* \ brief Drops the database
*
* Clears all caches and storages of the database
*
* \ exception LMDBAL : : Closed - thrown if the database is closed
* \ exception LMDBAL : : Unknown - thrown if something unexpected happend
*/
2023-03-21 11:05:54 +00:00
void LMDBAL : : Base : : drop ( ) {
2023-03-20 15:37:13 +00:00
if ( ! opened )
2022-09-17 12:31:58 +00:00
throw Closed ( " drop " , name ) ;
2023-04-03 18:48:13 +00:00
TransactionID txn = beginTransaction ( ) ;
2023-04-02 13:00:21 +00:00
for ( const std : : pair < const std : : string , iStorage * > & pair : storages ) {
int rc = pair . second - > drop ( txn ) ;
2023-04-03 18:48:13 +00:00
if ( rc ! = MDB_SUCCESS ) {
abortTransaction ( txn ) ;
2022-09-17 12:31:58 +00:00
throw Unknown ( name , mdb_strerror ( rc ) , pair . first ) ;
2023-04-03 18:48:13 +00:00
}
2022-09-17 12:31:58 +00:00
}
2023-04-03 18:48:13 +00:00
commitTransaction ( txn ) ;
2022-09-17 12:31:58 +00:00
}
2023-04-10 21:01:19 +00:00
/**
* \ brief Begins read - only transaction
*
* \ returns read - only transaction ID
*
* \ exception LMDBAL : : Closed - thrown if the database is closed
* \ exception LMDBAL : : Unknown - thrown if something unexpected happened
*/
2023-03-21 11:05:54 +00:00
LMDBAL : : TransactionID LMDBAL : : Base : : beginReadOnlyTransaction ( ) const {
2023-03-20 15:37:13 +00:00
return beginReadOnlyTransaction ( emptyName ) ; }
2023-04-10 21:01:19 +00:00
/**
* \ brief Begins writable transaction
*
* \ returns writable transaction ID
*
* \ exception LMDBAL : : Closed - thrown if the database is closed
* \ exception LMDBAL : : Unknown - thrown if something unexpected happened
*/
2023-03-21 11:05:54 +00:00
LMDBAL : : TransactionID LMDBAL : : Base : : beginTransaction ( ) const {
2023-03-20 15:37:13 +00:00
return beginTransaction ( emptyName ) ; }
2022-09-17 12:31:58 +00:00
2023-04-10 21:01:19 +00:00
/**
* \ brief Aborts transaction
*
* Terminates transaction cancelling changes .
* This is an optimal way to terminate read - only transactions
*
* \ param [ in ] id - transaction ID you want to abort
*
* \ exception LMDBAL : : Closed - thrown if the database is closed
* \ exception LMDBAL : : Unknown - thrown if transaction with given ID was not found or if something unexpected happened
*/
2023-03-21 11:05:54 +00:00
void LMDBAL : : Base : : abortTransaction ( LMDBAL : : TransactionID id ) const {
2023-03-20 15:37:13 +00:00
return abortTransaction ( id , emptyName ) ; }
2023-04-10 21:01:19 +00:00
/**
* \ brief Commits transaction
*
* Terminates transaction applying changes .
*
* \ param [ in ] id - transaction ID you want to commit
*
* \ exception LMDBAL : : Closed - thrown if the database is closed
* \ exception LMDBAL : : Unknown - thrown if transaction with given ID was not found or if something unexpected happened
*/
2023-04-03 18:48:13 +00:00
void LMDBAL : : Base : : commitTransaction ( LMDBAL : : TransactionID id ) {
2023-03-20 15:37:13 +00:00
return commitTransaction ( id , emptyName ) ; }
2023-04-10 21:01:19 +00:00
/**
* \ brief Begins read - only transaction
*
* This function is intended to be called from subordinate storage or cache
*
* \ param [ in ] storageName - name of the storage / cache that you begin transaction from , needed just to inform if something went wrong
*
* \ returns read - only transaction ID
*
* \ exception LMDBAL : : Closed - thrown if the database is closed
* \ exception LMDBAL : : Unknown - thrown if something unexpected happened
*/
2023-03-21 11:05:54 +00:00
LMDBAL : : TransactionID LMDBAL : : Base : : beginReadOnlyTransaction ( const std : : string & storageName ) const {
2023-03-20 15:37:13 +00:00
if ( ! opened )
throw Closed ( " beginReadOnlyTransaction " , name , storageName ) ;
2023-04-02 13:00:21 +00:00
TransactionID txn = beginPrivateReadOnlyTransaction ( storageName ) ;
2023-03-20 15:37:13 +00:00
transactions - > emplace ( txn ) ;
2023-04-02 13:00:21 +00:00
for ( const std : : pair < const std : : string , LMDBAL : : iStorage * > & pair : storages )
pair . second - > transactionStarted ( txn , true ) ;
2023-03-20 15:37:13 +00:00
return txn ;
}
2023-04-10 21:01:19 +00:00
/**
* \ brief Begins writable transaction
*
* This function is intended to be called from subordinate storage or cache
*
* \ param [ in ] storageName - name of the storage / cache that you begin transaction from , needed just to inform if something went wrong
*
* \ returns writable transaction ID
*
* \ exception LMDBAL : : Closed - thrown if the database is closed
* \ exception LMDBAL : : Unknown - thrown if something unexpected happened
*/
2023-03-21 11:05:54 +00:00
LMDBAL : : TransactionID LMDBAL : : Base : : beginTransaction ( const std : : string & storageName ) const {
2023-03-20 15:37:13 +00:00
if ( ! opened )
throw Closed ( " beginTransaction " , name , storageName ) ;
2023-04-02 13:00:21 +00:00
TransactionID txn = beginPrivateTransaction ( storageName ) ;
2023-03-20 15:37:13 +00:00
transactions - > emplace ( txn ) ;
2023-04-02 13:00:21 +00:00
for ( const std : : pair < const std : : string , LMDBAL : : iStorage * > & pair : storages )
pair . second - > transactionStarted ( txn , false ) ;
2023-03-20 15:37:13 +00:00
return txn ;
}
2023-04-10 21:01:19 +00:00
/**
* \ brief Aborts transaction
*
* Terminates transaction cancelling changes .
* This is an optimal way to terminate read - only transactions .
* This function is intended to be called from subordinate storage or cache
*
* \ param [ in ] id - transaction ID you want to abort
* \ param [ in ] storageName - name of the storage / cache that you begin transaction from , needed just to inform if something went wrong
*
* \ exception LMDBAL : : Closed - thrown if the database is closed
* \ exception LMDBAL : : Unknown - thrown if transaction with given ID was not found or if something unexpected happened
*/
2023-03-21 11:05:54 +00:00
void LMDBAL : : Base : : abortTransaction ( LMDBAL : : TransactionID id , const std : : string & storageName ) const {
2023-03-20 15:37:13 +00:00
if ( ! opened )
throw Closed ( " abortTransaction " , name , storageName ) ;
Transactions : : iterator itr = transactions - > find ( id ) ;
if ( itr = = transactions - > end ( ) ) //TODO may be it's a good idea to make an exception class for this
throw Unknown ( name , " unable to abort transaction: transaction was not found " , storageName ) ;
2023-04-02 13:00:21 +00:00
abortPrivateTransaction ( id , storageName ) ;
for ( const std : : pair < const std : : string , LMDBAL : : iStorage * > & pair : storages )
pair . second - > transactionAborted ( id ) ;
2023-03-20 15:37:13 +00:00
transactions - > erase ( itr ) ;
}
2023-04-10 21:01:19 +00:00
/**
* \ brief Commits transaction
*
* Terminates transaction applying changes .
* This function is intended to be called from subordinate storage or cache
*
* \ param [ in ] id - transaction ID you want to commit
* \ param [ in ] storageName - name of the storage / cache that you begin transaction from , needed just to inform if something went wrong
*
* \ exception LMDBAL : : Closed - thrown if the database is closed
* \ exception LMDBAL : : Unknown - thrown if transaction with given ID was not found or if something unexpected happened
*/
2023-04-03 18:48:13 +00:00
void LMDBAL : : Base : : commitTransaction ( LMDBAL : : TransactionID id , const std : : string & storageName ) {
2023-03-20 15:37:13 +00:00
if ( ! opened )
throw Closed ( " abortTransaction " , name , storageName ) ;
Transactions : : iterator itr = transactions - > find ( id ) ;
if ( itr = = transactions - > end ( ) ) //TODO may be it's a good idea to make an exception class for this
throw Unknown ( name , " unable to commit transaction: transaction was not found " , storageName ) ;
2023-04-02 13:00:21 +00:00
commitPrivateTransaction ( id , storageName ) ;
for ( const std : : pair < const std : : string , LMDBAL : : iStorage * > & pair : storages )
pair . second - > transactionCommited ( id ) ;
2023-03-20 15:37:13 +00:00
transactions - > erase ( itr ) ;
2023-04-02 13:00:21 +00:00
}
2023-04-10 21:01:19 +00:00
/**
* \ brief Begins read - only transaction
*
* This function is intended to be called from subordinate storage or cache ,
* it ' s not accounted in transaction collection , other storages and caches are not notified about this kind of transaction
*
* \ param [ in ] storageName - name of the storage / cache that you begin transaction from , needed just to inform if something went wrong
*
* \ returns read - only transaction ID
*
* \ exception LMDBAL : : Unknown - thrown if something unexpected happened
*/
2023-04-02 13:00:21 +00:00
LMDBAL : : TransactionID LMDBAL : : Base : : beginPrivateReadOnlyTransaction ( const std : : string & storageName ) const {
MDB_txn * txn ;
int rc = mdb_txn_begin ( environment , NULL , MDB_RDONLY , & txn ) ;
2023-04-03 18:48:13 +00:00
if ( rc ! = MDB_SUCCESS ) {
2023-04-02 13:00:21 +00:00
mdb_txn_abort ( txn ) ;
throw Unknown ( name , mdb_strerror ( rc ) , storageName ) ;
}
return txn ;
}
2023-04-10 21:01:19 +00:00
/**
* \ brief Begins writable transaction
*
* This function is intended to be called from subordinate storage or cache ,
* it ' s not accounted in transaction collection , other storages and caches are not notified about this kind of transaction
*
* \ param [ in ] storageName - name of the storage / cache that you begin transaction from , needed just to inform if something went wrong
*
* \ returns writable transaction ID
*
* \ exception LMDBAL : : Unknown - thrown if something unexpected happened
*/
2023-04-02 13:00:21 +00:00
LMDBAL : : TransactionID LMDBAL : : Base : : beginPrivateTransaction ( const std : : string & storageName ) const {
MDB_txn * txn ;
int rc = mdb_txn_begin ( environment , NULL , 0 , & txn ) ;
2023-04-03 18:48:13 +00:00
if ( rc ! = MDB_SUCCESS ) {
2023-04-02 13:00:21 +00:00
mdb_txn_abort ( txn ) ;
throw Unknown ( name , mdb_strerror ( rc ) , storageName ) ;
}
return txn ;
}
2023-04-10 21:01:19 +00:00
/**
* \ brief Aborts transaction
*
* Terminates transaction cancelling changes .
* This is an optimal way to terminate read - only transactions .
* This function is intended to be called from subordinate storage or cache ,
* it ' s not accounted in transaction collection , other storages and caches are not notified about this kind of transaction
*
* \ param [ in ] id - transaction ID you want to abort
* \ param [ in ] storageName - name of the storage / cache that you begin transaction from , unused here
*/
2023-04-02 13:00:21 +00:00
void LMDBAL : : Base : : abortPrivateTransaction ( LMDBAL : : TransactionID id , const std : : string & storageName ) const {
UNUSED ( storageName ) ;
mdb_txn_abort ( id ) ;
}
2023-04-10 21:01:19 +00:00
/**
* \ brief Commits transaction
*
* Terminates transaction applying changes .
* This function is intended to be called from subordinate storage or cache
* it ' s not accounted in transaction collection , other storages and caches are not notified about this kind of transaction
*
* \ param [ in ] id - transaction ID you want to commit
* \ param [ in ] storageName - name of the storage / cache that you begin transaction from , needed just to inform if something went wrong
*
* \ exception LMDBAL : : Unknown - thrown if transaction with given ID was not found or if something unexpected happened
*/
2023-04-03 18:48:13 +00:00
void LMDBAL : : Base : : commitPrivateTransaction ( LMDBAL : : TransactionID id , const std : : string & storageName ) {
2023-04-02 13:00:21 +00:00
int rc = mdb_txn_commit ( id ) ;
2023-03-20 15:37:13 +00:00
if ( rc ! = MDB_SUCCESS )
throw Unknown ( name , mdb_strerror ( rc ) , storageName ) ;
}