diff --git a/docs/usage.md b/docs/usage.md index c169bc66..5259829c 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -41,13 +41,12 @@ struct Record { int a; int b; int c; }; auto conn = SqlConnection {}; auto stmt = SqlStatement { conn }; stmt.Prepare("SELECT a, b, c FROM That WHERE a = ? OR b = ?"); -stmt.Execute(42, 43); +auto cursor = stmt.Execute(42, 43); -SqlResultCursor cursor = stmt.GetResultCursor(); auto record = Record {}; -cursor.BindOutputColumns(&record.a, &rcord.b, &record.c); +cursor.BindOutputColumns(&record.a, &record.b, &record.c); while (cursor.FetchRow()) - std::println("{}|{}|{}", a, b, c); + std::println("{}|{}|{}", record.a, record.b, record.c); ``` ## SQL Query Builder diff --git a/src/Lightweight/DataMapper/DataMapper.hpp b/src/Lightweight/DataMapper/DataMapper.hpp index df9eba63..3deecd35 100644 --- a/src/Lightweight/DataMapper/DataMapper.hpp +++ b/src/Lightweight/DataMapper/DataMapper.hpp @@ -435,16 +435,10 @@ class DataMapper void SetId(Record& record, ValueType&& id); template - Record& BindOutputColumns(Record& record); - - template - Record& BindOutputColumns(Record& record, SqlStatement* stmt); - - template - Record& BindOutputColumns(Record& record); + Record& BindOutputColumns(Record& record, SqlResultCursor& cursor); template - Record& BindOutputColumns(Record& record, SqlStatement* stmt); + Record& BindOutputColumns(Record& record, SqlResultCursor& cursor); template std::optional LoadBelongsTo(FieldType::ValueType value); @@ -692,8 +686,7 @@ size_t SqlCoreDataMapperQueryBuilder::Count() this->_query.searchCondition.tableAlias, this->_query.searchCondition.tableJoins, this->_query.searchCondition.condition)); - stmt.ExecuteWithVariants(_boundInputs); - auto reader = stmt.GetResultCursor(); + auto reader = stmt.ExecuteWithVariants(_boundInputs); if (reader.FetchRow()) return reader.GetColumn(1); return 0; @@ -714,8 +707,7 @@ bool SqlCoreDataMapperQueryBuilder::Exist() 1); stmt.Prepare(query); - stmt.ExecuteWithVariants(_boundInputs); - if (SqlResultCursor reader = stmt.GetResultCursor(); reader.FetchRow()) + if (auto reader = stmt.ExecuteWithVariants(_boundInputs); reader.FetchRow()) return true; return false; } @@ -732,8 +724,7 @@ void SqlCoreDataMapperQueryBuilder::Delete() stmt.Prepare(query); stmt.Prepare(query); - stmt.ExecuteWithVariants(_boundInputs); - stmt.CloseCursor(); + [[maybe_unused]] auto cursor = stmt.ExecuteWithVariants(_boundInputs); } template @@ -750,8 +741,7 @@ std::vector SqlCoreDataMapperQueryBuilder this->_query.searchCondition.condition, this->_query.orderBy, this->_query.groupBy)); - stmt.ExecuteWithVariants(_boundInputs); - Derived::ReadResults(stmt.Connection().ServerType(), stmt.GetResultCursor(), &records); + Derived::ReadResults(stmt.Connection().ServerType(), stmt.ExecuteWithVariants(_boundInputs), &records); if constexpr (DataMapperRecord) { // This can be called when record type is not plain aggregate type @@ -789,8 +779,7 @@ auto SqlCoreDataMapperQueryBuilder::All() -> std: this->_query.searchCondition.condition, this->_query.orderBy, this->_query.groupBy)); - stmt.ExecuteWithVariants(_boundInputs); - SqlResultCursor reader = stmt.GetResultCursor(); + auto reader = stmt.ExecuteWithVariants(_boundInputs); auto const outputColumnsBound = detail::CanSafelyBindOutputColumn(stmt.Connection().ServerType()); while (true) { @@ -827,10 +816,9 @@ auto SqlCoreDataMapperQueryBuilder::All() -> std: this->_query.searchCondition.condition, this->_query.orderBy, this->_query.groupBy)); - stmt.ExecuteWithVariants(_boundInputs); + auto reader = stmt.ExecuteWithVariants(_boundInputs); auto const outputColumnsBound = detail::CanSafelyBindOutputColumns(stmt.Connection().ServerType()); - SqlResultCursor reader = stmt.GetResultCursor(); while (true) { auto& record = records.emplace_back(); @@ -868,8 +856,7 @@ std::optional SqlCoreDataMapperQueryBuilder_query.searchCondition.condition, this->_query.orderBy, 1)); - stmt.ExecuteWithVariants(_boundInputs); - Derived::ReadResult(stmt.Connection().ServerType(), stmt.GetResultCursor(), &record); + Derived::ReadResult(stmt.Connection().ServerType(), stmt.ExecuteWithVariants(_boundInputs), &record); if constexpr (QueryOptions.loadRelations) { if (record) @@ -897,8 +884,7 @@ auto SqlCoreDataMapperQueryBuilder::First() -> st this->_query.searchCondition.condition, this->_query.orderBy, count)); - stmt.ExecuteWithVariants(_boundInputs); - if (SqlResultCursor reader = stmt.GetResultCursor(); reader.FetchRow()) + if (auto reader = stmt.ExecuteWithVariants(_boundInputs); reader.FetchRow()) return reader.template GetColumn>(1); return std::nullopt; } @@ -919,10 +905,9 @@ auto SqlCoreDataMapperQueryBuilder::First() -> st this->_query.searchCondition.condition, this->_query.orderBy, 1)); - stmt.ExecuteWithVariants(_boundInputs); auto& record = optionalRecord.emplace(); - SqlResultCursor reader = stmt.GetResultCursor(); + auto reader = stmt.ExecuteWithVariants(_boundInputs); auto const outputColumnsBound = detail::CanSafelyBindOutputColumns(stmt.Connection().ServerType()); if (outputColumnsBound) #if defined(LIGHTWEIGHT_CXX26_REFLECTION) @@ -958,8 +943,7 @@ std::vector SqlCoreDataMapperQueryBuilder this->_query.searchCondition.condition, this->_query.orderBy, n)); - stmt.ExecuteWithVariants(_boundInputs); - Derived::ReadResults(stmt.Connection().ServerType(), stmt.GetResultCursor(), &records); + Derived::ReadResults(stmt.Connection().ServerType(), stmt.ExecuteWithVariants(_boundInputs), &records); if constexpr (QueryOptions.loadRelations) { @@ -988,8 +972,7 @@ std::vector SqlCoreDataMapperQueryBuilder this->_query.groupBy, offset, limit)); - stmt.ExecuteWithVariants(_boundInputs); - Derived::ReadResults(stmt.Connection().ServerType(), stmt.GetResultCursor(), &records); + Derived::ReadResults(stmt.Connection().ServerType(), stmt.ExecuteWithVariants(_boundInputs), &records); if constexpr (QueryOptions.loadRelations) { for (auto& record: records) @@ -1018,10 +1001,9 @@ std::vector SqlCoreDataMapperQueryBuilder this->_query.groupBy, offset, limit)); - stmt.ExecuteWithVariants(_boundInputs); + auto reader = stmt.ExecuteWithVariants(_boundInputs); auto const outputColumnsBound = detail::CanSafelyBindOutputColumns(stmt.Connection().ServerType()); - SqlResultCursor reader = stmt.GetResultCursor(); while (true) { auto& record = records.emplace_back(); @@ -1067,10 +1049,9 @@ template this->_query.searchCondition.condition, this->_query.orderBy, n)); - stmt.ExecuteWithVariants(_boundInputs); + auto reader = stmt.ExecuteWithVariants(_boundInputs); auto const outputColumnsBound = detail::CanSafelyBindOutputColumns(stmt.Connection().ServerType()); - SqlResultCursor reader = stmt.GetResultCursor(); while (true) { auto& record = records.emplace_back(); @@ -1230,8 +1211,8 @@ void DataMapper::CreateTable() static_assert(DataMapperRecord, "Record must satisfy DataMapperRecord"); auto const sqlQueryStrings = CreateTableString(_connection.ServerType()); - for (auto const& sqlQueryString: sqlQueryStrings) - _stmt.ExecuteDirect(sqlQueryString); + for (auto const& sqlQueryString: sqlQueryStrings) [[maybe_unused]] + auto cursor = _stmt.ExecuteDirect(sqlQueryString); } template @@ -1327,7 +1308,7 @@ RecordPrimaryKeyType DataMapper::CreateInternal( } }); #endif - _stmt.Execute(); + [[maybe_unused]] auto cursor = _stmt.Execute(); if constexpr (HasAutoIncrementPrimaryKey) return { _stmt.LastInsertId(RecordTableName) }; @@ -1482,7 +1463,7 @@ void DataMapper::Update(Record& record) }); #endif - _stmt.Execute(); + [[maybe_unused]] auto cursor = _stmt.Execute(); SetModifiedState(record); } @@ -1530,9 +1511,9 @@ std::size_t DataMapper::Delete(Record const& record) }); #endif - _stmt.Execute(); + auto cursor = _stmt.Execute(); - return _stmt.NumRowsAffected(); + return cursor.NumRowsAffected(); } template @@ -1553,10 +1534,9 @@ std::optional DataMapper::QuerySingleWithoutRelationAutoLoading(PrimaryK }); _stmt.Prepare(queryBuilder.First()); - _stmt.Execute(std::forward(primaryKeys)...); + auto reader = _stmt.Execute(std::forward(primaryKeys)...); auto resultRecord = std::optional { Record {} }; - auto reader = _stmt.GetResultCursor(); if (!detail::ReadSingleResult(_stmt.Connection().ServerType(), reader, *resultRecord)) return std::nullopt; @@ -1587,10 +1567,9 @@ std::optional DataMapper::QuerySingle(SqlSelectQueryBuilder selectQuery, selectQuery.Field(SqlQualifiedTableColumnName { RecordTableName, FieldNameAt }); }); _stmt.Prepare(selectQuery.First().ToSql()); - _stmt.Execute(std::forward(args)...); + auto reader = _stmt.Execute(std::forward(args)...); auto resultRecord = std::optional { Record {} }; - auto reader = _stmt.GetResultCursor(); if (!detail::ReadSingleResult(_stmt.Connection().ServerType(), reader, *resultRecord)) return std::nullopt; @@ -1619,14 +1598,14 @@ std::vector DataMapper::Query(std::string_view sqlQueryString, InputPara if constexpr (std::same_as) { _stmt.Prepare(sqlQueryString); - _stmt.Execute(std::forward(inputParameters)...); - size_t const numResultColumns = _stmt.NumColumnsAffected(); - while (_stmt.FetchRow()) + SqlResultCursor cursor = _stmt.Execute(std::forward(inputParameters)...); + size_t const numResultColumns = cursor.NumColumnsAffected(); + while (cursor.FetchRow()) { auto& record = result.emplace_back(); record.reserve(numResultColumns); for (auto const i: std::views::iota(1U, numResultColumns + 1)) - record.emplace_back(_stmt.GetColumn(static_cast(i))); + record.emplace_back(cursor.GetColumn(static_cast(i))); } } else @@ -1636,16 +1615,14 @@ std::vector DataMapper::Query(std::string_view sqlQueryString, InputPara bool const canSafelyBindOutputColumns = detail::CanSafelyBindOutputColumns(_stmt.Connection().ServerType()); _stmt.Prepare(sqlQueryString); - _stmt.Execute(std::forward(inputParameters)...); - - auto reader = _stmt.GetResultCursor(); + auto reader = _stmt.Execute(std::forward(inputParameters)...); for (;;) { auto& record = result.emplace_back(); if (canSafelyBindOutputColumns) - BindOutputColumns(record); + BindOutputColumns(record, reader); if (!reader.FetchRow()) break; @@ -1675,8 +1652,7 @@ std::vector> DataMapper::Query(SqlSelectQuery auto result = std::vector {}; _stmt.Prepare(selectQuery.ToSql()); - _stmt.Execute(); - auto reader = _stmt.GetResultCursor(); + auto reader = _stmt.Execute(); constexpr auto calculateOffset = []() { size_t offset = 1; @@ -1695,7 +1671,7 @@ std::vector> DataMapper::Query(SqlSelectQuery using TupleElement = std::decay_t>; auto& element = std::get(record); constexpr size_t offset = calculateOffset.template operator()(); - this->BindOutputColumns(element); + this->BindOutputColumns(element, reader); }); }; @@ -1755,21 +1731,20 @@ std::vector DataMapper::Query(SqlSelectQueryBuilder::ComposedQuery const static_assert(DataMapperRecord, "Record must satisfy DataMapperRecord"); _stmt.Prepare(selectQuery.ToSql()); - _stmt.Execute(std::forward(inputParameters)...); auto records = std::vector {}; // TODO: We could optimize this further by only considering ElementMask fields in Record. bool const canSafelyBindOutputColumns = detail::CanSafelyBindOutputColumns(_stmt.Connection().ServerType()); - auto reader = _stmt.GetResultCursor(); + auto reader = _stmt.Execute(std::forward(inputParameters)...); for (;;) { auto& record = records.emplace_back(); if (canSafelyBindOutputColumns) - BindOutputColumns(record); + BindOutputColumns(record, reader); if (!reader.FetchRow()) break; @@ -2240,36 +2215,20 @@ inline LIGHTWEIGHT_FORCE_INLINE void DataMapper::SetId(Record& record, ValueType #endif } -/// Binds all output columns of the record to the internal statement. +/// Binds all output columns of the record via the given cursor. template -inline LIGHTWEIGHT_FORCE_INLINE Record& DataMapper::BindOutputColumns(Record& record) +inline LIGHTWEIGHT_FORCE_INLINE Record& DataMapper::BindOutputColumns(Record& record, SqlResultCursor& cursor) { static_assert(DataMapperRecord, "Record must satisfy DataMapperRecord"); - BindOutputColumns(record, &_stmt); - return record; -} - -template -Record& DataMapper::BindOutputColumns(Record& record, SqlStatement* stmt) -{ return BindOutputColumns>, Record, InitialOffset>( - record, stmt); -} - -/// Binds output columns selected by ElementMask of the record to the internal statement. -template -inline LIGHTWEIGHT_FORCE_INLINE Record& DataMapper::BindOutputColumns(Record& record) -{ - static_assert(DataMapperRecord, "Record must satisfy DataMapperRecord"); - return BindOutputColumns(record, &_stmt); + record, cursor); } template -Record& DataMapper::BindOutputColumns(Record& record, SqlStatement* stmt) +Record& DataMapper::BindOutputColumns(Record& record, SqlResultCursor& cursor) { static_assert(DataMapperRecord, "Record must satisfy DataMapperRecord"); static_assert(!std::is_const_v); - assert(stmt != nullptr); #if defined(LIGHTWEIGHT_CXX26_REFLECTION) auto constexpr ctx = std::meta::access_context::current(); @@ -2280,23 +2239,23 @@ Record& DataMapper::BindOutputColumns(Record& record, SqlStatement* stmt) using FieldType = typename[:std::meta::type_of(el):]; if constexpr (IsField) { - stmt->BindOutputColumn(i++, &record.[:el:].MutableValue()); + cursor.BindOutputColumn(i++, &record.[:el:].MutableValue()); } else if constexpr (SqlOutputColumnBinder) { - stmt->BindOutputColumn(i++, &record.[:el:]); + cursor.BindOutputColumn(i++, &record.[:el:]); } } #else Reflection::EnumerateMembers( - record, [stmt, i = SQLUSMALLINT { InitialOffset }](Field& field) mutable { + record, [&cursor, i = SQLUSMALLINT { InitialOffset }](Field& field) mutable { if constexpr (IsField) { - stmt->BindOutputColumn(i++, &field.MutableValue()); + cursor.BindOutputColumn(i++, &field.MutableValue()); } else if constexpr (SqlOutputColumnBinder) { - stmt->BindOutputColumn(i++, &field); + cursor.BindOutputColumn(i++, &field); } }); #endif @@ -2332,11 +2291,10 @@ void DataMapper::ConfigureRelationAutoLoading(Record& record) DataMapper& dm = DataMapper::AcquireThreadLocal(); auto selectQuery = dm.BuildHasManySelectQuery(); dm._stmt.Prepare(selectQuery.Count()); - dm._stmt.Execute(pkValue); + SqlResultCursor cursor = dm._stmt.Execute(pkValue); size_t count = 0; - if (dm._stmt.FetchRow()) - count = dm._stmt.GetColumn(1); - dm._stmt.CloseCursor(); + if (cursor.FetchRow()) + count = cursor.GetColumn(1); return count; }, .all = [pkValue]() -> typename FieldType::ReferencedRecordList { @@ -2350,16 +2308,16 @@ void DataMapper::ConfigureRelationAutoLoading(Record& record) auto selectQuery = dm.BuildHasManySelectQuery(); auto stmt = SqlStatement { dm._connection }; stmt.Prepare(selectQuery.All()); - stmt.Execute(pkValue); + auto cursor = stmt.Execute(pkValue); auto referencedRecord = ReferencedRecord {}; - dm.BindOutputColumns(referencedRecord, &stmt); + dm.BindOutputColumns(referencedRecord, cursor); dm.ConfigureRelationAutoLoading(referencedRecord); - while (stmt.FetchRow()) + while (cursor.FetchRow()) { each(referencedRecord); - dm.BindOutputColumns(referencedRecord, &stmt); + dm.BindOutputColumns(referencedRecord, cursor); } }, }); @@ -2394,10 +2352,9 @@ void DataMapper::ConfigureRelationAutoLoading(Record& record) dm.CallOnHasManyThroughByPK( pkValue, [&](SqlSelectQueryBuilder& selectQuery, auto const& pk) { dm._stmt.Prepare(selectQuery.Count()); - dm._stmt.Execute(pk); - if (dm._stmt.FetchRow()) - count = dm._stmt.GetColumn(1); - dm._stmt.CloseCursor(); + SqlResultCursor cursor = dm._stmt.Execute(pk); + if (cursor.FetchRow()) + count = cursor.GetColumn(1); }); return count; }, @@ -2419,16 +2376,15 @@ void DataMapper::ConfigureRelationAutoLoading(Record& record) pkValue, [&](SqlSelectQueryBuilder& selectQuery, auto const& pk) { auto stmt = SqlStatement { dm._connection }; stmt.Prepare(selectQuery.All()); - stmt.Execute(pk); - + auto cursor = stmt.Execute(pk); auto referencedRecord = ReferencedRecord {}; - dm.BindOutputColumns(referencedRecord, &stmt); + dm.BindOutputColumns(referencedRecord, cursor); dm.ConfigureRelationAutoLoading(referencedRecord); - while (stmt.FetchRow()) + while (cursor.FetchRow()) { each(referencedRecord); - dm.BindOutputColumns(referencedRecord, &stmt); + dm.BindOutputColumns(referencedRecord, cursor); } }); }, diff --git a/src/Lightweight/SqlBackup/Backup.cpp b/src/Lightweight/SqlBackup/Backup.cpp index 65b81737..14844069 100644 --- a/src/Lightweight/SqlBackup/Backup.cpp +++ b/src/Lightweight/SqlBackup/Backup.cpp @@ -283,9 +283,9 @@ void ProcessTableBackup(BackupContext& ctx, SqlConnection& conn, SqlSchema::Tabl // Build query with current offset for resumption auto const selectQuery = BuildSelectQueryWithOffset( formatter, conn.ServerType(), table.schema, tableName, table.columns, table.primaryKeys, processedRows); - stmt.ExecuteDirect(selectQuery); + auto cursor = stmt.ExecuteDirect(selectQuery); - while (stmt.FetchRow()) + while (cursor.FetchRow()) { if constexpr (DebugBackupWorker) std::println(stderr, "DEBUG: FetchRow returned true for {}", tableName); @@ -305,7 +305,7 @@ void ProcessTableBackup(BackupContext& ctx, SqlConnection& conn, SqlSchema::Tabl { try { - auto valOpt = stmt.GetNullableColumn>(i); + auto valOpt = cursor.GetNullableColumn>(i); if (valOpt) { row.emplace_back(std::vector(valOpt->data(), valOpt->data() + valOpt->size())); @@ -325,7 +325,7 @@ void ProcessTableBackup(BackupContext& ctx, SqlConnection& conn, SqlSchema::Tabl } else if (std::holds_alternative(colDef.type)) { - auto valOpt = stmt.GetNullableColumn(i); + auto valOpt = cursor.GetNullableColumn(i); if (valOpt) row.emplace_back(*valOpt); else @@ -335,7 +335,7 @@ void ProcessTableBackup(BackupContext& ctx, SqlConnection& conn, SqlSchema::Tabl || std::holds_alternative(colDef.type) || std::holds_alternative(colDef.type)) { - auto valOpt = stmt.GetNullableColumn(i); + auto valOpt = cursor.GetNullableColumn(i); if (valOpt) row.emplace_back(*valOpt); else @@ -343,7 +343,7 @@ void ProcessTableBackup(BackupContext& ctx, SqlConnection& conn, SqlSchema::Tabl } else if (std::holds_alternative(colDef.type)) { - auto valOpt = stmt.GetNullableColumn(i); + auto valOpt = cursor.GetNullableColumn(i); if (valOpt) row.emplace_back(*valOpt); else @@ -351,7 +351,7 @@ void ProcessTableBackup(BackupContext& ctx, SqlConnection& conn, SqlSchema::Tabl } else if (std::holds_alternative(colDef.type) || std::holds_alternative(colDef.type)) { - auto valOpt = stmt.GetNullableColumn(i); + auto valOpt = cursor.GetNullableColumn(i); if (valOpt) { auto u8 = ToUtf8(*valOpt); @@ -363,7 +363,7 @@ void ProcessTableBackup(BackupContext& ctx, SqlConnection& conn, SqlSchema::Tabl else if (std::holds_alternative(colDef.type)) { // Read GUID columns using SqlGuid for proper ODBC binding, then convert to string - auto valOpt = stmt.GetNullableColumn(i); + auto valOpt = cursor.GetNullableColumn(i); if (valOpt) row.emplace_back(to_string(*valOpt)); else @@ -374,7 +374,7 @@ void ProcessTableBackup(BackupContext& ctx, SqlConnection& conn, SqlSchema::Tabl // Read Decimal as string directly to preserve full precision. // Using double would lose precision for values exceeding ~15-17 significant digits, // which is problematic for DECIMAL(38,10) and similar high-precision types. - auto valOpt = stmt.GetNullableColumn(i); + auto valOpt = cursor.GetNullableColumn(i); if (valOpt) row.emplace_back(*valOpt); else @@ -383,7 +383,7 @@ void ProcessTableBackup(BackupContext& ctx, SqlConnection& conn, SqlSchema::Tabl else if (std::holds_alternative(colDef.type)) { // Read Date using native type to avoid ODBC driver conversion issues. - auto valOpt = stmt.GetNullableColumn(i); + auto valOpt = cursor.GetNullableColumn(i); if (valOpt) row.emplace_back(std::format("{}", *valOpt)); else @@ -398,7 +398,7 @@ void ProcessTableBackup(BackupContext& ctx, SqlConnection& conn, SqlSchema::Tabl if (conn.ServerType() == SqlServerType::POSTGRESQL || conn.ServerType() == SqlServerType::MICROSOFT_SQL) { - auto valOpt = stmt.GetNullableColumn(i); + auto valOpt = cursor.GetNullableColumn(i); if (valOpt) row.emplace_back(*valOpt); else @@ -407,7 +407,7 @@ void ProcessTableBackup(BackupContext& ctx, SqlConnection& conn, SqlSchema::Tabl else { // Read Time using native type for other databases (e.g., SQLite). - auto valOpt = stmt.GetNullableColumn(i); + auto valOpt = cursor.GetNullableColumn(i); if (valOpt) row.emplace_back(std::format("{}", *valOpt)); else @@ -419,7 +419,7 @@ void ProcessTableBackup(BackupContext& ctx, SqlConnection& conn, SqlSchema::Tabl { // Read DateTime/Timestamp using native type to avoid MS SQL Server ODBC driver // issues with SQL_TYPE_TIMESTAMP to SQL_C_CHAR conversion (error 22003). - auto valOpt = stmt.GetNullableColumn(i); + auto valOpt = cursor.GetNullableColumn(i); if (valOpt) row.emplace_back(std::format("{}", *valOpt)); else @@ -431,7 +431,7 @@ void ProcessTableBackup(BackupContext& ctx, SqlConnection& conn, SqlSchema::Tabl // Read text/string types as strings directly. // Note: We must not use SqlDynamicBinary for text types on PostgreSQL as its ODBC driver // does not support reading TEXT as SQL_C_BINARY. - auto valOpt = stmt.GetNullableColumn(i); + auto valOpt = cursor.GetNullableColumn(i); if (valOpt) row.emplace_back(*valOpt); else @@ -440,7 +440,7 @@ void ProcessTableBackup(BackupContext& ctx, SqlConnection& conn, SqlSchema::Tabl else { // Fallback for any unhandled types - try reading as string - auto valOpt = stmt.GetNullableColumn(i); + auto valOpt = cursor.GetNullableColumn(i); if (valOpt) row.emplace_back(*valOpt); else diff --git a/src/Lightweight/SqlBackup/Common.cpp b/src/Lightweight/SqlBackup/Common.cpp index d0e8fccb..5f1051b3 100644 --- a/src/Lightweight/SqlBackup/Common.cpp +++ b/src/Lightweight/SqlBackup/Common.cpp @@ -116,7 +116,7 @@ bool DropTableIfExists(SqlConnection& conn, // PostgreSQL which uses CASCADE syntax, and SQLite which ignores cascade. auto dropSqls = formatter.DropTable(schema, tableName, /*ifExists=*/true, /*cascade=*/true); for (auto const& sql: dropSqls) - SqlStatement { conn }.ExecuteDirect(sql); + (void) SqlStatement { conn }.ExecuteDirect(sql); return true; } catch (std::exception const& e) diff --git a/src/Lightweight/SqlBackup/Restore.cpp b/src/Lightweight/SqlBackup/Restore.cpp index caa78b7f..4ee1bd86 100644 --- a/src/Lightweight/SqlBackup/Restore.cpp +++ b/src/Lightweight/SqlBackup/Restore.cpp @@ -152,7 +152,7 @@ bool RestoreChunkData(RestoreContext& ctx, SqlConnection& workerConn, RestoreChu if (hasIdentity) { identityTable = FormatTableName(ctx.schema, tableName); - SqlStatement { workerConn }.ExecuteDirect(std::format("SET IDENTITY_INSERT {} ON", identityTable)); + (void) SqlStatement { workerConn }.ExecuteDirect(std::format("SET IDENTITY_INSERT {} ON", identityTable)); } } @@ -166,7 +166,7 @@ bool RestoreChunkData(RestoreContext& ctx, SqlConnection& workerConn, RestoreChu stmt.Prepare(formatter.Insert(ctx.schema, tableName, fields, placeholders)); ::Lightweight::detail::BatchManager batchManager( - [&](std::vector const& cols, size_t rows) { stmt.ExecuteBatch(cols, rows); }, + [&](std::vector const& cols, size_t rows) { (void) stmt.ExecuteBatch(cols, rows); }, tableInfo.columns, batchCapacity, workerConn.ServerType()); @@ -221,14 +221,14 @@ bool RestoreChunkData(RestoreContext& ctx, SqlConnection& workerConn, RestoreChu batchManager.Flush(); transaction.Commit(); - stmt.CloseCursor(); + // Cursor cleanup is handled by RAII } if (hasIdentity) { try { - SqlStatement { workerConn }.ExecuteDirect(std::format("SET IDENTITY_INSERT {} OFF", identityTable)); + (void) SqlStatement { workerConn }.ExecuteDirect(std::format("SET IDENTITY_INSERT {} OFF", identityTable)); } catch (...) // NOLINT(bugprone-empty-catch) { @@ -277,12 +277,12 @@ bool RestoreChunkData(RestoreContext& ctx, SqlConnection& workerConn, RestoreChu // Re-apply SQLite optimizations after reconnect if (workerConn.ServerType() == SqlServerType::SQLITE) { - SqlStatement { workerConn }.ExecuteDirect("PRAGMA synchronous = OFF"); - SqlStatement { workerConn }.ExecuteDirect("PRAGMA journal_mode = WAL"); - SqlStatement { workerConn }.ExecuteDirect("PRAGMA foreign_keys = OFF"); + (void) SqlStatement { workerConn }.ExecuteDirect("PRAGMA synchronous = OFF"); + (void) SqlStatement { workerConn }.ExecuteDirect("PRAGMA journal_mode = WAL"); + (void) SqlStatement { workerConn }.ExecuteDirect("PRAGMA foreign_keys = OFF"); if (ctx.restoreSettings.cacheSizeKB > 0) { - SqlStatement { workerConn }.ExecuteDirect( + (void) SqlStatement { workerConn }.ExecuteDirect( std::format("PRAGMA cache_size = -{}", ctx.restoreSettings.cacheSizeKB)); } } @@ -311,12 +311,12 @@ void RestoreWorker(RestoreContext ctx, SqlConnection& workerConn) // SQLite optimization: Turn off synchronization for faster restore if (workerConn.ServerType() == SqlServerType::SQLITE) { - SqlStatement { workerConn }.ExecuteDirect("PRAGMA synchronous = OFF"); - SqlStatement { workerConn }.ExecuteDirect("PRAGMA journal_mode = WAL"); - SqlStatement { workerConn }.ExecuteDirect("PRAGMA foreign_keys = OFF"); + (void) SqlStatement { workerConn }.ExecuteDirect("PRAGMA synchronous = OFF"); + (void) SqlStatement { workerConn }.ExecuteDirect("PRAGMA journal_mode = WAL"); + (void) SqlStatement { workerConn }.ExecuteDirect("PRAGMA foreign_keys = OFF"); if (ctx.restoreSettings.cacheSizeKB > 0) { - SqlStatement { workerConn }.ExecuteDirect( + (void) SqlStatement { workerConn }.ExecuteDirect( std::format("PRAGMA cache_size = -{}", ctx.restoreSettings.cacheSizeKB)); } } @@ -414,7 +414,7 @@ void RestoreIndexes(SqlConnectionString const& connectionString, std::string const sql = std::format( R"(CREATE {}INDEX "{}" ON {} ({}))", uniqueKeyword, idx.name, formattedTableName, columnList); - SqlStatement { conn }.ExecuteDirect(sql); + (void) SqlStatement { conn }.ExecuteDirect(sql); progress.Update({ .state = Progress::State::InProgress, .tableName = tableName, @@ -475,7 +475,7 @@ void ApplyDatabaseConstraints(SqlConnectionString const& connectionString, { auto sqls = formatter.AlterTable(schema, tableName, commands); for (auto const& sql: sqls) - SqlStatement { conn }.ExecuteDirect(sql); + (void) SqlStatement { conn }.ExecuteDirect(sql); } catch (std::exception const& e) { @@ -597,7 +597,7 @@ std::set CreateTablesInOrder(SqlConnection& conn, std::string const formattedTableName = FormatTableName(schema, tableName); try { - SqlStatement { conn }.ExecuteDirect( + (void) SqlStatement { conn }.ExecuteDirect( std::format("ALTER TABLE {} DROP CONSTRAINT IF EXISTS \"{}\"", formattedTableName, fkName)); } catch (...) // NOLINT(bugprone-empty-catch) @@ -638,7 +638,7 @@ std::set CreateTablesInOrder(SqlConnection& conn, { try { - SqlStatement { conn }.ExecuteDirect(sql); + (void) SqlStatement { conn }.ExecuteDirect(sql); } catch (std::exception const& e) { @@ -690,9 +690,9 @@ std::set RecreateDatabaseSchema(SqlConnectionString const& connecti if (isSQLite) { SqlStatement stmt { conn }; - stmt.ExecuteDirect("PRAGMA synchronous = OFF"); - stmt.ExecuteDirect("PRAGMA journal_mode = WAL"); - stmt.ExecuteDirect("PRAGMA foreign_keys = OFF"); // Disable FKs during restore + (void) stmt.ExecuteDirect("PRAGMA synchronous = OFF"); + (void) stmt.ExecuteDirect("PRAGMA journal_mode = WAL"); + (void) stmt.ExecuteDirect("PRAGMA foreign_keys = OFF"); // Disable FKs during restore } auto const tableOrder = ComputeTableCreationOrder(tableMap, isSQLite); diff --git a/src/Lightweight/SqlConnection.cpp b/src/Lightweight/SqlConnection.cpp index 485ef510..b724e630 100644 --- a/src/Lightweight/SqlConnection.cpp +++ b/src/Lightweight/SqlConnection.cpp @@ -270,7 +270,7 @@ void SqlConnection::PostConnect() // Set a busy timeout to prevent "database is locked" errors during concurrent access. // 60 seconds should be sufficient for most operations. SqlStatement stmt(*this); - stmt.ExecuteDirect("PRAGMA busy_timeout = 60000"); + [[maybe_unused]] auto cursor = stmt.ExecuteDirect("PRAGMA busy_timeout = 60000"); // We could also enable WAL mode here, but that changes the database file structure. // However, for high-concurrency restoration, it is highly recommended. diff --git a/src/Lightweight/SqlMigration.cpp b/src/Lightweight/SqlMigration.cpp index ff76719a..d3402f24 100644 --- a/src/Lightweight/SqlMigration.cpp +++ b/src/Lightweight/SqlMigration.cpp @@ -145,7 +145,7 @@ void MigrationManager::ApplySingleMigration(MigrationBase const& migration) { try { - stmt.ExecuteDirect(sqlScript); + (void) stmt.ExecuteDirect(sqlScript); } catch (SqlException const& ex) { @@ -197,7 +197,7 @@ void MigrationManager::RevertSingleMigration(MigrationBase const& migration) { try { - stmt.ExecuteDirect(sqlScript); + (void) stmt.ExecuteDirect(sqlScript); } catch (SqlException const& ex) { diff --git a/src/Lightweight/SqlMigrationLock.cpp b/src/Lightweight/SqlMigrationLock.cpp index bab2507d..53ce3386 100644 --- a/src/Lightweight/SqlMigrationLock.cpp +++ b/src/Lightweight/SqlMigrationLock.cpp @@ -137,11 +137,11 @@ void MigrationLock::AcquireLockSqlServer(std::chrono::milliseconds timeout) _lockName, timeout.count()); - stmt.ExecuteDirect(sql); + auto cursor = stmt.ExecuteDirect(sql); - if (stmt.FetchRow()) + if (cursor.FetchRow()) { - auto const result = stmt.GetColumn(1); + auto const result = cursor.GetColumn(1); if (result >= 0) { _locked = true; @@ -163,7 +163,7 @@ void MigrationLock::ReleaseLockSqlServer() { auto stmt = SqlStatement { *_connection }; auto const sql = std::format("EXEC sp_releaseapplock @Resource = N'{}', @LockOwner = N'Session';", _lockName); - stmt.ExecuteDirect(sql); + [[maybe_unused]] auto releaseCursor = stmt.ExecuteDirect(sql); } // PostgreSQL implementation using pg_advisory_lock @@ -172,7 +172,7 @@ void MigrationLock::AcquireLockPostgreSQL(std::chrono::milliseconds timeout) auto stmt = SqlStatement { *_connection }; // Set lock timeout - stmt.ExecuteDirect(std::format("SET lock_timeout = '{} ms';", timeout.count())); + [[maybe_unused]] auto timeoutCursor = stmt.ExecuteDirect(std::format("SET lock_timeout = '{} ms';", timeout.count())); // Use pg_try_advisory_lock with a hash of the lock name // pg_advisory_lock uses a 64-bit key, we hash the lock name @@ -180,7 +180,7 @@ void MigrationLock::AcquireLockPostgreSQL(std::chrono::milliseconds timeout) try { - stmt.ExecuteDirect(sql); + [[maybe_unused]] auto lockCursor = stmt.ExecuteDirect(sql); _locked = true; } catch (SqlException const&) @@ -194,7 +194,7 @@ void MigrationLock::ReleaseLockPostgreSQL() { auto stmt = SqlStatement { *_connection }; auto const sql = std::format("SELECT pg_advisory_unlock(hashtext('{}')::bigint);", _lockName); - stmt.ExecuteDirect(sql); + [[maybe_unused]] auto unlockCursor = stmt.ExecuteDirect(sql); } // SQLite implementation using a lock table @@ -205,10 +205,10 @@ void MigrationLock::AcquireLockSQLite(std::chrono::milliseconds timeout) auto stmt = SqlStatement { *_connection }; // Set busy timeout for waiting on locks - stmt.ExecuteDirect(std::format("PRAGMA busy_timeout = {};", timeout.count())); + [[maybe_unused]] auto busyCursor = stmt.ExecuteDirect(std::format("PRAGMA busy_timeout = {};", timeout.count())); // Create lock table if it doesn't exist - stmt.ExecuteDirect("CREATE TABLE IF NOT EXISTS \"_migration_locks\" (" + [[maybe_unused]] auto createCursor = stmt.ExecuteDirect("CREATE TABLE IF NOT EXISTS \"_migration_locks\" (" "\"lock_name\" VARCHAR(255) PRIMARY KEY, " "\"acquired_at\" TEXT DEFAULT CURRENT_TIMESTAMP" ");"); @@ -216,7 +216,7 @@ void MigrationLock::AcquireLockSQLite(std::chrono::milliseconds timeout) // Try to insert a lock record - this will fail if already locked try { - stmt.ExecuteDirect(std::format(R"(INSERT INTO "_migration_locks" ("lock_name") VALUES ('{}');)", _lockName)); + [[maybe_unused]] auto insertCursor = stmt.ExecuteDirect(std::format(R"(INSERT INTO "_migration_locks" ("lock_name") VALUES ('{}');)", _lockName)); _locked = true; } catch (SqlException const&) @@ -232,7 +232,7 @@ void MigrationLock::ReleaseLockSQLite() auto stmt = SqlStatement { *_connection }; try { - stmt.ExecuteDirect(std::format(R"(DELETE FROM "_migration_locks" WHERE "lock_name" = '{}';)", _lockName)); + [[maybe_unused]] auto deleteCursor = stmt.ExecuteDirect(std::format(R"(DELETE FROM "_migration_locks" WHERE "lock_name" = '{}';)", _lockName)); } // NOLINTNEXTLINE(bugprone-empty-catch) catch (...) diff --git a/src/Lightweight/SqlSchema.cpp b/src/Lightweight/SqlSchema.cpp index 21a61ec4..9c1f2a91 100644 --- a/src/Lightweight/SqlSchema.cpp +++ b/src/Lightweight/SqlSchema.cpp @@ -59,11 +59,12 @@ namespace SqlErrorInfo::RequireStatementSuccess(sqlResult, stmt.NativeHandle(), "SQLTables"); auto result = std::vector(); - while (stmt.FetchRow()) + auto cursor = SqlResultCursor(stmt); + while (cursor.FetchRow()) { - auto schemaOpt = stmt.GetNullableColumn(2); - auto nameOpt = stmt.GetNullableColumn(3); - auto typeOpt = stmt.GetNullableColumn(4); + auto schemaOpt = cursor.GetNullableColumn(2); + auto nameOpt = cursor.GetNullableColumn(3); + auto typeOpt = cursor.GetNullableColumn(4); if (!nameOpt) continue; @@ -111,28 +112,29 @@ namespace // ODBC SQLForeignKeys() should return at least 14 columns per the spec. // However, some drivers (e.g., MS SQL ODBC Driver 18 in certain environments) may // return fewer columns. We need at least 9 columns (KEY_SEQ) to process foreign keys. + auto cursor = SqlResultCursor(stmt); constexpr size_t MinRequiredColumns = 9; - auto const numColumns = stmt.NumColumnsAffected(); + auto const numColumns = cursor.NumColumnsAffected(); if (numColumns < MinRequiredColumns) return {}; // Driver didn't return expected columns, return empty result using ColumnList = std::vector>; auto constraints = std::map(); - while (stmt.FetchRow()) + while (cursor.FetchRow()) { auto primaryKeyTable = FullyQualifiedTableName { - .catalog = stmt.GetNullableColumn(1).value_or(""), - .schema = stmt.GetNullableColumn(2).value_or(""), - .table = stmt.GetNullableColumn(3).value_or(""), + .catalog = cursor.GetNullableColumn(1).value_or(""), + .schema = cursor.GetNullableColumn(2).value_or(""), + .table = cursor.GetNullableColumn(3).value_or(""), }; - auto pkColumnName = stmt.GetNullableColumn(4).value_or(""); + auto pkColumnName = cursor.GetNullableColumn(4).value_or(""); auto foreignKeyTable = FullyQualifiedTableName { - .catalog = stmt.GetNullableColumn(5).value_or(""), - .schema = stmt.GetNullableColumn(6).value_or(""), - .table = stmt.GetNullableColumn(7).value_or(""), + .catalog = cursor.GetNullableColumn(5).value_or(""), + .schema = cursor.GetNullableColumn(6).value_or(""), + .table = cursor.GetNullableColumn(7).value_or(""), }; - auto foreignKeyColumn = stmt.GetNullableColumn(8).value_or(""); - auto const sequenceNumber = static_cast(stmt.GetNullableColumn(9).value_or(1)); + auto foreignKeyColumn = cursor.GetNullableColumn(8).value_or(""); + auto const sequenceNumber = static_cast(cursor.GetNullableColumn(9).value_or(1)); ColumnList& keyColumns = constraints[{ foreignKeyTable, primaryKeyTable }]; if (sequenceNumber > keyColumns.size()) keyColumns.resize(sequenceNumber); @@ -171,14 +173,14 @@ namespace else query = std::format("PRAGMA table_info(\"{}\")", table.table); - stmt.ExecuteDirect(query); + auto cursor = stmt.ExecuteDirect(query); std::vector> pkCols; - while (stmt.FetchRow()) + while (cursor.FetchRow()) { // cid, name, type, notnull, dflt_value, pk - auto name = stmt.GetColumn(2); - auto pkInfo = stmt.GetColumn(6); + auto name = cursor.GetColumn(2); + auto pkInfo = cursor.GetColumn(6); if (pkInfo > 0) pkCols.emplace_back(pkInfo, name); } @@ -205,10 +207,11 @@ namespace if (!SQL_SUCCEEDED(sqlResult)) throw std::runtime_error(std::format("SQLPrimaryKeys failed: {}", stmt.LastError())); - while (stmt.FetchRow()) + auto cursor = SqlResultCursor(stmt); + while (cursor.FetchRow()) { - keys.emplace_back(stmt.GetNullableColumn(4).value_or("")); - sequenceNumbers.emplace_back(stmt.GetNullableColumn(5).value_or(0)); + keys.emplace_back(cursor.GetNullableColumn(4).value_or("")); + sequenceNumbers.emplace_back(cursor.GetNullableColumn(5).value_or(0)); } std::vector sortedKeys; @@ -247,19 +250,20 @@ namespace // ODBC SQLStatistics() should return at least 13 columns per the spec. // However, some drivers (e.g., MS SQL ODBC Driver 18 in certain environments) may // return fewer columns. We need at least 9 columns (COLUMN_NAME) to process unique columns. + auto cursor = SqlResultCursor(stmt); constexpr size_t MinRequiredColumns = 9; - auto const numColumns = stmt.NumColumnsAffected(); + auto const numColumns = cursor.NumColumnsAffected(); if (numColumns < MinRequiredColumns) return {}; // Driver didn't return expected columns, return empty result std::map> uniqueIndices; - while (stmt.FetchRow()) + while (cursor.FetchRow()) { // Col 6: INDEX_NAME // Col 9: COLUMN_NAME - auto indexName = stmt.GetNullableColumn(6).value_or(""); - auto columnName = stmt.GetNullableColumn(9).value_or(""); + auto indexName = cursor.GetNullableColumn(6).value_or(""); + auto columnName = cursor.GetNullableColumn(9).value_or(""); if (!indexName.empty() && !columnName.empty()) uniqueIndices[indexName].push_back(columnName); } @@ -309,8 +313,9 @@ namespace // ODBC SQLStatistics() should return at least 13 columns per the spec. // However, some drivers (e.g., MS SQL ODBC Driver 18 in certain environments) may // return fewer columns. We need at least 9 columns (COLUMN_NAME) to process indexes. + auto indexCursor = SqlResultCursor(indexStmt); constexpr size_t MinRequiredColumns = 9; - auto const numColumns = indexStmt.NumColumnsAffected(); + auto const numColumns = indexCursor.NumColumnsAffected(); if (numColumns < MinRequiredColumns) return {}; // Driver didn't return expected columns, return empty result @@ -322,7 +327,7 @@ namespace }; std::map indexMap; - while (indexStmt.FetchRow()) + while (indexCursor.FetchRow()) { // Column mappings from SQLStatistics result set: // 4: NON_UNIQUE (0 = unique, 1 = not unique, NULL for statistics rows) @@ -332,18 +337,18 @@ namespace // 9: COLUMN_NAME // Skip statistics rows (TYPE == 0) - auto typeOpt = indexStmt.GetNullableColumn(7); + auto typeOpt = indexCursor.GetNullableColumn(7); if (!typeOpt || *typeOpt == 0) continue; - auto indexNameOpt = indexStmt.GetNullableColumn(6); - auto columnNameOpt = indexStmt.GetNullableColumn(9); + auto indexNameOpt = indexCursor.GetNullableColumn(6); + auto columnNameOpt = indexCursor.GetNullableColumn(9); if (!indexNameOpt || indexNameOpt->empty() || !columnNameOpt || columnNameOpt->empty()) continue; - auto ordinalOpt = indexStmt.GetNullableColumn(8); - auto nonUniqueOpt = indexStmt.GetNullableColumn(4); + auto ordinalOpt = indexCursor.GetNullableColumn(8); + auto nonUniqueOpt = indexCursor.GetNullableColumn(4); IndexInfo& info = indexMap[*indexNameOpt]; info.columnsWithOrdinal.emplace_back(ordinalOpt.value_or(1), *columnNameOpt); @@ -422,11 +427,11 @@ namespace // However, we should use a fresh statement to be safe if stmt is active? // ReadAllTables loop reuses stmt for PrimaryKeys etc. It should be fine. // Actually AllUniqueColumns etc use stmt. - stmt.ExecuteDirect(sql); + auto identityCursor = stmt.ExecuteDirect(sql); std::vector identityCols; - while (stmt.FetchRow()) + while (identityCursor.FetchRow()) { - identityCols.push_back(stmt.GetColumn(1)); + identityCols.push_back(identityCursor.GetColumn(1)); } return identityCols; } @@ -505,26 +510,27 @@ void ReadAllTables(SqlStatement& stmt, std::string_view database, std::string_vi // ODBC SQLColumns() should return 18 columns per the spec. // However, some drivers may return fewer columns. Track the actual column count // to avoid accessing non-existent columns which causes ODBC errors. - auto const numColumns = columnStmt.NumColumnsAffected(); + auto columnCursor = SqlResultCursor(columnStmt); + auto const numColumns = columnCursor.NumColumnsAffected(); Column column; - while (columnStmt.FetchRow()) + while (columnCursor.FetchRow()) { // std::cerr << "DEBUG: FetchRow success for " << tableName << "\n"; int type = 0; try { - column.name = columnStmt.GetNullableColumn(4).value_or(""); - type = columnStmt.GetColumn(5); // DATA_TYPE - column.dialectDependantTypeString = columnStmt.GetNullableColumn(6).value_or(""); + column.name = columnCursor.GetNullableColumn(4).value_or(""); + type = columnCursor.GetColumn(5); // DATA_TYPE + column.dialectDependantTypeString = columnCursor.GetNullableColumn(6).value_or(""); // COLUMN_SIZE (column 7) can be negative for some drivers (e.g., PostgreSQL returns -4 for BYTEA) // to indicate "unknown" size. Treat negative values as 0. - auto const rawSize = columnStmt.GetColumn(7); + auto const rawSize = columnCursor.GetColumn(7); column.size = rawSize > 0 ? static_cast(rawSize) : 0; // 8 - bufferLength - column.decimalDigits = numColumns >= 9 ? columnStmt.GetNullableColumn(9).value_or(0) : 0; + column.decimalDigits = numColumns >= 9 ? columnCursor.GetNullableColumn(9).value_or(0) : 0; } catch (std::exception const&) { @@ -538,7 +544,7 @@ void ReadAllTables(SqlStatement& stmt, std::string_view database, std::string_vi { try { - column.isNullable = columnStmt.GetColumn(11); + column.isNullable = columnCursor.GetColumn(11); } catch (std::exception&) { @@ -556,7 +562,7 @@ void ReadAllTables(SqlStatement& stmt, std::string_view database, std::string_vi { try { - column.defaultValue = columnStmt.GetNullableColumn(13).value_or(""); + column.defaultValue = columnCursor.GetNullableColumn(13).value_or(""); } catch (std::exception&) { diff --git a/src/Lightweight/SqlStatement.cpp b/src/Lightweight/SqlStatement.cpp index 0974a66b..5ab46f43 100644 --- a/src/Lightweight/SqlStatement.cpp +++ b/src/Lightweight/SqlStatement.cpp @@ -189,10 +189,10 @@ void SqlStatement::Prepare(std::string_view query) & m_data->indicators.resize(static_cast(m_expectedParameterCount) + 1); } -void SqlStatement::ExecuteDirect(std::string_view const& query, std::source_location location) +SqlResultCursor SqlStatement::ExecuteDirect(std::string_view const& query, std::source_location location) { if (query.empty()) - return; + return SqlResultCursor { *this }; m_preparedQuery.clear(); m_numColumns.reset(); @@ -205,9 +205,10 @@ void SqlStatement::ExecuteDirect(std::string_view const& query, std::source_loca SqlLogger::GetLogger().OnExecuteDirect(query); RequireSuccess(SQLExecDirectA(m_hStmt, (SQLCHAR*) query.data(), (SQLINTEGER) query.size()), location); + return SqlResultCursor { *this }; } -void SqlStatement::ExecuteWithVariants(std::vector const& args) +SqlResultCursor SqlStatement::ExecuteWithVariants(std::vector const& args) { SqlLogger::GetLogger().OnExecute(m_preparedQuery); @@ -231,9 +232,10 @@ void SqlStatement::ExecuteWithVariants(std::vector const& args) if (rc != SQL_NO_DATA) RequireSuccess(rc); ProcessPostExecuteCallbacks(); + return SqlResultCursor { *this }; } -void SqlStatement::ExecuteBatch(std::span columns, size_t rowCount) +SqlResultCursor SqlStatement::ExecuteBatch(std::span columns, size_t rowCount) { SqlLogger::GetLogger().OnExecute(m_preparedQuery); @@ -258,6 +260,7 @@ void SqlStatement::ExecuteBatch(std::span columns, size_t ro RequireSuccess(SQLExecute(m_hStmt)); ProcessPostExecuteCallbacks(); ClearBatchIndicators(); + return SqlResultCursor { *this }; } // Retrieves the number of rows affected by the last query. diff --git a/src/Lightweight/SqlStatement.hpp b/src/Lightweight/SqlStatement.hpp index 24c37528..9f74739d 100644 --- a/src/Lightweight/SqlStatement.hpp +++ b/src/Lightweight/SqlStatement.hpp @@ -125,32 +125,12 @@ class [[nodiscard]] SqlStatement final: public SqlDataBinderCallback template void BindInputParameter(SQLSMALLINT columnIndex, Arg const& arg, ColumnName&& columnNameHint); - /// Binds the given arguments to the prepared statement to store the fetched data to. - /// - /// @note The statement must be prepared before calling this function. - template - void BindOutputColumns(Args*... args); - - /// Binds the given arguments to the prepared statement to store the fetched data to. - /// - /// @param records The records to bind each member to. - /// - /// @note The statement must be prepared before calling this function. - /// @note The records must be aggregate types, i.e. classes with public members and no user-defined constructors. - template - requires(((std::is_class_v && std::is_aggregate_v) && ...)) - void BindOutputColumnsToRecord(Records*... records); - - /// Binds a single output column at the given index to store fetched data. - template - void BindOutputColumn(SQLUSMALLINT columnIndex, T* arg); - /// Binds the given arguments to the prepared statement and executes it. template - void Execute(Args const&... args); + [[nodiscard]] SqlResultCursor Execute(Args const&... args); /// Binds the given arguments to the prepared statement and executes it. - LIGHTWEIGHT_API void ExecuteWithVariants(std::vector const& args); + [[nodiscard]] LIGHTWEIGHT_API SqlResultCursor ExecuteWithVariants(std::vector const& args); /// Executes the prepared statement on a batch of data. /// @@ -161,7 +141,8 @@ class [[nodiscard]] SqlStatement final: public SqlDataBinderCallback /// Also the input range itself must be contiguous. /// If any of these conditions are not met, the function will not compile - use ExecuteBatch() instead. template - void ExecuteBatchNative(FirstColumnBatch const& firstColumnBatch, MoreColumnBatches const&... moreColumnBatches); + [[nodiscard]] SqlResultCursor ExecuteBatchNative(FirstColumnBatch const& firstColumnBatch, + MoreColumnBatches const&... moreColumnBatches); /// Executes the prepared statement on a batch of data. /// @@ -172,7 +153,8 @@ class [[nodiscard]] SqlStatement final: public SqlDataBinderCallback /// This function will bind and execute each row separately, /// which is less efficient than ExecuteBatchNative(), but works non-contiguous input ranges. template - void ExecuteBatchSoft(FirstColumnBatch const& firstColumnBatch, MoreColumnBatches const&... moreColumnBatches); + [[nodiscard]] SqlResultCursor ExecuteBatchSoft(FirstColumnBatch const& firstColumnBatch, + MoreColumnBatches const&... moreColumnBatches); /// Executes the prepared statement on a batch of data. /// @@ -180,20 +162,22 @@ class [[nodiscard]] SqlStatement final: public SqlDataBinderCallback /// and the number of elements in these bound column containers will /// mandate how many executions will happen. template - void ExecuteBatch(FirstColumnBatch const& firstColumnBatch, MoreColumnBatches const&... moreColumnBatches); + [[nodiscard]] SqlResultCursor ExecuteBatch(FirstColumnBatch const& firstColumnBatch, + MoreColumnBatches const&... moreColumnBatches); /// Executes the prepared statement on a batch of SqlRawColumn-prepared data. /// /// @param columns The columns to bind as input parameters. /// @param rowCount The number of rows to execute. - LIGHTWEIGHT_API void ExecuteBatch(std::span columns, size_t rowCount); + [[nodiscard]] LIGHTWEIGHT_API SqlResultCursor ExecuteBatch(std::span columns, size_t rowCount); /// Executes the given query directly. - LIGHTWEIGHT_API void ExecuteDirect(std::string_view const& query, - std::source_location location = std::source_location::current()); + [[nodiscard]] LIGHTWEIGHT_API SqlResultCursor + ExecuteDirect(std::string_view const& query, std::source_location location = std::source_location::current()); /// Executes the given query directly. - void ExecuteDirect(SqlQueryObject auto const& query, std::source_location location = std::source_location::current()); + [[nodiscard]] SqlResultCursor ExecuteDirect(SqlQueryObject auto const& query, + std::source_location location = std::source_location::current()); /// Executes an SQL migration query, as created b the callback. template @@ -226,61 +210,41 @@ class [[nodiscard]] SqlStatement final: public SqlDataBinderCallback [[nodiscard]] T ExecuteDirectScalar(SqlQueryObject auto const& query, std::source_location location = std::source_location::current()); - /// Retrieves the number of rows affected by the last query. - [[nodiscard]] LIGHTWEIGHT_API size_t NumRowsAffected() const; - - /// Retrieves the number of columns affected by the last query. - [[nodiscard]] LIGHTWEIGHT_API size_t NumColumnsAffected() const; - /// Retrieves the last insert ID of the given table. [[nodiscard]] LIGHTWEIGHT_API size_t LastInsertId(std::string_view tableName); - /// Fetches the next row of the result set. - /// - /// @note Automatically closes the cursor at the end of the result set. - /// - /// @retval true The next result row was successfully fetched - /// @retval false No result row was fetched, because the end of the result set was reached. - [[nodiscard]] LIGHTWEIGHT_API bool FetchRow(); + private: + friend class SqlResultCursor; - /// Attempts to fetch the next row, returning an error info on failure instead of throwing. + [[nodiscard]] LIGHTWEIGHT_API size_t NumRowsAffected() const; + [[nodiscard]] LIGHTWEIGHT_API size_t NumColumnsAffected() const; + [[nodiscard]] LIGHTWEIGHT_API bool FetchRow(); [[nodiscard]] LIGHTWEIGHT_API std::expected TryFetchRow( std::source_location location = std::source_location::current()) noexcept; - - /// Closes the result cursor on queries that yield a result set, e.g. SELECT statements. - /// - /// Call this function when done with fetching the results before the end of the result set is reached. void CloseCursor() noexcept; - /// Retrieves the result cursor for reading an SQL query result. - SqlResultCursor GetResultCursor() noexcept; + template + void BindOutputColumns(Args*... args); + + template + requires(((std::is_class_v && std::is_aggregate_v) && ...)) + void BindOutputColumnsToRecord(Records*... records); - /// Retrieves the variant row cursor for reading an SQL query result of unknown column types and column count. - SqlVariantRowCursor GetVariantRowCursor() noexcept; + template + void BindOutputColumn(SQLUSMALLINT columnIndex, T* arg); - /// Retrieves the value of the column at the given index for the currently selected row. - /// - /// Returns true if the value is not NULL, false otherwise. template [[nodiscard]] bool GetColumn(SQLUSMALLINT column, T* result) const; - /// Retrieves the value of the column at the given index for the currently selected row. template [[nodiscard]] T GetColumn(SQLUSMALLINT column) const; - /// Retrieves the value of the column at the given index for the currently selected row. - /// - /// If the value is NULL, std::nullopt is returned. template [[nodiscard]] std::optional GetNullableColumn(SQLUSMALLINT column) const; - /// Retrieves the value of the column at the given index for the currently selected row. - /// - /// If the value is NULL, the given @p defaultValue is returned. template [[nodiscard]] T GetColumnOr(SQLUSMALLINT column, T&& defaultValue) const; - private: LIGHTWEIGHT_API void RequireSuccess(SQLRETURN error, std::source_location sourceLocation = std::source_location::current()) const; LIGHTWEIGHT_API void PlanPostExecuteCallback(std::function&& cb) override; @@ -380,6 +344,21 @@ class [[nodiscard]] SqlResultCursor return m_stmt->FetchRow(); } + /// Attempts to fetch the next row, returning an error info on failure instead of throwing. + [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE std::expected TryFetchRow( + std::source_location location = std::source_location::current()) noexcept + { + return m_stmt->TryFetchRow(location); + } + + /// Binds the given records to the prepared statement to store the fetched data to. + template + requires(((std::is_class_v && std::is_aggregate_v) && ...)) + LIGHTWEIGHT_FORCE_INLINE void BindOutputColumnsToRecord(Records*... records) + { + m_stmt->BindOutputColumnsToRecord(records...); + } + /// Retrieves the value of the column at the given index for the currently selected row. /// /// Returns true if the value is not NULL, false otherwise. @@ -536,14 +515,12 @@ class SqlRowIterator iterator& operator++() { - _is_end = !_stmt->FetchRow(); - if (!_is_end) - { - } - else + if (_cursor) { - _stmt->CloseCursor(); + _is_end = !_cursor->FetchRow(); + return *this; } + _is_end = true; return *this; } @@ -552,7 +529,7 @@ class SqlRowIterator auto res = T {}; Reflection::EnumerateMembers(res, [this](auto&& value) { - auto tmp = _stmt->GetColumn::ValueType>(I + 1); + auto tmp = _cursor->GetColumn::ValueType>(I + 1); value = tmp; }); @@ -565,12 +542,14 @@ class SqlRowIterator } constexpr iterator(std::default_sentinel_t /*sentinel*/) noexcept: - _is_end { true } + _is_end { true }, + _cursor { std::nullopt } { } explicit iterator(SqlConnection& conn): - _stmt { std::make_unique(conn) } + _stmt { std::make_unique(conn) }, + _cursor { std::nullopt } { } @@ -579,9 +558,15 @@ class SqlRowIterator return *_stmt; } + void SetCursor(SqlResultCursor cursor) noexcept + { + _cursor.emplace(std::move(cursor)); + } + private: bool _is_end = false; std::unique_ptr _stmt; + std::optional _cursor; }; /// Returns an iterator to the first row of the result set. @@ -590,7 +575,7 @@ class SqlRowIterator auto it = iterator { *_connection }; auto& stmt = it.Statement(); stmt.Prepare(it.Statement().Query(RecordTableName).Select().template Fields().All()); - stmt.Execute(); + it.SetCursor(stmt.Execute()); ++it; return it; } @@ -706,7 +691,7 @@ inline LIGHTWEIGHT_FORCE_INLINE void SqlStatement::BindInputParameter(SQLSMALLIN } template -void SqlStatement::Execute(Args const&... args) +SqlResultCursor SqlStatement::Execute(Args const&... args) { // Each input parameter must have an address, // such that we can call SQLBindParameter() without needing to copy it. @@ -731,6 +716,7 @@ void SqlStatement::Execute(Args const&... args) throw SqlException(SqlErrorInfo::FromStatementHandle(m_hStmt), std::source_location::current()); ProcessPostExecuteCallbacks(); + return SqlResultCursor { *this }; } // clang-format off @@ -763,8 +749,8 @@ concept SqlNativeBatchable = // clang-format on template -void SqlStatement::ExecuteBatchNative(FirstColumnBatch const& firstColumnBatch, - MoreColumnBatches const&... moreColumnBatches) +SqlResultCursor SqlStatement::ExecuteBatchNative(FirstColumnBatch const& firstColumnBatch, + MoreColumnBatches const&... moreColumnBatches) { static_assert(SqlNativeBatchable, "Must be a supported native contiguous element type."); @@ -794,23 +780,25 @@ void SqlStatement::ExecuteBatchNative(FirstColumnBatch const& firstColumnBatch, RequireSuccess(SQLExecute(m_hStmt)); ProcessPostExecuteCallbacks(); // clang-format on + return SqlResultCursor { *this }; } /// @copydoc SqlStatement::ExecuteBatch template -inline LIGHTWEIGHT_FORCE_INLINE void SqlStatement::ExecuteBatch(FirstColumnBatch const& firstColumnBatch, - MoreColumnBatches const&... moreColumnBatches) +inline LIGHTWEIGHT_FORCE_INLINE SqlResultCursor SqlStatement::ExecuteBatch(FirstColumnBatch const& firstColumnBatch, + MoreColumnBatches const&... moreColumnBatches) { // If the input ranges are contiguous and their element types are contiguous and supported as well, // we can use the native batch execution. if constexpr (SqlNativeBatchable) - ExecuteBatchNative(firstColumnBatch, moreColumnBatches...); + return ExecuteBatchNative(firstColumnBatch, moreColumnBatches...); else - ExecuteBatchSoft(firstColumnBatch, moreColumnBatches...); + return ExecuteBatchSoft(firstColumnBatch, moreColumnBatches...); } template -void SqlStatement::ExecuteBatchSoft(FirstColumnBatch const& firstColumnBatch, MoreColumnBatches const&... moreColumnBatches) +SqlResultCursor SqlStatement::ExecuteBatchSoft(FirstColumnBatch const& firstColumnBatch, + MoreColumnBatches const&... moreColumnBatches) { if (m_expectedParameterCount != 1 + sizeof...(moreColumnBatches)) throw std::invalid_argument { "Invalid number of columns" }; @@ -833,6 +821,7 @@ void SqlStatement::ExecuteBatchSoft(FirstColumnBatch const& firstColumnBatch, Mo std::ref( *std::ranges::next(std::ranges::begin(moreColumnBatches), static_cast(rowIndex)))...)); } + return SqlResultCursor { *this }; } template @@ -880,8 +869,8 @@ T SqlStatement::GetColumnOr(SQLUSMALLINT column, T&& defaultValue) const return GetNullableColumn(column).value_or(std::forward(defaultValue)); } -inline LIGHTWEIGHT_FORCE_INLINE void SqlStatement::ExecuteDirect(SqlQueryObject auto const& query, - std::source_location location) +inline LIGHTWEIGHT_FORCE_INLINE SqlResultCursor SqlStatement::ExecuteDirect(SqlQueryObject auto const& query, + std::source_location location) { return ExecuteDirect(query.ToSql(), location); } @@ -895,8 +884,7 @@ void SqlStatement::MigrateDirect(Callable const& callable, std::source_location auto const queries = migration.GetPlan().ToSql(); for (auto const& query: queries) { - ExecuteDirect(query, location); - CloseCursor(); + [[maybe_unused]] auto cursor = ExecuteDirect(query, location); } } @@ -904,8 +892,7 @@ template requires(!std::same_as) inline std::optional SqlStatement::ExecuteDirectScalar(std::string_view const& query, std::source_location location) { - auto const _ = detail::Finally([this] { CloseCursor(); }); - ExecuteDirect(query, location); + auto cursor = ExecuteDirect(query, location); RequireSuccess(FetchRow()); return GetNullableColumn(1); } @@ -914,8 +901,7 @@ template requires(std::same_as) inline T SqlStatement::ExecuteDirectScalar(std::string_view const& query, std::source_location location) { - auto const _ = detail::Finally([this] { CloseCursor(); }); - ExecuteDirect(query, location); + auto cursor = ExecuteDirect(query, location); RequireSuccess(FetchRow()); if (auto result = GetNullableColumn(1); result.has_value()) return *result; @@ -943,16 +929,6 @@ inline LIGHTWEIGHT_FORCE_INLINE void SqlStatement::CloseCursor() noexcept SqlLogger::GetLogger().OnFetchEnd(); } -inline LIGHTWEIGHT_FORCE_INLINE SqlResultCursor SqlStatement::GetResultCursor() noexcept -{ - return SqlResultCursor { *this }; -} - -inline LIGHTWEIGHT_FORCE_INLINE SqlVariantRowCursor SqlStatement::GetVariantRowCursor() noexcept -{ - return SqlVariantRowCursor { SqlResultCursor { *this } }; -} - // }}} } // namespace Lightweight diff --git a/src/tests/CoreTests.cpp b/src/tests/CoreTests.cpp index 367fd4e5..12797916 100644 --- a/src/tests/CoreTests.cpp +++ b/src/tests/CoreTests.cpp @@ -126,10 +126,10 @@ TEST_CASE_METHOD(SqlTestFixture, "SqlStatement: ctor std::nullopt") TEST_CASE_METHOD(SqlTestFixture, "select: get columns") { auto stmt = Lightweight::SqlStatement {}; - stmt.ExecuteDirect("SELECT 42"); - (void) stmt.FetchRow(); - REQUIRE(stmt.GetColumn(1) == 42); - (void) stmt.FetchRow(); + auto cursor = stmt.ExecuteDirect("SELECT 42"); + (void) cursor.FetchRow(); + REQUIRE(cursor.GetColumn(1) == 42); + (void) cursor.FetchRow(); } TEST_CASE_METHOD(SqlTestFixture, "move semantics", "[SqlConnection]") @@ -185,13 +185,13 @@ TEST_CASE_METHOD(SqlTestFixture, "move semantics", "[SqlStatement]") TEST_CASE_METHOD(SqlTestFixture, "select: get column (invalid index)") { auto stmt = Lightweight::SqlStatement {}; - stmt.ExecuteDirect("SELECT 42"); - (void) stmt.FetchRow(); + auto cursor = stmt.ExecuteDirect("SELECT 42"); + (void) cursor.FetchRow(); auto const _ = ScopedSqlNullLogger {}; // suppress the error message, we are testing for it - CHECK_THROWS_AS(stmt.GetColumn(2), std::invalid_argument); - (void) stmt.FetchRow(); + CHECK_THROWS_AS(cursor.GetColumn(2), std::invalid_argument); + (void) cursor.FetchRow(); } TEST_CASE_METHOD(SqlTestFixture, "execute bound parameters and select back: VARCHAR, INT") @@ -203,32 +203,32 @@ TEST_CASE_METHOD(SqlTestFixture, "execute bound parameters and select back: VARC stmt.Prepare(R"(INSERT INTO "Employees" ("FirstName", "LastName", "Salary") VALUES (?, ?, ?))"); REQUIRE(stmt.IsPrepared()); - stmt.Execute("Alice", "Smith", 50'000); - stmt.Execute("Bob", "Johnson", 60'000); - stmt.Execute("Charlie", "Brown", 70'000); + (void) stmt.Execute("Alice", "Smith", 50'000); + (void) stmt.Execute("Bob", "Johnson", 60'000); + (void) stmt.Execute("Charlie", "Brown", 70'000); - stmt.ExecuteDirect(R"(SELECT COUNT(*) FROM "Employees")"); + auto cursor = stmt.ExecuteDirect(R"(SELECT COUNT(*) FROM "Employees")"); REQUIRE(!stmt.IsPrepared()); - REQUIRE(stmt.NumColumnsAffected() == 1); - (void) stmt.FetchRow(); - REQUIRE(stmt.GetColumn(1) == 3); - REQUIRE(!stmt.FetchRow()); + REQUIRE(cursor.NumColumnsAffected() == 1); + (void) cursor.FetchRow(); + REQUIRE(cursor.GetColumn(1) == 3); + REQUIRE(!cursor.FetchRow()); stmt.Prepare(R"(SELECT "FirstName", "LastName", "Salary" FROM "Employees" WHERE "Salary" >= ?)"); - REQUIRE(stmt.NumColumnsAffected() == 3); - stmt.Execute(55'000); + auto cursor2 = stmt.Execute(55'000); + REQUIRE(cursor2.NumColumnsAffected() == 3); - (void) stmt.FetchRow(); - REQUIRE(stmt.GetColumn(1) == "Bob"); - REQUIRE(stmt.GetColumn(2) == "Johnson"); - REQUIRE(stmt.GetColumn(3) == 60'000); + (void) cursor2.FetchRow(); + REQUIRE(cursor2.GetColumn(1) == "Bob"); + REQUIRE(cursor2.GetColumn(2) == "Johnson"); + REQUIRE(cursor2.GetColumn(3) == 60'000); - (void) stmt.FetchRow(); - REQUIRE(stmt.GetColumn(1) == "Charlie"); - REQUIRE(stmt.GetColumn(2) == "Brown"); - REQUIRE(stmt.GetColumn(3) == 70'000); + (void) cursor2.FetchRow(); + REQUIRE(cursor2.GetColumn(1) == "Charlie"); + REQUIRE(cursor2.GetColumn(2) == "Brown"); + REQUIRE(cursor2.GetColumn(3) == 70'000); - REQUIRE(!stmt.FetchRow()); + REQUIRE(!cursor2.FetchRow()); } TEST_CASE_METHOD(SqlTestFixture, "transaction: auto-rollback") @@ -240,15 +240,15 @@ TEST_CASE_METHOD(SqlTestFixture, "transaction: auto-rollback") { auto transaction = Lightweight::SqlTransaction { stmt.Connection(), Lightweight::SqlTransactionMode::ROLLBACK }; stmt.Prepare(R"(INSERT INTO "Employees" ("FirstName", "LastName", "Salary") VALUES (?, ?, ?))"); - stmt.Execute("Alice", "Smith", 50'000); + (void) stmt.Execute("Alice", "Smith", 50'000); REQUIRE(stmt.Connection().TransactionActive()); } // transaction automatically rolled back REQUIRE(!stmt.Connection().TransactionActive()); - stmt.ExecuteDirect("SELECT COUNT(*) FROM \"Employees\""); - (void) stmt.FetchRow(); - REQUIRE(stmt.GetColumn(1) == 0); + auto cursor = stmt.ExecuteDirect("SELECT COUNT(*) FROM \"Employees\""); + (void) cursor.FetchRow(); + REQUIRE(cursor.GetColumn(1) == 0); } TEST_CASE_METHOD(SqlTestFixture, "transaction: auto-commit") @@ -260,15 +260,15 @@ TEST_CASE_METHOD(SqlTestFixture, "transaction: auto-commit") { auto transaction = Lightweight::SqlTransaction { stmt.Connection(), Lightweight::SqlTransactionMode::COMMIT }; stmt.Prepare(R"(INSERT INTO "Employees" ("FirstName", "LastName", "Salary") VALUES (?, ?, ?))"); - stmt.Execute("Alice", "Smith", 50'000); + (void) stmt.Execute("Alice", "Smith", 50'000); REQUIRE(stmt.Connection().TransactionActive()); } // transaction automatically committed REQUIRE(!stmt.Connection().TransactionActive()); - stmt.ExecuteDirect("SELECT COUNT(*) FROM \"Employees\""); - (void) stmt.FetchRow(); - REQUIRE(stmt.GetColumn(1) == 1); + auto cursor = stmt.ExecuteDirect("SELECT COUNT(*) FROM \"Employees\""); + (void) cursor.FetchRow(); + REQUIRE(cursor.GetColumn(1) == 1); } TEST_CASE_METHOD(SqlTestFixture, "execute binding output parameters (direct)") @@ -282,15 +282,15 @@ TEST_CASE_METHOD(SqlTestFixture, "execute binding output parameters (direct)") unsigned int salary {}; stmt.Prepare(R"(SELECT "FirstName", "LastName", "Salary" FROM "Employees" WHERE "Salary" = ?)"); - stmt.BindOutputColumns(&firstName, &lastName, &salary); - stmt.Execute(50'000); + auto cursor = stmt.Execute(50'000); + cursor.BindOutputColumns(&firstName, &lastName, &salary); - (void) stmt.FetchRow(); + (void) cursor.FetchRow(); CHECK(firstName == "Alice"); CHECK(lastName == "Smith"); CHECK(salary == 50'000); - REQUIRE(!stmt.FetchRow()); + REQUIRE(!cursor.FetchRow()); } TEST_CASE_METHOD(SqlTestFixture, "SqlStatement.ExecuteBatch", "[SqlStatement]") @@ -308,26 +308,26 @@ TEST_CASE_METHOD(SqlTestFixture, "SqlStatement.ExecuteBatch", "[SqlStatement]") unsigned const salaries[3] = { 50'000, 60'000, 70'000 }; // C-style array // clang-format on - stmt.ExecuteBatch(firstNames, lastNames, salaries); + (void) stmt.ExecuteBatch(firstNames, lastNames, salaries); - stmt.ExecuteDirect(R"(SELECT "FirstName", "LastName", "Salary" FROM "Employees" ORDER BY "Salary" DESC)"); + auto cursor = stmt.ExecuteDirect(R"(SELECT "FirstName", "LastName", "Salary" FROM "Employees" ORDER BY "Salary" DESC)"); - REQUIRE(stmt.FetchRow()); - REQUIRE(stmt.GetColumn(1) == "Charlie"); - REQUIRE(stmt.GetColumn(2) == "Brown"); - REQUIRE(stmt.GetColumn(3) == 70'000); + REQUIRE(cursor.FetchRow()); + REQUIRE(cursor.GetColumn(1) == "Charlie"); + REQUIRE(cursor.GetColumn(2) == "Brown"); + REQUIRE(cursor.GetColumn(3) == 70'000); - REQUIRE(stmt.FetchRow()); - REQUIRE(stmt.GetColumn(1) == "Bob"); - REQUIRE(stmt.GetColumn(2) == "Johnson"); - REQUIRE(stmt.GetColumn(3) == 60'000); + REQUIRE(cursor.FetchRow()); + REQUIRE(cursor.GetColumn(1) == "Bob"); + REQUIRE(cursor.GetColumn(2) == "Johnson"); + REQUIRE(cursor.GetColumn(3) == 60'000); - REQUIRE(stmt.FetchRow()); - REQUIRE(stmt.GetColumn(1) == "Alice"); - REQUIRE(stmt.GetColumn(2) == "Smith"); - REQUIRE(stmt.GetColumn(3) == 50'000); + REQUIRE(cursor.FetchRow()); + REQUIRE(cursor.GetColumn(1) == "Alice"); + REQUIRE(cursor.GetColumn(2) == "Smith"); + REQUIRE(cursor.GetColumn(3) == 50'000); - REQUIRE(!stmt.FetchRow()); + REQUIRE(!cursor.FetchRow()); } TEST_CASE_METHOD(SqlTestFixture, "SqlStatement.ExecuteBatchNative", "[SqlStatement]") @@ -348,26 +348,26 @@ TEST_CASE_METHOD(SqlTestFixture, "SqlStatement.ExecuteBatchNative", "[SqlStateme auto const second = std::vector { 1.3, 2.3, 3.3 }; unsigned const third[3] = { 50'000, 60'000, 70'000 }; - stmt.ExecuteBatchNative(first, second, third); + (void) stmt.ExecuteBatchNative(first, second, third); - stmt.ExecuteDirect(R"(SELECT "A", "B", "C" FROM "Test" ORDER BY "C" DESC)"); + auto cursor = stmt.ExecuteDirect(R"(SELECT "A", "B", "C" FROM "Test" ORDER BY "C" DESC)"); - REQUIRE(stmt.FetchRow()); - CHECK(stmt.GetColumn(1) == "!"); - CHECK_THAT(stmt.GetColumn(2), Catch::Matchers::WithinAbs(3.3, 0.000'001)); - CHECK(stmt.GetColumn(3) == 70'000); + REQUIRE(cursor.FetchRow()); + CHECK(cursor.GetColumn(1) == "!"); + CHECK_THAT(cursor.GetColumn(2), Catch::Matchers::WithinAbs(3.3, 0.000'001)); + CHECK(cursor.GetColumn(3) == 70'000); - REQUIRE(stmt.FetchRow()); - CHECK(stmt.GetColumn(1) == "World"); - CHECK_THAT(stmt.GetColumn(2), Catch::Matchers::WithinAbs(2.3, 0.000'001)); - CHECK(stmt.GetColumn(3) == 60'000); + REQUIRE(cursor.FetchRow()); + CHECK(cursor.GetColumn(1) == "World"); + CHECK_THAT(cursor.GetColumn(2), Catch::Matchers::WithinAbs(2.3, 0.000'001)); + CHECK(cursor.GetColumn(3) == 60'000); - REQUIRE(stmt.FetchRow()); - CHECK(stmt.GetColumn(1) == "Hello"); - CHECK_THAT(stmt.GetColumn(2), Catch::Matchers::WithinAbs(1.3, 0.000'001)); - CHECK(stmt.GetColumn(3) == 50'000); + REQUIRE(cursor.FetchRow()); + CHECK(cursor.GetColumn(1) == "Hello"); + CHECK_THAT(cursor.GetColumn(2), Catch::Matchers::WithinAbs(1.3, 0.000'001)); + CHECK(cursor.GetColumn(3) == 50'000); - REQUIRE(!stmt.FetchRow()); + REQUIRE(!cursor.FetchRow()); } TEST_CASE_METHOD(SqlTestFixture, "SqlConnection: manual connect", "[SqlConnection]") @@ -450,11 +450,9 @@ TEST_CASE_METHOD(SqlTestFixture, "SELECT * FROM Table", "[SqlStatement]") CreateEmployeesTable(stmt); FillEmployeesTable(stmt); - stmt.ExecuteDirect("SELECT * FROM \"Employees\""); + auto result = stmt.ExecuteDirect("SELECT * FROM \"Employees\""); - auto result = stmt.GetResultCursor(); - - REQUIRE(stmt.NumColumnsAffected() == 4); + REQUIRE(result.NumColumnsAffected() == 4); REQUIRE(result.FetchRow()); CHECK(result.GetColumn(1) == 1); @@ -486,10 +484,9 @@ TEST_CASE_METHOD(SqlTestFixture, "GetNullableColumn", "[SqlStatement]") .Column("Remarks2", Lightweight::SqlColumnTypeDefinitions::Varchar { 50 }); }); stmt.Prepare(R"(INSERT INTO "Test" ("Remarks1", "Remarks2") VALUES (?, ?))"); - stmt.Execute("Blurb", Lightweight::SqlNullValue); + (void) stmt.Execute("Blurb", Lightweight::SqlNullValue); - stmt.ExecuteDirect(R"(SELECT "Remarks1", "Remarks2" FROM "Test")"); - auto result = stmt.GetResultCursor(); + auto result = stmt.ExecuteDirect(R"(SELECT "Remarks1", "Remarks2" FROM "Test")"); REQUIRE(result.FetchRow()); auto const actual1 = result.GetNullableColumn(1); auto const actual2 = result.GetNullableColumn(2); @@ -506,10 +503,9 @@ TEST_CASE_METHOD(SqlTestFixture, "GetColumnOr", "[SqlStatement]") .Column("Remarks2", Lightweight::SqlColumnTypeDefinitions::Varchar { 50 }); }); stmt.Prepare(R"(INSERT INTO "Test" ("Remarks1", "Remarks2") VALUES (?, ?))"); - stmt.Execute("Blurb", Lightweight::SqlNullValue); + (void) stmt.Execute("Blurb", Lightweight::SqlNullValue); - stmt.ExecuteDirect(R"(SELECT "Remarks1", "Remarks2" FROM "Test")"); - auto result = stmt.GetResultCursor(); + auto result = stmt.ExecuteDirect(R"(SELECT "Remarks1", "Remarks2" FROM "Test")"); REQUIRE(result.FetchRow()); auto const actual1 = result.GetColumnOr(1, "Foo"); auto const actual2 = result.GetColumnOr(2, "Bar"); @@ -521,9 +517,9 @@ TEST_CASE_METHOD(SqlTestFixture, "Prepare and move", "[SqlStatement]") { Lightweight::SqlStatement stmt; stmt = Lightweight::SqlStatement().Prepare("SELECT 42"); - stmt.Execute(); - REQUIRE(stmt.FetchRow()); - CHECK(stmt.GetColumn(1) == 42); + auto cursor = stmt.Execute(); + REQUIRE(cursor.FetchRow()); + CHECK(cursor.GetColumn(1) == 42); } struct Simple1 @@ -561,8 +557,8 @@ TEST_CASE_METHOD(SqlTestFixture, "SELECT into two structs", "[SqlStatement]") WHEN("inserting some data and getting it via multi struct query building") { - stmt.ExecuteDirect(conn.Query(Lightweight::RecordTableName).Insert().Set("c1", "a").Set("c2", "b")); - stmt.ExecuteDirect(conn.Query(Lightweight::RecordTableName).Insert().Set("c1", "a").Set("c2", "c")); + (void) stmt.ExecuteDirect(conn.Query(Lightweight::RecordTableName).Insert().Set("c1", "a").Set("c2", "b")); + (void) stmt.ExecuteDirect(conn.Query(Lightweight::RecordTableName).Insert().Set("c1", "a").Set("c2", "c")); // clang-format off stmt.Prepare( @@ -572,21 +568,21 @@ TEST_CASE_METHOD(SqlTestFixture, "SELECT into two structs", "[SqlStatement]") .LeftOuterJoin(Lightweight::RecordTableName, "c1", "c1").All()); // clang-format on - stmt.Execute(); + auto cursor = stmt.Execute(); THEN("we can fetch the data using multi struct output binding") { auto s1 = Simple1 {}; auto s2 = Simple2 {}; - stmt.BindOutputColumnsToRecord(&s1, &s2); + cursor.BindOutputColumnsToRecord(&s1, &s2); - REQUIRE(stmt.FetchRow()); + REQUIRE(cursor.FetchRow()); CHECK(s1.c1 == "a"); CHECK(s1.c2 == "b"); CHECK(s2.c1 == "a"); CHECK(s2.c2 == "c"); - REQUIRE(!stmt.FetchRow()); + REQUIRE(!cursor.FetchRow()); } } } @@ -609,8 +605,8 @@ TEST_CASE_METHOD(SqlTestFixture, "SELECT into SqlVariantRowIterator", "[SqlState WHEN("inserting some data and getting it via multi struct query building") { - stmt.ExecuteDirect(conn.Query(Lightweight::RecordTableName).Insert().Set("c1", "a").Set("c2", "b")); - stmt.ExecuteDirect(conn.Query(Lightweight::RecordTableName).Insert().Set("c1", "A").Set("c2", "B")); + (void) stmt.ExecuteDirect(conn.Query(Lightweight::RecordTableName).Insert().Set("c1", "a").Set("c2", "b")); + (void) stmt.ExecuteDirect(conn.Query(Lightweight::RecordTableName).Insert().Set("c1", "A").Set("c2", "B")); // clang-format off stmt.Prepare( @@ -620,12 +616,12 @@ TEST_CASE_METHOD(SqlTestFixture, "SELECT into SqlVariantRowIterator", "[SqlState .All()); // clang-format on - stmt.Execute(); + auto cursor = stmt.Execute(); THEN("we can fetch the data using SqlVariantRowIterator") { auto rowCount = 0; - for (auto& row: stmt.GetVariantRowCursor()) + for (auto& row: Lightweight::SqlVariantRowCursor(std::move(cursor))) { ++rowCount; CAPTURE(rowCount); diff --git a/src/tests/DataBinderTests.cpp b/src/tests/DataBinderTests.cpp index 8492e666..122387dc 100644 --- a/src/tests/DataBinderTests.cpp +++ b/src/tests/DataBinderTests.cpp @@ -187,35 +187,34 @@ TEST_CASE_METHOD(SqlTestFixture, "SqlVariant: GetColumn in-place store variant", CreateEmployeesTable(stmt); stmt.Prepare(R"(INSERT INTO "Employees" ("FirstName", "LastName", "Salary") VALUES (?, ?, ?))"); - stmt.Execute("Alice", SqlNullValue, 50'000); + (void) stmt.Execute("Alice", SqlNullValue, 50'000); - stmt.ExecuteDirect(R"(SELECT "FirstName", "LastName", "Salary" FROM "Employees")"); - (void) stmt.FetchRow(); + auto cursor = stmt.ExecuteDirect(R"(SELECT "FirstName", "LastName", "Salary" FROM "Employees")"); + (void) cursor.FetchRow(); - CHECK(stmt.GetColumn(1) == "Alice"); + CHECK(cursor.GetColumn(1) == "Alice"); SqlVariant lastName; - CHECK(!stmt.GetColumn(2, &lastName)); + CHECK(!cursor.GetColumn(2, &lastName)); CHECK(lastName.IsNull()); SqlVariant salary; - CHECK(stmt.GetColumn(3, &salary)); + CHECK(cursor.GetColumn(3, &salary)); CHECK(salary.TryGetInt().value_or(0) == 50'000); } TEST_CASE_METHOD(SqlTestFixture, "SqlVariant: NULL values", "[SqlDataBinder],[SqlVariant]") { auto stmt = SqlStatement(); - stmt.ExecuteDirect("CREATE TABLE Test (Remarks VARCHAR(50) NULL)"); + (void) stmt.ExecuteDirect("CREATE TABLE Test (Remarks VARCHAR(50) NULL)"); SECTION("Test for inserting/getting NULL values") { stmt.Prepare("INSERT INTO Test (Remarks) VALUES (?)"); - stmt.Execute(SqlNullValue); - stmt.ExecuteDirect("SELECT Remarks FROM Test"); + (void) stmt.Execute(SqlNullValue); - auto reader = stmt.GetResultCursor(); - (void) stmt.FetchRow(); + auto reader = stmt.ExecuteDirect("SELECT Remarks FROM Test"); + (void) reader.FetchRow(); auto const actual = reader.GetColumn(1); CHECK(std::holds_alternative(actual.value)); @@ -224,7 +223,7 @@ TEST_CASE_METHOD(SqlTestFixture, "SqlVariant: NULL values", "[SqlDataBinder],[Sq SECTION("Using ExecuteDirectScalar") { stmt.Prepare("INSERT INTO Test (Remarks) VALUES (?)"); - stmt.Execute(SqlNullValue); + (void) stmt.Execute(SqlNullValue); auto const result = stmt.ExecuteDirectScalar("SELECT Remarks FROM Test"); CHECK(result.IsNull()); } @@ -241,23 +240,21 @@ TEST_CASE_METHOD(SqlTestFixture, "SqlVariant: SqlGuid", "[SqlDataBinder],[SqlVar auto const& expectedValue = std::get(expectedVariant.value); stmt.Prepare(stmt.Query("Test").Insert().Set("Value", SqlWildcard)); - stmt.Execute(expectedVariant); + (void) stmt.Execute(expectedVariant); - stmt.ExecuteDirect(stmt.Query("Test").Select().Field("Value").All()); { - auto reader = stmt.GetResultCursor(); + auto reader = stmt.ExecuteDirect(stmt.Query("Test").Select().Field("Value").All()); (void) reader.FetchRow(); auto const actualVariant = reader.GetColumn(1); CHECK(actualVariant.TryGetGuid().value_or(SqlGuid {}) == expectedValue); } // Test for inserting/getting NULL values - stmt.ExecuteDirect(stmt.Query("Test").Delete()); + (void) stmt.ExecuteDirect(stmt.Query("Test").Delete()); stmt.Prepare(stmt.Query("Test").Insert().Set("Value", SqlWildcard)); - stmt.Execute(SqlNullValue); - stmt.ExecuteDirect(stmt.Query("Test").Select().Field("Value").All()); + (void) stmt.Execute(SqlNullValue); { - auto reader = stmt.GetResultCursor(); + auto reader = stmt.ExecuteDirect(stmt.Query("Test").Select().Field("Value").All()); (void) reader.FetchRow(); auto const actualVariant = reader.GetColumn(1); CHECK(actualVariant.IsNull()); @@ -280,20 +277,19 @@ TEST_CASE_METHOD(SqlTestFixture, "SqlVariant: SqlDate", "[SqlDataBinder],[SqlVar auto const& expectedDateTime = std::get(expected.value); stmt.Prepare(stmt.Query("Test").Insert().Set("Value", SqlWildcard)); - stmt.Execute(expected); + (void) stmt.Execute(expected); - stmt.ExecuteDirect(stmt.Query("Test").Select().Field("Value").All()); { - auto reader = stmt.GetResultCursor(); - (void) stmt.FetchRow(); + auto reader = stmt.ExecuteDirect(stmt.Query("Test").Select().Field("Value").All()); + (void) reader.FetchRow(); auto const actual = reader.GetColumn(1); CHECK(actual.TryGetDate().value_or(SqlDate {}) == expectedDateTime); } // Test for inserting/getting NULL values - stmt.ExecuteDirect(stmt.Query("Test").Delete()); + (void) stmt.ExecuteDirect(stmt.Query("Test").Delete()); stmt.Prepare(stmt.Query("Test").Insert().Set("Value", SqlWildcard)); - stmt.Execute(SqlNullValue); + (void) stmt.Execute(SqlNullValue); auto const result = stmt.ExecuteDirectScalar(stmt.Query("Test").Select().Field("Value").All()); CHECK(result.IsNull()); } @@ -308,7 +304,7 @@ TEST_CASE_METHOD(SqlTestFixture, "SqlVariant: SqlTime", "[SqlDataBinder],[SqlVar auto const expected = SqlVariant { SqlTime { 12h, 34min, 56s } }; stmt.Prepare(stmt.Query("Test").Insert().Set("Value", SqlWildcard)); - stmt.Execute(expected); + (void) stmt.Execute(expected); auto const actual = stmt.ExecuteDirectScalar(stmt.Query("Test").Select().Field("Value").All()); @@ -324,9 +320,9 @@ TEST_CASE_METHOD(SqlTestFixture, "SqlVariant: SqlTime", "[SqlDataBinder],[SqlVar CHECK(actual.TryGetTime().value() == std::get(expected.value)); // Test for inserting/getting NULL values - stmt.ExecuteDirect(stmt.Query("Test").Delete()); + (void) stmt.ExecuteDirect(stmt.Query("Test").Delete()); stmt.Prepare(stmt.Query("Test").Insert().Set("Value", SqlWildcard)); - stmt.Execute(SqlNullValue); + (void) stmt.Execute(SqlNullValue); auto const result = stmt.ExecuteDirectScalar(stmt.Query("Test").Select().Field("Value").All()); CHECK(result.IsNull()); } @@ -339,29 +335,28 @@ TEST_CASE_METHOD(SqlTestFixture, "InputParameter and GetColumn for very large va migration.CreateTable("Test").Column("Value", SqlColumnTypeDefinitions::Text { size }); }); stmt.Prepare(stmt.Query("Test").Insert().Set("Value", SqlWildcard)); - stmt.Execute(expectedText); + (void) stmt.Execute(expectedText); SECTION("check handling for explicitly fetched output columns") { - stmt.ExecuteDirect(stmt.Query("Test").Select().Field("Value").All()); - (void) stmt.FetchRow(); - CHECK(stmt.GetColumn(1) == expectedText); + auto cursor = stmt.ExecuteDirect(stmt.Query("Test").Select().Field("Value").All()); + (void) cursor.FetchRow(); + CHECK(cursor.GetColumn(1) == expectedText); } SECTION("check handling for explicitly fetched output columns (in-place store)") { - stmt.ExecuteDirect(stmt.Query("Test").Select().Field("Value").All()); - (void) stmt.FetchRow(); + auto cursor = stmt.ExecuteDirect(stmt.Query("Test").Select().Field("Value").All()); + (void) cursor.FetchRow(); std::string actualText; - CHECK(stmt.GetColumn(1, &actualText)); + CHECK(cursor.GetColumn(1, &actualText)); CHECK(actualText == expectedText); } SECTION("check handling for bound output columns") { stmt.Prepare(stmt.Query("Test").Select().Field("Value").All()); - stmt.Execute(); - auto reader = stmt.GetResultCursor(); + auto cursor = stmt.Execute(); // Intentionally an empty string, auto-growing behind the scenes std::string actualText; @@ -374,8 +369,8 @@ TEST_CASE_METHOD(SqlTestFixture, "InputParameter and GetColumn for very large va actualText = std::string(expectedText.size() + 1, '\0'); } - reader.BindOutputColumns(&actualText); - (void) stmt.FetchRow(); + cursor.BindOutputColumns(&actualText); + (void) cursor.FetchRow(); REQUIRE(actualText.size() == expectedText.size()); CHECK(actualText == expectedText); } @@ -387,7 +382,7 @@ TEST_CASE_METHOD(SqlTestFixture, "SqlDataBinder: Unicode", "[SqlDataBinder],[Uni if (stmt.Connection().ServerType() == SqlServerType::SQLITE) // SQLite does UTF-8 by default, so we need to switch to UTF-16 - stmt.ExecuteDirect("PRAGMA encoding = 'UTF-16'"); + (void) stmt.ExecuteDirect("PRAGMA encoding = 'UTF-16'"); // Create table with Unicode column. stmt.MigrateDirect([](SqlMigrationQueryBuilder& migration) { @@ -397,36 +392,35 @@ TEST_CASE_METHOD(SqlTestFixture, "SqlDataBinder: Unicode", "[SqlDataBinder],[Uni stmt.Prepare(stmt.Query("Test").Insert().Set("Value", SqlWildcard)); // Insert via wide string literal - stmt.Execute(WTEXT("Wide string literal \U0001F600")); + (void) stmt.Execute(WTEXT("Wide string literal \U0001F600")); // Insert via wide string view - stmt.Execute(WideStringView(WTEXT("Wide string literal \U0001F600"))); + (void) stmt.Execute(WideStringView(WTEXT("Wide string literal \U0001F600"))); // Insert via wide string object WideString const inputValue = WTEXT("Wide string literal \U0001F600"); - stmt.Execute(inputValue); + (void) stmt.Execute(inputValue); - stmt.ExecuteDirect(stmt.Query("Test").Select().Field("Value").All()); { - auto reader = stmt.GetResultCursor(); + auto reader = stmt.ExecuteDirect(stmt.Query("Test").Select().Field("Value").All()); // Fetch and check GetColumn for wide string - (void) stmt.FetchRow(); + (void) reader.FetchRow(); auto const actualValue = reader.GetColumn(1); CHECK(actualValue == inputValue); // Bind output column, fetch, and check result in output column for wide string WideString actualValue2; reader.BindOutputColumns(&actualValue2); - (void) stmt.FetchRow(); + (void) reader.FetchRow(); CHECK(actualValue2 == inputValue); } SECTION("Test for inserting/getting NULL VALUES") { - stmt.ExecuteDirect(stmt.Query("Test").Delete()); + (void) stmt.ExecuteDirect(stmt.Query("Test").Delete()); stmt.Prepare(stmt.Query("Test").Insert().Set("Value", SqlWildcard)); - stmt.Execute(SqlNullValue); + (void) stmt.Execute(SqlNullValue); auto const result = stmt.ExecuteDirectScalar(stmt.Query("Test").Select().Field("Value").First()); CHECK(!result.has_value()); } @@ -438,7 +432,7 @@ TEST_CASE_METHOD(SqlTestFixture, "SqlDataBinder: Unicode mixed", "[SqlDataBinder if (stmt.Connection().ServerType() == SqlServerType::SQLITE) // SQLite does UTF-8 by default, so we need to switch to UTF-16 - stmt.ExecuteDirect("PRAGMA encoding = 'UTF-16'"); + (void) stmt.ExecuteDirect("PRAGMA encoding = 'UTF-16'"); stmt.MigrateDirect([](SqlMigrationQueryBuilder& migration) { migration.CreateTable("Test").Column("Value", SqlColumnTypeDefinitions::NVarchar(10)); @@ -450,7 +444,7 @@ TEST_CASE_METHOD(SqlTestFixture, "SqlDataBinder: Unicode mixed", "[SqlDataBinder // Write value: UTF-8 encoded stmt.Prepare(stmt.Query("Test").Insert().Set("Value", SqlWildcard)); - stmt.Execute(inputValue); + (void) stmt.Execute(inputValue); // Read value: UTF-16 encoded auto actualValue = stmt.ExecuteDirectScalar(stmt.Query("Test").Select().Field("Value").First()); @@ -492,7 +486,7 @@ TEST_CASE_METHOD(SqlTestFixture, "SqlNumeric.StoreAndLoad", "[SqlDataBinder],[Sq auto const inputValue = SqlNumeric<10, 2> { 99999999.99 }; stmt.Prepare(stmt.Query("Test").Insert().Set("Value", SqlWildcard)); - stmt.Execute(inputValue); + (void) stmt.Execute(inputValue); // Check retrieval via type: string auto const receivedStr = stmt.ExecuteDirectScalar(stmt.Query("Test").Select().Field("Value").All()); @@ -988,32 +982,32 @@ TEMPLATE_LIST_TEST_CASE("SqlDataBinder specializations", "[SqlDataBinder]", Type return conn.QueryFormatter().ColumnType(SqlDataBinder::ColumnType); }(); - stmt.ExecuteDirect(std::format("CREATE TABLE Test (Value {} NULL)", sqlColumnType)); + (void) stmt.ExecuteDirect(std::format("CREATE TABLE Test (Value {} NULL)", sqlColumnType)); WHEN("Inserting a value") { stmt.Prepare("INSERT INTO Test (Value) VALUES (?)"); CAPTURE(TestTypeTraits::inputValue); - stmt.Execute(TestTypeTraits::inputValue); + (void) stmt.Execute(TestTypeTraits::inputValue); THEN("Retrieve value via GetColumn()") { - stmt.ExecuteDirect("SELECT Value FROM Test"); - CAPTURE(stmt.FetchRow()); + auto cursor = stmt.ExecuteDirect("SELECT Value FROM Test"); + CAPTURE(cursor.FetchRow()); if constexpr (std::is_convertible_v && !std::integral) { - auto const actualValue = stmt.GetColumn(1); + auto const actualValue = cursor.GetColumn(1); CHECK_THAT(actualValue, (Catch::Matchers::WithinAbs(double(TestTypeTraits::expectedOutputValue), 0.001))); } else if constexpr (requires { typename TestTypeTraits::GetColumnTypeOverride; }) { - auto const actualValue = stmt.GetColumn::GetColumnTypeOverride>(1); + auto const actualValue = cursor.GetColumn::GetColumnTypeOverride>(1); CHECK(actualValue == TestTypeTraits::expectedOutputValue); } else { - auto const actualValue = stmt.GetColumn(1); + auto const actualValue = cursor.GetColumn(1); CHECK(actualValue == TestTypeTraits::expectedOutputValue); } } @@ -1022,7 +1016,7 @@ TEMPLATE_LIST_TEST_CASE("SqlDataBinder specializations", "[SqlDataBinder]", Type { THEN("Retrieve value via BindOutputColumns()") { - stmt.ExecuteDirect("SELECT Value FROM Test"); + auto cursor = stmt.ExecuteDirect("SELECT Value FROM Test"); auto actualValue = [&]() -> TestType { if constexpr (requires(SqlServerType st) { TestTypeTraits::outputInitializer(st); }) return TestTypeTraits::outputInitializer(conn.ServerType()); @@ -1031,8 +1025,8 @@ TEMPLATE_LIST_TEST_CASE("SqlDataBinder specializations", "[SqlDataBinder]", Type else return TestType {}; }(); - stmt.BindOutputColumns(&actualValue); - (void) stmt.FetchRow(); + cursor.BindOutputColumns(&actualValue); + (void) cursor.FetchRow(); if constexpr (std::is_convertible_v && !std::integral) CHECK_THAT( double(actualValue), @@ -1048,29 +1042,29 @@ TEMPLATE_LIST_TEST_CASE("SqlDataBinder specializations", "[SqlDataBinder]", Type WHEN("Inserting a NULL value") { stmt.Prepare("INSERT INTO Test (Value) VALUES (?)"); - stmt.Execute(SqlNullValue); + (void) stmt.Execute(SqlNullValue); THEN("Retrieve value via GetNullableColumn()") { - stmt.ExecuteDirect("SELECT Value FROM Test"); - (void) stmt.FetchRow(); - CHECK(!stmt.GetNullableColumn(1).has_value()); + auto cursor = stmt.ExecuteDirect("SELECT Value FROM Test"); + (void) cursor.FetchRow(); + CHECK(!cursor.GetNullableColumn(1).has_value()); } THEN("Retrieve value via GetColumn()") { - stmt.ExecuteDirect("SELECT Value FROM Test"); - (void) stmt.FetchRow(); - CHECK_THROWS_AS(stmt.GetColumn(1), std::runtime_error); + auto cursor = stmt.ExecuteDirect("SELECT Value FROM Test"); + (void) cursor.FetchRow(); + CHECK_THROWS_AS(cursor.GetColumn(1), std::runtime_error); } THEN("Retrieve value via BindOutputColumns()") { stmt.Prepare("SELECT Value FROM Test"); - stmt.Execute(); + auto cursor = stmt.Execute(); auto actualValue = std::optional {}; - stmt.BindOutputColumns(&actualValue); - (void) stmt.FetchRow(); + cursor.BindOutputColumns(&actualValue); + (void) cursor.FetchRow(); CHECK(!actualValue.has_value()); } } @@ -1097,17 +1091,16 @@ TEST_CASE_METHOD(SqlTestFixture, "SqlWideString read from VARCHAR column", "[Sql SECTION("short value") { - stmt.Execute("Hello, World!"sv); + std::ignore = stmt.Execute("Hello, World!"sv); auto constexpr expectedWide = SqlWideString<100> { L"Hello, World!" }; SECTION("BindOutputColumns") { stmt.Prepare(stmt.Query("WideStringFromVarcharTest").Select().Field("value").All()); - stmt.Execute(); - auto reader = stmt.GetResultCursor(); + auto reader = stmt.Execute(); SqlWideString<100> actual; reader.BindOutputColumns(&actual); - (void) stmt.FetchRow(); + (void) reader.FetchRow(); CHECK(actual == expectedWide); } @@ -1124,16 +1117,15 @@ TEST_CASE_METHOD(SqlTestFixture, "SqlWideString read from VARCHAR column", "[Sql { auto const narrowStr = std::string(100, 'A'); auto const expectedWide = SqlWideString<100> { std::wstring(100, L'A') }; - stmt.Execute(narrowStr); + std::ignore = stmt.Execute(narrowStr); SECTION("BindOutputColumns") { stmt.Prepare(stmt.Query("WideStringFromVarcharTest").Select().Field("value").All()); - stmt.Execute(); - auto reader = stmt.GetResultCursor(); + auto reader = stmt.Execute(); SqlWideString<100> actual; reader.BindOutputColumns(&actual); - (void) stmt.FetchRow(); + (void) reader.FetchRow(); CHECK(actual == expectedWide); } @@ -1149,17 +1141,16 @@ TEST_CASE_METHOD(SqlTestFixture, "SqlWideString read from VARCHAR column", "[Sql SECTION("umlaut value") { // Insert UTF-8 encoded umlauts into VARCHAR; the ODBC driver converts to wide on read-back. - stmt.Execute(u8"Straße mit Häusern"sv); + std::ignore = stmt.Execute(u8"Straße mit Häusern"sv); auto constexpr expectedWide = SqlWideString<100> { L"Straße mit Häusern" }; SECTION("BindOutputColumns") { stmt.Prepare(stmt.Query("WideStringFromVarcharTest").Select().Field("value").All()); - stmt.Execute(); - auto reader = stmt.GetResultCursor(); + auto reader = stmt.Execute(); SqlWideString<100> actual; reader.BindOutputColumns(&actual); - (void) stmt.FetchRow(); + (void) reader.FetchRow(); CHECK(actual == expectedWide); } @@ -1188,16 +1179,15 @@ TEST_CASE_METHOD(SqlTestFixture, "SqlWideString read from VARCHAR column", "[Sql narrowStr.append("\xc3\xa4", 2); // UTF-8 encoding of ä (U+00E4) auto const expectedWide = SqlWideString<100> { std::wstring(100, L'ä') }; - stmt.Execute(narrowStr); + [[maybe_unused]] auto cursor = stmt.Execute(narrowStr); SECTION("BindOutputColumns") { stmt.Prepare(stmt.Query("WideStringFromVarcharTest").Select().Field("value").All()); - stmt.Execute(); - auto reader = stmt.GetResultCursor(); + auto reader = stmt.Execute(); SqlWideString<100> actual; reader.BindOutputColumns(&actual); - (void) stmt.FetchRow(); + (void) reader.FetchRow(); CHECK(actual == expectedWide); } @@ -1219,18 +1209,17 @@ TEST_CASE_METHOD(SqlTestFixture, "SqlWideString read from VARCHAR column", "[Sql if (stmt.Connection().ServerType() != SqlServerType::SQLITE) SKIP(); - stmt.Execute("Stra\xdf" - "e mit H\xe4usern"sv); + std::ignore = stmt.Execute("Stra\xdf" + "e mit H\xe4usern"sv); auto constexpr expectedWide = SqlWideString<100> { L"Straße mit Häusern" }; SECTION("BindOutputColumns") { stmt.Prepare(stmt.Query("WideStringFromVarcharTest").Select().Field("value").All()); - stmt.Execute(); - auto reader = stmt.GetResultCursor(); + auto reader = stmt.Execute(); SqlWideString<100> actual; reader.BindOutputColumns(&actual); - (void) stmt.FetchRow(); + (void) reader.FetchRow(); CHECK(actual == expectedWide); } @@ -1252,18 +1241,17 @@ TEST_CASE_METHOD(SqlTestFixture, "SqlWideString read from VARCHAR column", "[Sql if (stmt.Connection().ServerType() != SqlServerType::MICROSOFT_SQL) SKIP(); - stmt.ExecuteDirect("INSERT INTO WideStringFromVarcharTest (value) " - "VALUES ('Stra' + CHAR(223) + 'e mit H' + CHAR(228) + 'usern')"); + std::ignore = stmt.ExecuteDirect("INSERT INTO WideStringFromVarcharTest (value) " + "VALUES ('Stra' + CHAR(223) + 'e mit H' + CHAR(228) + 'usern')"); auto constexpr expectedWide = SqlWideString<100> { L"Straße mit Häusern" }; SECTION("BindOutputColumns") { stmt.Prepare(stmt.Query("WideStringFromVarcharTest").Select().Field("value").All()); - stmt.Execute(); - auto reader = stmt.GetResultCursor(); + auto reader = stmt.Execute(); SqlWideString<100> actual; reader.BindOutputColumns(&actual); - (void) stmt.FetchRow(); + (void) reader.FetchRow(); CHECK(actual == expectedWide); } @@ -1283,17 +1271,16 @@ TEST_CASE_METHOD(SqlTestFixture, "SqlWideString read from VARCHAR column", "[Sql if (stmt.Connection().ServerType() != SqlServerType::SQLITE) SKIP(); - stmt.Execute(std::string(100, '\xe4')); + std::ignore = stmt.Execute(std::string(100, '\xe4')); auto const expectedWide = SqlWideString<100> { std::wstring(100, L'\u00e4') }; SECTION("BindOutputColumns") { stmt.Prepare(stmt.Query("WideStringFromVarcharTest").Select().Field("value").All()); - stmt.Execute(); - auto reader = stmt.GetResultCursor(); + auto reader = stmt.Execute(); SqlWideString<100> actual; reader.BindOutputColumns(&actual); - (void) stmt.FetchRow(); + (void) reader.FetchRow(); CHECK(actual == expectedWide); } @@ -1313,18 +1300,17 @@ TEST_CASE_METHOD(SqlTestFixture, "SqlWideString read from VARCHAR column", "[Sql if (stmt.Connection().ServerType() != SqlServerType::MICROSOFT_SQL) SKIP(); - stmt.ExecuteDirect("INSERT INTO WideStringFromVarcharTest (value) " - "VALUES (REPLICATE(CHAR(228), 100))"); + std::ignore = stmt.ExecuteDirect("INSERT INTO WideStringFromVarcharTest (value) " + "VALUES (REPLICATE(CHAR(228), 100))"); auto const expectedWide = SqlWideString<100> { std::wstring(100, L'\u00e4') }; SECTION("BindOutputColumns") { stmt.Prepare(stmt.Query("WideStringFromVarcharTest").Select().Field("value").All()); - stmt.Execute(); - auto reader = stmt.GetResultCursor(); + auto reader = stmt.Execute(); SqlWideString<100> actual; reader.BindOutputColumns(&actual); - (void) stmt.FetchRow(); + (void) reader.FetchRow(); CHECK(actual == expectedWide); } diff --git a/src/tests/DataMapper/AsynchronousTests.cpp b/src/tests/DataMapper/AsynchronousTests.cpp index 70e1a78a..6f91606d 100644 --- a/src/tests/DataMapper/AsynchronousTests.cpp +++ b/src/tests/DataMapper/AsynchronousTests.cpp @@ -30,7 +30,7 @@ using namespace std::string_view_literals; using namespace std::string_literals; using namespace Lightweight; -TEST_CASE("Fetch from Local data mapper", "[DataMapper],[Executors]") +TEST_CASE_METHOD(SqlTestFixture, "Fetch from Local data mapper", "[DataMapper],[Executors]") { auto pool = exec::static_thread_pool(3); diff --git a/src/tests/DataMapper/MiscTests.cpp b/src/tests/DataMapper/MiscTests.cpp index f364dfed..9fe962a8 100644 --- a/src/tests/DataMapper/MiscTests.cpp +++ b/src/tests/DataMapper/MiscTests.cpp @@ -618,14 +618,14 @@ TEST_CASE_METHOD(SqlTestFixture, "Query builder", "[DataMapper]") if (stmt.Connection().ServerType() == SqlServerType::SQLITE) { - stmt.ExecuteDirect(R"SQL( + (void) stmt.ExecuteDirect(R"SQL( CREATE TABLE "That" ( "a" INT, "b" INT, "c" INT ))SQL"); - stmt.ExecuteDirect(R"SQL(INSERT INTO "That" ("a", "b", "c") VALUES (1, 2, 3))SQL"); + (void) stmt.ExecuteDirect(R"SQL(INSERT INTO "That" ("a", "b", "c") VALUES (1, 2, 3))SQL"); auto const result = dm.Query(query); REQUIRE(result.size() == 1); diff --git a/src/tests/DataMapper/NamingTests.cpp b/src/tests/DataMapper/NamingTests.cpp index 647ddda2..eb486f28 100644 --- a/src/tests/DataMapper/NamingTests.cpp +++ b/src/tests/DataMapper/NamingTests.cpp @@ -114,8 +114,8 @@ TEST_CASE_METHOD(SqlTestFixture, "Check aliasing of the columns and table for cr record1.name = "43"; dm.Update(record1); - SqlStatement(dm.Connection()) - .ExecuteDirect(R"( INSERT INTO "Human" ("index", "not_name") VALUES (5, 'Direct Insert') )"); + (void) SqlStatement(dm.Connection()) + .ExecuteDirect(R"( INSERT INTO "Human" ("index", "not_name") VALUES (5, 'Direct Insert') )"); CHECK(dm.Query().Count() == 2); { auto queriedRecordResult = dm.QuerySingle(record1.id); diff --git a/src/tests/DataMapper/ReadTests.cpp b/src/tests/DataMapper/ReadTests.cpp index 23852647..5152f1ca 100644 --- a/src/tests/DataMapper/ReadTests.cpp +++ b/src/tests/DataMapper/ReadTests.cpp @@ -324,8 +324,8 @@ TEST_CASE_METHOD(SqlTestFixture, "Query: SELECT into simple struct", "[DataMappe .Column("c2", Varchar { 30 }); }); - SqlStatement(dm.Connection()).ExecuteDirect(dm.FromTable("TableA").Insert().Set("c1", "a").Set("c2", "b")); - SqlStatement(dm.Connection()).ExecuteDirect(dm.FromTable("TableB").Insert().Set("c1", "a").Set("c2", "c")); + (void) SqlStatement(dm.Connection()).ExecuteDirect(dm.FromTable("TableA").Insert().Set("c1", "a").Set("c2", "b")); + (void) SqlStatement(dm.Connection()).ExecuteDirect(dm.FromTable("TableB").Insert().Set("c1", "a").Set("c2", "c")); auto records = dm.Query(dm.FromTable("TableA") @@ -364,8 +364,8 @@ TEST_CASE_METHOD(SqlTestFixture, "Query: SELECT into SqlVariantRow", "[DataMappe .Column("c2", Varchar { 30 }); }); - SqlStatement(dm.Connection()).ExecuteDirect(dm.FromTable("TableA").Insert().Set("c1", "a").Set("c2", "b")); - SqlStatement(dm.Connection()).ExecuteDirect(dm.FromTable("TableB").Insert().Set("c1", "a").Set("c2", "c")); + (void) SqlStatement(dm.Connection()).ExecuteDirect(dm.FromTable("TableA").Insert().Set("c1", "a").Set("c2", "b")); + (void) SqlStatement(dm.Connection()).ExecuteDirect(dm.FromTable("TableB").Insert().Set("c1", "a").Set("c2", "c")); auto records = dm.Query(dm.FromTable("TableA").Select().Field("*").LeftOuterJoin("TableB", "c1", "c1").All()); @@ -600,16 +600,14 @@ TEST_CASE_METHOD(SqlTestFixture, "Get optional values from the statement", "[Dat .All(); stmt.Prepare(query); { - stmt.Execute(record.id.Value()); - auto res = stmt.GetResultCursor(); + auto res = stmt.Execute(record.id.Value()); REQUIRE(res.FetchRow()); REQUIRE(res.GetColumn(1)); REQUIRE(!res.GetNullableColumn(2).has_value()); // similar to res.GetColumn>(2) } { - stmt.Execute(SqlGuid::Create()); - auto res = stmt.GetResultCursor(); + auto res = stmt.Execute(SqlGuid::Create()); REQUIRE(!res.FetchRow()); } } diff --git a/src/tests/LargeTestDatabase/DataGenerator.cpp b/src/tests/LargeTestDatabase/DataGenerator.cpp index 90d99daf..9a4a3aa0 100644 --- a/src/tests/LargeTestDatabase/DataGenerator.cpp +++ b/src/tests/LargeTestDatabase/DataGenerator.cpp @@ -272,7 +272,7 @@ void DropSchema(Light::DataMapper& dm) { try { - stmt.ExecuteDirect(std::format("DROP TABLE IF EXISTS \"{}\"", table)); + (void) stmt.ExecuteDirect(std::format("DROP TABLE IF EXISTS \"{}\"", table)); } // NOLINTNEXTLINE(bugprone-empty-catch) - intentionally ignoring errors during cleanup catch (...) diff --git a/src/tests/MigrationReflectionTests.cpp b/src/tests/MigrationReflectionTests.cpp index 1446fc25..892175db 100644 --- a/src/tests/MigrationReflectionTests.cpp +++ b/src/tests/MigrationReflectionTests.cpp @@ -109,7 +109,7 @@ TEST_CASE_METHOD(SqlTestFixture, auto parentMigration = conn.Migration(); parentMigration.CreateTable("FkTestParent").PrimaryKey("id", Integer {}); for (auto const& sql: parentMigration.GetPlan().ToSql()) - stmt.ExecuteDirect(sql); + (void) stmt.ExecuteDirect(sql); // Create child table with single-column foreign key auto childMigration = conn.Migration(); @@ -118,7 +118,7 @@ TEST_CASE_METHOD(SqlTestFixture, .ForeignKey( "parent_id", Integer {}, SqlForeignKeyReferenceDefinition { .tableName = "FkTestParent", .columnName = "id" }); for (auto const& sql: childMigration.GetPlan().ToSql()) - stmt.ExecuteDirect(sql); + (void) stmt.ExecuteDirect(sql); // Read schema back from database auto tables = SqlSchema::ReadAllTables(stmt, conn.DatabaseName(), ""); @@ -149,8 +149,8 @@ TEST_CASE_METHOD(SqlTestFixture, CHECK(plan.foreignKeys[0].referencedTableName == "FkTestParent"); // Cleanup - stmt.ExecuteDirect("DROP TABLE \"FkTestChild\""); - stmt.ExecuteDirect("DROP TABLE \"FkTestParent\""); + (void) stmt.ExecuteDirect("DROP TABLE \"FkTestChild\""); + (void) stmt.ExecuteDirect("DROP TABLE \"FkTestParent\""); } TEST_CASE_METHOD(SqlTestFixture, @@ -170,7 +170,7 @@ TEST_CASE_METHOD(SqlTestFixture, .Column(SqlColumnDeclaration { .name = "key2", .type = Integer {}, .primaryKey = SqlPrimaryKeyType::MANUAL, .required = true }); for (auto const& sql: parentMigration.GetPlan().ToSql()) - stmt.ExecuteDirect(sql); + (void) stmt.ExecuteDirect(sql); // Create child table with composite foreign key auto childMigration = conn.Migration(); @@ -180,7 +180,7 @@ TEST_CASE_METHOD(SqlTestFixture, .RequiredColumn("parent_key2", Integer {}) .ForeignKey({ "parent_key1", "parent_key2" }, "FkTestParentComposite", { "key1", "key2" }); for (auto const& sql: childMigration.GetPlan().ToSql()) - stmt.ExecuteDirect(sql); + (void) stmt.ExecuteDirect(sql); // Read schema back from database auto tables = SqlSchema::ReadAllTables(stmt, conn.DatabaseName(), ""); @@ -213,6 +213,6 @@ TEST_CASE_METHOD(SqlTestFixture, REQUIRE(plan.foreignKeys[0].referencedColumns.size() == 2); // Cleanup - stmt.ExecuteDirect("DROP TABLE \"FkTestChildComposite\""); - stmt.ExecuteDirect("DROP TABLE \"FkTestParentComposite\""); + (void) stmt.ExecuteDirect("DROP TABLE \"FkTestChildComposite\""); + (void) stmt.ExecuteDirect("DROP TABLE \"FkTestParentComposite\""); } diff --git a/src/tests/MigrationTests.cpp b/src/tests/MigrationTests.cpp index c05a77bd..674bb13e 100644 --- a/src/tests/MigrationTests.cpp +++ b/src/tests/MigrationTests.cpp @@ -225,7 +225,7 @@ TEST_CASE_METHOD(SqlMigrationTestFixture, "Revert Migration", "[SqlMigration]") auto conn = SqlConnection {}; auto stmt = SqlStatement { conn }; // Should not throw - stmt.ExecuteDirect("SELECT count(*) FROM reversible_table"); + (void) stmt.ExecuteDirect("SELECT count(*) FROM reversible_table"); } // Revert @@ -241,7 +241,7 @@ TEST_CASE_METHOD(SqlMigrationTestFixture, "Revert Migration", "[SqlMigration]") auto stmt = SqlStatement { conn }; auto const _ = ScopedSqlNullLogger {}; // Should throw because table does not exist - CHECK_THROWS_AS(stmt.ExecuteDirect("SELECT count(*) FROM reversible_table"), SqlException); + CHECK_THROWS_AS((void) stmt.ExecuteDirect("SELECT count(*) FROM reversible_table"), SqlException); } } @@ -275,7 +275,7 @@ TEST_CASE_METHOD(SqlMigrationTestFixture, "Transaction Rollback", "[SqlMigration auto stmt = SqlStatement { conn }; auto const _ = ScopedSqlNullLogger {}; // Should throw because table should have been rolled back - CHECK_THROWS_AS(stmt.ExecuteDirect("SELECT count(*) FROM should_not_exist"), SqlException); + CHECK_THROWS_AS((void) stmt.ExecuteDirect("SELECT count(*) FROM should_not_exist"), SqlException); } } @@ -301,9 +301,9 @@ TEST_CASE_METHOD(SqlMigrationTestFixture, "Raw SQL Migration", "[SqlMigration]") auto conn = SqlConnection {}; auto stmt = SqlStatement { conn }; // Verify using QueryBuilder to ensure portability - stmt.ExecuteDirect("SELECT id FROM raw_sql_test WHERE id = 42"); - CHECK(stmt.FetchRow()); - CHECK(stmt.GetColumn(1) == 42); + auto cursor = stmt.ExecuteDirect("SELECT id FROM raw_sql_test WHERE id = 42"); + CHECK(cursor.FetchRow()); + CHECK(cursor.GetColumn(1) == 42); } // Revert @@ -315,7 +315,7 @@ TEST_CASE_METHOD(SqlMigrationTestFixture, "Raw SQL Migration", "[SqlMigration]") auto stmt = SqlStatement { conn }; { auto const _ = ScopedSqlNullLogger {}; // suppress the error message, we are testing for it - CHECK_THROWS_AS(stmt.ExecuteDirect("SELECT * FROM raw_sql_test"), SqlException); + CHECK_THROWS_AS((void) stmt.ExecuteDirect("SELECT * FROM raw_sql_test"), SqlException); } } } @@ -355,7 +355,7 @@ TEST_CASE_METHOD(SqlMigrationTestFixture, "Dry Run Migration", "[SqlMigration]") auto conn = SqlConnection {}; auto stmt = SqlStatement { conn }; auto const _ = ScopedSqlNullLogger {}; - CHECK_THROWS_AS(stmt.ExecuteDirect("SELECT * FROM dry_run_test"), SqlException); + CHECK_THROWS_AS((void) stmt.ExecuteDirect("SELECT * FROM dry_run_test"), SqlException); } } @@ -393,7 +393,7 @@ TEST_CASE_METHOD(SqlMigrationTestFixture, "Checksum Stored on Migration", "[SqlM auto const _ = ScopedSqlNullLogger {}; try { - stmt.ExecuteDirect("DROP TABLE schema_migrations"); + (void) stmt.ExecuteDirect("DROP TABLE schema_migrations"); } // NOLINTNEXTLINE(bugprone-empty-catch) - Table may not exist, intentionally ignoring catch (SqlException const&) @@ -490,7 +490,7 @@ TEST_CASE_METHOD(SqlMigrationTestFixture, "MarkMigrationAsApplied", "[SqlMigrati auto conn = SqlConnection {}; auto stmt = SqlStatement { conn }; auto const _ = ScopedSqlNullLogger {}; - CHECK_THROWS_AS(stmt.ExecuteDirect("SELECT * FROM mark_applied_test"), SqlException); + CHECK_THROWS_AS((void) stmt.ExecuteDirect("SELECT * FROM mark_applied_test"), SqlException); } } @@ -560,15 +560,15 @@ TEST_CASE_METHOD(SqlMigrationTestFixture, "RevertToMigration", "[SqlMigration]") auto conn = SqlConnection {}; auto stmt = SqlStatement { conn }; auto const _ = ScopedSqlNullLogger {}; - CHECK_THROWS_AS(stmt.ExecuteDirect("SELECT * FROM revert_to_2"), SqlException); - CHECK_THROWS_AS(stmt.ExecuteDirect("SELECT * FROM revert_to_3"), SqlException); + CHECK_THROWS_AS((void) stmt.ExecuteDirect("SELECT * FROM revert_to_2"), SqlException); + CHECK_THROWS_AS((void) stmt.ExecuteDirect("SELECT * FROM revert_to_3"), SqlException); } // Table 1 should still exist { auto conn = SqlConnection {}; auto stmt = SqlStatement { conn }; - stmt.ExecuteDirect("SELECT * FROM revert_to_1"); + (void) stmt.ExecuteDirect("SELECT * FROM revert_to_1"); } } @@ -652,7 +652,7 @@ TEST_CASE_METHOD(SqlMigrationTestFixture, "CreateTableIfNotExists", "[SqlMigrati { auto conn = SqlConnection {}; auto stmt = SqlStatement { conn }; - stmt.ExecuteDirect("SELECT * FROM idempotent_table"); + (void) stmt.ExecuteDirect("SELECT * FROM idempotent_table"); } } diff --git a/src/tests/QueryBuilderTests.cpp b/src/tests/QueryBuilderTests.cpp index 3fd18308..24d48294 100644 --- a/src/tests/QueryBuilderTests.cpp +++ b/src/tests/QueryBuilderTests.cpp @@ -89,12 +89,12 @@ void RunSqlQueryBuilder(QueryBuilderCheck const& info, { auto sqlPrepareStatements = info.prepare(conn.Migration()).ToSql(); for (auto const& sql: sqlPrepareStatements) - stmt.ExecuteDirect(sql); + (void) stmt.ExecuteDirect(sql); } auto sqlTestStatements = info.test(conn.Migration()).ToSql(); for (auto const& sql: sqlTestStatements) - stmt.ExecuteDirect(sql); + (void) stmt.ExecuteDirect(sql); if (info.post) { @@ -859,10 +859,10 @@ TEST_CASE_METHOD(SqlTestFixture, "Use SqlQueryBuilder for SqlStatement.ExecuteDi CreateEmployeesTable(stmt); FillEmployeesTable(stmt); - stmt.ExecuteDirect(stmt.Connection().Query("Employees").Select().Fields("FirstName", "LastName").All()); + auto cursor = stmt.ExecuteDirect(stmt.Connection().Query("Employees").Select().Fields("FirstName", "LastName").All()); - REQUIRE(stmt.FetchRow()); - CHECK(stmt.GetColumn(1) == "Alice"); + REQUIRE(cursor.FetchRow()); + CHECK(cursor.GetColumn(1) == "Alice"); } TEST_CASE_METHOD(SqlTestFixture, "Use SqlQueryBuilder for SqlStatement.Prepare", "[SqlQueryBuilder]") @@ -882,13 +882,13 @@ TEST_CASE_METHOD(SqlTestFixture, "Use SqlQueryBuilder for SqlStatement.Prepare", CHECK(std::get(inputBindings[1].value) == 50'000); stmt.Prepare(sqlQuery); - stmt.ExecuteWithVariants(inputBindings); + (void) stmt.ExecuteWithVariants(inputBindings); - stmt.ExecuteDirect(R"(SELECT "FirstName", "LastName", "Salary" FROM "Employees" WHERE "Salary" = 55000)"); - REQUIRE(stmt.FetchRow()); - CHECK(stmt.GetColumn(1) == "Alice"); - CHECK(stmt.GetColumn(2) == "Smith"); - CHECK(stmt.GetColumn(3) == 55'000); + auto cursor = stmt.ExecuteDirect(R"(SELECT "FirstName", "LastName", "Salary" FROM "Employees" WHERE "Salary" = 55000)"); + REQUIRE(cursor.FetchRow()); + CHECK(cursor.GetColumn(1) == "Alice"); + CHECK(cursor.GetColumn(2) == "Smith"); + CHECK(cursor.GetColumn(3) == 55'000); } TEST_CASE_METHOD(SqlTestFixture, "Use SqlQueryBuilder for SqlStatement.Prepare: iterative", "[SqlQueryBuilder]") @@ -916,7 +916,7 @@ TEST_CASE_METHOD(SqlTestFixture, "Use SqlQueryBuilder for SqlStatement.Prepare: inputBindings.emplace_back(std::string(1, c) + std::to_string(i)); // Execute the query with the prepared data - stmt.ExecuteWithVariants(inputBindings); + (void) stmt.ExecuteWithVariants(inputBindings); } } @@ -924,8 +924,8 @@ TEST_CASE_METHOD(SqlTestFixture, "SqlQueryBuilder: sub select with Where", "[Sql { auto stmt = SqlStatement {}; - stmt.ExecuteDirect(R"SQL(DROP TABLE IF EXISTS "Test")SQL"); - stmt.ExecuteDirect(R"SQL( + (void) stmt.ExecuteDirect(R"SQL(DROP TABLE IF EXISTS "Test")SQL"); + (void) stmt.ExecuteDirect(R"SQL( CREATE TABLE "Test" ( "name" VARCHAR(20) NULL, "secret" INT NULL @@ -935,7 +935,7 @@ TEST_CASE_METHOD(SqlTestFixture, "SqlQueryBuilder: sub select with Where", "[Sql stmt.Prepare(R"SQL(INSERT INTO "Test" ("name", "secret") VALUES (?, ?))SQL"); auto const names = std::vector> { "Alice", "Bob", "Charlie", "David" }; auto const secrets = std::vector { 42, 43, 44, 45 }; - stmt.ExecuteBatchSoft(names, secrets); + (void) stmt.ExecuteBatchSoft(names, secrets); auto const totalRecords = stmt.ExecuteDirectScalar(R"SQL(SELECT COUNT(*) FROM "Test")SQL"); REQUIRE(totalRecords.value_or(0) == 4); @@ -953,21 +953,21 @@ TEST_CASE_METHOD(SqlTestFixture, "SqlQueryBuilder: sub select with Where", "[Sql .All(); // clang-format on stmt.Prepare(selectQuery); - stmt.Execute(); + auto cursor = stmt.Execute(); - REQUIRE(stmt.FetchRow()); - CHECK(stmt.GetColumn(1) == "Alice"); - CHECK(stmt.GetColumn(2) == 42); + REQUIRE(cursor.FetchRow()); + CHECK(cursor.GetColumn(1) == "Alice"); + CHECK(cursor.GetColumn(2) == 42); - REQUIRE(!stmt.FetchRow()); + REQUIRE(!cursor.FetchRow()); } TEST_CASE_METHOD(SqlTestFixture, "SqlQueryBuilder: sub select with WhereIn", "[SqlQueryBuilder]") { auto stmt = SqlStatement {}; - stmt.ExecuteDirect(R"SQL(DROP TABLE IF EXISTS "Test")SQL"); - stmt.ExecuteDirect(R"SQL( + (void) stmt.ExecuteDirect(R"SQL(DROP TABLE IF EXISTS "Test")SQL"); + (void) stmt.ExecuteDirect(R"SQL( CREATE TABLE "Test" ( "name" VARCHAR(20) NULL, "secret" INT NULL @@ -977,7 +977,7 @@ TEST_CASE_METHOD(SqlTestFixture, "SqlQueryBuilder: sub select with WhereIn", "[S stmt.Prepare(R"SQL(INSERT INTO "Test" ("name", "secret") VALUES (?, ?))SQL"); auto const names = std::vector> { "Alice", "Bob", "Charlie", "David" }; auto const secrets = std::vector { 42, 43, 44, 45 }; - stmt.ExecuteBatchSoft(names, secrets); + (void) stmt.ExecuteBatchSoft(names, secrets); auto const totalRecords = stmt.ExecuteDirectScalar("SELECT COUNT(*) FROM \"Test\""); REQUIRE(totalRecords.value_or(0) == 4); @@ -995,17 +995,17 @@ TEST_CASE_METHOD(SqlTestFixture, "SqlQueryBuilder: sub select with WhereIn", "[S .All(); // clang-format on stmt.Prepare(selectQuery); - stmt.Execute(); + auto cursor = stmt.Execute(); - REQUIRE(stmt.FetchRow()); - CHECK(stmt.GetColumn(1) == "Alice"); - CHECK(stmt.GetColumn(2) == 42); + REQUIRE(cursor.FetchRow()); + CHECK(cursor.GetColumn(1) == "Alice"); + CHECK(cursor.GetColumn(2) == 42); - REQUIRE(stmt.FetchRow()); - CHECK(stmt.GetColumn(1) == "Bob"); - CHECK(stmt.GetColumn(2) == 43); + REQUIRE(cursor.FetchRow()); + CHECK(cursor.GetColumn(1) == "Bob"); + CHECK(cursor.GetColumn(2) == 43); - REQUIRE(!stmt.FetchRow()); + REQUIRE(!cursor.FetchRow()); } TEST_CASE_METHOD(SqlTestFixture, "DropTable", "[SqlQueryBuilder][Migration]") diff --git a/src/tests/SqlBackup/BatchManagerIntegrationTests.cpp b/src/tests/SqlBackup/BatchManagerIntegrationTests.cpp index 355d3cde..a15bffab 100644 --- a/src/tests/SqlBackup/BatchManagerIntegrationTests.cpp +++ b/src/tests/SqlBackup/BatchManagerIntegrationTests.cpp @@ -20,8 +20,8 @@ TEST_CASE("BatchManager: Integration with SQLite", "[SqlBackup][Integration]") return; // Skip if no connection SqlStatement stmt { conn }; - stmt.ExecuteDirect("DROP TABLE IF EXISTS test_batch"); - stmt.ExecuteDirect("CREATE TABLE test_batch (id INTEGER PRIMARY KEY, txt VARCHAR(255))"); + (void) stmt.ExecuteDirect("DROP TABLE IF EXISTS test_batch"); + (void) stmt.ExecuteDirect("CREATE TABLE test_batch (id INTEGER PRIMARY KEY, txt VARCHAR(255))"); std::vector cols = { { .name = "id", .type = SqlColumnTypeDefinitions::Integer {} }, @@ -31,7 +31,7 @@ TEST_CASE("BatchManager: Integration with SQLite", "[SqlBackup][Integration]") BatchManager::BatchExecutor executor = [&](std::vector const& rawCols, size_t rowCount) { std::string sql = "INSERT INTO test_batch (id, txt) VALUES (?, ?)"; stmt.Prepare(sql); - stmt.ExecuteBatch(rawCols, rowCount); + (void) stmt.ExecuteBatch(rawCols, rowCount); }; BatchManager bm(executor, cols, 10); diff --git a/src/tests/SqlBackup/Benchmarks.cpp b/src/tests/SqlBackup/Benchmarks.cpp index df3f9fa3..bc58de27 100644 --- a/src/tests/SqlBackup/Benchmarks.cpp +++ b/src/tests/SqlBackup/Benchmarks.cpp @@ -59,12 +59,12 @@ void SetupBenchmarkDatabase(size_t rows) conn.Connect(SqlConnection::DefaultConnectionString()); SqlStatement stmt { conn }; - stmt.ExecuteDirect("DROP TABLE IF EXISTS bench_table"); + (void) stmt.ExecuteDirect("DROP TABLE IF EXISTS bench_table"); // Create a table with mix of types to stress packed formats std::string const idType = conn.ServerType() == SqlServerType::MICROSOFT_SQL ? "INT IDENTITY(1,1) PRIMARY KEY" : "INTEGER PRIMARY KEY"; - stmt.ExecuteDirect(std::format(R"( + (void) stmt.ExecuteDirect(std::format(R"( CREATE TABLE bench_table ( id {}, val_int INTEGER, @@ -72,9 +72,9 @@ void SetupBenchmarkDatabase(size_t rows) val_text VARCHAR(100) ) )", - idType)); + idType)); - stmt.ExecuteDirect("BEGIN TRANSACTION"); + (void) stmt.ExecuteDirect("BEGIN TRANSACTION"); stmt.Prepare("INSERT INTO bench_table (val_int, val_real, val_text) VALUES (?, ?, ?)"); std::mt19937 gen(42); // fixed seed @@ -83,9 +83,9 @@ void SetupBenchmarkDatabase(size_t rows) for (size_t i = 0; i < rows; ++i) { - stmt.Execute(distInt(gen), distReal(gen), std::format("Row {}", i)); + (void) stmt.Execute(distInt(gen), distReal(gen), std::format("Row {}", i)); } - stmt.ExecuteDirect("COMMIT"); + (void) stmt.ExecuteDirect("COMMIT"); } } // namespace @@ -119,7 +119,7 @@ TEST_CASE("SqlBackup Performance", "[.][Benchmark]") { SqlConnection conn; conn.Connect(SqlConnection::DefaultConnectionString()); - SqlStatement { conn }.ExecuteDirect("DROP TABLE bench_table"); + (void) SqlStatement { conn }.ExecuteDirect("DROP TABLE bench_table"); } SqlBackup::Restore(BackupFile, SqlConnection::DefaultConnectionString(), 1, pm); }; diff --git a/src/tests/SqlBackup/MultiPkTests.cpp b/src/tests/SqlBackup/MultiPkTests.cpp index 155a847d..d1cf70a8 100644 --- a/src/tests/SqlBackup/MultiPkTests.cpp +++ b/src/tests/SqlBackup/MultiPkTests.cpp @@ -67,9 +67,9 @@ TEST_CASE("SqlBackup: Composite Primary Key Order", "[SqlBackup][MultiPk]") SqlConnection conn; conn.Connect(SqlConnection::DefaultConnectionString()); SqlStatement stmt { conn }; - stmt.ExecuteDirect("DROP TABLE IF EXISTS multipk"); - stmt.ExecuteDirect("CREATE TABLE multipk (a INTEGER, b INTEGER, content VARCHAR(100), PRIMARY KEY (b, a))"); - stmt.ExecuteDirect("INSERT INTO multipk (a, b, content) VALUES (1, 2, 'row1')"); + (void) stmt.ExecuteDirect("DROP TABLE IF EXISTS multipk"); + (void) stmt.ExecuteDirect("CREATE TABLE multipk (a INTEGER, b INTEGER, content VARCHAR(100), PRIMARY KEY (b, a))"); + (void) stmt.ExecuteDirect("INSERT INTO multipk (a, b, content) VALUES (1, 2, 'row1')"); } // 2. Backup @@ -82,7 +82,7 @@ TEST_CASE("SqlBackup: Composite Primary Key Order", "[SqlBackup][MultiPk]") { SqlConnection conn; conn.Connect(SqlConnection::DefaultConnectionString()); - SqlStatement { conn }.ExecuteDirect("DROP TABLE multipk"); + (void) SqlStatement { conn }.ExecuteDirect("DROP TABLE multipk"); SilentProgressManager pm; SqlBackup::Restore(BackupFile, SqlConnection::DefaultConnectionString(), 1, pm); @@ -98,22 +98,22 @@ TEST_CASE("SqlBackup: Composite Primary Key Order", "[SqlBackup][MultiPk]") if (conn.ServerType() == SqlServerType::MICROSOFT_SQL) { - stmt.ExecuteDirect(R"( + auto cursor = stmt.ExecuteDirect(R"( SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE OBJECTPROPERTY(OBJECT_ID(CONSTRAINT_SCHEMA + '.' + CONSTRAINT_NAME), 'IsPrimaryKey') = 1 AND TABLE_NAME = 'multipk' ORDER BY ORDINAL_POSITION )"); - while (stmt.FetchRow()) + while (cursor.FetchRow()) { - pkColumns.push_back(stmt.GetColumn(1)); + pkColumns.push_back(cursor.GetColumn(1)); } } else if (conn.ServerType() == SqlServerType::POSTGRESQL) { // PostgreSQL: Query primary key columns from information_schema - stmt.ExecuteDirect(R"( + auto cursor = stmt.ExecuteDirect(R"( SELECT kcu.column_name FROM information_schema.table_constraints tc JOIN information_schema.key_column_usage kcu @@ -123,21 +123,21 @@ TEST_CASE("SqlBackup: Composite Primary Key Order", "[SqlBackup][MultiPk]") AND tc.table_name = 'multipk' ORDER BY kcu.ordinal_position )"); - while (stmt.FetchRow()) + while (cursor.FetchRow()) { - pkColumns.push_back(stmt.GetColumn(1)); + pkColumns.push_back(cursor.GetColumn(1)); } } else { // SQLite // PRAGMA table_info returns columns. - stmt.ExecuteDirect("PRAGMA table_info(multipk)"); + auto cursor = stmt.ExecuteDirect("PRAGMA table_info(multipk)"); std::vector> explicitPks; - while (stmt.FetchRow()) + while (cursor.FetchRow()) { - auto name = stmt.GetColumn(2); - auto pk = stmt.GetColumn(6); + auto name = cursor.GetColumn(2); + auto pk = cursor.GetColumn(6); if (pk > 0) { explicitPks.emplace_back(pk, name); diff --git a/src/tests/SqlBackup/ProductionReadinessTests.cpp b/src/tests/SqlBackup/ProductionReadinessTests.cpp index 4e53ec1a..b1bfbfa6 100644 --- a/src/tests/SqlBackup/ProductionReadinessTests.cpp +++ b/src/tests/SqlBackup/ProductionReadinessTests.cpp @@ -95,9 +95,9 @@ void SetupTestTable() }); stmt.Prepare("INSERT INTO prod_test_table (id, name) VALUES (?, ?)"); - stmt.Execute(1, "Alice"); - stmt.Execute(2, "Bob"); - stmt.Execute(3, "Charlie"); + (void) stmt.Execute(1, "Alice"); + (void) stmt.Execute(2, "Bob"); + (void) stmt.Execute(3, "Charlie"); } } // namespace @@ -235,9 +235,9 @@ TEST_CASE("SqlBackup: Filter tables in restore", "[SqlBackup][ProductionReadines migration.CreateTable("orders_table").PrimaryKey("id", Integer {}).Column("amount", Integer {}); }); - stmt.ExecuteDirect("INSERT INTO users_table (id, name) VALUES (1, 'User1')"); - stmt.ExecuteDirect("INSERT INTO products_table (id, name) VALUES (1, 'Product1')"); - stmt.ExecuteDirect("INSERT INTO orders_table (id, amount) VALUES (1, 100)"); + (void) stmt.ExecuteDirect("INSERT INTO users_table (id, name) VALUES (1, 'User1')"); + (void) stmt.ExecuteDirect("INSERT INTO products_table (id, name) VALUES (1, 'Product1')"); + (void) stmt.ExecuteDirect("INSERT INTO orders_table (id, amount) VALUES (1, 100)"); } // Backup all tables diff --git a/src/tests/SqlBackup/RobustnessTests.cpp b/src/tests/SqlBackup/RobustnessTests.cpp index 45e039c9..add612ea 100644 --- a/src/tests/SqlBackup/RobustnessTests.cpp +++ b/src/tests/SqlBackup/RobustnessTests.cpp @@ -102,10 +102,10 @@ TEST_CASE("SqlBackup: Foreign Key Restoration", "[SqlBackup]") // Insert data with explicit IDs stmt.Prepare("INSERT INTO parent (id, name) VALUES (?, ?)"); - stmt.Execute(1, "Parent1"); + (void) stmt.Execute(1, "Parent1"); stmt.Prepare("INSERT INTO child (id, parent_id) VALUES (?, ?)"); - stmt.Execute(10, 1); + (void) stmt.Execute(10, 1); } // 2. Backup @@ -154,7 +154,7 @@ TEST_CASE("SqlBackup: Foreign Key Restoration", "[SqlBackup]") bool constraintFailed = false; try { - stmt.ExecuteDirect("INSERT INTO child (id, parent_id) VALUES (11, 999)"); + (void) stmt.ExecuteDirect("INSERT INTO child (id, parent_id) VALUES (11, 999)"); } catch (...) { @@ -202,7 +202,7 @@ TEST_CASE("SqlBackup: Robustness Types and Nulls", "[SqlBackup]") bin.resize(1); bin.data()[0] = 0x01; stmt.Prepare("INSERT INTO types (id, b, i, d, t, bin) VALUES (?, ?, ?, ?, ?, ?)"); - stmt.Execute(1, true, 123, 12.34, "Hello", bin); + (void) stmt.Execute(1, true, 123, 12.34, "Hello", bin); } // Row 2: Zeros / Empty / False @@ -210,12 +210,12 @@ TEST_CASE("SqlBackup: Robustness Types and Nulls", "[SqlBackup]") { SqlDynamicBinary<100> bin; // empty stmt.Prepare("INSERT INTO types (id, b, i, d, t, bin) VALUES (?, ?, ?, ?, ?, ?)"); - stmt.Execute(2, false, 0, 0.0, "", bin); + (void) stmt.Execute(2, false, 0, 0.0, "", bin); } // Row 3 & 4: Nulls and special string "NULL" - stmt.ExecuteDirect("INSERT INTO types (id) VALUES (3)"); - stmt.ExecuteDirect("INSERT INTO types (id, t) VALUES (4, 'NULL')"); + (void) stmt.ExecuteDirect("INSERT INTO types (id) VALUES (3)"); + (void) stmt.ExecuteDirect("INSERT INTO types (id, t) VALUES (4, 'NULL')"); } // 2. Backup @@ -319,7 +319,7 @@ TEST_CASE("SqlBackup: Constraints and Defaults", "[SqlBackup]") }); }); - stmt.ExecuteDirect("INSERT INTO constraints (id, u) VALUES (1, 'A')"); // d should be 42 + (void) stmt.ExecuteDirect("INSERT INTO constraints (id, u) VALUES (1, 'A')"); // d should be 42 } // 2. Backup @@ -353,13 +353,13 @@ TEST_CASE("SqlBackup: Constraints and Defaults", "[SqlBackup]") REQUIRE(stmt.ExecuteDirectScalar("SELECT d FROM constraints WHERE u='A'") == 42); // Verify Default Value behavior on NEW insert - stmt.ExecuteDirect("INSERT INTO constraints (id, u) VALUES (2, 'B')"); + (void) stmt.ExecuteDirect("INSERT INTO constraints (id, u) VALUES (2, 'B')"); // If Default Constraint was lost, d would be NULL (or 0) REQUIRE(stmt.ExecuteDirectScalar("SELECT d FROM constraints WHERE u='B'") == 42); // Verify Unique Constraint // If Unique Constraint was lost, duplicate insert would succeed auto const _ = ScopedSqlNullLogger {}; - REQUIRE_THROWS_AS(stmt.ExecuteDirect("INSERT INTO constraints (u) VALUES ('A')"), SqlException); + REQUIRE_THROWS_AS((void) stmt.ExecuteDirect("INSERT INTO constraints (u) VALUES ('A')"), SqlException); } } diff --git a/src/tests/SqlBackup/SqlBackupCompositeFKTests.cpp b/src/tests/SqlBackup/SqlBackupCompositeFKTests.cpp index 8a212b1e..e9bb1033 100644 --- a/src/tests/SqlBackup/SqlBackupCompositeFKTests.cpp +++ b/src/tests/SqlBackup/SqlBackupCompositeFKTests.cpp @@ -106,7 +106,7 @@ TEST_CASE("SqlBackup: Composite Foreign Keys", "[SqlBackup][ForeignKeys]") WARN("DEBUG: Creating Parents table..."); // MSSQL supports TEXT but VARCHAR(MAX) is preferred. Using TEXT for compatibility with existing code if // possible. But let's stick to standard types. - stmt.ExecuteDirect("CREATE TABLE Parents (" + (void) stmt.ExecuteDirect("CREATE TABLE Parents (" " p1 INT," " p2 INT," " info VARCHAR(100)," @@ -114,7 +114,7 @@ TEST_CASE("SqlBackup: Composite Foreign Keys", "[SqlBackup][ForeignKeys]") ")"); WARN("DEBUG: Creating Children table..."); - stmt.ExecuteDirect("CREATE TABLE Children (" + (void) stmt.ExecuteDirect("CREATE TABLE Children (" " c1 INT PRIMARY KEY," " p1 INT," " p2 INT," @@ -123,10 +123,10 @@ TEST_CASE("SqlBackup: Composite Foreign Keys", "[SqlBackup][ForeignKeys]") ")"); WARN("DEBUG: Inserting data..."); - stmt.ExecuteDirect("INSERT INTO Parents (p1, p2, info) VALUES (1, 1, 'Mom')"); - stmt.ExecuteDirect("INSERT INTO Parents (p1, p2, info) VALUES (1, 2, 'Dad')"); - stmt.ExecuteDirect("INSERT INTO Children (c1, p1, p2, extra) VALUES (100, 1, 1, 'Child1')"); - stmt.ExecuteDirect("INSERT INTO Children (c1, p1, p2, extra) VALUES (101, 1, 2, 'Child2')"); + (void) stmt.ExecuteDirect("INSERT INTO Parents (p1, p2, info) VALUES (1, 1, 'Mom')"); + (void) stmt.ExecuteDirect("INSERT INTO Parents (p1, p2, info) VALUES (1, 2, 'Dad')"); + (void) stmt.ExecuteDirect("INSERT INTO Children (c1, p1, p2, extra) VALUES (100, 1, 1, 'Child1')"); + (void) stmt.ExecuteDirect("INSERT INTO Children (c1, p1, p2, extra) VALUES (101, 1, 2, 'Child2')"); } catch (std::exception const& e) { diff --git a/src/tests/SqlBackup/SqlBackupIndexTests.cpp b/src/tests/SqlBackup/SqlBackupIndexTests.cpp index c165d15a..98c16fa6 100644 --- a/src/tests/SqlBackup/SqlBackupIndexTests.cpp +++ b/src/tests/SqlBackup/SqlBackupIndexTests.cpp @@ -73,25 +73,25 @@ std::vector GetSqliteIndexes(SqlStatement& stmt, std::string const& t { std::vector indexes; - stmt.ExecuteDirect(std::format("SELECT name, \"unique\" FROM pragma_index_list('{}')", tableName)); - std::vector> indexList; - while (stmt.FetchRow()) { - auto name = stmt.GetColumn(1); - auto isUnique = stmt.GetColumn(2) != 0; - if (!name.starts_with("sqlite_autoindex_")) - indexList.emplace_back(name, isUnique); - } - stmt.CloseCursor(); + auto cursor = stmt.ExecuteDirect(std::format("SELECT name, \"unique\" FROM pragma_index_list('{}')", tableName)); + std::vector> indexList; + while (cursor.FetchRow()) + { + auto name = cursor.GetColumn(1); + auto isUnique = cursor.GetColumn(2) != 0; + if (!name.starts_with("sqlite_autoindex_")) + indexList.emplace_back(name, isUnique); + } - for (auto const& [indexName, isUnique]: indexList) - { - IndexInfo info { .name = indexName, .columns = {}, .isUnique = isUnique }; - stmt.ExecuteDirect(std::format("SELECT name FROM pragma_index_info('{}')", indexName)); - while (stmt.FetchRow()) - info.columns.push_back(stmt.GetColumn(1)); - stmt.CloseCursor(); - indexes.push_back(std::move(info)); + for (auto const& [indexName, isUnique]: indexList) + { + IndexInfo info { .name = indexName, .columns = {}, .isUnique = isUnique }; + auto cursor2 = stmt.ExecuteDirect(std::format("SELECT name FROM pragma_index_info('{}')", indexName)); + while (cursor2.FetchRow()) + info.columns.push_back(cursor2.GetColumn(1)); + indexes.push_back(std::move(info)); + } } return indexes; @@ -104,7 +104,7 @@ std::vector GetMssqlIndexes(SqlStatement& stmt, std::string const& ta // Use schema-qualified name for OBJECT_ID if schema is provided std::string qualifiedName = schema.empty() ? tableName : std::format("{}.{}", schema, tableName); - stmt.ExecuteDirect(std::format( + auto cursor = stmt.ExecuteDirect(std::format( R"(SELECT i.name, i.is_unique, c.name as column_name FROM sys.indexes i INNER JOIN sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id @@ -117,11 +117,11 @@ std::vector GetMssqlIndexes(SqlStatement& stmt, std::string const& ta std::string currentIndex; IndexInfo currentInfo; - while (stmt.FetchRow()) + while (cursor.FetchRow()) { - auto indexName = stmt.GetColumn(1); - auto isUnique = stmt.GetColumn(2) != 0; - auto columnName = stmt.GetColumn(3); + auto indexName = cursor.GetColumn(1); + auto isUnique = cursor.GetColumn(2) != 0; + auto columnName = cursor.GetColumn(3); if (indexName != currentIndex) { @@ -134,7 +134,6 @@ std::vector GetMssqlIndexes(SqlStatement& stmt, std::string const& ta } if (!currentIndex.empty()) indexes.push_back(std::move(currentInfo)); - stmt.CloseCursor(); return indexes; } @@ -147,15 +146,15 @@ std::vector GetPostgresIndexes(SqlStatement& stmt, std::string const& std::string lowerTableName = tableName; std::ranges::transform(lowerTableName, lowerTableName.begin(), [](unsigned char c) { return std::tolower(c); }); - stmt.ExecuteDirect( + auto cursor = stmt.ExecuteDirect( std::format(R"(SELECT indexname, indexdef FROM pg_indexes WHERE tablename = '{}' AND indexname NOT LIKE '%_pkey')", lowerTableName)); - while (stmt.FetchRow()) + while (cursor.FetchRow()) { IndexInfo info; - info.name = stmt.GetColumn(1); - auto indexDef = stmt.GetColumn(2); + info.name = cursor.GetColumn(1); + auto indexDef = cursor.GetColumn(2); info.isUnique = indexDef.find("UNIQUE") != std::string::npos; // Parse columns from CREATE INDEX ... ON table (col1, col2) @@ -180,7 +179,6 @@ std::vector GetPostgresIndexes(SqlStatement& stmt, std::string const& } indexes.push_back(std::move(info)); } - stmt.CloseCursor(); return indexes; } @@ -236,7 +234,7 @@ TEST_CASE("SqlBackup: Index Restoration", "[SqlBackup][Indexes]") SqlStatement stmt { conn }; try { - stmt.ExecuteDirect(R"(CREATE TABLE TestIndexes ( + (void) stmt.ExecuteDirect(R"(CREATE TABLE TestIndexes ( id INT PRIMARY KEY, name VARCHAR(100), email VARCHAR(200), @@ -244,13 +242,13 @@ TEST_CASE("SqlBackup: Index Restoration", "[SqlBackup][Indexes]") score INT ))"); - stmt.ExecuteDirect(R"(CREATE INDEX idx_name ON TestIndexes (name))"); - stmt.ExecuteDirect(R"(CREATE UNIQUE INDEX idx_email ON TestIndexes (email))"); - stmt.ExecuteDirect(R"(CREATE INDEX idx_category_score ON TestIndexes (category, score))"); + (void) stmt.ExecuteDirect(R"(CREATE INDEX idx_name ON TestIndexes (name))"); + (void) stmt.ExecuteDirect(R"(CREATE UNIQUE INDEX idx_email ON TestIndexes (email))"); + (void) stmt.ExecuteDirect(R"(CREATE INDEX idx_category_score ON TestIndexes (category, score))"); - stmt.ExecuteDirect("INSERT INTO TestIndexes VALUES (1, 'Alice', 'alice@test.com', 1, 100)"); - stmt.ExecuteDirect("INSERT INTO TestIndexes VALUES (2, 'Bob', 'bob@test.com', 1, 95)"); - stmt.ExecuteDirect("INSERT INTO TestIndexes VALUES (3, 'Charlie', 'charlie@test.com', 2, 88)"); + (void) stmt.ExecuteDirect("INSERT INTO TestIndexes VALUES (1, 'Alice', 'alice@test.com', 1, 100)"); + (void) stmt.ExecuteDirect("INSERT INTO TestIndexes VALUES (2, 'Bob', 'bob@test.com', 1, 95)"); + (void) stmt.ExecuteDirect("INSERT INTO TestIndexes VALUES (3, 'Charlie', 'charlie@test.com', 2, 88)"); } catch (std::exception const& e) { @@ -352,7 +350,7 @@ TEST_CASE("SqlBackup: Index Restoration", "[SqlBackup][Indexes]") bool uniqueConstraintWorks = false; try { - stmt.ExecuteDirect("INSERT INTO TestIndexes VALUES (4, 'Duplicate', 'alice@test.com', 3, 50)"); + (void) stmt.ExecuteDirect("INSERT INTO TestIndexes VALUES (4, 'Duplicate', 'alice@test.com', 3, 50)"); } catch (SqlException const&) { diff --git a/src/tests/SqlBackup/Tests.cpp b/src/tests/SqlBackup/Tests.cpp index b36984dc..f11684fb 100644 --- a/src/tests/SqlBackup/Tests.cpp +++ b/src/tests/SqlBackup/Tests.cpp @@ -68,9 +68,9 @@ void SetupDatabase() // Insert data with explicit IDs stmt.Prepare("INSERT INTO test_table (id, content) VALUES (?, ?)"); - stmt.Execute(1, "row1"); - stmt.Execute(2, "row2"); - stmt.Execute(3, "quoted \"content\""); + (void) stmt.Execute(1, "row1"); + (void) stmt.Execute(2, "row2"); + (void) stmt.Execute(3, "quoted \"content\""); } void VerifyDatabase() @@ -162,8 +162,8 @@ void SetupComplexDatabase() uint8_t const rawBinary[] = { 0x01, 0x00, 0xFF, 0x10 }; SqlDynamicBinary<1024> binaryData { rawBinary }; stmt.Prepare("INSERT INTO complex_table (id, b, c, vc, content) VALUES (?, ?, ?, ?, ?)"); - stmt.Execute(1, binaryData, "HI", "Varchar value", - u"Unicode \U0001F601"); // Emoji Grinning Face, U+1F601 + (void) stmt.Execute(1, binaryData, "HI", "Varchar value", + u"Unicode \U0001F601"); // Emoji Grinning Face, U+1F601 } void VerifyComplexDatabase() @@ -328,13 +328,13 @@ TEST_CASE("SqlBackup: Corner Cases", "[SqlBackup]") }); // Single quote - stmt.ExecuteDirect("INSERT INTO corner_cases (id, txt) VALUES (1, 'L''Abbaye')"); + (void) stmt.ExecuteDirect("INSERT INTO corner_cases (id, txt) VALUES (1, 'L''Abbaye')"); // Newline - stmt.ExecuteDirect("INSERT INTO corner_cases (id, txt) VALUES (2, 'Line1\nLine2')"); + (void) stmt.ExecuteDirect("INSERT INTO corner_cases (id, txt) VALUES (2, 'Line1\nLine2')"); // Quotes and commas - stmt.ExecuteDirect("INSERT INTO corner_cases (id, txt) VALUES (3, '\"quoted\", comma')"); + (void) stmt.ExecuteDirect("INSERT INTO corner_cases (id, txt) VALUES (3, '\"quoted\", comma')"); // Empty string - stmt.ExecuteDirect("INSERT INTO corner_cases (id, txt) VALUES (4, '')"); + (void) stmt.ExecuteDirect("INSERT INTO corner_cases (id, txt) VALUES (4, '')"); }; setup(); @@ -400,13 +400,13 @@ TEST_CASE("SqlBackup: Concurrent Restore", "[SqlBackup]") migration.CreateTable("large_table").PrimaryKey("id", Integer {}).Column("data", Varchar { 100 }); }); - stmt.ExecuteDirect("BEGIN TRANSACTION"); + (void) stmt.ExecuteDirect("BEGIN TRANSACTION"); stmt.Prepare("INSERT INTO large_table (id, data) VALUES (?, ?)"); for (int i = 0; i < 1000; ++i) { - stmt.Execute(i + 1, std::format("Row {}", i)); + (void) stmt.Execute(i + 1, std::format("Row {}", i)); } - stmt.ExecuteDirect("COMMIT"); + (void) stmt.ExecuteDirect("COMMIT"); } // 2. Backup (concurrency 4) @@ -497,7 +497,7 @@ TEST_CASE("SqlBackup: Table With Spaces", "[SqlBackup]") migration.CreateTable("Order Details").PrimaryKey("id", Integer {}).Column("product_name", Varchar { 100 }); }); - stmt.ExecuteDirect(R"(INSERT INTO "Order Details" ("id", "product_name") VALUES (1, 'Tofu'))"); + (void) stmt.ExecuteDirect(R"(INSERT INTO "Order Details" ("id", "product_name") VALUES (1, 'Tofu'))"); } auto progress = SqlBackup::Progress {}; @@ -571,14 +571,14 @@ TEST_CASE("SqlBackup: DateTime Columns", "[SqlBackup]") auto const datetime1 = SqlDateTime { year { 2024 }, month { 6 }, day { 15 }, hours { 14 }, minutes { 30 }, seconds { 45 } }; - stmt.Execute(1, "First Record", datetime1); + (void) stmt.Execute(1, "First Record", datetime1); auto const datetime2 = SqlDateTime { year { 1999 }, month { 12 }, day { 31 }, hours { 23 }, minutes { 59 }, seconds { 59 } }; - stmt.Execute(2, "Second Record", datetime2); + (void) stmt.Execute(2, "Second Record", datetime2); // Test NULL datetime - stmt.Execute(3, "Null DateTime", std::optional {}); + (void) stmt.Execute(3, "Null DateTime", std::optional {}); } // Backup @@ -677,13 +677,13 @@ TEST_CASE("SqlBackup: Date Columns", "[SqlBackup]") using namespace std::chrono; auto const date1 = SqlDate { year { 2024 }, month { 6 }, day { 15 } }; - stmt.Execute(1, "First Record", date1); + (void) stmt.Execute(1, "First Record", date1); auto const date2 = SqlDate { year { 1999 }, month { 12 }, day { 31 } }; - stmt.Execute(2, "Second Record", date2); + (void) stmt.Execute(2, "Second Record", date2); // Test NULL date - stmt.Execute(3, "Null Date", std::optional {}); + (void) stmt.Execute(3, "Null Date", std::optional {}); } // Backup @@ -777,24 +777,24 @@ TEST_CASE("SqlBackup: Time Columns", "[SqlBackup]") if (conn.ServerType() == SqlServerType::MICROSOFT_SQL) { // Insert using string literals to preserve fractional seconds - stmt.ExecuteDirect( + (void) stmt.ExecuteDirect( "INSERT INTO time_backup_test (id, name, event_time) VALUES (1, 'First Record', '14:30:45.123456')"); - stmt.ExecuteDirect( + (void) stmt.ExecuteDirect( "INSERT INTO time_backup_test (id, name, event_time) VALUES (2, 'Second Record', '23:59:59.000000')"); - stmt.ExecuteDirect("INSERT INTO time_backup_test (id, name, event_time) VALUES (3, 'Null Time', NULL)"); - stmt.ExecuteDirect( + (void) stmt.ExecuteDirect("INSERT INTO time_backup_test (id, name, event_time) VALUES (3, 'Null Time', NULL)"); + (void) stmt.ExecuteDirect( "INSERT INTO time_backup_test (id, name, event_time) VALUES (4, 'Midnight', '00:00:00.000000')"); } else if (conn.ServerType() == SqlServerType::POSTGRESQL) { // PostgreSQL: Use string literals to preserve microseconds // (SqlTime binding uses SQL_C_TYPE_TIME which loses fractional seconds) - stmt.ExecuteDirect( + (void) stmt.ExecuteDirect( "INSERT INTO time_backup_test (id, name, event_time) VALUES (1, 'First Record', '14:30:45.123456')"); - stmt.ExecuteDirect( + (void) stmt.ExecuteDirect( "INSERT INTO time_backup_test (id, name, event_time) VALUES (2, 'Second Record', '23:59:59')"); - stmt.ExecuteDirect("INSERT INTO time_backup_test (id, name, event_time) VALUES (3, 'Null Time', NULL)"); - stmt.ExecuteDirect("INSERT INTO time_backup_test (id, name, event_time) VALUES (4, 'Midnight', '00:00:00')"); + (void) stmt.ExecuteDirect("INSERT INTO time_backup_test (id, name, event_time) VALUES (3, 'Null Time', NULL)"); + (void) stmt.ExecuteDirect("INSERT INTO time_backup_test (id, name, event_time) VALUES (4, 'Midnight', '00:00:00')"); } else { @@ -804,17 +804,17 @@ TEST_CASE("SqlBackup: Time Columns", "[SqlBackup]") using namespace std::chrono; auto const time1 = SqlTime { hours { 14 }, minutes { 30 }, seconds { 45 }, microseconds { 123456 } }; - stmt.Execute(1, "First Record", time1); + (void) stmt.Execute(1, "First Record", time1); auto const time2 = SqlTime { hours { 23 }, minutes { 59 }, seconds { 59 }, microseconds { 0 } }; - stmt.Execute(2, "Second Record", time2); + (void) stmt.Execute(2, "Second Record", time2); // Test NULL time - stmt.Execute(3, "Null Time", std::optional {}); + (void) stmt.Execute(3, "Null Time", std::optional {}); // Test midnight auto const time4 = SqlTime { hours { 0 }, minutes { 0 }, seconds { 0 }, microseconds { 0 } }; - stmt.Execute(4, "Midnight", time4); + (void) stmt.Execute(4, "Midnight", time4); } } @@ -952,33 +952,33 @@ TEST_CASE("SqlBackup: Decimal Precision", "[SqlBackup]") // (MSSQL parses numeric literals as floats, which loses precision for large numbers) if (conn.ServerType() == SqlServerType::MICROSOFT_SQL) { - stmt.ExecuteDirect("INSERT INTO decimal_backup_test (id, name, amount) VALUES " - "(1, 'Small', CAST('123.4567890123' AS DECIMAL(28,10)))"); - stmt.ExecuteDirect("INSERT INTO decimal_backup_test (id, name, amount) VALUES " - "(2, 'Large', CAST('123456789012345678.1234567890' AS DECIMAL(28,10)))"); - stmt.ExecuteDirect("INSERT INTO decimal_backup_test (id, name, amount) VALUES " - "(3, 'Tiny', CAST('0.0000000001' AS DECIMAL(28,10)))"); - stmt.ExecuteDirect("INSERT INTO decimal_backup_test (id, name, amount) VALUES " - "(4, 'Null', NULL)"); - stmt.ExecuteDirect("INSERT INTO decimal_backup_test (id, name, amount) VALUES " - "(5, 'Zero', CAST('0.0000000000' AS DECIMAL(28,10)))"); - stmt.ExecuteDirect("INSERT INTO decimal_backup_test (id, name, amount) VALUES " - "(6, 'Negative', CAST('-99999999999999.9999999999' AS DECIMAL(28,10)))"); + (void) stmt.ExecuteDirect("INSERT INTO decimal_backup_test (id, name, amount) VALUES " + "(1, 'Small', CAST('123.4567890123' AS DECIMAL(28,10)))"); + (void) stmt.ExecuteDirect("INSERT INTO decimal_backup_test (id, name, amount) VALUES " + "(2, 'Large', CAST('123456789012345678.1234567890' AS DECIMAL(28,10)))"); + (void) stmt.ExecuteDirect("INSERT INTO decimal_backup_test (id, name, amount) VALUES " + "(3, 'Tiny', CAST('0.0000000001' AS DECIMAL(28,10)))"); + (void) stmt.ExecuteDirect("INSERT INTO decimal_backup_test (id, name, amount) VALUES " + "(4, 'Null', NULL)"); + (void) stmt.ExecuteDirect("INSERT INTO decimal_backup_test (id, name, amount) VALUES " + "(5, 'Zero', CAST('0.0000000000' AS DECIMAL(28,10)))"); + (void) stmt.ExecuteDirect("INSERT INTO decimal_backup_test (id, name, amount) VALUES " + "(6, 'Negative', CAST('-99999999999999.9999999999' AS DECIMAL(28,10)))"); } else { - stmt.ExecuteDirect("INSERT INTO decimal_backup_test (id, name, amount) VALUES " - "(1, 'Small', 123.4567890123)"); - stmt.ExecuteDirect("INSERT INTO decimal_backup_test (id, name, amount) VALUES " - "(2, 'Large', 123456789012345678.1234567890)"); - stmt.ExecuteDirect("INSERT INTO decimal_backup_test (id, name, amount) VALUES " - "(3, 'Tiny', 0.0000000001)"); - stmt.ExecuteDirect("INSERT INTO decimal_backup_test (id, name, amount) VALUES " - "(4, 'Null', NULL)"); - stmt.ExecuteDirect("INSERT INTO decimal_backup_test (id, name, amount) VALUES " - "(5, 'Zero', 0.0000000000)"); - stmt.ExecuteDirect("INSERT INTO decimal_backup_test (id, name, amount) VALUES " - "(6, 'Negative', -99999999999999.9999999999)"); + (void) stmt.ExecuteDirect("INSERT INTO decimal_backup_test (id, name, amount) VALUES " + "(1, 'Small', 123.4567890123)"); + (void) stmt.ExecuteDirect("INSERT INTO decimal_backup_test (id, name, amount) VALUES " + "(2, 'Large', 123456789012345678.1234567890)"); + (void) stmt.ExecuteDirect("INSERT INTO decimal_backup_test (id, name, amount) VALUES " + "(3, 'Tiny', 0.0000000001)"); + (void) stmt.ExecuteDirect("INSERT INTO decimal_backup_test (id, name, amount) VALUES " + "(4, 'Null', NULL)"); + (void) stmt.ExecuteDirect("INSERT INTO decimal_backup_test (id, name, amount) VALUES " + "(5, 'Zero', 0.0000000000)"); + (void) stmt.ExecuteDirect("INSERT INTO decimal_backup_test (id, name, amount) VALUES " + "(6, 'Negative', -99999999999999.9999999999)"); } } @@ -1151,7 +1151,7 @@ TEST_CASE("SqlBackup: Backup with Custom Compression", "[SqlBackup]") stmt.Prepare("INSERT INTO compression_test (id, data) VALUES (?, ?)"); for (int i = 1; i <= 10; ++i) - stmt.Execute(i, std::format("Row data {}", i)); + (void) stmt.Execute(i, std::format("Row data {}", i)); } LambdaProgressManager pm { [](auto&&) {} }; @@ -1205,9 +1205,9 @@ TEST_CASE("SqlBackup: Backup with Table Filter", "[SqlBackup]") migration.CreateTable("other_table").PrimaryKey("id", Integer {}).Column("data", Varchar { 50 }); }); - stmt.ExecuteDirect("INSERT INTO filter_table_a (id, data) VALUES (1, 'A1')"); - stmt.ExecuteDirect("INSERT INTO filter_table_b (id, data) VALUES (1, 'B1')"); - stmt.ExecuteDirect("INSERT INTO other_table (id, data) VALUES (1, 'O1')"); + (void) stmt.ExecuteDirect("INSERT INTO filter_table_a (id, data) VALUES (1, 'A1')"); + (void) stmt.ExecuteDirect("INSERT INTO filter_table_b (id, data) VALUES (1, 'B1')"); + (void) stmt.ExecuteDirect("INSERT INTO other_table (id, data) VALUES (1, 'O1')"); } LambdaProgressManager pm { [](auto&&) {} }; @@ -1549,7 +1549,7 @@ TEST_CASE("SqlBackup: Restore rejects unsupported format version", "[SqlBackup]" stmt.MigrateDirect([](SqlMigrationQueryBuilder& migration) { migration.CreateTable("version_test_table").Column("id", SqlColumnTypeDefinitions::Integer {}); }); - stmt.ExecuteDirect("INSERT INTO version_test_table (id) VALUES (1)"); + (void) stmt.ExecuteDirect("INSERT INTO version_test_table (id) VALUES (1)"); } SqlBackup::NullProgressManager pm; diff --git a/src/tests/Utils.hpp b/src/tests/Utils.hpp index 90d05e52..a866b8bc 100644 --- a/src/tests/Utils.hpp +++ b/src/tests/Utils.hpp @@ -511,7 +511,7 @@ class SqlTestFixture case SqlServerType::SQLITE: { auto stmt = Lightweight::SqlStatement { connection }; // Enable foreign key constraints for SQLite - stmt.ExecuteDirect("PRAGMA foreign_keys = ON"); + (void) stmt.ExecuteDirect("PRAGMA foreign_keys = ON"); break; } case SqlServerType::MICROSOFT_SQL: @@ -561,7 +561,7 @@ class SqlTestFixture auto const dependantTables = Lightweight::SqlSchema::AllForeignKeysTo(stmt, table); for (auto const& dependantTable: dependantTables) DropTableRecursively(stmt, dependantTable.foreignKey.table); - stmt.ExecuteDirect(std::format("DROP TABLE IF EXISTS \"{}\"", table.table)); + (void) stmt.ExecuteDirect(std::format("DROP TABLE IF EXISTS \"{}\"", table.table)); } static void DropTableIfExists(Lightweight::SqlConnection& conn, std::string const& tableName) @@ -569,7 +569,7 @@ class SqlTestFixture Lightweight::SqlStatement stmt { conn }; try { - stmt.ExecuteDirect(std::format("DROP TABLE IF EXISTS {}", tableName)); + (void) stmt.ExecuteDirect(std::format("DROP TABLE IF EXISTS {}", tableName)); } catch (...) { @@ -584,7 +584,7 @@ class SqlTestFixture { case SqlServerType::MICROSOFT_SQL: case SqlServerType::MYSQL: - stmt.ExecuteDirect(std::format("USE \"{}\"", testDatabaseName)); + (void) stmt.ExecuteDirect(std::format("USE \"{}\"", testDatabaseName)); [[fallthrough]]; case SqlServerType::SQLITE: case SqlServerType::UNKNOWN: { @@ -607,7 +607,7 @@ class SqlTestFixture if (m_createdTables.empty()) m_createdTables = GetAllTableNames(stmt); for (auto& createdTable: std::views::reverse(m_createdTables)) - stmt.ExecuteDirect(std::format("DROP TABLE IF EXISTS \"{}\" CASCADE", createdTable)); + (void) stmt.ExecuteDirect(std::format("DROP TABLE IF EXISTS \"{}\" CASCADE", createdTable)); break; } m_createdTables.clear(); @@ -642,9 +642,10 @@ class SqlTestFixture SQL_NTS); if (SQL_SUCCEEDED(sqlResult)) { - while (stmt.FetchRow()) + auto cursor = Lightweight::SqlResultCursor(stmt); + while (cursor.FetchRow()) { - result.emplace_back(stmt.GetColumn(3)); // table name + result.emplace_back(cursor.GetColumn(3)); // table name } } return result; @@ -773,7 +774,7 @@ inline void EnableForeignKeysIfNeeded(Lightweight::SqlConnection& conn) if (conn.ServerType() == Lightweight::SqlServerType::SQLITE) { Lightweight::SqlStatement stmt { conn }; - stmt.ExecuteDirect("PRAGMA foreign_keys = ON"); + (void) stmt.ExecuteDirect("PRAGMA foreign_keys = ON"); } } @@ -783,7 +784,7 @@ inline void DisableForeignKeysIfNeeded(Lightweight::SqlConnection& conn) if (conn.ServerType() == Lightweight::SqlServerType::SQLITE) { Lightweight::SqlStatement stmt { conn }; - stmt.ExecuteDirect("PRAGMA foreign_keys = OFF"); + (void) stmt.ExecuteDirect("PRAGMA foreign_keys = OFF"); } } @@ -794,15 +795,15 @@ inline void WithIdentityInsert(Lightweight::SqlStatement& stmt, std::string_view { if (stmt.Connection().ServerType() == Lightweight::SqlServerType::MICROSOFT_SQL) { - stmt.ExecuteDirect(std::format("SET IDENTITY_INSERT \"{}\" ON", tableName)); + (void) stmt.ExecuteDirect(std::format("SET IDENTITY_INSERT \"{}\" ON", tableName)); try { std::forward(func)(); - stmt.ExecuteDirect(std::format("SET IDENTITY_INSERT \"{}\" OFF", tableName)); + (void) stmt.ExecuteDirect(std::format("SET IDENTITY_INSERT \"{}\" OFF", tableName)); } catch (...) { - stmt.ExecuteDirect(std::format("SET IDENTITY_INSERT \"{}\" OFF", tableName)); + (void) stmt.ExecuteDirect(std::format("SET IDENTITY_INSERT \"{}\" OFF", tableName)); throw; } } @@ -844,9 +845,9 @@ inline void FillEmployeesTable(Lightweight::SqlStatement& stmt) .Set("FirstName", Lightweight::SqlWildcard) .Set("LastName", Lightweight::SqlWildcard) .Set("Salary", Lightweight::SqlWildcard)); - stmt.Execute("Alice", "Smith", 50'000); - stmt.Execute("Bob", "Johnson", 60'000); - stmt.Execute("Charlie", "Brown", 70'000); + (void) stmt.Execute("Alice", "Smith", 50'000); + (void) stmt.Execute("Bob", "Johnson", 60'000); + (void) stmt.Execute("Charlie", "Brown", 70'000); } template diff --git a/src/tools/LupMigrationsPlugin/TransitionGlue.cpp b/src/tools/LupMigrationsPlugin/TransitionGlue.cpp index 0a4fe8e1..d62447d9 100644 --- a/src/tools/LupMigrationsPlugin/TransitionGlue.cpp +++ b/src/tools/LupMigrationsPlugin/TransitionGlue.cpp @@ -26,9 +26,9 @@ std::optional TransitionGlue::GetCurrentLupVersion(Lightweight::SqlConn Lightweight::SqlStatement stmt(connection); // Try to query the version - if table doesn't exist, this will throw - stmt.ExecuteDirect("SELECT VALUE FROM LASTRADA_PROPERTIES WHERE NR = 4"); - if (stmt.FetchRow()) - return stmt.GetColumn(1); + auto cursor = stmt.ExecuteDirect("SELECT VALUE FROM LASTRADA_PROPERTIES WHERE NR = 4"); + if (cursor.FetchRow()) + return cursor.GetColumn(1); } catch (std::exception const& /*ex*/) // NOLINT(bugprone-empty-catch) { diff --git a/src/tools/ddl2cpp.cpp b/src/tools/ddl2cpp.cpp index bd6480a8..bfcad1f3 100644 --- a/src/tools/ddl2cpp.cpp +++ b/src/tools/ddl2cpp.cpp @@ -70,7 +70,7 @@ void CreateTestTables() ); )"; auto stmt = SqlStatement(); - stmt.ExecuteDirect(std::format(createStatement, PrimaryKeyAutoIncrement(stmt.Connection().ServerType()))); + (void) stmt.ExecuteDirect(std::format(createStatement, PrimaryKeyAutoIncrement(stmt.Connection().ServerType()))); } void PostConnectedHook(SqlConnection& connection) @@ -80,7 +80,7 @@ void PostConnectedHook(SqlConnection& connection) case SqlServerType::SQLITE: { auto stmt = SqlStatement { connection }; // Enable foreign key constraints for SQLite - stmt.ExecuteDirect("PRAGMA foreign_keys = ON"); + (void) stmt.ExecuteDirect("PRAGMA foreign_keys = ON"); break; } case SqlServerType::MICROSOFT_SQL: