Lightweight 0.20260625.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 <cstdint>
11#include <format>
12#include <string>
13#include <string_view>
14#include <thread>
15#include <utility>
16
17#if defined(__clang__)
18 #pragma clang diagnostic push
19 #pragma clang diagnostic ignored "-Wnullability-extension"
20#endif
21#include <zip.h>
22#if defined(__clang__)
23 #pragma clang diagnostic pop
24#endif
25
26namespace Lightweight::SqlBackup::detail
27{
28
29/// Maximum declared buffer size for binary LOB columns during backup.
30/// The actual data can grow beyond this via automatic ODBC buffer resizing.
31/// Set to 16MB to handle typical BLOB/VARBINARY(MAX) columns.
32constexpr size_t MaxBinaryLobBufferSize = 16 * 1024 * 1024;
33
34/// Metadata for a ZIP entry used during restore operations.
35struct ZipEntryInfo
36{
37 zip_int64_t index {};
38 std::string name;
39 zip_uint64_t size {};
40 bool valid = false;
41};
42
43/// Determines if the given SQL error is a transient error that can be retried.
44///
45/// Transient errors include:
46/// - Connection errors (ODBC class 08)
47/// - Timeout errors (HYT00, HYT01)
48/// - Transaction rollback due to deadlock/serialization (class 40)
49/// - Database locked (common in SQLite)
50///
51/// @param error The SQL error information to check.
52/// @return true if the error is transient and the operation can be retried.
53LIGHTWEIGHT_API bool IsTransientError(SqlErrorInfo const& error);
54
55/// Calculates the delay for the given retry attempt using exponential backoff.
56///
57/// @param attempt The current retry attempt number (0-based).
58/// @param settings The retry configuration.
59/// @return The delay to wait before the next retry.
60LIGHTWEIGHT_API std::chrono::milliseconds CalculateRetryDelay(unsigned attempt, RetrySettings const& settings) noexcept;
61
62/// What a retry loop should do after an attempt failed.
63///
64/// @see ClassifyRetryOutcome
65enum class RetryAction : uint8_t
66{
67 /// The error is transient and the retry budget is not exhausted — wait and try again.
68 Retry,
69 /// The error is not transient, or the retry budget is spent — surface the failure.
70 GiveUp,
71};
72
73/// @brief Decides whether a failed attempt should be retried.
74///
75/// Extracted from the retry loops so the policy can be exercised without provoking a real
76/// transient driver failure. This performs no I/O and touches no handle, so a test drives it by
77/// constructing a @ref SqlErrorInfo — the same approach the `IsTransientError` cases above use.
78///
79/// @param error The error reported by the failed attempt.
80/// @param attemptsSoFar How many retries have already been consumed.
81/// @param settings The retry configuration supplying the budget.
82/// @return @ref RetryAction::Retry when the caller should back off and try again.
83[[nodiscard]] LIGHTWEIGHT_API RetryAction ClassifyRetryOutcome(SqlErrorInfo const& error,
84 unsigned attemptsSoFar,
85 RetrySettings const& settings);
86
87/// Connects to the database with retry logic for transient errors.
88///
89/// @param conn The connection object to use.
90/// @param connectionString The connection string.
91/// @param settings The retry configuration.
92/// @param progress Progress manager for reporting retry attempts.
93/// @param operation Name of the operation for progress messages.
94/// @return true if connection succeeded, false if failed after all retries.
95LIGHTWEIGHT_API bool ConnectWithRetry(SqlConnection& conn,
96 SqlConnectionString const& connectionString,
97 RetrySettings const& settings,
98 ProgressManager& progress,
99 std::string const& operation);
100
101/// Retries a function on transient errors with exponential backoff.
102///
103/// @tparam Func The callable type.
104/// @param func The function to execute.
105/// @param settings Retry configuration.
106/// @param progress Progress manager for reporting retry attempts.
107/// @param operation Name of the operation for progress messages.
108/// @return The result of the function.
109/// @throws SqlException if max retries exceeded or non-transient error occurs.
110template <typename Func>
111auto RetryOnTransientError(Func&& func, // NOLINT(cppcoreguidelines-missing-std-forward)
112 RetrySettings const& settings,
113 ProgressManager& progress,
114 std::string const& operation) -> decltype(func())
115{
116 unsigned attempts = 0;
117
118 while (true)
119 {
120 try
121 {
122 return func();
123 }
124 catch (SqlException const& e)
125 {
126 if (ClassifyRetryOutcome(e.info(), attempts, settings) == RetryAction::GiveUp)
127 throw;
128
129 ++attempts;
130 progress.Update(
131 { .state = Progress::State::Warning,
132 .tableName = operation,
133 .currentRows = 0,
134 .totalRows = std::nullopt,
135 .message = std::format("Transient error, retry {}/{}: {}", attempts, settings.maxRetries, e.what()) });
136
137 std::this_thread::sleep_for(CalculateRetryDelay(attempts - 1, settings));
138 }
139 }
140}
141
142/// Returns the current date and time in ISO 8601 format.
143///
144/// @return ISO 8601 formatted timestamp string.
145LIGHTWEIGHT_API std::string CurrentDateTime();
146
147/// Reads a ZIP entry into a container.
148///
149/// @tparam Container The container type (e.g., std::string, std::vector<uint8_t>).
150/// @param zip The ZIP archive handle.
151/// @param index The index of the entry to read.
152/// @param size The size of the entry in bytes.
153/// @return The container with the entry contents, or empty on failure.
154template <typename Container>
155Container ReadZipEntry(zip_t* zip, zip_int64_t index, zip_uint64_t size)
156{
157 zip_file_t* file = zip_fopen_index(zip, static_cast<zip_uint64_t>(index), 0);
158 if (!file)
159 return {}; // LCOV_EXCL_LINE - zip file open failure
160
161 Container data;
162 data.resize(size);
163
164 zip_int64_t bytesRead = zip_fread(file, data.data(), size);
165 zip_fclose(file);
166
167 if (bytesRead < 0 || std::cmp_not_equal(bytesRead, size))
168 return {}; // LCOV_EXCL_LINE - zip file read failure
169
170 return data;
171}
172
173/// Formats a table name with optional schema prefix.
174///
175/// @param schema The schema name (may be empty).
176/// @param table The table name.
177/// @return Quoted table name, optionally prefixed with quoted schema.
178LIGHTWEIGHT_API std::string FormatTableName(std::string_view schema, std::string_view table);
179
180/// Drops a table if it exists, handling FK constraints via cascade.
181///
182/// @param conn The database connection.
183/// @param schema The schema name.
184/// @param tableName The table name.
185/// @param progress Progress manager for reporting errors.
186/// @return true if table was dropped or didn't exist, false on error.
187LIGHTWEIGHT_API bool DropTableIfExists(SqlConnection& conn,
188 std::string const& schema,
189 std::string const& tableName,
190 ProgressManager& progress);
191
192} // namespace Lightweight::SqlBackup::detail