Lightweight 0.20251202.0
Loading...
Searching...
No Matches
SqlStatement.hpp
1// SPDX-License-Identifier: Apache-2.0
2
3#pragma once
4
5#if defined(_WIN32) || defined(_WIN64)
6 #include <Windows.h>
7#endif
8
9#include "Api.hpp"
10#include "SqlConnection.hpp"
11#include "SqlQuery.hpp"
12#include "SqlServerType.hpp"
13#include "Utils.hpp"
14
15#include <cstring>
16#include <expected>
17#include <optional>
18#include <ranges>
19#include <source_location>
20#include <span>
21#include <stdexcept>
22#include <type_traits>
23#include <vector>
24
25#include <sql.h>
26#include <sqlext.h>
27#include <sqlspi.h>
28#include <sqltypes.h>
29
30namespace Lightweight
31{
32
33struct SqlRawColumn;
34
35/// @brief Represents an SQL query object, that provides a ToSql() method.
36template <typename QueryObject>
37concept SqlQueryObject = requires(QueryObject const& queryObject) {
38 { queryObject.ToSql() } -> std::convertible_to<std::string>;
39};
40
41class SqlResultCursor;
42class SqlVariantRowCursor;
43
44/// @brief High level API for (prepared) raw SQL statements
45///
46/// SQL prepared statement lifecycle:
47/// 1. Prepare the statement
48/// 2. Optionally bind output columns to local variables
49/// 3. Execute the statement (optionally with input parameters)
50/// 4. Fetch rows (if any)
51/// 5. Repeat steps 3 and 4 as needed
52class [[nodiscard]] SqlStatement final: public SqlDataBinderCallback
53{
54 public:
55 /// Construct a new SqlStatement object, using a new connection, and connect to the default database.
56 LIGHTWEIGHT_API SqlStatement();
57
58 LIGHTWEIGHT_API SqlStatement(SqlStatement&& other) noexcept;
59 LIGHTWEIGHT_API SqlStatement& operator=(SqlStatement&& other) noexcept;
60
61 SqlStatement(SqlStatement const&) noexcept = delete;
62 SqlStatement& operator=(SqlStatement const&) noexcept = delete;
63
64 /// Construct a new SqlStatement object, using the given connection.
65 LIGHTWEIGHT_API explicit SqlStatement(SqlConnection& relatedConnection);
66
67 /// Construct a new empty SqlStatement object. No SqlConnection is associated with this statement.
68 LIGHTWEIGHT_API explicit SqlStatement(std::nullopt_t /*nullopt*/);
69
70 LIGHTWEIGHT_API ~SqlStatement() noexcept final;
71
72 [[nodiscard]] LIGHTWEIGHT_API bool IsAlive() const noexcept;
73
74 [[nodiscard]] LIGHTWEIGHT_API bool IsPrepared() const noexcept;
75
76 /// Retrieves the connection associated with this statement.
77 [[nodiscard]] LIGHTWEIGHT_API SqlConnection& Connection() noexcept;
78
79 /// Retrieves the connection associated with this statement.
80 [[nodiscard]] LIGHTWEIGHT_API SqlConnection const& Connection() const noexcept;
81
82 /// Retrieves the last error information with respect to this SQL statement handle.
83 [[nodiscard]] LIGHTWEIGHT_API SqlErrorInfo LastError() const;
84
85 /// Creates a new query builder for the given table, compatible with the SQL server being connected.
86 LIGHTWEIGHT_API SqlQueryBuilder Query(std::string_view const& table = {}) const;
87
88 /// Creates a new query builder for the given table with an alias, compatible with the SQL server being connected.
89 [[nodiscard]] LIGHTWEIGHT_API SqlQueryBuilder QueryAs(std::string_view const& table,
90 std::string_view const& tableAlias) const;
91
92 /// Retrieves the native handle of the statement.
93 [[nodiscard]] LIGHTWEIGHT_API SQLHSTMT NativeHandle() const noexcept;
94
95 /// Prepares the statement for execution.
96 ///
97 /// @note When preparing a new SQL statement the previously executed statement, yielding a result set,
98 /// must have been closed.
99 LIGHTWEIGHT_API void Prepare(std::string_view query) &;
100
101 LIGHTWEIGHT_API SqlStatement Prepare(std::string_view query) &&;
102
103 /// Prepares the statement for execution.
104 ///
105 /// @note When preparing a new SQL statement the previously executed statement, yielding a result set,
106 /// must have been closed.
107 void Prepare(SqlQueryObject auto const& queryObject) &;
108
109 SqlStatement Prepare(SqlQueryObject auto const& queryObject) &&;
110
111 [[nodiscard]] std::string const& PreparedQuery() const noexcept;
112
113 template <SqlInputParameterBinder Arg>
114 void BindInputParameter(SQLSMALLINT columnIndex, Arg const& arg);
115
116 template <SqlInputParameterBinder Arg, typename ColumnName>
117 void BindInputParameter(SQLSMALLINT columnIndex, Arg const& arg, ColumnName&& columnNameHint);
118
119 /// Binds the given arguments to the prepared statement to store the fetched data to.
120 ///
121 /// @note The statement must be prepared before calling this function.
122 template <SqlOutputColumnBinder... Args>
123 void BindOutputColumns(Args*... args);
124
125 /// Binds the given arguments to the prepared statement to store the fetched data to.
126 ///
127 /// @param records The records to bind each member to.
128 ///
129 /// @note The statement must be prepared before calling this function.
130 /// @note The records must be aggregate types, i.e. classes with public members and no user-defined constructors.
131 template <typename... Records>
132 requires(((std::is_class_v<Records> && std::is_aggregate_v<Records>) && ...))
133 void BindOutputColumnsToRecord(Records*... records);
134
135 template <SqlOutputColumnBinder T>
136 void BindOutputColumn(SQLUSMALLINT columnIndex, T* arg);
137
138 /// Binds the given arguments to the prepared statement and executes it.
139 template <SqlInputParameterBinder... Args>
140 void Execute(Args const&... args);
141
142 /// Binds the given arguments to the prepared statement and executes it.
143 LIGHTWEIGHT_API void ExecuteWithVariants(std::vector<SqlVariant> const& args);
144
145 /// Executes the prepared statement on a batch of data.
146 ///
147 /// Each parameter represents a column, to be bound as input parameter.
148 /// The element types of each column container must be explicitly supported.
149 ///
150 /// In order to support column value types, their underlying storage must be contiguous.
151 /// Also the input range itself must be contiguous.
152 /// If any of these conditions are not met, the function will not compile - use ExecuteBatch() instead.
153 template <SqlInputParameterBatchBinder FirstColumnBatch, std::ranges::contiguous_range... MoreColumnBatches>
154 void ExecuteBatchNative(FirstColumnBatch const& firstColumnBatch, MoreColumnBatches const&... moreColumnBatches);
155
156 /// Executes the prepared statement on a batch of data.
157 ///
158 /// Each parameter represents a column, to be bound as input parameter,
159 /// and the number of elements in these bound column containers will
160 /// mandate how many executions will happen.
161 ///
162 /// This function will bind and execute each row separately,
163 /// which is less efficient than ExecuteBatchNative(), but works non-contiguous input ranges.
164 template <SqlInputParameterBatchBinder FirstColumnBatch, std::ranges::range... MoreColumnBatches>
165 void ExecuteBatchSoft(FirstColumnBatch const& firstColumnBatch, MoreColumnBatches const&... moreColumnBatches);
166
167 /// Executes the prepared statement on a batch of data.
168 ///
169 /// Each parameter represents a column, to be bound as input parameter,
170 /// and the number of elements in these bound column containers will
171 /// mandate how many executions will happen.
172 template <SqlInputParameterBatchBinder FirstColumnBatch, std::ranges::range... MoreColumnBatches>
173 void ExecuteBatch(FirstColumnBatch const& firstColumnBatch, MoreColumnBatches const&... moreColumnBatches);
174
175 /// Executes the prepared statement on a batch of SqlRawColumn-prepared data.
176 ///
177 /// @param columns The columns to bind as input parameters.
178 /// @param rowCount The number of rows to execute.
179 LIGHTWEIGHT_API void ExecuteBatch(std::span<SqlRawColumn const> columns, size_t rowCount);
180
181 /// Executes the given query directly.
182 LIGHTWEIGHT_API void ExecuteDirect(std::string_view const& query,
183 std::source_location location = std::source_location::current());
184
185 /// Executes the given query directly.
186 void ExecuteDirect(SqlQueryObject auto const& query, std::source_location location = std::source_location::current());
187
188 /// Executes an SQL migration query, as created b the callback.
189 template <typename Callable>
190 requires std::invocable<Callable, SqlMigrationQueryBuilder&>
191 void MigrateDirect(Callable const& callable, std::source_location location = std::source_location::current());
192
193 /// Executes the given query, assuming that only one result row and column is affected, that one will be
194 /// returned.
195 template <typename T>
196 requires(!std::same_as<T, SqlVariant>)
197 [[nodiscard]] std::optional<T> ExecuteDirectScalar(std::string_view const& query,
198 std::source_location location = std::source_location::current());
199
200 template <typename T>
201 requires(std::same_as<T, SqlVariant>)
202 [[nodiscard]] T ExecuteDirectScalar(std::string_view const& query,
203 std::source_location location = std::source_location::current());
204
205 /// Executes the given query, assuming that only one result row and column is affected, that one will be
206 /// returned.
207 template <typename T>
208 requires(!std::same_as<T, SqlVariant>)
209 [[nodiscard]] std::optional<T> ExecuteDirectScalar(SqlQueryObject auto const& query,
210 std::source_location location = std::source_location::current());
211
212 template <typename T>
213 requires(std::same_as<T, SqlVariant>)
214 [[nodiscard]] T ExecuteDirectScalar(SqlQueryObject auto const& query,
215 std::source_location location = std::source_location::current());
216
217 /// Retrieves the number of rows affected by the last query.
218 [[nodiscard]] LIGHTWEIGHT_API size_t NumRowsAffected() const;
219
220 /// Retrieves the number of columns affected by the last query.
221 [[nodiscard]] LIGHTWEIGHT_API size_t NumColumnsAffected() const;
222
223 /// Retrieves the last insert ID of the given table.
224 [[nodiscard]] LIGHTWEIGHT_API size_t LastInsertId(std::string_view tableName);
225
226 /// Fetches the next row of the result set.
227 ///
228 /// @note Automatically closes the cursor at the end of the result set.
229 ///
230 /// @retval true The next result row was successfully fetched
231 /// @retval false No result row was fetched, because the end of the result set was reached.
232 [[nodiscard]] LIGHTWEIGHT_API bool FetchRow();
233
234 [[nodiscard]] LIGHTWEIGHT_API std::expected<bool, SqlErrorInfo> TryFetchRow(
235 std::source_location location = std::source_location::current()) noexcept;
236
237 /// Closes the result cursor on queries that yield a result set, e.g. SELECT statements.
238 ///
239 /// Call this function when done with fetching the results before the end of the result set is reached.
240 void CloseCursor() noexcept;
241
242 /// Retrieves the result cursor for reading an SQL query result.
243 SqlResultCursor GetResultCursor() noexcept;
244
245 /// Retrieves the variant row cursor for reading an SQL query result of unknown column types and column count.
246 SqlVariantRowCursor GetVariantRowCursor() noexcept;
247
248 /// Retrieves the value of the column at the given index for the currently selected row.
249 ///
250 /// Returns true if the value is not NULL, false otherwise.
251 template <SqlGetColumnNativeType T>
252 [[nodiscard]] bool GetColumn(SQLUSMALLINT column, T* result) const;
253
254 /// Retrieves the value of the column at the given index for the currently selected row.
255 template <SqlGetColumnNativeType T>
256 [[nodiscard]] T GetColumn(SQLUSMALLINT column) const;
257
258 /// Retrieves the value of the column at the given index for the currently selected row.
259 ///
260 /// If the value is NULL, std::nullopt is returned.
261 template <SqlGetColumnNativeType T>
262 [[nodiscard]] std::optional<T> GetNullableColumn(SQLUSMALLINT column) const;
263
264 /// Retrieves the value of the column at the given index for the currently selected row.
265 ///
266 /// If the value is NULL, the given @p defaultValue is returned.
267 template <SqlGetColumnNativeType T>
268 [[nodiscard]] T GetColumnOr(SQLUSMALLINT column, T&& defaultValue) const;
269
270 private:
271 LIGHTWEIGHT_API void RequireSuccess(SQLRETURN error,
272 std::source_location sourceLocation = std::source_location::current()) const;
273 LIGHTWEIGHT_API void PlanPostExecuteCallback(std::function<void()>&& cb) override;
274 LIGHTWEIGHT_API void PlanPostProcessOutputColumn(std::function<void()>&& cb) override;
275 [[nodiscard]] LIGHTWEIGHT_API SqlServerType ServerType() const noexcept override;
276 [[nodiscard]] LIGHTWEIGHT_API std::string const& DriverName() const noexcept override;
277 LIGHTWEIGHT_API void ProcessPostExecuteCallbacks();
278
279 LIGHTWEIGHT_API SQLLEN* ProvideInputIndicator() override;
280 LIGHTWEIGHT_API SQLLEN* ProvideInputIndicators(size_t rowCount) override;
281 LIGHTWEIGHT_API void ClearBatchIndicators();
282 LIGHTWEIGHT_API void RequireIndicators();
283 LIGHTWEIGHT_API SQLLEN* GetIndicatorForColumn(SQLUSMALLINT column) noexcept;
284
285 // private data members
286 struct Data;
287 std::unique_ptr<Data, void (*)(Data*)> m_data; // The private data of the statement
288 SqlConnection* m_connection {}; // Pointer to the connection object
289 SQLHSTMT m_hStmt {}; // The native oDBC statement handle
290 std::string m_preparedQuery; // The last prepared query
291 std::optional<SQLSMALLINT> m_numColumns; // The number of columns in the result set, if known
292 SQLSMALLINT m_expectedParameterCount {}; // The number of parameters expected by the query
293};
294
295/// API for reading an SQL query result set.
296class [[nodiscard]] SqlResultCursor
297{
298 public:
299 explicit LIGHTWEIGHT_FORCE_INLINE SqlResultCursor(SqlStatement& stmt) noexcept:
300 m_stmt { &stmt }
301 {
302 }
303
304 SqlResultCursor() = delete;
305 SqlResultCursor(SqlResultCursor const&) = delete;
306 SqlResultCursor& operator=(SqlResultCursor const&) = delete;
307
308 constexpr SqlResultCursor(SqlResultCursor&& other) noexcept:
309 m_stmt { other.m_stmt }
310 {
311 other.m_stmt = nullptr;
312 }
313
314 constexpr SqlResultCursor& operator=(SqlResultCursor&& other) noexcept
315 {
316 if (this != &other)
317 {
318 m_stmt = other.m_stmt;
319 other.m_stmt = nullptr;
320 }
321 return *this;
322 }
323
324 LIGHTWEIGHT_FORCE_INLINE ~SqlResultCursor()
325 {
326 if (m_stmt)
327 {
328 m_stmt->CloseCursor();
329 m_stmt = nullptr;
330 }
331 }
332
333 /// Retrieves the number of rows affected by the last query.
334 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE size_t NumRowsAffected() const
335 {
336 return m_stmt->NumRowsAffected();
337 }
338
339 /// Retrieves the number of columns affected by the last query.
340 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE size_t NumColumnsAffected() const
341 {
342 return m_stmt->NumColumnsAffected();
343 }
344
345 /// Binds the given arguments to the prepared statement to store the fetched data to.
346 ///
347 /// The statement must be prepared before calling this function.
348 template <SqlOutputColumnBinder... Args>
349 LIGHTWEIGHT_FORCE_INLINE void BindOutputColumns(Args*... args)
350 {
351 m_stmt->BindOutputColumns(args...);
352 }
353
354 template <SqlOutputColumnBinder T>
355 LIGHTWEIGHT_FORCE_INLINE void BindOutputColumn(SQLUSMALLINT columnIndex, T* arg)
356 {
357 m_stmt->BindOutputColumn(columnIndex, arg);
358 }
359
360 /// Fetches the next row of the result set.
361 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE bool FetchRow()
362 {
363 return m_stmt->FetchRow();
364 }
365
366 /// Retrieves the value of the column at the given index for the currently selected row.
367 ///
368 /// Returns true if the value is not NULL, false otherwise.
369 template <SqlGetColumnNativeType T>
370 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE bool GetColumn(SQLUSMALLINT column, T* result) const
371 {
372 return m_stmt->GetColumn<T>(column, result);
373 }
374
375 /// Retrieves the value of the column at the given index for the currently selected row.
376 template <SqlGetColumnNativeType T>
377 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE T GetColumn(SQLUSMALLINT column) const
378 {
379 return m_stmt->GetColumn<T>(column);
380 }
381
382 /// Retrieves the value of the column at the given index for the currently selected row.
383 ///
384 /// If the value is NULL, std::nullopt is returned.
385 template <SqlGetColumnNativeType T>
386 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE std::optional<T> GetNullableColumn(SQLUSMALLINT column) const
387 {
388 return m_stmt->GetNullableColumn<T>(column);
389 }
390
391 /// Retrieves the value of the column at the given index for the currently selected row.
392 ///
393 /// If the value is NULL, the given @p defaultValue is returned.
394 template <SqlGetColumnNativeType T>
395 [[nodiscard]] T GetColumnOr(SQLUSMALLINT column, T&& defaultValue) const
396 {
397 return m_stmt->GetColumnOr(column, std::forward<T>(defaultValue));
398 }
399
400 private:
401 SqlStatement* m_stmt;
402};
403
404struct [[nodiscard]] SqlSentinelIterator
405{
406};
407
408class [[nodiscard]] SqlVariantRowIterator
409{
410 public:
411 explicit SqlVariantRowIterator(SqlSentinelIterator /*sentinel*/) noexcept:
412 _cursor { nullptr }
413 {
414 }
415
416 explicit SqlVariantRowIterator(SqlResultCursor& cursor) noexcept:
417 _numResultColumns { static_cast<SQLUSMALLINT>(cursor.NumColumnsAffected()) },
418 _cursor { &cursor }
419 {
420 _row.reserve(_numResultColumns);
421 ++(*this);
422 }
423
424 SqlVariantRow& operator*() noexcept
425 {
426 return _row;
427 }
428
429 SqlVariantRow const& operator*() const noexcept
430 {
431 return _row;
432 }
433
434 SqlVariantRowIterator& operator++() noexcept
435 {
436 _end = !_cursor->FetchRow();
437 if (!_end)
438 {
439 _row.clear();
440 for (auto const i: std::views::iota(SQLUSMALLINT(1), SQLUSMALLINT(_numResultColumns + 1)))
441 _row.emplace_back(_cursor->GetColumn<SqlVariant>(i));
442 }
443 return *this;
444 }
445
446 bool operator!=(SqlSentinelIterator /*sentinel*/) const noexcept
447 {
448 return !_end;
449 }
450
451 bool operator!=(SqlVariantRowIterator const& /*rhs*/) const noexcept
452 {
453 return !_end;
454 }
455
456 private:
457 bool _end = false;
458 SQLUSMALLINT _numResultColumns = 0;
459 SqlResultCursor* _cursor;
460 SqlVariantRow _row;
461};
462
463class [[nodiscard]] SqlVariantRowCursor
464{
465 public:
466 explicit SqlVariantRowCursor(SqlResultCursor&& cursor):
467 _resultCursor { std::move(cursor) }
468 {
469 }
470
471 SqlVariantRowIterator begin() noexcept
472 {
473 return SqlVariantRowIterator { _resultCursor };
474 }
475
476 static SqlSentinelIterator end() noexcept
477 {
478 return SqlSentinelIterator {};
479 }
480
481 private:
482 SqlResultCursor _resultCursor;
483};
484
485/// @brief SQL query result row iterator
486///
487/// Can be used to iterate over rows of the database and fetch them into a record type.
488/// @tparam T The record type to fetch the rows into.
489/// @code
490///
491/// struct MyRecord
492/// {
493/// Field<SqlGuid, PrimaryKey::AutoAssign> field1;
494/// Field<int> field2;
495/// Field<double> field3;
496/// };
497///
498/// for (auto const& row : SqlRowIterator<MyRecord>(stmt))
499/// {
500/// // row is of type MyRecord
501/// // row.field1, row.field2, row.field3 are accessible
502/// }
503/// @endcode
504template <typename T>
506{
507 public:
508 explicit SqlRowIterator(SqlConnection& conn):
509 _connection { &conn }
510 {
511 }
512
513 class iterator
514 {
515 public:
516 using difference_type = bool;
517 using value_type = T;
518
519 iterator& operator++()
520 {
521 _is_end = !_stmt->FetchRow();
522 if (!_is_end)
523 {
524 }
525 else
526 {
527 _stmt->CloseCursor();
528 }
529 return *this;
530 }
531
532 LIGHTWEIGHT_FORCE_INLINE value_type operator*() noexcept
533 {
534 auto res = T {};
535
536 Reflection::EnumerateMembers(res, [this]<size_t I>(auto&& value) {
537 auto tmp = _stmt->GetColumn<typename Reflection::MemberTypeOf<I, value_type>::ValueType>(I + 1);
538 value = tmp;
539 });
540
541 return res;
542 }
543
544 LIGHTWEIGHT_FORCE_INLINE constexpr bool operator!=(iterator const& other) const noexcept
545 {
546 return _is_end != other._is_end;
547 }
548
549 constexpr iterator(std::default_sentinel_t /*sentinel*/) noexcept:
550 _is_end { true }
551 {
552 }
553
554 explicit iterator(SqlConnection& conn):
555 _stmt { std::make_unique<SqlStatement>(conn) }
556 {
557 }
558
559 LIGHTWEIGHT_FORCE_INLINE SqlStatement& Statement() noexcept
560 {
561 return *_stmt;
562 }
563
564 private:
565 bool _is_end = false;
566 std::unique_ptr<SqlStatement> _stmt;
567 };
568
569 iterator begin()
570 {
571 auto it = iterator { *_connection };
572 auto& stmt = it.Statement();
573 stmt.Prepare(it.Statement().Query(RecordTableName<T>).Select().template Fields<T>().All());
574 stmt.Execute();
575 ++it;
576 return it;
577 }
578
579 iterator end() noexcept
580 {
581 return iterator { std::default_sentinel };
582 }
583
584 private:
585 SqlConnection* _connection;
586};
587
588// {{{ inline implementation
589inline LIGHTWEIGHT_FORCE_INLINE bool SqlStatement::IsAlive() const noexcept
590{
591 return m_connection && m_connection->IsAlive() && m_hStmt != nullptr;
592}
593
594inline LIGHTWEIGHT_FORCE_INLINE bool SqlStatement::IsPrepared() const noexcept
595{
596 return !m_preparedQuery.empty();
597}
598
599inline LIGHTWEIGHT_FORCE_INLINE SqlConnection& SqlStatement::Connection() noexcept
600{
601 return *m_connection;
602}
603
604inline LIGHTWEIGHT_FORCE_INLINE SqlConnection const& SqlStatement::Connection() const noexcept
605{
606 return *m_connection;
607}
608
609inline LIGHTWEIGHT_FORCE_INLINE SqlErrorInfo SqlStatement::LastError() const
610{
611 return SqlErrorInfo::FromStatementHandle(m_hStmt);
612}
613
614inline LIGHTWEIGHT_FORCE_INLINE SQLHSTMT SqlStatement::NativeHandle() const noexcept
615{
616 return m_hStmt;
617}
618
619inline LIGHTWEIGHT_FORCE_INLINE void SqlStatement::Prepare(SqlQueryObject auto const& queryObject) &
620{
621 Prepare(queryObject.ToSql());
622}
623
624inline LIGHTWEIGHT_FORCE_INLINE SqlStatement SqlStatement::Prepare(SqlQueryObject auto const& queryObject) &&
625{
626 return Prepare(queryObject.ToSql());
627}
628
629inline LIGHTWEIGHT_FORCE_INLINE std::string const& SqlStatement::PreparedQuery() const noexcept
630{
631 return m_preparedQuery;
632}
633
634template <SqlOutputColumnBinder... Args>
635inline LIGHTWEIGHT_FORCE_INLINE void SqlStatement::BindOutputColumns(Args*... args)
636{
637 RequireIndicators();
638
639 SQLUSMALLINT i = 0;
640 ((++i, RequireSuccess(SqlDataBinder<Args>::OutputColumn(m_hStmt, i, args, GetIndicatorForColumn(i), *this))), ...);
641}
642
643template <typename... Records>
644 requires(((std::is_class_v<Records> && std::is_aggregate_v<Records>) && ...))
646{
647 RequireIndicators();
648
649 SQLUSMALLINT i = 0;
650 ((Reflection::EnumerateMembers(*records,
651 [this, &i]<size_t I, typename FieldType>(FieldType& value) {
652 ++i;
653 RequireSuccess(SqlDataBinder<FieldType>::OutputColumn(
654 m_hStmt, i, &value, GetIndicatorForColumn(i), *this));
655 })),
656 ...);
657}
658
659template <SqlOutputColumnBinder T>
660inline LIGHTWEIGHT_FORCE_INLINE void SqlStatement::BindOutputColumn(SQLUSMALLINT columnIndex, T* arg)
661{
662 RequireIndicators();
663
664 RequireSuccess(SqlDataBinder<T>::OutputColumn(m_hStmt, columnIndex, arg, GetIndicatorForColumn(columnIndex), *this));
665}
666
667template <SqlInputParameterBinder Arg>
668inline LIGHTWEIGHT_FORCE_INLINE void SqlStatement::BindInputParameter(SQLSMALLINT columnIndex, Arg const& arg)
669{
670 // tell Execute() that we don't know the expected count
671 m_expectedParameterCount = (std::numeric_limits<decltype(m_expectedParameterCount)>::max)();
672 RequireSuccess(SqlDataBinder<Arg>::InputParameter(m_hStmt, columnIndex, arg, *this));
673}
674
675template <SqlInputParameterBinder Arg, typename ColumnName>
676inline LIGHTWEIGHT_FORCE_INLINE void SqlStatement::BindInputParameter(SQLSMALLINT columnIndex,
677 Arg const& arg,
678 ColumnName&& columnNameHint)
679{
680 SqlLogger::GetLogger().OnBindInputParameter(std::forward<ColumnName>(columnNameHint), arg);
681 BindInputParameter(columnIndex, arg);
682}
683
684template <SqlInputParameterBinder... Args>
685void SqlStatement::Execute(Args const&... args)
686{
687 // Each input parameter must have an address,
688 // such that we can call SQLBindParameter() without needing to copy it.
689 // The memory region behind the input parameter must exist until the SQLExecute() call.
690
691 SqlLogger::GetLogger().OnExecute(m_preparedQuery);
692
693 if (!(m_expectedParameterCount == (std::numeric_limits<decltype(m_expectedParameterCount)>::max)()
694 && sizeof...(args) == 0)
695 && !(m_expectedParameterCount == sizeof...(args)))
696 throw std::invalid_argument { "Invalid argument count" };
697
698 SQLUSMALLINT i = 0;
699 ((++i,
701 RequireSuccess(SqlDataBinder<Args>::InputParameter(m_hStmt, i, args, *this))),
702 ...);
703
704 auto const result = SQLExecute(m_hStmt);
705
706 if (result != SQL_NO_DATA && result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO)
707 throw SqlException(SqlErrorInfo::FromStatementHandle(m_hStmt), std::source_location::current());
708
709 ProcessPostExecuteCallbacks();
710}
711
712// clang-format off
713template <typename T>
714concept SqlNativeContiguousValueConcept =
715 std::same_as<T, bool>
716 || std::same_as<T, char>
717 || std::same_as<T, unsigned char>
718 || std::same_as<T, wchar_t>
719 || std::same_as<T, std::int16_t>
720 || std::same_as<T, std::uint16_t>
721 || std::same_as<T, std::int32_t>
722 || std::same_as<T, std::uint32_t>
723 || std::same_as<T, std::int64_t>
724 || std::same_as<T, std::uint64_t>
725 || std::same_as<T, float>
726 || std::same_as<T, double>
727 || std::same_as<T, SqlDate>
728 || std::same_as<T, SqlTime>
729 || std::same_as<T, SqlDateTime>
730 || std::same_as<T, SqlFixedString<T::Capacity, typename T::value_type, T::PostRetrieveOperation>>;
731
732template <typename FirstColumnBatch, typename... MoreColumnBatches>
733concept SqlNativeBatchable =
734 std::ranges::contiguous_range<FirstColumnBatch>
735 && (std::ranges::contiguous_range<MoreColumnBatches> && ...)
736 && SqlNativeContiguousValueConcept<std::ranges::range_value_t<FirstColumnBatch>>
737 && (SqlNativeContiguousValueConcept<std::ranges::range_value_t<MoreColumnBatches>> && ...);
738
739// clang-format on
740
741template <SqlInputParameterBatchBinder FirstColumnBatch, std::ranges::contiguous_range... MoreColumnBatches>
742void SqlStatement::ExecuteBatchNative(FirstColumnBatch const& firstColumnBatch,
743 MoreColumnBatches const&... moreColumnBatches)
744{
745 static_assert(SqlNativeBatchable<FirstColumnBatch, MoreColumnBatches...>,
746 "Must be a supported native contiguous element type.");
747
748 if (m_expectedParameterCount != 1 + sizeof...(moreColumnBatches))
749 throw std::invalid_argument { "Invalid number of columns" };
750
751 auto const rowCount = std::ranges::size(firstColumnBatch);
752 if (!((std::size(moreColumnBatches) == rowCount) && ...))
753 throw std::invalid_argument { "Uneven number of rows" };
754
755 size_t rowStart = 0;
756
757 // clang-format off
758 // NOLINTNEXTLINE(performance-no-int-to-ptr)
759 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_PARAMSET_SIZE, (SQLPOINTER) rowCount, 0));
760 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_PARAM_BIND_OFFSET_PTR, &rowStart, 0));
761 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_PARAM_BIND_TYPE, SQL_PARAM_BIND_BY_COLUMN, 0));
762 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_PARAM_OPERATION_PTR, SQL_PARAM_PROCEED, 0));
763 ClearBatchIndicators();
764 RequireSuccess(SqlDataBinder<std::remove_cvref_t<decltype(*std::ranges::data(firstColumnBatch))>>::
765 BatchInputParameter(m_hStmt, 1, std::ranges::data(firstColumnBatch), rowCount, *this));
766 SQLUSMALLINT column = 1;
767 (RequireSuccess(SqlDataBinder<std::remove_cvref_t<decltype(*std::ranges::data(moreColumnBatches))>>::
768 BatchInputParameter(m_hStmt, ++column, std::ranges::data(moreColumnBatches), rowCount, *this)),
769 ...);
770 RequireSuccess(SQLExecute(m_hStmt));
771 ProcessPostExecuteCallbacks();
772 // clang-format on
773}
774
775template <SqlInputParameterBatchBinder FirstColumnBatch, std::ranges::range... MoreColumnBatches>
776inline LIGHTWEIGHT_FORCE_INLINE void SqlStatement::ExecuteBatch(FirstColumnBatch const& firstColumnBatch,
777 MoreColumnBatches const&... moreColumnBatches)
778{
779 // If the input ranges are contiguous and their element types are contiguous and supported as well,
780 // we can use the native batch execution.
781 if constexpr (SqlNativeBatchable<FirstColumnBatch, MoreColumnBatches...>)
782 ExecuteBatchNative(firstColumnBatch, moreColumnBatches...);
783 else
784 ExecuteBatchSoft(firstColumnBatch, moreColumnBatches...);
785}
786
787template <SqlInputParameterBatchBinder FirstColumnBatch, std::ranges::range... MoreColumnBatches>
788void SqlStatement::ExecuteBatchSoft(FirstColumnBatch const& firstColumnBatch, MoreColumnBatches const&... moreColumnBatches)
789{
790 if (m_expectedParameterCount != 1 + sizeof...(moreColumnBatches))
791 throw std::invalid_argument { "Invalid number of columns" };
792
793 auto const rowCount = std::ranges::size(firstColumnBatch);
794 if (!((std::size(moreColumnBatches) == rowCount) && ...))
795 throw std::invalid_argument { "Uneven number of rows" };
796
797 for (auto const rowIndex: std::views::iota(size_t { 0 }, rowCount))
798 {
799 std::apply(
800 [&]<SqlInputParameterBinder... ColumnValues>(ColumnValues const&... columnsInRow) {
801 SQLUSMALLINT column = 0;
802 ((++column, SqlDataBinder<ColumnValues>::InputParameter(m_hStmt, column, columnsInRow, *this)), ...);
803 RequireSuccess(SQLExecute(m_hStmt));
804 ProcessPostExecuteCallbacks();
805 },
806 std::make_tuple(std::ref(*std::ranges::next(std::ranges::begin(firstColumnBatch), rowIndex)),
807 std::ref(*std::ranges::next(std::ranges::begin(moreColumnBatches), rowIndex))...));
808 }
809}
810
811template <SqlGetColumnNativeType T>
812inline bool SqlStatement::GetColumn(SQLUSMALLINT column, T* result) const
813{
814 SQLLEN indicator {}; // TODO: Handle NULL values if we find out that we need them for our use-cases.
815 RequireSuccess(SqlDataBinder<T>::GetColumn(m_hStmt, column, result, &indicator, *this));
816 return indicator != SQL_NULL_DATA;
817}
818
819namespace detail
820{
821
822 template <typename T>
823 concept SqlNullableType = (std::same_as<T, SqlVariant> || IsSpecializationOf<std::optional, T>);
824
825} // end namespace detail
826
827template <SqlGetColumnNativeType T>
828inline T SqlStatement::GetColumn(SQLUSMALLINT column) const
829{
830 T result {};
831 SQLLEN indicator {};
832 RequireSuccess(SqlDataBinder<T>::GetColumn(m_hStmt, column, &result, &indicator, *this));
833 if constexpr (!detail::SqlNullableType<T>)
834 if (indicator == SQL_NULL_DATA)
835 throw std::runtime_error { "Column value is NULL" };
836 return result;
837}
838
839template <SqlGetColumnNativeType T>
840inline std::optional<T> SqlStatement::GetNullableColumn(SQLUSMALLINT column) const
841{
842 T result {};
843 SQLLEN indicator {}; // TODO: Handle NULL values if we find out that we need them for our use-cases.
844 RequireSuccess(SqlDataBinder<T>::GetColumn(m_hStmt, column, &result, &indicator, *this));
845 if (indicator == SQL_NULL_DATA)
846 return std::nullopt;
847 return { std::move(result) };
848}
849
850template <SqlGetColumnNativeType T>
851T SqlStatement::GetColumnOr(SQLUSMALLINT column, T&& defaultValue) const
852{
853 return GetNullableColumn<T>(column).value_or(std::forward<T>(defaultValue));
854}
855
856inline LIGHTWEIGHT_FORCE_INLINE void SqlStatement::ExecuteDirect(SqlQueryObject auto const& query,
857 std::source_location location)
858{
859 return ExecuteDirect(query.ToSql(), location);
860}
861
862template <typename Callable>
863 requires std::invocable<Callable, SqlMigrationQueryBuilder&>
864void SqlStatement::MigrateDirect(Callable const& callable, std::source_location location)
865{
866 auto migration = SqlMigrationQueryBuilder { Connection().QueryFormatter() };
867 callable(migration);
868 auto const queries = migration.GetPlan().ToSql();
869 for (auto const& query: queries)
870 {
871 ExecuteDirect(query, location);
872 CloseCursor();
873 }
874}
875
876template <typename T>
877 requires(!std::same_as<T, SqlVariant>)
878inline std::optional<T> SqlStatement::ExecuteDirectScalar(std::string_view const& query, std::source_location location)
879{
880 auto const _ = detail::Finally([this] { CloseCursor(); });
881 ExecuteDirect(query, location);
882 RequireSuccess(FetchRow());
883 return GetNullableColumn<T>(1);
884}
885
886template <typename T>
887 requires(std::same_as<T, SqlVariant>)
888inline T SqlStatement::ExecuteDirectScalar(std::string_view const& query, std::source_location location)
889{
890 auto const _ = detail::Finally([this] { CloseCursor(); });
891 ExecuteDirect(query, location);
892 RequireSuccess(FetchRow());
893 if (auto result = GetNullableColumn<T>(1); result.has_value())
894 return *result;
895 return SqlVariant { SqlNullValue };
896}
897
898template <typename T>
899 requires(!std::same_as<T, SqlVariant>)
900inline std::optional<T> SqlStatement::ExecuteDirectScalar(SqlQueryObject auto const& query, std::source_location location)
901{
902 return ExecuteDirectScalar<T>(query.ToSql(), location);
903}
904
905template <typename T>
906 requires(std::same_as<T, SqlVariant>)
907inline T SqlStatement::ExecuteDirectScalar(SqlQueryObject auto const& query, std::source_location location)
908{
909 return ExecuteDirectScalar<T>(query.ToSql(), location);
910}
911
912inline LIGHTWEIGHT_FORCE_INLINE void SqlStatement::CloseCursor() noexcept
913{
914 // SQLCloseCursor(m_hStmt);
915 SQLFreeStmt(m_hStmt, SQL_CLOSE);
917}
918
919inline LIGHTWEIGHT_FORCE_INLINE SqlResultCursor SqlStatement::GetResultCursor() noexcept
920{
921 return SqlResultCursor { *this };
922}
923
924inline LIGHTWEIGHT_FORCE_INLINE SqlVariantRowCursor SqlStatement::GetVariantRowCursor() noexcept
925{
926 return SqlVariantRowCursor { SqlResultCursor { *this } };
927}
928
929// }}}
930
931} // namespace Lightweight
Represents a connection to a SQL database.
LIGHTWEIGHT_API bool IsAlive() const noexcept
Tests if the connection is still active.
SqlQueryFormatter const & QueryFormatter() const noexcept
Retrieves a query formatter suitable for the SQL server being connected.
virtual void OnExecute(std::string_view const &query)=0
Invoked when a prepared query is executed.
static LIGHTWEIGHT_API SqlLogger & GetLogger()
Retrieves the currently configured logger.
virtual void OnFetchEnd()=0
Invoked when fetching is done.
void OnBindInputParameter(std::string_view const &name, T &&value)
Invoked when an input parameter is bound.
Definition SqlLogger.hpp:87
Query builder for building SQL migration queries.
Definition Migrate.hpp:218
API Entry point for building SQL queries.
Definition SqlQuery.hpp:29
API for reading an SQL query result set.
LIGHTWEIGHT_FORCE_INLINE bool GetColumn(SQLUSMALLINT column, T *result) const
LIGHTWEIGHT_FORCE_INLINE void BindOutputColumns(Args *... args)
LIGHTWEIGHT_FORCE_INLINE std::optional< T > GetNullableColumn(SQLUSMALLINT column) const
LIGHTWEIGHT_FORCE_INLINE T GetColumn(SQLUSMALLINT column) const
Retrieves the value of the column at the given index for the currently selected row.
T GetColumnOr(SQLUSMALLINT column, T &&defaultValue) const
LIGHTWEIGHT_FORCE_INLINE size_t NumColumnsAffected() const
Retrieves the number of columns affected by the last query.
LIGHTWEIGHT_FORCE_INLINE size_t NumRowsAffected() const
Retrieves the number of rows affected by the last query.
LIGHTWEIGHT_FORCE_INLINE bool FetchRow()
Fetches the next row of the result set.
SQL query result row iterator.
High level API for (prepared) raw SQL statements.
LIGHTWEIGHT_API void Prepare(std::string_view query) &
LIGHTWEIGHT_API SQLHSTMT NativeHandle() const noexcept
Retrieves the native handle of the statement.
LIGHTWEIGHT_API SqlQueryBuilder QueryAs(std::string_view const &table, std::string_view const &tableAlias) const
Creates a new query builder for the given table with an alias, compatible with the SQL server being c...
void MigrateDirect(Callable const &callable, std::source_location location=std::source_location::current())
Executes an SQL migration query, as created b the callback.
LIGHTWEIGHT_API SqlConnection & Connection() noexcept
Retrieves the connection associated with this statement.
void ExecuteBatch(FirstColumnBatch const &firstColumnBatch, MoreColumnBatches const &... moreColumnBatches)
void ExecuteBatchSoft(FirstColumnBatch const &firstColumnBatch, MoreColumnBatches const &... moreColumnBatches)
void Execute(Args const &... args)
Binds the given arguments to the prepared statement and executes it.
void ExecuteBatchNative(FirstColumnBatch const &firstColumnBatch, MoreColumnBatches const &... moreColumnBatches)
LIGHTWEIGHT_API SqlErrorInfo LastError() const
Retrieves the last error information with respect to this SQL statement handle.
SqlVariantRowCursor GetVariantRowCursor() noexcept
Retrieves the variant row cursor for reading an SQL query result of unknown column types and column c...
SqlResultCursor GetResultCursor() noexcept
Retrieves the result cursor for reading an SQL query result.
bool GetColumn(SQLUSMALLINT column, T *result) const
LIGHTWEIGHT_API SqlStatement()
Construct a new SqlStatement object, using a new connection, and connect to the default database.
void BindOutputColumnsToRecord(Records *... records)
T GetColumnOr(SQLUSMALLINT column, T &&defaultValue) const
LIGHTWEIGHT_API SqlStatement(std::nullopt_t)
Construct a new empty SqlStatement object. No SqlConnection is associated with this statement.
std::optional< T > ExecuteDirectScalar(std::string_view const &query, std::source_location location=std::source_location::current())
LIGHTWEIGHT_API void ExecuteDirect(std::string_view const &query, std::source_location location=std::source_location::current())
Executes the given query directly.
std::optional< T > GetNullableColumn(SQLUSMALLINT column) const
LIGHTWEIGHT_API SqlStatement(SqlConnection &relatedConnection)
Construct a new SqlStatement object, using the given connection.
void BindOutputColumns(Args *... args)
Represents an SQL query object, that provides a ToSql() method.
constexpr auto SqlNullValue
Represents an ODBC SQL error.
Definition SqlError.hpp:33
static SqlErrorInfo FromStatementHandle(SQLHSTMT hStmt)
Constructs an ODBC error info object from the given ODBC statement handle.
Definition SqlError.hpp:45
A non-owning reference to a raw column data for batch processing.
Represents a value that can be any of the supported SQL data types.