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