reapack

Package manager for REAPER
Log | Files | Refs | Submodules | README | LICENSE

database.cpp (4261B)


      1 /* ReaPack: Package manager for REAPER
      2  * Copyright (C) 2015-2025  Christian Fillion
      3  *
      4  * This program is free software: you can redistribute it and/or modify
      5  * it under the terms of the GNU Lesser General Public License as published by
      6  * the Free Software Foundation, either version 3 of the License, or
      7  * (at your option) any later version.
      8  *
      9  * This program is distributed in the hope that it will be useful,
     10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12  * GNU Lesser General Public License for more details.
     13  *
     14  * You should have received a copy of the GNU Lesser General Public License
     15  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
     16  */
     17 
     18 #include "database.hpp"
     19 
     20 #include "errors.hpp"
     21 
     22 #include <cinttypes>
     23 #include <sqlite3.h>
     24 
     25 Database::Database(const std::string &fn)
     26   : m_savePoint(0)
     27 {
     28   if(sqlite3_open(fn.empty() ? ":memory:" : fn.c_str(), &m_db)) {
     29     const auto &error = lastError();
     30     sqlite3_close(m_db);
     31 
     32     throw error;
     33   }
     34 
     35   exec("PRAGMA foreign_keys = 1");
     36 }
     37 
     38 Database::~Database()
     39 {
     40   for(Statement *stmt : m_statements)
     41     delete stmt;
     42 
     43   sqlite3_close(m_db);
     44 }
     45 
     46 Statement *Database::prepare(const char *sql)
     47 {
     48   Statement *stmt = new Statement(sql, this);
     49   m_statements.push_back(stmt);
     50 
     51   return stmt;
     52 }
     53 
     54 void Database::exec(const char *sql)
     55 {
     56   if(sqlite3_exec(m_db, sql, nullptr, nullptr, nullptr) != SQLITE_OK)
     57     throw lastError();
     58 }
     59 
     60 reapack_error Database::lastError() const
     61 {
     62   return reapack_error(sqlite3_errmsg(m_db));
     63 }
     64 
     65 int64_t Database::lastInsertId() const
     66 {
     67   return sqlite3_last_insert_rowid(m_db);
     68 }
     69 
     70 auto Database::version() const -> Version
     71 {
     72   int32_t version = 0;
     73 
     74   Statement stmt("PRAGMA user_version", this);
     75   stmt.exec([&] {
     76     version = static_cast<int32_t>(stmt.intColumn(0));
     77     return false;
     78   });
     79 
     80   return {static_cast<int16_t>(version >> 16), static_cast<int16_t>(version)};
     81 }
     82 
     83 void Database::setVersion(const Version &version)
     84 {
     85   int32_t value = version.minor | static_cast<int32_t>(version.major) << 16;
     86 
     87   char sql[255];
     88   sprintf(sql, "PRAGMA user_version = %" PRId32, value);
     89   exec(sql);
     90 }
     91 
     92 int Database::errorCode() const
     93 {
     94   return sqlite3_errcode(m_db);
     95 }
     96 
     97 void Database::begin()
     98 {
     99   // IMMEDIATE -> don't wait until the first query to aquire a lock
    100   // but still allow new read-only connections (unlike EXCLUSIVE)
    101   // while preventing new transactions to be made as long as it's active
    102   exec("BEGIN IMMEDIATE TRANSACTION");
    103 }
    104 
    105 void Database::commit()
    106 {
    107   exec("COMMIT");
    108 }
    109 
    110 void Database::savepoint()
    111 {
    112   char sql[64];
    113   sprintf(sql, "SAVEPOINT sp%zu", m_savePoint++);
    114 
    115   exec(sql);
    116 }
    117 
    118 void Database::restore()
    119 {
    120   char sql[64];
    121   sprintf(sql, "ROLLBACK TO SAVEPOINT sp%zu", --m_savePoint);
    122 
    123   exec(sql);
    124 }
    125 
    126 void Database::release()
    127 {
    128   char sql[64];
    129   sprintf(sql, "RELEASE SAVEPOINT sp%zu", --m_savePoint);
    130 
    131   exec(sql);
    132 }
    133 
    134 Statement::Statement(const char *sql, const Database *db)
    135   : m_db(db)
    136 {
    137   if(sqlite3_prepare_v2(db->m_db, sql, -1, &m_stmt, nullptr) != SQLITE_OK)
    138     throw m_db->lastError();
    139 }
    140 
    141 Statement::~Statement()
    142 {
    143   sqlite3_finalize(m_stmt);
    144 }
    145 
    146 void Statement::bind(const int index, const std::string &text)
    147 {
    148   if(sqlite3_bind_text(m_stmt, index, text.c_str(), -1, SQLITE_TRANSIENT))
    149     throw m_db->lastError();
    150 }
    151 
    152 void Statement::bind(const int index, const int64_t integer)
    153 {
    154   if(sqlite3_bind_int64(m_stmt, index, integer))
    155     throw m_db->lastError();
    156 }
    157 
    158 void Statement::exec()
    159 {
    160   exec([=] { return false; });
    161 }
    162 
    163 void Statement::exec(const ExecCallback &callback)
    164 {
    165   while(true) {
    166     switch(sqlite3_step(m_stmt)) {
    167     case SQLITE_ROW:
    168       if(callback())
    169         break;
    170       [[fallthrough]];
    171     case SQLITE_DONE:
    172       sqlite3_clear_bindings(m_stmt);
    173       sqlite3_reset(m_stmt);
    174       return;
    175     default:
    176       sqlite3_clear_bindings(m_stmt);
    177       sqlite3_reset(m_stmt);
    178       throw m_db->lastError();
    179     };
    180   }
    181 }
    182 
    183 int64_t Statement::intColumn(const int index) const
    184 {
    185   return sqlite3_column_int64(m_stmt, index);
    186 }
    187 
    188 std::string Statement::stringColumn(const int index) const
    189 {
    190   const unsigned char *col = sqlite3_column_text(m_stmt, index);
    191 
    192   if(col)
    193     return reinterpret_cast<const char *>(col);
    194   else
    195     return {};
    196 }