99#include < iostream>
1010#include < limits>
1111#include < map>
12+ #include < memory>
1213#include < optional>
1314#include < sqlite3.h>
1415#include < sstream>
@@ -77,13 +78,13 @@ void sqliteCloseAll() {
7778void sqliteAttachDb (const std::string& mainDBName, const std::string& docPath, const std::string& databaseToAttach,
7879 const std::string& alias) {
7980 /* *
80- * There is no need to check if mainDBName is opened because sqliteExecuteLiteral will do that.
81+ * There is no need to check if mainDBName is opened because sqliteExecuteCommand will do that.
8182 * */
8283 std::string dbPath = get_db_path (databaseToAttach, docPath);
8384 std::string statement = " ATTACH DATABASE '" + dbPath + " ' AS " + alias;
8485
8586 try {
86- sqliteExecuteLiteral (mainDBName, statement);
87+ sqliteExecuteCommand (mainDBName, statement);
8788 } catch (NitroSQLiteException& e) {
8889 throw NitroSQLiteException (NitroSQLiteExceptionType::UnableToAttachToDatabase,
8990 mainDBName + " was unable to attach another database: " + std::string (e.what ()));
@@ -92,12 +93,12 @@ void sqliteAttachDb(const std::string& mainDBName, const std::string& docPath, c
9293
9394void sqliteDetachDb (const std::string& mainDBName, const std::string& alias) {
9495 /* *
95- * There is no need to check if mainDBName is opened because sqliteExecuteLiteral will do that.
96+ * There is no need to check if mainDBName is opened because sqliteExecuteCommand will do that.
9697 * */
9798 std::string statement = " DETACH DATABASE " + alias;
9899
99100 try {
100- sqliteExecuteLiteral (mainDBName, statement);
101+ sqliteExecuteCommand (mainDBName, statement);
101102 } catch (NitroSQLiteException& e) {
102103 throw NitroSQLiteException (NitroSQLiteExceptionType::UnableToAttachToDatabase,
103104 mainDBName + " was unable to detach database: " + std::string (e.what ()));
@@ -144,173 +145,135 @@ void bindStatement(sqlite3_stmt* statement, const SQLiteQueryParams& values) {
144145 }
145146}
146147
147- std::shared_ptr<HybridNitroSQLiteQueryResult> sqliteExecute (const std::string& dbName, const std::string& query,
148- const std::optional<SQLiteQueryParams>& params) {
149- if (dbMap.count (dbName) == 0 ) {
150- throw NitroSQLiteException::DatabaseNotOpen (dbName);
151- }
152-
153- auto db = dbMap[dbName];
148+ namespace {
154149
155- sqlite3_stmt* statement;
156- int statementStatus = sqlite3_prepare_v2 (db, query.c_str (), -1 , &statement, NULL );
157- if (statementStatus == SQLITE_OK ) // statement is correct, bind the passed parameters
158- {
159- if (params) {
160- bindStatement (statement, *params);
150+ struct SQLiteStatementFinalizer {
151+ void operator ()(sqlite3_stmt* statement) const noexcept {
152+ if (statement != nullptr ) {
153+ sqlite3_finalize (statement);
154+ }
161155 }
162- } else {
163- throw NitroSQLiteException::SqlExecution (sqlite3_errmsg (db));
164- }
156+ };
165157
166- auto isConsuming = true ;
167- auto isFailed = false ;
158+ using SQLiteStatement = std::unique_ptr<sqlite3_stmt, SQLiteStatementFinalizer>;
168159
169- int result, i, count, column_type;
170- std::string column_name;
171- ColumnType column_declared_type;
172- SQLiteQueryResultRow row;
173- SQLiteQueryResults results;
174- std::optional<SQLiteQueryTableMetadata> metadata = std::nullopt ;
175-
176- while (isConsuming) {
177- result = sqlite3_step (statement);
178-
179- switch (result) {
180- case SQLITE_ROW :
181- i = 0 ;
182- row = std::unordered_map<std::string, SQLiteValue>();
183- count = sqlite3_column_count (statement);
184-
185- while (i < count) {
186- column_type = sqlite3_column_type (statement, i);
187- column_name = sqlite3_column_name (statement, i);
188- switch (column_type) {
189-
190- case SQLITE_INTEGER : {
191- auto column_value = sqlite3_column_double (statement, i);
192- row[column_name] = column_value;
193- break ;
194- }
195- case SQLITE_FLOAT : {
196- auto column_value = sqlite3_column_double (statement, i);
197- row[column_name] = column_value;
198- break ;
199- }
200- case SQLITE_TEXT : {
201- auto column_value = reinterpret_cast <const char *>(sqlite3_column_text (statement, i));
202- sqlite3_column_bytes (statement, i);
203- row[column_name] = column_value;
204- break ;
205- }
206- case SQLITE_BLOB : {
207- int blob_size = sqlite3_column_bytes (statement, i);
208- const void * blob = sqlite3_column_blob (statement, i);
209- // Copy the SQLite BLOB into a new native ArrayBuffer.
210- // This avoids manual memory management and unsafe pointer handling.
211- if (blob_size > 0 ) {
212- const auto * blob_data = reinterpret_cast <const uint8_t *>(blob);
213- row[column_name] = ArrayBuffer::copy (blob_data, static_cast <size_t >(blob_size));
214- } else {
215- // Represent empty BLOBs as an empty, but valid, ArrayBuffer.
216- row[column_name] = ArrayBuffer::allocate (0 );
217- }
218- break ;
219- }
220- case SQLITE_NULL :
221- // Intentionally left blank to switch to default case
222- default :
223- row[column_name] = NullType::null;
224- break ;
225- }
226- i++;
227- }
228- results.push_back (std::move (row));
229- break ;
230- case SQLITE_DONE :
231- i = 0 ;
232- count = sqlite3_column_count (statement);
233- while (i < count) {
234- column_name = sqlite3_column_name (statement, i);
235- const char * tp = sqlite3_column_decltype (statement, i);
236- column_declared_type = mapSQLiteTypeToColumnType (tp);
237- auto columnMeta = NitroSQLiteQueryColumnMetadata (std::move (column_name), std::move (column_declared_type), i);
238-
239- if (!metadata) {
240- metadata = std::make_optional<SQLiteQueryTableMetadata>();
241- }
242- metadata->insert ({column_name, columnMeta});
243- i++;
244- }
245- isConsuming = false ;
246- break ;
247- default :
248- isFailed = true ;
249- isConsuming = false ;
160+ sqlite3* getOpenDatabase (const std::string& dbName) {
161+ if (dbMap.count (dbName) == 0 ) {
162+ throw NitroSQLiteException::DatabaseNotOpen (dbName);
250163 }
164+
165+ return dbMap[dbName];
251166 }
252167
253- sqlite3_finalize (statement);
168+ SQLiteStatement prepareStatement (sqlite3* db, const std::string& query, const std::optional<SQLiteQueryParams>& params) {
169+ sqlite3_stmt* rawStatement = nullptr ;
170+ int statementStatus = sqlite3_prepare_v2 (db, query.c_str (), -1 , &rawStatement, nullptr );
171+ SQLiteStatement statement (rawStatement);
254172
255- if (isFailed ) {
256- throw NitroSQLiteException::SqlExecution (sqlite3_errmsg (db));
257- }
173+ if (statementStatus != SQLITE_OK ) {
174+ throw NitroSQLiteException::SqlExecution (sqlite3_errmsg (db));
175+ }
258176
259- int rowsAffected = sqlite3_changes (db);
260- long long latestInsertRowId = sqlite3_last_insert_rowid (db);
261- return std::make_shared<HybridNitroSQLiteQueryResult>(results, static_cast <double >(latestInsertRowId), rowsAffected, metadata);
262- }
177+ if (params) {
178+ bindStatement (statement.get (), *params);
179+ }
263180
264- SQLiteOperationResult sqliteExecuteLiteral (const std::string& dbName, const std::string& query) {
265- // Check if db connection is opened
266- if (dbMap.count (dbName) == 0 ) {
267- throw NitroSQLiteException::DatabaseNotOpen (dbName);
181+ return statement;
268182 }
269183
270- sqlite3* db = dbMap[dbName];
184+ template <typename OnRow>
185+ void consumeStatement (sqlite3* db, sqlite3_stmt* statement, OnRow&& onRow) {
186+ while (true ) {
187+ int result = sqlite3_step (statement);
271188
272- // SQLite statements need to be compiled before executed
273- sqlite3_stmt* statement;
189+ if (result == SQLITE_ROW ) {
190+ onRow (statement);
191+ continue ;
192+ }
274193
275- // Compile and move result into statement memory spot
276- int statementStatus = sqlite3_prepare_v2 (db, query.c_str (), -1 , &statement, NULL );
194+ if (result == SQLITE_DONE ) {
195+ return ;
196+ }
277197
278- if (statementStatus != SQLITE_OK ) // statemnet is correct, bind the passed parameters
279- {
280- throw NitroSQLiteException::SqlExecution (sqlite3_errmsg (db));
198+ throw NitroSQLiteException::SqlExecution (sqlite3_errmsg (db));
199+ }
281200 }
282201
283- bool isConsuming = true ;
284- bool isFailed = false ;
285-
286- int result;
287- std::string column_name;
202+ } // namespace
288203
289- while (isConsuming) {
290- result = sqlite3_step (statement);
204+ std::shared_ptr<HybridNitroSQLiteQueryResult> sqliteExecute (const std::string& dbName, const std::string& query,
205+ const std::optional<SQLiteQueryParams>& params) {
206+ auto db = getOpenDatabase (dbName);
207+ auto statement = prepareStatement (db, query, params);
208+ SQLiteQueryResults results;
291209
292- switch (result) {
293- case SQLITE_ROW :
294- isConsuming = true ;
295- break ;
210+ consumeStatement (db, statement.get (), [&](sqlite3_stmt* currentStatement) {
211+ SQLiteQueryResultRow row;
212+ int count = sqlite3_column_count (currentStatement);
213+
214+ for (int i = 0 ; i < count; i++) {
215+ int columnType = sqlite3_column_type (currentStatement, i);
216+ std::string columnName = sqlite3_column_name (currentStatement, i);
217+
218+ switch (columnType) {
219+ case SQLITE_INTEGER :
220+ case SQLITE_FLOAT :
221+ row[columnName] = sqlite3_column_double (currentStatement, i);
222+ break ;
223+ case SQLITE_TEXT : {
224+ auto columnValue = reinterpret_cast <const char *>(sqlite3_column_text (currentStatement, i));
225+ row[columnName] = columnValue;
226+ break ;
227+ }
228+ case SQLITE_BLOB : {
229+ int blobSize = sqlite3_column_bytes (currentStatement, i);
230+ const void * blob = sqlite3_column_blob (currentStatement, i);
231+ if (blobSize > 0 ) {
232+ const auto * blobData = reinterpret_cast <const uint8_t *>(blob);
233+ row[columnName] = ArrayBuffer::copy (blobData, static_cast <size_t >(blobSize));
234+ } else {
235+ row[columnName] = ArrayBuffer::allocate (0 );
236+ }
237+ break ;
238+ }
239+ case SQLITE_NULL :
240+ default :
241+ row[columnName] = NullType::null;
242+ break ;
243+ }
244+ }
296245
297- case SQLITE_DONE :
298- isConsuming = false ;
299- break ;
246+ results.push_back (std::move (row));
247+ });
300248
301- default :
302- isFailed = true ;
303- isConsuming = false ;
249+ std::optional<SQLiteQueryTableMetadata> metadata = std::nullopt ;
250+ int count = sqlite3_column_count (statement.get ());
251+ for (int i = 0 ; i < count; i++) {
252+ std::string columnName = sqlite3_column_name (statement.get (), i);
253+ ColumnType columnDeclaredType = mapSQLiteTypeToColumnType (sqlite3_column_decltype (statement.get (), i));
254+ auto columnMeta = NitroSQLiteQueryColumnMetadata (columnName, std::move (columnDeclaredType), i);
255+
256+ if (!metadata) {
257+ metadata = std::make_optional<SQLiteQueryTableMetadata>();
304258 }
259+ metadata->insert ({columnName, std::move (columnMeta)});
305260 }
306261
307- sqlite3_finalize (statement);
262+ int rowsAffected = sqlite3_changes (db);
263+ long long latestInsertRowId = sqlite3_last_insert_rowid (db);
264+ return std::make_shared<HybridNitroSQLiteQueryResult>(std::move (results), static_cast <double >(latestInsertRowId), rowsAffected,
265+ std::move (metadata));
266+ }
308267
309- if (isFailed) {
310- throw NitroSQLiteException::SqlExecution (sqlite3_errmsg (db));
311- }
268+ SQLiteOperationResult sqliteExecuteCommand (const std::string& dbName, const std::string& query,
269+ const std::optional<SQLiteQueryParams>& params) {
270+ auto db = getOpenDatabase (dbName);
271+ auto statement = prepareStatement (db, query, params);
272+ bool isReadOnly = sqlite3_stmt_readonly (statement.get ()) != 0 ;
273+
274+ consumeStatement (db, statement.get (), [](sqlite3_stmt*) {});
312275
313- return {.rowsAffected = sqlite3_changes (db)};
276+ return {.rowsAffected = isReadOnly ? 0 : sqlite3_changes (db)};
314277}
315278
316279} // namespace margelo::rnnitrosqlite
0 commit comments