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