Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -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>(&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
Expand Down
156 changes: 56 additions & 100 deletions src/Lightweight/DataMapper/DataMapper.hpp

Large diffs are not rendered by default.

30 changes: 15 additions & 15 deletions src/Lightweight/SqlBackup/Backup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -305,7 +305,7 @@ void ProcessTableBackup(BackupContext& ctx, SqlConnection& conn, SqlSchema::Tabl
{
try
{
auto valOpt = stmt.GetNullableColumn<SqlDynamicBinary<MaxBinaryLobBufferSize>>(i);
auto valOpt = cursor.GetNullableColumn<SqlDynamicBinary<MaxBinaryLobBufferSize>>(i);
if (valOpt)
{
row.emplace_back(std::vector<uint8_t>(valOpt->data(), valOpt->data() + valOpt->size()));
Expand All @@ -325,7 +325,7 @@ void ProcessTableBackup(BackupContext& ctx, SqlConnection& conn, SqlSchema::Tabl
}
else if (std::holds_alternative<Bool>(colDef.type))
{
auto valOpt = stmt.GetNullableColumn<bool>(i);
auto valOpt = cursor.GetNullableColumn<bool>(i);
if (valOpt)
row.emplace_back(*valOpt);
else
Expand All @@ -335,23 +335,23 @@ void ProcessTableBackup(BackupContext& ctx, SqlConnection& conn, SqlSchema::Tabl
|| std::holds_alternative<Smallint>(colDef.type)
|| std::holds_alternative<Tinyint>(colDef.type))
{
auto valOpt = stmt.GetNullableColumn<int64_t>(i);
auto valOpt = cursor.GetNullableColumn<int64_t>(i);
if (valOpt)
row.emplace_back(*valOpt);
else
row.emplace_back(std::monostate {});
}
else if (std::holds_alternative<Real>(colDef.type))
{
auto valOpt = stmt.GetNullableColumn<double>(i);
auto valOpt = cursor.GetNullableColumn<double>(i);
if (valOpt)
row.emplace_back(*valOpt);
else
row.emplace_back(std::monostate {});
}
else if (std::holds_alternative<NVarchar>(colDef.type) || std::holds_alternative<NChar>(colDef.type))
{
auto valOpt = stmt.GetNullableColumn<std::u16string>(i);
auto valOpt = cursor.GetNullableColumn<std::u16string>(i);
if (valOpt)
{
auto u8 = ToUtf8(*valOpt);
Expand All @@ -363,7 +363,7 @@ void ProcessTableBackup(BackupContext& ctx, SqlConnection& conn, SqlSchema::Tabl
else if (std::holds_alternative<Guid>(colDef.type))
{
// Read GUID columns using SqlGuid for proper ODBC binding, then convert to string
auto valOpt = stmt.GetNullableColumn<SqlGuid>(i);
auto valOpt = cursor.GetNullableColumn<SqlGuid>(i);
if (valOpt)
row.emplace_back(to_string(*valOpt));
else
Expand All @@ -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<std::string>(i);
auto valOpt = cursor.GetNullableColumn<std::string>(i);
if (valOpt)
row.emplace_back(*valOpt);
else
Expand All @@ -383,7 +383,7 @@ void ProcessTableBackup(BackupContext& ctx, SqlConnection& conn, SqlSchema::Tabl
else if (std::holds_alternative<Date>(colDef.type))
{
// Read Date using native type to avoid ODBC driver conversion issues.
auto valOpt = stmt.GetNullableColumn<SqlDate>(i);
auto valOpt = cursor.GetNullableColumn<SqlDate>(i);
if (valOpt)
row.emplace_back(std::format("{}", *valOpt));
else
Expand All @@ -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<std::string>(i);
auto valOpt = cursor.GetNullableColumn<std::string>(i);
if (valOpt)
row.emplace_back(*valOpt);
else
Expand All @@ -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<SqlTime>(i);
auto valOpt = cursor.GetNullableColumn<SqlTime>(i);
if (valOpt)
row.emplace_back(std::format("{}", *valOpt));
else
Expand All @@ -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<SqlDateTime>(i);
auto valOpt = cursor.GetNullableColumn<SqlDateTime>(i);
if (valOpt)
row.emplace_back(std::format("{}", *valOpt));
else
Expand All @@ -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<std::string>(i);
auto valOpt = cursor.GetNullableColumn<std::string>(i);
if (valOpt)
row.emplace_back(*valOpt);
else
Expand All @@ -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<std::string>(i);
auto valOpt = cursor.GetNullableColumn<std::string>(i);
if (valOpt)
row.emplace_back(*valOpt);
else
Expand Down
2 changes: 1 addition & 1 deletion src/Lightweight/SqlBackup/Common.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
38 changes: 19 additions & 19 deletions src/Lightweight/SqlBackup/Restore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
}

Expand All @@ -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<SqlRawColumn> const& cols, size_t rows) { stmt.ExecuteBatch(cols, rows); },
[&](std::vector<SqlRawColumn> const& cols, size_t rows) { (void) stmt.ExecuteBatch(cols, rows); },
tableInfo.columns,
batchCapacity,
workerConn.ServerType());
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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));
}
}
Expand Down Expand Up @@ -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));
}
}
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -597,7 +597,7 @@ std::set<std::string> 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)
Expand Down Expand Up @@ -638,7 +638,7 @@ std::set<std::string> CreateTablesInOrder(SqlConnection& conn,
{
try
{
SqlStatement { conn }.ExecuteDirect(sql);
(void) SqlStatement { conn }.ExecuteDirect(sql);
}
catch (std::exception const& e)
{
Expand Down Expand Up @@ -690,9 +690,9 @@ std::set<std::string> 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);
Expand Down
2 changes: 1 addition & 1 deletion src/Lightweight/SqlConnection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
4 changes: 2 additions & 2 deletions src/Lightweight/SqlMigration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ void MigrationManager::ApplySingleMigration(MigrationBase const& migration)
{
try
{
stmt.ExecuteDirect(sqlScript);
(void) stmt.ExecuteDirect(sqlScript);
}
catch (SqlException const& ex)
{
Expand Down Expand Up @@ -197,7 +197,7 @@ void MigrationManager::RevertSingleMigration(MigrationBase const& migration)
{
try
{
stmt.ExecuteDirect(sqlScript);
(void) stmt.ExecuteDirect(sqlScript);
}
catch (SqlException const& ex)
{
Expand Down
Loading
Loading