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 }