1 // 2 // database.hpp 3 // 4 // Copyright © 2024 Apple Inc. All rights reserved. 5 // 6 // Please refer to the license found in the LICENSE file in the root directory of the source tree. 7 8 #pragma once 9 10 #include <bitset> 11 #include <functional> 12 #include <memory> 13 #include <string> 14 #include <system_error> 15 16 #include <sqlite3.h> 17 18 #include <statement.hpp> 19 20 namespace executorchcoreml { 21 namespace sqlite { 22 23 /// The deleter for a sqlite database, closes the database at the time of deallocation. 24 struct DatabaseDeleter { operator ()executorchcoreml::sqlite::DatabaseDeleter25 inline void operator()(sqlite3* handle) { 26 if (handle) { 27 sqlite3_close(handle); 28 } 29 } 30 }; 31 32 /// Database represents a sqlite database. 33 class Database { 34 public: 35 /// OpenOptions lists all the options that can be used to open a sqlite database. 36 /// 37 /// The caller is responsible for setting and passing a valid option when opening the database. 38 class OpenOptions { 39 public: 40 /// Corresponds to `SQLITE_OPEN_READONLY` flag, when set the database will be opened in read-only mode. set_read_only_option(bool enable)41 inline void set_read_only_option(bool enable) noexcept { 42 flags_[0] = enable; 43 } 44 45 /// Returns `true` if read-only option is enabled otherwise `false`. is_read_only_option_enabled() const46 inline bool is_read_only_option_enabled() const noexcept { 47 return flags_[0]; 48 } 49 50 /// Corresponds to `SQLITE_OPEN_READWRITE` flag, when set the database will be opened in read and write mode. set_read_write_option(bool enable)51 inline void set_read_write_option(bool enable) noexcept { 52 flags_[1] = enable; 53 } 54 55 /// Returns `true` if read and write option is enabled otherwise `false`. is_read_write_option_enabled() const56 inline bool is_read_write_option_enabled() const noexcept { 57 return flags_[1]; 58 } 59 60 /// Corresponds to `SQLITE_OPEN_CREATE` flag, when set the database will be created if it does not exist. set_create_option(bool enable)61 inline void set_create_option(bool enable) noexcept { 62 flags_[2] = enable; 63 } 64 65 /// Returns `true` if create option is enabled otherwise `false`. is_create_option_enabled() const66 inline bool is_create_option_enabled() const noexcept { 67 return flags_[2]; 68 } 69 70 /// Corresponds to `SQLITE_OPEN_MEMORY` flag, when set the database will be opened as in-memory database. set_memory_option(bool enable)71 inline void set_memory_option(bool enable) noexcept { 72 flags_[3] = enable; 73 } 74 75 /// Returns `true` if memory option is enabled otherwise `false`. is_memory_option_enabled() const76 inline bool is_memory_option_enabled() const noexcept { 77 return flags_[3]; 78 } 79 80 /// Corresponds to `SQLITE_OPEN_NOMUTEX` flag, when set the database connection will use the "multi-thread" threading mode. set_no_mutex_option(bool enable)81 inline void set_no_mutex_option(bool enable) noexcept { 82 flags_[4] = enable; 83 } 84 85 /// Returns `true` if no mutex option is enabled otherwise `false`. is_no_mutex_option_enabled() const86 inline bool is_no_mutex_option_enabled() const noexcept { 87 return flags_[4]; 88 } 89 90 /// Corresponds to `SQLITE_OPEN_FULLMUTEX` flag, when set the database connection will use the "serialized" threading mode. set_full_mutex_option(bool enable)91 inline void set_full_mutex_option(bool enable) noexcept { 92 flags_[5] = enable; 93 } 94 95 /// Returns `true` if full mutex option is enabled otherwise `false`. is_full_mutex_option_enabled() const96 inline bool is_full_mutex_option_enabled() const noexcept { 97 return flags_[5]; 98 } 99 100 /// Corresponds to `SQLITE_OPEN_SHAREDCACHE` flag, when set the database will be opened with shared cache enabled. set_shared_cache_option(bool enable)101 inline void set_shared_cache_option(bool enable) noexcept { 102 flags_[6] = enable; 103 } 104 105 /// Returns `true` if shared cache option is enabled otherwise `false`. is_shared_cache_option_enabled() const106 inline bool is_shared_cache_option_enabled() const noexcept { 107 return flags_[6]; 108 } 109 110 /// Corresponds to `SQLITE_OPEN_URI` flag, when set the filename can be interpreted as a URI. set_uri_option(bool enable)111 inline void set_uri_option(bool enable) noexcept { 112 flags_[7] = enable; 113 } 114 115 /// Returns `true` if URI option is enabled otherwise `false`. is_uri_option_enabled() const116 inline bool is_uri_option_enabled() const noexcept { 117 return flags_[7]; 118 } 119 120 /// Returns the sqlite flags that can be used to open a sqlite database from the set options. 121 int get_sqlite_flags() const noexcept; 122 123 private: 124 std::bitset<8> flags_; 125 }; 126 127 /// Represents sqlite synchronous flag. 128 enum class SynchronousMode: uint8_t { 129 Extra = 0, 130 Full, 131 Normal, 132 Off, 133 }; 134 135 /// Represents the behavior of a sqlite transaction 136 enum class TransactionBehavior: uint8_t { 137 Deferred = 0, 138 Immediate, 139 Exclusive, 140 }; 141 142 /// Constructs a database from a file path. Database(const std::string & filePath)143 Database(const std::string& filePath) noexcept 144 :file_path_(filePath) 145 {} 146 147 Database(Database const&) = delete; 148 Database& operator=(Database const&) = delete; 149 150 /// Opens a database 151 /// 152 /// @param options The options for opening the database. 153 /// @param mode The synchronous mode for the database connection. 154 /// @param busy_timeout_ms The busy timeout interval in milliseconds. 155 /// @param error On failure, error is populated with the failure reason. 156 /// @retval `true` if the database is opened otherwise `false`. 157 bool open(OpenOptions options, 158 SynchronousMode mode, 159 int busy_timeout_ms, 160 std::error_code& error) noexcept; 161 162 /// Returns `true` is the database is opened otherwise `false`. 163 bool is_open() const noexcept; 164 165 /// Check if a table exists with the specified name. 166 /// 167 /// @param tableName The table name. 168 /// @param error On failure, error is populated with the failure reason. 169 /// @retval `true` if the table exists otherwise `false`. 170 bool table_exists(const std::string& tableName, std::error_code& error) const noexcept; 171 172 /// Drops a table with the specified name. 173 /// 174 /// @param tableName The table name. 175 /// @param error On failure, error is populated with the failure reason. 176 /// @retval `true` if the table is dropped otherwise `false`. 177 bool drop_table(const std::string& tableName, std::error_code& error) const noexcept; 178 179 /// Returns the number of rows in the table. 180 /// 181 /// @param tableName The table name. 182 /// @param error On failure, error is populated with the failure reason. 183 /// @retval The number of rows in the table. 184 int64_t get_row_count(const std::string& tableName, std::error_code& error) const noexcept; 185 186 /// Executes the provided statements. 187 /// 188 /// @param statements The statements to execute. 189 /// @param error On failure, error is populated with the failure reason. 190 /// @retval `true` if the execution succeeded otherwise `false`. 191 bool execute(const std::string& statements, std::error_code& error) const noexcept; 192 193 /// Returns the number of rows updated by the last statement. 194 int get_updated_row_count() const noexcept; 195 196 /// Returns the error message of the last failed sqlite call. 197 std::string get_last_error_message() const noexcept; 198 199 /// Returns the error code of the last failed sqlite call. 200 std::error_code get_last_error_code() const noexcept; 201 202 /// Returns the extended error code of the last failed sqlite call. 203 std::error_code get_last_extended_error_code() const noexcept; 204 205 /// Returns the value of the last inserted row id. 206 int64_t get_last_inserted_row_id() const noexcept; 207 208 /// Returns the file path that was used to create the database. file_path() const209 std::string_view file_path() const noexcept { 210 return file_path_; 211 } 212 213 /// Compiles the provided statement and returns it. 214 /// 215 /// @param statement The statement to be compiled. 216 /// @param error On failure, error is populated with the failure reason. 217 /// @retval The compiled statement. 218 std::unique_ptr<PreparedStatement> 219 prepare_statement(const std::string& statement, std::error_code& error) const noexcept; 220 221 /// Executes the provided function inside a transaction. 222 /// 223 /// The transaction is committed only if the provided function returns `true` otherwise the transaction is rolled-back. 224 /// 225 /// @param fn The function that will be executed inside a transaction. 226 /// @param behavior The transaction behavior. 227 /// @param error On failure, error is populated with the failure reason. 228 /// @retval `true` if the transaction is committed otherwise `false`. 229 bool transaction(const std::function<bool(void)>& fn, 230 TransactionBehavior behavior, 231 std::error_code& error) noexcept; 232 233 /// Opens an in-memory database. 234 /// 235 /// @param mode The synchronous mode. 236 /// @param busy_timeout_ms The total busy timeout duration, in milliseconds. 237 /// @param error On failure, error is populated with the failure reason. 238 /// @retval The opened in-memory database. 239 static std::shared_ptr<Database> make_inmemory(SynchronousMode mode, 240 int busy_timeout_ms, 241 std::error_code& error); 242 243 /// Creates and opens a database at the specified path. 244 /// 245 /// @param filePath The file path of the database. 246 /// @param options The open options. 247 /// @param mode The synchronous mode. 248 /// @param busy_timeout_ms The total busy timeout duration, in milliseconds. 249 /// @param error On failure, error is populated with the failure reason. 250 /// @retval The opened database. 251 static std::shared_ptr<Database> make(const std::string& filePath, 252 OpenOptions options, 253 SynchronousMode mode, 254 int busy_timeout_ms, 255 std::error_code& error); 256 257 private: 258 /// Returns the internal sqlite database. get_underlying_database() const259 inline sqlite3 *get_underlying_database() const noexcept { 260 return sqlite_database_.get(); 261 } 262 263 /// Registers an internal busy handler that keeps attempting to acquire a busy lock until the total specified time has passed. 264 bool set_busy_timeout(int busy_timeout_ms, std::error_code& error) const noexcept; 265 266 /// Begins an explicit transaction with the specified behavior. 267 bool begin_transaction(TransactionBehavior behavior, std::error_code& error) const noexcept; 268 269 /// Commits the last open transaction. 270 bool commit_transaction(std::error_code& error) const noexcept; 271 272 /// Rollbacks the last open transaction. 273 bool rollback_transaction(std::error_code& error) const noexcept; 274 275 std::string file_path_; 276 std::unique_ptr<sqlite3, DatabaseDeleter> sqlite_database_; 277 }; 278 279 } // namespace sqlite 280 } // namespace executorchcoreml 281