5#if defined(_WIN32) || defined(_WIN64)
10#include "DataBinder/Core.hpp"
11#include "DataBinder/SqlDate.hpp"
12#include "DataBinder/SqlDateTime.hpp"
13#include "DataBinder/SqlFixedString.hpp"
14#include "DataBinder/SqlGuid.hpp"
15#include "DataBinder/SqlNumeric.hpp"
16#include "DataBinder/StringInterface.hpp"
17#include "DataBinder/UnicodeConverter.hpp"
18#include "SqlConnection.hpp"
19#include "SqlQuery.hpp"
20#include "SqlQueryFormatter.hpp"
21#include "SqlServerType.hpp"
22#include "TracyProfiler.hpp"
33#include <source_location>
50template <
typename QueryObject>
52 { queryObject.ToSql() } -> std::convertible_to<std::string>;
56class SqlVariantRowCursor;
90 [[nodiscard]] LIGHTWEIGHT_API
bool IsAlive() const noexcept;
93 [[nodiscard]] LIGHTWEIGHT_API
bool IsPrepared() const noexcept;
96 [[nodiscard]] LIGHTWEIGHT_API
SqlConnection& Connection() noexcept;
99 [[nodiscard]] LIGHTWEIGHT_API
SqlConnection const& Connection() const noexcept;
102 [[nodiscard]] LIGHTWEIGHT_API
SqlErrorInfo LastError() const;
109 std::string_view
const& tableAlias)
const;
112 [[nodiscard]] LIGHTWEIGHT_API SQLHSTMT NativeHandle() const noexcept;
118 LIGHTWEIGHT_API
void Prepare(std::string_view query) &;
133 [[nodiscard]] std::
string const& PreparedQuery() const noexcept;
136 template <SqlInputParameterBinder Arg>
137 void BindInputParameter(SQLSMALLINT columnIndex, Arg const& arg);
140 template <SqlInputParameterBinder Arg, typename ColumnName>
141 void BindInputParameter(SQLSMALLINT columnIndex, Arg const& arg, ColumnName&& columnNameHint);
144 template <SqlInputParameterBinder... Args>
158 template <SqlInputParameterBatchBinder FirstColumnBatch, std::ranges::contiguous_range... MoreColumnBatches>
159 [[nodiscard]]
SqlResultCursor ExecuteBatchNative(FirstColumnBatch const& firstColumnBatch,
160 MoreColumnBatches const&... moreColumnBatches);
170 template <SqlInputParameterBatchBinder FirstColumnBatch, std::ranges::range... MoreColumnBatches>
171 [[nodiscard]]
SqlResultCursor ExecuteBatchSoft(FirstColumnBatch const& firstColumnBatch,
172 MoreColumnBatches const&... moreColumnBatches);
179 template <SqlInputParameterBatchBinder FirstColumnBatch, std::ranges::range... MoreColumnBatches>
181 MoreColumnBatches const&... moreColumnBatches);
211 template <std::ranges::contiguous_range Rows, typename... ColumnAccessors>
212 requires(sizeof...(ColumnAccessors) >= 1
213 && (std::invocable<ColumnAccessors const&, std::ranges::range_value_t<Rows> const&> && ...))
214 [[nodiscard]]
SqlResultCursor ExecuteBatch(Rows const& rows, ColumnAccessors const&... accessors);
218 ExecuteDirect(std::string_view const& query, std::source_location location = std::source_location::current());
222 std::source_location location = std::source_location::current());
236 [[nodiscard]] LIGHTWEIGHT_API
RowArrayCursor ExecuteBatchFetch(std::string_view query, std::
size_t arrayDepth);
239 template <typename Callable>
241 void MigrateDirect(Callable const& callable, std::source_location location = std::source_location::current());
245 template <typename T>
247 [[nodiscard]] std::optional<T> ExecuteDirectScalar(std::string_view const& query,
248 std::source_location location = std::source_location::current());
251 template <typename T>
253 [[nodiscard]] T ExecuteDirectScalar(std::string_view const& query,
254 std::source_location location = std::source_location::current());
258 template <typename T>
260 [[nodiscard]] std::optional<T> ExecuteDirectScalar(
SqlQueryObject auto const& query,
261 std::source_location location = std::source_location::current());
264 template <typename T>
266 [[nodiscard]] T ExecuteDirectScalar(
SqlQueryObject auto const& query,
267 std::source_location location = std::source_location::current());
270 [[nodiscard]] LIGHTWEIGHT_API
size_t LastInsertId(std::string_view tableName);
276 [[nodiscard]] LIGHTWEIGHT_API
size_t NumRowsAffected() const;
277 [[nodiscard]] LIGHTWEIGHT_API
size_t NumColumnsAffected() const;
278 [[nodiscard]] LIGHTWEIGHT_API
bool FetchRow();
279 [[nodiscard]] LIGHTWEIGHT_API std::expected<
bool,
SqlErrorInfo> TryFetchRow(
280 std::source_location location = std::source_location::current()) noexcept;
281 void CloseCursor() noexcept;
286 template <SqlOutputColumnBinder... Args>
287 void BindOutputColumns(Args*... args);
293 template <typename... Records>
294 requires(((std::is_class_v<Records> && std::is_aggregate_v<Records>) && ...))
295 void BindOutputColumnsToRecord(Records*... records);
301 template <SqlOutputColumnBinder T>
302 void BindOutputColumn(SQLUSMALLINT columnIndex, T* arg);
304 template <SqlGetColumnNativeType T>
305 [[nodiscard]]
bool GetColumn(SQLUSMALLINT column, T* result) const;
307 template <SqlGetColumnNativeType T>
308 [[nodiscard]] T GetColumn(SQLUSMALLINT column) const;
312 template <std::ranges::contiguous_range Rows, typename... ColumnAccessors>
313 [[nodiscard]]
SqlResultCursor ExecuteBatchNativeRowWise(Rows const& rows, ColumnAccessors const&... accessors);
317 template <std::ranges::contiguous_range Rows, typename... ColumnAccessors>
318 [[nodiscard]]
SqlResultCursor ExecuteBatchSoftRowMajor(Rows const& rows, ColumnAccessors const&... accessors);
335 template <typename Record, typename... ColumnAccessors>
336 void FetchAllRowWise(std::vector<Record>& out, std::
size_t arrayDepth, ColumnAccessors const&... accessors);
341 template <typename ValueType>
342 [[nodiscard]] SQLLEN* BindRowWiseOutputColumn(SQLUSMALLINT column,
344 std::
size_t rowStride,
351 template <typename Value>
352 void BindRowWiseValue(SQLUSMALLINT column,
void* base0, SQLLEN* indicators);
356 template <typename ValueType>
357 static
void FinalizeRowWiseOutputColumn(
void* base0,
358 std::
size_t rowStride,
359 std::
size_t rowCount,
360 SQLLEN const* indicators) noexcept;
362 template <SqlGetColumnNativeType T>
363 [[nodiscard]] std::optional<T> GetNullableColumn(SQLUSMALLINT column) const;
365 template <SqlGetColumnNativeType T>
366 [[nodiscard]] T GetColumnOr(SQLUSMALLINT column, T&& defaultValue) const;
368 LIGHTWEIGHT_API
void RequireSuccess(SQLRETURN error,
369 std::source_location sourceLocation = std::source_location::current()) const;
370 LIGHTWEIGHT_API
void PlanPostExecuteCallback(std::function<
void()>&& cb) override;
371 LIGHTWEIGHT_API
void PlanPostProcessOutputColumn(std::function<
void()>&& cb) override;
372 [[nodiscard]] LIGHTWEIGHT_API SqlServerType ServerType() const noexcept override;
373 [[nodiscard]] LIGHTWEIGHT_API std::
string const& DriverName() const noexcept override;
374 LIGHTWEIGHT_API
void ProcessPostExecuteCallbacks();
376 LIGHTWEIGHT_API SQLLEN* ProvideInputIndicator() override;
377 LIGHTWEIGHT_API SQLLEN* ProvideInputIndicators(
size_t rowCount) override;
378 LIGHTWEIGHT_API std::
byte* ProvideBatchStagingBuffer(std::
size_t byteCount) override;
379 LIGHTWEIGHT_API
void ClearBatchIndicators();
382 LIGHTWEIGHT_API
void ResetParameterArrayBinding() noexcept;
385 LIGHTWEIGHT_API
void RequireExecuteSucceededOrNoData(
386 SQLRETURN result, std::source_location sourceLocation = std::source_location::current()) const;
389 LIGHTWEIGHT_API
void RequireSuccessfulBatchExecute(
391 SQLULEN processedCount,
392 SQLULEN expectedCount,
393 std::source_location sourceLocation = std::source_location::current()) const;
394 LIGHTWEIGHT_API
void RequireIndicators();
395 LIGHTWEIGHT_API SQLLEN* GetIndicatorForColumn(SQLUSMALLINT column) noexcept;
404 [[nodiscard]] std::
size_t EffectivePrefetchDepth() const noexcept;
406 void ArmPrefetchOnFirstFetch() noexcept;
409 [[nodiscard]] std::expected<
bool,
SqlErrorInfo> FetchRowPrefetched() noexcept;
411 [[nodiscard]] LIGHTWEIGHT_API
bool IsPrefetchActive() const noexcept;
413 [[nodiscard]] LIGHTWEIGHT_API
RowArrayCursor const& PrefetchCursorRef() const noexcept;
415 [[nodiscard]] LIGHTWEIGHT_API std::
size_t PrefetchRowInBlock() const noexcept;
418 [[nodiscard]] LIGHTWEIGHT_API
bool ShouldRecordPrefetchBinding() const noexcept;
420 LIGHTWEIGHT_API
void ResetPrefetchBindings() noexcept;
423 LIGHTWEIGHT_API
void MarkPrefetchBindingUnsupported() noexcept;
432 LIGHTWEIGHT_API
void RecordPrefetchColumn(SQLUSMALLINT column,
433 std::function<
void()> scatter,
434 std::function<
void()> deferredBind);
436 LIGHTWEIGHT_API
void ResetPrefetchState() noexcept;
440 SQLUSMALLINT column) const;
442 template <typename T>
443 [[nodiscard]] T ConvertCell(
RowArrayCursor const& cursor, std::
size_t row, SQLUSMALLINT column) const;
448 LIGHTWEIGHT_API
void RequirePrefetchColumnInRange(
RowArrayCursor const& cursor, SQLUSMALLINT column) const;
452 template <SqlOutputColumnBinder T>
453 void RecordPrefetchOutputColumn(SQLUSMALLINT column, T* arg);
457 std::unique_ptr<Data,
void (*)(Data*)> m_data;
460 std::string m_preparedQuery;
461 std::optional<SQLSMALLINT> m_numColumns;
462 SQLSMALLINT m_expectedParameterCount {};
475 SqlResultCursor() =
delete;
476 SqlResultCursor(SqlResultCursor
const&) =
delete;
477 SqlResultCursor& operator=(SqlResultCursor
const&) =
delete;
481 m_stmt { other.m_stmt }
483 other.m_stmt =
nullptr;
491 m_stmt = other.m_stmt;
492 other.m_stmt =
nullptr;
501 m_stmt->CloseCursor();
509 return m_stmt->NumRowsAffected();
515 return m_stmt->NumColumnsAffected();
521 template <SqlOutputColumnBinder... Args>
524 m_stmt->BindOutputColumns(args...);
528 template <SqlOutputColumnBinder T>
531 m_stmt->BindOutputColumn(columnIndex, arg);
535 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE
bool FetchRow()
537 return m_stmt->FetchRow();
541 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE std::expected<bool, SqlErrorInfo>
TryFetchRow(
542 std::source_location location = std::source_location::current()) noexcept
544 return m_stmt->TryFetchRow(location);
548 template <
typename... Records>
549 requires(((std::is_class_v<Records> && std::is_aggregate_v<Records>) && ...))
552 m_stmt->BindOutputColumnsToRecord(records...);
561 template <
typename Record,
typename... ColumnAccessors>
563 std::size_t arrayDepth,
564 ColumnAccessors
const&... accessors)
566 m_stmt->FetchAllRowWise(out, arrayDepth, accessors...);
572 template <SqlGetColumnNativeType T>
573 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE
bool GetColumn(SQLUSMALLINT column, T* result)
const
575 return m_stmt->GetColumn<T>(column, result);
579 template <SqlGetColumnNativeType T>
580 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE T
GetColumn(SQLUSMALLINT column)
const
582 return m_stmt->GetColumn<T>(column);
588 template <SqlGetColumnNativeType T>
589 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE std::optional<T>
GetNullableColumn(SQLUSMALLINT column)
const
591 return m_stmt->GetNullableColumn<T>(column);
597 template <SqlGetColumnNativeType T>
598 [[nodiscard]] T
GetColumnOr(SQLUSMALLINT column, T&& defaultValue)
const
600 return m_stmt->GetColumnOr(column, std::forward<T>(defaultValue));
618 using std::runtime_error::runtime_error;
652 static constexpr std::size_t MaxCharColumnBytes = 8192;
658 static constexpr std::size_t MemoryBudgetBytes = 4 * 1024 * 1024;
662 static constexpr std::size_t MinArrayDepth = 16;
684 [[nodiscard]] LIGHTWEIGHT_API std::
size_t FetchArray();
687 [[nodiscard]] LIGHTWEIGHT_API std::
size_t ColumnCount() const noexcept;
692 [[nodiscard]] LIGHTWEIGHT_API std::
size_t ArrayDepth() const noexcept;
698 [[nodiscard]] LIGHTWEIGHT_API std::optional<std::int64_t> GetI64(std::
size_t rowInBatch, SQLUSMALLINT column) const;
704 [[nodiscard]] LIGHTWEIGHT_API std::optional<
double> GetF64(std::
size_t rowInBatch, SQLUSMALLINT column) const;
717 [[nodiscard]] LIGHTWEIGHT_API std::optional<std::
string> GetString(std::
size_t rowInBatch, SQLUSMALLINT column) const;
723 [[nodiscard]] LIGHTWEIGHT_API std::optional<
SqlDate> GetDate(std::
size_t rowInBatch, SQLUSMALLINT column) const;
730 [[nodiscard]] LIGHTWEIGHT_API std::optional<
SqlDateTime> GetTimestamp(std::
size_t rowInBatch, SQLUSMALLINT column) const;
737 [[nodiscard]] LIGHTWEIGHT_API std::optional<
SqlGuid> GetGuid(std::
size_t rowInBatch, SQLUSMALLINT column) const;
763 [[nodiscard]] LIGHTWEIGHT_API SQLSMALLINT
ColumnSqlType(SQLUSMALLINT column)
const;
769 [[nodiscard]] LIGHTWEIGHT_API
bool IsCellNull(std::size_t rowInBatch, SQLUSMALLINT column)
const;
776 SQLSMALLINT sqlType {};
777 std::size_t elementWidth {};
778 std::vector<char> buffer;
779 std::vector<SQLLEN> indicators;
782 void ResetStatementState() noexcept;
787 [[nodiscard]]
char const* CheckedCell(std::
size_t rowInBatch,
790 char const* accessorName) const;
792 SqlStatement* m_stmt;
793 std::
size_t m_arrayDepth;
794 std::
size_t m_lastFetched = 0;
795 std::vector<BoundColumn> m_columns;
796 SQLULEN m_rowsFetched = 0;
797 std::vector<SQLUSMALLINT> m_rowStatus;
800struct [[nodiscard]] SqlSentinelIterator
804class [[nodiscard]] SqlVariantRowIterator
807 explicit SqlVariantRowIterator(SqlSentinelIterator )
noexcept:
812 explicit SqlVariantRowIterator(SqlResultCursor& cursor)
noexcept:
813 _numResultColumns {
static_cast<SQLUSMALLINT
>(cursor.NumColumnsAffected()) },
816 _row.reserve(_numResultColumns);
820 SqlVariantRow& operator*() noexcept
825 SqlVariantRow
const& operator*() const noexcept
830 SqlVariantRowIterator& operator++() noexcept
832 _end = !_cursor->FetchRow();
836 for (
auto const i: std::views::iota(SQLUSMALLINT(1), SQLUSMALLINT(_numResultColumns + 1)))
837 _row.emplace_back(_cursor->GetColumn<SqlVariant>(i));
842 bool operator!=(SqlSentinelIterator )
const noexcept
847 bool operator!=(SqlVariantRowIterator
const& )
const noexcept
854 SQLUSMALLINT _numResultColumns = 0;
855 SqlResultCursor* _cursor;
859class [[nodiscard]] SqlVariantRowCursor
862 explicit SqlVariantRowCursor(SqlResultCursor&& cursor):
863 _resultCursor { std::move(cursor) }
867 SqlVariantRowIterator begin() noexcept
869 return SqlVariantRowIterator { _resultCursor };
872 static SqlSentinelIterator end() noexcept
874 return SqlSentinelIterator {};
878 SqlResultCursor _resultCursor;
906 _connection { &conn }
913 using difference_type = bool;
914 using value_type = T;
916 iterator& operator++()
920 _is_end = !_cursor->FetchRow();
927 LIGHTWEIGHT_FORCE_INLINE value_type operator*() noexcept
932 auto tmp = _cursor->GetColumn<
typename RecordMemberTypeOf<I, value_type>::ValueType>(I + 1);
939 LIGHTWEIGHT_FORCE_INLINE
constexpr bool operator!=(iterator
const& other)
const noexcept
941 return _is_end != other._is_end;
944 constexpr iterator(std::default_sentinel_t )
noexcept:
946 _cursor { std::nullopt }
950 explicit iterator(SqlConnection& conn):
951 _stmt { std::make_unique<SqlStatement>(conn) },
952 _cursor { std::nullopt }
956 LIGHTWEIGHT_FORCE_INLINE SqlStatement& Statement() noexcept
961 void SetCursor(SqlResultCursor cursor)
noexcept
963 _cursor.emplace(std::move(cursor));
967 bool _is_end =
false;
968 std::unique_ptr<SqlStatement> _stmt;
969 std::optional<SqlResultCursor> _cursor;
975 auto it = iterator { *_connection };
976 auto& stmt = it.Statement();
977 stmt.Prepare(it.Statement().Query(RecordTableName<T>).Select().template Fields<T>().All());
978 it.SetCursor(stmt.Execute());
986 return iterator { std::default_sentinel };
994inline LIGHTWEIGHT_FORCE_INLINE
bool SqlStatement::IsAlive() const noexcept
996 return m_connection && m_connection->
IsAlive() && m_hStmt !=
nullptr;
999inline LIGHTWEIGHT_FORCE_INLINE
bool SqlStatement::IsPrepared() const noexcept
1001 return !m_preparedQuery.empty();
1004inline LIGHTWEIGHT_FORCE_INLINE
SqlConnection& SqlStatement::Connection() noexcept
1006 return *m_connection;
1009inline LIGHTWEIGHT_FORCE_INLINE
SqlConnection const& SqlStatement::Connection() const noexcept
1011 return *m_connection;
1014inline LIGHTWEIGHT_FORCE_INLINE
SqlErrorInfo SqlStatement::LastError()
const
1016 return SqlErrorInfo::FromStatementHandle(m_hStmt);
1019inline LIGHTWEIGHT_FORCE_INLINE SQLHSTMT SqlStatement::NativeHandle() const noexcept
1024inline LIGHTWEIGHT_FORCE_INLINE
void SqlStatement::Prepare(
SqlQueryObject auto const& queryObject) &
1026 Prepare(queryObject.ToSql());
1031 return Prepare(queryObject.ToSql());
1034inline LIGHTWEIGHT_FORCE_INLINE std::string
const& SqlStatement::PreparedQuery() const noexcept
1036 return m_preparedQuery;
1040template <SqlOutputColumnBinder... Args>
1041inline LIGHTWEIGHT_FORCE_INLINE
void SqlStatement::BindOutputColumns(Args*... args)
1043 if (ShouldRecordPrefetchBinding())
1048 ResetPrefetchBindings();
1050 ((++i, RecordPrefetchOutputColumn<Args>(i, args)), ...);
1054 RequireIndicators();
1057 ((++i, RequireSuccess(SqlDataBinder<Args>::OutputColumn(m_hStmt, i, args, GetIndicatorForColumn(i), *
this))), ...);
1060template <
typename... Records>
1061 requires(((std::is_class_v<Records> && std::is_aggregate_v<Records>) && ...))
1062void SqlStatement::BindOutputColumnsToRecord(Records*... records)
1064 if (ShouldRecordPrefetchBinding())
1066 ResetPrefetchBindings();
1069 [
this, &i]<
size_t I,
typename FieldType>(FieldType& value) {
1071 this->RecordPrefetchOutputColumn<FieldType>(i, &value);
1077 RequireIndicators();
1081 [
this, &i]<
size_t I,
typename FieldType>(FieldType& value) {
1083 RequireSuccess(SqlDataBinder<FieldType>::OutputColumn(
1084 m_hStmt, i, &value, GetIndicatorForColumn(i), *
this));
1090template <SqlOutputColumnBinder T>
1091inline LIGHTWEIGHT_FORCE_INLINE
void SqlStatement::BindOutputColumn(SQLUSMALLINT columnIndex, T* arg)
1095 if (ShouldRecordPrefetchBinding())
1097 RecordPrefetchOutputColumn<T>(columnIndex, arg);
1101 RequireIndicators();
1103 RequireSuccess(SqlDataBinder<T>::OutputColumn(m_hStmt, columnIndex, arg, GetIndicatorForColumn(columnIndex), *
this));
1107template <SqlInputParameterBinder Arg>
1108inline LIGHTWEIGHT_FORCE_INLINE
void SqlStatement::BindInputParameter(SQLSMALLINT columnIndex, Arg
const& arg)
1111 m_expectedParameterCount = (std::numeric_limits<
decltype(m_expectedParameterCount)>::max)();
1112 RequireSuccess(SqlDataBinder<Arg>::InputParameter(m_hStmt,
static_cast<SQLUSMALLINT
>(columnIndex), arg, *
this));
1116template <SqlInputParameterBinder Arg,
typename ColumnName>
1117inline LIGHTWEIGHT_FORCE_INLINE
void SqlStatement::BindInputParameter(SQLSMALLINT columnIndex,
1119 ColumnName&& columnNameHint)
1121 SqlLogger::GetLogger().OnBindInputParameter(std::forward<ColumnName>(columnNameHint), arg);
1122 BindInputParameter(columnIndex, arg);
1125template <SqlInputParameterBinder... Args>
1132 ZoneScopedN(
"SqlStatement::Execute");
1133 ZoneTextObject(m_preparedQuery);
1134 SqlLogger::GetLogger().OnExecute(m_preparedQuery);
1136 if (!(m_expectedParameterCount == (std::numeric_limits<
decltype(m_expectedParameterCount)>::max)()
1137 &&
sizeof...(args) == 0)
1138 && !(m_expectedParameterCount ==
sizeof...(args)))
1139 throw std::invalid_argument {
"Invalid argument count" };
1143 SqlLogger::GetLogger().OnBindInputParameter({}, args),
1144 RequireSuccess(SqlDataBinder<Args>::InputParameter(m_hStmt, i, args, *
this))),
1147 auto const result = SQLExecute(m_hStmt);
1149 if (result != SQL_NO_DATA && result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO)
1150 throw SqlException(SqlErrorInfo::FromStatementHandle(m_hStmt), std::source_location::current());
1152 ProcessPostExecuteCallbacks();
1157template <
typename T>
1158concept SqlNativeContiguousValueConcept =
1159 std::same_as<T, bool>
1160 || std::same_as<T, char>
1161 || std::same_as<T, unsigned char>
1162 || std::same_as<T, wchar_t>
1163 || std::same_as<T, std::int16_t>
1164 || std::same_as<T, std::uint16_t>
1165 || std::same_as<T, std::int32_t>
1166 || std::same_as<T, std::uint32_t>
1167 || std::same_as<T, std::int64_t>
1168 || std::same_as<T, std::uint64_t>
1169 || std::same_as<T, float>
1170 || std::same_as<T, double>
1171 || std::same_as<T, SqlDate>
1172 || std::same_as<T, SqlTime>
1173 || std::same_as<T, SqlDateTime>
1174 || std::same_as<T, SqlFixedString<T::Capacity, typename T::value_type, T::PostRetrieveOperation>>;
1176template <
typename FirstColumnBatch,
typename... MoreColumnBatches>
1177concept SqlNativeBatchable =
1178 std::ranges::contiguous_range<FirstColumnBatch>
1179 && (std::ranges::contiguous_range<MoreColumnBatches> && ...)
1180 && SqlNativeContiguousValueConcept<std::ranges::range_value_t<FirstColumnBatch>>
1181 && (SqlNativeContiguousValueConcept<std::ranges::range_value_t<MoreColumnBatches>> && ...);
1188template <
typename V>
1194template <
typename V>
1200template <
typename V>
1213template <
typename V>
1222template <
typename V>
1224 requires(SQLHSTMT stmt, SQLUSMALLINT column, V
const* elem0, std::size_t n, SqlDataBinderCallback& cb) {
1225 { SqlDataBinder<V>::BatchRowWiseInputParameter(stmt, column, elem0, n, n, cb) } -> std::same_as<SQLRETURN>;
1228template <SqlInputParameterBatchBinder FirstColumnBatch, std::ranges::contiguous_range... MoreColumnBatches>
1230 MoreColumnBatches
const&... moreColumnBatches)
1232 static_assert(SqlNativeBatchable<FirstColumnBatch, MoreColumnBatches...>,
1233 "Must be a supported native contiguous element type.");
1235 ZoneScopedN(
"SqlStatement::ExecuteBatchNative");
1236 ZoneTextObject(m_preparedQuery);
1238 if (m_expectedParameterCount != 1 +
sizeof...(moreColumnBatches))
1239 throw std::invalid_argument {
"Invalid number of columns" };
1241 auto const rowCount = std::ranges::size(firstColumnBatch);
1242 ZoneValue(rowCount);
1243 if (!((std::size(moreColumnBatches) == rowCount) && ...))
1244 throw std::invalid_argument {
"Uneven number of rows" };
1246 size_t rowStart = 0;
1250 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_PARAMSET_SIZE, (SQLPOINTER) rowCount, 0));
1251 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_PARAM_BIND_OFFSET_PTR, &rowStart, 0));
1252 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_PARAM_BIND_TYPE, SQL_PARAM_BIND_BY_COLUMN, 0));
1253 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_PARAM_OPERATION_PTR, SQL_PARAM_PROCEED, 0));
1254 ClearBatchIndicators();
1255 RequireSuccess(SqlDataBinder<std::remove_cvref_t<
decltype(*std::ranges::data(firstColumnBatch))>>::
1256 BatchInputParameter(m_hStmt, 1, std::ranges::data(firstColumnBatch), rowCount, *
this));
1257 SQLUSMALLINT column = 1;
1258 (RequireSuccess(SqlDataBinder<std::remove_cvref_t<
decltype(*std::ranges::data(moreColumnBatches))>>::
1259 BatchInputParameter(m_hStmt, ++column, std::ranges::data(moreColumnBatches), rowCount, *
this)),
1261 RequireSuccess(SQLExecute(m_hStmt));
1262 ProcessPostExecuteCallbacks();
1268template <SqlInputParameterBatchBinder FirstColumnBatch, std::ranges::range... MoreColumnBatches>
1269inline LIGHTWEIGHT_FORCE_INLINE
SqlResultCursor SqlStatement::ExecuteBatch(FirstColumnBatch
const& firstColumnBatch,
1270 MoreColumnBatches
const&... moreColumnBatches)
1274 if constexpr (SqlNativeBatchable<FirstColumnBatch, MoreColumnBatches...>)
1275 return ExecuteBatchNative(firstColumnBatch, moreColumnBatches...);
1277 return ExecuteBatchSoft(firstColumnBatch, moreColumnBatches...);
1280template <SqlInputParameterBatchBinder FirstColumnBatch, std::ranges::range... MoreColumnBatches>
1282 MoreColumnBatches
const&... moreColumnBatches)
1284 ZoneScopedN(
"SqlStatement::ExecuteBatchSoft");
1285 ZoneTextObject(m_preparedQuery);
1287 if (m_expectedParameterCount != 1 +
sizeof...(moreColumnBatches))
1288 throw std::invalid_argument {
"Invalid number of columns" };
1290 auto const rowCount = std::ranges::size(firstColumnBatch);
1291 ZoneValue(rowCount);
1292 if (!((std::size(moreColumnBatches) == rowCount) && ...))
1293 throw std::invalid_argument {
"Uneven number of rows" };
1295 for (
auto const rowIndex: std::views::iota(
size_t { 0 }, rowCount))
1298 [&]<SqlInputParameterBinder... ColumnValues>(ColumnValues
const&... columnsInRow) {
1299 SQLUSMALLINT column = 0;
1300 ((++column, SqlDataBinder<ColumnValues>::InputParameter(m_hStmt, column, columnsInRow, *this)), ...);
1301 RequireSuccess(SQLExecute(m_hStmt));
1302 ProcessPostExecuteCallbacks();
1305 std::ref(*std::ranges::next(std::ranges::begin(firstColumnBatch),
static_cast<std::ptrdiff_t
>(rowIndex))),
1307 *std::ranges::next(std::ranges::begin(moreColumnBatches),
static_cast<std::ptrdiff_t
>(rowIndex)))...));
1312template <std::ranges::contiguous_range Rows,
typename... ColumnAccessors>
1313 requires(
sizeof...(ColumnAccessors) >= 1
1314 && (std::invocable<ColumnAccessors
const&, std::ranges::range_value_t<Rows>
const&> && ...))
1315SqlResultCursor SqlStatement::ExecuteBatch(Rows
const& rows, ColumnAccessors
const&... accessors)
1317 ZoneScopedN(
"SqlStatement::ExecuteBatch(row-major)");
1318 ZoneTextObject(m_preparedQuery);
1320 using RowElem = std::ranges::range_value_t<Rows>;
1322 auto const rowCount = std::ranges::size(rows);
1326 if (m_expectedParameterCount !=
static_cast<SQLSMALLINT
>(
sizeof...(accessors)))
1327 throw std::invalid_argument {
"Invalid number of columns" };
1333 constexpr bool allColumnsRowBindable =
1335 constexpr bool allAccessorsReturnReference =
1336 (std::is_reference_v<std::invoke_result_t<ColumnAccessors const&, RowElem const&>> && ...);
1337 constexpr bool anyStridedIndicatorColumn =
1339 constexpr bool indicatorAlignmentSatisfied = (
sizeof(RowElem) %
alignof(SQLLEN)) == 0;
1341 if constexpr (allColumnsRowBindable && allAccessorsReturnReference
1342 && (!anyStridedIndicatorColumn || indicatorAlignmentSatisfied))
1344 auto const* rowData = std::ranges::data(rows);
1348 auto const accessorStrideMatchesRow = [&](
auto const& accessor)
noexcept ->
bool {
1349 auto const* first =
reinterpret_cast<std::byte const*
>(std::addressof(accessor(rowData[0])));
1350 auto const* second =
reinterpret_cast<std::byte const*
>(std::addressof(accessor(rowData[1])));
1351 return static_cast<std::size_t
>(second - first) ==
sizeof(RowElem);
1353 bool const rowStrideOk = rowCount < 2 || (accessorStrideMatchesRow(accessors) && ...);
1355 if (m_connection->SupportsNativeRowBatch() && rowStrideOk)
1356 return ExecuteBatchNativeRowWise(rows, accessors...);
1359 return ExecuteBatchSoftRowMajor(rows, accessors...);
1362template <std::ranges::contiguous_range Rows,
typename... ColumnAccessors>
1363SqlResultCursor SqlStatement::ExecuteBatchNativeRowWise(Rows
const& rows, ColumnAccessors
const&... accessors)
1365 ZoneScopedN(
"SqlStatement::ExecuteBatchNativeRowWise");
1366 ZoneTextObject(m_preparedQuery);
1368 using RowElem = std::ranges::range_value_t<Rows>;
1369 auto const rowCount = std::ranges::size(rows);
1370 ZoneValue(rowCount);
1371 auto const* rowData = std::ranges::data(rows);
1375 SQLULEN processedCount = rowCount;
1381 auto const restoreParameterBinding = detail::Finally([
this] {
1382 ResetParameterArrayBinding();
1383 ClearBatchIndicators();
1389 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_PARAMSET_SIZE, (SQLPOINTER) rowCount, 0));
1391 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_PARAM_BIND_TYPE, (SQLPOINTER)
sizeof(RowElem), 0));
1392 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_PARAM_BIND_OFFSET_PTR,
nullptr, 0));
1393 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_PARAM_OPERATION_PTR, SQL_PARAM_PROCEED, 0));
1394 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_PARAMS_PROCESSED_PTR, &processedCount, 0));
1397 SQLUSMALLINT column = 0;
1398 auto const bindColumn = [&](
auto const& accessor) {
1400 using ValueType = std::remove_cvref_t<
decltype(accessor(rowData[0]))>;
1403 if constexpr (SqlHasRowWiseBatchBinder<ValueType>)
1404 RequireSuccess(SqlDataBinder<ValueType>::BatchRowWiseInputParameter(
1405 m_hStmt, column, std::addressof(accessor(rowData[0])),
sizeof(RowElem), rowCount, *
this));
1407 RequireSuccess(SqlDataBinder<ValueType>::InputParameter(m_hStmt, column, accessor(rowData[0]), *
this));
1409 (bindColumn(accessors), ...);
1411 SqlLogger::GetLogger().OnExecuteBatch();
1414 auto const executeResult = SQLExecute(m_hStmt);
1415 RequireSuccessfulBatchExecute(executeResult, processedCount,
static_cast<SQLULEN
>(rowCount));
1416 ProcessPostExecuteCallbacks();
1418 return SqlResultCursor { *
this };
1421template <std::ranges::contiguous_range Rows,
typename... ColumnAccessors>
1422SqlResultCursor SqlStatement::ExecuteBatchSoftRowMajor(Rows
const& rows, ColumnAccessors
const&... accessors)
1424 ZoneScopedN(
"SqlStatement::ExecuteBatchSoftRowMajor");
1425 ZoneTextObject(m_preparedQuery);
1427 auto const* rowData = std::ranges::data(rows);
1428 auto const rowCount = std::ranges::size(rows);
1429 ZoneValue(rowCount);
1431 for (
auto const rowIndex: std::views::iota(std::size_t { 0 }, rowCount))
1433 auto const& row = rowData[rowIndex];
1434 SQLUSMALLINT column = 0;
1436 RequireSuccess(SqlDataBinder<std::remove_cvref_t<
decltype(accessors(row))>>::InputParameter(
1437 m_hStmt, column, accessors(row), *
this))),
1439 SqlLogger::GetLogger().OnExecute(m_preparedQuery);
1440 RequireExecuteSucceededOrNoData(SQLExecute(m_hStmt));
1441 ProcessPostExecuteCallbacks();
1444 return SqlResultCursor { *
this };
1447template <
typename Value>
1448void SqlStatement::BindRowWiseValue(SQLUSMALLINT column,
void* base0, SQLLEN* indicators)
1450 if constexpr (IsSqlFixedString<Value>)
1457 RequireSuccess(SQLBindCol(m_hStmt,
1460 (SQLPOINTER) SqlBasicStringOperations<Value>::Data(
static_cast<Value*
>(base0)),
1461 static_cast<SQLLEN
>(Value::Capacity) + 1,
1468 RequireSuccess(SqlDataBinder<Value>::OutputColumn(m_hStmt, column,
static_cast<Value*
>(base0), indicators, *
this));
1472template <
typename ValueType>
1473SQLLEN* SqlStatement::BindRowWiseOutputColumn(SQLUSMALLINT column,
void* base0, std::size_t rowStride, std::size_t depth)
1479 auto*
const indicatorBytes = ProvideBatchStagingBuffer(((depth - 1) * rowStride) +
sizeof(SQLLEN));
1480 auto*
const indicators =
reinterpret_cast<SQLLEN*
>(indicatorBytes);
1482 if constexpr (SqlIsStdOptional<ValueType>)
1484 using Inner =
typename ValueType::value_type;
1485 auto*
const optBytes =
static_cast<std::byte*
>(base0);
1488 for (
auto const i: std::views::iota(std::size_t { 0 }, depth))
1489 reinterpret_cast<ValueType*>(optBytes + (i * rowStride))->emplace();
1492 auto*
const contained0 =
reinterpret_cast<Inner*
>(optBytes + detail::OptionalValueOffset<Inner>());
1493 BindRowWiseValue<Inner>(column, contained0, indicators);
1497 BindRowWiseValue<ValueType>(column, base0, indicators);
1502template <
typename ValueType>
1503void SqlStatement::FinalizeRowWiseOutputColumn(
void* base0,
1504 std::size_t rowStride,
1505 std::size_t rowCount,
1506 SQLLEN
const* indicators)
noexcept
1508 auto const indicatorAt = [&](std::size_t i)
noexcept {
1509 return *
reinterpret_cast<SQLLEN const*
>(
reinterpret_cast<std::byte const*
>(indicators) + (i * rowStride));
1512 if constexpr (SqlIsStdOptional<ValueType>)
1514 using Inner =
typename ValueType::value_type;
1515 auto*
const optBytes =
static_cast<std::byte*
>(base0);
1516 for (
auto const i: std::views::iota(std::size_t { 0 }, rowCount))
1518 auto*
const optional =
reinterpret_cast<ValueType*
>(optBytes + (i * rowStride));
1519 if (indicatorAt(i) == SQL_NULL_DATA)
1521 else if constexpr (IsSqlFixedString<Inner>)
1523 SqlBasicStringOperations<Inner>::PostProcessOutputColumn(std::addressof(**optional), indicatorAt(i));
1527 else if constexpr (IsSqlFixedString<ValueType>)
1529 auto*
const base =
static_cast<std::byte*
>(base0);
1530 for (
auto const i: std::views::iota(std::size_t { 0 }, rowCount))
1531 SqlBasicStringOperations<ValueType>::PostProcessOutputColumn(
1532 reinterpret_cast<ValueType*>(base + (i * rowStride)), indicatorAt(i));
1538template <
typename Record,
typename... ColumnAccessors>
1539void SqlStatement::FetchAllRowWise(std::vector<Record>& out, std::size_t arrayDepth, ColumnAccessors
const&... accessors)
1541 ZoneScopedN(
"SqlStatement::FetchAllRowWise");
1542 ZoneTextObject(m_preparedQuery);
1544 static_assert(
sizeof...(ColumnAccessors) >= 1,
"FetchAllRowWise requires at least one column accessor");
1545 constexpr std::size_t columnCount =
sizeof...(ColumnAccessors);
1552 auto const perRow =
sizeof(Record) * (1 + columnCount);
1553 auto const budgetDepth = RowArrayCursor::MemoryBudgetBytes / std::max<std::size_t>(perRow, 1);
1554 auto const minDepth = std::min(RowArrayCursor::MinArrayDepth, arrayDepth);
1555 arrayDepth = std::clamp(budgetDepth, minDepth, arrayDepth);
1558 std::vector<SQLUSMALLINT> rowStatus(arrayDepth);
1559 SQLULEN rowsFetched = 0;
1564 auto const restoreFetchState = detail::Finally([
this] {
1565 SQLFreeStmt(m_hStmt, SQL_UNBIND);
1568 SQLSetStmtAttr(m_hStmt, SQL_ATTR_ROW_ARRAY_SIZE, (SQLPOINTER) 1, 0);
1569 SQLSetStmtAttr(m_hStmt, SQL_ATTR_ROW_BIND_TYPE, SQL_BIND_BY_COLUMN, 0);
1570 SQLSetStmtAttr(m_hStmt, SQL_ATTR_ROW_STATUS_PTR,
nullptr, 0);
1571 SQLSetStmtAttr(m_hStmt, SQL_ATTR_ROWS_FETCHED_PTR,
nullptr, 0);
1573 ClearBatchIndicators();
1578 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_ROW_BIND_TYPE, (SQLPOINTER)
sizeof(Record), 0));
1580 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_ROW_ARRAY_SIZE, (SQLPOINTER) arrayDepth, 0));
1581 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_ROW_STATUS_PTR, rowStatus.data(), 0));
1582 RequireSuccess(SQLSetStmtAttr(m_hStmt, SQL_ATTR_ROWS_FETCHED_PTR, &rowsFetched, 0));
1587 std::size_t
const base = out.size();
1588 out.resize(base + arrayDepth);
1589 Record*
const row0 = out.data() + base;
1593 ClearBatchIndicators();
1594 std::array<SQLLEN*, columnCount> indicators {};
1595 SQLUSMALLINT column = 0;
1596 std::size_t bindIndex = 0;
1597 ((indicators[bindIndex++] = BindRowWiseOutputColumn<std::remove_cvref_t<
decltype(accessors(*row0))>>(
1598 ++column, std::addressof(accessors(*row0)),
sizeof(Record), arrayDepth)),
1602 auto const fetchResult = SQLFetchScroll(m_hStmt, SQL_FETCH_NEXT, 0);
1603 if (fetchResult == SQL_NO_DATA)
1610 if (!SQL_SUCCEEDED(fetchResult))
1611 RequireSuccess(fetchResult);
1613 auto const fetched =
static_cast<std::size_t
>(rowsFetched);
1614 SqlLogger::GetLogger().OnFetchRow();
1616 std::size_t finalizeIndex = 0;
1617 (FinalizeRowWiseOutputColumn<std::remove_cvref_t<
decltype(accessors(*row0))>>(
1618 std::addressof(accessors(*row0)),
sizeof(Record), fetched, indicators[finalizeIndex++]),
1621 out.resize(base + fetched);
1622 if (fetched < arrayDepth)
1626 SqlLogger::GetLogger().OnFetchEnd();
1629template <SqlGetColumnNativeType T>
1630inline bool SqlStatement::GetColumn(SQLUSMALLINT column, T* result)
const
1632 if (IsPrefetchActive())
1634 auto const& cursor = PrefetchCursorRef();
1635 auto const row = PrefetchRowInBlock();
1636 RequirePrefetchColumnInRange(cursor, column);
1637 if (cursor.IsCellNull(row, column))
1639 *result = ConvertCell<T>(cursor, row, column);
1642 SQLLEN indicator {};
1643 RequireSuccess(SqlDataBinder<T>::GetColumn(m_hStmt, column, result, &indicator, *
this));
1644 return indicator != SQL_NULL_DATA;
1650 template <
typename T>
1651 concept SqlNullableType = (std::same_as<T, SqlVariant> || IsSpecializationOf<std::optional, T>);
1654 template <
typename T>
1655 struct IsSqlFixedStringSpec: std::false_type
1658 template <std::
size_t N,
typename Char, SqlFixedStringMode Mode>
1659 struct IsSqlFixedStringSpec<SqlFixedString<N, Char, Mode>>: std::true_type
1662 template <
typename T>
1663 concept SqlFixedStringCell = IsSqlFixedStringSpec<std::remove_cvref_t<T>>::value;
1666 template <
typename T>
1667 concept PlainStringCell =
1668 std::same_as<T, std::string> || std::same_as<T, std::u8string> || std::same_as<T, std::u16string>
1669 || std::same_as<T, std::u32string> || std::same_as<T, std::wstring>;
1672 template <
typename T>
1673 struct IsSqlNumericSpec: std::false_type
1676 template <std::
size_t Precision, std::
size_t Scale>
1677 struct IsSqlNumericSpec<SqlNumeric<Precision, Scale>>: std::true_type
1680 template <
typename T>
1681 concept SqlNumericCell = IsSqlNumericSpec<std::remove_cvref_t<T>>::value;
1684 [[nodiscard]]
inline std::u8string_view AsU8View(std::string
const& utf8)
noexcept
1686 return std::u8string_view {
reinterpret_cast<char8_t const*
>(utf8.data()), utf8.size() };
1698 template <SqlFixedStringMode Mode>
1699 inline void TrimFixedStringBytes(std::string& bytes)
noexcept
1701 auto const isTrailingTrimmable = [](
char c)
noexcept {
1704 if constexpr (Mode == SqlFixedStringMode::FIXED_SIZE_RIGHT_TRIMMED)
1705 return c ==
' ' || c ==
'\t' || c ==
'\n' || c ==
'\r' || c ==
'\v' || c ==
'\f';
1709 while (!bytes.empty() && isTrailingTrimmable(bytes.back()))
1719 template <
typename Char>
1720 [[nodiscard]]
inline std::basic_string<Char> DecodeUtf8To(std::string
const& utf8)
1722 if constexpr (std::same_as<Char, char>)
1724 else if constexpr (std::same_as<Char, char8_t>)
1725 return std::u8string { AsU8View(utf8) };
1726 else if constexpr (std::same_as<Char, char16_t>)
1727 return ToUtf16(AsU8View(utf8));
1728 else if constexpr (std::same_as<Char, char32_t>)
1729 return ToUtf32<std::u32string>(AsU8View(utf8));
1737 template <
typename T>
1738 concept StringLikeCell = PlainStringCell<T> || SqlStringInterface<T>;
1743 template <
typename T>
1744 concept PrefetchConvertibleScalar =
1745 std::same_as<T, SqlVariant> || std::same_as<T, SqlDate> || std::same_as<T, SqlDateTime> || std::same_as<T, SqlGuid>
1746 || StringLikeCell<T> || std::is_floating_point_v<T> || std::is_integral_v<T> || std::is_enum_v<T>;
1748 template <
typename T>
1749 struct PrefetchConvertibleOptional: std::false_type
1752 template <
typename U>
1753 struct PrefetchConvertibleOptional<std::optional<U>>: std::bool_constant<PrefetchConvertibleScalar<U>>
1758 template <
typename T>
1759 concept PrefetchConvertible = PrefetchConvertibleScalar<T> || PrefetchConvertibleOptional<T>::value;
1764 template <
typename T>
1765 [[nodiscard]]
inline T ReadTemporalGuidCell(RowArrayCursor
const& cursor, std::size_t row, SQLUSMALLINT column)
1767 using BoundType = RowArrayCursor::BoundType;
1768 auto const boundType = cursor.ColumnBoundType(column);
1769 if constexpr (std::same_as<T, SqlDate>)
1770 return boundType == BoundType::Date ? cursor.GetDate(row, column).value_or(SqlDate {}) : SqlDate {};
1771 else if constexpr (std::same_as<T, SqlDateTime>)
1772 return boundType == BoundType::Timestamp ? cursor.GetTimestamp(row, column).value_or(SqlDateTime {})
1776 if (boundType == BoundType::Guid)
1777 return cursor.GetGuid(row, column).value_or(SqlGuid {});
1778 if (boundType == BoundType::Char || boundType == BoundType::WChar)
1779 return SqlGuid::TryParse(cursor.GetString(row, column).value_or(std::string {})).value_or(SqlGuid {});
1786 template <
typename T>
1787 [[nodiscard]]
inline T ReadNumericCell(RowArrayCursor
const& cursor, std::size_t row, SQLUSMALLINT column)
1789 using BoundType = RowArrayCursor::BoundType;
1790 switch (cursor.ColumnBoundType(column))
1792 case BoundType::Double:
1793 return T { cursor.GetF64(row, column).value_or(0.0) };
1794 case BoundType::Int64:
1795 return T {
static_cast<double>(cursor.GetI64(row, column).value_or(0)) };
1807 [[nodiscard]]
inline std::string RenderCellAsUtf8(RowArrayCursor
const& cursor, std::size_t row, SQLUSMALLINT column)
1809 switch (cursor.ColumnBoundType(column))
1811 case RowArrayCursor::BoundType::Char:
1812 case RowArrayCursor::BoundType::WChar:
1813 return cursor.GetString(row, column).value_or(std::string {});
1814 case RowArrayCursor::BoundType::Int64:
1815 return std::format(
"{}", cursor.GetI64(row, column).value_or(0));
1816 case RowArrayCursor::BoundType::Double:
1817 return std::format(
"{}", cursor.GetF64(row, column).value_or(0.0));
1818 case RowArrayCursor::BoundType::Date:
1819 return std::format(
"{}", cursor.GetDate(row, column).value_or(SqlDate {}));
1820 case RowArrayCursor::BoundType::Timestamp:
1821 return std::format(
"{}", cursor.GetTimestamp(row, column).value_or(SqlDateTime {}));
1822 case RowArrayCursor::BoundType::Guid:
1823 return std::format(
"{}", cursor.GetGuid(row, column).value_or(SqlGuid {}));
1825 return std::string {};
1833 template <
typename T>
1834 [[nodiscard]]
inline T ReadStringLikeCell(RowArrayCursor
const& cursor, std::size_t row, SQLUSMALLINT column)
1836 auto utf8 = RenderCellAsUtf8(cursor, row, column);
1837 if constexpr (SqlFixedStringCell<T>)
1838 TrimFixedStringBytes<T::PostRetrieveOperation>(utf8);
1839 return T { DecodeUtf8To<typename T::value_type>(utf8) };
1845 template <
typename T>
1846 [[nodiscard]]
inline T ReadArithmeticCell(RowArrayCursor
const& cursor, std::size_t row, SQLUSMALLINT column)
1848 using BoundType = RowArrayCursor::BoundType;
1849 switch (cursor.ColumnBoundType(column))
1851 case BoundType::Int64:
1852 return static_cast<T
>(cursor.GetI64(row, column).value_or(0));
1853 case BoundType::Double:
1854 return static_cast<T
>(cursor.GetF64(row, column).value_or(0.0));
1862template <
typename T>
1863inline T SqlStatement::ConvertCell(RowArrayCursor
const& cursor, std::size_t row, SQLUSMALLINT column)
const
1868 if constexpr (std::same_as<T, SqlVariant>)
1869 return MakePrefetchVariantCell(cursor, row, column);
1870 else if constexpr (IsSpecializationOf<std::optional, T>)
1872 if (cursor.IsCellNull(row, column))
1873 return std::nullopt;
1874 return T { ConvertCell<typename T::value_type>(cursor, row, column) };
1876 else if constexpr (std::same_as<T, SqlDate> || std::same_as<T, SqlDateTime> || std::same_as<T, SqlGuid>)
1877 return detail::ReadTemporalGuidCell<T>(cursor, row, column);
1878 else if constexpr (detail::SqlNumericCell<T>)
1879 return detail::ReadNumericCell<T>(cursor, row, column);
1880 else if constexpr (detail::StringLikeCell<T>)
1881 return detail::ReadStringLikeCell<T>(cursor, row, column);
1882 else if constexpr (std::is_floating_point_v<T> || std::is_integral_v<T> || std::is_enum_v<T>)
1883 return detail::ReadArithmeticCell<T>(cursor, row, column);
1891template <SqlOutputColumnBinder T>
1892inline void SqlStatement::RecordPrefetchOutputColumn(SQLUSMALLINT column, T* arg)
1894 auto deferredBind = [
this, column, arg] {
1895 RequireIndicators();
1896 RequireSuccess(SqlDataBinder<T>::OutputColumn(m_hStmt, column, arg, GetIndicatorForColumn(column), *
this));
1898 if constexpr (detail::PrefetchConvertible<T>)
1900 RecordPrefetchColumn(
1902 [
this, column, arg] { *arg = ConvertCell<T>(PrefetchCursorRef(), PrefetchRowInBlock(), column); },
1903 std::move(deferredBind));
1909 RecordPrefetchColumn(column, {}, std::move(deferredBind));
1910 MarkPrefetchBindingUnsupported();
1914template <SqlGetColumnNativeType T>
1915inline T SqlStatement::GetColumn(SQLUSMALLINT column)
const
1917 if (IsPrefetchActive())
1919 auto const& cursor = PrefetchCursorRef();
1920 auto const row = PrefetchRowInBlock();
1921 RequirePrefetchColumnInRange(cursor, column);
1922 if constexpr (!detail::SqlNullableType<T>)
1923 if (cursor.IsCellNull(row, column))
1924 throw std::runtime_error {
"Column value is NULL" };
1925 return ConvertCell<T>(cursor, row, column);
1928 SQLLEN indicator {};
1933 ZoneScopedN(
"SqlStatement::ColumnGetData");
1934 RequireSuccess(SqlDataBinder<T>::GetColumn(m_hStmt, column, &result, &indicator, *
this));
1936 if constexpr (!detail::SqlNullableType<T>)
1937 if (indicator == SQL_NULL_DATA)
1938 throw std::runtime_error {
"Column value is NULL" };
1942template <SqlGetColumnNativeType T>
1943inline std::optional<T> SqlStatement::GetNullableColumn(SQLUSMALLINT column)
const
1945 if (IsPrefetchActive())
1947 auto const& cursor = PrefetchCursorRef();
1948 auto const row = PrefetchRowInBlock();
1949 RequirePrefetchColumnInRange(cursor, column);
1950 if (cursor.IsCellNull(row, column))
1951 return std::nullopt;
1952 return ConvertCell<T>(cursor, row, column);
1955 SQLLEN indicator {};
1957 ZoneScopedN(
"SqlStatement::ColumnGetData");
1958 RequireSuccess(SqlDataBinder<T>::GetColumn(m_hStmt, column, &result, &indicator, *
this));
1960 if (indicator == SQL_NULL_DATA)
1961 return std::nullopt;
1962 return { std::move(result) };
1965template <SqlGetColumnNativeType T>
1966T SqlStatement::GetColumnOr(SQLUSMALLINT column, T&& defaultValue)
const
1968 return GetNullableColumn<T>(column).value_or(std::forward<T>(defaultValue));
1972 std::source_location location)
1974 return ExecuteDirect(query.ToSql(), location);
1977template <
typename Callable>
1978 requires std::invocable<Callable, SqlMigrationQueryBuilder&>
1979void SqlStatement::MigrateDirect(Callable
const& callable, std::source_location location)
1981 ZoneScopedN(
"SqlStatement::MigrateDirect");
1983 callable(migration);
1984 auto const queries = migration.GetPlan().ToSql();
1985 ZoneValue(queries.size());
1986 for (
auto const& query: queries)
1988 [[maybe_unused]]
auto cursor = ExecuteDirect(query, location);
1992template <
typename T>
1993 requires(!std::same_as<T, SqlVariant>)
1994inline std::optional<T> SqlStatement::ExecuteDirectScalar(std::string_view
const& query, std::source_location location)
1996 auto cursor = ExecuteDirect(query, location);
1997 RequireSuccess(FetchRow());
1998 return GetNullableColumn<T>(1);
2001template <
typename T>
2002 requires(std::same_as<T, SqlVariant>)
2003inline T SqlStatement::ExecuteDirectScalar(std::string_view
const& query, std::source_location location)
2005 auto cursor = ExecuteDirect(query, location);
2006 RequireSuccess(FetchRow());
2007 if (
auto result = GetNullableColumn<T>(1); result.has_value())
2012template <
typename T>
2013 requires(!std::same_as<T, SqlVariant>)
2014inline std::optional<T> SqlStatement::ExecuteDirectScalar(
SqlQueryObject auto const& query, std::source_location location)
2016 return ExecuteDirectScalar<T>(query.ToSql(), location);
2019template <
typename T>
2020 requires(std::same_as<T, SqlVariant>)
2021inline T SqlStatement::ExecuteDirectScalar(
SqlQueryObject auto const& query, std::source_location location)
2023 return ExecuteDirectScalar<T>(query.ToSql(), location);
2026inline LIGHTWEIGHT_FORCE_INLINE
void SqlStatement::CloseCursor() noexcept
2031 ResetPrefetchState();
2045 auto const rc = SQLMoreResults(m_hStmt);
2046 if (rc == SQL_NO_DATA || !SQL_SUCCEEDED(rc))
2049 SQLFreeStmt(m_hStmt, SQL_CLOSE);
2050 SqlLogger::GetLogger().OnFetchEnd();
Thrown by RowArrayCursor's constructor when the executed result set cannot be fixed-stride array-boun...
A cursor that fetches result rows in bulk (ODBC row-array binding) for fast column reads.
LIGHTWEIGHT_API SQLSMALLINT ColumnSqlType(SQLUSMALLINT column) const
The raw SQL data type the driver reported for a result column (the SQL_* value from SQLDescribeCol),...
LIGHTWEIGHT_API RowArrayCursor(SqlStatement &stmt, std::size_t arrayDepth)
Constructs the cursor on a statement whose query has already been executed. Inspects the result colum...
LIGHTWEIGHT_API BoundType ColumnBoundType(SQLUSMALLINT column) const
The bound representation chosen for a result column.
LIGHTWEIGHT_API ~RowArrayCursor() noexcept
Resets the statement's row-array attributes and unbinds the columns so the handle can be safely reuse...
LIGHTWEIGHT_API bool IsCellNull(std::size_t rowInBatch, SQLUSMALLINT column) const
Whether a cell in the last fetched block is SQL NULL.
BoundType
How a result column is bound for bulk fetch (the canonical fixed-stride C representation chosen from ...
Represents a connection to a SQL database.
LIGHTWEIGHT_API bool IsAlive() const noexcept
Tests if the connection is still active.
Query builder for building SQL migration queries.
API Entry point for building SQL queries.
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.
LIGHTWEIGHT_FORCE_INLINE void FetchAllRowWise(std::vector< Record > &out, std::size_t arrayDepth, ColumnAccessors const &... accessors)
Fast bulk retrieval: materializes this result set into out via native ODBC row-wise array fetch....
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 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...
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 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 SqlStatement(SqlConnection &relatedConnection)
Construct a new SqlStatement object, using the given connection.
Whether V's binder provides a row-wise batch entry point (BatchRowWiseInputParameter).
A value type that can be bound in a native ODBC row-wise parameter array (fixed-width,...
A std::optional column that can be bound zero-copy in a native row-wise batch: the contained type is ...
Represents an SQL query object, that provides a ToSql() method.
A column value type usable on the native row-wise batch path — either a row-bindable fixed value or a...
A column usable on the native row-wise array-FETCH fast path. Intentionally identical to the write-si...
constexpr void EnumerateRecordMembers(Record &record, Callable &&callable)
Invokes callable as callable<I>(member) for each member of record.
constexpr auto SqlNullValue
std::u16string ToUtf16(std::basic_string_view< T > const u32InputString)
LIGHTWEIGHT_API std::wstring ToStdWideString(std::u8string_view u8InputString)
Represents an ODBC SQL error.
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.