Lightweight 0.20251202.0
Loading...
Searching...
No Matches
Common.hpp
1// SPDX-License-Identifier: Apache-2.0
2
3#pragma once
4
5#include "../SqlConnection.hpp"
6#include "../SqlError.hpp"
7#include "SqlBackup.hpp"
8
9#include <chrono>
10#include <format>
11#include <string>
12#include <string_view>
13#include <thread>
14#include <utility>
15
16#if defined(__clang__)
17 #pragma clang diagnostic push
18 #pragma clang diagnostic ignored "-Wnullability-extension"
19#endif
20#include <zip.h>
21#if defined(__clang__)
22 #pragma clang diagnostic pop
23#endif
24
25namespace Lightweight::SqlBackup::detail
26{
27
28/// Maximum declared buffer size for binary LOB columns during backup.
29/// The actual data can grow beyond this via automatic ODBC buffer resizing.
30/// Set to 16MB to handle typical BLOB/VARBINARY(MAX) columns.
31constexpr size_t MaxBinaryLobBufferSize = 16 * 1024 * 1024;
32
33/// Metadata for a ZIP entry used during restore operations.
34struct ZipEntryInfo
35{
36 zip_int64_t index {};
37 std::string name;
38 zip_uint64_t size {};
39 bool valid = false;
40};
41
42/// Determines if the given SQL error is a transient error that can be retried.
43///
44/// Transient errors include:
45/// - Connection errors (ODBC class 08)
46/// - Timeout errors (HYT00, HYT01)
47/// - Transaction rollback due to deadlock/serialization (class 40)
48/// - Database locked (common in SQLite)
49///
50/// @param error The SQL error information to check.
51/// @return true if the error is transient and the operation can be retried.
52bool IsTransientError(SqlErrorInfo const& error);
53
54/// Calculates the delay for the given retry attempt using exponential backoff.
55///
56/// @param attempt The current retry attempt number (0-based).
57/// @param settings The retry configuration.
58/// @return The delay to wait before the next retry.
59std::chrono::milliseconds CalculateRetryDelay(unsigned attempt, RetrySettings const& settings) noexcept;
60
61/// Connects to the database with retry logic for transient errors.
62///
63/// @param conn The connection object to use.
64/// @param connectionString The connection string.
65/// @param settings The retry configuration.
66/// @param progress Progress manager for reporting retry attempts.
67/// @param operation Name of the operation for progress messages.
68/// @return true if connection succeeded, false if failed after all retries.
69bool ConnectWithRetry(SqlConnection& conn,
70 SqlConnectionString const& connectionString,
71 RetrySettings const& settings,
72 ProgressManager& progress,
73 std::string const& operation);
74
75/// Retries a function on transient errors with exponential backoff.
76///
77/// @tparam Func The callable type.
78/// @param func The function to execute.
79/// @param settings Retry configuration.
80/// @param progress Progress manager for reporting retry attempts.
81/// @param operation Name of the operation for progress messages.
82/// @return The result of the function.
83/// @throws SqlException if max retries exceeded or non-transient error occurs.
84template <typename Func>
85auto RetryOnTransientError(Func&& func, // NOLINT(cppcoreguidelines-missing-std-forward)
86 RetrySettings const& settings,
87 ProgressManager& progress,
88 std::string const& operation) -> decltype(func())
89{
90 unsigned attempts = 0;
91
92 while (true)
93 {
94 try
95 {
96 return func();
97 }
98 catch (SqlException const& e)
99 {
100 if (!IsTransientError(e.info()) || attempts >= settings.maxRetries)
101 throw;
102
103 ++attempts;
104 progress.Update(
105 { .state = Progress::State::Warning,
106 .tableName = operation,
107 .currentRows = 0,
108 .totalRows = std::nullopt,
109 .message = std::format("Transient error, retry {}/{}: {}", attempts, settings.maxRetries, e.what()) });
110
111 std::this_thread::sleep_for(CalculateRetryDelay(attempts - 1, settings));
112 }
113 }
114}
115
116/// Returns the current date and time in ISO 8601 format.
117///
118/// @return ISO 8601 formatted timestamp string.
119std::string CurrentDateTime();
120
121/// Reads a ZIP entry into a container.
122///
123/// @tparam Container The container type (e.g., std::string, std::vector<uint8_t>).
124/// @param zip The ZIP archive handle.
125/// @param index The index of the entry to read.
126/// @param size The size of the entry in bytes.
127/// @return The container with the entry contents, or empty on failure.
128template <typename Container>
129Container ReadZipEntry(zip_t* zip, zip_int64_t index, zip_uint64_t size)
130{
131 zip_file_t* file = zip_fopen_index(zip, static_cast<zip_uint64_t>(index), 0);
132 if (!file)
133 return {}; // LCOV_EXCL_LINE - zip file open failure
134
135 Container data;
136 data.resize(size);
137
138 zip_int64_t bytesRead = zip_fread(file, data.data(), size);
139 zip_fclose(file);
140
141 if (bytesRead < 0 || std::cmp_not_equal(bytesRead, size))
142 return {}; // LCOV_EXCL_LINE - zip file read failure
143
144 return data;
145}
146
147/// Formats a table name with optional schema prefix.
148///
149/// @param schema The schema name (may be empty).
150/// @param table The table name.
151/// @return Quoted table name, optionally prefixed with quoted schema.
152std::string FormatTableName(std::string_view schema, std::string_view table);
153
154/// Drops a table if it exists, handling FK constraints via cascade.
155///
156/// @param conn The database connection.
157/// @param schema The schema name.
158/// @param tableName The table name.
159/// @param progress Progress manager for reporting errors.
160/// @return true if table was dropped or didn't exist, false on error.
161bool DropTableIfExists(SqlConnection& conn,
162 std::string const& schema,
163 std::string const& tableName,
164 ProgressManager& progress);
165
166} // namespace Lightweight::SqlBackup::detail