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