Lightweight 0.20261105.1
Loading...
Searching...
No Matches
BatchManager.hpp
1// SPDX-License-Identifier: Apache-2.0
2#pragma once
3
4#include "../Api.hpp"
5#include "../DataBinder/SqlRawColumn.hpp" // For SqlRawColumn
6#include "../SqlSchema.hpp"
7#include "../SqlServerType.hpp"
8#include "RowIsolation.hpp"
9#include "SqlBackupFormats.hpp"
10
11#include <cstddef>
12#include <expected>
13#include <functional>
14#include <memory>
15#include <optional>
16#include <span>
17#include <string>
18#include <string_view>
19#include <vector>
20
21namespace Lightweight::detail
22{
23
24struct BatchColumn;
25
26/// Executes and commits @p rowCount rows given as @p slice (one slice per column, each holding
27/// exactly those rows), or rolls them back and returns the error. The rows of
28/// BatchInsertError::reportedFailedRows are indices into the slice.
29using SliceInserter =
30 std::function<std::expected<void, BatchInsertError>(std::span<SqlRawColumn const> slice, std::size_t rowCount)>;
31
32/// The callbacks through which a BatchManager inserts rows and reports what it could not insert.
33/// All are optional except @c tryInsert.
34struct BatchManagerHooks
35{
36 /// Inserts a contiguous range of the pending rows.
37 SliceInserter tryInsert;
38
39 /// Classifies a failed insert; when empty, every error is FailureDisposition::Propagate.
40 FailureClassifier classify;
41
42 /// Called for each row that is not inserted, in row order: @p rowInManager is 1-based and counts
43 /// every row the manager was given (skipped rows included); @p sqlState is empty for a row
44 /// excluded because of a value that does not convert; @p sampleKey names the row by its primary
45 /// key columns (by all columns if it has none), at most 200 characters.
46 std::function<void(std::size_t rowInManager, std::string_view sqlState, std::string_view message, std::string sampleKey)>
47 onRowFailed;
48
49 /// Called for each value that does not convert and was inserted as NULL into the nullable
50 /// @p column, once its row is committed; never for a row that is not inserted.
51 std::function<void(std::string_view column, std::string_view reason)> onCellNulled;
52
53 /// Called with BatchManager::SettledRows() whenever it advances.
54 std::function<void(std::size_t settledRows)> onSettled;
55};
56
57#if defined(_MSC_VER)
58 #pragma warning(push)
59 #pragma warning(disable : 4251) // STL types in DLL interface
60#endif
61
62/// A batch manager that manages multiple batch columns for a statement.
63struct LIGHTWEIGHT_API BatchManager
64{
65 /// Create a new BatchManager that inserts and reports through @p hooks.
66 ///
67 /// @param hooks The callbacks that insert rows and receive what could not be inserted.
68 /// @param colDecls The column declarations for the statement.
69 /// @param capacity The capacity of the batch manager.
70 /// @param serverType The server type for database-specific bindings.
71 /// @param sourceServerType The DBMS the data was backed up from. When it pads CHAR(n) values and
72 /// @p serverType does not, the trailing pad of CHAR/NCHAR values is stripped.
73 explicit BatchManager(BatchManagerHooks hooks,
74 std::vector<SqlColumnDeclaration> const& colDecls,
75 size_t capacity,
76 SqlServerType serverType = SqlServerType::UNKNOWN,
77 SqlServerType sourceServerType = SqlServerType::UNKNOWN);
78
79 ~BatchManager();
80
81 BatchManager(BatchManager const&) = delete;
82 BatchManager& operator=(BatchManager const&) = delete;
83 BatchManager(BatchManager&&) noexcept;
84 BatchManager& operator=(BatchManager&&) noexcept;
85
86 /// Push a row to the batch manager.
87 ///
88 /// A value that cannot be converted to its column's type becomes NULL in a nullable column
89 /// (reported via BatchManagerHooks::onCellNulled); in a NOT NULL column it excludes the row,
90 /// which Flush() reports via BatchManagerHooks::onRowFailed instead of inserting it.
91 void PushRow(std::vector<SqlBackup::BackupValue> const& row);
92
93 /// Push a batch of data (from a chunk) to the batch manager; values that do not convert are
94 /// handled as by PushRow().
95 void PushBatch(SqlBackup::ColumnBatch const& batch);
96
97 /// Push a batch of data (from a chunk) to the batch manager, leaving out its first rows.
98 ///
99 /// @param batch The rows to push.
100 /// @param skipRows The number of leading rows of @p batch not to insert, e.g. because an earlier
101 /// attempt already settled them. They count toward the row numbers and SettledRows().
102 void PushBatch(SqlBackup::ColumnBatch const& batch, std::size_t skipRows);
103
104 /// Flush the current batch to the database, isolating rows the database rejects.
105 ///
106 /// @throws SqlException for an insert error classified FailureDisposition::Propagate, and
107 /// TableRestoreFailure for one classified FailureDisposition::FailTable; the rows not yet
108 /// inserted are discarded either way.
109 void Flush();
110
111 /// The resume watermark: the length of the gap-free prefix of the rows given to this manager
112 /// (skipped rows included) that are each inserted, excluded or reported failed.
113 /// @return The number of leading rows that need no further attempt.
114 [[nodiscard]] std::size_t SettledRows() const noexcept;
115
116 // private:
117 [[nodiscard]] std::unique_ptr<BatchColumn> CreateColumn(SqlColumnDeclaration const& col) const;
118
119 /// Clears every column of the rows not yet flushed.
120 void DiscardPendingRows();
121
122 /// Appends @p cellCount cells to column @p columnIndex via @p push, which is called with the
123 /// first cell and the number of cells to append. A cell that does not convert is appended as
124 /// NULL and recorded (see RecordValueFailure()), and @p push resumes after it.
125 /// @param columnIndex The index of the column in `columns`.
126 /// @param cellCount The number of cells to append.
127 /// @param push Appends cells [first, first + count) of the pushed range to the column.
128 void PushIntoColumn(size_t columnIndex,
129 size_t cellCount,
130 std::function<void(BatchColumn& column, size_t first, size_t count)> const& push);
131
132 /// Handles the value of column @p columnIndex in pending row @p pendingRow that did not convert.
133 /// @param columnIndex The index of the column in `columns`.
134 /// @param pendingRow The zero-based index of the row among the pending rows.
135 /// @param reason Why the value does not convert.
136 void RecordValueFailure(size_t columnIndex, size_t pendingRow, std::string_view reason);
137
138 /// Names pending row @p row for a failure report by its primary key (all columns without one).
139 /// @param rawColumns The raw columns of the pending rows.
140 /// @param row The zero-based index of the row among the pending rows.
141 /// @return The cells, joined by ", ", cut to 200 characters.
142 [[nodiscard]] std::string DescribeRowKey(std::vector<SqlRawColumn> const& rawColumns, size_t row) const;
143
144 /// Settles @p count rows that directly follow the pending ones without inserting them.
145 /// @param count The number of rows to skip.
146 void SkipRows(size_t count);
147
148 /// Advances the watermark to @p settledEnd, given that the rows [@p firstRow, @p settledEnd) are
149 /// settled, provided every row before @p firstRow is settled already; reports it via
150 /// BatchManagerHooks::onSettled.
151 /// @param firstRow The first of the newly settled rows, counted as SettledRows() counts.
152 /// @param settledEnd The row after the last of them.
153 void ExtendWatermark(size_t firstRow, size_t settledEnd);
154
155 size_t rowCount = 0;
156 /// Rows given to the manager before the pending ones (flushed, failed or skipped).
157 size_t firstPendingRow = 0;
158 /// See SettledRows().
159 size_t settledRows = 0;
160 size_t capacity = 1000;
161 BatchManagerHooks hooks;
162 /// Per column: a value that does not convert excludes its row instead of becoming NULL.
163 std::vector<bool> columnRequired;
164 /// The indices of the primary key columns, which name a failed row.
165 std::vector<size_t> keyColumns;
166 /// Per pending row: excluded because a NOT NULL value does not convert. Sized on demand.
167 std::vector<bool> excludedRows;
168 /// Per pending row, parallel to `excludedRows`: why the row is excluded.
169 std::vector<std::optional<std::string>> exclusionReasons;
170
171 /// A value set to NULL in a nullable column of a pending row.
172 struct NulledCell
173 {
174 /// The zero-based index of the row among the pending rows.
175 size_t pendingRow {};
176 /// The index of the column in `columns`.
177 size_t columnIndex {};
178 /// Why the value does not convert.
179 std::string reason;
180 };
181
182 /// The pending rows' nulled cells, reported via BatchManagerHooks::onCellNulled only once their
183 /// row is inserted.
184 std::vector<NulledCell> nulledCells;
185 std::vector<std::unique_ptr<BatchColumn>> columns;
186 SqlServerType serverType = SqlServerType::UNKNOWN;
187 /// The DBMS the backup was taken from; UNKNOWN when the archive does not name a known one.
188 SqlServerType sourceServerType = SqlServerType::UNKNOWN;
189 /// Whether CHAR/NCHAR values lose their trailing pad: the source DBMS pads them and the target
190 /// does not, so the pad would otherwise stay in the data and break comparisons. False if either
191 /// DBMS is unknown.
192 bool trimsFixedCharPadding = false;
193 /// Whether the target stores GUID columns in a native GUID type rather than as text.
194 bool hasNativeGuidType = false;
195 /// The column names, parallel to `columns`, for failure reports.
196 std::vector<std::string> columnNames;
197};
198
199#if defined(_MSC_VER)
200 #pragma warning(pop)
201#endif
202
203} // namespace Lightweight::detail